✅ test: 补上待处理请求与页面结构的回归用例
This commit is contained in:
+82
-22
@@ -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}`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user