summaryrefslogtreecommitdiff
path: root/web/test
diff options
context:
space:
mode:
authorClaudomator Agent <agent@claudomator>2026-03-13 09:31:31 +0000
committerClaudomator Agent <agent@claudomator>2026-03-13 09:31:31 +0000
commit03f8b0e8b1aef2429f825b300c427147c30d4b0b (patch)
tree7c7f689543178d0ae2d0e5b477df1caafb1c0291 /web/test
parentc602ddd799d94bf3bbd35a57b98ad09e28df8ee9 (diff)
feat: reorganize web UI to 7-tab layout (Queue, Interrupted, Ready, Running, All, Stats, Settings)
- Replace Tasks/Active tabs with Queue (QUEUED+PENDING), Interrupted, Ready top-level tabs - Add All tab (COMPLETED, TIMED_OUT, BUDGET_EXCEEDED within last 24h) and Settings placeholder - Export filterQueueTasks, filterReadyTasks, filterAllDoneTasks from app.js - Refactor poll() to dispatch to active tab's render function instead of always rendering all panels - Add renderQueuePanel, renderInterruptedPanel, renderReadyPanel, renderAllPanel helpers - Add tests in web/test/tab-filters.test.mjs covering all new filter functions (16 tests) - All 165 JS tests and all Go tests pass Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'web/test')
-rw-r--r--web/test/tab-filters.test.mjs126
1 files changed, 126 insertions, 0 deletions
diff --git a/web/test/tab-filters.test.mjs b/web/test/tab-filters.test.mjs
new file mode 100644
index 0000000..f173521
--- /dev/null
+++ b/web/test/tab-filters.test.mjs
@@ -0,0 +1,126 @@
+// tab-filters.test.mjs — TDD tests for new tab filter functions
+//
+// Run with: node --test web/test/tab-filters.test.mjs
+
+import { describe, it } from 'node:test';
+import assert from 'node:assert/strict';
+import { filterQueueTasks, filterReadyTasks, filterAllDoneTasks } from '../app.js';
+
+// ── Helpers ────────────────────────────────────────────────────────────────────
+
+function makeTask(state, created_at = null) {
+ return { id: state, name: `task-${state}`, state, created_at };
+}
+
+const ALL_STATES = [
+ 'PENDING', 'QUEUED', 'RUNNING', 'READY', 'BLOCKED',
+ 'COMPLETED', 'FAILED', 'TIMED_OUT', 'CANCELLED', 'BUDGET_EXCEEDED',
+];
+
+// ── filterQueueTasks ──────────────────────────────────────────────────────────
+
+describe('filterQueueTasks', () => {
+ it('includes QUEUED tasks', () => {
+ const tasks = ALL_STATES.map(s => makeTask(s));
+ const result = filterQueueTasks(tasks);
+ assert.ok(result.some(t => t.state === 'QUEUED'), 'QUEUED should be included');
+ });
+
+ it('includes PENDING tasks', () => {
+ const tasks = ALL_STATES.map(s => makeTask(s));
+ const result = filterQueueTasks(tasks);
+ assert.ok(result.some(t => t.state === 'PENDING'), 'PENDING should be included');
+ });
+
+ it('excludes all other states', () => {
+ const tasks = ALL_STATES.map(s => makeTask(s));
+ const result = filterQueueTasks(tasks);
+ for (const state of ['RUNNING', 'READY', 'BLOCKED', 'COMPLETED', 'FAILED', 'TIMED_OUT', 'CANCELLED', 'BUDGET_EXCEEDED']) {
+ assert.ok(!result.some(t => t.state === state), `${state} should be excluded`);
+ }
+ });
+
+ it('returns only QUEUED and PENDING (length 2 from all states)', () => {
+ const tasks = ALL_STATES.map(s => makeTask(s));
+ const result = filterQueueTasks(tasks);
+ assert.equal(result.length, 2);
+ });
+
+ it('returns empty array for empty input', () => {
+ assert.deepEqual(filterQueueTasks([]), []);
+ });
+});
+
+// ── filterReadyTasks ──────────────────────────────────────────────────────────
+
+describe('filterReadyTasks', () => {
+ it('includes READY tasks', () => {
+ const tasks = ALL_STATES.map(s => makeTask(s));
+ const result = filterReadyTasks(tasks);
+ assert.ok(result.some(t => t.state === 'READY'), 'READY should be included');
+ });
+
+ it('excludes all non-READY states', () => {
+ const tasks = ALL_STATES.map(s => makeTask(s));
+ const result = filterReadyTasks(tasks);
+ for (const state of ['PENDING', 'QUEUED', 'RUNNING', 'BLOCKED', 'COMPLETED', 'FAILED', 'TIMED_OUT', 'CANCELLED', 'BUDGET_EXCEEDED']) {
+ assert.ok(!result.some(t => t.state === state), `${state} should be excluded`);
+ }
+ });
+
+ it('returns only READY tasks (length 1 from all states)', () => {
+ const tasks = ALL_STATES.map(s => makeTask(s));
+ const result = filterReadyTasks(tasks);
+ assert.equal(result.length, 1);
+ });
+
+ it('returns empty array for empty input', () => {
+ assert.deepEqual(filterReadyTasks([]), []);
+ });
+});
+
+// ── filterAllDoneTasks ────────────────────────────────────────────────────────
+
+describe('filterAllDoneTasks', () => {
+ it('includes COMPLETED tasks within 24h', () => {
+ const now = new Date().toISOString();
+ const result = filterAllDoneTasks([makeTask('COMPLETED', now)]);
+ assert.equal(result.length, 1);
+ });
+
+ it('includes TIMED_OUT tasks within 24h', () => {
+ const now = new Date().toISOString();
+ const result = filterAllDoneTasks([makeTask('TIMED_OUT', now)]);
+ assert.equal(result.length, 1);
+ });
+
+ it('includes BUDGET_EXCEEDED tasks within 24h', () => {
+ const now = new Date().toISOString();
+ const result = filterAllDoneTasks([makeTask('BUDGET_EXCEEDED', now)]);
+ assert.equal(result.length, 1);
+ });
+
+ it('excludes non-done states', () => {
+ const now = new Date().toISOString();
+ const tasks = ALL_STATES.map(s => makeTask(s, now));
+ const result = filterAllDoneTasks(tasks);
+ for (const state of ['PENDING', 'QUEUED', 'RUNNING', 'READY', 'BLOCKED', 'FAILED', 'CANCELLED']) {
+ assert.ok(!result.some(t => t.state === state), `${state} should be excluded`);
+ }
+ });
+
+ it('excludes COMPLETED tasks older than 24h by default', () => {
+ const longAgo = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString();
+ const result = filterAllDoneTasks([makeTask('COMPLETED', longAgo)]);
+ assert.equal(result.length, 0, 'should hide tasks older than 24h');
+ });
+
+ it('includes tasks with null created_at by default (defensive)', () => {
+ const result = filterAllDoneTasks([makeTask('COMPLETED', null)]);
+ assert.equal(result.length, 1);
+ });
+
+ it('returns empty array for empty input', () => {
+ assert.deepEqual(filterAllDoneTasks([]), []);
+ });
+});