Skip to content

Commit

Permalink
test(web): CameraImage (basic)
Browse files Browse the repository at this point in the history
Testing Image and Canvas calls requires a lot of heavy dependencies, so this skips that part of the tests
  • Loading branch information
paularmstrong authored and blakeblackshear committed Feb 20, 2021
1 parent a202c44 commit 1aa9a7a
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 38 deletions.
19 changes: 3 additions & 16 deletions web/src/components/CameraImage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,19 @@ import { h } from 'preact';
import ActivityIndicator from './ActivityIndicator';
import { useApiHost, useConfig } from '../api';
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { useResizeObserver } from '../hooks';

export default function CameraImage({ camera, onload, searchParams = '' }) {
const { data: config } = useConfig();
const apiHost = useApiHost();
const [availableWidth, setAvailableWidth] = useState(0);
const [hasLoaded, setHasLoaded] = useState(false);
const containerRef = useRef(null);
const canvasRef = useRef(null);
const [{ width: availableWidth }] = useResizeObserver(containerRef);

const { name, width, height } = config.cameras[camera];
const aspectRatio = width / height;

const resizeObserver = useMemo(() => {
return new ResizeObserver((entries) => {
window.requestAnimationFrame(() => {
if (Array.isArray(entries) && entries.length) {
setAvailableWidth(entries[0].contentRect.width);
}
});
});
}, []);

useEffect(() => {
resizeObserver.observe(containerRef.current);
}, [resizeObserver, containerRef]);

const scaledHeight = useMemo(() => Math.min(Math.ceil(availableWidth / aspectRatio), height), [
availableWidth,
aspectRatio,
Expand Down Expand Up @@ -57,7 +44,7 @@ export default function CameraImage({ camera, onload, searchParams = '' }) {

return (
<div className="relative w-full" ref={containerRef}>
<canvas height={scaledHeight} ref={canvasRef} width={scaledWidth} />
<canvas data-testid="cameraimage-canvas" height={scaledHeight} ref={canvasRef} width={scaledWidth} />
{!hasLoaded ? (
<div className="absolute inset-0 flex justify-center" style={`height: ${scaledHeight}px`}>
<ActivityIndicator />
Expand Down
36 changes: 36 additions & 0 deletions web/src/components/__tests__/CameraImage.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { h } from 'preact';
import * as Api from '../../api';
import * as Hooks from '../../hooks';
import CameraImage from '../CameraImage';
import { render, screen } from '@testing-library/preact';

jest.mock('../../api/baseUrl');

describe('CameraImage', () => {
beforeEach(() => {
jest.spyOn(Api, 'useConfig').mockImplementation(() => {
return { data: { cameras: { front: { name: 'front', width: 1280, height: 720 } } } };
});
jest.spyOn(Api, 'useApiHost').mockReturnValue('http:https://base-url.local:5000');
jest.spyOn(Hooks, 'useResizeObserver').mockImplementation(() => [{ width: 0 }]);
});

test('renders an activity indicator while loading', async () => {
render(<CameraImage camera="front" />);
expect(screen.queryByLabelText('Loading…')).toBeInTheDocument();
});

test('creates a scaled canvas using the available width & height, preserving camera aspect ratio', async () => {
jest.spyOn(Hooks, 'useResizeObserver').mockReturnValueOnce([{ width: 720 }]);

render(<CameraImage camera="front" />);
expect(screen.queryByLabelText('Loading…')).toBeInTheDocument();
expect(screen.queryByTestId('cameraimage-canvas')).toMatchInlineSnapshot(`
<canvas
data-testid="cameraimage-canvas"
height="405"
width="720"
/>
`);
});
});
30 changes: 30 additions & 0 deletions web/src/hooks/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useEffect, useMemo, useState } from 'preact/hooks';

export function useResizeObserver(...refs) {
const [dimensions, setDimensions] = useState(
new Array(refs.length).fill({ width: 0, height: 0, x: -Infinity, y: -Infinity })
);
const resizeObserver = useMemo(
() =>
new ResizeObserver((entries) => {
window.requestAnimationFrame(() => {
setDimensions(entries.map((entry) => entry.contentRect));
});
}),
[]
);

useEffect(() => {
refs.forEach((ref) => {
resizeObserver.observe(ref.current);
});

return () => {
refs.forEach((ref) => {
resizeObserver.unobserve(ref.current);
});
};
}, [refs, resizeObserver]);

return dimensions;
}
26 changes: 4 additions & 22 deletions web/src/routes/CameraMap.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import Card from '../components/Card.jsx';
import Button from '../components/Button.jsx';
import Heading from '../components/Heading.jsx';
import Switch from '../components/Switch.jsx';
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
import { useResizeObserver } from '../hooks';
import { useCallback, useMemo, useRef, useState } from 'preact/hooks';
import { useApiHost, useConfig } from '../api';

export default function CameraMasks({ camera, url }) {
const { data: config } = useConfig();
const apiHost = useApiHost();
const imageRef = useRef(null);
const [imageScale, setImageScale] = useState(1);
const [snap, setSnap] = useState(true);

const cameraConfig = config.cameras[camera];
Expand All @@ -22,26 +22,8 @@ export default function CameraMasks({ camera, url }) {
zones,
} = cameraConfig;

const resizeObserver = useMemo(
() =>
new ResizeObserver((entries) => {
window.requestAnimationFrame(() => {
if (Array.isArray(entries) && entries.length) {
const scaledWidth = entries[0].contentRect.width;
const scale = scaledWidth / width;
setImageScale(scale);
}
});
}),
[width, setImageScale]
);

useEffect(() => {
if (!imageRef.current) {
return;
}
resizeObserver.observe(imageRef.current);
}, [resizeObserver, imageRef]);
const [{ width: scaledWidth }] = useResizeObserver(imageRef);
const imageScale = scaledWidth / width;

const [motionMaskPoints, setMotionMaskPoints] = useState(
Array.isArray(motionMask)
Expand Down

0 comments on commit 1aa9a7a

Please sign in to comment.