build(fe-tooling): set up vitest to replace jest et al.

fix: include mocha types

fix: missing vitest import

fix: missing vitest import
This commit is contained in:
Marc 2023-12-30 00:04:39 -05:00
parent 64743fdad9
commit 984ab6d022
Signed by: marc
GPG key ID: 048E042F22B5DC79
16 changed files with 590 additions and 2090 deletions

View file

@ -1,9 +0,0 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: "ts-jest",
testEnvironment: "jsdom",
setupFilesAfterEnv: ["./src/tests/testSetup.ts"],
transform: {
"^.+\\.(ts|tsx)$": "ts-jest",
},
}

View file

@ -15,7 +15,7 @@
"build": "vite build ./src",
"lint": "biome check src *.js --verbose && biome format src *.js --verbose",
"lint:fix": "biome check src ./*.js --apply --verbose && biome format src ./*.js --write --verbose",
"test": "yarn jest",
"test": "yarn vitest run src",
"typecheck": "yarn tsc --noEmit"
},
"devDependencies": {
@ -24,20 +24,19 @@
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.1",
"@types/jest": "^29.5.3",
"@types/mocha": "^10.0.6",
"@types/node": "^20.10.6",
"@types/react": "^18.2.18",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-basic-ssl": "^1.0.2",
"@vitejs/plugin-legacy": "^5.2.0",
"@vitejs/plugin-react": "^4.2.1",
"axios-mock-adapter": "^1.21.5",
"buffer": "^5.5.0||^6.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jsdom": "^23.0.1",
"process": "^0.11.10",
"ts-jest": "^29.1.1",
"terser": "^5.26.0",
"typescript": "^5.3.0",
"vite": "^5.0.10"
"vite": "^5.0.10",
"vitest": "^1.1.0"
}
}

View file

@ -1,3 +1,4 @@
import { vi, expect, describe, it } from "vitest"
import { act } from "@testing-library/react"
import { within } from "@testing-library/dom"
import userEvent from "@testing-library/user-event"
@ -19,9 +20,9 @@ describe("FileDetails", () => {
size: 1,
id: "b61bf93d-a9db-473e-822e-a65003b1b7e3",
}
test("Clicking the download button trigger a file download", async () => {
it("Clicking the download button trigger a file download", async () => {
// FIXME: Validating file downloads is ... tricky. The current interaction with dynamically created DOM
// elements is not visible by jest.
// elements is not visible by vi.
const expectedUrlPattern = new RegExp(`/files/${mockItem.id}/content/$`)
@ -29,12 +30,10 @@ describe("FileDetails", () => {
axiosMock.onGet(expectedUrlPattern).reply(200, mockItem)
jest
.spyOn(fileQueries, "useFileDetails")
.mockReturnValue({ data: mockItem, isLoading: false } as UseQueryResult<
FileData,
unknown
>)
vi.spyOn(fileQueries, "useFileDetails").mockReturnValue({
data: mockItem,
isLoading: false,
} as UseQueryResult<FileData, unknown>)
const user = userEvent.setup()
const { getByLabelText, debug, rerender } = render(
@ -54,19 +53,17 @@ describe("FileDetails", () => {
expect(downloadRequest.url).toMatch(expectedUrlPattern)
})
test("Clicking the delete button fires request to delete file", async () => {
it("Clicking the delete button fires request to delete file", async () => {
const expectedUrlPattern = new RegExp(`/files/${mockItem.id}/$`)
const axiosMock = getAxiosMockAdapter()
axiosMock.onDelete(expectedUrlPattern).reply(200, mockItem)
jest
.spyOn(fileQueries, "useFileDetails")
.mockReturnValue({ data: mockItem, isLoading: false } as UseQueryResult<
FileData,
unknown
>)
vi.spyOn(fileQueries, "useFileDetails").mockReturnValue({
data: mockItem,
isLoading: false,
} as UseQueryResult<FileData, unknown>)
const user = userEvent.setup()
const { getByLabelText, debug, rerender } = render(
@ -86,22 +83,20 @@ describe("FileDetails", () => {
expect(deleteRequest.url).toMatch(expectedUrlPattern)
})
test("Clicking the delete button redirects to the file list after success", async () => {
it("Clicking the delete button redirects to the file list after success", async () => {
const expectedUrlPattern = new RegExp(`/files/${mockItem.id}/$`)
const axiosMock = getAxiosMockAdapter()
axiosMock.onDelete(expectedUrlPattern).reply(200, mockItem)
jest
.spyOn(fileQueries, "useFileDetails")
.mockReturnValue({ data: mockItem, isLoading: false } as UseQueryResult<
FileData,
unknown
>)
vi.spyOn(fileQueries, "useFileDetails").mockReturnValue({
data: mockItem,
isLoading: false,
} as UseQueryResult<FileData, unknown>)
const navigateMock = jest.fn().mockImplementation((a: string) => {})
jest.spyOn(locationContextUtils, "useLocationContext").mockReturnValue({
const navigateMock = vi.fn().mockImplementation((a: string) => {})
vi.spyOn(locationContextUtils, "useLocationContext").mockReturnValue({
location: {
path: "",
label: null,

View file

@ -1,3 +1,4 @@
import { expect, describe, it, vi } from "vitest"
import { within } from "@testing-library/dom"
import userEvent from "@testing-library/user-event"
@ -28,14 +29,14 @@ describe("FileList", () => {
{ title: "Async Item 0", filename: "async.txt", size: 2, type: "upload" },
]
test("Renders list items provided", () => {
it("Renders list items provided", () => {
const { getAllByText } = render(<FileList data={mockItems} />)
const renderedItems = getAllByText(/Item/)
expect(renderedItems.length).toEqual(mockItems.length)
})
test("Prepends items in flight as tracked by async task context", () => {
it("Prepends items in flight as tracked by async task context", () => {
const { getAllByText, getByText } = render(
<FileList data={[mockItems[0]]} />,
{ asyncTaskContext: mockAsyncTasks },
@ -50,7 +51,7 @@ describe("FileList", () => {
})
describe("FileListItem", () => {
test("Renders the item title", () => {
it("Renders the item title", () => {
const { getByLabelText, debug } = render(
<FileList data={[mockItems[0]]} />,
)
@ -58,7 +59,7 @@ describe("FileList", () => {
within(title).getByText(mockItems[0].title)
})
test("Renders the item size", () => {
it("Renders the item size", () => {
const { getByLabelText, debug } = render(
<FileList data={[mockItems[0]]} />,
)
@ -66,7 +67,7 @@ describe("FileList", () => {
within(title).getByText(`${mockItems[0].size} B`)
})
test.each(["download item", "delete item"])(
it.each(["download item", "delete item"])(
"Renders secondary action buttons (%s)",
(action) => {
const { getByLabelText, debug } = render(
@ -76,7 +77,7 @@ describe("FileList", () => {
},
)
test("Clicking the delete button fires request to delete file", async () => {
it("Clicking the delete button fires request to delete file", async () => {
const expectedUrlPattern = new RegExp(`/files/${mockItems[0].id}/$`)
const axiosMock = getAxiosMockAdapter()
@ -101,9 +102,9 @@ describe("FileList", () => {
expect(deleteRequest.url).toMatch(expectedUrlPattern)
})
test("Clicking the download button trigger a file download", async () => {
it("Clicking the download button trigger a file download", async () => {
// FIXME: Validating file downloads is ... tricky. The current interaction with dynamically created DOM
// elements is not visible by jest.
// elements is not visible by vi.
const expectedUrlPattern = new RegExp(
`/files/${mockItems[0].id}/content/$`,
)

View file

@ -1,3 +1,4 @@
import { vi, expect, describe, it, afterEach } from "vitest"
import { render, screen, waitFor } from "@testing-library/react"
import { QueryClientProvider, QueryClient } from "@tanstack/react-query"
import AxiosMockAdapter from "axios-mock-adapter"
@ -21,11 +22,11 @@ function renderComponent() {
describe("FileListView", () => {
afterEach(() => {
jest.resetAllMocks()
vi.resetAllMocks()
})
it("renders no sidebar if no item is in the path", async () => {
jest.spyOn(globalThis, "location", "get").mockReturnValue({
vi.spyOn(globalThis, "location", "get").mockReturnValue({
...globalThis.location,
pathname: "/",
})
@ -54,7 +55,7 @@ describe("FileListView", () => {
it("renders a sidebar if an item is selected", async () => {
const mockItemId = "b61bf93d-a9db-473e-822e-a65003b1b7e3"
jest.spyOn(globalThis, "location", "get").mockReturnValue({
vi.spyOn(globalThis, "location", "get").mockReturnValue({
...globalThis.location,
pathname: `/item/${mockItemId}/`,
})

View file

@ -1,3 +1,5 @@
import { expect, describe, it, vi } from "vitest"
import { render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { QueryClientProvider, QueryClient } from "@tanstack/react-query"
@ -41,7 +43,7 @@ describe("LoginView", () => {
})
it("renders a registration link", async () => {
const mock = jest.fn()
const mock = vi.fn()
const { user } = renderComponent()
expect(screen.getByText(/don\'t have an account yet?/i)).toBeInTheDocument()
@ -113,8 +115,8 @@ describe("LoginView", () => {
})
it("redirects the user on success", async () => {
const mockNavigate = jest.fn()
const mockLocationHook = jest
const mockNavigate = vi.fn()
const mockLocationHook = vi
.spyOn(locationHook, "useLocationContext")
.mockImplementation(() => ({
location: {

View file

@ -1,3 +1,4 @@
import { vi, it, describe, expect } from "vitest"
import { within } from "@testing-library/dom"
import userEvent from "@testing-library/user-event"
@ -11,12 +12,12 @@ import { type FileData } from "../../types/files"
describe("NavigationBar", () => {
describe("Upload functionality", () => {
test("Renders the upload button", () => {
it("Renders the upload button", () => {
const { getByText } = render(<NavigationBar />)
getByText("Upload file")
})
test("Clicking the upload button and selecting a file POSTs the file", async () => {
it("Clicking the upload button and selecting a file POSTs the file", async () => {
const axiosMock = getAxiosMockAdapter()
const expectedUrlPattern = new RegExp("/files/$")
axiosMock.onPost(expectedUrlPattern).reply(200, {

View file

@ -1,3 +1,4 @@
import { expect, it, vi, describe } from "vitest"
import { screen, render, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { QueryClientProvider, QueryClient } from "@tanstack/react-query"

View file

@ -1,3 +1,4 @@
import { describe, it, expect } from "vitest"
import { validateEmail, validatePassword } from "./validation"
describe("Email address format validation", () => {

View file

@ -1,3 +1,4 @@
import { describe, it, expect, vi } from "vitest"
import React from "react"
import { screen, render, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
@ -46,7 +47,7 @@ function renderComponent(props?: Partial<TextInputProps>) {
describe("TextInput", () => {
it("runs the provided onChange on input", async () => {
const mockOnChange = jest.fn()
const mockOnChange = vi.fn()
const mockInput = "testinput"
const { user } = renderComponent({ onChange: mockOnChange })

View file

@ -1,3 +1,5 @@
import React from "react"
import { describe, expect, it } from "vitest"
import { render, screen } from "@testing-library/react"
import Route from "./Route"

View file

@ -1,3 +1,4 @@
import { afterEach, describe, it, vi, expect } from "vitest"
import { render, screen } from "@testing-library/react"
import { LocationContext } from "../contexts/LocationContext"
@ -10,17 +11,17 @@ function renderComponent(component: React.ReactElement) {
describe("Router", () => {
afterEach(() => {
jest.resetAllMocks()
vi.resetAllMocks()
})
it("throws an error if no Route exists for the given location", () => {
jest.spyOn(globalThis, "location", "get").mockReturnValue({
vi.spyOn(globalThis, "location", "get").mockReturnValue({
...globalThis.location,
pathname: "/doesnotexist",
})
// Silence the error to avoid logspam in tests.
jest.spyOn(console, "error").mockImplementation(() => {})
vi.spyOn(console, "error").mockImplementation(() => {})
expect(() =>
renderComponent(
@ -34,7 +35,7 @@ describe("Router", () => {
})
it("renders the route matching the given location", () => {
jest.spyOn(globalThis, "location", "get").mockReturnValue({
vi.spyOn(globalThis, "location", "get").mockReturnValue({
...globalThis.location,
pathname: "/exists",
})
@ -51,7 +52,7 @@ describe("Router", () => {
})
it("only renders the route that matches", () => {
jest.spyOn(globalThis, "location", "get").mockReturnValue({
vi.spyOn(globalThis, "location", "get").mockReturnValue({
...globalThis.location,
pathname: "/matches",
})

View file

@ -1,9 +1,10 @@
import { vi } from "vitest"
import "@testing-library/jest-dom"
// URL.createObjectURL does not exist in jest-jsdom.
globalThis.URL.createObjectURL = jest
globalThis.URL.createObjectURL = vi
.fn()
.mockImplementation(() => "http://localhost/downloadUrl")
// Clicking DOM objects is not implemented in jest-jsdom.
HTMLAnchorElement.prototype.click = jest.fn()
HTMLAnchorElement.prototype.click = vi.fn()

View file

@ -11,6 +11,6 @@
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true,
"types": ["jest", "node"]
"types": ["node", "mocha"]
}
}

View file

@ -1,14 +1,19 @@
import legacy from '@vitejs/plugin-legacy'
import react from '@vitejs/plugin-react'
import basicSSL from '@vitejs/plugin-basic-ssl'
import legacy from "@vitejs/plugin-legacy"
import basicSSL from "@vitejs/plugin-basic-ssl"
import { defineConfig } from 'vite'
import { defineConfig } from "vite"
export default defineConfig({
plugins: [legacy(), react(), basicSSL()],
plugins: [legacy(), basicSSL()],
server: {
port: 1234,
strictPort: true,
https: false
}
https: false,
},
test: {
environment: "jsdom",
setupFiles: ["./src/tests/testSetup.ts"],
testMatch: ["./src/**/*.test.tsx?"],
globals: true,
},
})

File diff suppressed because it is too large Load diff