Files
bowenliang123 9aaa11591a test(client): split the oversized specs into siblings with shared harnesses
browser.spec.ts (2360), trendChart.spec.ts (1629), contextView.spec.ts (1504), and
overview.spec.ts (1065) exceeded the 1000-line limit. Each splits along its existing
describe seams into same-directory siblings, with the fixtures that more than one
sibling needs extracted once into a shared harness.

The move is verbatim: test titles, describe blocks, and every assertion line are
preserved exactly (2061 tests before and after, no duplicate titles, no skips), and
the suite reports the same coverage.
2026-10-10 01:21:15 +08:00

82 lines
3.2 KiB
TypeScript

import { act } from 'react'
import { afterEach, vi } from 'vitest'
import { makeContextBrowser, type ContextBrowserProps } from '../../../src/client/components/browser'
import { makeStackedBar } from '../../../src/client/components/stackedBar'
import { createContextSettings } from '../../../src/client/settings'
import type { ContextHeaders, ContextTimeline, HeaderEpochContent, RequestRecord } from '../../../src/shared/types'
import { makeKit, query, queryAll, type Mounted } from '../helpers/kit'
export const kit = makeKit()
export const settings = createContextSettings()
export const Browser = makeContextBrowser(kit, makeStackedBar(kit), settings)
// Tests that arm the fetch-on-miss retry with a deliberately failing fetcher
// silence the production `console.warn` the failure legitimately emits.
afterEach(() => {
vi.restoreAllMocks()
})
export function silenceFetchWarn(): void {
vi.spyOn(console, 'warn').mockImplementation(() => {})
}
// Category row index follows the CATS order.
export const ROW = { system: 0, tools: 1, user: 2, inject: 3, skill: 4, assistant: 5, tool: 6 } as const
export function tl(over: Partial<ContextTimeline>): ContextTimeline {
return {
ok: true,
current: { system: 0, tools: 0, user: 0, inject: 0, skill: 0, assistant: 0, tool: 0, total: 0 },
requests: [], events: [], nodes: [], droppedNodes: 0, archive: [],
...over,
}
}
export function req(over: Partial<RequestRecord>): RequestRecord {
return {
time: 1_000, seq: 1, turn: 1, step: 0,
system: 0, tools: 0, user: 0, inject: 0, assistant: 0, tool: 0, total: 0,
...over,
}
}
export function catRow(m: Mounted, cat: keyof typeof ROW): HTMLElement {
return queryAll(m.container, '.lc-br-cat-row')[ROW[cat]]
}
export function elemRows(m: Mounted): HTMLElement[] {
return queryAll(m.container, '.lc-br-elem-row')
}
/** Type into the tool-schema search box like a real user (native setter + input event). */
export async function typeToolSearch(m: Mounted, value: string): Promise<void> {
await act(async () => {
const input = query<HTMLInputElement>(m.container, '.lc-br-tool-search')
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set
setter?.call(input, value)
input.dispatchEvent(new Event('input', { bubbles: true }))
})
}
export async function pickStep(m: Mounted, value: string): Promise<void> {
const sel = query<HTMLSelectElement>(m.container, 'select.lc-br-pick')
await act(async () => {
sel.value = value
sel.dispatchEvent(new Event('change', { bubbles: true }))
})
}
export function props(over: Partial<ContextBrowserProps>): ContextBrowserProps {
return { data: tl({}), headers: null, ...over }
}
/** Pair metadata-only `contextHeaders` with a fetchHeader serving per-epoch
* CONTENT — the lazy-load contract the browser consumes: the projection
* carries boundaries and token prices, the content arrives on demand. */
export function withEpochContent(
headers: ContextHeaders,
contents: Record<number, HeaderEpochContent>,
): { headers: ContextHeaders; fetchHeader: (seq: number) => Promise<HeaderEpochContent | null> } {
return { headers, fetchHeader: seq => Promise.resolve(contents[seq] ?? null) }
}