summaryrefslogtreecommitdiff
path: root/web/test
diff options
context:
space:
mode:
Diffstat (limited to 'web/test')
-rw-r--r--web/test/task-card-di.test.mjs169
1 files changed, 169 insertions, 0 deletions
diff --git a/web/test/task-card-di.test.mjs b/web/test/task-card-di.test.mjs
new file mode 100644
index 0000000..e5dff74
--- /dev/null
+++ b/web/test/task-card-di.test.mjs
@@ -0,0 +1,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');
+ });
+});