test(web): standardize frontend tests on Vitest (#6569)

* test(web): standardize frontend tests on Vitest

- configure Vitest, jsdom, and React Testing Library with shared test scripts.
- migrate existing node:test suites to the Vitest runner.
- rewrite JsonCodeEditor component tests with RTL and remove the direct happy-dom dependency.

* fix(ci): run frontend tests with Vitest

- invoke the configured Vitest script so browser test setup loads in CI.
- migrate remaining node:test suites to Vitest lifecycle APIs.

* test(web): use shared jsdom environment for component tests

- migrate usage cost and tool price tests to React Testing Library.
- remove duplicate happy-dom globals and rely on the configured Vitest setup.

* test(web): verify behavior with shared vitest setup

- replace Node test assertions with Vitest expect across frontend suites.
- migrate Keys component tests to React Testing Library interactions.
- centralize jsdom browser mocks for consistent component execution.

* fix(web): unblock frozen installs and Vitest CI

- sync dompurify 3.4.13 metadata into the Bun lockfile.
- replace the bun:test and happy-dom redemption harness with Vitest and RTL.
- preserve quota conversion, error feedback, and stale-response coverage in jsdom.
This commit is contained in:
QuentinHsu
2026-08-15 14:18:10 +08:00
committed by GitHub
parent 116255f076
commit e2c7aa7b10
37 changed files with 1569 additions and 2173 deletions
@@ -16,8 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import assert from 'node:assert/strict'
import { describe, test } from 'node:test'
import { describe, expect, test } from 'vitest'
import { positiveIntegerSchema } from '../../utils/numeric-field'
@@ -26,15 +25,15 @@ const schema = positiveIntegerSchema(t('Enter a positive integer'))
describe('per-token Auto group limit validation', () => {
test('accepts any positive integer without a product upper bound', () => {
assert.equal(schema.safeParse(1000).success, true)
expect(schema.safeParse(1000).success).toBe(true)
})
test('rejects zero, negative, and fractional limits', () => {
for (const maxTokenAutoGroups of [0, -1, 1.5]) {
const result = schema.safeParse(maxTokenAutoGroups)
assert.equal(result.success, false)
expect(result.success).toBe(false)
if (result.success) continue
assert.equal(result.error.issues[0]?.message, 'Enter a positive integer')
expect(result.error.issues[0]?.message).toBe('Enter a positive integer')
}
})
})
@@ -16,128 +16,49 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import assert from 'node:assert/strict'
import { after, describe, test } from 'node:test'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fireEvent, render, screen } from '@testing-library/react'
import i18next from 'i18next'
import { beforeAll, describe, expect, test } from 'vitest'
import { Window } from 'happy-dom'
const domWindow = new Window()
const domGlobals = [
'window',
'document',
'navigator',
'HTMLElement',
'HTMLInputElement',
'SVGElement',
'Node',
'Element',
'Event',
'CustomEvent',
'MutationObserver',
'requestAnimationFrame',
'cancelAnimationFrame',
'getComputedStyle',
] as const
for (const key of domGlobals) {
Object.defineProperty(globalThis, key, {
configurable: true,
value: domWindow[key],
})
}
const { act } = await import('react')
const { createRoot } = await import('react-dom/client')
const { QueryClient, QueryClientProvider } =
await import('@tanstack/react-query')
const { createInstance } = await import('i18next')
const { I18nextProvider, initReactI18next } = await import('react-i18next')
const { ToolPriceSettings } = await import('../tool-price-settings')
const i18n = createInstance()
await i18n.use(initReactI18next).init({
lng: 'en',
resources: {
en: {
translation: {
'Price ($/1K calls)': 'Price ($/1K calls)',
'Please enter a valid number': 'Please enter a valid number',
'Tool identifier': 'Tool identifier',
},
},
},
})
const reactTestGlobals = globalThis as typeof globalThis & {
IS_REACT_ACT_ENVIRONMENT?: boolean
}
reactTestGlobals.IS_REACT_ACT_ENVIRONMENT = true
function changeInputValue(input: HTMLInputElement, value: string) {
const valueSetter = Object.getOwnPropertyDescriptor(
domWindow.HTMLInputElement.prototype,
'value'
)?.set
assert.ok(valueSetter)
valueSetter.call(input, value)
input.dispatchEvent(
new domWindow.Event('input', { bubbles: true }) as unknown as Event
)
}
import { ToolPriceSettings } from '../tool-price-settings'
describe('tool price validation', () => {
after(() => {
domWindow.close()
beforeAll(() => {
i18next.addResourceBundle('en', 'translation', {
'Price ($/1K calls)': 'Price ($/1K calls)',
'Please enter a valid number': 'Please enter a valid number',
'Tool identifier': 'Tool identifier',
})
})
test('blocks an empty price without converting it to an explicit zero', async () => {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
test('blocks an empty price without converting it to an explicit zero', () => {
const queryClient = new QueryClient({
defaultOptions: { mutations: { retry: false } },
})
await act(async () => {
root.render(
<QueryClientProvider client={queryClient}>
<I18nextProvider i18n={i18n}>
<ToolPriceSettings defaultValue='{"web_search":10}' />
</I18nextProvider>
</QueryClientProvider>
)
})
const priceInput = container.querySelector<HTMLInputElement>(
'input[aria-label="Price ($/1K calls): web_search"]'
render(
<QueryClientProvider client={queryClient}>
<ToolPriceSettings defaultValue='{"web_search":10}' />
</QueryClientProvider>
)
assert.ok(priceInput)
await act(async () => {
changeInputValue(priceInput, '')
const priceInput = screen.getByRole('spinbutton', {
name: 'Price ($/1K calls): web_search',
})
const saveButton = screen.getByRole('button', { name: 'Save tool prices' })
assert.equal(priceInput.getAttribute('aria-invalid'), 'true')
assert.equal(
priceInput.closest('[data-slot="field"]')?.querySelector('[role="alert"]')
?.textContent,
'Please enter a valid number'
)
const saveButton = [...container.querySelectorAll('button')].find(
(button) => button.textContent === 'Save tool prices'
)
assert.ok(saveButton)
assert.equal(saveButton.disabled, true)
fireEvent.change(priceInput, { target: { value: '' } })
await act(async () => {
changeInputValue(priceInput, '0')
})
expect(priceInput).toHaveAttribute('aria-invalid', 'true')
expect(screen.getByText('Please enter a valid number')).toBeInTheDocument()
expect(saveButton).toBeDisabled()
assert.equal(priceInput.getAttribute('aria-invalid'), 'false')
assert.equal(saveButton.disabled, false)
fireEvent.change(priceInput, { target: { value: '0' } })
expect(priceInput).toHaveAttribute('aria-invalid', 'false')
expect(saveButton).toBeEnabled()
await act(async () => root.unmount())
container.remove()
queryClient.clear()
})
})