1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
// event-timeline.test.mjs — Unit tests for the event timeline component.
//
// Run with: node --test web/test/event-timeline.test.mjs
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { formatEventText, renderEventTimeline, fetchTaskEvents } from '../app.js';
function makeDoc() {
return {
createElement(tag) {
return {
tag,
className: '',
textContent: '',
children: [],
appendChild(child) { this.children.push(child); return child; },
};
},
};
}
describe('formatEventText', () => {
it('formats state_change with from/to', () => {
assert.equal(
formatEventText({ kind: 'state_change', payload: { from: 'QUEUED', to: 'RUNNING' } }),
'State: QUEUED → RUNNING',
);
});
it('formats summary text', () => {
assert.equal(
formatEventText({ kind: 'summary', payload: { text: 'did the thing' } }),
'Summary: did the thing',
);
});
it('formats clarification_request and answer', () => {
assert.equal(
formatEventText({ kind: 'clarification_request', payload: { text: 'which branch?' } }),
'Question: which branch?',
);
assert.equal(
formatEventText({ kind: 'clarification_answer', payload: { answer: 'main' } }),
'Answer: main',
);
});
it('formats subtask_spawned by name', () => {
assert.equal(
formatEventText({ kind: 'subtask_spawned', payload: { name: 'write tests', subtask_id: 'x' } }),
'Spawned subtask: write tests',
);
});
it('falls back to kind for unknown events', () => {
assert.equal(formatEventText({ kind: 'mystery', payload: {} }), 'mystery');
});
it('handles null/empty defensively', () => {
assert.equal(formatEventText(null), '');
assert.equal(formatEventText({ kind: 'agent_message', payload: { message: 'hi' } }), 'hi');
});
});
describe('renderEventTimeline', () => {
it('renders one item per event with actor and text', () => {
const events = [
{ kind: 'state_change', actor: 'system', payload: { from: 'PENDING', to: 'QUEUED' } },
{ kind: 'summary', actor: 'agent', payload: { text: 'done' } },
];
const ul = renderEventTimeline(events, makeDoc());
assert.equal(ul.className, 'event-timeline');
assert.equal(ul.children.length, 2);
// each item has [actor, text]
assert.equal(ul.children[0].children[0].textContent, 'system');
assert.equal(ul.children[0].children[1].textContent, 'State: PENDING → QUEUED');
assert.equal(ul.children[1].children[1].textContent, 'Summary: done');
});
it('renders an empty-state item for no events', () => {
const ul = renderEventTimeline([], makeDoc());
assert.equal(ul.children.length, 1);
assert.equal(ul.children[0].className, 'event-timeline__empty');
});
it('returns null when doc is null', () => {
assert.equal(renderEventTimeline([], null), null);
});
});
describe('fetchTaskEvents', () => {
it('returns parsed array on success', async () => {
const stub = async () => ({ ok: true, json: async () => [{ kind: 'summary', payload: {} }] });
const events = await fetchTaskEvents('t1', stub);
assert.equal(events.length, 1);
});
it('returns [] on non-ok response', async () => {
const stub = async () => ({ ok: false, json: async () => ({}) });
assert.deepEqual(await fetchTaskEvents('t1', stub), []);
});
it('returns [] when fetch throws', async () => {
const stub = async () => { throw new Error('network'); };
assert.deepEqual(await fetchTaskEvents('t1', stub), []);
});
it('returns [] when no fetch implementation is available', async () => {
assert.deepEqual(await fetchTaskEvents('t1', null), []);
});
});
|