Skip to content

Commit

Permalink
Bump the npm-development group across 1 directory with 8 updates (#589)
Browse files Browse the repository at this point in the history
  • Loading branch information
dependabot[bot] authored Nov 5, 2024
1 parent 06eca57 commit 7bc09a8
Show file tree
Hide file tree
Showing 14 changed files with 1,347 additions and 245 deletions.
1,353 changes: 1,151 additions & 202 deletions web/package-lock.json

Large diffs are not rendered by default.

21 changes: 13 additions & 8 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,35 @@
"semver": "^7.6.3"
},
"devDependencies": {
"@eslint/compat": "^1.2.1",
"@eslint/compat": "^1.2.2",
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.13.0",
"@types/node": "^22.7.9",
"@eslint/js": "^9.14.0",
"@fetch-mock/vitest": "^0.2.3",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^22.9.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/semver": "^7.5.8",
"@typescript-eslint/eslint-plugin": "^8.11.0",
"@typescript-eslint/parser": "^8.11.0",
"@typescript-eslint/parser": "^8.13.0",
"@vitejs/plugin-react": "^4.3.3",
"eslint": "^9.13.0",
"eslint": "^9.14.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"globals": "^15.11.0",
"globals": "^15.12.0",
"jsdom": "^25.0.1",
"openapi-typescript": "^7.4.1",
"postcss": "^8.4.47",
"postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1",
"prettier": "^3.3.3",
"typescript": "^5.6.3",
"vite": "^5.4.10",
"vitest": "^2.1.3",
"vitest-fetch-mock": "^0.3.0"
"vitest": "^2.1.4"
},
"volta": {
"node": "22"
Expand Down
52 changes: 52 additions & 0 deletions web/src/api/client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { afterAll, beforeAll, describe, expect, test } from 'vitest'
import fetchMock from '@fetch-mock/vitest'
import { APIErrorCommon } from './errors'
import { Client } from './client'

beforeAll(() => fetchMock.mockGlobal())
afterAll(() => fetchMock.mockRestore())

const baseUrl = 'http://localhost'

/** A quick way to test if an error is thrown is to check it. */
const expectError = async (fn: () => Promise<unknown> | unknown, checkErrorFn: (e: TypeError) => void) => {
try {
await fn()

expect(true).toBe(false) // fail the test if the error is not thrown
} catch (e: TypeError | unknown) {
expect(e).toBeInstanceOf(Error)

checkErrorFn(e as Error)
}
}

describe('currentVersion', () => {
const mockUrlMatcher = /\/api\/version$/

test('pass', async () => {
fetchMock.getOnce(mockUrlMatcher, { status: 200, body: { version: 'v1.2.3' } })

const client = new Client({ baseUrl })

expect((await client.currentVersion()).toString()).equals('1.2.3')
expect((await client.currentVersion()).toString()).equals('1.2.3') // the second call should use the cache

fetchMock.getOnce(mockUrlMatcher, { status: 200, body: { version: 'V3.2.1' } })

expect((await client.currentVersion(true)).toString()).equals('3.2.1') // the cache should be updated
expect((await client.currentVersion()).toString()).equals('3.2.1') // the second call should use the cache
})

test('throws', async () => {
fetchMock.getOnce(mockUrlMatcher, { status: 501, body: '"error"' })

await expectError(
async () => await new Client({ baseUrl }).currentVersion(),
(e) => {
expect(e).toBeInstanceOf(APIErrorCommon)
expect(e.message).toBe('Not Implemented')
}
)
})
})
16 changes: 11 additions & 5 deletions web/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,17 @@ export class Client {
}> = {}

constructor(opt?: ClientOptions) {
this.baseUrl = new URL(
opt?.baseUrl ? opt.baseUrl.replace(/\/+$/, '') : window.location.protocol + '//' + window.location.host
)
const baseUrl: string | null = opt?.baseUrl
? opt.baseUrl.replace(/\/+$/, '')
: typeof window !== 'undefined' // for non-browser environments, like tests
? window.location.protocol + '//' + window.location.host
: null

if (!baseUrl) {
throw new Error('The base URL is not provided and cannot be determined')
}

this.baseUrl = new URL(baseUrl)

this.api = createClient<paths>(opt)
this.api.use(throwIfNotJSON, throwIfNotValidResponse)
Expand Down Expand Up @@ -385,5 +393,3 @@ export class Client {
throw new APIErrorUnknown({ message: response.statusText, response })
}
}

export default new Client() // singleton instance
2 changes: 1 addition & 1 deletion web/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default as apiClient, type Client } from './client'
export { Client } from './client'
export { type APIError, APIErrorNotFound, APIErrorCommon, APIErrorUnknown } from './errors'
46 changes: 22 additions & 24 deletions web/src/api/middleware.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { afterEach, beforeAll, describe, expect, test, vi } from 'vitest'
import { describe, expect, test, beforeAll, afterAll } from 'vitest'
import { throwIfNotJSON, throwIfNotValidResponse } from './middleware'
import createFetchMock from 'vitest-fetch-mock'
import fetchMock from '@fetch-mock/vitest'
import type { Middleware } from 'openapi-fetch'
import createClient from 'openapi-fetch'
import { APIErrorCommon, APIErrorNotFound } from './errors'

const fetchMocker = createFetchMock(vi)

beforeAll(() => fetchMocker.enableMocks())
afterEach(() => fetchMocker.resetMocks())
beforeAll(() => fetchMock.mockGlobal())
afterAll(() => fetchMock.mockRestore())

interface paths {
'/self': {
Expand Down Expand Up @@ -37,11 +35,11 @@ describe('throwIfNotJSON', () => {
test('pass', async () => {
const client = newClient(throwIfNotJSON)

fetchMocker.mockResponseOnce(() => ({
fetchMock.getOnce(/\/self$/, {
status: 200,
body: '"ok"',
headers: { 'Content-Type': 'application/json' }, // the header is correct
}))
})

const { data, error } = await client.GET('/self')

Expand All @@ -52,11 +50,11 @@ describe('throwIfNotJSON', () => {
test('throws', async () => {
const client = newClient(throwIfNotJSON)

fetchMocker.mockResponseOnce(() => ({
fetchMock.getOnce(/\/self$/, {
status: 200,
body: '"ok"',
headers: { 'Content-Type': 'text/html' }, // the header is incorrect
}))
})

try {
await client.GET('/self')
Expand All @@ -73,11 +71,11 @@ describe('throwIfNotValidResponse', () => {
test('pass', async () => {
const client = newClient(throwIfNotValidResponse)

fetchMocker.mockResponseOnce(() => ({
fetchMock.getOnce(/\/self$/, {
status: 200,
body: '"ok"',
headers: { 'Content-Type': 'text/html' }, // the header doesn't matter
}))
})

const { data, error } = await client.GET('/self')

Expand All @@ -88,11 +86,11 @@ describe('throwIfNotValidResponse', () => {
test('throws ({ message: "..." })', async () => {
const client = newClient(throwIfNotValidResponse)

fetchMocker.mockResponseOnce(() => ({
fetchMock.getOnce(/\/self$/, {
status: 404,
body: `{"message": "some value"}`,
headers: { 'Content-Type': 'application/json' }, // the header is correct
}))
})

try {
await client.GET('/self')
Expand All @@ -107,11 +105,11 @@ describe('throwIfNotValidResponse', () => {
test('throws ({ error: "..." })', async () => {
const client = newClient(throwIfNotValidResponse)

fetchMocker.mockResponseOnce(() => ({
fetchMock.getOnce(/\/self$/, {
status: 404,
body: `{"error": "some value"}`,
headers: { 'Content-Type': 'application/json' }, // the header is correct
}))
})

try {
await client.GET('/self')
Expand All @@ -126,11 +124,11 @@ describe('throwIfNotValidResponse', () => {
test('throws ({ errors: ["...", ...] })', async () => {
const client = newClient(throwIfNotValidResponse)

fetchMocker.mockResponseOnce(() => ({
fetchMock.getOnce(/\/self$/, {
status: 404,
body: `{"errors": ["some", "value"]}`,
headers: { 'Content-Type': 'application/json' }, // the header is correct
}))
})

try {
await client.GET('/self')
Expand All @@ -145,11 +143,11 @@ describe('throwIfNotValidResponse', () => {
test('throws ({ errors: { "...": "..." } })', async () => {
const client = newClient(throwIfNotValidResponse)

fetchMocker.mockResponseOnce(() => ({
fetchMock.getOnce(/\/self$/, {
status: 500,
body: `{"errors": {"a": "some", "b": "value"}}`,
headers: { 'Content-Type': 'application/json' }, // the header is correct
}))
})

try {
await client.GET('/self')
Expand All @@ -164,11 +162,11 @@ describe('throwIfNotValidResponse', () => {
test('throws', async () => {
const client = newClient(throwIfNotValidResponse)

fetchMocker.mockResponseOnce(() => ({
fetchMock.getOnce(/\/self$/, {
status: 500,
body: `{"error]`, // since the content type, the error message will be `res.statusText`
headers: { 'Content-Type': 'foo/bar' }, // the header is incorrect
}))
})

try {
await client.GET('/self')
Expand All @@ -183,11 +181,11 @@ describe('throwIfNotValidResponse', () => {
test('throws json (Failed to parse the response body as JSON)', async () => {
const client = newClient(throwIfNotValidResponse)

fetchMocker.mockResponseOnce(() => ({
fetchMock.getOnce(/\/self$/, {
status: 500,
body: `{"error]`,
headers: { 'Content-Type': 'application/json' }, // the header is correct
}))
})

try {
await client.GET('/self')
Expand Down
7 changes: 5 additions & 2 deletions web/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { MantineProvider } from '@mantine/core'
import { Notifications } from '@mantine/notifications'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { routes } from './routing'
import { Client } from './api'
import { createRoutes } from './routing'
import { BrowserNotificationsProvider, SessionsProvider, UISettingsProvider } from './shared'
import '@mantine/core/styles.css'
import '@mantine/code-highlight/styles.css'
Expand All @@ -16,13 +17,15 @@ dayjs.extend(relativeTime) // https://day.js.org/docs/en/plugin/relative-time

/** App component */
const App = (): React.JSX.Element => {
const apiClient = new Client()

return (
<MantineProvider defaultColorScheme="auto">
<Notifications />
<BrowserNotificationsProvider>
<UISettingsProvider>
<SessionsProvider>
<RouterProvider router={createBrowserRouter(routes)} />
<RouterProvider router={createBrowserRouter(createRoutes(apiClient))} />
</SessionsProvider>
</UISettingsProvider>
</BrowserNotificationsProvider>
Expand Down
2 changes: 1 addition & 1 deletion web/src/routing/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { routes, pathTo, RouteIDs } from './routing'
export { createRoutes, pathTo, RouteIDs } from './routing'
4 changes: 2 additions & 2 deletions web/src/routing/routing.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createPath, Navigate, type RouteObject } from 'react-router-dom'
import { apiClient } from '~/api'
import { type Client } from '~/api'
import { DefaultLayout } from '~/screens'
import { NotFoundScreen } from '~/screens/not-found'
import { SessionAndRequestScreen } from '~/screens/session'
Expand All @@ -10,7 +10,7 @@ export enum RouteIDs {
SessionAndRequest = 'session-and-request',
}

export const routes: RouteObject[] = [
export const createRoutes = (apiClient: Client): RouteObject[] => [
{
path: '/',
element: <DefaultLayout apiClient={apiClient} />,
Expand Down
40 changes: 40 additions & 0 deletions web/src/screens/layout.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { MantineProvider } from '@mantine/core'
import { SemVer } from 'semver'
import { describe, afterEach, expect, test, vi } from 'vitest'
import { render, cleanup } from '~/test-utils'
import { MemoryRouter, Route, Routes } from 'react-router-dom'
import { default as Layout } from './layout'
import { Client } from '~/api'

afterEach(() => cleanup())

/** @jest-environment jsdom */
describe('layout', () => {
test('common', () => {
const apiClient = new Client({ baseUrl: 'http://unit/test' })
const FakeComponent = () => <div>fake text</div>

vi.spyOn(apiClient, 'currentVersion').mockResolvedValueOnce(new SemVer('0.0.0-unit-test'))
vi.spyOn(apiClient, 'latestVersion').mockResolvedValueOnce(new SemVer('99.0.0-unit-test'))
vi.spyOn(apiClient, 'getSettings').mockResolvedValueOnce({
limits: { maxRequests: 1000, sessionTTL: 60, maxRequestBodySize: 1000 },
})

const { unmount } = render(
<MantineProvider>
<MemoryRouter initialEntries={['/']}>
<Routes>
<Route element={<Layout apiClient={apiClient} />}>
<Route path="/" element={<FakeComponent />} />
</Route>
</Routes>
</MemoryRouter>
</MantineProvider>
)

expect(apiClient.currentVersion).toHaveBeenCalledTimes(1)
expect(apiClient.latestVersion).toHaveBeenCalledTimes(1)

unmount()
})
})
5 changes: 5 additions & 0 deletions web/src/test-utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import userEvent from '@testing-library/user-event'

export * from '@testing-library/react'
export { render } from './render'
export { userEvent }
9 changes: 9 additions & 0 deletions web/src/test-utils/render.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { render as testingLibraryRender } from '@testing-library/react'
import { MantineProvider } from '@mantine/core'

/** @link https://mantine.dev/guides/vitest/#custom-render */
export function render(ui: React.ReactNode) {
return testingLibraryRender(<>{ui}</>, {
wrapper: ({ children }: { children: React.ReactNode }) => <MantineProvider>{children}</MantineProvider>,
})
}
6 changes: 6 additions & 0 deletions web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,10 @@ export default defineConfig({
esbuild: {
legalComments: 'none',
},
// @ts-ignore-next-line The `vite` type definitions are not up-to-date
test: {
globals: true,
environment: 'jsdom',
setupFiles: './vitest.setup.js',
},
})
Loading

0 comments on commit 7bc09a8

Please sign in to comment.