From f9d2056f4d10fe7dde71f5d17d8e8c473b3a611f Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Mon, 9 Mar 2026 01:12:48 +0000 Subject: web: sort Done/Interrupted tabs newest-first and filter Done tab by 24h Updated sortTasksByDate to support descending sort. Changed renderTaskList to use descending sort for Done and Interrupted tabs. Updated filterTasksByTab to hide Done tasks older than 24 hours by default. Updated frontend tests to match new behavior. --- web/test/filter-tabs.test.mjs | 67 +++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 28 deletions(-) (limited to 'web/test/filter-tabs.test.mjs') diff --git a/web/test/filter-tabs.test.mjs b/web/test/filter-tabs.test.mjs index 3a4e569..6819863 100644 --- a/web/test/filter-tabs.test.mjs +++ b/web/test/filter-tabs.test.mjs @@ -8,8 +8,8 @@ import { filterTasksByTab } from '../app.js'; // ── Helpers ──────────────────────────────────────────────────────────────────── -function makeTask(state) { - return { id: state, name: `task-${state}`, state }; +function makeTask(state, created_at = null) { + return { id: state, name: `task-${state}`, state, created_at }; } const ALL_STATES = [ @@ -20,18 +20,18 @@ const ALL_STATES = [ // ── Tests ────────────────────────────────────────────────────────────────────── describe('filterTasksByTab — active tab', () => { - it('includes PENDING, QUEUED, RUNNING, READY, BLOCKED', () => { - const tasks = ALL_STATES.map(makeTask); + it('includes PENDING, QUEUED, RUNNING, READY', () => { + const tasks = ALL_STATES.map(s => makeTask(s)); const result = filterTasksByTab(tasks, 'active'); - for (const state of ['PENDING', 'QUEUED', 'RUNNING', 'READY', 'BLOCKED']) { + for (const state of ['PENDING', 'QUEUED', 'RUNNING', 'READY']) { assert.ok(result.some(t => t.state === state), `${state} should be included`); } }); - it('excludes COMPLETED, FAILED, TIMED_OUT, CANCELLED, BUDGET_EXCEEDED', () => { - const tasks = ALL_STATES.map(makeTask); + it('excludes BLOCKED, COMPLETED, FAILED, TIMED_OUT, CANCELLED, BUDGET_EXCEEDED', () => { + const tasks = ALL_STATES.map(s => makeTask(s)); const result = filterTasksByTab(tasks, 'active'); - for (const state of ['COMPLETED', 'FAILED', 'TIMED_OUT', 'CANCELLED', 'BUDGET_EXCEEDED']) { + for (const state of ['BLOCKED', 'COMPLETED', 'FAILED', 'TIMED_OUT', 'CANCELLED', 'BUDGET_EXCEEDED']) { assert.ok(!result.some(t => t.state === state), `${state} should be excluded`); } }); @@ -42,18 +42,18 @@ describe('filterTasksByTab — active tab', () => { }); describe('filterTasksByTab — interrupted tab', () => { - it('includes CANCELLED and FAILED', () => { - const tasks = ALL_STATES.map(makeTask); + it('includes CANCELLED, FAILED, BUDGET_EXCEEDED, BLOCKED', () => { + const tasks = ALL_STATES.map(s => makeTask(s)); const result = filterTasksByTab(tasks, 'interrupted'); - for (const state of ['CANCELLED', 'FAILED']) { + for (const state of ['CANCELLED', 'FAILED', 'BUDGET_EXCEEDED', 'BLOCKED']) { assert.ok(result.some(t => t.state === state), `${state} should be included`); } }); it('excludes all non-interrupted states', () => { - const tasks = ALL_STATES.map(makeTask); + const tasks = ALL_STATES.map(s => makeTask(s)); const result = filterTasksByTab(tasks, 'interrupted'); - for (const state of ['PENDING', 'QUEUED', 'RUNNING', 'READY', 'BLOCKED', 'COMPLETED', 'TIMED_OUT', 'BUDGET_EXCEEDED']) { + for (const state of ['PENDING', 'QUEUED', 'RUNNING', 'READY', 'COMPLETED', 'TIMED_OUT']) { assert.ok(!result.some(t => t.state === state), `${state} should be excluded`); } }); @@ -64,26 +64,37 @@ describe('filterTasksByTab — interrupted tab', () => { }); describe('filterTasksByTab — done tab', () => { - it('includes COMPLETED, TIMED_OUT, BUDGET_EXCEEDED', () => { - const tasks = ALL_STATES.map(makeTask); + it('includes COMPLETED, TIMED_OUT (if recent)', () => { + const now = new Date().toISOString(); + const tasks = [ + makeTask('COMPLETED', now), + makeTask('TIMED_OUT', now), + ]; const result = filterTasksByTab(tasks, 'done'); - for (const state of ['COMPLETED', 'TIMED_OUT', 'BUDGET_EXCEEDED']) { - assert.ok(result.some(t => t.state === state), `${state} should be included`); - } + assert.equal(result.length, 2); }); - it('excludes CANCELLED and FAILED (moved to interrupted tab)', () => { - const tasks = ALL_STATES.map(makeTask); + it('excludes COMPLETED, TIMED_OUT if older than 24h', () => { + const longAgo = new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString(); + const tasks = [ + makeTask('COMPLETED', longAgo), + makeTask('TIMED_OUT', longAgo), + ]; const result = filterTasksByTab(tasks, 'done'); - for (const state of ['CANCELLED', 'FAILED']) { - assert.ok(!result.some(t => t.state === state), `${state} should be excluded from done`); - } + assert.equal(result.length, 0, 'should hide tasks older than 24h'); + }); + + it('includes tasks with null created_at by default (defensive)', () => { + const tasks = [makeTask('COMPLETED', null)]; + const result = filterTasksByTab(tasks, 'done'); + assert.equal(result.length, 1); }); - it('excludes PENDING, QUEUED, RUNNING, READY, BLOCKED', () => { - const tasks = ALL_STATES.map(makeTask); + it('excludes PENDING, QUEUED, RUNNING, READY, BLOCKED, CANCELLED, FAILED, BUDGET_EXCEEDED', () => { + const now = new Date().toISOString(); + const tasks = ALL_STATES.map(s => makeTask(s, now)); const result = filterTasksByTab(tasks, 'done'); - for (const state of ['PENDING', 'QUEUED', 'RUNNING', 'READY', 'BLOCKED']) { + for (const state of ['PENDING', 'QUEUED', 'RUNNING', 'READY', 'BLOCKED', 'CANCELLED', 'FAILED', 'BUDGET_EXCEEDED']) { assert.ok(!result.some(t => t.state === state), `${state} should be excluded`); } }); @@ -95,7 +106,7 @@ describe('filterTasksByTab — done tab', () => { describe('filterTasksByTab — all tab', () => { it('returns all tasks unchanged', () => { - const tasks = ALL_STATES.map(makeTask); + const tasks = ALL_STATES.map(s => makeTask(s)); const result = filterTasksByTab(tasks, 'all'); assert.equal(result.length, ALL_STATES.length); assert.strictEqual(result, tasks, 'should return the same array reference'); @@ -108,7 +119,7 @@ describe('filterTasksByTab — all tab', () => { describe('filterTasksByTab — unknown tab', () => { it('returns all tasks as defensive fallback', () => { - const tasks = ALL_STATES.map(makeTask); + const tasks = ALL_STATES.map(s => makeTask(s)); const result = filterTasksByTab(tasks, 'unknown-tab'); assert.equal(result.length, ALL_STATES.length); assert.strictEqual(result, tasks, 'should return the same array reference'); -- cgit v1.2.3