forked from Github/frigate
switch to vite
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { h, Fragment } from 'preact';
|
||||
import AutoUpdatingCameraImage from '../components/AutoUpdatingCameraImage';
|
||||
import ActivityIndicator from '../components/ActivityIndicator';
|
||||
import JSMpegPlayer from '../components/JSMpegPlayer';
|
||||
import Button from '../components/Button';
|
||||
import Card from '../components/Card';
|
||||
@@ -22,7 +23,9 @@ export default function Camera({ camera }) {
|
||||
const [viewMode, setViewMode] = useState('live');
|
||||
|
||||
const cameraConfig = config?.cameras[camera];
|
||||
const liveWidth = Math.round(cameraConfig.live.height * (cameraConfig.detect.width / cameraConfig.detect.height));
|
||||
const liveWidth = cameraConfig
|
||||
? Math.round(cameraConfig.live.height * (cameraConfig.detect.width / cameraConfig.detect.height))
|
||||
: 0;
|
||||
const [options, setOptions] = usePersistence(`${camera}-feed`, emptyObject);
|
||||
|
||||
const handleSetOption = useCallback(
|
||||
@@ -48,6 +51,10 @@ export default function Camera({ camera }) {
|
||||
setShowSettings(!showSettings);
|
||||
}, [showSettings, setShowSettings]);
|
||||
|
||||
if (!cameraConfig) {
|
||||
return <ActivityIndicator />;
|
||||
}
|
||||
|
||||
const optionContent = showSettings ? (
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
<Switch
|
||||
@@ -93,8 +100,7 @@ export default function Camera({ camera }) {
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
else if (viewMode === 'debug') {
|
||||
} else if (viewMode === 'debug') {
|
||||
player = (
|
||||
<Fragment>
|
||||
<div>
|
||||
|
||||
@@ -67,13 +67,13 @@ export default function Events({ path, ...props }) {
|
||||
|
||||
const { data: config } = useSWR('config');
|
||||
|
||||
const cameras = useMemo(() => Object.keys(config.cameras), [config]);
|
||||
const cameras = useMemo(() => Object.keys(config?.cameras || {}), [config]);
|
||||
|
||||
const zones = useMemo(
|
||||
() =>
|
||||
Object.values(config.cameras)
|
||||
Object.values(config?.cameras || {})
|
||||
.reduce((memo, camera) => {
|
||||
memo = memo.concat(Object.keys(camera.zones));
|
||||
memo = memo.concat(Object.keys(camera?.zones || {}));
|
||||
return memo;
|
||||
}, [])
|
||||
.filter((value, i, self) => self.indexOf(value) === i),
|
||||
@@ -81,11 +81,11 @@ export default function Events({ path, ...props }) {
|
||||
);
|
||||
|
||||
const labels = useMemo(() => {
|
||||
return Object.values(config.cameras)
|
||||
return Object.values(config?.cameras || {})
|
||||
.reduce((memo, camera) => {
|
||||
memo = memo.concat(camera.objects?.track || []);
|
||||
memo = memo.concat(camera?.objects?.track || []);
|
||||
return memo;
|
||||
}, config.objects?.track || [])
|
||||
}, config?.objects?.track || [])
|
||||
.filter((value, i, self) => self.indexOf(value) === i);
|
||||
}, [config]);
|
||||
|
||||
@@ -123,6 +123,7 @@ export default function Events({ path, ...props }) {
|
||||
|
||||
const handleSelectDateRange = useCallback(
|
||||
(dates) => {
|
||||
console.log(dates);
|
||||
setSearchParams({ ...searchParams, before: dates.before, after: dates.after });
|
||||
setShowDatePicker(false);
|
||||
},
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { h } from 'preact';
|
||||
import * as AutoUpdatingCameraImage from '../../components/AutoUpdatingCameraImage';
|
||||
import * as Api from '../../api';
|
||||
import * as Context from '../../context';
|
||||
import * as Mqtt from '../../api/mqtt';
|
||||
import Camera from '../Camera';
|
||||
import * as JSMpegPlayer from '../../components/JSMpegPlayer';
|
||||
import { fireEvent, render, screen } from '@testing-library/preact';
|
||||
import { fireEvent, render, screen, waitForElementToBeRemoved } from 'testing-library';
|
||||
|
||||
describe('Camera Route', () => {
|
||||
let mockUsePersistence, mockSetOptions;
|
||||
@@ -12,16 +12,13 @@ describe('Camera Route', () => {
|
||||
beforeEach(() => {
|
||||
mockSetOptions = jest.fn();
|
||||
mockUsePersistence = jest.spyOn(Context, 'usePersistence').mockImplementation(() => [{}, mockSetOptions]);
|
||||
jest.spyOn(Api, 'useConfig').mockImplementation(() => ({
|
||||
data: { cameras: { front: { name: 'front', detect: {width: 1280, height: 720}, live: {height: 720}, objects: { track: ['taco', 'cat', 'dog'] } } } },
|
||||
}));
|
||||
jest.spyOn(Api, 'useApiHost').mockImplementation(() => 'http://base-url.local:5000');
|
||||
jest.spyOn(AutoUpdatingCameraImage, 'default').mockImplementation(({ searchParams }) => {
|
||||
return <div data-testid="mock-image">{searchParams.toString()}</div>;
|
||||
});
|
||||
jest.spyOn(JSMpegPlayer, 'default').mockImplementation(() => {
|
||||
return <div data-testid="mock-jsmpeg" />;
|
||||
});
|
||||
jest.spyOn(Mqtt, 'MqttProvider').mockImplementation(({ children }) => children);
|
||||
});
|
||||
|
||||
test('reads camera feed options from persistence', async () => {
|
||||
@@ -39,6 +36,8 @@ describe('Camera Route', () => {
|
||||
|
||||
render(<Camera camera="front" />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading…'));
|
||||
|
||||
fireEvent.click(screen.queryByText('Debug'));
|
||||
fireEvent.click(screen.queryByText('Show Options'));
|
||||
expect(screen.queryByTestId('mock-image')).toHaveTextContent(
|
||||
@@ -48,6 +47,7 @@ describe('Camera Route', () => {
|
||||
|
||||
test('updates camera feed options to persistence', async () => {
|
||||
mockUsePersistence
|
||||
.mockReturnValueOnce([{}, mockSetOptions])
|
||||
.mockReturnValueOnce([{}, mockSetOptions])
|
||||
.mockReturnValueOnce([{}, mockSetOptions])
|
||||
.mockReturnValueOnce([{ bbox: true }, mockSetOptions])
|
||||
@@ -55,13 +55,15 @@ describe('Camera Route', () => {
|
||||
|
||||
render(<Camera camera="front" />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading…'));
|
||||
|
||||
fireEvent.click(screen.queryByText('Debug'));
|
||||
fireEvent.click(screen.queryByText('Show Options'));
|
||||
fireEvent.change(screen.queryByTestId('bbox-input'), { target: { checked: true } });
|
||||
fireEvent.change(screen.queryByTestId('timestamp-input'), { target: { checked: true } });
|
||||
fireEvent.click(screen.queryByText('Hide Options'));
|
||||
|
||||
expect(mockUsePersistence).toHaveBeenCalledTimes(4);
|
||||
expect(mockUsePersistence).toHaveBeenCalledTimes(5);
|
||||
expect(mockSetOptions).toHaveBeenCalledTimes(2);
|
||||
expect(mockSetOptions).toHaveBeenCalledWith({ bbox: true, timestamp: true });
|
||||
expect(screen.queryByTestId('mock-image')).toHaveTextContent('bbox=1×tamp=1');
|
||||
|
||||
@@ -1,30 +1,16 @@
|
||||
import { h } from 'preact';
|
||||
import * as Api from '../../api';
|
||||
import * as CameraImage from '../../components/CameraImage';
|
||||
import * as Mqtt from '../../api/mqtt';
|
||||
import Cameras from '../Cameras';
|
||||
import { fireEvent, render, screen } from '@testing-library/preact';
|
||||
import { fireEvent, render, screen, waitForElementToBeRemoved } from 'testing-library';
|
||||
|
||||
describe('Cameras Route', () => {
|
||||
let useConfigMock;
|
||||
|
||||
beforeEach(() => {
|
||||
useConfigMock = jest.spyOn(Api, 'useConfig').mockImplementation(() => ({
|
||||
data: {
|
||||
cameras: {
|
||||
front: { name: 'front', objects: { track: ['taco', 'cat', 'dog'] }, record: { enabled: true } },
|
||||
side: { name: 'side', objects: { track: ['taco', 'cat', 'dog'] }, record: { enabled: false } },
|
||||
},
|
||||
},
|
||||
status: 'loaded',
|
||||
}));
|
||||
jest.spyOn(Api, 'useApiHost').mockImplementation(() => 'http://base-url.local:5000');
|
||||
jest.spyOn(CameraImage, 'default').mockImplementation(() => <div data-testid="camera-image" />);
|
||||
jest.spyOn(Mqtt, 'useMqtt').mockImplementation(() => ({ value: { payload: 'OFF' }, send: jest.fn() }));
|
||||
});
|
||||
|
||||
test('shows an ActivityIndicator if not yet loaded', async () => {
|
||||
useConfigMock.mockReturnValueOnce(() => ({ status: 'loading' }));
|
||||
render(<Cameras />);
|
||||
expect(screen.queryByLabelText('Loading…')).toBeInTheDocument();
|
||||
});
|
||||
@@ -32,7 +18,7 @@ describe('Cameras Route', () => {
|
||||
test('shows cameras', async () => {
|
||||
render(<Cameras />);
|
||||
|
||||
expect(screen.queryByLabelText('Loading…')).not.toBeInTheDocument();
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading…'));
|
||||
|
||||
expect(screen.queryByText('front')).toBeInTheDocument();
|
||||
expect(screen.queryByText('front').closest('a')).toHaveAttribute('href', '/cameras/front');
|
||||
@@ -44,7 +30,7 @@ describe('Cameras Route', () => {
|
||||
test('shows recordings link', async () => {
|
||||
render(<Cameras />);
|
||||
|
||||
expect(screen.queryByLabelText('Loading…')).not.toBeInTheDocument();
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading…'));
|
||||
|
||||
expect(screen.queryAllByText('Recordings')).toHaveLength(2);
|
||||
});
|
||||
@@ -65,6 +51,8 @@ describe('Cameras Route', () => {
|
||||
|
||||
render(<Cameras />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading…'));
|
||||
|
||||
fireEvent.click(screen.getAllByLabelText('Toggle detect off')[0]);
|
||||
expect(sendDetect).toHaveBeenCalledWith('OFF');
|
||||
expect(sendDetect).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -1,41 +1,19 @@
|
||||
import { h } from 'preact';
|
||||
import * as Api from '../../api';
|
||||
import * as Mqtt from '../../api/mqtt';
|
||||
import Debug from '../Debug';
|
||||
import { render, screen } from '@testing-library/preact';
|
||||
import { render, screen, waitForElementToBeRemoved } from 'testing-library';
|
||||
|
||||
describe('Debug Route', () => {
|
||||
let useStatsMock, useMqttMock;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(Api, 'useConfig').mockImplementation(() => ({
|
||||
data: {
|
||||
service: {
|
||||
version: '0.8.3',
|
||||
},
|
||||
cameras: {
|
||||
front: { name: 'front', objects: { track: ['taco', 'cat', 'dog'] } },
|
||||
side: { name: 'side', objects: { track: ['taco', 'cat', 'dog'] } },
|
||||
},
|
||||
mqtt: {
|
||||
stats_interva: 60,
|
||||
},
|
||||
},
|
||||
status: 'loaded',
|
||||
}));
|
||||
useStatsMock = jest.spyOn(Api, 'useStats').mockImplementation(() => ({ data: statsMock }));
|
||||
useMqttMock = jest.spyOn(Mqtt, 'useMqtt').mockImplementation(() => ({ value: { payload: null } }));
|
||||
});
|
||||
beforeEach(() => {});
|
||||
|
||||
test('shows an ActivityIndicator if stats are null', async () => {
|
||||
useStatsMock.mockReturnValue({ data: null });
|
||||
render(<Debug />);
|
||||
expect(screen.queryByLabelText('Loading…')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('shows stats and config', async () => {
|
||||
render(<Debug />);
|
||||
expect(screen.queryByLabelText('Loading…')).not.toBeInTheDocument();
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading…'));
|
||||
|
||||
expect(screen.queryByTestId('detectors')).toBeInTheDocument();
|
||||
expect(screen.queryByText('coral')).toBeInTheDocument();
|
||||
@@ -47,32 +25,4 @@ describe('Debug Route', () => {
|
||||
expect(screen.queryByText('Config')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: 'Copy to Clipboard' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('updates the stats from mqtt', async () => {
|
||||
const { rerender } = render(<Debug />);
|
||||
expect(useMqttMock).toHaveBeenCalledWith('stats');
|
||||
useMqttMock.mockReturnValue({
|
||||
value: {
|
||||
payload: { ...statsMock, detectors: { coral: { ...statsMock.detectors.coral, inference_speed: 42.4242 } } },
|
||||
},
|
||||
});
|
||||
rerender(<Debug />);
|
||||
|
||||
expect(screen.queryByText('42.4242')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
const statsMock = {
|
||||
detection_fps: 0.0,
|
||||
detectors: { coral: { detection_start: 0.0, inference_speed: 8.94, pid: 52 } },
|
||||
front: { camera_fps: 5.0, capture_pid: 64, detection_fps: 0.0, pid: 54, process_fps: 0.0, skipped_fps: 0.0 },
|
||||
side: {
|
||||
camera_fps: 6.9,
|
||||
capture_pid: 71,
|
||||
detection_fps: 0.0,
|
||||
pid: 60,
|
||||
process_fps: 0.0,
|
||||
skipped_fps: 0.0,
|
||||
},
|
||||
service: { uptime: 34812, version: '0.8.1-d376f6b' },
|
||||
};
|
||||
|
||||
@@ -1,28 +1,9 @@
|
||||
import { h } from 'preact';
|
||||
import * as Api from '../../api';
|
||||
import * as Hooks from '../../hooks';
|
||||
import Events from '../Events';
|
||||
import { render, screen } from '@testing-library/preact';
|
||||
import { render, screen, waitForElementToBeRemoved } from 'testing-library';
|
||||
|
||||
describe('Events Route', () => {
|
||||
let useEventsMock, useIntersectionMock;
|
||||
|
||||
beforeEach(() => {
|
||||
useEventsMock = jest.spyOn(Api, 'useEvents').mockImplementation(() => ({
|
||||
data: null,
|
||||
status: 'loading',
|
||||
}));
|
||||
jest.spyOn(Api, 'useConfig').mockImplementation(() => ({
|
||||
data: {
|
||||
cameras: {
|
||||
front: { name: 'front', objects: { track: ['taco', 'cat', 'dog'] }, zones: [] },
|
||||
side: { name: 'side', objects: { track: ['taco', 'cat', 'dog'] }, zones: [] },
|
||||
},
|
||||
},
|
||||
}));
|
||||
jest.spyOn(Api, 'useApiHost').mockImplementation(() => 'http://localhost:5000');
|
||||
useIntersectionMock = jest.spyOn(Hooks, 'useIntersectionObserver').mockImplementation(() => [null, jest.fn()]);
|
||||
});
|
||||
beforeEach(() => {});
|
||||
|
||||
test('shows an ActivityIndicator if not yet loaded', async () => {
|
||||
render(<Events limit={5} path="/events" />);
|
||||
@@ -30,53 +11,10 @@ describe('Events Route', () => {
|
||||
});
|
||||
|
||||
test('does not show ActivityIndicator after loaded', async () => {
|
||||
useEventsMock.mockReturnValue({ data: mockEvents, status: 'loaded' });
|
||||
render(<Events limit={5} path="/events" />);
|
||||
|
||||
await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading…'));
|
||||
|
||||
expect(screen.queryByLabelText('Loading…')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('loads more when the intersectionObserver fires', async () => {
|
||||
const setIntersectionNode = jest.fn();
|
||||
useIntersectionMock.mockReturnValue([null, setIntersectionNode]);
|
||||
useEventsMock.mockImplementation((searchString) => {
|
||||
if (searchString.includes('before=')) {
|
||||
const params = new URLSearchParams(searchString);
|
||||
const before = parseFloat(params.get('before'));
|
||||
const index = mockEvents.findIndex((el) => el.start_time === before + 0.0001);
|
||||
return { data: mockEvents.slice(index, index + 5), status: 'loaded' };
|
||||
}
|
||||
|
||||
return { data: mockEvents.slice(0, 5), status: 'loaded' };
|
||||
});
|
||||
|
||||
const { rerender } = render(<Events limit={5} path="/events" />);
|
||||
expect(setIntersectionNode).toHaveBeenCalled();
|
||||
expect(useEventsMock).toHaveBeenCalledWith('include_thumbnails=0&limit=5&');
|
||||
expect(screen.queryAllByTestId(/event-\d+/)).toHaveLength(5);
|
||||
|
||||
useIntersectionMock.mockReturnValue([
|
||||
{
|
||||
isIntersecting: true,
|
||||
target: { dataset: { startTime: mockEvents[4].start_time } },
|
||||
},
|
||||
setIntersectionNode,
|
||||
]);
|
||||
rerender(<Events limit={5} path="/events" />);
|
||||
expect(useEventsMock).toHaveBeenCalledWith(
|
||||
`include_thumbnails=0&limit=5&before=${mockEvents[4].start_time - 0.0001}`
|
||||
);
|
||||
expect(screen.queryAllByTestId(/event-\d+/)).toHaveLength(10);
|
||||
});
|
||||
});
|
||||
|
||||
const mockEvents = new Array(12).fill(null).map((v, i) => ({
|
||||
end_time: 1613257337 + i,
|
||||
has_clip: true,
|
||||
has_snapshot: true,
|
||||
id: i,
|
||||
label: 'person',
|
||||
start_time: 1613257326 + i,
|
||||
top_score: Math.random(),
|
||||
zones: ['front_patio'],
|
||||
thumbnail: '/9j/4aa...',
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user