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:
parent
64743fdad9
commit
984ab6d022
16 changed files with 590 additions and 2090 deletions
|
@ -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",
|
||||
},
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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/$`,
|
||||
)
|
||||
|
|
|
@ -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}/`,
|
||||
})
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { describe, it, expect } from "vitest"
|
||||
import { validateEmail, validatePassword } from "./validation"
|
||||
|
||||
describe("Email address format validation", () => {
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["jest", "node"]
|
||||
"types": ["node", "mocha"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()],
|
||||
server: {
|
||||
port: 1234,
|
||||
strictPort: true,
|
||||
https: false
|
||||
}
|
||||
plugins: [legacy(), basicSSL()],
|
||||
server: {
|
||||
port: 1234,
|
||||
strictPort: true,
|
||||
https: false,
|
||||
},
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
setupFiles: ["./src/tests/testSetup.ts"],
|
||||
testMatch: ["./src/**/*.test.tsx?"],
|
||||
globals: true,
|
||||
},
|
||||
})
|
||||
|
|
2528
frontend/yarn.lock
2528
frontend/yarn.lock
File diff suppressed because it is too large
Load diff
Reference in a new issue