test(web): Switch (and add label back in)

This commit is contained in:
Paul Armstrong 2021-02-12 16:06:51 -08:00 committed by Blake Blackshear
parent f70fb12c3d
commit 5eaf8a5448
5 changed files with 138 additions and 71 deletions

View File

@ -1,7 +1,7 @@
import { h } from 'preact'; import { h } from 'preact';
import { useCallback, useState } from 'preact/hooks'; import { useCallback, useState } from 'preact/hooks';
export default function Switch({ checked, id, onChange }) { export default function Switch({ checked, id, onChange, label, labelPosition = 'before' }) {
const [isFocused, setFocused] = useState(false); const [isFocused, setFocused] = useState(false);
const handleChange = useCallback( const handleChange = useCallback(
@ -24,15 +24,21 @@ export default function Switch({ checked, id, onChange }) {
return ( return (
<label <label
htmlFor={id} htmlFor={id}
className={`flex items-center justify-center ${onChange ? 'cursor-pointer' : 'cursor-not-allowed'}`} className={`flex items-center space-x-4 w-full ${onChange ? 'cursor-pointer' : 'cursor-not-allowed'}`}
> >
{label && labelPosition === 'before' ? (
<div data-testid={`${id}-label`} className="inline-flex flex-grow">
{label}
</div>
) : null}
<div <div
onMouseOver={handleFocus} onMouseOver={handleFocus}
onMouseOut={handleBlur} onMouseOut={handleBlur}
className={`w-8 h-5 relative ${!onChange ? 'opacity-60' : ''}`} className={`self-end w-8 h-5 relative ${!onChange ? 'opacity-60' : ''}`}
> >
<div className="relative overflow-hidden"> <div className="relative overflow-hidden">
<input <input
data-testid={`${id}-input`}
className="absolute left-48" className="absolute left-48"
onBlur={handleBlur} onBlur={handleBlur}
onFocus={handleFocus} onFocus={handleFocus}
@ -55,6 +61,11 @@ export default function Switch({ checked, id, onChange }) {
style={checked ? 'transform: translateX(100%);' : 'transform: translateX(0%);'} style={checked ? 'transform: translateX(100%);' : 'transform: translateX(0%);'}
/> />
</div> </div>
{label && labelPosition !== 'before' ? (
<div data-testid={`${id}-label`} class="inline-flex flex-grow">
{label}
</div>
) : null}
</label> </label>
); );
} }

View File

@ -0,0 +1,47 @@
import { h } from 'preact';
import Switch from '../Switch';
import { fireEvent, render, screen } from '@testing-library/preact';
describe('Switch', () => {
test('renders a hidden checkbox', async () => {
render(
<div>
<Switch id="unchecked-switch" />
<Switch id="checked-switch" checked={true} />
</div>
);
const unchecked = screen.queryByTestId('unchecked-switch-input');
expect(unchecked).toHaveAttribute('type', 'checkbox');
expect(unchecked).not.toBeChecked();
const checked = screen.queryByTestId('checked-switch-input');
expect(checked).toHaveAttribute('type', 'checkbox');
expect(checked).toBeChecked();
});
test('calls onChange callback when checked/unchecked', async () => {
const handleChange = jest.fn();
const { rerender } = render(<Switch id="check" onChange={handleChange} />);
fireEvent.change(screen.queryByTestId('check-input'), { checked: true });
expect(handleChange).toHaveBeenCalledWith('check', true);
rerender(<Switch id="check" onChange={handleChange} checked />);
fireEvent.change(screen.queryByTestId('check-input'), { checked: false });
expect(handleChange).toHaveBeenCalledWith('check', false);
});
test('renders a label before', async () => {
render(<Switch id="check" label="This is the label" />);
const items = screen.queryAllByTestId(/check-.+/);
expect(items[0]).toHaveTextContent('This is the label');
expect(items[1]).toHaveAttribute('data-testid', 'check-input');
});
test('renders a label after', async () => {
render(<Switch id="check" label="This is the label" labelPosition="after" />);
const items = screen.queryAllByTestId(/check-.+/);
expect(items[0]).toHaveAttribute('data-testid', 'check-input');
expect(items[1]).toHaveTextContent('This is the label');
});
});

View File

@ -45,30 +45,36 @@ export default function Camera({ camera }) {
const optionContent = showSettings ? ( const optionContent = showSettings ? (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"> <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
<div className="flex space-x-3"> <Switch
<Switch checked={options['bbox']} id="bbox" onChange={handleSetOption} /> checked={options['bbox']}
<span className="inline-flex">Bounding box</span> id="bbox"
</div> onChange={handleSetOption}
<div className="flex space-x-3"> label="Bounding box"
<Switch checked={options['timestamp']} id="timestamp" onChange={handleSetOption} /> labelPosition="after"
<span className="inline-flex">Timestamp</span> />
</div> <Switch
<div className="flex space-x-3"> checked={options['timestamp']}
<Switch checked={options['zones']} id="zones" onChange={handleSetOption} /> id="timestamp"
<span className="inline-flex">Zones</span> onChange={handleSetOption}
</div> label="Timestamp"
<div className="flex space-x-3"> labelPosition="after"
<Switch checked={options['mask']} id="mask" onChange={handleSetOption} /> />
<span className="inline-flex">Masks</span> <Switch checked={options['zones']} id="zones" onChange={handleSetOption} label="Zones" labelPosition="after" />
</div> <Switch checked={options['mask']} id="mask" onChange={handleSetOption} label="Masks" labelPosition="after" />
<div className="flex space-x-3"> <Switch
<Switch checked={options['motion']} id="motion" onChange={handleSetOption} /> checked={options['motion']}
<span className="inline-flex">Motion boxes</span> id="motion"
</div> onChange={handleSetOption}
<div className="flex space-x-3"> label="Motion boxes"
<Switch checked={options['regions']} id="regions" onChange={handleSetOption} /> labelPosition="after"
<span className="inline-flex">Regions</span> />
</div> <Switch
checked={options['regions']}
id="regions"
onChange={handleSetOption}
label="Regions"
labelPosition="after"
/>
<Link href={`/cameras/${camera}/editor`}>Mask & Zone creator</Link> <Link href={`/cameras/${camera}/editor`}>Mask & Zone creator</Link>
</div> </div>
) : null; ) : null;

View File

@ -222,8 +222,8 @@ ${Object.keys(objectMaskPoints)
height={height} height={height}
/> />
</div> </div>
<div className="flex space-x-4"> <div className="max-w-xs">
<span>Snap to edges</span> <Switch checked={snap} onChange={handleChangeSnap} /> <Switch checked={snap} label="Snap to edges" labelPosition="after" onChange={handleChangeSnap} />
</div> </div>
</div> </div>

View File

@ -9,7 +9,7 @@ import TextField from '../components/TextField';
import { useCallback, useState } from 'preact/hooks'; import { useCallback, useState } from 'preact/hooks';
export default function StyleGuide() { export default function StyleGuide() {
const [switches, setSwitches] = useState({ 0: false, 1: true }); const [switches, setSwitches] = useState({ 0: false, 1: true, 2: false, 3: false });
const handleSwitch = useCallback( const handleSwitch = useCallback(
(id, checked) => { (id, checked) => {
@ -53,23 +53,26 @@ export default function StyleGuide() {
</div> </div>
<Heading size="md">Switch</Heading> <Heading size="md">Switch</Heading>
<div className="flex"> <div className="flex-col space-y-4 max-w-4xl">
<div> <Switch label="Disabled, off" labelPosition="after" />
<p>Disabled, off</p> <Switch label="Disabled, on" labelPosition="after" checked />
<Switch /> <Switch
</div> label="Enabled, (off initial)"
<div> labelPosition="after"
<p>Disabled, on</p> checked={switches[0]}
<Switch checked /> id={0}
</div> onChange={handleSwitch}
<div> />
<p>Enabled, (off initial)</p> <Switch
<Switch checked={switches[0]} id={0} onChange={handleSwitch} label="Default" /> label="Enabled, (on initial)"
</div> labelPosition="after"
<div> checked={switches[1]}
<p>Enabled, (on initial)</p> id={1}
<Switch checked={switches[1]} id={1} onChange={handleSwitch} label="Default" /> onChange={handleSwitch}
</div> />
<Switch checked={switches[2]} id={2} label="Label before" onChange={handleSwitch} />
<Switch checked={switches[3]} id={3} label="Label after" labelPosition="after" onChange={handleSwitch} />
</div> </div>
<Heading size="md">Select</Heading> <Heading size="md">Select</Heading>