✅ test: 补上待处理请求与页面结构的回归用例

This commit is contained in:
pyh
2026-10-04 19:24:03 +08:00
parent ec8661b317
commit 302e0a8bf5
4 changed files with 153 additions and 69 deletions
+82 -22
View File
@@ -1,10 +1,10 @@
// The components, rendered through a tiny React double.
//
// Why a double instead of React: this repository ships no runtime dependency at
// all, and the whole point of these tests is the wiring the refactor introduced —
// which component reads which prop through which seat. The double implements the
// four React entry points this package uses, so a component render is exercised
// end to end (hooks included) without a DOM or a real React.
// all, and the whole point of these tests is the wiring — which component reads
// which prop through which seat. The double implements the four React entry
// points this package uses, so a component render is exercised end to end (hooks
// included) without a DOM or a real React.
//
// It runs against the BUILT artifact, because that is what the page loads.
import { test } from 'node:test'
@@ -98,11 +98,23 @@ function walk(node, collected = { text: [], types: [], elements: [] }) {
}
const flatten = (node) => walk(node).text.join(' ')
const classesOf = (rendered) => rendered.elements.map((element) => element.props.className ?? '')
test('the plugin page renders every section with copy, not raw keys', () => {
/**
* The permission hint the plugin shows on this machine. Node exposes a
* `navigator` too, so the platform branch is exercised here rather than falling
* through to the neutral copy.
*/
const HINT_BY_PLATFORM = {
win32: 'Windows Settings → System → Notifications → DeepSeek Harness',
darwin: 'System Settings → Notifications → DeepSeek Harness',
linux: 'Allow DeepSeek Harness in your desktop notification settings (GNOME / KDE)',
}
const platformHint = HINT_BY_PLATFORM[process.platform] ?? 'Allow DeepSeek Harness in your system notification settings'
test('the plugin page reads as three groups with copy, not raw keys', () => {
const { config } = mount()
const props = { ...config.definition.inject(), view: 'page' }
const rendered = walk(config.component(props))
const rendered = walk(config.component({ ...config.definition.inject(), view: 'page' }))
const text = rendered.text.join(' ')
// No locale service in this context, so the seat falls back to English.
@@ -111,10 +123,14 @@ test('the plugin page renders every section with copy, not raw keys', () => {
'What to notify about',
'The switches are stored in this browser',
'System notification permission',
'The buttons here go straight to the system channel',
'This environment cannot show system notifications',
platformHint,
'Status',
'Window',
'Session watch',
'Last delivery',
'nothing delivered yet',
'nothing waiting to be replayed',
'Replay',
'Test alert',
]) {
assert.ok(text.includes(copy), `the page is missing: ${copy}`)
@@ -122,30 +138,74 @@ test('the plugin page renders every section with copy, not raw keys', () => {
assert.ok(!text.includes('config.'), 'a raw translation key reached the page')
assert.ok(!text.includes('settings.'), 'a raw translation key reached the page')
const classes = rendered.elements.map((element) => element.props.className ?? '')
for (const className of ['dsn-sections', 'dsn-section', 'dsn-section-title', 'dsn-row', 'dsn-button', 'dsn-status']) {
const classes = classesOf(rendered)
for (const className of [
'dsn-page',
'dsn-lede',
'dsn-group',
'dsn-group-title',
'dsn-group-note',
'dsn-list',
'dsn-row',
'dsn-row-title',
'dsn-state',
'dsn-facts',
'dsn-fact-key',
'dsn-fact-value',
'dsn-button',
]) {
assert.ok(classes.includes(className), `the page is missing .${className}`)
}
// Three switches, one status dot, and the readings as key/value pairs.
assert.equal(classes.filter((className) => className === 'dsn-switch').length, 3)
// The live window fact is rendered, not remembered.
assert.match(text, /Window: (in front \(popup is used\)|not in front \(system notification is used\))/)
assert.equal(classes.filter((className) => className === 'dsn-dot is-muted').length, 1)
assert.equal(classes.filter((className) => className === 'dsn-fact-key').length, 4)
assert.equal(classes.filter((className) => className === 'dsn-fact-value').length, 4)
})
test('the page reads the store: switches, permission and diagnostics follow it', () => {
test('the page follows the store: switches, permission state and delivery facts', () => {
const { config } = mount()
const props = config.definition.inject()
props.store.set({
...props.store.getSnapshot(),
kinds: { completion: false, approval: true, question: true },
})
const switched = walk(config.component({ ...props, view: 'page' }))
assert.deepEqual(
switched.elements
.filter((element) => element.props.className === 'dsn-switch')
.map((element) => element.props['aria-checked']),
[false, true, true],
)
props.store.set({
...props.store.getSnapshot(),
permission: 'granted',
lastDelivery: { outcome: 'system', shown: true, at: '17:52:30', title: 'Conversation finished' },
})
const rendered = walk(config.component({ ...props, view: 'page' }))
const switches = rendered.elements
.filter((element) => element.props.className === 'dsn-switch')
.map((element) => element.props['aria-checked'])
assert.deepEqual(switches, [false, true, true])
assert.match(rendered.text.join(' '), /Last delivery: 17:52:30 · system notification \(confirmed on screen\) · Conversation finished/)
const granted = walk(config.component({ ...props, view: 'page' }))
const grantedClasses = classesOf(granted)
// A healthy permission has no warning colour and the button becomes the
// channel-specific test.
assert.equal(grantedClasses.includes('dsn-dot is-muted'), false)
assert.equal(grantedClasses.includes('dsn-dot is-error'), false)
assert.ok(grantedClasses.includes('dsn-dot'))
assert.ok(granted.text.join(' ').includes('Test system notification'))
assert.ok(granted.text.join(' ').includes('17:52:30 · system notification (confirmed on screen)'))
assert.ok(grantedClasses.includes('dsn-fact-title'))
props.store.set({
...props.store.getSnapshot(),
permission: 'denied',
lastDelivery: null,
})
const denied = walk(config.component({ ...props, view: 'page' }))
const deniedText = denied.text.join(' ')
assert.ok(classesOf(denied).includes('dsn-dot is-error'))
assert.ok(deniedText.includes('switched off in system settings'))
assert.ok(deniedText.includes(platformHint))
assert.ok(deniedText.includes('nothing delivered yet'))
})
test('the summary view is the one-line description the plugin row shows', () => {
@@ -169,7 +229,7 @@ test('the popup stack renders nothing while empty, and one card per alert', () =
assert.ok(text.includes('Bash is waiting for your approval'))
assert.ok(text.includes('View'))
const classes = rendered.elements.map((element) => element.props.className ?? '')
const classes = classesOf(rendered)
for (const className of ['dsn-stack', 'dsn-toast', 'dsn-toast-icon is-approval', 'dsn-toast-title', 'dsn-toast-desc', 'dsn-toast-action', 'dsn-toast-close']) {
assert.ok(classes.includes(className), `the popup is missing .${className}`)
}