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
+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)
})
})