✅ test: 补上待处理请求与页面结构的回归用例
This commit is contained in:
+30
-35
@@ -8,7 +8,7 @@ import { createCopy } from '../../src/client/core/copy.js'
|
||||
import { createTranslator } from '../../src/client/i18n/index.js'
|
||||
|
||||
const t = createTranslator({ getSnapshot: () => ({ active: 'zh' }) })
|
||||
const { copyFor, describeDelivery, describeReplay } = createCopy(t)
|
||||
const { copyFor, deliveryFacts, replayValue } = createCopy(t)
|
||||
|
||||
/** One trigger candidate with every field a caller may leave empty. */
|
||||
function candidate(overrides) {
|
||||
@@ -41,47 +41,42 @@ test('an approval without a tool name still says what is happening', () => {
|
||||
assert.equal(notice.body, '标题:有工具正在等待你的授权')
|
||||
})
|
||||
|
||||
test('a question carries the question text, and a plan review the plan\'s own text', () => {
|
||||
const question = copyFor(candidate({ kind: 'question', pendingKind: 'question', detail: '要继续吗?' }))
|
||||
assert.deepEqual(question, { kind: 'question', title: '需要回答', body: '标题 · 要继续吗?' })
|
||||
test('a question carries the question text', () => {
|
||||
assert.deepEqual(
|
||||
copyFor(candidate({ kind: 'question', pendingKind: 'question', detail: '要继续吗?' })),
|
||||
{ kind: 'question', title: '需要回答', body: '标题 · 要继续吗?' },
|
||||
)
|
||||
assert.equal(copyFor(candidate({ kind: 'question', pendingKind: 'question', detail: '' })).body, '标题:正在等待你的回答')
|
||||
})
|
||||
|
||||
// v1.0.6 behavior, pinned on purpose: a plan review is queued with
|
||||
// `kind: 'question'`, so the question branch above answers it and the
|
||||
// `body.planReview` copy is unreachable. See the note in core/copy.js — fixing
|
||||
// it changes what a plan review says, so it is not part of this refactor.
|
||||
const review = copyFor(candidate({ kind: 'question', pendingKind: 'plan-review', detail: '' }))
|
||||
test('a plan review says what it is instead of repeating the plan text', () => {
|
||||
// A plan review arrives as `kind: 'question'` with its own discriminator, so the
|
||||
// discriminator has to be read first — otherwise the plan-review copy is unreachable.
|
||||
const review = copyFor(candidate({ kind: 'question', pendingKind: 'plan-review', detail: '计划正文…' }))
|
||||
assert.equal(review.kind, 'question')
|
||||
assert.equal(review.body, '标题:正在等待你的回答')
|
||||
assert.notEqual(review.body, t('body.planReview', { title: '标题' }))
|
||||
assert.equal(review.body, '标题:计划正在等待你确认')
|
||||
assert.equal(review.title, '需要回答')
|
||||
})
|
||||
|
||||
test('a delivery that never happened is described as such', () => {
|
||||
assert.equal(describeDelivery(null, t), '还没有投递过')
|
||||
assert.equal(describeDelivery(undefined, t), '还没有投递过')
|
||||
test('a delivery that never happened has no facts to show', () => {
|
||||
assert.equal(deliveryFacts(null, t), null)
|
||||
assert.equal(deliveryFacts(undefined, t), null)
|
||||
})
|
||||
|
||||
test('the newest delivery is described by channel, confirmation and local time', () => {
|
||||
const system = describeDelivery({ outcome: 'system', shown: true, at: '17:52:30', title: '会话已完成' }, t)
|
||||
assert.equal(system, '最近一次投递: 17:52:30 · 系统通知(系统已确认弹出) · 会话已完成')
|
||||
|
||||
const unconfirmed = describeDelivery({ outcome: 'system', shown: undefined, at: '17:52:30', title: 'X' }, t)
|
||||
assert.match(unconfirmed, /系统没有回报/)
|
||||
|
||||
const refused = describeDelivery({ outcome: 'system', shown: false, at: '17:52:30', title: 'X' }, t)
|
||||
assert.match(refused, /没弹出来/)
|
||||
|
||||
const fellBack = describeDelivery({ outcome: 'system-refused-threw', shown: undefined, at: '17:52:30', title: 'X' }, t)
|
||||
assert.match(fellBack, /系统通知被拒绝,改用了轻弹窗/)
|
||||
|
||||
const replayed = describeDelivery({ outcome: 'replay', at: '17:52:30', title: 'X' }, t)
|
||||
assert.match(replayed, /应用内轻弹窗(回到窗口时补发)/)
|
||||
|
||||
const popup = describeDelivery({ outcome: 'popup', at: '17:52:30', title: 'X' }, t)
|
||||
assert.match(popup, /· 应用内轻弹窗 ·/)
|
||||
test('the newest delivery is split into time, channel and what it was', () => {
|
||||
assert.deepEqual(
|
||||
deliveryFacts({ outcome: 'system', shown: true, at: '17:52:30', title: '会话已完成' }, t),
|
||||
{ at: '17:52:30', outcome: '系统通知(系统已确认弹出)', title: '会话已完成' },
|
||||
)
|
||||
assert.equal(deliveryFacts({ outcome: 'system', shown: undefined, at: '1:00:00', title: 'X' }, t).outcome, '系统通知(系统没有回报,回到窗口时补发轻弹窗)')
|
||||
assert.equal(deliveryFacts({ outcome: 'system', shown: false, at: '1:00:00', title: 'X' }, t).outcome, '系统通知没弹出来,回到窗口时补发轻弹窗')
|
||||
assert.equal(deliveryFacts({ outcome: 'system-refused-threw', at: '1:00:00', title: 'X' }, t).outcome, '系统通知被拒绝,改用了轻弹窗')
|
||||
assert.equal(deliveryFacts({ outcome: 'replay', at: '1:00:00', title: 'X' }, t).outcome, '应用内轻弹窗(回到窗口时补发)')
|
||||
assert.equal(deliveryFacts({ outcome: 'popup', at: '1:00:00', title: 'X' }, t).outcome, '应用内轻弹窗')
|
||||
assert.equal(deliveryFacts({ outcome: 'popup', at: '1:00:00', title: undefined }, t).title, '')
|
||||
})
|
||||
|
||||
test('the replay line counts what is still waiting', () => {
|
||||
assert.equal(describeReplay(0, t), '没有待补发的提醒')
|
||||
assert.equal(describeReplay(3, t), '等你回到窗口时补发:3 条')
|
||||
test('the replay value is short enough for one status line', () => {
|
||||
assert.equal(replayValue(0, t), '没有')
|
||||
assert.equal(replayValue(3, t), '3 条')
|
||||
})
|
||||
|
||||
+39
-11
@@ -9,11 +9,14 @@ import {
|
||||
preview,
|
||||
questionText,
|
||||
servedPendingKind,
|
||||
stillWorth,
|
||||
stillOwed,
|
||||
titleOf,
|
||||
} from '../../src/client/core/session.js'
|
||||
|
||||
test('only the three interaction kinds the Harness renders are served', () => {
|
||||
test('only the three interaction kinds the domains publish are served', () => {
|
||||
// The literals the shipped domains publish: dsh-client-ui-approval sets
|
||||
// `kind = "approval"`, dsh-client-ui-user-questions picks between `"question"`
|
||||
// and `"plan-review"` for one batch.
|
||||
assert.equal(servedPendingKind({ kind: 'approval' }), 'approval')
|
||||
assert.equal(servedPendingKind({ kind: 'question' }), 'question')
|
||||
assert.equal(servedPendingKind({ kind: 'plan-review' }), 'plan-review')
|
||||
@@ -58,17 +61,42 @@ test('the conversation on screen is the one the main view retains', () => {
|
||||
assert.equal(isOnScreen('a', undefined), false)
|
||||
})
|
||||
|
||||
test('a queued alert is dropped when its conversation resumed inside the settle window', () => {
|
||||
const candidate = { kind: 'completion', sessionId: 'a', test: false }
|
||||
test('a pending interaction stays owed while it waits, even though the run is still running', () => {
|
||||
// The session is `running` whenever the agent loop is active, and a pending
|
||||
// approval or question is exactly that: the loop is waiting for the human. The
|
||||
// run state must therefore not gate these alerts — it used to drop every one
|
||||
// of them, which is why only completions ever reached the user.
|
||||
const candidate = { kind: 'approval', sessionId: 'a', pendingKind: 'approval', test: false }
|
||||
const status = (pendingInteraction, running) => new Map([['a', { pendingInteraction, running }]])
|
||||
|
||||
assert.equal(stillOwed(candidate, { list: { byId: { a: { running: true } } }, status: status({ kind: 'approval' }, true) }), true)
|
||||
assert.equal(stillOwed(candidate, { list: { byId: { a: { running: false } } }, status: status({ kind: 'approval' }, false) }), true)
|
||||
// Answered inside the settle window: the request is gone, so nothing is owed.
|
||||
assert.equal(stillOwed(candidate, { list: { byId: { a: { running: true } } }, status: status(undefined, true) }), false)
|
||||
// Replaced by a different request of the same kind: a new request is a new alert.
|
||||
assert.equal(stillOwed({ ...candidate, kind: 'question', pendingKind: 'question' }, { list: { byId: {} }, status: status({ kind: 'approval' }, true) }), false)
|
||||
// A plan review is owed on its own discriminator.
|
||||
assert.equal(stillOwed({ ...candidate, kind: 'question', pendingKind: 'plan-review' }, { list: { byId: {} }, status: status({ kind: 'plan-review' }, true) }), true)
|
||||
// Nothing contradicts the request, so it is still the user's move.
|
||||
assert.equal(stillOwed(candidate, { list: { byId: {} }, status: new Map() }), true)
|
||||
assert.equal(stillOwed(candidate, { list: undefined, status: undefined }), true)
|
||||
})
|
||||
|
||||
test('a completion is owed only while the conversation stayed idle', () => {
|
||||
const candidate = { kind: 'completion', sessionId: 'a', pendingKind: '', test: false }
|
||||
const live = (byId, status) => ({ list: { byId }, status })
|
||||
|
||||
assert.equal(stillWorth(candidate, { list: undefined, status: undefined }), true)
|
||||
assert.equal(stillWorth(candidate, live({ a: { running: false } })), true)
|
||||
assert.equal(stillWorth(candidate, live({})), true)
|
||||
assert.equal(stillWorth(candidate, live({ a: { running: true } })), false)
|
||||
assert.equal(stillOwed(candidate, { list: undefined, status: undefined }), true)
|
||||
assert.equal(stillOwed(candidate, live({ a: { running: false } })), true)
|
||||
assert.equal(stillOwed(candidate, live({})), true)
|
||||
assert.equal(stillOwed(candidate, live({ a: { running: true } })), false)
|
||||
// The per-session selector wins over the list summary, which still carries the
|
||||
// pre-transition flag.
|
||||
assert.equal(stillWorth(candidate, { list: { byId: { a: { running: true } } }, status: new Map([['a', { running: false }]]) }), true)
|
||||
// A test alert is owed no matter what the live state says.
|
||||
assert.equal(stillWorth({ ...candidate, test: true }, live({ a: { running: true } })), true)
|
||||
assert.equal(stillOwed(candidate, { list: { byId: { a: { running: true } } }, status: new Map([['a', { running: false }]]) }), true)
|
||||
})
|
||||
|
||||
test('a test alert is always owed', () => {
|
||||
const live = { list: { byId: { a: { running: true } } }, status: new Map([['a', { running: true }]]) }
|
||||
assert.equal(stillOwed({ kind: 'completion', sessionId: 'a', pendingKind: '', test: true }, live), true)
|
||||
assert.equal(stillOwed({ kind: 'approval', sessionId: 'a', pendingKind: 'approval', test: true }, live), true)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user