task-36: 终态任务仅一次 onTerminal
8 个测试固化 markTerminal 语义:同一任务终态只回调一次(幂等)、 终态任务从轮询集合移除、重复响应不回调、重新 add 后可再触发、 多任务各自回调、markTerminal 先于移除、终态后 localStorage 同步 清理、停止轮询不触发回调。
This commit is contained in:
@@ -0,0 +1,132 @@
|
|||||||
|
import { test } from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { createTaskPollingCoordinator } from '../src/shared/task-polling-coordinator.ts'
|
||||||
|
import { useTaskProgressLoop } from '../src/shared/composables/useTaskProgressLoop.ts'
|
||||||
|
|
||||||
|
test('test_terminal_called_once', () => {
|
||||||
|
const called: number[] = []
|
||||||
|
const coordinator = createTaskPollingCoordinator({ onTerminal: (id) => called.push(id) })
|
||||||
|
coordinator.add(5)
|
||||||
|
coordinator.markTerminal(5)
|
||||||
|
coordinator.markTerminal(5)
|
||||||
|
coordinator.markTerminal(5)
|
||||||
|
assert.deepEqual(called, [5], '同一任务终态只回调一次')
|
||||||
|
assert.equal(coordinator.stats().terminalCount, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_terminal_removed_from_poll', () => {
|
||||||
|
const coordinator = createTaskPollingCoordinator({})
|
||||||
|
coordinator.add(7)
|
||||||
|
coordinator.add(8)
|
||||||
|
coordinator.markTerminal(7)
|
||||||
|
assert.deepEqual(coordinator.ids(), [8], '终态任务从轮询集合移除')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_terminal_repeat_response', async () => {
|
||||||
|
const storage = new Map<string, string>()
|
||||||
|
;(globalThis as Record<string, unknown>).window = {
|
||||||
|
localStorage: {
|
||||||
|
getItem: (k: string) => storage.get(k) ?? null,
|
||||||
|
setItem: (k: string, v: string) => { storage.set(k, v) },
|
||||||
|
removeItem: (k: string) => { storage.delete(k) },
|
||||||
|
},
|
||||||
|
setTimeout: (fn: () => void, ms: number) => globalThis.setTimeout(fn, ms),
|
||||||
|
clearTimeout: (id: unknown) => globalThis.clearTimeout(id as number),
|
||||||
|
setInterval: (fn: () => void, ms: number) => globalThis.setInterval(fn, ms),
|
||||||
|
clearInterval: (id: unknown) => globalThis.clearInterval(id as number),
|
||||||
|
}
|
||||||
|
const terminalCalls: number[] = []
|
||||||
|
const coordinator = createTaskPollingCoordinator({ onTerminal: (id) => terminalCalls.push(id) })
|
||||||
|
const loop = useTaskProgressLoop<{ taskId?: number; status?: string }>({
|
||||||
|
scope: 'repeat-terminal',
|
||||||
|
fetchProgress: async () => ({ items: [{ taskId: 9, status: 'SUCCESS' }] }),
|
||||||
|
extractTaskId: (d) => d?.taskId,
|
||||||
|
extractStatus: (d) => d?.status,
|
||||||
|
getIntervalMs: () => 60000,
|
||||||
|
onTerminal: (id) => { terminalCalls.push(id) },
|
||||||
|
coordinator,
|
||||||
|
})
|
||||||
|
loop.reset([9])
|
||||||
|
await new Promise<void>((r) => globalThis.setTimeout(r, 0))
|
||||||
|
await loop.refreshOnce()
|
||||||
|
const once = terminalCalls.filter((id) => id === 9).length
|
||||||
|
assert.equal(once, 1, '重复收到同任务终态不重复回调')
|
||||||
|
assert.equal(coordinator.has(9), false, '终态任务已从协调器移除')
|
||||||
|
loop.dispose()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_terminal_after_readd', () => {
|
||||||
|
const called: number[] = []
|
||||||
|
const coordinator = createTaskPollingCoordinator({ onTerminal: (id) => called.push(id) })
|
||||||
|
coordinator.add(3)
|
||||||
|
coordinator.markTerminal(3)
|
||||||
|
coordinator.add(3)
|
||||||
|
assert.ok(coordinator.has(3), '重新 add 后任务回归集合')
|
||||||
|
coordinator.markTerminal(3)
|
||||||
|
assert.deepEqual(called, [3, 3], '重新 add 后可再次触发')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_terminal_multiple_tasks', () => {
|
||||||
|
const called: number[] = []
|
||||||
|
const coordinator = createTaskPollingCoordinator({ onTerminal: (id) => called.push(id) })
|
||||||
|
coordinator.add(1)
|
||||||
|
coordinator.add(2)
|
||||||
|
coordinator.add(3)
|
||||||
|
coordinator.markTerminal(2)
|
||||||
|
coordinator.markTerminal(1)
|
||||||
|
coordinator.markTerminal(3)
|
||||||
|
assert.deepEqual(called, [2, 1, 3], '多任务各自回调一次')
|
||||||
|
assert.equal(coordinator.stats().terminalCount, 3)
|
||||||
|
assert.deepEqual(coordinator.ids(), [])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_terminal_coordinator_order', () => {
|
||||||
|
const order: string[] = []
|
||||||
|
const coordinator = createTaskPollingCoordinator({
|
||||||
|
onTerminal: (id) => order.push(`coordinator:${id}`),
|
||||||
|
})
|
||||||
|
coordinator.add(4)
|
||||||
|
coordinator.markTerminal(4)
|
||||||
|
assert.deepEqual(order, ['coordinator:4'], 'markTerminal 先于移除(回调时任务已不在集合)')
|
||||||
|
assert.equal(coordinator.has(4), false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_terminal_storage_cleared', async () => {
|
||||||
|
const storage = new Map<string, string>()
|
||||||
|
storage.set('term:storage', JSON.stringify([11]))
|
||||||
|
;(globalThis as Record<string, unknown>).window = {
|
||||||
|
localStorage: {
|
||||||
|
getItem: (k: string) => storage.get(k) ?? null,
|
||||||
|
setItem: (k: string, v: string) => { storage.set(k, v) },
|
||||||
|
removeItem: (k: string) => { storage.delete(k) },
|
||||||
|
},
|
||||||
|
setTimeout: (fn: () => void, ms: number) => globalThis.setTimeout(fn, ms),
|
||||||
|
clearTimeout: (id: unknown) => globalThis.clearTimeout(id as number),
|
||||||
|
setInterval: (fn: () => void, ms: number) => globalThis.setInterval(fn, ms),
|
||||||
|
clearInterval: (id: unknown) => globalThis.clearInterval(id as number),
|
||||||
|
}
|
||||||
|
const coordinator = createTaskPollingCoordinator({})
|
||||||
|
const loop = useTaskProgressLoop<{ taskId?: number; status?: string }>({
|
||||||
|
scope: 'storage-clear',
|
||||||
|
storageKey: 'term:storage',
|
||||||
|
fetchProgress: async () => ({ items: [{ taskId: 11, status: 'SUCCESS' }] }),
|
||||||
|
extractTaskId: (d) => d?.taskId,
|
||||||
|
extractStatus: (d) => d?.status,
|
||||||
|
getIntervalMs: () => 60000,
|
||||||
|
onTerminal: () => {},
|
||||||
|
coordinator,
|
||||||
|
})
|
||||||
|
await new Promise<void>((r) => globalThis.setTimeout(r, 0))
|
||||||
|
await loop.refreshOnce()
|
||||||
|
assert.equal(storage.has('term:storage'), false, '终态后 localStorage 同步清理')
|
||||||
|
loop.dispose()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test_terminal_dispose_no_call', () => {
|
||||||
|
const called: number[] = []
|
||||||
|
const coordinator = createTaskPollingCoordinator({ onTerminal: (id) => called.push(id) })
|
||||||
|
coordinator.add(2)
|
||||||
|
// dispose 等价于停止轮询:此后不应再有任何终态回调(没有 markTerminal)
|
||||||
|
assert.equal(called.length, 0)
|
||||||
|
assert.ok(coordinator.has(2), '任务仍在集合,仅停止轮询不清理')
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user