feat(web): add task SSE client and live phase-event mapper
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { applyPhaseEvent } from './phaseEventMapper';
|
||||
|
||||
test('applyPhaseEvent: appends first phase as running node', () => {
|
||||
const strip = applyPhaseEvent([], { phase_id: 'squad_assembly', message: '组建小队' });
|
||||
assert.equal(strip.length, 1);
|
||||
assert.equal(strip[0].step, 'squad_assembly');
|
||||
assert.equal(strip[0].icon, '👥');
|
||||
assert.equal(strip[0].status, 'running');
|
||||
});
|
||||
|
||||
test('applyPhaseEvent: completes predecessors when a new phase arrives', () => {
|
||||
let strip = applyPhaseEvent([], { phase_id: 'squad_assembly', message: 'a' });
|
||||
strip = applyPhaseEvent(strip, { phase_id: 'knowledge_retrieval', message: 'b' });
|
||||
assert.equal(strip.length, 2);
|
||||
assert.equal(strip[0].status, 'completed');
|
||||
assert.equal(strip[1].status, 'running');
|
||||
});
|
||||
|
||||
test('applyPhaseEvent: same phase refreshes label in place (no duplicate node)', () => {
|
||||
let strip = applyPhaseEvent([], { phase_id: 'quality_gate', message: 'x', progress: 85 });
|
||||
strip = applyPhaseEvent(strip, { phase_id: 'quality_gate', message: 'y', progress: 95 });
|
||||
assert.equal(strip.length, 1);
|
||||
assert.match(strip[0].label, /95%/);
|
||||
});
|
||||
|
||||
test('applyPhaseEvent: unknown phase falls back to hourglass meta', () => {
|
||||
const strip = applyPhaseEvent([], { phase_id: 'mystery_phase' });
|
||||
assert.equal(strip[0].icon, '⏳');
|
||||
assert.equal(strip[0].label, '任务推进');
|
||||
});
|
||||
|
||||
test('applyPhaseEvent: prefers backend message over short label', () => {
|
||||
const strip = applyPhaseEvent([], { phase_id: 'squad_assembly', message: '正在组建真实小队...' });
|
||||
assert.equal(strip[0].icon, '👥');
|
||||
assert.equal(strip[0].label, '正在组建真实小队...');
|
||||
});
|
||||
|
||||
test('applyPhaseEvent: step_completed closes running entry without appending', () => {
|
||||
let strip = applyPhaseEvent([], { phase_id: 'squad_assembly', message: 'a' });
|
||||
strip = applyPhaseEvent(strip, { phase_id: 'step_executing', message: '撰写中' });
|
||||
strip = applyPhaseEvent(strip, { phase_id: 'step_completed', message: '完成' });
|
||||
assert.equal(strip.length, 2);
|
||||
assert.equal(strip[0].status, 'completed');
|
||||
assert.equal(strip[1].status, 'completed');
|
||||
assert.match(strip[1].label, /撰写中/);
|
||||
});
|
||||
|
||||
test('applyPhaseEvent: stage_completed closes entries', () => {
|
||||
let strip = applyPhaseEvent([], { phase_id: 'stage_started', message: 's' });
|
||||
strip = applyPhaseEvent(strip, { phase_id: 'stage_completed', message: 'done' });
|
||||
assert.equal(strip.length, 1);
|
||||
assert.equal(strip[0].status, 'completed');
|
||||
});
|
||||
|
||||
test('applyPhaseEvent: task_failed appends warning node', () => {
|
||||
let strip = applyPhaseEvent([], { phase_id: 'step_executing', message: 'e' });
|
||||
strip = applyPhaseEvent(strip, { phase_id: 'task_failed', message: 'boom' });
|
||||
assert.equal(strip.length, 2);
|
||||
assert.equal(strip[0].status, 'completed');
|
||||
assert.equal(strip[1].step, 'task_failed');
|
||||
assert.equal(strip[1].icon, '⚠️');
|
||||
assert.equal(strip[1].status, 'running');
|
||||
});
|
||||
|
||||
test('applyPhaseEvent: decorates label with step index / total steps', () => {
|
||||
const strip = applyPhaseEvent([], {
|
||||
phase_id: 'step_executing',
|
||||
message: 'm',
|
||||
step_index: 2,
|
||||
total_steps: 3,
|
||||
});
|
||||
assert.match(strip[0].label, /第 2\/3 步/);
|
||||
});
|
||||
|
||||
test('applyPhaseEvent: repeated step_executing with step_index gets separate nodes', () => {
|
||||
let strip = applyPhaseEvent([], { phase_id: 'step_executing', message: '一', step_index: 1, total_steps: 3 });
|
||||
strip = applyPhaseEvent(strip, { phase_id: 'step_executing', message: '二', step_index: 2, total_steps: 3 });
|
||||
assert.equal(strip.length, 2);
|
||||
assert.equal(strip[0].status, 'completed');
|
||||
assert.match(strip[0].label, /第 1\/3 步/);
|
||||
assert.equal(strip[1].status, 'running');
|
||||
assert.match(strip[1].label, /第 2\/3 步/);
|
||||
});
|
||||
|
||||
test('applyPhaseEvent: ignores event without phase payload', () => {
|
||||
const strip = applyPhaseEvent([], undefined);
|
||||
assert.equal(strip.length, 0);
|
||||
});
|
||||
Reference in New Issue
Block a user