switch to vite

This commit is contained in:
Blake Blackshear
2022-03-05 22:16:31 -06:00
parent 1c9ba11e07
commit 95fe62e141
82 changed files with 4651 additions and 32812 deletions

View File

@@ -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>

View File

@@ -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);
},

View File

@@ -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&timestamp=1');

View File

@@ -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);

View File

@@ -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' },
};

View File

@@ -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...',
}));