mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-02-23 11:32:33 -05:00
switch to vitest
This commit is contained in:
parent
24880a5c2d
commit
3911334fee
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@ -7,7 +7,8 @@
|
|||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"rust-lang.rust-analyzer",
|
"rust-lang.rust-analyzer",
|
||||||
"yzhang.markdown-all-in-one"
|
"yzhang.markdown-all-in-one",
|
||||||
|
"zixuanchen.vitest-explorer"
|
||||||
],
|
],
|
||||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||||
"unwantedRecommendations": [
|
"unwantedRecommendations": [
|
||||||
|
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@ -35,11 +35,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
// It seems like rust-analyzer is supposed to be able to format
|
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||||
// Rust files, but with "matklad.rust-analyzer" here, VS Code says
|
|
||||||
// "There is no formatter for 'rust' files installed."
|
|
||||||
"editor.defaultFormatter": "matklad.rust-analyzer"
|
|
||||||
//"editor.defaultFormatter": null
|
|
||||||
},
|
},
|
||||||
"markdown.extension.list.indentationSize": "inherit",
|
"markdown.extension.list.indentationSize": "inherit",
|
||||||
"markdown.extension.toc.unorderedList.marker": "*",
|
"markdown.extension.toc.unorderedList.marker": "*",
|
||||||
@ -47,5 +43,7 @@
|
|||||||
// Specify the path to the workspace version of TypeScript. Note this only
|
// Specify the path to the workspace version of TypeScript. Note this only
|
||||||
// takes effect when workspace version is selected in the UI.
|
// takes effect when workspace version is selected in the UI.
|
||||||
// https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-the-workspace-version-of-typescript
|
// https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-the-workspace-version-of-typescript
|
||||||
"typescript.tsdk": "./ui/node_modules/typescript/lib"
|
"typescript.tsdk": "./ui/node_modules/typescript/lib",
|
||||||
|
"cmake.configureOnOpen": false,
|
||||||
|
"vitest.enable": true
|
||||||
}
|
}
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
// This file is part of Moonfire NVR, a security camera network video recorder.
|
|
||||||
// Copyright (C) 2023 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
|
|
||||||
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception
|
|
||||||
|
|
||||||
// Environment based on `jsdom` with some extra globals, inspired by
|
|
||||||
// the following comment:
|
|
||||||
// https://github.com/jsdom/jsdom/issues/1724#issuecomment-1446858041
|
|
||||||
|
|
||||||
import JSDOMEnvironment from "jest-environment-jsdom";
|
|
||||||
|
|
||||||
// https://github.com/facebook/jest/blob/v29.4.3/website/versioned_docs/version-29.4/Configuration.md#testenvironment-string
|
|
||||||
export default class FixJSDOMEnvironment extends JSDOMEnvironment {
|
|
||||||
constructor(...args: ConstructorParameters<typeof JSDOMEnvironment>) {
|
|
||||||
super(...args);
|
|
||||||
|
|
||||||
// Tests use fetch calls with relative URLs + msw to intercept.
|
|
||||||
this.global.fetch = (
|
|
||||||
resource: RequestInfo | URL,
|
|
||||||
options?: RequestInit
|
|
||||||
) => {
|
|
||||||
throw "must use msw to fetch: " + resource;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MyRequest extends Request {
|
|
||||||
constructor(input: RequestInfo | URL, init?: RequestInit | undefined) {
|
|
||||||
input = new URL(input as string, "http://localhost");
|
|
||||||
super(input, init);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.global.Headers = Headers;
|
|
||||||
this.global.Request = MyRequest;
|
|
||||||
this.global.Response = Response;
|
|
||||||
|
|
||||||
// `src/LiveCamera/parser.ts` uses TextDecoder.
|
|
||||||
this.global.TextDecoder = TextDecoder;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
// This file is part of Moonfire NVR, a security camera network video recorder.
|
|
||||||
// Copyright (C) 2023 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
|
|
||||||
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception
|
|
||||||
|
|
||||||
import type { Config } from "jest";
|
|
||||||
|
|
||||||
const config: Config = {
|
|
||||||
testEnvironment: "./FixJSDomEnvironment.ts",
|
|
||||||
|
|
||||||
transform: {
|
|
||||||
// https://github.com/swc-project/jest
|
|
||||||
"\\.[tj]sx?$": [
|
|
||||||
"@swc/jest",
|
|
||||||
{
|
|
||||||
// https://swc.rs/docs/configuration/compilation
|
|
||||||
// https://github.com/swc-project/jest/issues/167#issuecomment-1809868077
|
|
||||||
jsc: {
|
|
||||||
transform: {
|
|
||||||
react: {
|
|
||||||
runtime: "automatic",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],
|
|
||||||
|
|
||||||
// https://github.com/jaredLunde/react-hook/issues/300#issuecomment-1845227937
|
|
||||||
moduleNameMapper: {
|
|
||||||
"@react-hook/(.*)": "<rootDir>/node_modules/@react-hook/$1/dist/main",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
2117
ui/package-lock.json
generated
2117
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -27,12 +27,12 @@
|
|||||||
"format": "prettier --write --ignore-path .gitignore .",
|
"format": "prettier --write --ignore-path .gitignore .",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test": "jest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:jest/recommended",
|
"plugin:vitest/recommended",
|
||||||
"plugin:react/recommended",
|
"plugin:react/recommended",
|
||||||
"plugin:react/jsx-runtime",
|
"plugin:react/jsx-runtime",
|
||||||
"plugin:react-hooks/recommended"
|
"plugin:react-hooks/recommended"
|
||||||
@ -54,7 +54,6 @@
|
|||||||
"sourceType": "module"
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"jest/no-disabled-tests": "off",
|
|
||||||
"no-restricted-imports": [
|
"no-restricted-imports": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
@ -86,12 +85,10 @@
|
|||||||
"@babel/preset-react": "^7.23.3",
|
"@babel/preset-react": "^7.23.3",
|
||||||
"@babel/preset-typescript": "^7.23.3",
|
"@babel/preset-typescript": "^7.23.3",
|
||||||
"@swc/core": "^1.3.100",
|
"@swc/core": "^1.3.100",
|
||||||
"@swc/jest": "^0.2.29",
|
|
||||||
"@testing-library/dom": "^8.11.3",
|
"@testing-library/dom": "^8.11.3",
|
||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^6.1.5",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
"@types/jest": "^29.5.11",
|
|
||||||
"@types/node": "^18.8.1",
|
"@types/node": "^18.8.1",
|
||||||
"@types/react": "^18.0.26",
|
"@types/react": "^18.0.26",
|
||||||
"@types/react-dom": "^18.0.10",
|
"@types/react-dom": "^18.0.10",
|
||||||
@ -99,18 +96,17 @@
|
|||||||
"@typescript-eslint/parser": "^6.14.0",
|
"@typescript-eslint/parser": "^6.14.0",
|
||||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||||
"eslint": "^8.55.0",
|
"eslint": "^8.55.0",
|
||||||
"eslint-plugin-jest": "^27.6.0",
|
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.5",
|
"eslint-plugin-react-refresh": "^0.4.5",
|
||||||
|
"eslint-plugin-vitest": "^0.3.18",
|
||||||
"http-proxy-middleware": "^2.0.4",
|
"http-proxy-middleware": "^2.0.4",
|
||||||
"jest": "^29.7.0",
|
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
|
||||||
"msw": "^1.3.2",
|
"msw": "^1.3.2",
|
||||||
"prettier": "^2.6.0",
|
"prettier": "^2.6.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.1.0",
|
"typescript": "^5.1.0",
|
||||||
"vite": "^5.0.8",
|
"vite": "^5.0.8",
|
||||||
"vite-plugin-compression": "^0.5.1"
|
"vite-plugin-compression": "^0.5.1",
|
||||||
|
"vitest": "^1.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import App from "./App";
|
|||||||
import { renderWithCtx } from "./testutil";
|
import { renderWithCtx } from "./testutil";
|
||||||
import { rest } from "msw";
|
import { rest } from "msw";
|
||||||
import { setupServer } from "msw/node";
|
import { setupServer } from "msw/node";
|
||||||
|
import { beforeAll, afterAll, afterEach, expect, test } from "vitest";
|
||||||
|
|
||||||
const server = setupServer(
|
const server = setupServer(
|
||||||
rest.get("/api/", (req, res, ctx) => {
|
rest.get("/api/", (req, res, ctx) => {
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import { render, screen } from "@testing-library/react";
|
import { render, screen } from "@testing-library/react";
|
||||||
import ErrorBoundary from "./ErrorBoundary";
|
import ErrorBoundary from "./ErrorBoundary";
|
||||||
|
import { expect, test } from "vitest";
|
||||||
|
|
||||||
const ThrowsLiteralComponent = () => {
|
const ThrowsLiteralComponent = () => {
|
||||||
throw "simple string error";
|
throw "simple string error";
|
||||||
|
@ -11,6 +11,7 @@ import { Recording, VideoSampleEntry } from "../api";
|
|||||||
import { renderWithCtx } from "../testutil";
|
import { renderWithCtx } from "../testutil";
|
||||||
import { Camera, Stream } from "../types";
|
import { Camera, Stream } from "../types";
|
||||||
import VideoList from "./VideoList";
|
import VideoList from "./VideoList";
|
||||||
|
import { beforeAll, afterAll, afterEach, expect, test } from "vitest";
|
||||||
|
|
||||||
const TEST_CAMERA: Camera = {
|
const TEST_CAMERA: Camera = {
|
||||||
uuid: "c7278ba0-a001-420c-911e-fff4e33f6916",
|
uuid: "c7278ba0-a001-420c-911e-fff4e33f6916",
|
||||||
|
@ -8,6 +8,7 @@ import { rest } from "msw";
|
|||||||
import { setupServer } from "msw/node";
|
import { setupServer } from "msw/node";
|
||||||
import Login from "./Login";
|
import Login from "./Login";
|
||||||
import { renderWithCtx } from "./testutil";
|
import { renderWithCtx } from "./testutil";
|
||||||
|
import { beforeAll, afterEach, afterAll, test, vi, expect } from "vitest";
|
||||||
|
|
||||||
// Set up a fake API backend.
|
// Set up a fake API backend.
|
||||||
const server = setupServer(
|
const server = setupServer(
|
||||||
@ -47,20 +48,20 @@ afterAll(() => server.close());
|
|||||||
// https://github.com/facebook/jest/issues/13018 ?
|
// https://github.com/facebook/jest/issues/13018 ?
|
||||||
//
|
//
|
||||||
// Argh!
|
// Argh!
|
||||||
// beforeEach(() => jest.useFakeTimers({
|
// beforeEach(() => vi.useFakeTimers({
|
||||||
// legacyFakeTimers: true,
|
// legacyFakeTimers: true,
|
||||||
// }));
|
// }));
|
||||||
// afterEach(() => {
|
// afterEach(() => {
|
||||||
// act(() => {
|
// act(() => {
|
||||||
// jest.runOnlyPendingTimers();
|
// vi.runOnlyPendingTimers();
|
||||||
// jest.useRealTimers();
|
// vi.useRealTimers();
|
||||||
// });
|
// });
|
||||||
// });
|
// });
|
||||||
|
|
||||||
test("success", async () => {
|
test("success", async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
const handleClose = jest.fn().mockName("handleClose");
|
const handleClose = vi.fn().mockName("handleClose");
|
||||||
const onSuccess = jest.fn().mockName("handleOpen");
|
const onSuccess = vi.fn().mockName("handleOpen");
|
||||||
renderWithCtx(
|
renderWithCtx(
|
||||||
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
||||||
);
|
);
|
||||||
@ -75,8 +76,8 @@ test("success", async () => {
|
|||||||
// so the delay("infinite") request just sticks around, even though the fetch
|
// so the delay("infinite") request just sticks around, even though the fetch
|
||||||
// has been aborted. Maybe https://github.com/mswjs/msw/pull/585 will fix it.
|
// has been aborted. Maybe https://github.com/mswjs/msw/pull/585 will fix it.
|
||||||
test.skip("close while pending", async () => {
|
test.skip("close while pending", async () => {
|
||||||
const handleClose = jest.fn().mockName("handleClose");
|
const handleClose = vi.fn().mockName("handleClose");
|
||||||
const onSuccess = jest.fn().mockName("handleOpen");
|
const onSuccess = vi.fn().mockName("handleOpen");
|
||||||
const { rerender } = renderWithCtx(
|
const { rerender } = renderWithCtx(
|
||||||
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
||||||
);
|
);
|
||||||
@ -96,8 +97,8 @@ test.skip("close while pending", async () => {
|
|||||||
// TODO: fix and re-enable this test.
|
// TODO: fix and re-enable this test.
|
||||||
// It depends on the timers; see TODO above.
|
// It depends on the timers; see TODO above.
|
||||||
test.skip("bad credentials", async () => {
|
test.skip("bad credentials", async () => {
|
||||||
const handleClose = jest.fn().mockName("handleClose");
|
const handleClose = vi.fn().mockName("handleClose");
|
||||||
const onSuccess = jest.fn().mockName("handleOpen");
|
const onSuccess = vi.fn().mockName("handleOpen");
|
||||||
renderWithCtx(
|
renderWithCtx(
|
||||||
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
||||||
);
|
);
|
||||||
@ -110,8 +111,8 @@ test.skip("bad credentials", async () => {
|
|||||||
// TODO: fix and re-enable this test.
|
// TODO: fix and re-enable this test.
|
||||||
// It depends on the timers; see TODO above.
|
// It depends on the timers; see TODO above.
|
||||||
test.skip("server error", async () => {
|
test.skip("server error", async () => {
|
||||||
const handleClose = jest.fn().mockName("handleClose");
|
const handleClose = vi.fn().mockName("handleClose");
|
||||||
const onSuccess = jest.fn().mockName("handleOpen");
|
const onSuccess = vi.fn().mockName("handleOpen");
|
||||||
renderWithCtx(
|
renderWithCtx(
|
||||||
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
||||||
);
|
);
|
||||||
@ -127,8 +128,8 @@ test.skip("server error", async () => {
|
|||||||
// TODO: fix and re-enable this test.
|
// TODO: fix and re-enable this test.
|
||||||
// It depends on the timers; see TODO above.
|
// It depends on the timers; see TODO above.
|
||||||
test.skip("network error", async () => {
|
test.skip("network error", async () => {
|
||||||
const handleClose = jest.fn().mockName("handleClose");
|
const handleClose = vi.fn().mockName("handleClose");
|
||||||
const onSuccess = jest.fn().mockName("handleOpen");
|
const onSuccess = vi.fn().mockName("handleOpen");
|
||||||
renderWithCtx(
|
renderWithCtx(
|
||||||
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
|
||||||
);
|
);
|
||||||
|
@ -40,7 +40,7 @@ async function myfetch(
|
|||||||
): Promise<FetchResult<Response>> {
|
): Promise<FetchResult<Response>> {
|
||||||
let response;
|
let response;
|
||||||
try {
|
try {
|
||||||
response = await fetch(url, init);
|
response = await fetch(window.location.origin + url, init);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!(e instanceof DOMException)) {
|
if (!(e instanceof DOMException)) {
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -5,12 +5,13 @@
|
|||||||
import { act, render, screen, waitFor } from "@testing-library/react";
|
import { act, render, screen, waitFor } from "@testing-library/react";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { SnackbarProvider, useSnackbars } from "./snackbars";
|
import { SnackbarProvider, useSnackbars } from "./snackbars";
|
||||||
|
import { beforeEach, afterEach, expect, test, vi } from "vitest";
|
||||||
|
|
||||||
// Mock out timers.
|
// Mock out timers.
|
||||||
beforeEach(() => jest.useFakeTimers());
|
beforeEach(() => { vi.useFakeTimers(); });
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.runOnlyPendingTimers();
|
vi.runOnlyPendingTimers();
|
||||||
jest.useRealTimers();
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("notifications that time out", async () => {
|
test("notifications that time out", async () => {
|
||||||
@ -34,24 +35,24 @@ test("notifications that time out", async () => {
|
|||||||
expect(screen.queryByText(/message B/)).not.toBeInTheDocument();
|
expect(screen.queryByText(/message B/)).not.toBeInTheDocument();
|
||||||
|
|
||||||
// ...then start to close...
|
// ...then start to close...
|
||||||
act(() => jest.advanceTimersByTime(5000));
|
act(() => vi.advanceTimersByTime(5000));
|
||||||
expect(screen.getByText(/message A/)).toBeInTheDocument();
|
expect(screen.getByText(/message A/)).toBeInTheDocument();
|
||||||
expect(screen.queryByText(/message B/)).not.toBeInTheDocument();
|
expect(screen.queryByText(/message B/)).not.toBeInTheDocument();
|
||||||
|
|
||||||
// ...then it should close and message B should open...
|
// ...then it should close and message B should open...
|
||||||
act(() => jest.runOnlyPendingTimers());
|
act(() => vi.runOnlyPendingTimers());
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(screen.queryByText(/message A/)).not.toBeInTheDocument()
|
expect(screen.queryByText(/message A/)).not.toBeInTheDocument()
|
||||||
);
|
);
|
||||||
expect(screen.getByText(/message B/)).toBeInTheDocument();
|
expect(screen.getByText(/message B/)).toBeInTheDocument();
|
||||||
|
|
||||||
// ...then message B should start to close...
|
// ...then message B should start to close...
|
||||||
act(() => jest.advanceTimersByTime(5000));
|
act(() => vi.advanceTimersByTime(5000));
|
||||||
expect(screen.queryByText(/message A/)).not.toBeInTheDocument();
|
expect(screen.queryByText(/message A/)).not.toBeInTheDocument();
|
||||||
expect(screen.getByText(/message B/)).toBeInTheDocument();
|
expect(screen.getByText(/message B/)).toBeInTheDocument();
|
||||||
|
|
||||||
// ...then message B should fully close.
|
// ...then message B should fully close.
|
||||||
act(() => jest.runOnlyPendingTimers());
|
act(() => vi.runOnlyPendingTimers());
|
||||||
expect(screen.queryByText(/message A/)).not.toBeInTheDocument();
|
expect(screen.queryByText(/message A/)).not.toBeInTheDocument();
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(screen.queryByText(/message B/)).not.toBeInTheDocument()
|
expect(screen.queryByText(/message B/)).not.toBeInTheDocument()
|
||||||
|
13
ui/vitest.config.ts
Normal file
13
ui/vitest.config.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// This file is part of Moonfire NVR, a security camera network video recorder.
|
||||||
|
// Copyright (C) 2023 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
|
||||||
|
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception
|
||||||
|
|
||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: "jsdom",
|
||||||
|
globals: true,
|
||||||
|
setupFiles: ["./src/setupTests.ts"],
|
||||||
|
},
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user