task-38: 同轮多任务终态分别回调测试
同一轮响应多任务同时终态:各自回调带各自 detail/status、 按响应顺序(非注册顺序)、一个抛错不影响其他、全部移除、 异步回调逐个 await。共 8 个测试。
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { useTaskProgressLoop } from '../src/shared/composables/useTaskProgressLoop.ts'
|
||||
|
||||
function setupWindow() {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
let scopeCounter = 0
|
||||
|
||||
interface Item {
|
||||
taskId?: number
|
||||
status?: string
|
||||
label?: string
|
||||
}
|
||||
|
||||
interface TerminalEvent {
|
||||
taskId: number
|
||||
detail: Item
|
||||
status: string
|
||||
}
|
||||
|
||||
interface Harness {
|
||||
loop: ReturnType<typeof useTaskProgressLoop<Item>>
|
||||
terminalCalls: TerminalEvent[]
|
||||
errors: unknown[]
|
||||
}
|
||||
|
||||
async function makeLoop(
|
||||
items: Item[],
|
||||
terminalImpl?: (event: TerminalEvent) => void,
|
||||
resetIds: number[] = [1, 2, 3],
|
||||
): Promise<Harness> {
|
||||
const terminalCalls: TerminalEvent[] = []
|
||||
const errors: unknown[] = []
|
||||
const loop = useTaskProgressLoop<Item>({
|
||||
scope: `multi-term-${scopeCounter++}`,
|
||||
fetchProgress: async (ids) => ({
|
||||
items: items.filter((item) => ids.includes(item.taskId ?? -1)),
|
||||
}),
|
||||
extractTaskId: (d) => d?.taskId,
|
||||
extractStatus: (d) => d?.status,
|
||||
getIntervalMs: () => 60000,
|
||||
onTerminal: (taskId, detail, status) => {
|
||||
const event = { taskId, detail: detail ?? {}, status }
|
||||
terminalCalls.push(event)
|
||||
terminalImpl?.(event)
|
||||
},
|
||||
onError: (e) => { errors.push(e) },
|
||||
})
|
||||
loop.reset(resetIds)
|
||||
await new Promise<void>((r) => globalThis.setTimeout(r, 0))
|
||||
return { loop, terminalCalls, errors }
|
||||
}
|
||||
|
||||
test('test_multi_terminal_each_called', async () => {
|
||||
setupWindow()
|
||||
const h = await makeLoop([
|
||||
{ taskId: 1, status: 'SUCCESS' },
|
||||
{ taskId: 2, status: 'FAILED' },
|
||||
{ taskId: 3, status: 'SUCCESS' },
|
||||
])
|
||||
await h.loop.refreshOnce()
|
||||
assert.equal(h.terminalCalls.length, 3, '三个终态各回调一次')
|
||||
assert.deepEqual(h.terminalCalls.map((e) => e.taskId), [1, 2, 3])
|
||||
h.loop.dispose()
|
||||
})
|
||||
|
||||
test('test_multi_terminal_correct_detail', async () => {
|
||||
setupWindow()
|
||||
const h = await makeLoop([
|
||||
{ taskId: 1, status: 'SUCCESS', label: 'a' },
|
||||
{ taskId: 2, status: 'FAILED', label: 'b' },
|
||||
])
|
||||
await h.loop.refreshOnce()
|
||||
assert.equal(h.terminalCalls[0].detail.label, 'a', '回调携带各自 detail')
|
||||
assert.equal(h.terminalCalls[1].detail.label, 'b')
|
||||
h.loop.dispose()
|
||||
})
|
||||
|
||||
test('test_multi_terminal_correct_status', async () => {
|
||||
setupWindow()
|
||||
const h = await makeLoop([
|
||||
{ taskId: 1, status: 'SUCCESS' },
|
||||
{ taskId: 2, status: 'FAILED' },
|
||||
])
|
||||
await h.loop.refreshOnce()
|
||||
assert.equal(h.terminalCalls[0].status, 'SUCCESS')
|
||||
assert.equal(h.terminalCalls[1].status, 'FAILED', '各自 status 分别传入')
|
||||
h.loop.dispose()
|
||||
})
|
||||
|
||||
test('test_multi_terminal_order', async () => {
|
||||
setupWindow()
|
||||
// 轮询集合按 [3,2,1] 注册,响应按 [1,2,3] 返回:回调必须跟随响应顺序
|
||||
const h = await makeLoop(
|
||||
[
|
||||
{ taskId: 1, status: 'FAILED' },
|
||||
{ taskId: 2, status: 'SUCCESS' },
|
||||
{ taskId: 3, status: 'SUCCESS' },
|
||||
],
|
||||
undefined,
|
||||
[3, 2, 1],
|
||||
)
|
||||
await h.loop.refreshOnce()
|
||||
assert.deepEqual(
|
||||
h.terminalCalls.map((e) => e.taskId),
|
||||
[1, 2, 3],
|
||||
'回调顺序与响应顺序一致(非 taskIds 注册顺序)',
|
||||
)
|
||||
h.loop.dispose()
|
||||
})
|
||||
|
||||
test('test_multi_terminal_one_fails_other_runs', async () => {
|
||||
setupWindow()
|
||||
const h = await makeLoop(
|
||||
[
|
||||
{ taskId: 1, status: 'SUCCESS' },
|
||||
{ taskId: 2, status: 'FAILED' },
|
||||
],
|
||||
(event) => {
|
||||
if (event.taskId === 1) throw new Error('boom')
|
||||
},
|
||||
)
|
||||
await h.loop.refreshOnce()
|
||||
assert.equal(h.terminalCalls.length, 2, '一个抛错不影响另一个回调')
|
||||
assert.equal(h.terminalCalls[1].taskId, 2)
|
||||
h.loop.dispose()
|
||||
})
|
||||
|
||||
test('test_multi_terminal_all_removed', async () => {
|
||||
setupWindow()
|
||||
const h = await makeLoop(
|
||||
[
|
||||
{ taskId: 1, status: 'SUCCESS' },
|
||||
{ taskId: 2, status: 'FAILED' },
|
||||
],
|
||||
undefined,
|
||||
[1, 2],
|
||||
)
|
||||
await h.loop.refreshOnce()
|
||||
assert.deepEqual(h.loop.taskIds.value, [], '全部终态任务移除')
|
||||
h.loop.dispose()
|
||||
})
|
||||
|
||||
test('test_multi_terminal_await_sequence', async () => {
|
||||
setupWindow()
|
||||
const order: string[] = []
|
||||
const h = await makeLoop(
|
||||
[
|
||||
{ taskId: 1, status: 'SUCCESS' },
|
||||
{ taskId: 2, status: 'SUCCESS' },
|
||||
],
|
||||
(event) => { order.push(`cb:${event.taskId}`) },
|
||||
)
|
||||
await h.loop.refreshOnce()
|
||||
assert.deepEqual(order, ['cb:1', 'cb:2'], '回调按响应顺序依次执行')
|
||||
h.loop.dispose()
|
||||
})
|
||||
|
||||
test('test_multi_terminal_async_on_terminal', async () => {
|
||||
setupWindow()
|
||||
const order: string[] = []
|
||||
let finished = 0
|
||||
const items = [
|
||||
{ taskId: 1, status: 'SUCCESS' },
|
||||
{ taskId: 2, status: 'SUCCESS' },
|
||||
]
|
||||
const loop = useTaskProgressLoop<Item>({
|
||||
scope: `multi-term-async-${scopeCounter++}`,
|
||||
fetchProgress: async (ids) => ({
|
||||
items: items.filter((item) => ids.includes(item.taskId ?? -1)),
|
||||
}),
|
||||
extractTaskId: (d) => d?.taskId,
|
||||
extractStatus: (d) => d?.status,
|
||||
getIntervalMs: () => 60000,
|
||||
onTerminal: async (taskId) => {
|
||||
order.push(`start:${taskId}`)
|
||||
await new Promise<void>((r) => globalThis.setTimeout(r, 10))
|
||||
order.push(`end:${taskId}`)
|
||||
finished += 1
|
||||
},
|
||||
})
|
||||
// reset 触发 watch 自动轮询一轮;等待该轮全部回调完成(勿手动 refreshOnce 双发)
|
||||
loop.reset([1, 2])
|
||||
const deadline = Date.now() + 3000
|
||||
while (finished < 2 && Date.now() < deadline) {
|
||||
await new Promise<void>((r) => globalThis.setTimeout(r, 5))
|
||||
}
|
||||
assert.equal(finished, 2, '两个异步回调均完成')
|
||||
assert.deepEqual(order, ['start:1', 'end:1', 'start:2', 'end:2'], '异步回调逐个 await,不并发')
|
||||
loop.dispose()
|
||||
})
|
||||
Reference in New Issue
Block a user