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
|
- name: Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
working-directory: ./web
|
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
|
frigate/version.py
|
||||||
web/build
|
web/build
|
||||||
web/node_modules
|
web/node_modules
|
||||||
|
web/coverage
|
||||||
|
@ -9,8 +9,14 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
extends: ['prettier', 'preact', 'plugin:import/react'],
|
extends: [
|
||||||
plugins: ['import'],
|
'prettier',
|
||||||
|
'preact',
|
||||||
|
'plugin:import/react',
|
||||||
|
'plugin:testing-library/recommended',
|
||||||
|
'plugin:jest/recommended',
|
||||||
|
],
|
||||||
|
plugins: ['import', 'testing-library', 'jest'],
|
||||||
|
|
||||||
env: {
|
env: {
|
||||||
es6: true,
|
es6: true,
|
||||||
@ -113,6 +119,15 @@ module.exports = {
|
|||||||
'import/no-unresolved': 'error',
|
'import/no-unresolved': 'error',
|
||||||
|
|
||||||
'react-hooks/exhaustive-deps': '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: {
|
settings: {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
presets: ['@babel/preset-env'],
|
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',
|
||||||
|
};
|
5057
web/package-lock.json
generated
5057
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,8 @@
|
|||||||
"prebuild": "rimraf build",
|
"prebuild": "rimraf build",
|
||||||
"build": "cross-env NODE_ENV=production SNOWPACK_MODE=production SNOWPACK_PUBLIC_API_HOST='' snowpack build",
|
"build": "cross-env NODE_ENV=production SNOWPACK_MODE=production SNOWPACK_PUBLIC_API_HOST='' snowpack build",
|
||||||
"lint": "npm run lint:cmd -- --fix",
|
"lint": "npm run lint:cmd -- --fix",
|
||||||
"lint:cmd": "eslint ./ --ext .jsx,.js"
|
"lint:cmd": "eslint ./ --ext .jsx,.js",
|
||||||
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"idb-keyval": "^5.0.2",
|
"idb-keyval": "^5.0.2",
|
||||||
@ -21,12 +22,17 @@
|
|||||||
"@babel/preset-env": "^7.12.13",
|
"@babel/preset-env": "^7.12.13",
|
||||||
"@prefresh/snowpack": "^3.0.1",
|
"@prefresh/snowpack": "^3.0.1",
|
||||||
"@snowpack/plugin-postcss": "^1.1.0",
|
"@snowpack/plugin-postcss": "^1.1.0",
|
||||||
|
"@testing-library/jest-dom": "^5.11.9",
|
||||||
|
"@testing-library/preact": "^2.0.1",
|
||||||
"autoprefixer": "^10.2.1",
|
"autoprefixer": "^10.2.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^7.19.0",
|
"eslint": "^7.19.0",
|
||||||
"eslint-config-preact": "^1.1.3",
|
"eslint-config-preact": "^1.1.3",
|
||||||
"eslint-config-prettier": "^7.2.0",
|
"eslint-config-prettier": "^7.2.0",
|
||||||
"eslint-plugin-import": "^2.22.1",
|
"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": "^8.2.2",
|
||||||
"postcss-cli": "^8.3.1",
|
"postcss-cli": "^8.3.1",
|
||||||
"prettier": "^2.2.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) => {
|
(value) => {
|
||||||
setPersistedMode(value);
|
setPersistedMode(value);
|
||||||
setData('darkmode', value);
|
setData('darkmode', value);
|
||||||
if (value !== 'media') {
|
|
||||||
setCurrentMode(value);
|
setCurrentMode(value);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[setPersistedMode]
|
[setPersistedMode]
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user