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.
161 lines
6.3 KiB
TypeScript
161 lines
6.3 KiB
TypeScript
// openPluginSettings (src/client/settingsJump.ts): the guarded DOM jump to the plugin's preferences on the Plugins
|
|
// main panel — panel entry first, the bundle card's open control after the page settles.
|
|
|
|
import assert from 'node:assert/strict'
|
|
import { afterEach, describe, test } from 'vitest'
|
|
import { openPluginSettings } from '../../src/client/settingsJump'
|
|
|
|
/** Immediate scheduler: captures the deferred runs in issue order. */
|
|
function syncSchedule(): { runs: Array<() => void>; schedule: (run: () => void, ms: number) => void } {
|
|
const runs: Array<() => void> = []
|
|
return { runs, schedule: (run) => { runs.push(run) } }
|
|
}
|
|
|
|
/** A sidebar-style settings trigger (dialog semantics + expanded flag) — a distractor the jump must ignore. */
|
|
function trigger(doc: Document, expanded: 'true' | 'false'): HTMLButtonElement {
|
|
const b = doc.createElement('button')
|
|
b.setAttribute('aria-haspopup', 'dialog')
|
|
b.setAttribute('aria-expanded', expanded)
|
|
doc.body.appendChild(b)
|
|
return b
|
|
}
|
|
|
|
function clicks(el: HTMLElement): { count: () => number } {
|
|
let n = 0
|
|
el.addEventListener('click', () => { n++ })
|
|
return { count: () => n }
|
|
}
|
|
|
|
describe('openPluginSettings (the Plugins main panel)', () => {
|
|
afterEach(() => {
|
|
document.body.textContent = ''
|
|
})
|
|
|
|
/** A sidebar-style Plugins panel entry (shipped aria-label). */
|
|
function panelEntry(doc: Document, label: string): HTMLButtonElement {
|
|
const b = doc.createElement('button')
|
|
b.setAttribute('aria-label', label)
|
|
doc.body.appendChild(b)
|
|
return b
|
|
}
|
|
|
|
/** The bundle card as the Plugins page ships it: a title button plus the enable switch. */
|
|
function bundleCard(doc: Document, withTitle = true): { title: HTMLButtonElement; switchEl: HTMLButtonElement } {
|
|
const li = doc.createElement('li')
|
|
li.setAttribute('data-plugin-package', 'dsh-context')
|
|
const title = doc.createElement('button')
|
|
title.setAttribute('aria-label', 'View dsh-context')
|
|
li.appendChild(title)
|
|
const switchEl = doc.createElement('button')
|
|
switchEl.setAttribute('role', 'switch')
|
|
switchEl.setAttribute('aria-label', 'Enable dsh-context')
|
|
li.appendChild(switchEl)
|
|
if (!withTitle) title.remove()
|
|
doc.body.appendChild(li)
|
|
return { title, switchEl }
|
|
}
|
|
|
|
test('clicks the Plugins panel entry, then follows the page to the bundle card', () => {
|
|
const panel = panelEntry(document, '插件')
|
|
const pClicks = clicks(panel)
|
|
const t = trigger(document, 'false')
|
|
const tClicks = clicks(t)
|
|
const { runs, schedule } = syncSchedule()
|
|
|
|
openPluginSettings(document, schedule)
|
|
|
|
assert.equal(pClicks.count(), 1, 'the panel entry is clicked immediately')
|
|
assert.equal(tClicks.count(), 0, 'the settings-dialog chrome is never touched')
|
|
assert.equal(runs.length, 1, 'the card poll is scheduled')
|
|
// The list has not rendered the card yet: the poll keeps ticking.
|
|
runs[0]()
|
|
assert.equal(runs.length, 2)
|
|
const { title, switchEl } = bundleCard(document)
|
|
const titleClicks = clicks(title)
|
|
const switchClicks = clicks(switchEl)
|
|
runs[1]()
|
|
assert.equal(titleClicks.count(), 1, 'the title control opens the bundle page')
|
|
assert.equal(switchClicks.count(), 0, 'the enable switch is never touched')
|
|
assert.equal(runs.length, 2, 'the poll stops on success')
|
|
})
|
|
|
|
test('the en-labeled panel entry matches too', () => {
|
|
const panel = panelEntry(document, 'Plugins')
|
|
const pClicks = clicks(panel)
|
|
const { runs, schedule } = syncSchedule()
|
|
|
|
openPluginSettings(document, schedule)
|
|
|
|
assert.equal(pClicks.count(), 1)
|
|
assert.equal(runs.length, 1)
|
|
})
|
|
|
|
test('the default schedule defers through window.setTimeout', async () => {
|
|
const panel = panelEntry(document, '插件')
|
|
const pClicks = clicks(panel)
|
|
|
|
openPluginSettings(document)
|
|
await new Promise(resolve => { setTimeout(resolve, 20) })
|
|
|
|
assert.equal(pClicks.count(), 1)
|
|
})
|
|
|
|
test('the poll keeps ticking to its deadline and stops silently without a card', () => {
|
|
const panel = panelEntry(document, 'Plugins')
|
|
const pClicks = clicks(panel)
|
|
const { runs, schedule } = syncSchedule()
|
|
|
|
openPluginSettings(document, schedule)
|
|
// 1 bootstrap tick + 40 poll ticks, then the chain ends.
|
|
for (let i = 0; i < 60 && runs.length > 0; i++) runs.shift()?.()
|
|
|
|
assert.equal(pClicks.count(), 1)
|
|
assert.equal(runs.length, 0, 'the poll exhausted its ticks and stopped')
|
|
})
|
|
|
|
test('a chrome without the Plugins panel entry degrades to a silent no-op', () => {
|
|
trigger(document, 'false')
|
|
const { runs, schedule } = syncSchedule()
|
|
assert.doesNotThrow(() => openPluginSettings(document, schedule))
|
|
assert.equal(runs.length, 0, 'no card poll without the panel entry')
|
|
})
|
|
|
|
test('a panel entry that throws on click never surfaces the error', () => {
|
|
const panel = panelEntry(document, '插件')
|
|
panel.click = () => { throw new Error('detached') }
|
|
const { runs, schedule } = syncSchedule()
|
|
assert.doesNotThrow(() => openPluginSettings(document, schedule))
|
|
// The entry's failure aborts this jump entirely — the guarded path.
|
|
assert.equal(runs.length, 0)
|
|
})
|
|
|
|
test('a hostile card (no title control, or one that throws) never surfaces an error', () => {
|
|
panelEntry(document, '插件')
|
|
const { runs, schedule } = syncSchedule()
|
|
openPluginSettings(document, schedule)
|
|
runs.shift()?.()
|
|
// Only a switch inside the card: no unique open control, the poll keeps ticking.
|
|
bundleCard(document, false)
|
|
assert.doesNotThrow(() => runs.shift()?.())
|
|
assert.ok(runs.length > 0, 'still polling')
|
|
// The card's title control throws on click: contained by the poll's guard, still polling.
|
|
document.querySelector('[data-plugin-package="dsh-context"]')?.remove()
|
|
const { title, switchEl } = bundleCard(document)
|
|
const switchClicks = clicks(switchEl)
|
|
title.click = () => { throw new Error('detached') }
|
|
assert.doesNotThrow(() => runs.shift()?.())
|
|
assert.equal(switchClicks.count(), 0, 'the enable switch is never touched')
|
|
})
|
|
|
|
test('lookalike aria-labels are ignored (exact match only)', () => {
|
|
const lookalike = panelEntry(document, 'My Plugins')
|
|
const lClicks = clicks(lookalike)
|
|
const { runs, schedule } = syncSchedule()
|
|
|
|
openPluginSettings(document, schedule)
|
|
|
|
assert.equal(lClicks.count(), 0)
|
|
assert.equal(runs.length, 0, 'no panel entry: silent no-op')
|
|
})
|
|
})
|