89fff86333
同一轮响应多任务同时终态:各自回调带各自 detail/status、 按响应顺序(非注册顺序)、一个抛错不影响其他、全部移除、 异步回调逐个 await。共 8 个测试。
205 lines
6.0 KiB
TypeScript
205 lines
6.0 KiB
TypeScript
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()
|
|
})
|