Apply the AGENTS.md comments guideline to every .ts under tests/: remove what the code already expresses, keep only what it cannot. Removed: spec-header coverage inventories the describe/test tree already names; comments restating the next line's assertion or its message; helper JSDoc whose body already says it; within-file duplicates; five stale claims (trendChart's "six aggregated turns" against agg.length === 8, agentGraph's "six categories" against CATS' seven, fold-timing's "cheapest call LAST" against a loop that feeds it first, fold-surface's unreachable plain-JSON note, contextView's "two turns" for a one-turn fixture). Kept and condensed: the projection-freeze class, strict wire/state-schema hazards, retention and pricing windows, pairing rules, hostile-input rationale, fixture and expectation-array ground truth, jsdom mechanics, and issue-numbered regressions. Comment lines 2604 -> 2014 (-22%); 85 module headers kept, 30 coverage inventories dropped; every surviving comment line is within the 140-column limit, with 36 wrapped lines joined onto fuller ones. No logic, string, identifier, import or directive changed: a comment-strip gate proves each file's comment-free code is byte-identical to HEAD, and a line-level proof pairs all 845 changed lines with an unchanged code part. pnpm run lint && pnpm run test (106 files / 2061 tests, per-file 100% coverage) && pnpm run build all pass.
112 lines
5.4 KiB
TypeScript
112 lines
5.4 KiB
TypeScript
// The Context emblem (src/client/icon.tsx): the bundled document sheet behind the right-Sidebar tab type's glyph and
|
|
// chip-title seats. Bundled rather than read off the harness primitives, so these specs render the real component; the
|
|
// same sheet ships as the package-root icon.svg, pinned in lockstep by a spec here.
|
|
|
|
import { readFile } from 'node:fs/promises'
|
|
import { createElement as h } from 'react'
|
|
import assert from 'node:assert/strict'
|
|
import { describe, test } from 'vitest'
|
|
import { ContextIcon, makeContextTabTitle } from '../../src/client/icon'
|
|
import { makeKit, mount, query, queryAll, text } from './helpers/kit'
|
|
|
|
describe('ContextIcon', () => {
|
|
test('draws the colourful sheet at the requested edge', async () => {
|
|
const m = await mount(h(ContextIcon, { size: 16, className: 'lc-title-icon' }))
|
|
const svg = query<SVGSVGElement>(m.container, 'svg')
|
|
assert.equal(svg.getAttribute('width'), '16')
|
|
assert.equal(svg.getAttribute('height'), '16')
|
|
assert.equal(svg.getAttribute('viewBox'), '0 0 1024 1024')
|
|
assert.equal(svg.getAttribute('class'), 'lc-title-icon')
|
|
assert.equal(svg.getAttribute('aria-hidden'), 'true')
|
|
// Every stroke carries its own fixed fill — the sheet stays polychrome.
|
|
const paths = queryAll<SVGPathElement>(m.container, 'path')
|
|
assert.equal(paths.length, 10)
|
|
const fills = paths.map(p => p.getAttribute('fill'))
|
|
assert.ok(fills.every(f => typeof f === 'string' && f.startsWith('#')))
|
|
assert.equal(new Set(fills).size, 6, 'the six palette colours all appear')
|
|
await m.unmount()
|
|
})
|
|
|
|
test('defaults to a size and can drop the class', async () => {
|
|
const m = await mount(h(ContextIcon, {}))
|
|
const svg = query<SVGSVGElement>(m.container, 'svg')
|
|
assert.equal(svg.getAttribute('width'), '20')
|
|
assert.equal(svg.getAttribute('height'), '20')
|
|
assert.equal(svg.getAttribute('class'), null)
|
|
await m.unmount()
|
|
})
|
|
|
|
test('the mono seat inks the sheet in the current colour at the harness line weight', async () => {
|
|
const m = await mount(h(ContextIcon, { mono: true }))
|
|
const svg = query<SVGSVGElement>(m.container, 'svg')
|
|
assert.equal(svg.innerHTML.match(/fill="#[0-9A-Fa-f]{6}"/g), null, 'no palette fill survives')
|
|
const rects = queryAll<SVGRectElement>(m.container, 'rect')
|
|
assert.equal(
|
|
rects.filter(r => r.getAttribute('fill') === 'currentColor').length,
|
|
1,
|
|
'one current-colour shape carries the whole glyph',
|
|
)
|
|
// The plugin ships one artwork: the drawing appears exactly once, in the
|
|
// complement mask that each eroding shift is painted through.
|
|
assert.equal(queryAll<SVGPathElement>(m.container, 'path').length, 10)
|
|
// The inset itself: a white flood minus one black shift per axis
|
|
// direction, 17 units each — the source's 98-unit bars less the harness weight's 64 makes for two 17s.
|
|
const [hole, thin] = queryAll<SVGMaskElement>(m.container, 'mask')
|
|
assert.match(hole.id, /-hole$/)
|
|
assert.match(thin.id, /-thin$/)
|
|
const shifts = queryAll<SVGRectElement>(thin, 'rect').filter(r => r.getAttribute('fill') === '#000')
|
|
assert.deepEqual(shifts.map(r => r.getAttribute('transform')), [
|
|
'translate(17 0)', 'translate(-17 0)', 'translate(0 17)', 'translate(0 -17)',
|
|
])
|
|
assert.ok(shifts.every(r => r.getAttribute('mask') === `url(#${hole.id})`))
|
|
await m.unmount()
|
|
})
|
|
|
|
test('emblem instances never share mask defs', async () => {
|
|
const a = await mount(h(ContextIcon, { mono: true }))
|
|
const b = await mount(h(ContextIcon, { mono: true }))
|
|
const idsOf = (m: { container: HTMLElement }): string[] =>
|
|
queryAll<SVGMaskElement>(m.container, 'mask').map(mask => mask.id)
|
|
const ids = [...idsOf(a), ...idsOf(b)]
|
|
assert.equal(new Set(ids).size, ids.length, 'a duplicate id would strand one emblem on the other defs')
|
|
await a.unmount()
|
|
await b.unmount()
|
|
})
|
|
|
|
test('the package-root icon.svg stays in lockstep with the component', async () => {
|
|
// The static file is what the Host's package-meta reader serves to the
|
|
// Plugins page (package.json `icon`), so a component edit that skips the
|
|
// file — or a manual file edit that skips the component — fails here.
|
|
const raw = await readFile('icon.svg', 'utf8')
|
|
const svgPaths = [...raw.matchAll(/<path d="([^"]+)" fill="([^"]+)"\/>/g)].map(([, d, fill]) => ({ d, fill }))
|
|
const m = await mount(h(ContextIcon, {}))
|
|
const componentPaths = queryAll<SVGPathElement>(m.container, 'path').map(p => ({
|
|
d: p.getAttribute('d'),
|
|
fill: p.getAttribute('fill'),
|
|
}))
|
|
await m.unmount()
|
|
assert.ok(svgPaths.length >= 10)
|
|
assert.deepEqual(svgPaths, componentPaths)
|
|
assert.match(raw, /^<svg xmlns="http:\/\/www\.w3\.org\/2000\/svg" viewBox="0 0 1024 1024">/)
|
|
})
|
|
})
|
|
|
|
describe('makeContextTabTitle — the chip-title seat', () => {
|
|
test('renders the emblem beside the plugin label in the active locale', async () => {
|
|
const { t } = makeKit()
|
|
const Title = makeContextTabTitle(t)
|
|
const m = await mount(h(Title))
|
|
assert.equal(query<SVGSVGElement>(m.container, 'svg').getAttribute('width'), '16')
|
|
const label = query<HTMLSpanElement>(m.container, '.lc-title-label')
|
|
assert.equal(text(label), 'Context')
|
|
await m.unmount()
|
|
})
|
|
|
|
test('follows the bound translate at render (zh label)', async () => {
|
|
const Title = makeContextTabTitle(makeKit('zh').t)
|
|
const m = await mount(h(Title))
|
|
assert.equal(text(m.container), '上下文')
|
|
await m.unmount()
|
|
})
|
|
})
|