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 {
applyJsonSmartEnter,
@@ -29,42 +28,42 @@ import {
describe('json code editor utils', () => {
test('treats empty drafts as valid editable JSON drafts', () => {
assert.deepEqual(getJsonValidationState(' \n'), {
expect(getJsonValidationState(' \n')).toEqual({
isValid: true,
messageKey: 'JSON',
})
})
test('reports invalid JSON without throwing away the draft', () => {
assert.deepEqual(getJsonValidationState('{"model": }'), {
expect(getJsonValidationState('{"model": }')).toEqual({
isValid: false,
messageKey: 'Invalid JSON',
})
})
test('formats valid JSON with stable two-space indentation', () => {
assert.deepEqual(formatJsonDraft('{"model":{"ratio":2}}'), {
expect(formatJsonDraft('{"model":{"ratio":2}}')).toEqual({
didFormat: true,
value: '{\n "model": {\n "ratio": 2\n }\n}',
})
})
test('keeps invalid JSON drafts unchanged when formatting is requested', () => {
assert.deepEqual(formatJsonDraft('{"model": }'), {
expect(formatJsonDraft('{"model": }')).toEqual({
didFormat: false,
value: '{"model": }',
})
})
test('derives the one-based cursor line and column from text offsets', () => {
assert.deepEqual(getCursorLocation('{\n "model": 1\n}', 5), {
expect(getCursorLocation('{\n "model": 1\n}', 5)).toEqual({
line: 2,
column: 4,
})
})
test('expands paired JSON brackets with a nested indentation line', () => {
assert.deepEqual(applyJsonSmartEnter('{}', 1, 1), {
expect(applyJsonSmartEnter('{}', 1, 1)).toEqual({
value: '{\n \n}',
selectionStart: 4,
selectionEnd: 4,
@@ -90,11 +89,11 @@ describe('json code editor utils', () => {
source.scrollTop = 80
synchronizer.sync()
assert.equal(queuedFrames.length, 1)
expect(queuedFrames.length).toBe(1)
queuedFrames[0]()
assert.equal(contentLayer.style.transform, 'translate3d(-24px, -80px, 0)')
assert.equal(lineNumberLayer.style.transform, 'translate3d(0, -80px, 0)')
expect(contentLayer.style.transform).toBe('translate3d(-24px, -80px, 0)')
expect(lineNumberLayer.style.transform).toBe('translate3d(0, -80px, 0)')
})
})
@@ -16,165 +16,98 @@ 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 { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, test, vi } from 'vitest'
import { Window } from 'happy-dom'
const domWindow = new Window()
const domGlobals = [
'window',
'document',
'navigator',
'HTMLElement',
'HTMLTextAreaElement',
'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 i18next = (await import('i18next')).default
const { initReactI18next } = await import('react-i18next')
await i18next.use(initReactI18next).init({
lng: 'en',
resources: {
en: {
translation: {
JSON: 'JSON',
'Invalid JSON': 'Invalid JSON',
'Copied to clipboard': 'Copied to clipboard',
'Failed to copy': 'Failed to copy',
'Format JSON': 'Format JSON',
},
},
},
})
const { JsonCodeEditor } = await import('../../json-code-editor')
const reactTestGlobals = globalThis as typeof globalThis & {
IS_REACT_ACT_ENVIRONMENT?: boolean
}
reactTestGlobals.IS_REACT_ACT_ENVIRONMENT = true
type RenderedEditor = {
container: HTMLDivElement
root: ReturnType<typeof createRoot>
}
async function renderEditor(
props: React.ComponentProps<typeof JsonCodeEditor>
): Promise<RenderedEditor> {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
await act(async () => {
root.render(<JsonCodeEditor {...props} />)
})
return { container, root }
}
async function unmountEditor(rendered: RenderedEditor) {
await act(async () => rendered.root.unmount())
rendered.container.remove()
}
import { JsonCodeEditor } from '../../json-code-editor'
describe('JsonCodeEditor component', () => {
after(() => {
domWindow.close()
test('forwards form attributes and the textarea ref', () => {
const textareaRef = vi.fn()
const rendered = render(
<JsonCodeEditor
value='{"model":"gpt"}'
onChange={() => undefined}
id='json-input'
name='model_config'
placeholder='{"model":"gpt"}'
disabled
ariaLabel='Model configuration'
aria-describedby='model-help'
aria-invalid
data-form-root='settings-form'
textareaRef={textareaRef}
/>
)
const textarea = screen.getByRole('textbox', {
name: 'Model configuration',
})
expect(textarea).toHaveAttribute('id', 'json-input')
expect(textarea).toHaveAttribute('name', 'model_config')
expect(textarea).toHaveAttribute('placeholder', '{"model":"gpt"}')
expect(textarea).toBeDisabled()
expect(textarea).toHaveAttribute('aria-describedby', 'model-help')
expect(textarea).toHaveAttribute('aria-invalid', 'true')
expect(textarea).toHaveAttribute('data-form-root', 'settings-form')
expect(textareaRef).toHaveBeenCalledWith(textarea)
rendered.unmount()
expect(textareaRef).toHaveBeenLastCalledWith(null)
})
test('forwards form attributes and lifecycle callbacks to the textarea', async () => {
const blurCalls: number[] = []
const refValues: Array<HTMLTextAreaElement | null> = []
const rendered = await renderEditor({
value: '{"model":"gpt"}',
onChange: () => undefined,
id: 'json-input',
name: 'model_config',
placeholder: '{"model":"gpt"}',
disabled: true,
'aria-describedby': 'model-help',
'aria-invalid': true,
'data-form-root': 'settings-form',
onBlur: () => blurCalls.push(1),
textareaRef: (element) => refValues.push(element),
})
const textarea = rendered.container.querySelector('textarea')
test('calls onBlur when focus leaves the editor', () => {
const onBlur = vi.fn()
render(
<JsonCodeEditor
value='{}'
onChange={() => undefined}
onBlur={onBlur}
ariaLabel='Model configuration'
/>
)
assert.ok(textarea)
assert.equal(textarea.id, 'json-input')
assert.equal(textarea.name, 'model_config')
assert.equal(textarea.placeholder, '{"model":"gpt"}')
assert.equal(textarea.disabled, true)
assert.equal(textarea.getAttribute('aria-describedby'), 'model-help')
assert.equal(textarea.getAttribute('aria-invalid'), 'true')
assert.equal(textarea.getAttribute('data-form-root'), 'settings-form')
fireEvent.blur(screen.getByRole('textbox', { name: 'Model configuration' }))
await act(async () => textarea.dispatchEvent(new Event('blur')))
assert.deepEqual(blurCalls, [1])
assert.equal(refValues[0], textarea)
await unmountEditor(rendered)
assert.equal(refValues.at(-1), null)
expect(onBlur).toHaveBeenCalledOnce()
})
test('emits user edits and synchronizes a controlled value', async () => {
const changes: string[] = []
const rendered = await renderEditor({
value: '{"count":1}',
onChange: (value) => changes.push(value),
test('emits user edits and synchronizes a controlled value', () => {
const onChange = vi.fn()
const rendered = render(
<JsonCodeEditor
value='{"count":1}'
onChange={onChange}
ariaLabel='Model configuration'
/>
)
const textarea = screen.getByRole('textbox', {
name: 'Model configuration',
})
const textarea = rendered.container.querySelector('textarea')
assert.ok(textarea)
await act(async () => {
textarea.value = '{"count":2}'
textarea.dispatchEvent(new Event('input', { bubbles: true }))
})
assert.deepEqual(changes, ['{"count":2}'])
fireEvent.input(textarea, { target: { value: '{"count":2}' } })
expect(onChange).toHaveBeenCalledWith('{"count":2}')
await act(async () => {
rendered.root.render(
<JsonCodeEditor
value='{"count":3}'
onChange={(value) => changes.push(value)}
/>
)
})
assert.equal(textarea.value, '{"count":3}')
await unmountEditor(rendered)
rendered.rerender(
<JsonCodeEditor
value='{"count":3}'
onChange={onChange}
ariaLabel='Model configuration'
/>
)
expect(textarea).toHaveValue('{"count":3}')
})
test('formats valid JSON through the public toolbar action', async () => {
const changes: string[] = []
const rendered = await renderEditor({
value: '{"model":{"ratio":2}}',
onChange: (value) => changes.push(value),
})
const formatButton = [
...rendered.container.querySelectorAll('button'),
].find((button) => button.textContent?.includes('Format JSON'))
const user = userEvent.setup()
const onChange = vi.fn()
render(<JsonCodeEditor value='{"model":{"ratio":2}}' onChange={onChange} />)
assert.ok(formatButton)
await act(async () => formatButton.click())
assert.deepEqual(changes, ['{\n "model": {\n "ratio": 2\n }\n}'])
await user.click(screen.getByRole('button', { name: 'Format JSON' }))
await unmountEditor(rendered)
expect(onChange).toHaveBeenCalledWith(
'{\n "model": {\n "ratio": 2\n }\n}'
)
})
})
@@ -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 {
modelGroupSelectorLayoutClasses,
@@ -29,8 +28,8 @@ describe('model group selector layout', () => {
const groupScrollClasses =
modelGroupSelectorLayoutClasses.groupScroll.split(' ')
assert.ok(groupScrollClasses.includes('auto-rows-[2rem]'))
assert.ok(groupScrollClasses.includes('content-start'))
expect(groupScrollClasses.includes('auto-rows-[2rem]')).toBeTruthy()
expect(groupScrollClasses.includes('content-start')).toBeTruthy()
})
test('centers the selected group inside its own scroll container', () => {
@@ -50,7 +49,7 @@ describe('model group selector layout', () => {
scrollSelectedOptionIntoView(selectedOption, scrollContainer)
assert.deepEqual(scrollCalls, [{ top: 76, behavior: 'auto' }])
expect(scrollCalls).toEqual([{ top: 76, behavior: 'auto' }])
})
test('falls back to scrollIntoView when no group container is provided', () => {
@@ -63,6 +62,6 @@ describe('model group selector layout', () => {
scrollSelectedOptionIntoView(selectedOption)
assert.deepEqual(scrollCalls, [{ block: 'center', inline: 'nearest' }])
expect(scrollCalls).toEqual([{ block: 'center', inline: 'nearest' }])
})
})
+5 -6
View File
@@ -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 { handleDropdownMenuItemSelect } from './dropdown-menu-events'
@@ -52,8 +51,8 @@ describe('DropdownMenuItem onSelect compatibility', () => {
selected = true
})
assert.equal(selected, true)
assert.equal(event.baseUIHandlerPrevented, false)
expect(selected).toBe(true)
expect(event.baseUIHandlerPrevented).toBe(false)
})
test('keeps the Base UI menu open when onSelect prevents default', () => {
@@ -63,7 +62,7 @@ describe('DropdownMenuItem onSelect compatibility', () => {
selectEvent.preventDefault()
})
assert.equal(event.defaultPrevented, true)
assert.equal(event.baseUIHandlerPrevented, true)
expect(event.defaultPrevented).toBe(true)
expect(event.baseUIHandlerPrevented).toBe(true)
})
})
+9 -11
View File
@@ -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 type { RefreshOutcome } from '@/lib/api'
import type { AuthBundle } from '@/stores/auth-store'
@@ -63,8 +62,8 @@ describe('logout coordination', () => {
},
})
assert.deepEqual(result, { success: false, message: 'not revoked' })
assert.equal(refreshCount, 0)
expect(result).toEqual({ success: false, message: 'not revoked' })
expect(refreshCount).toBe(0)
})
test('recovers a cookie mismatch and retries with the refreshed SID', async () => {
@@ -83,8 +82,8 @@ describe('logout coordination', () => {
},
})
assert.deepEqual(result, { success: true, message: '' })
assert.deepEqual(requestedSIDs, ['session-a', 'session-b'])
expect(result).toEqual({ success: true, message: '' })
expect(requestedSIDs).toEqual(['session-a', 'session-b'])
})
test('treats a mismatch that refresh confirms anonymous as signed out', async () => {
@@ -96,7 +95,7 @@ describe('logout coordination', () => {
refresh: async () => ({ kind: 'anonymous' }),
})
assert.deepEqual(result, { success: true, message: '' })
expect(result).toEqual({ success: true, message: '' })
})
test('preserves the active session when mismatch recovery is temporary', async () => {
@@ -106,15 +105,14 @@ describe('logout coordination', () => {
error: new Error('offline'),
}
await assert.rejects(
await expect(
executeLogout({
getExpectedSID: () => 'session-a',
request: async () => {
throw originalError
},
refresh: async () => transient,
}),
(error) => error === originalError
)
})
).rejects.toBe(originalError)
})
})
@@ -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 {
getOAuthSessionStorage,
@@ -40,15 +39,14 @@ const bindState = 'bind-state'
describe('resolveOAuthCallbackMode', () => {
test('matching provider and state mark is treated as a bind flow', () => {
const storage = fakeStorage()
assert.equal(markOAuthBindPopup(storage, 'oidc', bindState), true)
expect(markOAuthBindPopup(storage, 'oidc', bindState)).toBe(true)
assert.equal(
expect(
resolveOAuthCallbackMode('oidc', bindState, {
opener: openOpener,
storage,
}),
'bind'
)
})
).toBe('bind')
})
// Regression: a tab opened from an external link (Slack, e-mail, another
@@ -58,75 +56,69 @@ describe('resolveOAuthCallbackMode', () => {
test('login redirect in a tab with a foreign opener stays a login flow', () => {
const storage = fakeStorage()
assert.equal(
expect(
resolveOAuthCallbackMode('oidc', bindState, {
opener: openOpener,
storage,
}),
'login'
)
})
).toBe('login')
})
test('bind marker for another provider does not hijack this callback', () => {
const storage = fakeStorage()
markOAuthBindPopup(storage, 'github', bindState)
assert.equal(
expect(
resolveOAuthCallbackMode('oidc', bindState, {
opener: openOpener,
storage,
}),
'login'
)
})
).toBe('login')
})
test('stale bind marker does not hijack a later callback', () => {
const storage = fakeStorage()
markOAuthBindPopup(storage, 'oidc', 'previous-state')
assert.equal(
expect(
resolveOAuthCallbackMode('oidc', bindState, {
opener: openOpener,
storage,
}),
'login'
)
})
).toBe('login')
})
test('bind marker without an opener falls back to login', () => {
const storage = fakeStorage()
markOAuthBindPopup(storage, 'oidc', bindState)
assert.equal(
expect(
resolveOAuthCallbackMode('oidc', bindState, {
opener: null,
storage,
}),
'login'
)
})
).toBe('login')
})
test('closed opener falls back to login', () => {
const storage = fakeStorage()
markOAuthBindPopup(storage, 'oidc', bindState)
assert.equal(
expect(
resolveOAuthCallbackMode('oidc', bindState, {
opener: { closed: true },
storage,
}),
'login'
)
})
).toBe('login')
})
test('missing storage degrades to login instead of throwing', () => {
assert.equal(
expect(
resolveOAuthCallbackMode('oidc', bindState, {
opener: openOpener,
storage: null,
}),
'login'
)
})
).toBe('login')
})
test('storage read failure degrades to login instead of throwing', () => {
@@ -137,13 +129,12 @@ describe('resolveOAuthCallbackMode', () => {
setItem: () => undefined,
}
assert.equal(
expect(
resolveOAuthCallbackMode('oidc', bindState, {
opener: openOpener,
storage,
}),
'login'
)
})
).toBe('login')
})
})
@@ -155,7 +146,7 @@ describe('OAuth bind popup storage', () => {
},
}
assert.equal(getOAuthSessionStorage(owner), null)
expect(getOAuthSessionStorage(owner)).toBe(null)
})
test('marking reports unavailable or unwritable storage', () => {
@@ -166,9 +157,9 @@ describe('OAuth bind popup storage', () => {
},
}
assert.equal(markOAuthBindPopup(null, 'oidc', bindState), false)
assert.equal(markOAuthBindPopup(storage, 'oidc', bindState), false)
assert.equal(
expect(markOAuthBindPopup(null, 'oidc', bindState)).toBe(false)
expect(markOAuthBindPopup(storage, 'oidc', bindState)).toBe(false)
expect(
markOAuthBindPopup(
{
getItem: () => null,
@@ -176,8 +167,7 @@ describe('OAuth bind popup storage', () => {
},
'oidc',
bindState
),
false
)
)
).toBe(false)
})
})
+15 -22
View File
@@ -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 type { AuthUser } from '@/stores/auth-store'
@@ -27,17 +26,15 @@ const origin = 'https://dashboard.example.com'
describe('authentication redirect validation', () => {
test('preserves safe internal paths, search parameters, and fragments', () => {
assert.equal(
sanitizeAuthRedirect('/console?tab=usage#recent', origin),
expect(sanitizeAuthRedirect('/console?tab=usage#recent', origin)).toBe(
'/console?tab=usage#recent'
)
assert.equal(
expect(
sanitizeAuthRedirect(
'https://dashboard.example.com/dashboard?tab=quota#daily',
origin
),
'/dashboard?tab=quota#daily'
)
)
).toBe('/dashboard?tab=quota#daily')
})
test('rejects external and ambiguously parsed redirect targets', () => {
@@ -53,13 +50,13 @@ describe('authentication redirect validation', () => {
]
for (const target of unsafeTargets) {
assert.equal(sanitizeAuthRedirect(target, origin), null)
expect(sanitizeAuthRedirect(target, origin)).toBe(null)
}
})
test('rejects invalid or non-HTTP application origins', () => {
assert.equal(sanitizeAuthRedirect('/dashboard', 'not-an-origin'), null)
assert.equal(sanitizeAuthRedirect('/dashboard', 'file:///tmp/app'), null)
expect(sanitizeAuthRedirect('/dashboard', 'not-an-origin')).toBe(null)
expect(sanitizeAuthRedirect('/dashboard', 'file:///tmp/app')).toBe(null)
})
})
@@ -67,31 +64,27 @@ describe('saved authentication language', () => {
const user: AuthUser = { id: 1, username: 'user', role: 1 }
test('prefers the explicit user language', () => {
assert.equal(
expect(
getSavedLanguage({
...user,
language: 'ja',
setting: { language: 'fr' },
}),
'ja'
)
})
).toBe('ja')
})
test('reads object and JSON string settings', () => {
assert.equal(
getSavedLanguage({ ...user, setting: { language: 'fr' } }),
expect(getSavedLanguage({ ...user, setting: { language: 'fr' } })).toBe(
'fr'
)
assert.equal(
getSavedLanguage({ ...user, setting: '{"language":"ru"}' }),
expect(getSavedLanguage({ ...user, setting: '{"language":"ru"}' })).toBe(
'ru'
)
})
test('ignores malformed and non-string setting languages', () => {
assert.equal(getSavedLanguage({ ...user, setting: '{' }), undefined)
assert.equal(
getSavedLanguage({ ...user, setting: { language: 123 } }),
expect(getSavedLanguage({ ...user, setting: '{' })).toBe(undefined)
expect(getSavedLanguage({ ...user, setting: { language: 123 } })).toBe(
undefined
)
})
@@ -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 {
parseTelegramBindCallback,
@@ -51,51 +50,48 @@ function fakeTimerRuntime() {
describe('OAuth bind popup lifecycle', () => {
test('parses Telegram success and stable error callbacks', () => {
assert.deepEqual(
expect(
parseTelegramBindCallback({
telegram_bind: 'success',
flow_token: 'flow-success',
}),
{
kind: 'result',
flowToken: 'flow-success',
success: true,
}
)
assert.deepEqual(
})
).toEqual({
kind: 'result',
flowToken: 'flow-success',
success: true,
})
expect(
parseTelegramBindCallback({
telegram_bind: 'error',
flow_token: 'flow-error',
error_code: 'TELEGRAM_BIND_ALREADY_BOUND',
}),
{
kind: 'result',
flowToken: 'flow-error',
success: false,
code: 'TELEGRAM_BIND_ALREADY_BOUND',
}
)
})
).toEqual({
kind: 'result',
flowToken: 'flow-error',
success: false,
code: 'TELEGRAM_BIND_ALREADY_BOUND',
})
})
test('rejects Telegram callbacks without a flow token and ignores descriptions', () => {
assert.deepEqual(parseTelegramBindCallback({ telegram_bind: 'error' }), {
expect(parseTelegramBindCallback({ telegram_bind: 'error' })).toEqual({
kind: 'invalid',
})
assert.deepEqual(
expect(
parseTelegramBindCallback({
telegram_bind: 'error',
flow_token: 'flow-error',
error_code: 'UNKNOWN_CODE',
error_description: 'untrusted message',
} as Parameters<typeof parseTelegramBindCallback>[0]),
{
kind: 'result',
flowToken: 'flow-error',
success: false,
code: 'UNKNOWN_CODE',
}
)
assert.equal(parseTelegramBindCallback({}), null)
} as Parameters<typeof parseTelegramBindCallback>[0])
).toEqual({
kind: 'result',
flowToken: 'flow-error',
success: false,
code: 'UNKNOWN_CODE',
})
expect(parseTelegramBindCallback({})).toBe(null)
})
test('posts only complete Telegram bind results to an available opener', () => {
@@ -112,11 +108,10 @@ describe('OAuth bind popup lifecycle', () => {
error_code: 'UNKNOWN_CODE',
})
assert.equal(
postTelegramBindResult(callback, opener, 'https://dashboard.example.com'),
true
)
assert.deepEqual(messages, [
expect(
postTelegramBindResult(callback, opener, 'https://dashboard.example.com')
).toBe(true)
expect(messages).toEqual([
{
message: {
type: 'telegram:binding:result',
@@ -128,23 +123,17 @@ describe('OAuth bind popup lifecycle', () => {
},
])
assert.equal(
postTelegramBindResult(
{ kind: 'invalid' },
opener,
'https://example.com'
),
false
)
assert.equal(
expect(
postTelegramBindResult({ kind: 'invalid' }, opener, 'https://example.com')
).toBe(false)
expect(
postTelegramBindResult(
callback,
{ ...opener, closed: true },
'https://example.com'
),
false
)
assert.equal(messages.length, 1)
)
).toBe(false)
expect(messages.length).toBe(1)
})
test('waits 30 seconds for the opener response and can be cancelled', () => {
@@ -158,11 +147,11 @@ describe('OAuth bind popup lifecycle', () => {
timer.runtime
)
assert.equal(timer.delay, 30_000)
expect(timer.delay).toBe(30_000)
cancel()
timer.fire()
assert.equal(timedOut, false)
assert.deepEqual(timer.cancelled, [timer.handle])
expect(timedOut).toBe(false)
expect(timer.cancelled).toEqual([timer.handle])
})
test('reports a closed popup once and clears its poller', () => {
@@ -178,13 +167,13 @@ describe('OAuth bind popup lifecycle', () => {
timer.runtime
)
assert.equal(timer.delay, 500)
expect(timer.delay).toBe(500)
timer.fire()
assert.equal(closedCount, 0)
expect(closedCount).toBe(0)
popup.closed = true
timer.fire()
timer.fire()
assert.equal(closedCount, 1)
assert.deepEqual(timer.cancelled, [timer.handle])
expect(closedCount).toBe(1)
expect(timer.cancelled).toEqual([timer.handle])
})
})
@@ -16,14 +16,13 @@ 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 { pickTelegramAuthorization } from './telegram-login'
describe('Telegram login authorization', () => {
test('keeps only fields signed by the Telegram login contract', () => {
assert.deepEqual(
expect(
pickTelegramAuthorization({
id: 12345,
first_name: 'Test',
@@ -35,33 +34,27 @@ describe('Telegram login authorization', () => {
lang: 'en',
admin: true,
redirect: 'https://attacker.example',
}),
{
id: 12345,
first_name: 'Test',
last_name: 'User',
username: 'test_user',
photo_url: 'https://t.me/i/userpic/320/test.jpg',
auth_date: 1_900_000_000,
hash: 'signed-hash',
lang: 'en',
}
)
})
).toEqual({
id: 12345,
first_name: 'Test',
last_name: 'User',
username: 'test_user',
photo_url: 'https://t.me/i/userpic/320/test.jpg',
auth_date: 1_900_000_000,
hash: 'signed-hash',
lang: 'en',
})
})
test('rejects incomplete or structurally invalid callbacks', () => {
assert.equal(pickTelegramAuthorization(null), null)
assert.equal(
pickTelegramAuthorization({ auth_date: 1, hash: 'hash' }),
null
)
assert.equal(
pickTelegramAuthorization({ id: 1, auth_date: 1, hash: '' }),
null
)
assert.equal(
pickTelegramAuthorization({ id: {}, auth_date: 1, hash: 'hash' }),
expect(pickTelegramAuthorization(null)).toBe(null)
expect(pickTelegramAuthorization({ auth_date: 1, hash: 'hash' })).toBe(null)
expect(pickTelegramAuthorization({ id: 1, auth_date: 1, hash: '' })).toBe(
null
)
expect(
pickTelegramAuthorization({ id: {}, auth_date: 1, hash: 'hash' })
).toBe(null)
})
})
@@ -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 {
CHANNEL_FIELD_UPDATE_DELAY_MS,
@@ -31,7 +30,7 @@ function createFakeTimers() {
return {
timers: {
setTimeout: (callback: () => void, delay: number) => {
assert.equal(delay, CHANNEL_FIELD_UPDATE_DELAY_MS)
expect(delay).toBe(CHANNEL_FIELD_UPDATE_DELAY_MS)
const id = nextId++
pending.set(id, callback)
return id
@@ -63,11 +62,11 @@ describe('channel field update scheduler', () => {
scheduler.schedule(1)
scheduler.schedule(2)
scheduler.schedule(3)
assert.deepEqual(updates, [])
assert.equal(fake.pendingCount, 1)
expect(updates).toEqual([])
expect(fake.pendingCount).toBe(1)
fake.fireAll()
assert.deepEqual(updates, [3])
expect(updates).toEqual([3])
})
test('flush commits the pending value immediately and cancels the timer', () => {
@@ -80,11 +79,11 @@ describe('channel field update scheduler', () => {
scheduler.schedule(7)
scheduler.flush()
assert.deepEqual(updates, [7])
assert.equal(fake.pendingCount, 0)
expect(updates).toEqual([7])
expect(fake.pendingCount).toBe(0)
fake.fireAll()
assert.deepEqual(updates, [7])
expect(updates).toEqual([7])
})
test('flush without a pending value does nothing', () => {
@@ -99,7 +98,7 @@ describe('channel field update scheduler', () => {
scheduler.schedule(5)
scheduler.flush()
scheduler.flush()
assert.deepEqual(updates, [5])
expect(updates).toEqual([5])
})
test('preserves a pending value of 0', () => {
@@ -112,6 +111,6 @@ describe('channel field update scheduler', () => {
scheduler.schedule(0)
scheduler.flush()
assert.deepEqual(updates, [0])
expect(updates).toEqual([0])
})
})
@@ -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 type { Channel } from '../../types'
import { getChannelTableRowId, type TagRow } from '../channel-utils'
@@ -35,12 +34,8 @@ describe('channel table row identity', () => {
const beforeUpdate = [first, updated, third].map(getChannelTableRowId)
const afterUpdate = [updated, first, third].map(getChannelTableRowId)
assert.deepEqual(beforeUpdate, [
'channel:101',
'channel:202',
'channel:303',
])
assert.deepEqual(afterUpdate, ['channel:202', 'channel:101', 'channel:303'])
expect(beforeUpdate).toEqual(['channel:101', 'channel:202', 'channel:303'])
expect(afterUpdate).toEqual(['channel:202', 'channel:101', 'channel:303'])
})
test('uses separate namespaces for tag and channel rows', () => {
@@ -50,7 +45,7 @@ describe('channel table row identity', () => {
children: [channel(202)],
} as TagRow
assert.equal(getChannelTableRowId(tagRow), 'tag:202')
assert.equal(getChannelTableRowId(channel(202)), 'channel:202')
expect(getChannelTableRowId(tagRow)).toBe('tag:202')
expect(getChannelTableRowId(channel(202))).toBe('channel:202')
})
})
@@ -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 {
CHANNEL_TYPE_NEW_API,
@@ -45,45 +44,40 @@ describe('New API channel', () => {
(item) => item.value === CHANNEL_TYPE_NEW_API
)
assert.deepEqual(option, {
expect(option).toEqual({
value: CHANNEL_TYPE_NEW_API,
label: 'New API',
})
assert.equal(
expect(
CHANNEL_TYPE_OPTIONS.findIndex(
(item) => item.value === CHANNEL_TYPE_NEW_API
) + 1,
CHANNEL_TYPE_OPTIONS.findIndex((item) => item.value === 58)
)
assert.equal(MODEL_FETCHABLE_TYPES.has(CHANNEL_TYPE_NEW_API), true)
assert.equal(getChannelTypeIcon(CHANNEL_TYPE_NEW_API), 'NewAPI')
assert.equal(
getKeyPromptForType(CHANNEL_TYPE_NEW_API),
) + 1
).toBe(CHANNEL_TYPE_OPTIONS.findIndex((item) => item.value === 58))
expect(MODEL_FETCHABLE_TYPES.has(CHANNEL_TYPE_NEW_API)).toBe(true)
expect(getChannelTypeIcon(CHANNEL_TYPE_NEW_API)).toBe('NewAPI')
expect(getKeyPromptForType(CHANNEL_TYPE_NEW_API)).toBe(
'Enter API key for this channel'
)
assert.equal(getChannelTypeConfig(CHANNEL_TYPE_NEW_API).icon, 'NewAPI')
expect(getChannelTypeConfig(CHANNEL_TYPE_NEW_API).icon).toBe('NewAPI')
})
test('requires a non-blank Base URL', () => {
const blankResult = channelFormSchema.safeParse(newAPIForm(' '))
assert.equal(blankResult.success, false)
expect(blankResult.success).toBe(false)
if (!blankResult.success) {
assert.equal(
expect(
blankResult.error.issues.some(
(issue) =>
issue.path[0] === 'base_url' &&
issue.message === 'Base URL is required for this channel type'
),
true
)
)
).toBe(true)
}
assert.equal(
channelFormSchema.safeParse(newAPIForm('https://new-api.example'))
.success,
true
)
expect(
channelFormSchema.safeParse(newAPIForm('https://new-api.example')).success
).toBe(true)
})
test('keeps Sub2API Base URL validation unchanged', () => {
@@ -92,6 +86,6 @@ describe('New API channel', () => {
type: 59,
})
assert.equal(result.success, true)
expect(result.success).toBe(true)
})
})
@@ -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 type { FlowUserFilterOption } from '../types'
import {
@@ -46,89 +45,75 @@ const users: FlowUserFilterOption[] = [
describe('dashboard flow selection helpers', () => {
test('limits user chips to currently visible users', () => {
assert.deepEqual(
visibleFlowUsers(users, []).map((user) => user.value),
['user:1', 'user:2']
)
assert.deepEqual(
visibleFlowUsers(users, ['user:2']).map((user) => user.value),
['user:2']
)
expect(visibleFlowUsers(users, []).map((user) => user.value)).toEqual([
'user:1',
'user:2',
])
expect(
visibleFlowUsers(users, ['user:2']).map((user) => user.value)
).toEqual(['user:2'])
})
test('filters visible users without mutating the source options', () => {
const visible = visibleFlowUsers(users, ['user:1'])
assert.deepEqual(
visible.map((user) => user.value),
['user:1']
)
assert.deepEqual(
users.map((user) => user.value),
['user:1', 'user:2']
)
expect(visible.map((user) => user.value)).toEqual(['user:1'])
expect(users.map((user) => user.value)).toEqual(['user:1', 'user:2'])
})
test('formats compact selected counts for flow multiselect summaries', () => {
assert.equal(compactFlowSelectionLabel(0), '*')
assert.equal(compactFlowSelectionLabel(1), '1')
assert.equal(compactFlowSelectionLabel(23), '23')
expect(compactFlowSelectionLabel(0)).toBe('*')
expect(compactFlowSelectionLabel(1)).toBe('1')
expect(compactFlowSelectionLabel(23)).toBe('23')
})
test('prioritizes loading and error states before empty flow data', () => {
assert.equal(
expect(
flowDisplayState({
isLoading: true,
isError: true,
linkCount: 0,
themeReady: true,
}),
'loading'
)
assert.equal(
})
).toBe('loading')
expect(
flowDisplayState({
isLoading: false,
isError: true,
linkCount: 0,
themeReady: true,
}),
'error'
)
assert.equal(
})
).toBe('error')
expect(
flowDisplayState({
isLoading: false,
isError: false,
linkCount: 0,
themeReady: true,
}),
'empty'
)
assert.equal(
})
).toBe('empty')
expect(
flowDisplayState({
isLoading: false,
isError: false,
linkCount: 1,
themeReady: false,
}),
'loading'
)
})
).toBe('loading')
})
test('throws unsuccessful flow responses instead of treating them as empty data', () => {
assert.throws(
() =>
requireSuccessfulFlowRows(
{ success: false, data: [], message: 'database unavailable' },
'Failed to load'
),
/database unavailable/
)
assert.deepEqual(
expect(() =>
requireSuccessfulFlowRows(
{ success: false, data: [], message: 'database unavailable' },
'Failed to load'
)
).toThrow(/database unavailable/)
expect(
requireSuccessfulFlowRows(
{ success: true, data: [{ user_id: 1, quota: 10 }] },
'Failed to load'
),
[{ user_id: 1, quota: 10 }]
)
)
).toEqual([{ user_id: 1, quota: 10 }])
})
})
+243 -282
View File
@@ -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 type { FlowQuotaDataItem } from '../types'
import {
@@ -113,18 +112,16 @@ describe('dashboard flow data', () => {
role: 'user',
})
assert.equal(result.summary.quota, 150)
assert.equal(result.summary.tokens, 60)
assert.equal(result.summary.requests, 3)
assert.deepEqual(
result.flow.links.map((link) => [link.source, link.target, link.value]),
[
['group:vip', 'model:gpt-4.1', 150],
['token:11', 'group:vip', 150],
]
)
assert.equal(
result.flow.nodes.some((node) => node.kind === 'channel'),
expect(result.summary.quota).toBe(150)
expect(result.summary.tokens).toBe(60)
expect(result.summary.requests).toBe(3)
expect(
result.flow.links.map((link) => [link.source, link.target, link.value])
).toEqual([
['group:vip', 'model:gpt-4.1', 150],
['token:11', 'group:vip', 150],
])
expect(result.flow.nodes.some((node) => node.kind === 'channel')).toBe(
false
)
})
@@ -134,18 +131,17 @@ describe('dashboard flow data', () => {
role: 'admin',
})
assert.deepEqual(
result.flow.links.map((link) => [link.source, link.target, link.value]),
[
['group:default', 'model:claude-4-sonnet', 70],
['group:vip', 'model:gpt-4.1', 150],
['model:claude-4-sonnet', 'channel:101', 70],
['model:gpt-4.1', 'channel:101', 100],
['model:gpt-4.1', 'channel:102', 50],
['user:1', 'group:vip', 150],
['user:2', 'group:default', 70],
]
)
expect(
result.flow.links.map((link) => [link.source, link.target, link.value])
).toEqual([
['group:default', 'model:claude-4-sonnet', 70],
['group:vip', 'model:gpt-4.1', 150],
['model:claude-4-sonnet', 'channel:101', 70],
['model:gpt-4.1', 'channel:101', 100],
['model:gpt-4.1', 'channel:102', 50],
['user:1', 'group:vip', 150],
['user:2', 'group:default', 70],
])
})
test('builds root user-node-token-group-model-channel flow', () => {
@@ -153,22 +149,21 @@ describe('dashboard flow data', () => {
role: 'root',
})
assert.deepEqual(
result.flow.links.map((link) => [link.source, link.target, link.value]),
[
['group:default', 'model:claude-4-sonnet', 3],
['group:vip', 'model:gpt-4.1', 3],
['model:claude-4-sonnet', 'channel:101', 3],
['model:gpt-4.1', 'channel:101', 2],
['model:gpt-4.1', 'channel:102', 1],
['node:node-a', 'token:11', 3],
['node:node-b', 'token:22', 3],
['token:11', 'group:vip', 3],
['token:22', 'group:default', 3],
['user:1', 'node:node-a', 3],
['user:2', 'node:node-b', 3],
]
)
expect(
result.flow.links.map((link) => [link.source, link.target, link.value])
).toEqual([
['group:default', 'model:claude-4-sonnet', 3],
['group:vip', 'model:gpt-4.1', 3],
['model:claude-4-sonnet', 'channel:101', 3],
['model:gpt-4.1', 'channel:101', 2],
['model:gpt-4.1', 'channel:102', 1],
['node:node-a', 'token:11', 3],
['node:node-b', 'token:22', 3],
['token:11', 'group:vip', 3],
['token:22', 'group:default', 3],
['user:1', 'node:node-a', 3],
['user:2', 'node:node-b', 3],
])
})
test('filters by selected users', () => {
@@ -177,15 +172,14 @@ describe('dashboard flow data', () => {
selectedUsers: ['user:2'],
})
assert.equal(result.summary.quota, 70)
assert.deepEqual(
result.flow.links.map((link) => [link.source, link.target, link.value]),
[
['group:default', 'model:claude-4-sonnet', 70],
['model:claude-4-sonnet', 'channel:101', 70],
['user:2', 'group:default', 70],
]
)
expect(result.summary.quota).toBe(70)
expect(
result.flow.links.map((link) => [link.source, link.target, link.value])
).toEqual([
['group:default', 'model:claude-4-sonnet', 70],
['model:claude-4-sonnet', 'channel:101', 70],
['user:2', 'group:default', 70],
])
})
test('filters rows by selected flow nodes', () => {
@@ -194,16 +188,15 @@ describe('dashboard flow data', () => {
selectedNodes: [{ kind: 'model', id: 'model:gpt-4.1' }],
})
assert.equal(result.summary.quota, 150)
assert.deepEqual(
result.flow.links.map((link) => [link.source, link.target, link.value]),
[
['group:vip', 'model:gpt-4.1', 150],
['model:gpt-4.1', 'channel:101', 100],
['model:gpt-4.1', 'channel:102', 50],
['user:1', 'group:vip', 150],
]
)
expect(result.summary.quota).toBe(150)
expect(
result.flow.links.map((link) => [link.source, link.target, link.value])
).toEqual([
['group:vip', 'model:gpt-4.1', 150],
['model:gpt-4.1', 'channel:101', 100],
['model:gpt-4.1', 'channel:102', 50],
['user:1', 'group:vip', 150],
])
})
test('combines node filters with OR inside a column and AND across columns', () => {
@@ -222,20 +215,19 @@ describe('dashboard flow data', () => {
],
})
assert.equal(sameColumn.summary.quota, 220)
assert.equal(crossColumn.summary.quota, 100)
assert.deepEqual(
expect(sameColumn.summary.quota).toBe(220)
expect(crossColumn.summary.quota).toBe(100)
expect(
crossColumn.flow.links.map((link) => [
link.source,
link.target,
link.value,
]),
[
['group:vip', 'model:gpt-4.1', 100],
['model:gpt-4.1', 'channel:101', 100],
['user:1', 'group:vip', 100],
]
)
])
).toEqual([
['group:vip', 'model:gpt-4.1', 100],
['model:gpt-4.1', 'channel:101', 100],
['user:1', 'group:vip', 100],
])
})
test('combines user and node filters', () => {
@@ -245,15 +237,14 @@ describe('dashboard flow data', () => {
selectedNodes: [{ kind: 'channel', id: 'channel:101' }],
})
assert.equal(result.summary.quota, 100)
assert.deepEqual(
result.flow.links.map((link) => [link.source, link.target, link.value]),
[
['group:vip', 'model:gpt-4.1', 100],
['model:gpt-4.1', 'channel:101', 100],
['user:1', 'group:vip', 100],
]
)
expect(result.summary.quota).toBe(100)
expect(
result.flow.links.map((link) => [link.source, link.target, link.value])
).toEqual([
['group:vip', 'model:gpt-4.1', 100],
['model:gpt-4.1', 'channel:101', 100],
['user:1', 'group:vip', 100],
])
})
test('reconnects links when a middle stage is hidden', () => {
@@ -262,20 +253,16 @@ describe('dashboard flow data', () => {
visibleStages: ['user', 'model', 'channel'],
})
assert.deepEqual(
result.flow.links.map((link) => [link.source, link.target, link.value]),
[
['model:claude-4-sonnet', 'channel:101', 70],
['model:gpt-4.1', 'channel:101', 100],
['model:gpt-4.1', 'channel:102', 50],
['user:1', 'model:gpt-4.1', 150],
['user:2', 'model:claude-4-sonnet', 70],
]
)
assert.equal(
result.flow.nodes.some((node) => node.kind === 'group'),
false
)
expect(
result.flow.links.map((link) => [link.source, link.target, link.value])
).toEqual([
['model:claude-4-sonnet', 'channel:101', 70],
['model:gpt-4.1', 'channel:101', 100],
['model:gpt-4.1', 'channel:102', 50],
['user:1', 'model:gpt-4.1', 150],
['user:2', 'model:claude-4-sonnet', 70],
])
expect(result.flow.nodes.some((node) => node.kind === 'group')).toBe(false)
})
test('ignores stage filters that would leave fewer than two columns', () => {
@@ -284,26 +271,24 @@ describe('dashboard flow data', () => {
visibleStages: ['model'],
})
assert.deepEqual(
result.flow.links.map((link) => [link.source, link.target, link.value]),
[
['group:vip', 'model:gpt-4.1', 150],
['token:11', 'group:vip', 150],
]
)
expect(
result.flow.links.map((link) => [link.source, link.target, link.value])
).toEqual([
['group:vip', 'model:gpt-4.1', 150],
['token:11', 'group:vip', 150],
])
})
test('builds user filter options with stable values', () => {
const options = buildFlowFilterOptions(rows, 'quota')
assert.deepEqual(
options.users.map((user) => [user.value, user.label, user.valueLabel]),
[
['user:1', 'alice', '150'],
['user:2', 'bob', '70'],
]
)
assert.notEqual(options.users[0].color, options.users[1].color)
expect(
options.users.map((user) => [user.value, user.label, user.valueLabel])
).toEqual([
['user:1', 'alice', '150'],
['user:2', 'bob', '70'],
])
expect(options.users[0].color).not.toBe(options.users[1].color)
})
test('builds node filter options without applying top limits', () => {
@@ -313,22 +298,20 @@ describe('dashboard flow data', () => {
overflowMode: 'aggregate',
})
assert.equal(
expect(
result.filterOptions.nodes.some(
(option) => option.kind === 'model' && option.value === 'model:model-c'
),
true
)
assert.deepEqual(
)
).toBe(true)
expect(
result.filterOptions.nodes
.filter((option) => option.kind === 'model')
.map((option) => [option.value, option.valueLabel]),
[
['model:model-a', '100'],
['model:model-b', '80'],
['model:model-c', '10'],
]
)
.map((option) => [option.value, option.valueLabel])
).toEqual([
['model:model-a', '100'],
['model:model-b', '80'],
['model:model-c', '10'],
])
})
test('facets node filter options by selected nodes from other columns', () => {
@@ -338,30 +321,27 @@ describe('dashboard flow data', () => {
})
const nodeOptions = result.filterOptions.nodes
assert.deepEqual(
expect(
nodeOptions
.filter((option) => option.kind === 'node')
.map((option) => [option.value, option.valueLabel]),
[
['node:node-a', '150'],
['node:node-b', '70'],
]
)
assert.deepEqual(
.map((option) => [option.value, option.valueLabel])
).toEqual([
['node:node-a', '150'],
['node:node-b', '70'],
])
expect(
nodeOptions
.filter((option) => option.kind === 'token')
.map((option) => [option.value, option.valueLabel]),
[['token:11', '150']]
)
assert.deepEqual(
.map((option) => [option.value, option.valueLabel])
).toEqual([['token:11', '150']])
expect(
nodeOptions
.filter((option) => option.kind === 'channel')
.map((option) => [option.value, option.valueLabel]),
[
['channel:101', '100'],
['channel:102', '50'],
]
)
.map((option) => [option.value, option.valueLabel])
).toEqual([
['channel:101', '100'],
['channel:102', '50'],
])
})
test('keeps same-column node options available for OR filtering', () => {
@@ -370,24 +350,22 @@ describe('dashboard flow data', () => {
selectedNodes: [{ kind: 'model', id: 'model:gpt-4.1' }],
})
assert.deepEqual(
expect(
result.filterOptions.nodes
.filter((option) => option.kind === 'model')
.map((option) => [option.value, option.valueLabel]),
[
['model:gpt-4.1', '150'],
['model:claude-4-sonnet', '70'],
]
)
assert.deepEqual(
.map((option) => [option.value, option.valueLabel])
).toEqual([
['model:gpt-4.1', '150'],
['model:claude-4-sonnet', '70'],
])
expect(
result.filterOptions.nodes
.filter((option) => option.kind === 'channel')
.map((option) => [option.value, option.valueLabel]),
[
['channel:101', '100'],
['channel:102', '50'],
]
)
.map((option) => [option.value, option.valueLabel])
).toEqual([
['channel:101', '100'],
['channel:102', '50'],
])
})
test('combines user filters with faceted node filter options', () => {
@@ -397,22 +375,20 @@ describe('dashboard flow data', () => {
selectedNodes: [{ kind: 'channel', id: 'channel:101' }],
})
assert.equal(result.summary.quota, 100)
assert.deepEqual(
expect(result.summary.quota).toBe(100)
expect(
result.filterOptions.nodes
.filter((option) => option.kind === 'model')
.map((option) => [option.value, option.valueLabel]),
[['model:gpt-4.1', '100']]
)
assert.deepEqual(
.map((option) => [option.value, option.valueLabel])
).toEqual([['model:gpt-4.1', '100']])
expect(
result.filterOptions.nodes
.filter((option) => option.kind === 'channel')
.map((option) => [option.value, option.valueLabel]),
[
['channel:101', '100'],
['channel:102', '50'],
]
)
.map((option) => [option.value, option.valueLabel])
).toEqual([
['channel:101', '100'],
['channel:102', '50'],
])
})
test('aggregates overflow nodes into per-column Other buckets', () => {
@@ -434,18 +410,18 @@ describe('dashboard flow data', () => {
.filter((link) => link.source.startsWith('user:'))
.reduce((sum, link) => sum + link.value, 0)
assert.equal(result.summary.quota, 190)
assert.equal(firstStepTotal, 190)
assert.equal(otherUser?.label, 'Other user')
assert.equal(otherFirstStepLink?.value, 10)
assert.equal(nodeIds.has('user:3'), false)
assert.equal(nodeIds.has('group:free'), false)
assert.equal(nodeIds.has('model:model-c'), false)
assert.equal(nodeIds.has('channel:203'), false)
assert.equal(nodeIds.has('user:__other__'), true)
assert.equal(nodeIds.has('group:__other__'), true)
assert.equal(nodeIds.has('model:__other__'), true)
assert.equal(nodeIds.has('channel:__other__'), true)
expect(result.summary.quota).toBe(190)
expect(firstStepTotal).toBe(190)
expect(otherUser?.label).toBe('Other user')
expect(otherFirstStepLink?.value).toBe(10)
expect(nodeIds.has('user:3')).toBe(false)
expect(nodeIds.has('group:free')).toBe(false)
expect(nodeIds.has('model:model-c')).toBe(false)
expect(nodeIds.has('channel:203')).toBe(false)
expect(nodeIds.has('user:__other__')).toBe(true)
expect(nodeIds.has('group:__other__')).toBe(true)
expect(nodeIds.has('model:__other__')).toBe(true)
expect(nodeIds.has('channel:__other__')).toBe(true)
})
test('hides overflow paths when overflow mode is hide', () => {
@@ -460,11 +436,11 @@ describe('dashboard flow data', () => {
.filter((link) => link.source.startsWith('user:'))
.reduce((sum, link) => sum + link.value, 0)
assert.equal(result.summary.quota, 190)
assert.equal(firstStepTotal, 180)
assert.equal(nodeIds.has('user:3'), false)
assert.equal(nodeIds.has('user:__other__'), false)
assert.equal(nodeIds.has('model:__other__'), false)
expect(result.summary.quota).toBe(190)
expect(firstStepTotal).toBe(180)
expect(nodeIds.has('user:3')).toBe(false)
expect(nodeIds.has('user:__other__')).toBe(false)
expect(nodeIds.has('model:__other__')).toBe(false)
})
test('ranks top nodes using the selected flow metric', () => {
@@ -484,18 +460,11 @@ describe('dashboard flow data', () => {
overflowMode: 'aggregate',
})
assert.equal(
byQuota.flow.nodes.some((node) => node.id === 'user:1'),
true
)
assert.equal(
byRequests.flow.nodes.some((node) => node.id === 'user:2'),
true
)
assert.equal(
byTokens.flow.nodes.some((node) => node.id === 'user:3'),
expect(byQuota.flow.nodes.some((node) => node.id === 'user:1')).toBe(true)
expect(byRequests.flow.nodes.some((node) => node.id === 'user:2')).toBe(
true
)
expect(byTokens.flow.nodes.some((node) => node.id === 'user:3')).toBe(true)
})
test('applies top limits only to visible stages', () => {
@@ -507,19 +476,18 @@ describe('dashboard flow data', () => {
})
const nodeIds = new Set(result.flow.nodes.map((node) => node.id))
assert.equal(nodeIds.has('user:1'), true)
assert.equal(nodeIds.has('user:__other__'), true)
assert.equal(nodeIds.has('model:model-a'), true)
assert.equal(nodeIds.has('model:__other__'), true)
assert.equal(nodeIds.has('group:__other__'), false)
assert.equal(nodeIds.has('channel:__other__'), false)
assert.deepEqual(
result.flow.links.map((link) => [link.source, link.target, link.value]),
[
['user:__other__', 'model:__other__', 90],
['user:1', 'model:model-a', 100],
]
)
expect(nodeIds.has('user:1')).toBe(true)
expect(nodeIds.has('user:__other__')).toBe(true)
expect(nodeIds.has('model:model-a')).toBe(true)
expect(nodeIds.has('model:__other__')).toBe(true)
expect(nodeIds.has('group:__other__')).toBe(false)
expect(nodeIds.has('channel:__other__')).toBe(false)
expect(
result.flow.links.map((link) => [link.source, link.target, link.value])
).toEqual([
['user:__other__', 'model:__other__', 90],
['user:1', 'model:model-a', 100],
])
})
test('applies top limits after node filters', () => {
@@ -531,17 +499,16 @@ describe('dashboard flow data', () => {
})
const nodeIds = new Set(result.flow.nodes.map((node) => node.id))
assert.equal(result.summary.quota, 10)
assert.equal(nodeIds.has('model:model-c'), true)
assert.equal(nodeIds.has('model:__other__'), false)
assert.deepEqual(
result.flow.links.map((link) => [link.source, link.target, link.value]),
[
['group:free', 'model:model-c', 10],
['model:model-c', 'channel:203', 10],
['user:3', 'group:free', 10],
]
)
expect(result.summary.quota).toBe(10)
expect(nodeIds.has('model:model-c')).toBe(true)
expect(nodeIds.has('model:__other__')).toBe(false)
expect(
result.flow.links.map((link) => [link.source, link.target, link.value])
).toEqual([
['group:free', 'model:model-c', 10],
['model:model-c', 'channel:203', 10],
['user:3', 'group:free', 10],
])
})
test('ignores selected node filters for hidden stages', () => {
@@ -551,9 +518,8 @@ describe('dashboard flow data', () => {
selectedNodes: [{ kind: 'group', id: 'group:vip' }],
})
assert.equal(result.summary.quota, 220)
assert.equal(
result.flow.nodes.some((node) => node.id === 'group:vip'),
expect(result.summary.quota).toBe(220)
expect(result.flow.nodes.some((node) => node.id === 'group:vip')).toBe(
false
)
})
@@ -576,35 +542,35 @@ describe('dashboard flow data', () => {
])
)
assert.deepEqual(nodeState.get('user:1'), {
expect(nodeState.get('user:1')).toEqual({
highlighted: true,
dimmed: false,
})
assert.deepEqual(nodeState.get('node:node-a'), {
expect(nodeState.get('node:node-a')).toEqual({
highlighted: true,
dimmed: false,
})
assert.deepEqual(nodeState.get('model:gpt-4.1'), {
expect(nodeState.get('model:gpt-4.1')).toEqual({
highlighted: true,
dimmed: false,
})
assert.deepEqual(nodeState.get('channel:101'), {
expect(nodeState.get('channel:101')).toEqual({
highlighted: true,
dimmed: false,
})
assert.deepEqual(nodeState.get('user:2'), {
expect(nodeState.get('user:2')).toEqual({
highlighted: false,
dimmed: true,
})
assert.deepEqual(linkState.get('user:1->node:node-a'), {
expect(linkState.get('user:1->node:node-a')).toEqual({
highlighted: true,
dimmed: false,
})
assert.deepEqual(linkState.get('model:gpt-4.1->channel:101'), {
expect(linkState.get('model:gpt-4.1->channel:101')).toEqual({
highlighted: true,
dimmed: false,
})
assert.deepEqual(linkState.get('model:claude-4-sonnet->channel:101'), {
expect(linkState.get('model:claude-4-sonnet->channel:101')).toEqual({
highlighted: false,
dimmed: true,
})
@@ -628,23 +594,23 @@ describe('dashboard flow data', () => {
])
)
assert.deepEqual(linkState.get('model:gpt-4.1->channel:101'), {
expect(linkState.get('model:gpt-4.1->channel:101')).toEqual({
highlighted: true,
dimmed: false,
})
assert.deepEqual(linkState.get('model:gpt-4.1->channel:102'), {
expect(linkState.get('model:gpt-4.1->channel:102')).toEqual({
highlighted: false,
dimmed: true,
})
assert.deepEqual(nodeState.get('user:1'), {
expect(nodeState.get('user:1')).toEqual({
highlighted: true,
dimmed: false,
})
assert.deepEqual(nodeState.get('node:node-a'), {
expect(nodeState.get('node:node-a')).toEqual({
highlighted: true,
dimmed: false,
})
assert.deepEqual(nodeState.get('user:2'), {
expect(nodeState.get('user:2')).toEqual({
highlighted: false,
dimmed: true,
})
@@ -686,11 +652,11 @@ describe('dashboard flow data', () => {
(link) => link.source === 'user:2' && link.target === 'group:vip'
)
assert.equal(sharedLink?.value, 150)
assert.equal(sharedLink?.highlighted, true)
assert.equal(sharedLink?.dimmed, false)
assert.equal(inactiveUserLink?.highlighted, false)
assert.equal(inactiveUserLink?.dimmed, true)
expect(sharedLink?.value).toBe(150)
expect(sharedLink?.highlighted).toBe(true)
expect(sharedLink?.dimmed).toBe(false)
expect(inactiveUserLink?.highlighted).toBe(false)
expect(inactiveUserLink?.dimmed).toBe(true)
})
test('does not emit highlight states without a visible active node', () => {
@@ -703,30 +669,26 @@ describe('dashboard flow data', () => {
activeNode: { kind: 'user', id: 'user:1' },
})
assert.equal(
expect(
withoutActive.flow.nodes.every(
(node) => node.highlighted === undefined && node.dimmed === undefined
),
true
)
assert.equal(
)
).toBe(true)
expect(
withoutActive.flow.links.every(
(link) => link.highlighted === undefined && link.dimmed === undefined
),
true
)
assert.equal(
)
).toBe(true)
expect(
hiddenActive.flow.nodes.every(
(node) => node.highlighted === undefined && node.dimmed === undefined
),
true
)
assert.equal(
)
).toBe(true)
expect(
hiddenActive.flow.links.every(
(link) => link.highlighted === undefined && link.dimmed === undefined
),
true
)
)
).toBe(true)
})
test('builds Sankey spec with quota token request tooltips', () => {
@@ -743,19 +705,19 @@ describe('dashboard flow data', () => {
link.source === 'user:1' && link.target === 'node:node-a'
)
assert.equal(flowSpec.type, 'sankey')
assert.equal(flowSpec.title.text, 'Flow')
assert.deepEqual(flowSpec.emphasis, { enable: false })
assert.equal(flowSpec.tooltip.mark.visible({ datum: aliceNode }), true)
assert.equal(flowSpec.tooltip.mark.visible({ datum: userNodeLink }), true)
assert.equal(flowSpec.animation, false)
assert.equal(values.nodes.length, 6)
assert.equal(values.links.length, 5)
assert.equal(aliceNode.name, 'alice')
assert.match(userNodeLink.linkColor, /^rgba\(/)
expect(flowSpec.type).toBe('sankey')
expect(flowSpec.title.text).toBe('Flow')
expect(flowSpec.emphasis).toEqual({ enable: false })
expect(flowSpec.tooltip.mark.visible({ datum: aliceNode })).toBe(true)
expect(flowSpec.tooltip.mark.visible({ datum: userNodeLink })).toBe(true)
expect(flowSpec.animation).toBe(false)
expect(values.nodes.length).toBe(6)
expect(values.links.length).toBe(5)
expect(aliceNode.name).toBe('alice')
expect(userNodeLink.linkColor).toMatch(/^rgba\(/)
const tooltipRows = flowSpec.tooltip.mark.content
assert.deepEqual(
expect(
tooltipRows
.filter((row: Record<string, unknown>) =>
typeof row.visible === 'function'
@@ -767,14 +729,13 @@ describe('dashboard flow data', () => {
typeof row.value === 'function'
? row.value({ datum: userNodeLink })
: row.value,
]),
[
['Quota', '100'],
['Tokens', '40'],
['Requests', '2'],
['Share', '100.0%'],
]
)
])
).toEqual([
['Quota', '100'],
['Tokens', '40'],
['Requests', '2'],
['Share', '100.0%'],
])
})
test('maps active flow highlight states into the Sankey spec', () => {
@@ -801,15 +762,15 @@ describe('dashboard flow data', () => {
const nodeOpacity = flowSpec.node.style.fillOpacity
const linkOpacity = flowSpec.link.style.fillOpacity
assert.deepEqual(flowSpec.emphasis, { enable: false })
assert.equal(aliceNode.highlighted, true)
assert.equal(bobNode.dimmed, true)
assert.equal(highlightedLink.highlighted, true)
assert.equal(dimmedLink.dimmed, true)
assert.equal(nodeOpacity(aliceNode), 1)
assert.equal(nodeOpacity(bobNode), 0.18)
assert.equal(linkOpacity(highlightedLink), 0.86)
assert.equal(linkOpacity(dimmedLink), 0.08)
assert.equal(highlightedLink.zIndex > dimmedLink.zIndex, true)
expect(flowSpec.emphasis).toEqual({ enable: false })
expect(aliceNode.highlighted).toBe(true)
expect(bobNode.dimmed).toBe(true)
expect(highlightedLink.highlighted).toBe(true)
expect(dimmedLink.dimmed).toBe(true)
expect(nodeOpacity(aliceNode)).toBe(1)
expect(nodeOpacity(bobNode)).toBe(0.18)
expect(linkOpacity(highlightedLink)).toBe(0.86)
expect(linkOpacity(dimmedLink)).toBe(0.08)
expect(highlightedLink.zIndex > dimmedLink.zIndex).toBe(true)
})
})
@@ -16,39 +16,9 @@ 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 { render } from '@testing-library/react'
import { describe, expect, test } from 'vitest'
import { Window } from 'happy-dom'
const domWindow = new Window()
const domGlobals = [
'window',
'document',
'navigator',
'HTMLElement',
'HTMLButtonElement',
'SVGElement',
'Node',
'Element',
'Event',
'CustomEvent',
'MutationObserver',
'ResizeObserver',
'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 { createInstance } = await import('i18next')
const { I18nextProvider, initReactI18next } = await import('react-i18next')
const { TooltipProvider } = await import('@/components/ui/tooltip')
@@ -70,11 +40,6 @@ await i18n.use(initReactI18next).init({
},
})
const reactTestGlobals = globalThis as typeof globalThis & {
IS_REACT_ACT_ENVIRONMENT?: boolean
}
reactTestGlobals.IS_REACT_ACT_ENVIRONMENT = true
function CellHarness(props: {
group: string
ratio?: number | string
@@ -96,141 +61,93 @@ function CellHarness(props: {
}
describe('API key group table cell', () => {
after(() => {
domWindow.close()
})
test('renders two unclipped rings and a localized Auto ratio when API data uses a nonlocalized string', async () => {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
await act(async () =>
root.render(
<CellHarness
group='auto'
ratio='自动'
crossGroupRetry
shouldReduceMotion={false}
/>
)
test('renders an unclipped ring and a localized Auto ratio when API data uses a nonlocalized string', () => {
const { container } = render(
<CellHarness
group='auto'
ratio='自动'
crossGroupRetry
shouldReduceMotion={false}
/>
)
const badgeCell = container.querySelector<HTMLElement>(
'[data-api-key-group-cell="auto"]'
)
assert.ok(badgeCell)
assert.equal(badgeCell.classList.contains('overflow-visible'), true)
assert.equal(badgeCell.classList.contains('overflow-hidden'), false)
expect(badgeCell).toHaveClass('overflow-visible')
expect(badgeCell).not.toHaveClass('overflow-hidden')
const frames = container.querySelectorAll('[data-auto-group-frame]')
const movingRings = container.querySelectorAll(
'[data-auto-group-flow-border]'
)
assert.equal(frames.length, 2)
assert.equal(movingRings.length, 2)
expect(frames.length).toBe(1)
expect(movingRings.length).toBe(1)
for (const frame of frames) {
assert.equal(frame.classList.contains('relative'), true)
assert.equal(frame.classList.contains('overflow-visible'), true)
assert.equal(frame.classList.contains('rounded-4xl'), true)
assert.equal(frame.classList.contains('p-px'), true)
expect(frame).toHaveClass(
'relative',
'overflow-visible',
'rounded-4xl',
'p-px'
)
}
const ratio = container.querySelector<HTMLElement>(
'[data-auto-group-effect="ratio"]'
)
assert.ok(ratio)
assert.equal(ratio.textContent, 'Auto Ratio')
assert.equal(ratio.textContent?.includes('x'), false)
assert.equal(container.textContent?.includes('自动'), false)
assert.equal(container.textContent?.includes('Cross-group'), true)
expect(ratio).toHaveTextContent('Auto Ratio')
expect(ratio).not.toHaveTextContent('x')
expect(container).not.toHaveTextContent('自动')
expect(container).toHaveTextContent('Cross-group')
const crossGroupBadge = [
...container.querySelectorAll<HTMLElement>('[data-slot="status-badge"]'),
].find((badge) => badge.textContent === 'Cross-group')
assert.ok(crossGroupBadge)
assert.equal(crossGroupBadge.closest('[data-auto-group-frame]'), null)
await act(async () => root.unmount())
container.remove()
expect(crossGroupBadge).not.toBeUndefined()
expect(crossGroupBadge?.closest('[data-auto-group-frame]')).toBeNull()
})
test('keeps static Auto frames but omits both moving layers for reduced motion', async () => {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
await act(async () =>
root.render(<CellHarness group='auto' ratio='Auto' shouldReduceMotion />)
test('keeps the static Auto ratio frame but omits its moving layer for reduced motion', () => {
const { container } = render(
<CellHarness group='auto' ratio='Auto' shouldReduceMotion />
)
assert.equal(
container.querySelectorAll('[data-auto-group-frame]').length,
2
)
assert.equal(
container.querySelectorAll('[data-auto-group-flow-border]').length,
0
)
await act(async () => root.unmount())
container.remove()
expect(container.querySelectorAll('[data-auto-group-frame]').length).toBe(1)
expect(
container.querySelectorAll('[data-auto-group-flow-border]').length
).toBe(0)
})
test('shows only the Auto badge when ratio data is unavailable', async () => {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
await act(async () =>
root.render(<CellHarness group='auto' shouldReduceMotion={false} />)
test('shows only the cross-group badge when ratio data is unavailable', () => {
const { container } = render(
<CellHarness group='auto' shouldReduceMotion={false} />
)
assert.equal(
container.querySelectorAll('[data-auto-group-frame]').length,
1
)
assert.equal(
container.querySelectorAll('[data-auto-group-flow-border]').length,
1
)
assert.equal(
container.querySelector('[data-auto-group-effect="ratio"]'),
expect(container.querySelectorAll('[data-auto-group-frame]').length).toBe(0)
expect(
container.querySelectorAll('[data-auto-group-flow-border]').length
).toBe(0)
expect(container.querySelector('[data-auto-group-effect="ratio"]')).toBe(
null
)
assert.equal(container.textContent?.includes('Auto'), true)
assert.equal(container.textContent?.includes('Ratio'), false)
await act(async () => root.unmount())
container.remove()
expect(container).toHaveTextContent('Cross-group')
expect(container).not.toHaveTextContent('Auto')
expect(container).not.toHaveTextContent('Ratio')
})
test('narrows normal group ratios to numbers and never applies Auto rings', async () => {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
await act(async () =>
root.render(
<CellHarness group='vip' ratio='自动' shouldReduceMotion={false} />
)
test('narrows normal group ratios to numbers and never applies Auto rings', () => {
const { container, rerender } = render(
<CellHarness group='vip' ratio='自动' shouldReduceMotion={false} />
)
assert.equal(container.textContent?.includes('vip'), true)
assert.equal(container.textContent?.includes('自动'), false)
assert.equal(container.querySelector('[data-auto-group-frame]'), null)
assert.equal(container.querySelector('[data-auto-group-flow-border]'), null)
expect(container).toHaveTextContent('vip')
expect(container).not.toHaveTextContent('自动')
expect(container.querySelector('[data-auto-group-frame]')).toBe(null)
expect(container.querySelector('[data-auto-group-flow-border]')).toBe(null)
await act(async () =>
root.render(
<CellHarness group='vip' ratio={3} shouldReduceMotion={false} />
)
)
rerender(<CellHarness group='vip' ratio={3} shouldReduceMotion={false} />)
assert.equal(container.textContent?.includes('3x'), true)
assert.equal(container.querySelector('[data-auto-group-frame]'), null)
await act(async () => root.unmount())
container.remove()
expect(container).toHaveTextContent('3x')
expect(container.querySelector('[data-auto-group-frame]')).toBe(null)
})
})
@@ -16,58 +16,26 @@ 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 { Window } from 'happy-dom'
const domWindow = new Window()
const domGlobals = [
'window',
'document',
'navigator',
'HTMLElement',
'HTMLButtonElement',
'HTMLInputElement',
'SVGElement',
'Node',
'Element',
'Event',
'KeyboardEvent',
'PointerEvent',
'CustomEvent',
'MutationObserver',
'ResizeObserver',
'requestAnimationFrame',
'cancelAnimationFrame',
'getComputedStyle',
] as const
for (const key of domGlobals) {
Object.defineProperty(globalThis, key, {
configurable: true,
value: domWindow[key],
})
}
import { fireEvent, render, screen, within } from '@testing-library/react'
import { describe, expect, test } from 'vitest'
let shouldReduceMotion = false
const reducedMotionMediaQuery = domWindow.matchMedia('(prefers-reduced-motion)')
const reducedMotionMediaQuery = window.matchMedia('(prefers-reduced-motion)')
Object.defineProperty(reducedMotionMediaQuery, 'matches', {
configurable: true,
get: () => shouldReduceMotion,
})
Object.defineProperty(domWindow, 'matchMedia', {
Object.defineProperty(window, 'matchMedia', {
configurable: true,
value: () => reducedMotionMediaQuery,
})
function setReducedMotion(value: boolean) {
shouldReduceMotion = value
reducedMotionMediaQuery.dispatchEvent(new domWindow.Event('change'))
reducedMotionMediaQuery.dispatchEvent(new Event('change'))
}
const { act, useState } = await import('react')
const { createRoot } = await import('react-dom/client')
const { useState } = await import('react')
const { createInstance } = await import('i18next')
const { I18nextProvider, initReactI18next } = await import('react-i18next')
const { ApiKeyGroupCombobox } = await import('../api-key-group-combobox')
@@ -88,11 +56,6 @@ await i18n.use(initReactI18next).init({
},
})
const reactTestGlobals = globalThis as typeof globalThis & {
IS_REACT_ACT_ENVIRONMENT?: boolean
}
reactTestGlobals.IS_REACT_ACT_ENVIRONMENT = true
const options = [
{
value: 'auto',
@@ -119,176 +82,133 @@ function Harness(props: { initialValue: string }) {
)
}
function getTrigger(container: ParentNode): HTMLButtonElement {
const trigger = container.querySelector<HTMLButtonElement>(
'button[role="combobox"]'
)
assert.ok(trigger)
return trigger
function getTrigger(): HTMLButtonElement {
return screen.getByRole('combobox')
}
function getCommandItem(label: string): HTMLElement {
const item = [
...document.querySelectorAll<HTMLElement>('[data-slot="command-item"]'),
].find((candidate) => candidate.textContent?.includes(label))
assert.ok(item)
if (!item) {
throw new Error(`Expected command item containing "${label}"`)
}
return item
}
describe('API key group combobox Auto effect', () => {
after(() => {
domWindow.close()
})
test('rings the selected Auto trigger and its localized ratio without rendering the API ratio text', async () => {
test('rings the selected Auto trigger and its localized ratio without rendering the API ratio text', () => {
setReducedMotion(false)
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
render(<Harness initialValue='auto' />)
await act(async () => root.render(<Harness initialValue='auto' />))
const trigger = getTrigger(container)
assert.equal(trigger.getAttribute('aria-expanded'), 'false')
assert.equal(trigger.dataset.autoGroupEffect, 'trigger')
assert.equal(trigger.classList.contains('bg-linear-to-r'), false)
assert.equal(trigger.classList.contains('overflow-hidden'), false)
assert.equal(trigger.classList.contains('overflow-visible'), true)
const trigger = getTrigger()
expect(trigger).toHaveAttribute('aria-expanded', 'false')
expect(trigger).toHaveAttribute('data-auto-group-effect', 'trigger')
expect(trigger).not.toHaveClass('bg-linear-to-r', 'overflow-hidden')
expect(trigger).toHaveClass('overflow-visible')
const triggerFlowBorder = trigger.querySelector<HTMLElement>(
'[data-auto-group-flow-border]'
)
assert.ok(triggerFlowBorder)
assert.equal(triggerFlowBorder.getAttribute('aria-hidden'), 'true')
assert.equal(
triggerFlowBorder.classList.contains('pointer-events-none'),
true
)
assert.equal(
triggerFlowBorder.classList.contains('auto-group-flow-border'),
true
expect(triggerFlowBorder).toHaveAttribute('aria-hidden', 'true')
expect(triggerFlowBorder).toHaveClass(
'pointer-events-none',
'auto-group-flow-border'
)
const triggerRatio = trigger.querySelector<HTMLElement>(
'[data-auto-group-effect="ratio"]'
)
assert.ok(triggerRatio)
assert.equal(triggerRatio.textContent, 'Auto Ratio')
assert.equal(triggerRatio.textContent?.includes('Auto'), true)
assert.equal(triggerRatio.textContent?.includes('x'), false)
assert.equal(trigger.textContent?.includes('自动'), false)
assert.equal(triggerRatio.classList.contains('relative'), true)
assert.equal(triggerRatio.classList.contains('overflow-visible'), true)
assert.equal(triggerRatio.classList.contains('rounded-4xl'), true)
assert.ok(triggerRatio.querySelector('[data-auto-group-flow-border]'))
expect(triggerRatio).toHaveTextContent('Auto Ratio')
expect(triggerRatio).not.toHaveTextContent('x')
expect(trigger).not.toHaveTextContent('自动')
expect(triggerRatio).toHaveClass(
'relative',
'overflow-visible',
'rounded-4xl'
)
expect(
triggerRatio?.querySelector('[data-auto-group-flow-border]')
).toBeInTheDocument()
await act(async () => trigger.click())
assert.equal(trigger.getAttribute('aria-expanded'), 'true')
fireEvent.click(trigger)
expect(trigger).toHaveAttribute('aria-expanded', 'true')
const autoOption = getCommandItem('Global automatic routing')
assert.equal(autoOption.dataset.autoGroupEffect, 'option')
assert.equal(autoOption.getAttribute('aria-selected'), 'true')
assert.equal(autoOption.classList.contains('bg-linear-to-r'), false)
assert.equal(autoOption.classList.contains('overflow-visible'), true)
assert.ok(autoOption.querySelector('[data-auto-group-flow-border]'))
expect(autoOption).toHaveAttribute('data-auto-group-effect', 'option')
expect(autoOption).toHaveAttribute('aria-selected', 'true')
expect(autoOption).not.toHaveClass('bg-linear-to-r')
expect(autoOption).toHaveClass('overflow-visible')
expect(
autoOption.querySelector('[data-auto-group-flow-border]')
).toBeInTheDocument()
const optionRatio = autoOption.querySelector<HTMLElement>(
'[data-auto-group-effect="ratio"]'
)
assert.ok(optionRatio)
assert.equal(optionRatio.textContent, 'Auto Ratio')
assert.ok(optionRatio.querySelector('[data-auto-group-flow-border]'))
expect(optionRatio).toHaveTextContent('Auto Ratio')
expect(
optionRatio?.querySelector('[data-auto-group-flow-border]')
).toBeInTheDocument()
const defaultOption = getCommandItem('User group')
assert.equal(defaultOption.hasAttribute('data-auto-group-effect'), false)
assert.equal(
defaultOption.querySelector('[data-auto-group-flow-border]'),
expect(defaultOption).not.toHaveAttribute('data-auto-group-effect')
expect(defaultOption.querySelector('[data-auto-group-flow-border]')).toBe(
null
)
assert.equal(defaultOption.textContent?.includes('1x Ratio'), true)
assert.equal(
defaultOption.querySelector('[data-auto-group-effect="ratio"]'),
null
)
await act(async () => root.unmount())
container.remove()
expect(defaultOption).toHaveTextContent('1x Ratio')
expect(
defaultOption.querySelector('[data-auto-group-effect="ratio"]')
).toBe(null)
})
test('keeps search and selection behavior while leaving normal groups unstyled', async () => {
setReducedMotion(false)
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
const { container } = render(<Harness initialValue='auto' />)
await act(async () => root.render(<Harness initialValue='auto' />))
const trigger = getTrigger()
fireEvent.click(trigger)
const trigger = getTrigger(container)
await act(async () => trigger.click())
const searchInput = document.querySelector<HTMLInputElement>(
'input[placeholder="Search..."]'
)
assert.ok(searchInput)
await act(async () => {
const valueSetter = Object.getOwnPropertyDescriptor(
domWindow.HTMLInputElement.prototype,
'value'
)?.set
assert.ok(valueSetter)
valueSetter.call(searchInput, 'vip')
searchInput.dispatchEvent(
new domWindow.Event('input', { bubbles: true }) as unknown as Event
)
fireEvent.input(screen.getByPlaceholderText('Search...'), {
target: { value: 'vip' },
})
const visibleOptions = [
...document.querySelectorAll<HTMLElement>('[data-slot="command-item"]'),
]
assert.equal(
expect(
visibleOptions.some((option) =>
option.textContent?.includes('Global automatic routing')
),
false
)
)
).toBe(false)
const vipOption = getCommandItem('Priority group')
await act(async () => vipOption.click())
fireEvent.click(vipOption)
assert.equal(
container.querySelector('[data-testid="selected-group"]')?.textContent,
expect(within(container).getByTestId('selected-group')).toHaveTextContent(
'vip'
)
assert.equal(trigger.getAttribute('aria-expanded'), 'false')
assert.equal(trigger.hasAttribute('data-auto-group-effect'), false)
assert.equal(trigger.querySelector('[data-auto-group-flow-border]'), null)
await act(async () => root.unmount())
container.remove()
expect(trigger).toHaveAttribute('aria-expanded', 'false')
expect(trigger).not.toHaveAttribute('data-auto-group-effect')
expect(trigger.querySelector('[data-auto-group-flow-border]')).toBe(null)
})
test('preserves the static Auto treatment but omits moving layers for reduced motion', async () => {
setReducedMotion(true)
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
render(<Harness initialValue='auto' />)
await act(async () => root.render(<Harness initialValue='auto' />))
const trigger = getTrigger()
expect(trigger).toHaveAttribute('data-auto-group-effect', 'trigger')
expect(trigger.querySelector('[data-auto-group-flow-border]')).toBe(null)
expect(
trigger.querySelector('[data-auto-group-effect="ratio"]')
).toBeInTheDocument()
const trigger = getTrigger(container)
assert.equal(trigger.dataset.autoGroupEffect, 'trigger')
assert.equal(trigger.querySelector('[data-auto-group-flow-border]'), null)
assert.ok(trigger.querySelector('[data-auto-group-effect="ratio"]'))
await act(async () => trigger.click())
fireEvent.click(trigger)
const autoOption = getCommandItem('Global automatic routing')
assert.equal(autoOption.dataset.autoGroupEffect, 'option')
assert.equal(
autoOption.querySelector('[data-auto-group-flow-border]'),
null
)
assert.ok(autoOption.querySelector('[data-auto-group-effect="ratio"]'))
await act(async () => root.unmount())
container.remove()
expect(autoOption).toHaveAttribute('data-auto-group-effect', 'option')
expect(autoOption.querySelector('[data-auto-group-flow-border]')).toBe(null)
expect(
autoOption.querySelector('[data-auto-group-effect="ratio"]')
).toBeInTheDocument()
setReducedMotion(false)
})
})
@@ -16,45 +16,9 @@ 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, afterEach, describe, test } from 'node:test'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterEach, describe, expect, test } from 'vitest'
import { Window } from 'happy-dom'
const domWindow = new Window()
const domGlobals = [
'window',
'document',
'navigator',
'HTMLElement',
'HTMLButtonElement',
'HTMLInputElement',
'HTMLFormElement',
'SVGElement',
'Node',
'Element',
'Event',
'KeyboardEvent',
'PointerEvent',
'MouseEvent',
'FocusEvent',
'CustomEvent',
'MutationObserver',
'ResizeObserver',
'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 { createInstance } = await import('i18next')
const { I18nextProvider, initReactI18next } = await import('react-i18next')
const { QueryClient, QueryClientProvider } =
@@ -69,20 +33,13 @@ await i18n.use(initReactI18next).init({
resources: { en: { translation: {} } },
})
const reactTestGlobals = globalThis as typeof globalThis & {
IS_REACT_ACT_ENVIRONMENT?: boolean
}
reactTestGlobals.IS_REACT_ACT_ENVIRONMENT = true
type ApiMethod = (url: string, data?: unknown) => Promise<{ data: unknown }>
type MockableApi = {
get: ApiMethod
post: ApiMethod
}
type RenderedDrawer = {
host: HTMLDivElement
queryClient: InstanceType<typeof QueryClient>
root: ReturnType<typeof createRoot>
}
const apiClient = api as unknown as MockableApi
@@ -120,44 +77,14 @@ function installApiFixtures(createdPayloads: Array<Record<string, unknown>>) {
}
}
apiClient.post = async (url, data) => {
assert.equal(url, '/api/token/')
assert.ok(data && typeof data === 'object')
expect(url).toBe('/api/token/')
expect(data && typeof data === 'object').toBeTruthy()
createdPayloads.push(data as Record<string, unknown>)
return { data: { success: true, data: {} } }
}
}
async function waitForCondition(
condition: () => boolean,
failureMessage: string
): Promise<void> {
if (condition()) return
await new Promise<void>((resolve, reject) => {
const observer = new MutationObserver(() => {
if (!condition()) return
clearTimeout(timeoutId)
observer.disconnect()
resolve()
})
const timeoutId = setTimeout(() => {
observer.disconnect()
reject(new Error(`${failureMessage}: ${document.body.textContent}`))
}, 1500)
observer.observe(document, {
attributes: true,
childList: true,
characterData: true,
subtree: true,
})
})
}
async function renderCreateDrawer(): Promise<void> {
const host = document.createElement('div')
document.body.append(host)
const root = createRoot(host)
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
})
@@ -192,43 +119,49 @@ async function renderCreateDrawer(): Promise<void> {
},
{ updatedAt: freshAt }
)
renderedDrawer = { host, queryClient, root }
renderedDrawer = { queryClient }
await act(async () =>
root.render(
<QueryClientProvider client={queryClient}>
<I18nextProvider i18n={i18n}>
<ApiKeysProvider>
<ApiKeysMutateDrawer open onOpenChange={() => undefined} />
</ApiKeysProvider>
</I18nextProvider>
</QueryClientProvider>
)
render(
<QueryClientProvider client={queryClient}>
<I18nextProvider i18n={i18n}>
<ApiKeysProvider>
<ApiKeysMutateDrawer open onOpenChange={() => undefined} />
</ApiKeysProvider>
</I18nextProvider>
</QueryClientProvider>
)
await act(async () =>
waitForCondition(() => {
await waitFor(
() => {
const saveButton = findButton('Save changes', false)
return saveButton !== null && !saveButton.disabled
}, 'API key drawer did not finish initializing')
expect(saveButton).toBeEnabled()
},
{ timeout: 1500 }
)
}
function findButton(text: string, required: true): HTMLButtonElement
function findButton(text: string, required: false): HTMLButtonElement | null
function findButton(text: string, required = true): HTMLButtonElement | null {
const button = [
...document.querySelectorAll<HTMLButtonElement>('button'),
].find((candidate) => candidate.textContent?.includes(text))
if (required) assert.ok(button, `Expected button containing "${text}"`)
const button = screen
.queryAllByRole<HTMLButtonElement>('button')
.find((candidate) => candidate.textContent?.includes(text))
if (required && !button) {
throw new Error(`Expected button containing "${text}"`)
}
return button ?? null
}
function getControlByLabel<T extends HTMLElement>(labelText: string): T {
function getControlByLabel(labelText: 'Name' | 'Quantity'): HTMLInputElement
function getControlByLabel(labelText: 'Group'): HTMLButtonElement
function getControlByLabel(labelText: 'Auto group order'): HTMLElement
function getControlByLabel(labelText: string): HTMLElement {
const label = [...document.querySelectorAll<HTMLLabelElement>('label')].find(
(candidate) => candidate.textContent?.trim() === labelText
)
assert.ok(label, `Expected label "${labelText}"`)
assert.ok(label.htmlFor)
if (!label) {
throw new Error(`Expected label "${labelText}"`)
}
const control =
label.control ??
label
@@ -236,51 +169,38 @@ function getControlByLabel<T extends HTMLElement>(labelText: string): T {
?.querySelector<HTMLElement>(
'[data-slot="form-control"], input, textarea, button[role="combobox"], [role="group"]'
)
assert.ok(control)
return control as T
if (!control) {
throw new Error(`Expected control for label "${labelText}"`)
}
return control
}
async function changeInput(input: HTMLInputElement, value: string) {
await act(async () => {
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
)
})
function changeInput(input: HTMLInputElement, value: string): void {
fireEvent.input(input, { target: { value } })
}
async function selectComboboxOption(
function selectComboboxOption(
trigger: HTMLButtonElement,
optionDescription: string
) {
await act(async () => trigger.click())
): void {
fireEvent.click(trigger)
const option = [
...document.querySelectorAll<HTMLElement>('[data-slot="command-item"]'),
].find((candidate) => candidate.textContent?.includes(optionDescription))
assert.ok(option, `Expected option containing "${optionDescription}"`)
await act(async () => option.click())
if (!option) {
throw new Error(`Expected option containing "${optionDescription}"`)
}
fireEvent.click(option)
}
afterEach(async () => {
afterEach(() => {
apiClient.get = originalGet
apiClient.post = originalPost
domWindow.localStorage.clear()
localStorage.clear()
if (renderedDrawer) {
await act(async () => renderedDrawer?.root.unmount())
renderedDrawer.queryClient.clear()
renderedDrawer.host.remove()
renderedDrawer = null
}
document.body.replaceChildren()
})
after(() => {
domWindow.close()
})
describe('API keys mutate drawer Auto group integration', () => {
@@ -289,38 +209,31 @@ describe('API keys mutate drawer Auto group integration', () => {
installApiFixtures(createdPayloads)
await renderCreateDrawer()
const groupTrigger = getControlByLabel<HTMLButtonElement>('Group')
assert.equal(groupTrigger.textContent?.includes('auto'), true)
assert.equal(
const groupTrigger = getControlByLabel('Group')
expect(groupTrigger.textContent?.includes('auto')).toBe(true)
expect(
document.body.textContent?.includes(
'Using the complete global Auto order (2 groups)'
),
true
)
assert.deepEqual(
)
).toBe(true)
expect(
[
...document.querySelectorAll('[data-slot="global-auto-order-name"]'),
].map((item) => item.textContent),
['vip', 'default']
)
assert.equal(findButton('Restore global Auto', true).disabled, true)
].map((item) => item.textContent)
).toEqual(['vip', 'default'])
expect(findButton('Restore global Auto', true).disabled).toBe(true)
await changeInput(getControlByLabel<HTMLInputElement>('Name'), 'batch')
await changeInput(getControlByLabel<HTMLInputElement>('Quantity'), '2')
await act(async () => findButton('Save changes', true).click())
await act(async () =>
waitForCondition(
() => createdPayloads.length === 2,
'batch API keys were not created'
)
)
changeInput(getControlByLabel('Name'), 'batch')
changeInput(getControlByLabel('Quantity'), '2')
fireEvent.click(findButton('Save changes', true))
await waitFor(() => expect(createdPayloads).toHaveLength(2))
assert.equal(createdPayloads.length, 2)
assert.equal(createdPayloads[0]?.name, 'batch')
expect(createdPayloads.length).toBe(2)
expect(createdPayloads[0]?.name).toBe('batch')
for (const payload of createdPayloads) {
assert.equal(payload.group, 'auto')
assert.deepEqual(payload.auto_groups, [])
assert.equal(payload.cross_group_retry, true)
expect(payload.group).toBe('auto')
expect(payload.auto_groups).toEqual([])
expect(payload.cross_group_retry).toBe(true)
}
})
@@ -329,43 +242,39 @@ describe('API keys mutate drawer Auto group integration', () => {
installApiFixtures(createdPayloads)
await renderCreateDrawer()
const autoOrderControl = getControlByLabel<HTMLElement>('Auto group order')
const autoOrderControl = getControlByLabel('Auto group order')
const addGroupTrigger = autoOrderControl.querySelector<HTMLButtonElement>(
'button[role="combobox"]'
)
assert.ok(addGroupTrigger)
await selectComboboxOption(addGroupTrigger, 'Priority access')
if (!addGroupTrigger) {
throw new Error('Expected Auto group order combobox')
}
selectComboboxOption(addGroupTrigger, 'Priority access')
assert.ok(document.querySelector('button[aria-label="Remove vip"]'))
assert.equal(
document.body.textContent?.includes('1 / 3 groups selected'),
expect(
document.querySelector('button[aria-label="Remove vip"]')
).toBeTruthy()
expect(document.body.textContent?.includes('1 / 3 groups selected')).toBe(
true
)
assert.equal(findButton('Restore global Auto', true).disabled, false)
expect(findButton('Restore global Auto', true).disabled).toBe(false)
const groupTrigger = getControlByLabel<HTMLButtonElement>('Group')
await selectComboboxOption(groupTrigger, 'Standard access')
assert.equal(
document.querySelector('button[aria-label="Remove vip"]'),
null
)
await selectComboboxOption(groupTrigger, 'Automatic routing')
const groupTrigger = getControlByLabel('Group')
selectComboboxOption(groupTrigger, 'Standard access')
expect(document.querySelector('button[aria-label="Remove vip"]')).toBe(null)
selectComboboxOption(groupTrigger, 'Automatic routing')
assert.ok(document.querySelector('button[aria-label="Remove vip"]'))
assert.equal(
document.body.textContent?.includes('1 / 3 groups selected'),
expect(
document.querySelector('button[aria-label="Remove vip"]')
).toBeTruthy()
expect(document.body.textContent?.includes('1 / 3 groups selected')).toBe(
true
)
assert.equal(findButton('Restore global Auto', true).disabled, false)
expect(findButton('Restore global Auto', true).disabled).toBe(false)
await changeInput(getControlByLabel<HTMLInputElement>('Name'), 'custom')
await act(async () => findButton('Save changes', true).click())
await act(async () =>
waitForCondition(
() => createdPayloads.length === 1,
'custom-order API key was not created'
)
)
assert.deepEqual(createdPayloads[0]?.auto_groups, ['vip'])
changeInput(getControlByLabel('Name'), 'custom')
fireEvent.click(findButton('Save changes', true))
await waitFor(() => expect(createdPayloads).toHaveLength(1))
expect(createdPayloads[0]?.auto_groups).toEqual(['vip'])
})
})
@@ -16,42 +16,10 @@ 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 { fireEvent, render, within } from '@testing-library/react'
import { describe, expect, test } from 'vitest'
import { Window } from 'happy-dom'
const domWindow = new Window()
const domGlobals = [
'window',
'document',
'navigator',
'HTMLElement',
'HTMLButtonElement',
'HTMLInputElement',
'SVGElement',
'Node',
'Element',
'Event',
'KeyboardEvent',
'PointerEvent',
'CustomEvent',
'MutationObserver',
'ResizeObserver',
'requestAnimationFrame',
'cancelAnimationFrame',
'getComputedStyle',
] as const
for (const key of domGlobals) {
Object.defineProperty(globalThis, key, {
configurable: true,
value: domWindow[key],
})
}
const { act, useState } = await import('react')
const { createRoot } = await import('react-dom/client')
const { useState } = await import('react')
const { createInstance } = await import('i18next')
const { I18nextProvider, initReactI18next } = await import('react-i18next')
const { AutoGroupOrderEditor } = await import('../auto-group-order-editor')
@@ -90,11 +58,6 @@ await i18n.use(initReactI18next).init({
},
})
const reactTestGlobals = globalThis as typeof globalThis & {
IS_REACT_ACT_ENVIRONMENT?: boolean
}
reactTestGlobals.IS_REACT_ACT_ENVIRONMENT = true
const globalOptions = [
{ value: 'vip', label: 'VIP', desc: 'Priority access', ratio: 3 },
{ value: 'default', label: 'Default', desc: 'Standard access', ratio: 1 },
@@ -176,163 +139,104 @@ function CustomEmptyHarness() {
)
}
function findButton(container: ParentNode, label: string): HTMLButtonElement {
const button = container.querySelector<HTMLButtonElement>(
`button[aria-label="${label}"]`
)
assert.ok(button)
return button
function findButton(container: HTMLElement, label: string): HTMLButtonElement {
return within(container).getByRole('button', { name: label })
}
function getCommandItem(label: string): HTMLElement {
const item = [
...document.querySelectorAll<HTMLElement>('[data-slot="command-item"]'),
].find((candidate) => candidate.textContent?.includes(label))
if (!item) {
throw new Error(`Expected command item containing "${label}"`)
}
return item
}
describe('Auto group order editor', () => {
after(() => {
domWindow.close()
})
test('enforces the limit and exposes accessible reorder controls', () => {
const { container } = render(<Harness />)
test('enforces the limit and exposes accessible reorder controls', async () => {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
const addButton = within(container).getByRole('combobox')
expect(addButton).toBeDisabled()
expect(container).toHaveTextContent('2 / 2 groups selected')
expect(
within(container).getByRole('group', { name: 'Auto group order' })
).toBeInTheDocument()
expect(findButton(container, 'Drag default to reorder').type).toBe('button')
await act(async () => root.render(<Harness />))
const addButton = container.querySelector<HTMLButtonElement>(
'button[role="combobox"]'
)
assert.ok(addButton)
assert.equal(addButton.disabled, true)
assert.equal(container.textContent?.includes('2 / 2 groups selected'), true)
assert.ok(
container.querySelector('[role="group"][aria-label="Auto group order"]')
)
assert.equal(
findButton(container, 'Drag default to reorder').type,
'button'
)
await act(async () => findButton(container, 'Move default down').click())
assert.equal(
container.querySelector('[data-testid="order"]')?.textContent,
fireEvent.click(findButton(container, 'Move default down'))
expect(within(container).getByTestId('order')).toHaveTextContent(
'vip,default'
)
await act(async () => {
findButton(container, 'Drag vip to reorder').dispatchEvent(
new domWindow.KeyboardEvent('keydown', {
key: 'ArrowDown',
bubbles: true,
}) as unknown as KeyboardEvent
)
fireEvent.keyDown(findButton(container, 'Drag vip to reorder'), {
key: 'ArrowDown',
})
assert.equal(
container.querySelector('[data-testid="order"]')?.textContent,
expect(within(container).getByTestId('order')).toHaveTextContent(
'default,vip'
)
await act(async () => root.unmount())
container.remove()
})
test('adds and removes groups, then restores inheritance as an empty value', async () => {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
test('adds and removes groups, then restores inheritance as an empty value', () => {
const { container } = render(<Harness />)
fireEvent.click(findButton(container, 'Remove vip'))
await act(async () => root.render(<Harness />))
await act(async () => findButton(container, 'Remove vip').click())
expect(within(container).getByTestId('order')).toHaveTextContent('default')
const addButton = within(container).getByRole('combobox')
expect(addButton).toBeEnabled()
assert.equal(
container.querySelector('[data-testid="order"]')?.textContent,
'default'
)
const addButton = container.querySelector<HTMLButtonElement>(
'button[role="combobox"]'
)
assert.ok(addButton)
assert.equal(addButton.disabled, false)
await act(async () => addButton.click())
const teamOption = [
...document.querySelectorAll<HTMLElement>('[data-slot="command-item"]'),
].find((option) => option.textContent?.includes('team'))
assert.ok(teamOption)
await act(async () => teamOption.click())
assert.equal(
container.querySelector('[data-testid="order"]')?.textContent,
fireEvent.click(addButton)
fireEvent.click(getCommandItem('team'))
expect(within(container).getByTestId('order')).toHaveTextContent(
'default,team'
)
assert.equal(addButton.disabled, true)
expect(addButton).toBeDisabled()
const restoreButton = [...container.querySelectorAll('button')].find(
(button) => button.textContent?.includes('Restore global Auto')
fireEvent.click(
within(container).getByRole('button', { name: 'Restore global Auto' })
)
assert.ok(restoreButton)
await act(async () => restoreButton.click())
assert.equal(
container.querySelector('[data-testid="order"]')?.textContent,
''
)
assert.equal(
container.querySelector('[data-testid="mode"]')?.textContent,
'inherit'
)
assert.equal(
container.textContent?.includes(
'Using the complete global Auto order (3 groups)'
),
true
expect(within(container).getByTestId('order')).toBeEmptyDOMElement()
expect(within(container).getByTestId('mode')).toHaveTextContent('inherit')
expect(container).toHaveTextContent(
'Using the complete global Auto order (3 groups)'
)
const inheritedItems = container.querySelectorAll(
'[data-slot="global-auto-order"] > li'
)
assert.deepEqual(
expect(
[...inheritedItems].map(
(item) =>
item.querySelector('[data-slot="global-auto-order-name"]')
?.textContent
),
['VIP', 'Default', 'Team']
)
await act(async () => root.unmount())
container.remove()
)
).toEqual(['VIP', 'Default', 'Team'])
})
test('shows the complete inherited order with metadata beyond the custom limit', async () => {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
test('shows the complete inherited order with metadata beyond the custom limit', () => {
const { container } = render(<InheritanceHarness />)
await act(async () => root.render(<InheritanceHarness />))
assert.equal(
container.textContent?.includes(
'Using the complete global Auto order (3 groups)'
),
true
)
assert.equal(
container.textContent?.includes('0 / 2 groups selected'),
false
expect(container).toHaveTextContent(
'Using the complete global Auto order (3 groups)'
)
expect(container).not.toHaveTextContent('0 / 2 groups selected')
const order = container.querySelector<HTMLOListElement>(
'[data-slot="global-auto-order"]'
)
assert.ok(order)
assert.equal(order.classList.contains('overflow-y-auto'), true)
assert.equal(order.classList.contains('flex-wrap'), true)
if (!order) {
throw new Error('Expected inherited Auto group order')
}
expect(order).toHaveClass('overflow-y-auto', 'flex-wrap')
const items = [...order.querySelectorAll('li')]
assert.equal(items.length, 3)
assert.equal(
order.querySelectorAll('[data-slot="global-auto-order-connector"]')
.length,
2
)
assert.deepEqual(
expect(items.length).toBe(3)
expect(
order.querySelectorAll('[data-slot="global-auto-order-connector"]').length
).toBe(2)
expect(
items.map((item) => ({
index: item.querySelector('[data-slot="global-auto-order-index"]')
?.textContent,
@@ -345,196 +249,117 @@ describe('Auto group order editor', () => {
'[data-slot="global-auto-order-description"]'
)?.textContent,
ratio: item.querySelector('[data-slot="badge"]')?.textContent,
})),
[
{
index: '1',
name: 'VIP',
title: 'Priority access',
description: 'Priority access',
ratio: '3x Ratio',
},
{
index: '2',
name: 'Default',
title: 'Standard access',
description: 'Standard access',
ratio: '1x Ratio',
},
{
index: '3',
name: 'Team',
title: 'Shared access',
description: 'Shared access',
ratio: '2x Ratio',
},
]
)
}))
).toEqual([
{
index: '1',
name: 'VIP',
title: 'Priority access',
description: 'Priority access',
ratio: '3x Ratio',
},
{
index: '2',
name: 'Default',
title: 'Standard access',
description: 'Standard access',
ratio: '1x Ratio',
},
{
index: '3',
name: 'Team',
title: 'Shared access',
description: 'Shared access',
ratio: '2x Ratio',
},
])
for (const item of items) {
const chip = item.querySelector('[data-slot="global-auto-order-chip"]')
assert.ok(chip)
expect(chip).toBeInTheDocument()
const description = item.querySelector(
'[data-slot="global-auto-order-description"]'
)
assert.ok(description)
assert.equal(description.classList.contains('sr-only'), true)
expect(description).toHaveClass('sr-only')
}
assert.equal(
items[0]?.querySelector('[data-slot="global-auto-order-connector"]'),
null
)
expect(
items[0]?.querySelector('[data-slot="global-auto-order-connector"]')
).toBe(null)
for (const item of items.slice(1)) {
const connector = item.querySelector(
'[data-slot="global-auto-order-connector"]'
)
assert.ok(connector)
assert.equal(connector.getAttribute('aria-hidden'), 'true')
expect(connector).toHaveAttribute('aria-hidden', 'true')
}
assert.equal(container.querySelector('[aria-label^="Drag "]'), null)
assert.equal(container.querySelector('[aria-label^="Move "]'), null)
assert.equal(container.querySelector('[aria-label^="Remove "]'), null)
expect(container.querySelector('[aria-label^="Drag "]')).toBe(null)
expect(container.querySelector('[aria-label^="Move "]')).toBe(null)
expect(container.querySelector('[aria-label^="Remove "]')).toBe(null)
const restoreButton = [...container.querySelectorAll('button')].find(
(button) => button.textContent?.includes('Restore global Auto')
)
assert.ok(restoreButton)
assert.equal(restoreButton.disabled, true)
await act(async () => root.unmount())
container.remove()
expect(
within(container).getByRole('button', { name: 'Restore global Auto' })
).toBeDisabled()
})
test('shows an explicit empty state when the global Auto order has no groups', async () => {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
test('shows an explicit empty state when the global Auto order has no groups', () => {
const { container } = render(<InheritanceHarness globalOptions={[]} />)
await act(async () =>
root.render(<InheritanceHarness globalOptions={[]} />)
expect(container).toHaveTextContent(
'Using the complete global Auto order (0 groups)'
)
expect(container).toHaveTextContent(
'No available groups in the global Auto order.'
)
expect(container.querySelector('[data-slot="global-auto-order"]')).toBe(
null
)
})
assert.equal(
container.textContent?.includes(
'Using the complete global Auto order (0 groups)'
),
true
test('keeps an empty custom order distinct from global inheritance', () => {
const { container } = render(<CustomEmptyHarness />)
expect(within(container).getByTestId('mode')).toHaveTextContent('custom')
expect(container).toHaveTextContent(
'No valid custom Auto groups remain. Add a group or restore global Auto.'
)
assert.equal(
container.textContent?.includes(
'No available groups in the global Auto order.'
),
true
)
assert.equal(
container.querySelector('[data-slot="global-auto-order"]'),
expect(container.querySelector('[data-slot="global-auto-order"]')).toBe(
null
)
await act(async () => root.unmount())
container.remove()
const restoreButton = within(container).getByRole('button', {
name: 'Restore global Auto',
})
expect(restoreButton).toBeEnabled()
fireEvent.click(restoreButton)
expect(within(container).getByTestId('mode')).toHaveTextContent('inherit')
expect(
container.querySelector('[data-slot="global-auto-order"]')
).toBeInTheDocument()
})
test('keeps an empty custom order distinct from global inheritance', async () => {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
test('adding a group from inheritance explicitly creates a custom order', () => {
const { container } = render(<InheritanceHarness />)
await act(async () => root.render(<CustomEmptyHarness />))
fireEvent.click(within(container).getByRole('combobox'))
fireEvent.click(getCommandItem('VIP'))
assert.equal(
container.querySelector('[data-testid="mode"]')?.textContent,
'custom'
)
assert.equal(
container.textContent?.includes(
'No valid custom Auto groups remain. Add a group or restore global Auto.'
),
true
)
assert.equal(
container.querySelector('[data-slot="global-auto-order"]'),
expect(within(container).getByTestId('mode')).toHaveTextContent('custom')
expect(within(container).getByTestId('order')).toHaveTextContent('vip')
expect(container.querySelector('[data-slot="global-auto-order"]')).toBe(
null
)
const restoreButton = [...container.querySelectorAll('button')].find(
(button) => button.textContent?.includes('Restore global Auto')
)
assert.ok(restoreButton)
assert.equal(restoreButton.disabled, false)
await act(async () => restoreButton.click())
assert.equal(
container.querySelector('[data-testid="mode"]')?.textContent,
'inherit'
)
assert.ok(container.querySelector('[data-slot="global-auto-order"]'))
await act(async () => root.unmount())
container.remove()
})
test('adding a group from inheritance explicitly creates a custom order', async () => {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
test('removing the last custom group does not silently enable inheritance', () => {
const { container } = render(<Harness initialGroups={['default']} />)
fireEvent.click(findButton(container, 'Remove default'))
await act(async () => root.render(<InheritanceHarness />))
const addButton = container.querySelector<HTMLButtonElement>(
'button[role="combobox"]'
expect(within(container).getByTestId('order')).toBeEmptyDOMElement()
expect(within(container).getByTestId('mode')).toHaveTextContent('custom')
expect(container).toHaveTextContent(
'No valid custom Auto groups remain. Add a group or restore global Auto.'
)
assert.ok(addButton)
await act(async () => addButton.click())
const vipOption = [
...document.querySelectorAll<HTMLElement>('[data-slot="command-item"]'),
].find((option) => option.textContent?.includes('VIP'))
assert.ok(vipOption)
await act(async () => vipOption.click())
assert.equal(
container.querySelector('[data-testid="mode"]')?.textContent,
'custom'
)
assert.equal(
container.querySelector('[data-testid="order"]')?.textContent,
'vip'
)
assert.equal(
container.querySelector('[data-slot="global-auto-order"]'),
null
)
await act(async () => root.unmount())
container.remove()
})
test('removing the last custom group does not silently enable inheritance', async () => {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
await act(async () => root.render(<Harness initialGroups={['default']} />))
await act(async () => findButton(container, 'Remove default').click())
assert.equal(
container.querySelector('[data-testid="order"]')?.textContent,
''
)
assert.equal(
container.querySelector('[data-testid="mode"]')?.textContent,
'custom'
)
assert.equal(
container.textContent?.includes(
'No valid custom Auto groups remain. Add a group or restore global Auto.'
),
true
)
await act(async () => root.unmount())
container.remove()
})
})
@@ -16,10 +16,8 @@ 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 type { TFunction } from 'i18next'
import { describe, expect, test } from 'vitest'
import { apiKeySchema, type ApiKey } from '../../types'
import {
@@ -60,16 +58,16 @@ describe('API key Auto group form mapping', () => {
const legacyApiKey: Record<string, unknown> = { ...baseApiKey }
delete legacyApiKey.auto_groups
assert.equal(apiKeySchema.parse(legacyApiKey).auto_groups, null)
expect(apiKeySchema.parse(legacyApiKey).auto_groups).toBe(null)
})
test('creates an Auto token that inherits the global order', () => {
const defaults = getApiKeyFormDefaultValues(true)
assert.equal(defaults.group, 'auto')
assert.equal(defaults.auto_groups_mode, 'inherit')
assert.deepEqual(defaults.auto_groups, [])
assert.deepEqual(transformFormDataToPayload(defaults).auto_groups, [])
expect(defaults.group).toBe('auto')
expect(defaults.auto_groups_mode).toBe('inherit')
expect(defaults.auto_groups).toEqual([])
expect(transformFormDataToPayload(defaults).auto_groups).toEqual([])
})
test('maps omitted, null, and empty snapshots to inheritance on edit', () => {
@@ -88,8 +86,8 @@ describe('API key Auto group form mapping', () => {
2
)
assert.equal(defaults.auto_groups_mode, 'inherit')
assert.deepEqual(defaults.auto_groups, [])
expect(defaults.auto_groups_mode).toBe('inherit')
expect(defaults.auto_groups).toEqual([])
}
})
@@ -103,8 +101,8 @@ describe('API key Auto group form mapping', () => {
2
)
assert.equal(defaults.auto_groups_mode, 'custom')
assert.deepEqual(defaults.auto_groups, ['vip', 'default'])
expect(defaults.auto_groups_mode).toBe('custom')
expect(defaults.auto_groups).toEqual(['vip', 'default'])
})
test('keeps a fully filtered snapshot custom and rejects it until resolved', () => {
@@ -114,15 +112,14 @@ describe('API key Auto group form mapping', () => {
2
)
assert.equal(defaults.auto_groups_mode, 'custom')
assert.deepEqual(defaults.auto_groups, [])
expect(defaults.auto_groups_mode).toBe('custom')
expect(defaults.auto_groups).toEqual([])
const result = getApiKeyFormSchema(t, 2).safeParse(defaults)
assert.equal(result.success, false)
expect(result.success).toBe(false)
if (result.success) return
assert.deepEqual(result.error.issues[0]?.path, ['auto_groups'])
assert.equal(
result.error.issues[0]?.message,
expect(result.error.issues[0]?.path).toEqual(['auto_groups'])
expect(result.error.issues[0]?.message).toBe(
'Select at least one Auto group or restore global Auto.'
)
})
@@ -134,7 +131,7 @@ describe('API key Auto group form mapping', () => {
auto_groups: ['vip', 'default'],
}
assert.deepEqual(transformFormDataToPayload(custom).auto_groups, [
expect(transformFormDataToPayload(custom).auto_groups).toEqual([
'vip',
'default',
])
@@ -142,7 +139,7 @@ describe('API key Auto group form mapping', () => {
test('submits an empty array for inheritance and for non-Auto groups', () => {
const inherited = getApiKeyFormDefaultValues(true)
assert.deepEqual(transformFormDataToPayload(inherited).auto_groups, [])
expect(transformFormDataToPayload(inherited).auto_groups).toEqual([])
const nonAuto = {
...inherited,
@@ -150,8 +147,8 @@ describe('API key Auto group form mapping', () => {
auto_groups_mode: 'custom' as const,
auto_groups: ['vip'],
}
assert.deepEqual(transformFormDataToPayload(nonAuto).auto_groups, [])
assert.equal(transformFormDataToPayload(nonAuto).cross_group_retry, false)
expect(transformFormDataToPayload(nonAuto).auto_groups).toEqual([])
expect(transformFormDataToPayload(nonAuto).cross_group_retry).toBe(false)
})
test('rejects snapshots over the configured limit', () => {
@@ -162,13 +159,10 @@ describe('API key Auto group form mapping', () => {
auto_groups: ['default', 'vip'],
})
assert.equal(result.success, false)
expect(result.success).toBe(false)
if (result.success) return
assert.equal(result.error.issues[0]?.path[0], 'auto_groups')
assert.equal(
result.error.issues[0]?.message,
'Select at most 1 Auto groups'
)
expect(result.error.issues[0]?.path[0]).toBe('auto_groups')
expect(result.error.issues[0]?.message).toBe('Select at most 1 Auto groups')
})
test('rejects duplicate custom groups', () => {
@@ -179,10 +173,9 @@ describe('API key Auto group form mapping', () => {
auto_groups: ['vip', 'vip'],
})
assert.equal(result.success, false)
expect(result.success).toBe(false)
if (result.success) return
assert.equal(
result.error.issues[0]?.message,
expect(result.error.issues[0]?.message).toBe(
'Auto groups must not contain duplicates'
)
})
@@ -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 type { ChatCompletionRequest } from '../types'
import { createStreamRequestController } from './use-stream-request'
@@ -103,12 +102,12 @@ describe('latest-wins stream request coordination', () => {
const second = controller.send(payload, noopCallbacks)
firstHeaders.resolve({ Authorization: 'Bearer stale' })
await first
assert.equal(sources.length, 0)
expect(sources.length).toBe(0)
secondHeaders.resolve({ Authorization: 'Bearer current' })
await second
assert.equal(sources.length, 1)
assert.equal(sources[0]?.streamed, true)
expect(sources.length).toBe(1)
expect(sources[0]?.streamed).toBe(true)
})
test('stop cancels a request that is still waiting for headers', async () => {
@@ -128,7 +127,7 @@ describe('latest-wins stream request coordination', () => {
headers.resolve({ Authorization: 'Bearer ignored' })
await request
assert.equal(sourceCount, 0)
expect(sourceCount).toBe(0)
})
test('dispose cancels a pending header request without a state update', async () => {
@@ -149,8 +148,8 @@ describe('latest-wins stream request coordination', () => {
headers.resolve({ Authorization: 'Bearer ignored' })
await request
assert.equal(sourceCount, 0)
assert.deepEqual(streamingStates, [false])
expect(sourceCount).toBe(0)
expect(streamingStates).toEqual([false])
})
test('closes the previous source and ignores all of its later events', async () => {
@@ -182,7 +181,7 @@ describe('latest-wins stream request coordination', () => {
await controller.send(payload, callbacks)
const second = controller.send(payload, callbacks)
assert.equal(sources[0]?.closed, true)
expect(sources[0]?.closed).toBe(true)
sources[0]?.emit(
'message',
JSON.stringify({ choices: [{ delta: { content: 'stale' } }] })
@@ -195,6 +194,6 @@ describe('latest-wins stream request coordination', () => {
JSON.stringify({ choices: [{ delta: { content: 'current' } }] })
)
assert.deepEqual(updates, ['current'])
expect(updates).toEqual(['current'])
})
})
@@ -1,3 +1,4 @@
import type { TFunction } from 'i18next'
/*
Copyright (C) 2023-2026 QuantumNous
@@ -16,10 +17,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 type { TFunction } from 'i18next'
import { describe, expect, test } from 'vitest'
import { loginMethodLabel, sessionDevice } from '../login-session-utils'
@@ -27,14 +25,10 @@ const translate = ((key: string) => key) as TFunction
describe('login session presentation', () => {
test('labels built-in and provider OAuth login methods', () => {
assert.equal(loginMethodLabel('password', translate), 'Password')
assert.equal(
loginMethodLabel('2fa', translate),
'Two-factor Authentication'
)
assert.equal(loginMethodLabel('oauth:github', translate), 'OAuth · GitHub')
assert.equal(
loginMethodLabel('oauth:custom-provider', translate),
expect(loginMethodLabel('password', translate)).toBe('Password')
expect(loginMethodLabel('2fa', translate)).toBe('Two-factor Authentication')
expect(loginMethodLabel('oauth:github', translate)).toBe('OAuth · GitHub')
expect(loginMethodLabel('oauth:custom-provider', translate)).toBe(
'OAuth · custom-provider'
)
})
@@ -43,8 +37,7 @@ describe('login session presentation', () => {
const userAgent =
'Mozilla/5.0 (iPad; CPU OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1'
assert.equal(
sessionDevice(userAgent, 'Unknown device', 'Browser'),
expect(sessionDevice(userAgent, 'Unknown device', 'Browser')).toBe(
'Safari · iOS'
)
})
@@ -53,8 +46,7 @@ describe('login session presentation', () => {
const userAgent =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15'
assert.equal(
sessionDevice(userAgent, 'Unknown device', 'Browser', 5),
expect(sessionDevice(userAgent, 'Unknown device', 'Browser', 5)).toBe(
'Safari · iOS'
)
})
@@ -63,8 +55,7 @@ describe('login session presentation', () => {
const userAgent =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
assert.equal(
sessionDevice(userAgent, 'Unknown device', 'Browser', 10),
expect(sessionDevice(userAgent, 'Unknown device', 'Browser', 10)).toBe(
'Chrome · Windows'
)
})
@@ -73,8 +64,7 @@ describe('login session presentation', () => {
const userAgent =
'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.36'
assert.equal(
sessionDevice(userAgent, 'Unknown device', 'Browser', 5),
expect(sessionDevice(userAgent, 'Unknown device', 'Browser', 5)).toBe(
'Chrome · Android'
)
})
@@ -83,15 +73,13 @@ describe('login session presentation', () => {
const userAgent =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15'
assert.equal(
sessionDevice(userAgent, 'Unknown device', 'Browser'),
expect(sessionDevice(userAgent, 'Unknown device', 'Browser')).toBe(
'Safari · macOS'
)
})
test('falls back to the unknown-device label for an empty user agent', () => {
assert.equal(
sessionDevice('', 'Unknown device', 'Browser'),
expect(sessionDevice('', 'Unknown device', 'Browser')).toBe(
'Unknown device'
)
})
@@ -16,56 +16,17 @@ 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 { Window } from 'happy-dom'
import {
fireEvent,
render,
screen,
waitFor,
type RenderResult,
} from '@testing-library/react'
import { afterEach, describe, expect, test } from 'vitest'
import type { Redemption } from '../../types'
// Use Bun's runner at runtime while reusing the Node test types installed here.
const bunTestModule = 'bun:test'
const { afterAll, afterEach, test } = (await import(bunTestModule)) as {
afterAll: typeof import('node:test').after
afterEach: typeof import('node:test').afterEach
test: typeof import('node:test').test
}
const domWindow = new Window()
const domGlobals = [
'window',
'document',
'navigator',
'HTMLElement',
'HTMLButtonElement',
'HTMLInputElement',
'HTMLFormElement',
'HTMLLabelElement',
'HTMLFieldSetElement',
'SVGElement',
'Node',
'Element',
'Event',
'KeyboardEvent',
'PointerEvent',
'MouseEvent',
'FocusEvent',
'CustomEvent',
'MutationObserver',
'ResizeObserver',
'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 i18n = (await import('i18next')).default
const { I18nextProvider, initReactI18next } = await import('react-i18next')
const { Toaster, toast } = await import('sonner')
@@ -88,19 +49,13 @@ await i18n.use(initReactI18next).init({
},
})
const reactTestGlobals = globalThis as typeof globalThis & {
IS_REACT_ACT_ENVIRONMENT?: boolean
}
reactTestGlobals.IS_REACT_ACT_ENVIRONMENT = true
type ApiMethod = (url: string, data?: unknown) => Promise<{ data: unknown }>
type MockableApi = {
get: ApiMethod
put: ApiMethod
}
type RenderedDrawer = {
host: HTMLDivElement
root: ReturnType<typeof createRoot>
result: RenderResult
}
type CurrencyFixture = {
quotaDisplayType: 'USD' | 'CNY'
@@ -171,269 +126,189 @@ async function renderDrawer(
},
})
const host = document.createElement('div')
document.body.append(host)
const root = createRoot(host)
renderedDrawer = { host, root }
await act(async () => root.render(drawerTree(currentRow)))
renderedDrawer = { result: render(drawerTree(currentRow)) }
}
async function rerenderDrawer(currentRow: Redemption): Promise<void> {
assert.ok(renderedDrawer)
await act(async () => renderedDrawer?.root.render(drawerTree(currentRow)))
}
async function waitForCondition(
condition: () => boolean,
failureMessage: string
): Promise<void> {
if (condition()) return
await new Promise<void>((resolve, reject) => {
const observer = new MutationObserver(() => {
if (!condition()) return
clearTimeout(timeoutId)
observer.disconnect()
resolve()
})
const timeoutId = setTimeout(() => {
observer.disconnect()
reject(new Error(`${failureMessage}: ${document.body.textContent}`))
}, 1500)
observer.observe(document, {
attributes: true,
childList: true,
characterData: true,
subtree: true,
})
})
if (!renderedDrawer) {
throw new Error('Expected a rendered redemption drawer')
}
renderedDrawer.result.rerender(drawerTree(currentRow))
}
function getSaveButton(): HTMLButtonElement {
const button = document.querySelector<HTMLButtonElement>(
'button[form="redemption-form"][type="submit"]'
)
assert.ok(button)
return button
return screen.getByRole('button', { name: 'Save changes' })
}
function getControlByLabel<T extends HTMLElement>(labelText: string): T {
function getControlByLabel(labelText: 'Name'): HTMLInputElement
function getControlByLabel(labelText: 'Quota (CNY)'): HTMLInputElement
function getControlByLabel(labelText: 'Quota (USD)'): HTMLInputElement
function getControlByLabel(labelText: string): HTMLElement {
const label = [...document.querySelectorAll<HTMLLabelElement>('label')].find(
(candidate) => candidate.textContent?.trim() === labelText
)
assert.ok(label, `Expected label "${labelText}"`)
assert.ok(label.htmlFor)
if (!label) {
throw new Error(`Expected label "${labelText}"`)
}
const control =
label.control ??
label
.closest('[data-slot="form-item"]')
?.querySelector<HTMLElement>('[data-slot="form-control"], input')
assert.ok(control)
return control as T
if (!control) {
throw new Error(`Expected control for label "${labelText}"`)
}
return control
}
async function changeInput(input: HTMLInputElement, value: string) {
await act(async () => {
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
)
})
function changeInput(input: HTMLInputElement, value: string): void {
fireEvent.input(input, { target: { value } })
}
async function submitForm(): Promise<void> {
function submitForm(): void {
const form = document.querySelector<HTMLFormElement>('#redemption-form')
assert.ok(form)
await act(async () =>
form.dispatchEvent(
new domWindow.Event('submit', {
bubbles: true,
cancelable: true,
}) as unknown as Event
)
)
if (!form) {
throw new Error('Expected redemption form')
}
fireEvent.submit(form)
}
async function waitForLoadedForm(): Promise<void> {
await act(async () =>
waitForCondition(() => {
const saveButton = getSaveButton()
return (
saveButton.textContent?.includes('Save changes') === true &&
!saveButton.disabled
)
}, 'redemption drawer did not finish loading')
)
await waitFor(() => expect(getSaveButton()).toBeEnabled())
}
afterEach(async () => {
afterEach(() => {
apiClient.get = originalGet
apiClient.put = originalPut
Reflect.set(console, 'log', originalConsoleLog)
toast.dismiss()
domWindow.localStorage.clear()
if (renderedDrawer) {
await act(async () => renderedDrawer?.root.unmount())
renderedDrawer.host.remove()
renderedDrawer = null
}
document.body.replaceChildren()
localStorage.clear()
renderedDrawer = null
})
afterAll(() => {
domWindow.close()
})
describe('redemption drawer', () => {
test('shows the reported CNY quota without floating-point noise', async () => {
const original = redemption(1, 13888889)
apiClient.get = async () => ({ data: { success: true, data: original } })
test('redemption drawer shows the reported CNY quota without floating-point noise', async () => {
const original = redemption(1, 13888889)
apiClient.get = async () => ({ data: { success: true, data: original } })
await renderDrawer(original, {
quotaDisplayType: 'CNY',
usdExchangeRate: 7.2,
})
await waitForLoadedForm()
await renderDrawer(original, {
quotaDisplayType: 'CNY',
usdExchangeRate: 7.2,
})
await waitForLoadedForm()
assert.equal(getControlByLabel<HTMLInputElement>('Quota (CNY)').value, '200')
})
test('redemption drawer blocks updates and reports an error when loading rejects', async () => {
const updates: unknown[] = []
Reflect.set(console, 'log', () => undefined)
apiClient.get = async () => {
throw new Error('network failure')
}
apiClient.put = async (_url, data) => {
updates.push(data)
return { data: { success: true } }
}
await renderDrawer(redemption(1))
await act(async () =>
waitForCondition(
() =>
document.body.textContent?.includes('Something went wrong!') === true,
'load error toast was not shown'
)
)
assert.equal(getSaveButton().disabled, true)
await submitForm()
assert.deepEqual(updates, [])
})
test('redemption drawer blocks updates and uses localized feedback for unsuccessful responses', async () => {
apiClient.get = async () => ({
data: { success: false, message: 'raw server message' },
expect(getControlByLabel('Quota (CNY)').value).toBe('200')
})
await renderDrawer(redemption(1))
await act(async () =>
waitForCondition(
() => document.body.textContent?.includes('Failed to load') === true,
'unsuccessful-load toast was not shown'
test('blocks updates and reports an error when loading rejects', async () => {
const updates: unknown[] = []
Reflect.set(console, 'log', () => undefined)
apiClient.get = async () => {
throw new Error('network failure')
}
apiClient.put = async (_url, data) => {
updates.push(data)
return { data: { success: true } }
}
await renderDrawer(redemption(1))
await waitFor(() =>
expect(document.body).toHaveTextContent('Something went wrong!')
)
)
assert.equal(getSaveButton().disabled, true)
assert.equal(document.body.textContent?.includes('raw server message'), false)
})
expect(getSaveButton()).toBeDisabled()
submitForm()
expect(updates).toEqual([])
})
test('redemption drawer keeps the original quota when another field changes', async () => {
const original = redemption(1)
const updates: Array<Record<string, unknown>> = []
apiClient.get = async () => ({ data: { success: true, data: original } })
apiClient.put = async (_url, data) => {
assert.ok(data && typeof data === 'object')
updates.push(data as Record<string, unknown>)
return { data: { success: true, data: original } }
}
test('blocks updates and uses localized feedback for unsuccessful responses', async () => {
apiClient.get = async () => ({
data: { success: false, message: 'raw server message' },
})
await renderDrawer(original)
await waitForLoadedForm()
assert.equal(getControlByLabel<HTMLInputElement>('Quota (USD)').value, '1')
await changeInput(getControlByLabel<HTMLInputElement>('Name'), 'renamed')
await submitForm()
await act(async () =>
waitForCondition(() => updates.length === 1, 'update was not submitted')
)
assert.equal(updates[0]?.name, 'renamed')
assert.equal(updates[0]?.quota, 500001)
})
test('redemption drawer recalculates quota when the quota field changes', async () => {
const original = redemption(1)
const updates: Array<Record<string, unknown>> = []
apiClient.get = async () => ({ data: { success: true, data: original } })
apiClient.put = async (_url, data) => {
assert.ok(data && typeof data === 'object')
updates.push(data as Record<string, unknown>)
return { data: { success: true, data: original } }
}
await renderDrawer(original)
await waitForLoadedForm()
await changeInput(getControlByLabel<HTMLInputElement>('Quota (USD)'), '2')
await submitForm()
await act(async () =>
waitForCondition(() => updates.length === 1, 'update was not submitted')
)
assert.equal(updates[0]?.quota, 1000000)
})
test('redemption drawer ignores an older response after switching records', async () => {
const first = redemption(1, 500001)
const second = redemption(2, 1000001)
const firstRequest = deferred<{ data: unknown }>()
const secondRequest = deferred<{ data: unknown }>()
const requestedUrls: string[] = []
const updates: Array<Record<string, unknown>> = []
apiClient.get = (url) => {
requestedUrls.push(url)
if (url === '/api/redemption/1') return firstRequest.promise
if (url === '/api/redemption/2') return secondRequest.promise
throw new Error(`Unexpected GET ${url}`)
}
apiClient.put = async (_url, data) => {
assert.ok(data && typeof data === 'object')
updates.push(data as Record<string, unknown>)
return { data: { success: true, data: second } }
}
await renderDrawer(first)
await rerenderDrawer(second)
await act(async () =>
waitForCondition(
() => requestedUrls.includes('/api/redemption/2'),
'second redemption was not requested'
await renderDrawer(redemption(1))
await waitFor(() =>
expect(document.body).toHaveTextContent('Failed to load')
)
)
await act(async () =>
expect(getSaveButton()).toBeDisabled()
expect(document.body).not.toHaveTextContent('raw server message')
})
test('keeps the original quota when another field changes', async () => {
const original = redemption(1)
const updates: Array<Record<string, unknown>> = []
apiClient.get = async () => ({ data: { success: true, data: original } })
apiClient.put = async (_url, data) => {
expect(data && typeof data === 'object').toBeTruthy()
updates.push(data as Record<string, unknown>)
return { data: { success: true, data: original } }
}
await renderDrawer(original)
await waitForLoadedForm()
expect(getControlByLabel('Quota (USD)').value).toBe('1')
changeInput(getControlByLabel('Name'), 'renamed')
submitForm()
await waitFor(() => expect(updates).toHaveLength(1))
expect(updates[0]?.name).toBe('renamed')
expect(updates[0]?.quota).toBe(500001)
})
test('recalculates quota when the quota field changes', async () => {
const original = redemption(1)
const updates: Array<Record<string, unknown>> = []
apiClient.get = async () => ({ data: { success: true, data: original } })
apiClient.put = async (_url, data) => {
expect(data && typeof data === 'object').toBeTruthy()
updates.push(data as Record<string, unknown>)
return { data: { success: true, data: original } }
}
await renderDrawer(original)
await waitForLoadedForm()
changeInput(getControlByLabel('Quota (USD)'), '2')
submitForm()
await waitFor(() => expect(updates).toHaveLength(1))
expect(updates[0]?.quota).toBe(1000000)
})
test('ignores an older response after switching records', async () => {
const first = redemption(1, 500001)
const second = redemption(2, 1000001)
const firstRequest = deferred<{ data: unknown }>()
const secondRequest = deferred<{ data: unknown }>()
const requestedUrls: string[] = []
const updates: Array<Record<string, unknown>> = []
apiClient.get = (url) => {
requestedUrls.push(url)
if (url === '/api/redemption/1') return firstRequest.promise
if (url === '/api/redemption/2') return secondRequest.promise
throw new Error(`Unexpected GET ${url}`)
}
apiClient.put = async (_url, data) => {
expect(data && typeof data === 'object').toBeTruthy()
updates.push(data as Record<string, unknown>)
return { data: { success: true, data: second } }
}
await renderDrawer(first)
await rerenderDrawer(second)
await waitFor(() => expect(requestedUrls).toContain('/api/redemption/2'))
secondRequest.resolve({ data: { success: true, data: second } })
)
await waitForLoadedForm()
await waitForLoadedForm()
await act(async () =>
firstRequest.resolve({ data: { success: true, data: first } })
)
assert.equal(getControlByLabel<HTMLInputElement>('Name').value, 'code-2')
expect(getControlByLabel('Name').value).toBe('code-2')
await changeInput(getControlByLabel<HTMLInputElement>('Name'), 'second')
await submitForm()
await act(async () =>
waitForCondition(() => updates.length === 1, 'update was not submitted')
)
changeInput(getControlByLabel('Name'), 'second')
submitForm()
await waitFor(() => expect(updates).toHaveLength(1))
assert.equal(updates[0]?.id, 2)
assert.equal(updates[0]?.quota, 1000001)
expect(updates[0]?.id).toBe(2)
expect(updates[0]?.quota).toBe(1000001)
})
})
@@ -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()
})
})
@@ -16,88 +16,19 @@ 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 { Window } from 'happy-dom'
import { render, screen } from '@testing-library/react'
import i18next from 'i18next'
import type React from 'react'
import { beforeAll, describe, expect, test } from 'vitest'
const domWindow = new Window()
const domGlobals = [
'window',
'document',
'navigator',
'HTMLElement',
'SVGElement',
'Node',
'Element',
'Event',
'CustomEvent',
'MutationObserver',
'requestAnimationFrame',
'cancelAnimationFrame',
'getComputedStyle',
] as const
import { formatLogQuota } from '@/lib/format'
for (const key of domGlobals) {
Object.defineProperty(globalThis, key, {
configurable: true,
value: domWindow[key],
})
}
import { LogCostDisplay } from '../log-cost-display'
const { act } = await import('react')
const { createRoot } = await import('react-dom/client')
const { createInstance } = await import('i18next')
const { I18nextProvider, initReactI18next } = await import('react-i18next')
const i18n = createInstance()
await i18n.use(initReactI18next).init({
lng: 'en',
resources: {
en: {
translation: {
Subscription: 'Subscription',
'Deducted by subscription': 'Deducted by subscription',
'Includes tool-call surcharge': 'Includes tool-call surcharge',
},
},
},
})
const { LogCostDisplay } = await import('../log-cost-display')
const { formatLogQuota } = await import('@/lib/format')
const reactTestGlobals = globalThis as typeof globalThis & {
IS_REACT_ACT_ENVIRONMENT?: boolean
}
reactTestGlobals.IS_REACT_ACT_ENVIRONMENT = true
type RenderedCost = {
container: HTMLDivElement
root: ReturnType<typeof createRoot>
}
async function renderCost(
function renderCost(
props: React.ComponentProps<typeof LogCostDisplay>
): Promise<RenderedCost> {
const container = document.createElement('div')
document.body.append(container)
const root = createRoot(container)
await act(async () => {
root.render(
<I18nextProvider i18n={i18n}>
<LogCostDisplay {...props} />
</I18nextProvider>
)
})
return { container, root }
}
async function unmountCost(rendered: RenderedCost) {
await act(async () => rendered.root.unmount())
rendered.container.remove()
): ReturnType<typeof render> {
return render(<LogCostDisplay {...props} />)
}
function normalizedText(value: string | null): string {
@@ -105,39 +36,36 @@ function normalizedText(value: string | null): string {
}
describe('log cost display', () => {
after(() => {
domWindow.close()
beforeAll(() => {
i18next.addResourceBundle('en', 'translation', {
Subscription: 'Subscription',
'Deducted by subscription': 'Deducted by subscription',
'Includes tool-call surcharge': 'Includes tool-call surcharge',
})
})
test('keeps the regular cost visible and adds an accessible surcharge marker', async () => {
const rendered = await renderCost({
test('keeps the regular cost visible and adds an accessible surcharge marker', () => {
const rendered = renderCost({
quota: 12500,
other: {
tool_surcharges: [{ name: 'lookup_customer', count: 1, price: 5 }],
},
})
assert.equal(
expect(
normalizedText(rendered.container.textContent).includes(
normalizedText(formatLogQuota(12500))
),
true
)
const marker = rendered.container.querySelector(
'[data-tool-surcharge-indicator="true"]'
)
assert.ok(marker)
assert.equal(
marker.getAttribute('aria-label'),
'Includes tool-call surcharge'
)
assert.equal(marker.getAttribute('tabindex'), '0')
await unmountCost(rendered)
)
).toBe(true)
const marker = screen.getByRole('img', {
name: 'Includes tool-call surcharge',
})
expect(marker).toHaveAttribute('data-tool-surcharge-indicator', 'true')
expect(marker).toHaveAttribute('tabindex', '0')
})
test('preserves the subscription badge and adds the same legacy surcharge marker', async () => {
const rendered = await renderCost({
test('preserves the subscription badge and adds the same legacy surcharge marker', () => {
renderCost({
quota: 5000,
other: {
billing_source: 'subscription',
@@ -147,11 +75,9 @@ describe('log cost display', () => {
},
})
assert.equal(rendered.container.textContent?.includes('Subscription'), true)
assert.ok(
rendered.container.querySelector('[data-tool-surcharge-indicator="true"]')
)
await unmountCost(rendered)
expect(screen.getByText('Subscription')).toBeInTheDocument()
expect(
screen.getByRole('img', { name: 'Includes tool-call surcharge' })
).toHaveAttribute('data-tool-surcharge-indicator', 'true')
})
})
@@ -16,20 +16,18 @@ 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 type { LogOtherData } from '../../types'
import { hasToolSurcharge } from '../format'
describe('tool surcharge detection', () => {
test('shows the marker for a charged structured tool surcharge', () => {
assert.equal(
expect(
hasToolSurcharge({
tool_surcharges: [{ name: 'lookup_customer', count: 2, price: 5 }],
}),
true
)
})
).toBe(true)
})
const legacyCases: Array<{
@@ -63,7 +61,7 @@ describe('tool surcharge detection', () => {
for (const scenario of legacyCases) {
test(`keeps the marker visible for legacy ${scenario.name} charges`, () => {
assert.equal(hasToolSurcharge(scenario.other), true)
expect(hasToolSurcharge(scenario.other)).toBe(true)
})
}
@@ -93,7 +91,7 @@ describe('tool surcharge detection', () => {
]
for (const other of invalidCases) {
assert.equal(hasToolSurcharge(other), false)
expect(hasToolSurcharge(other)).toBe(false)
}
})
})
@@ -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 { PAYMENT_TYPES } from '../constants'
import { requestPaymentAmount } from './use-payment'
@@ -44,7 +43,7 @@ describe('payment amount routing', () => {
},
})
assert.equal(amount, 18.75)
assert.deepEqual(calls, ['waffo:120'])
expect(amount).toBe(18.75)
expect(calls).toEqual(['waffo:120'])
})
})
+10 -11
View File
@@ -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 { PAYMENT_TYPES } from '../constants'
import {
@@ -29,11 +28,11 @@ import {
describe('payment type classification', () => {
test('keeps Waffo and Waffo Pancake on their dedicated flows', () => {
assert.equal(isWaffoPayment(PAYMENT_TYPES.WAFFO), true)
assert.equal(isWaffoPayment(PAYMENT_TYPES.WAFFO_PANCAKE), false)
assert.equal(isWaffoPancakePayment(PAYMENT_TYPES.WAFFO_PANCAKE), true)
assert.equal(isWaffoPancakePayment(PAYMENT_TYPES.WAFFO), false)
assert.equal(isStripePayment(PAYMENT_TYPES.STRIPE), true)
expect(isWaffoPayment(PAYMENT_TYPES.WAFFO)).toBe(true)
expect(isWaffoPayment(PAYMENT_TYPES.WAFFO_PANCAKE)).toBe(false)
expect(isWaffoPancakePayment(PAYMENT_TYPES.WAFFO_PANCAKE)).toBe(true)
expect(isWaffoPancakePayment(PAYMENT_TYPES.WAFFO)).toBe(false)
expect(isStripePayment(PAYMENT_TYPES.STRIPE)).toBe(true)
})
})
@@ -60,8 +59,8 @@ describe('payment dispatch', () => {
}
)
assert.equal(success, true)
assert.deepEqual(calls, ['waffo:120:3'])
expect(success).toBe(true)
expect(calls).toEqual(['waffo:120:3'])
})
test('does not create a Waffo order without a selected method index', async () => {
@@ -80,7 +79,7 @@ describe('payment dispatch', () => {
}
)
assert.equal(success, false)
assert.equal(called, false)
expect(success).toBe(false)
expect(called).toBe(false)
})
})
+57 -66
View File
@@ -16,10 +16,8 @@ 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 { afterEach, describe, test } from 'node:test'
import { QueryClient } from '@tanstack/react-query'
import { afterEach, describe, expect, test } from 'vitest'
import { useAuthStore, type AuthBundle } from '../stores/auth-store'
import {
@@ -59,10 +57,10 @@ afterEach(() => {
describe('authentication session coordination', () => {
test('bootstrap distinguishes a completed anonymous check from an active session', async () => {
useAuthStore.getState().auth.reset('complete')
assert.deepEqual(await bootstrapAuthentication(), { kind: 'anonymous' })
expect(await bootstrapAuthentication()).toEqual({ kind: 'anonymous' })
useAuthStore.getState().auth.setBundle(bundle)
assert.deepEqual(await bootstrapAuthentication(), {
expect(await bootstrapAuthentication()).toEqual({
kind: 'authenticated',
bundle,
})
@@ -97,10 +95,10 @@ describe('authentication session coordination', () => {
const outcome = await createRefreshRunner(runtime)()
assert.equal(outcome.kind, 'authenticated')
assert.deepEqual(requestedSIDs, [bundle.session.sid, undefined])
assert.deepEqual(clears, [[false, 'idle']])
assert.deepEqual(accepted, [bundle])
expect(outcome.kind).toBe('authenticated')
expect(requestedSIDs).toEqual([bundle.session.sid, undefined])
expect(clears).toEqual([[false, 'idle']])
expect(accepted).toEqual([bundle])
})
test('a rejected refresh confirms anonymous state and synchronizes sign-out', async () => {
@@ -117,10 +115,10 @@ describe('authentication session coordination', () => {
wait: async () => undefined,
}
assert.deepEqual(await createRefreshRunner(runtime)(), {
expect(await createRefreshRunner(runtime)()).toEqual({
kind: 'anonymous',
})
assert.deepEqual(clears, [[true, undefined]])
expect(clears).toEqual([[true, undefined]])
})
test('a temporary refresh failure remains retryable without clearing the session', async () => {
@@ -142,9 +140,9 @@ describe('authentication session coordination', () => {
const outcome = await createRefreshRunner(runtime)()
assert.equal(outcome.kind, 'transient_error')
assert.equal(clearCount, 0)
assert.equal(transientCount, 1)
expect(outcome.kind).toBe('transient_error')
expect(clearCount).toBe(0)
expect(transientCount).toBe(1)
})
test('a rate limited refresh remains retryable without clearing the session', async () => {
@@ -166,9 +164,9 @@ describe('authentication session coordination', () => {
const outcome = await createRefreshRunner(runtime)()
assert.equal(outcome.kind, 'transient_error')
assert.equal(clearCount, 0)
assert.equal(transientCount, 1)
expect(outcome.kind).toBe('transient_error')
expect(clearCount).toBe(0)
expect(transientCount).toBe(1)
})
test('an exhausted refresh race clears the unusable local session', async () => {
@@ -191,12 +189,12 @@ describe('authentication session coordination', () => {
},
}
assert.deepEqual(await createRefreshRunner(runtime)(), {
expect(await createRefreshRunner(runtime)()).toEqual({
kind: 'out_of_sync',
code: 'AUTH_REFRESH_RACE',
})
assert.deepEqual(requestedDelays, [80, 200, 500])
assert.deepEqual(clears, [[false, undefined]])
expect(requestedDelays).toEqual([80, 200, 500])
expect(clears).toEqual([[false, undefined]])
})
test('an unexpected successful response is treated as out of sync', async () => {
@@ -213,11 +211,11 @@ describe('authentication session coordination', () => {
wait: async () => undefined,
}
assert.deepEqual(await createRefreshRunner(runtime)(), {
expect(await createRefreshRunner(runtime)()).toEqual({
kind: 'out_of_sync',
code: 'AUTH_INVALID_REFRESH_RESPONSE',
})
assert.equal(cleared, true)
expect(cleared).toBe(true)
})
test('a refresh response cannot restore credentials after a newer auth operation', async () => {
@@ -241,8 +239,8 @@ describe('authentication session coordination', () => {
const outcome = await createRefreshRunner(runtime)()
assert.equal(outcome.kind, 'transient_error')
assert.equal(accepted, false)
expect(outcome.kind).toBe('transient_error')
expect(accepted).toBe(false)
})
test('explicit rotations update only the current session', () => {
@@ -254,41 +252,35 @@ describe('authentication session coordination', () => {
session: { ...bundle.session, last_active_at: 200 },
})
assert.equal(useAuthStore.getState().auth.accessToken, 'rotated-token')
assert.strictEqual(useAuthStore.getState().auth.user, bundle.user)
expect(useAuthStore.getState().auth.accessToken).toBe('rotated-token')
expect(useAuthStore.getState().auth.user).toBe(bundle.user)
assert.throws(
() =>
applyAuthRotation({
access_token: 'non-bearer-token',
token_type: 'Custom',
access_expires_at: bundle.access_expires_at + 120,
session: bundle.session,
}),
/Invalid authentication rotation response/
)
assert.throws(
() =>
applyAuthRotation({
access_token: 'non-current-token',
token_type: 'Bearer',
access_expires_at: bundle.access_expires_at + 120,
session: { ...bundle.session, current: false },
}),
/Invalid authentication rotation response/
)
expect(() =>
applyAuthRotation({
access_token: 'non-bearer-token',
token_type: 'Custom',
access_expires_at: bundle.access_expires_at + 120,
session: bundle.session,
})
).toThrow(/Invalid authentication rotation response/)
expect(() =>
applyAuthRotation({
access_token: 'non-current-token',
token_type: 'Bearer',
access_expires_at: bundle.access_expires_at + 120,
session: { ...bundle.session, current: false },
})
).toThrow(/Invalid authentication rotation response/)
assert.throws(
() =>
applyAuthRotation({
access_token: 'wrong-session-token',
token_type: 'Bearer',
access_expires_at: bundle.access_expires_at + 120,
session: { ...bundle.session, sid: 'session-b' },
}),
/session mismatch/
)
assert.equal(useAuthStore.getState().auth.accessToken, 'rotated-token')
expect(() =>
applyAuthRotation({
access_token: 'wrong-session-token',
token_type: 'Bearer',
access_expires_at: bundle.access_expires_at + 120,
session: { ...bundle.session, sid: 'session-b' },
})
).toThrow(/session mismatch/)
expect(useAuthStore.getState().auth.accessToken).toBe('rotated-token')
})
test('sign-out clears user-scoped query, mutation, and authentication state', () => {
@@ -305,13 +297,13 @@ describe('authentication session coordination', () => {
clearAuthenticatedClientState(queryClient, false)
assert.equal(queryClient.getQueryCache().getAll().length, 0)
assert.equal(queryClient.getMutationCache().getAll().length, 0)
assert.equal(useAuthStore.getState().auth.user, null)
assert.equal(useAuthStore.getState().auth.accessToken, null)
assert.equal(useAuthStore.getState().auth.session, null)
assert.equal(useAuthStore.getState().auth.pending2FAFlowToken, null)
assert.equal(useAuthStore.getState().auth.bootstrapState, 'complete')
expect(queryClient.getQueryCache().getAll().length).toBe(0)
expect(queryClient.getMutationCache().getAll().length).toBe(0)
expect(useAuthStore.getState().auth.user).toBe(null)
expect(useAuthStore.getState().auth.accessToken).toBe(null)
expect(useAuthStore.getState().auth.session).toBe(null)
expect(useAuthStore.getState().auth.pending2FAFlowToken).toBe(null)
expect(useAuthStore.getState().auth.bootstrapState).toBe('complete')
const nextBundle: AuthBundle = {
...bundle,
@@ -320,8 +312,7 @@ describe('authentication session coordination', () => {
session: { ...bundle.session, sid: 'session-b' },
}
useAuthStore.getState().auth.setBundle(nextBundle)
assert.equal(
queryClient.getQueryData(['account', bundle.user.id]),
expect(queryClient.getQueryData(['account', bundle.user.id])).toBe(
undefined
)
})
+11 -17
View File
@@ -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 { resolveLegacyRoute } from './legacy-route'
@@ -43,17 +42,15 @@ describe('legacy frontend route migration', () => {
}
for (const [source, target] of Object.entries(routes)) {
assert.equal(resolveLegacyRoute(source), target)
expect(resolveLegacyRoute(source)).toBe(target)
}
})
test('preserves search and hash while applying route-specific behavior', () => {
assert.equal(
resolveLegacyRoute('/login?redirect=%2Fkeys#continue'),
expect(resolveLegacyRoute('/login?redirect=%2Fkeys#continue')).toBe(
'/sign-in?redirect=%2Fkeys#continue'
)
assert.equal(
resolveLegacyRoute('/console/topup?source=email#orders'),
expect(resolveLegacyRoute('/console/topup?source=email#orders')).toBe(
'/wallet?source=email#orders'
)
})
@@ -75,23 +72,20 @@ describe('legacy frontend route migration', () => {
}
for (const [tab, target] of Object.entries(settingsTabs)) {
assert.equal(
resolveLegacyRoute(`/console/setting?tab=${tab}&from=bookmark#form`),
`${target}?tab=${tab}&from=bookmark#form`
)
expect(
resolveLegacyRoute(`/console/setting?tab=${tab}&from=bookmark#form`)
).toBe(`${target}?tab=${tab}&from=bookmark#form`)
}
assert.equal(
resolveLegacyRoute('/console/setting?tab=unknown'),
expect(resolveLegacyRoute('/console/setting?tab=unknown')).toBe(
'/system-settings?tab=unknown'
)
})
test('safely redirects unknown console locations without touching new routes', () => {
assert.equal(
resolveLegacyRoute('/console/removed?page=2#old'),
expect(resolveLegacyRoute('/console/removed?page=2#old')).toBe(
'/dashboard?page=2#old'
)
assert.equal(resolveLegacyRoute('/dashboard'), null)
assert.equal(resolveLegacyRoute('/api/status'), null)
expect(resolveLegacyRoute('/dashboard')).toBe(null)
expect(resolveLegacyRoute('/api/status')).toBe(null)
})
})
+9 -11
View File
@@ -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 { getServerErrorMessageKey } from './server-error-message'
@@ -25,8 +24,8 @@ describe('server error message mapping', () => {
test('maps the active-session limit to recovery instructions', () => {
const message = getServerErrorMessageKey({ code: 'AUTH_SESSION_LIMIT' })
assert.match(message ?? '', /Sign out other sessions/)
assert.match(message ?? '', /reset your password/)
expect(message ?? '').toMatch(/Sign out other sessions/)
expect(message ?? '').toMatch(/reset your password/)
})
test('maps an Axios-shaped issuance limit to rolling-window guidance', () => {
@@ -34,8 +33,8 @@ describe('server error message mapping', () => {
response: { data: { code: 'AUTH_SESSION_ISSUANCE_LIMIT' } },
})
assert.match(message ?? '', /rolling window/)
assert.equal(getServerErrorMessageKey({ code: 'UNKNOWN_CODE' }), null)
expect(message ?? '').toMatch(/rolling window/)
expect(getServerErrorMessageKey({ code: 'UNKNOWN_CODE' })).toBe(null)
})
test('maps stable Telegram bind errors without exposing server text', () => {
@@ -55,16 +54,15 @@ describe('server error message mapping', () => {
}
for (const [code, message] of Object.entries(expected)) {
assert.equal(getServerErrorMessageKey({ code }), message)
expect(getServerErrorMessageKey({ code })).toBe(message)
}
assert.equal(
expect(
getServerErrorMessageKey({
response: {
data: { code: 'TELEGRAM_BIND_INTERNAL_ERROR', message: 'raw detail' },
},
}),
expected.TELEGRAM_BIND_INTERNAL_ERROR
)
})
).toBe(expected.TELEGRAM_BIND_INTERNAL_ERROR)
})
})
+73
View File
@@ -0,0 +1,73 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import i18next from 'i18next'
import { initReactI18next } from 'react-i18next'
import { afterEach, beforeAll } from 'vitest'
beforeAll(async () => {
await i18next.use(initReactI18next).init({
lng: 'en',
fallbackLng: 'en',
resources: {
en: {
translation: {},
},
},
})
})
afterEach(() => {
cleanup()
})
Object.defineProperty(window, 'matchMedia', {
configurable: true,
value: (query: string): MediaQueryList => ({
matches: false,
media: query,
onchange: null,
addListener: () => undefined,
removeListener: () => undefined,
addEventListener: () => undefined,
removeEventListener: () => undefined,
dispatchEvent: () => false,
}),
})
window.requestAnimationFrame = (callback: FrameRequestCallback) =>
window.setTimeout(() => callback(performance.now()), 0)
window.cancelAnimationFrame = (handle: number) => window.clearTimeout(handle)
class ResizeObserverMock {
observe(): void {}
unobserve(): void {}
disconnect(): void {}
}
Object.defineProperty(globalThis, 'ResizeObserver', {
configurable: true,
value: ResizeObserverMock,
})
Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', {
configurable: true,
value: () => undefined,
})