mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-02-09 00:17:00 +01:00
test(web): Switch (and add label back in)
This commit is contained in:
parent
f70fb12c3d
commit
5eaf8a5448
@ -1,7 +1,7 @@
|
||||
import { h } from 'preact';
|
||||
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 handleChange = useCallback(
|
||||
@ -24,15 +24,21 @@ export default function Switch({ checked, id, onChange }) {
|
||||
return (
|
||||
<label
|
||||
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
|
||||
onMouseOver={handleFocus}
|
||||
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">
|
||||
<input
|
||||
data-testid={`${id}-input`}
|
||||
className="absolute left-48"
|
||||
onBlur={handleBlur}
|
||||
onFocus={handleFocus}
|
||||
@ -55,6 +61,11 @@ export default function Switch({ checked, id, onChange }) {
|
||||
style={checked ? 'transform: translateX(100%);' : 'transform: translateX(0%);'}
|
||||
/>
|
||||
</div>
|
||||
{label && labelPosition !== 'before' ? (
|
||||
<div data-testid={`${id}-label`} class="inline-flex flex-grow">
|
||||
{label}
|
||||
</div>
|
||||
) : null}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
47
web/src/components/__tests__/Switch.test.jsx
Normal file
47
web/src/components/__tests__/Switch.test.jsx
Normal 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');
|
||||
});
|
||||
});
|
@ -45,30 +45,36 @@ export default function Camera({ camera }) {
|
||||
|
||||
const optionContent = showSettings ? (
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
<div className="flex space-x-3">
|
||||
<Switch checked={options['bbox']} id="bbox" onChange={handleSetOption} />
|
||||
<span className="inline-flex">Bounding box</span>
|
||||
</div>
|
||||
<div className="flex space-x-3">
|
||||
<Switch checked={options['timestamp']} id="timestamp" onChange={handleSetOption} />
|
||||
<span className="inline-flex">Timestamp</span>
|
||||
</div>
|
||||
<div className="flex space-x-3">
|
||||
<Switch checked={options['zones']} id="zones" onChange={handleSetOption} />
|
||||
<span className="inline-flex">Zones</span>
|
||||
</div>
|
||||
<div className="flex space-x-3">
|
||||
<Switch checked={options['mask']} id="mask" onChange={handleSetOption} />
|
||||
<span className="inline-flex">Masks</span>
|
||||
</div>
|
||||
<div className="flex space-x-3">
|
||||
<Switch checked={options['motion']} id="motion" onChange={handleSetOption} />
|
||||
<span className="inline-flex">Motion boxes</span>
|
||||
</div>
|
||||
<div className="flex space-x-3">
|
||||
<Switch checked={options['regions']} id="regions" onChange={handleSetOption} />
|
||||
<span className="inline-flex">Regions</span>
|
||||
</div>
|
||||
<Switch
|
||||
checked={options['bbox']}
|
||||
id="bbox"
|
||||
onChange={handleSetOption}
|
||||
label="Bounding box"
|
||||
labelPosition="after"
|
||||
/>
|
||||
<Switch
|
||||
checked={options['timestamp']}
|
||||
id="timestamp"
|
||||
onChange={handleSetOption}
|
||||
label="Timestamp"
|
||||
labelPosition="after"
|
||||
/>
|
||||
<Switch checked={options['zones']} id="zones" onChange={handleSetOption} label="Zones" labelPosition="after" />
|
||||
<Switch checked={options['mask']} id="mask" onChange={handleSetOption} label="Masks" labelPosition="after" />
|
||||
<Switch
|
||||
checked={options['motion']}
|
||||
id="motion"
|
||||
onChange={handleSetOption}
|
||||
label="Motion boxes"
|
||||
labelPosition="after"
|
||||
/>
|
||||
<Switch
|
||||
checked={options['regions']}
|
||||
id="regions"
|
||||
onChange={handleSetOption}
|
||||
label="Regions"
|
||||
labelPosition="after"
|
||||
/>
|
||||
<Link href={`/cameras/${camera}/editor`}>Mask & Zone creator</Link>
|
||||
</div>
|
||||
) : null;
|
||||
|
@ -29,8 +29,8 @@ export default function CameraMasks({ camera, url }) {
|
||||
Array.isArray(motionMask)
|
||||
? motionMask.map((mask) => getPolylinePoints(mask))
|
||||
: motionMask
|
||||
? [getPolylinePoints(motionMask)]
|
||||
: []
|
||||
? [getPolylinePoints(motionMask)]
|
||||
: []
|
||||
);
|
||||
|
||||
const [zonePoints, setZonePoints] = useState(
|
||||
@ -44,8 +44,8 @@ export default function CameraMasks({ camera, url }) {
|
||||
[name]: Array.isArray(objectFilters[name].mask)
|
||||
? objectFilters[name].mask.map((mask) => getPolylinePoints(mask))
|
||||
: objectFilters[name].mask
|
||||
? [getPolylinePoints(objectFilters[name].mask)]
|
||||
: [],
|
||||
? [getPolylinePoints(objectFilters[name].mask)]
|
||||
: [],
|
||||
}),
|
||||
{}
|
||||
)
|
||||
@ -128,11 +128,11 @@ ${motionMaskPoints.map((mask, i) => ` - ${polylinePointsToPolyline(mask)}`)
|
||||
const handleCopyZones = useCallback(async () => {
|
||||
await window.navigator.clipboard.writeText(` zones:
|
||||
${Object.keys(zonePoints)
|
||||
.map(
|
||||
(zoneName) => ` ${zoneName}:
|
||||
.map(
|
||||
(zoneName) => ` ${zoneName}:
|
||||
coordinates: ${polylinePointsToPolyline(zonePoints[zoneName])}`
|
||||
)
|
||||
.join('\n')}`);
|
||||
)
|
||||
.join('\n')}`);
|
||||
}, [zonePoints]);
|
||||
|
||||
// Object methods
|
||||
@ -164,14 +164,14 @@ ${Object.keys(zonePoints)
|
||||
await window.navigator.clipboard.writeText(` objects:
|
||||
filters:
|
||||
${Object.keys(objectMaskPoints)
|
||||
.map((objectName) =>
|
||||
objectMaskPoints[objectName].length
|
||||
? ` ${objectName}:
|
||||
.map((objectName) =>
|
||||
objectMaskPoints[objectName].length
|
||||
? ` ${objectName}:
|
||||
mask: ${polylinePointsToPolyline(objectMaskPoints[objectName])}`
|
||||
: ''
|
||||
)
|
||||
.filter(Boolean)
|
||||
.join('\n')}`);
|
||||
: ''
|
||||
)
|
||||
.filter(Boolean)
|
||||
.join('\n')}`);
|
||||
}, [objectMaskPoints]);
|
||||
|
||||
const handleAddToObjectMask = useCallback(
|
||||
@ -222,8 +222,8 @@ ${Object.keys(objectMaskPoints)
|
||||
height={height}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex space-x-4">
|
||||
<span>Snap to edges</span> <Switch checked={snap} onChange={handleChangeSnap} />
|
||||
<div className="max-w-xs">
|
||||
<Switch checked={snap} label="Snap to edges" labelPosition="after" onChange={handleChangeSnap} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -360,15 +360,15 @@ function EditableMask({ onChange, points, scale, snap, width, height }) {
|
||||
{!scaledPoints
|
||||
? null
|
||||
: scaledPoints.map(([x, y], i) => (
|
||||
<PolyPoint
|
||||
boundingRef={boundingRef}
|
||||
index={i}
|
||||
onMove={handleMovePoint}
|
||||
onRemove={handleRemovePoint}
|
||||
x={x + MaskInset}
|
||||
y={y + MaskInset}
|
||||
/>
|
||||
))}
|
||||
<PolyPoint
|
||||
boundingRef={boundingRef}
|
||||
index={i}
|
||||
onMove={handleMovePoint}
|
||||
onRemove={handleRemovePoint}
|
||||
x={x + MaskInset}
|
||||
y={y + MaskInset}
|
||||
/>
|
||||
))}
|
||||
<div className="absolute inset-0 right-0 bottom-0" onClick={handleAddPoint} ref={boundingRef} />
|
||||
<svg
|
||||
width="100%"
|
||||
|
@ -9,7 +9,7 @@ import TextField from '../components/TextField';
|
||||
import { useCallback, useState } from 'preact/hooks';
|
||||
|
||||
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(
|
||||
(id, checked) => {
|
||||
@ -53,23 +53,26 @@ export default function StyleGuide() {
|
||||
</div>
|
||||
|
||||
<Heading size="md">Switch</Heading>
|
||||
<div className="flex">
|
||||
<div>
|
||||
<p>Disabled, off</p>
|
||||
<Switch />
|
||||
</div>
|
||||
<div>
|
||||
<p>Disabled, on</p>
|
||||
<Switch checked />
|
||||
</div>
|
||||
<div>
|
||||
<p>Enabled, (off initial)</p>
|
||||
<Switch checked={switches[0]} id={0} onChange={handleSwitch} label="Default" />
|
||||
</div>
|
||||
<div>
|
||||
<p>Enabled, (on initial)</p>
|
||||
<Switch checked={switches[1]} id={1} onChange={handleSwitch} label="Default" />
|
||||
</div>
|
||||
<div className="flex-col space-y-4 max-w-4xl">
|
||||
<Switch label="Disabled, off" labelPosition="after" />
|
||||
<Switch label="Disabled, on" labelPosition="after" checked />
|
||||
<Switch
|
||||
label="Enabled, (off initial)"
|
||||
labelPosition="after"
|
||||
checked={switches[0]}
|
||||
id={0}
|
||||
onChange={handleSwitch}
|
||||
/>
|
||||
<Switch
|
||||
label="Enabled, (on initial)"
|
||||
labelPosition="after"
|
||||
checked={switches[1]}
|
||||
id={1}
|
||||
onChange={handleSwitch}
|
||||
/>
|
||||
|
||||
<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>
|
||||
|
||||
<Heading size="md">Select</Heading>
|
||||
|
Loading…
Reference in New Issue
Block a user