summaryrefslogtreecommitdiff
path: root/web/test/tab-badges.test.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'web/test/tab-badges.test.mjs')
-rw-r--r--web/test/tab-badges.test.mjs110
1 files changed, 110 insertions, 0 deletions
diff --git a/web/test/tab-badges.test.mjs b/web/test/tab-badges.test.mjs
new file mode 100644
index 0000000..c07338f
--- /dev/null
+++ b/web/test/tab-badges.test.mjs
@@ -0,0 +1,110 @@
+// tab-badges.test.mjs — TDD tests for computeTabBadgeCounts
+//
+// Tests the pure function that computes badge counts for the
+// 'interrupted', 'ready', and 'running' tabs.
+//
+// Run with: node --test web/test/tab-badges.test.mjs
+
+import { describe, it } from 'node:test';
+import assert from 'node:assert/strict';
+
+// ── Inline implementation (will be replaced by import once exported) ───────────
+
+const INTERRUPTED_STATES = new Set(['CANCELLED', 'FAILED', 'BUDGET_EXCEEDED', 'BLOCKED']);
+
+function computeTabBadgeCounts(tasks) {
+ let interrupted = 0;
+ let ready = 0;
+ let running = 0;
+ for (const t of tasks) {
+ if (INTERRUPTED_STATES.has(t.state)) interrupted++;
+ if (t.state === 'READY') ready++;
+ if (t.state === 'RUNNING') running++;
+ }
+ return { interrupted, ready, running };
+}
+
+// ── Helpers ────────────────────────────────────────────────────────────────────
+
+function makeTask(state) {
+ return { id: state, name: `task-${state}`, state };
+}
+
+// ── Tests ──────────────────────────────────────────────────────────────────────
+
+describe('computeTabBadgeCounts', () => {
+ it('returns all zeros for empty task list', () => {
+ assert.deepEqual(computeTabBadgeCounts([]), { interrupted: 0, ready: 0, running: 0 });
+ });
+
+ it('counts RUNNING tasks', () => {
+ const tasks = [makeTask('RUNNING'), makeTask('RUNNING'), makeTask('QUEUED')];
+ const counts = computeTabBadgeCounts(tasks);
+ assert.equal(counts.running, 2);
+ assert.equal(counts.ready, 0);
+ assert.equal(counts.interrupted, 0);
+ });
+
+ it('counts READY tasks', () => {
+ const tasks = [makeTask('READY'), makeTask('READY'), makeTask('QUEUED')];
+ const counts = computeTabBadgeCounts(tasks);
+ assert.equal(counts.ready, 2);
+ assert.equal(counts.running, 0);
+ assert.equal(counts.interrupted, 0);
+ });
+
+ it('counts CANCELLED as interrupted', () => {
+ const counts = computeTabBadgeCounts([makeTask('CANCELLED')]);
+ assert.equal(counts.interrupted, 1);
+ });
+
+ it('counts FAILED as interrupted', () => {
+ const counts = computeTabBadgeCounts([makeTask('FAILED')]);
+ assert.equal(counts.interrupted, 1);
+ });
+
+ it('counts BUDGET_EXCEEDED as interrupted', () => {
+ const counts = computeTabBadgeCounts([makeTask('BUDGET_EXCEEDED')]);
+ assert.equal(counts.interrupted, 1);
+ });
+
+ it('counts BLOCKED as interrupted', () => {
+ const counts = computeTabBadgeCounts([makeTask('BLOCKED')]);
+ assert.equal(counts.interrupted, 1);
+ });
+
+ it('does not count COMPLETED as interrupted', () => {
+ const counts = computeTabBadgeCounts([makeTask('COMPLETED')]);
+ assert.equal(counts.interrupted, 0);
+ });
+
+ it('does not count TIMED_OUT as interrupted', () => {
+ const counts = computeTabBadgeCounts([makeTask('TIMED_OUT')]);
+ assert.equal(counts.interrupted, 0);
+ });
+
+ it('counts across multiple states simultaneously', () => {
+ const tasks = [
+ makeTask('RUNNING'),
+ makeTask('RUNNING'),
+ makeTask('READY'),
+ makeTask('CANCELLED'),
+ makeTask('FAILED'),
+ makeTask('BLOCKED'),
+ makeTask('QUEUED'),
+ makeTask('COMPLETED'),
+ ];
+ const counts = computeTabBadgeCounts(tasks);
+ assert.equal(counts.running, 2);
+ assert.equal(counts.ready, 1);
+ assert.equal(counts.interrupted, 3);
+ });
+
+ it('returns zero for a tab when no tasks match that state', () => {
+ const tasks = [makeTask('QUEUED'), makeTask('PENDING'), makeTask('COMPLETED')];
+ const counts = computeTabBadgeCounts(tasks);
+ assert.equal(counts.running, 0);
+ assert.equal(counts.ready, 0);
+ assert.equal(counts.interrupted, 0);
+ });
+});