switch to vitest

This commit is contained in:
Scott Lamb 2023-12-17 23:07:28 -08:00
parent 24880a5c2d
commit 3911334fee
13 changed files with 1592 additions and 688 deletions

View File

@ -7,7 +7,8 @@
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"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.
"unwantedRecommendations": [

10
.vscode/settings.json vendored
View File

@ -35,11 +35,7 @@
}
],
// It seems like rust-analyzer is supposed to be able to format
// 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
"editor.defaultFormatter": "rust-lang.rust-analyzer"
},
"markdown.extension.list.indentationSize": "inherit",
"markdown.extension.toc.unorderedList.marker": "*",
@ -47,5 +43,7 @@
// Specify the path to the workspace version of TypeScript. Note this only
// takes effect when workspace version is selected in the UI.
// 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
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -27,12 +27,12 @@
"format": "prettier --write --ignore-path .gitignore .",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"test": "jest"
"test": "vitest"
},
"eslintConfig": {
"extends": [
"eslint:recommended",
"plugin:jest/recommended",
"plugin:vitest/recommended",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended"
@ -54,7 +54,6 @@
"sourceType": "module"
},
"rules": {
"jest/no-disabled-tests": "off",
"no-restricted-imports": [
"error",
{
@ -86,12 +85,10 @@
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.23.3",
"@swc/core": "^1.3.100",
"@swc/jest": "^0.2.29",
"@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/user-event": "^14.4.3",
"@types/jest": "^29.5.11",
"@types/node": "^18.8.1",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
@ -99,18 +96,17 @@
"@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"eslint": "^8.55.0",
"eslint-plugin-jest": "^27.6.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"eslint-plugin-vitest": "^0.3.18",
"http-proxy-middleware": "^2.0.4",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"msw": "^1.3.2",
"prettier": "^2.6.0",
"ts-node": "^10.9.2",
"typescript": "^5.1.0",
"vite": "^5.0.8",
"vite-plugin-compression": "^0.5.1"
"vite-plugin-compression": "^0.5.1",
"vitest": "^1.0.4"
}
}

View File

@ -7,6 +7,7 @@ import App from "./App";
import { renderWithCtx } from "./testutil";
import { rest } from "msw";
import { setupServer } from "msw/node";
import { beforeAll, afterAll, afterEach, expect, test } from "vitest";
const server = setupServer(
rest.get("/api/", (req, res, ctx) => {

View File

@ -4,6 +4,7 @@
import { render, screen } from "@testing-library/react";
import ErrorBoundary from "./ErrorBoundary";
import { expect, test } from "vitest";
const ThrowsLiteralComponent = () => {
throw "simple string error";

View File

@ -11,6 +11,7 @@ import { Recording, VideoSampleEntry } from "../api";
import { renderWithCtx } from "../testutil";
import { Camera, Stream } from "../types";
import VideoList from "./VideoList";
import { beforeAll, afterAll, afterEach, expect, test } from "vitest";
const TEST_CAMERA: Camera = {
uuid: "c7278ba0-a001-420c-911e-fff4e33f6916",

View File

@ -8,6 +8,7 @@ import { rest } from "msw";
import { setupServer } from "msw/node";
import Login from "./Login";
import { renderWithCtx } from "./testutil";
import { beforeAll, afterEach, afterAll, test, vi, expect } from "vitest";
// Set up a fake API backend.
const server = setupServer(
@ -47,20 +48,20 @@ afterAll(() => server.close());
// https://github.com/facebook/jest/issues/13018 ?
//
// Argh!
// beforeEach(() => jest.useFakeTimers({
// beforeEach(() => vi.useFakeTimers({
// legacyFakeTimers: true,
// }));
// afterEach(() => {
// act(() => {
// jest.runOnlyPendingTimers();
// jest.useRealTimers();
// vi.runOnlyPendingTimers();
// vi.useRealTimers();
// });
// });
test("success", async () => {
const user = userEvent.setup();
const handleClose = jest.fn().mockName("handleClose");
const onSuccess = jest.fn().mockName("handleOpen");
const handleClose = vi.fn().mockName("handleClose");
const onSuccess = vi.fn().mockName("handleOpen");
renderWithCtx(
<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
// has been aborted. Maybe https://github.com/mswjs/msw/pull/585 will fix it.
test.skip("close while pending", async () => {
const handleClose = jest.fn().mockName("handleClose");
const onSuccess = jest.fn().mockName("handleOpen");
const handleClose = vi.fn().mockName("handleClose");
const onSuccess = vi.fn().mockName("handleOpen");
const { rerender } = renderWithCtx(
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
);
@ -96,8 +97,8 @@ test.skip("close while pending", async () => {
// TODO: fix and re-enable this test.
// It depends on the timers; see TODO above.
test.skip("bad credentials", async () => {
const handleClose = jest.fn().mockName("handleClose");
const onSuccess = jest.fn().mockName("handleOpen");
const handleClose = vi.fn().mockName("handleClose");
const onSuccess = vi.fn().mockName("handleOpen");
renderWithCtx(
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
);
@ -110,8 +111,8 @@ test.skip("bad credentials", async () => {
// TODO: fix and re-enable this test.
// It depends on the timers; see TODO above.
test.skip("server error", async () => {
const handleClose = jest.fn().mockName("handleClose");
const onSuccess = jest.fn().mockName("handleOpen");
const handleClose = vi.fn().mockName("handleClose");
const onSuccess = vi.fn().mockName("handleOpen");
renderWithCtx(
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
);
@ -127,8 +128,8 @@ test.skip("server error", async () => {
// TODO: fix and re-enable this test.
// It depends on the timers; see TODO above.
test.skip("network error", async () => {
const handleClose = jest.fn().mockName("handleClose");
const onSuccess = jest.fn().mockName("handleOpen");
const handleClose = vi.fn().mockName("handleClose");
const onSuccess = vi.fn().mockName("handleOpen");
renderWithCtx(
<Login open={true} onSuccess={onSuccess} handleClose={handleClose} />
);

View File

@ -40,7 +40,7 @@ async function myfetch(
): Promise<FetchResult<Response>> {
let response;
try {
response = await fetch(url, init);
response = await fetch(window.location.origin + url, init);
} catch (e) {
if (!(e instanceof DOMException)) {
throw e;

View File

@ -5,12 +5,13 @@
import { act, render, screen, waitFor } from "@testing-library/react";
import { useEffect } from "react";
import { SnackbarProvider, useSnackbars } from "./snackbars";
import { beforeEach, afterEach, expect, test, vi } from "vitest";
// Mock out timers.
beforeEach(() => jest.useFakeTimers());
beforeEach(() => { vi.useFakeTimers(); });
afterEach(() => {
jest.runOnlyPendingTimers();
jest.useRealTimers();
vi.runOnlyPendingTimers();
vi.useRealTimers();
});
test("notifications that time out", async () => {
@ -34,24 +35,24 @@ test("notifications that time out", async () => {
expect(screen.queryByText(/message B/)).not.toBeInTheDocument();
// ...then start to close...
act(() => jest.advanceTimersByTime(5000));
act(() => vi.advanceTimersByTime(5000));
expect(screen.getByText(/message A/)).toBeInTheDocument();
expect(screen.queryByText(/message B/)).not.toBeInTheDocument();
// ...then it should close and message B should open...
act(() => jest.runOnlyPendingTimers());
act(() => vi.runOnlyPendingTimers());
await waitFor(() =>
expect(screen.queryByText(/message A/)).not.toBeInTheDocument()
);
expect(screen.getByText(/message B/)).toBeInTheDocument();
// ...then message B should start to close...
act(() => jest.advanceTimersByTime(5000));
act(() => vi.advanceTimersByTime(5000));
expect(screen.queryByText(/message A/)).not.toBeInTheDocument();
expect(screen.getByText(/message B/)).toBeInTheDocument();
// ...then message B should fully close.
act(() => jest.runOnlyPendingTimers());
act(() => vi.runOnlyPendingTimers());
expect(screen.queryByText(/message A/)).not.toBeInTheDocument();
await waitFor(() =>
expect(screen.queryByText(/message B/)).not.toBeInTheDocument()

13
ui/vitest.config.ts Normal file
View 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"],
},
});