mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
test(web): add unit test framework
This commit is contained in:
parent
daa759cc55
commit
a803ab8577
14
.github/workflows/pull_request.yml
vendored
14
.github/workflows/pull_request.yml
vendored
@ -30,3 +30,17 @@ jobs:
|
||||
- name: Build
|
||||
run: npm run build
|
||||
working-directory: ./web
|
||||
|
||||
web_test:
|
||||
name: Web - Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/setup-node@master
|
||||
with:
|
||||
node-version: 14.x
|
||||
- run: npm install
|
||||
working-directory: ./web
|
||||
- name: Test
|
||||
run: npm run test
|
||||
working-directory: ./web
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,3 +9,4 @@ models
|
||||
frigate/version.py
|
||||
web/build
|
||||
web/node_modules
|
||||
web/coverage
|
||||
|
@ -9,8 +9,14 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
|
||||
extends: ['prettier', 'preact', 'plugin:import/react'],
|
||||
plugins: ['import'],
|
||||
extends: [
|
||||
'prettier',
|
||||
'preact',
|
||||
'plugin:import/react',
|
||||
'plugin:testing-library/recommended',
|
||||
'plugin:jest/recommended',
|
||||
],
|
||||
plugins: ['import', 'testing-library', 'jest'],
|
||||
|
||||
env: {
|
||||
es6: true,
|
||||
@ -113,6 +119,15 @@ module.exports = {
|
||||
'import/no-unresolved': 'error',
|
||||
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
|
||||
'jest/consistent-test-it': ['error', { fn: 'test' }],
|
||||
'jest/no-test-prefixes': 'error',
|
||||
'jest/no-restricted-matchers': [
|
||||
'error',
|
||||
{ toMatchSnapshot: 'Use `toMatchInlineSnapshot()` and ensure you only snapshot very small elements' },
|
||||
],
|
||||
'jest/valid-describe': 'error',
|
||||
'jest/valid-expect-in-promise': 'error',
|
||||
},
|
||||
|
||||
settings: {
|
||||
|
@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
presets: ['@babel/preset-env'],
|
||||
plugins: [['@babel/plugin-transform-react-jsx', { pragma: 'preact.h' }]],
|
||||
plugins: [['@babel/plugin-transform-react-jsx', { pragma: 'h' }]],
|
||||
};
|
||||
|
14
web/config/setupTests.js
Normal file
14
web/config/setupTests.js
Normal file
@ -0,0 +1,14 @@
|
||||
import 'regenerator-runtime/runtime';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: (query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
dispatchEvent: jest.fn(),
|
||||
}),
|
||||
});
|
9
web/jest.config.js
Normal file
9
web/jest.config.js
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
moduleFileExtensions: ['js', 'jsx'],
|
||||
name: 'react-component-benchmark',
|
||||
resetMocks: true,
|
||||
roots: ['<rootDir>'],
|
||||
setupFilesAfterEnv: ['<rootDir>/config/setupTests.js'],
|
||||
testEnvironment: 'jsdom',
|
||||
timers: 'fake',
|
||||
};
|
5059
web/package-lock.json
generated
5059
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,8 @@
|
||||
"prebuild": "rimraf build",
|
||||
"build": "cross-env NODE_ENV=production SNOWPACK_MODE=production SNOWPACK_PUBLIC_API_HOST='' snowpack build",
|
||||
"lint": "npm run lint:cmd -- --fix",
|
||||
"lint:cmd": "eslint ./ --ext .jsx,.js"
|
||||
"lint:cmd": "eslint ./ --ext .jsx,.js",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"idb-keyval": "^5.0.2",
|
||||
@ -21,12 +22,17 @@
|
||||
"@babel/preset-env": "^7.12.13",
|
||||
"@prefresh/snowpack": "^3.0.1",
|
||||
"@snowpack/plugin-postcss": "^1.1.0",
|
||||
"@testing-library/jest-dom": "^5.11.9",
|
||||
"@testing-library/preact": "^2.0.1",
|
||||
"autoprefixer": "^10.2.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^7.19.0",
|
||||
"eslint-config-preact": "^1.1.3",
|
||||
"eslint-config-prettier": "^7.2.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jest": "^24.1.3",
|
||||
"eslint-plugin-testing-library": "^3.10.1",
|
||||
"jest": "^26.6.3",
|
||||
"postcss": "^8.2.2",
|
||||
"postcss-cli": "^8.3.1",
|
||||
"prettier": "^2.2.1",
|
||||
|
217
web/src/context/__tests__/index.test.jsx
Normal file
217
web/src/context/__tests__/index.test.jsx
Normal file
@ -0,0 +1,217 @@
|
||||
import { h } from 'preact';
|
||||
import * as IDB from 'idb-keyval';
|
||||
import { DarkModeProvider, useDarkMode, usePersistence } from '..';
|
||||
import { fireEvent, render, screen } from '@testing-library/preact';
|
||||
import { useCallback } from 'preact/hooks';
|
||||
|
||||
function DarkModeChecker() {
|
||||
const { currentMode } = useDarkMode();
|
||||
return <div data-testid={currentMode}>{currentMode}</div>;
|
||||
}
|
||||
|
||||
describe('DarkMode', () => {
|
||||
let MockIDB;
|
||||
beforeEach(() => {
|
||||
MockIDB = {
|
||||
get: jest.spyOn(IDB, 'get').mockImplementation(() => Promise.resolve(undefined)),
|
||||
set: jest.spyOn(IDB, 'set').mockImplementation(() => Promise.resolve(true)),
|
||||
};
|
||||
});
|
||||
|
||||
test('uses media by default', async () => {
|
||||
render(
|
||||
<DarkModeProvider>
|
||||
<DarkModeChecker />
|
||||
</DarkModeProvider>
|
||||
);
|
||||
const el = await screen.findByTestId('media');
|
||||
expect(el).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('uses the mode stored in idb - dark', async () => {
|
||||
MockIDB.get.mockResolvedValue('dark');
|
||||
render(
|
||||
<DarkModeProvider>
|
||||
<DarkModeChecker />
|
||||
</DarkModeProvider>
|
||||
);
|
||||
const el = await screen.findByTestId('dark');
|
||||
expect(el).toBeInTheDocument();
|
||||
expect(document.body.classList.contains('dark')).toBe(true);
|
||||
});
|
||||
|
||||
test('uses the mode stored in idb - light', async () => {
|
||||
MockIDB.get.mockResolvedValue('light');
|
||||
render(
|
||||
<DarkModeProvider>
|
||||
<DarkModeChecker />
|
||||
</DarkModeProvider>
|
||||
);
|
||||
const el = await screen.findByTestId('light');
|
||||
expect(el).toBeInTheDocument();
|
||||
expect(document.body.classList.contains('dark')).toBe(false);
|
||||
});
|
||||
|
||||
test('allows updating the mode', async () => {
|
||||
MockIDB.get.mockResolvedValue('dark');
|
||||
|
||||
function Updater() {
|
||||
const { setDarkMode } = useDarkMode();
|
||||
const handleClick = useCallback(() => {
|
||||
setDarkMode('light');
|
||||
}, [setDarkMode]);
|
||||
return <div onClick={handleClick}>click me</div>;
|
||||
}
|
||||
|
||||
render(
|
||||
<DarkModeProvider>
|
||||
<DarkModeChecker />
|
||||
<Updater />
|
||||
</DarkModeProvider>
|
||||
);
|
||||
|
||||
const dark = await screen.findByTestId('dark');
|
||||
expect(dark).toBeInTheDocument();
|
||||
expect(document.body.classList.contains('dark')).toBe(true);
|
||||
|
||||
const button = await screen.findByText('click me');
|
||||
fireEvent.click(button);
|
||||
|
||||
const light = await screen.findByTestId('light');
|
||||
expect(light).toBeInTheDocument();
|
||||
expect(document.body.classList.contains('dark')).toBe(false);
|
||||
});
|
||||
|
||||
test('when using media, matches on preference', async () => {
|
||||
MockIDB.get.mockResolvedValue('media');
|
||||
jest.spyOn(window, 'matchMedia').mockImplementation((query) => {
|
||||
if (query === '(prefers-color-scheme: dark)') {
|
||||
return { matches: true, addEventListener: jest.fn(), removeEventListener: jest.fn() };
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected query to matchMedia: ${query}`);
|
||||
});
|
||||
render(
|
||||
<DarkModeProvider>
|
||||
<DarkModeChecker />
|
||||
</DarkModeProvider>
|
||||
);
|
||||
|
||||
const el = await screen.findByTestId('dark');
|
||||
expect(el).toBeInTheDocument();
|
||||
expect(document.body.classList.contains('dark')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('usePersistence', () => {
|
||||
let MockIDB;
|
||||
beforeEach(() => {
|
||||
MockIDB = {
|
||||
get: jest.spyOn(IDB, 'get').mockImplementation(() => Promise.resolve(undefined)),
|
||||
set: jest.spyOn(IDB, 'set').mockImplementation(() => Promise.resolve(true)),
|
||||
};
|
||||
});
|
||||
|
||||
test('returns a defaultValue initially', async () => {
|
||||
MockIDB.get.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve('foo');
|
||||
}, 1);
|
||||
})
|
||||
);
|
||||
|
||||
function Component() {
|
||||
const [value, , loaded] = usePersistence('tacos', 'my-default');
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="loaded">{loaded ? 'loaded' : 'not loaded'}</div>
|
||||
<div data-testid="value">{value}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<Component />);
|
||||
|
||||
expect(screen.getByTestId('loaded')).toMatchInlineSnapshot(`
|
||||
<div
|
||||
data-testid="loaded"
|
||||
>
|
||||
not loaded
|
||||
</div>
|
||||
`);
|
||||
expect(screen.getByTestId('value')).toMatchInlineSnapshot(`
|
||||
<div
|
||||
data-testid="value"
|
||||
>
|
||||
my-default
|
||||
</div>
|
||||
`);
|
||||
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
test('updates with the previously-persisted value', async () => {
|
||||
MockIDB.get.mockResolvedValue('are delicious');
|
||||
|
||||
function Component() {
|
||||
const [value, , loaded] = usePersistence('tacos', 'my-default');
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="loaded">{loaded ? 'loaded' : 'not loaded'}</div>
|
||||
<div data-testid="value">{value}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<Component />);
|
||||
|
||||
await screen.findByText('loaded');
|
||||
|
||||
expect(screen.getByTestId('loaded')).toMatchInlineSnapshot(`
|
||||
<div
|
||||
data-testid="loaded"
|
||||
>
|
||||
loaded
|
||||
</div>
|
||||
`);
|
||||
expect(screen.getByTestId('value')).toMatchInlineSnapshot(`
|
||||
<div
|
||||
data-testid="value"
|
||||
>
|
||||
are delicious
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
|
||||
test('can be updated manually', async () => {
|
||||
MockIDB.get.mockResolvedValue('are delicious');
|
||||
|
||||
function Component() {
|
||||
const [value, setValue] = usePersistence('tacos', 'my-default');
|
||||
const handleClick = useCallback(() => {
|
||||
setValue('super delicious');
|
||||
}, [setValue]);
|
||||
return (
|
||||
<div>
|
||||
<div onClick={handleClick}>click me</div>
|
||||
<div data-testid="value">{value}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<Component />);
|
||||
|
||||
const button = await screen.findByText('click me');
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByTestId('value')).toMatchInlineSnapshot(`
|
||||
<div
|
||||
data-testid="value"
|
||||
>
|
||||
super delicious
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
});
|
@ -12,9 +12,7 @@ export function DarkModeProvider({ children }) {
|
||||
(value) => {
|
||||
setPersistedMode(value);
|
||||
setData('darkmode', value);
|
||||
if (value !== 'media') {
|
||||
setCurrentMode(value);
|
||||
}
|
||||
setCurrentMode(value);
|
||||
},
|
||||
[setPersistedMode]
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user