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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
// task-card-di.test.mjs — Unit tests for the createTaskCard DI doc param,
// log-tail placeholder (PENDING/QUEUED), and elapsed timer (RUNNING).
//
// Run with: node --test web/test/task-card-di.test.mjs
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { createTaskCard } from '../app.js';
// ── Mock DOM ───────────────────────────────────────────────────────────────────
function makeEl(tag) {
return {
tag,
className: '',
textContent: '',
title: '',
hidden: false,
dataset: {},
children: [],
classList: { add() {} },
append(...nodes) {
for (const n of nodes) {
if (n != null && typeof n === 'object') this.children.push(n);
}
},
appendChild(child) {
if (child != null) this.children.push(child);
return child;
},
addEventListener() {},
setAttribute() {},
getAttribute() { return null; },
};
}
function makeDoc() {
return {
createElement(tag) { return makeEl(tag); },
};
}
// Depth-first search for first element with the given class name.
function findByClass(el, cls) {
for (const child of el.children || []) {
if (typeof child.className === 'string' &&
child.className.split(' ').includes(cls)) return child;
const found = findByClass(child, cls);
if (found) return found;
}
return null;
}
// ── Tests: DI doc param ────────────────────────────────────────────────────────
describe('createTaskCard doc DI param', () => {
it('accepts a custom doc and uses it for createElement', () => {
const created = [];
const doc = {
createElement(tag) {
const el = makeEl(tag);
created.push(tag);
return el;
},
};
createTaskCard({ id: 't1', state: 'QUEUED', name: 'Test Task' }, doc);
assert.ok(created.length > 0, 'doc.createElement should have been called');
assert.ok(created.includes('div'), 'should create at least one div');
assert.ok(created.includes('span'), 'should create at least one span');
});
it('returns a card element whose tag is div', () => {
const card = createTaskCard({ id: 't1', state: 'QUEUED', name: 'Test' }, makeDoc());
assert.equal(card.tag, 'div');
assert.equal(card.className, 'task-card');
assert.equal(card.dataset.taskId, 't1');
});
});
// ── Tests: log-tail placeholder (QUEUED) ──────────────────────────────────────
describe('log-tail placeholder for queue column', () => {
it('QUEUED task has a task-log-placeholder element', () => {
const card = createTaskCard({ id: 't1', state: 'QUEUED', name: 'Test' }, makeDoc());
const placeholder = findByClass(card, 'task-log-placeholder');
assert.ok(placeholder, 'QUEUED card should have a task-log-placeholder element');
});
it('QUEUED task log-tail placeholder text is "Waiting to start\u2026"', () => {
const card = createTaskCard({ id: 't1', state: 'QUEUED', name: 'Test' }, makeDoc());
const placeholder = findByClass(card, 'task-log-placeholder');
assert.equal(placeholder.textContent, 'Waiting to start\u2026');
});
it('QUEUED task has a task-log-tail wrapper containing the placeholder', () => {
const card = createTaskCard({ id: 't1', state: 'QUEUED', name: 'Test' }, makeDoc());
const logTail = findByClass(card, 'task-log-tail');
assert.ok(logTail, 'QUEUED card should have a task-log-tail wrapper');
const placeholder = findByClass(logTail, 'task-log-placeholder');
assert.ok(placeholder, 'task-log-tail should contain task-log-placeholder');
});
it('RUNNING task does not have a task-log-placeholder', () => {
const card = createTaskCard({ id: 't1', state: 'RUNNING', name: 'Test' }, makeDoc());
const placeholder = findByClass(card, 'task-log-placeholder');
assert.equal(placeholder, null, 'RUNNING card should not have a task-log-placeholder');
});
it('COMPLETED task does not have a task-log-placeholder', () => {
const card = createTaskCard({ id: 't1', state: 'COMPLETED', name: 'Test' }, makeDoc());
const placeholder = findByClass(card, 'task-log-placeholder');
assert.equal(placeholder, null, 'COMPLETED card should not have a task-log-placeholder');
});
});
// ── Tests: elapsed timer (RUNNING) ────────────────────────────────────────────
describe('elapsed timer for RUNNING tasks', () => {
it('RUNNING task has a running-elapsed span', () => {
const card = createTaskCard(
{ id: 't1', state: 'RUNNING', name: 'Test', updated_at: '2024-01-01T00:00:00Z' },
makeDoc(),
);
const elapsed = findByClass(card, 'running-elapsed');
assert.ok(elapsed, 'RUNNING card should have a running-elapsed element');
});
it('RUNNING task running-elapsed has data-started-at from task.updated_at', () => {
const card = createTaskCard(
{ id: 't1', state: 'RUNNING', name: 'Test', updated_at: '2024-06-15T12:00:00Z' },
makeDoc(),
);
const elapsed = findByClass(card, 'running-elapsed');
assert.equal(elapsed.dataset.startedAt, '2024-06-15T12:00:00Z');
});
it('RUNNING task running-elapsed data-started-at is empty string when updated_at is absent', () => {
const card = createTaskCard(
{ id: 't1', state: 'RUNNING', name: 'Test' },
makeDoc(),
);
const elapsed = findByClass(card, 'running-elapsed');
assert.equal(elapsed.dataset.startedAt, '');
});
it('running-elapsed is inside the card header', () => {
const card = createTaskCard(
{ id: 't1', state: 'RUNNING', name: 'Test', updated_at: '2024-01-01T00:00:00Z' },
makeDoc(),
);
// The header is the first child of the card (a div.task-card-header).
const header = findByClass(card, 'task-card-header');
assert.ok(header, 'card should have a task-card-header');
const elapsed = findByClass(header, 'running-elapsed');
assert.ok(elapsed, 'running-elapsed should be inside the task-card-header');
});
it('QUEUED task does not have a running-elapsed span', () => {
const card = createTaskCard({ id: 't1', state: 'QUEUED', name: 'Test' }, makeDoc());
const elapsed = findByClass(card, 'running-elapsed');
assert.equal(elapsed, null, 'QUEUED card should not have a running-elapsed element');
});
it('COMPLETED task does not have a running-elapsed span', () => {
const card = createTaskCard({ id: 't1', state: 'COMPLETED', name: 'Test' }, makeDoc());
const elapsed = findByClass(card, 'running-elapsed');
assert.equal(elapsed, null, 'COMPLETED card should not have a running-elapsed element');
});
});
|