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.
203 lines
11 KiB
TypeScript
203 lines
11 KiB
TypeScript
import { createElement as h } from 'react'
|
|
import assert from 'node:assert/strict'
|
|
import { describe, test } from 'vitest'
|
|
import { attachMarkers } from '../../../src/client/components/trendChart'
|
|
import { CATS } from '../../../src/client/categories'
|
|
import type { ContextEventRecord } from '../../../src/shared/types'
|
|
import { click, hover, mount, query, queryAll, unhover } from '../helpers/kit'
|
|
import { CHART_H, TrendChart, assertColor, bars, kit, makeSpies, propsOf, req } from './trendChartHarness'
|
|
|
|
describe('TrendChart selection and hover linking', () => {
|
|
test('clicking a bar picks it, clicking the picked bar clears it; turnless bars match activeTurn 0', async () => {
|
|
const { spies, handlers } = makeSpies()
|
|
const x1 = req(1, { turn: undefined, step: undefined })
|
|
const x2 = req(2, { turn: 2, step: 0 })
|
|
const m = await mount(h(TrendChart, propsOf([x1, x2], handlers)))
|
|
|
|
await click(bars(m.container)[0])
|
|
assert.deepEqual(spies.select, [1])
|
|
await m.update(h(TrendChart, propsOf([x1, x2], { ...handlers, selectedSeq: 1 })))
|
|
assert.ok(bars(m.container)[0].className.includes('lc-bar-selected'))
|
|
assert.ok(!bars(m.container)[1].className.includes('lc-bar-selected'))
|
|
|
|
await click(bars(m.container)[0])
|
|
assert.deepEqual(spies.select, [1, null])
|
|
await m.update(h(TrendChart, propsOf([x1, x2], { ...handlers, selectedSeq: 2 })))
|
|
assert.ok(bars(m.container)[1].className.includes('lc-bar-selected'))
|
|
|
|
// activeTurn against a turnless bar: 0 (its ?? fallback) mismatches turn 5, matches turn 0.
|
|
await m.update(h(TrendChart, propsOf([x1, x2], { ...handlers, selectedSeq: 2, activeTurn: 5 })))
|
|
assert.ok(!bars(m.container)[0].className.includes('lc-bar-in-turn'))
|
|
await m.update(h(TrendChart, propsOf([x1, x2], { ...handlers, selectedSeq: 2, activeTurn: 0 })))
|
|
assert.ok(bars(m.container)[0].className.includes('lc-bar-in-turn'))
|
|
assert.ok(!bars(m.container)[1].className.includes('lc-bar-in-turn'))
|
|
await m.unmount()
|
|
})
|
|
})
|
|
|
|
describe('TrendChart category hover-link', () => {
|
|
test('segments carry their category key; the container mirrors the shared hover for CSS to light', async () => {
|
|
const r1 = req(1, { turn: 1, step: 0, skill: 10 })
|
|
const r2 = req(2, { turn: 1, step: 1, system: 200 })
|
|
const r3 = req(3, { turn: 2, step: 0 })
|
|
const m = await mount(h(TrendChart, propsOf([r1, r2, r3], { hoverCat: 'tools' })))
|
|
|
|
await m.update(h(TrendChart, propsOf([r1, r2, r3])))
|
|
const chart = query(m.container, '.lc-chart')
|
|
assert.equal(chart.hasAttribute('data-catdim'), false)
|
|
|
|
// Every total-mode segment is tagged with its category; a zero-value category renders no segment to light.
|
|
const segs = queryAll(chart, '.lc-bar[data-seq="1"] .lc-bar-stack > .lc-cat-seg')
|
|
assert.deepEqual(segs.map(s => s.getAttribute('data-cat')), CATS.map(c => c.key))
|
|
|
|
// The hovered key rides the container attribute — the dim/highlight pairing itself is CSS.
|
|
await m.update(h(TrendChart, propsOf([r1, r2, r3], { hoverCat: 'tools' })))
|
|
assert.equal(query(m.container, '.lc-chart').getAttribute('data-catdim'), 'tools')
|
|
|
|
// Delta mode: diverging stacks' segments are tagged the same way — an all-shrinking pair hangs all seven below the line.
|
|
const big = req(4, { turn: 3, step: 0, system: 200, tools: 100, user: 60, inject: 40, skill: 30, assistant: 80, tool: 120 })
|
|
await m.update(h(TrendChart, propsOf([big, r1], { mode: 'delta', hoverCat: 'user' })))
|
|
const deltaChart = query(m.container, '.lc-chart')
|
|
assert.equal(deltaChart.getAttribute('data-catdim'), 'user')
|
|
assert.deepEqual(
|
|
queryAll(deltaChart, '.lc-bar-down > .lc-cat-seg').map(s => s.getAttribute('data-cat')),
|
|
CATS.map(c => c.key),
|
|
)
|
|
await m.unmount()
|
|
})
|
|
})
|
|
|
|
describe('TrendChart category focus (the browser open category)', () => {
|
|
const r1 = req(1, { turn: 1, step: 0 })
|
|
const r2 = req(2, { turn: 1, step: 1, system: 200, tools: 100, user: 60, inject: 40, assistant: 80, tool: 120, total: 600 })
|
|
|
|
test('total mode plots only the focused category, rescaled to its own max, and the tip names it', async () => {
|
|
const m = await mount(h(TrendChart, propsOf([r1, r2], { focusCat: 'tool' })))
|
|
// maxTotal follows the focused figure (60 / 120), not the bars' whole compositions (300 / 600).
|
|
assert.equal(query(m.container, '.lc-axis-top').textContent, '120')
|
|
const bs = bars(m.container)
|
|
const segs1 = queryAll(bs[0], '.lc-bar-stack > div')
|
|
assert.equal(segs1.length, 1)
|
|
assert.equal(segs1[0].getAttribute('data-cat'), 'tool')
|
|
assertColor(segs1[0].style.background, CATS[6].color)
|
|
assert.equal(segs1[0].style.height, `${Math.round(60 / 120 * CHART_H)}px`)
|
|
assert.equal(queryAll(bs[1], '.lc-bar-stack > div')[0].style.height, `${CHART_H}px`)
|
|
|
|
await m.update(h(TrendChart, propsOf([r1, r2], { focusCat: 'tool', hoveredSeq: 1 })))
|
|
assert.deepEqual(
|
|
queryAll(query(m.container, '.lc-chart-tip'), 'span').map(r => r.textContent),
|
|
[kit.t('tip.step', { t: 1, s: 0, n: 2 }), kit.t('tip.cat', { cat: kit.catLabel('tool'), n: '60' })],
|
|
)
|
|
await m.unmount()
|
|
})
|
|
|
|
test('the focused figure is the raw fold value; a category at zero everywhere keeps the unit axis', async () => {
|
|
// r1a carries usage: the focused tool value (60) still plots as-is — no provider rescale.
|
|
const r1a = req(1, { turn: 1, step: 0, prompt: 600 })
|
|
const m = await mount(h(TrendChart, propsOf([r1a, r2], { focusCat: 'tool' })))
|
|
assert.equal(query(m.container, '.lc-axis-top').textContent, '120')
|
|
assert.equal(queryAll(bars(m.container)[0], '.lc-bar-stack > div')[0].style.height, `${Math.round(60 / 120 * CHART_H)}px`)
|
|
await m.unmount()
|
|
|
|
const zero = req(9, { turn: 1, step: 0, system: 0, tools: 0, user: 0, inject: 0, assistant: 0, tool: 0, total: 0 })
|
|
const mz = await mount(h(TrendChart, propsOf([zero], { focusCat: 'user' })))
|
|
assert.equal(query(mz.container, '.lc-axis-top').textContent, '1')
|
|
assert.equal(queryAll(bars(mz.container)[0], '.lc-bar-stack > div').length, 0)
|
|
await mz.unmount()
|
|
})
|
|
|
|
test('delta mode diffs the focused category only', async () => {
|
|
const base = req(1, { turn: 1, step: 0 })
|
|
const grown = req(2, { turn: 1, step: 1, system: 130, tools: 50, user: 30, inject: 20, assistant: 40, tool: 60, total: 330 })
|
|
const shrunk = req(3, { turn: 2, step: 0, system: 90, tools: 50, user: 30, inject: 20, assistant: 40, tool: 60, total: 290 })
|
|
const m = await mount(h(TrendChart, propsOf([base, grown, shrunk], { mode: 'delta', focusCat: 'system' })))
|
|
// Only the system deltas (+30 / -40) drive the scale: maxUp 30, maxDown 40.
|
|
assert.equal(query(m.container, '.lc-axis-top').textContent, '+30')
|
|
assert.equal(query(m.container, '.lc-axis-bot').textContent, '-40')
|
|
const bs = bars(m.container)
|
|
assert.equal(queryAll(bs[0], '.lc-bar-up > div').length, 0)
|
|
const up = queryAll(bs[1], '.lc-bar-up > div')
|
|
assert.equal(up.length, 1)
|
|
assert.equal(up[0].getAttribute('data-cat'), 'system')
|
|
assert.equal(up[0].style.height, `${Math.round(30 * CHART_H / 70)}px`)
|
|
const down = queryAll(bs[2], '.lc-bar-down > div')
|
|
assert.equal(down.length, 1)
|
|
assert.equal(down[0].style.height, `${Math.round(40 * CHART_H / 70)}px`)
|
|
|
|
await m.update(h(TrendChart, propsOf([base, grown, shrunk], { mode: 'delta', focusCat: 'system', hoveredSeq: 2 })))
|
|
assert.ok(query(m.container, '.lc-chart-tip').textContent!.includes(kit.t('tip.delta', { n: '+30' })))
|
|
await m.unmount()
|
|
})
|
|
|
|
test('an unknown focus key and an explicit null degrade to the unfocused chart', async () => {
|
|
for (const focusCat of ['nope', null]) {
|
|
const m = await mount(h(TrendChart, propsOf([r1, r2], { focusCat })))
|
|
assert.equal(queryAll(bars(m.container)[0], '.lc-bar-stack > div').length, 6)
|
|
assert.equal(query(m.container, '.lc-axis-top').textContent, '600')
|
|
await m.unmount()
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('TrendChart markers', () => {
|
|
test('compaction/prune markers render the ✂ glyph with a positioned or bare title', async () => {
|
|
const reqs = [req(1, { turn: 1, step: 0 }), req(2, { turn: 1, step: 1 }), req(3, { turn: 2, step: 0 })]
|
|
const compaction: ContextEventRecord = { seq: 2, time: 1700000000000, kind: 'compaction', count: 5, fromTurn: 1, fromStep: 0, turn: 1, step: 1 }
|
|
const prune: ContextEventRecord = { seq: 3, time: 1700000060000, kind: 'prune' }
|
|
const markers = attachMarkers(reqs, [compaction, prune])
|
|
assert.equal(markers[1], compaction)
|
|
assert.equal(markers[2], prune)
|
|
|
|
const m = await mount(h(TrendChart, propsOf(reqs, { markers })))
|
|
const glyph = queryAll(m.container, '.lc-bar-marker')
|
|
assert.equal(glyph.length, 2)
|
|
assert.equal(glyph[0].textContent, '✂')
|
|
assert.equal(
|
|
glyph[0].getAttribute('title'),
|
|
'✂ ' + kit.t('events.range', { t: 1, a: 0, b: 1 }) + ' — ' + kit.t('ev.compaction', { n: 5 }),
|
|
)
|
|
// No host-stamped position → bare label title.
|
|
assert.equal(glyph[1].getAttribute('title'), '✂ ' + kit.t('ev.prune'))
|
|
await m.unmount()
|
|
})
|
|
})
|
|
|
|
describe('TrendChart tooltips', () => {
|
|
const r1 = req(1, { turn: 1, step: 0 })
|
|
const r4 = req(4, { turn: undefined, step: undefined })
|
|
|
|
test('hover floats a two-row tip (identity + bar total) and clears on leave', async () => {
|
|
const { spies, handlers } = makeSpies()
|
|
const reqs = [r1, r4]
|
|
const m = await mount(h(TrendChart, propsOf(reqs, handlers)))
|
|
|
|
const bs = bars(m.container)
|
|
await hover(bs[0])
|
|
assert.deepEqual(spies.hover, [1])
|
|
await m.update(h(TrendChart, propsOf(reqs, { ...handlers, hoveredSeq: 1 })))
|
|
assert.deepEqual(
|
|
queryAll(query(m.container, '.lc-chart-tip'), 'span').map(r => r.textContent),
|
|
[kit.t('tip.step', { t: 1, s: 0, n: 1 }), kit.t('tip.total', { n: '300' })],
|
|
)
|
|
assert.equal(query(m.container, '.lc-chart-tip').style.transform, 'translate(7px, 0)')
|
|
|
|
// Turnless/step-less bars fall back to Turn 0 · Step 0.
|
|
await m.update(h(TrendChart, propsOf(reqs, { ...handlers, hoveredSeq: 4 })))
|
|
assert.deepEqual(
|
|
queryAll(query(m.container, '.lc-chart-tip'), 'span').map(r => r.textContent),
|
|
[kit.t('tip.step', { t: 0, s: 0, n: 1 }), kit.t('tip.total', { n: '300' })],
|
|
)
|
|
|
|
await m.update(h(TrendChart, propsOf(reqs, { ...handlers, hoveredSeq: 999 })))
|
|
assert.equal(queryAll(m.container, '.lc-chart-tip').length, 0)
|
|
|
|
await m.update(h(TrendChart, propsOf(reqs, { ...handlers, hoveredSeq: 1 })))
|
|
assert.equal(queryAll(m.container, '.lc-chart-tip').length, 1)
|
|
await unhover(query(m.container, '.lc-chart'))
|
|
assert.deepEqual(spies.hover, [1, null])
|
|
await m.update(h(TrendChart, propsOf(reqs, { ...handlers, hoveredSeq: null })))
|
|
assert.equal(queryAll(m.container, '.lc-chart-tip').length, 0)
|
|
await m.unmount()
|
|
})
|
|
})
|