From fd42a54d96fcd3342941caaeb61a4b0d5d3f1b4f Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Fri, 6 Mar 2026 23:55:07 +0000 Subject: recover: restore untracked work from recovery branch (no Gemini changes) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Recovered files with no Claude→Agent contamination: - docs/adr/002-task-state-machine.md - internal/api/logs.go/logs_test.go: task-level log streaming endpoint - internal/api/validate.go/validate_test.go: POST /api/tasks/validate - internal/api/server_test.go, storage/db_test.go: expanded test coverage - scripts/reset-failed-tasks, reset-running-tasks - web/app.js, index.html, style.css: frontend improvements - web/test/: active-tasks-tab, delete-button, filter-tabs, sort-tasks tests Manually applied from server.go diff (skipping Claude→Agent rename): - taskLogStore field + validateCmdPath field - DELETE /api/tasks/{id} route + handleDeleteTask - GET /api/tasks/{id}/logs/stream route - POST /api/tasks/{id}/resume route + handleResumeTimedOutTask - handleCancelTask: allow cancelling PENDING/QUEUED tasks directly Co-Authored-By: Claude Sonnet 4.6 --- web/test/sort-tasks.test.mjs | 88 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 web/test/sort-tasks.test.mjs (limited to 'web/test/sort-tasks.test.mjs') diff --git a/web/test/sort-tasks.test.mjs b/web/test/sort-tasks.test.mjs new file mode 100644 index 0000000..fe47702 --- /dev/null +++ b/web/test/sort-tasks.test.mjs @@ -0,0 +1,88 @@ +// sort-tasks.test.mjs — TDD contract tests for sortTasksByDate +// +// sortTasksByDate is defined inline here to establish expected behaviour. +// Once sortTasksByDate is exported from web/app.js or a shared module, +// remove the inline definition and import it instead. +// +// Run with: node --test web/test/sort-tasks.test.mjs + +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +// ── Implementation under contract ───────────────────────────────────────────── +// Remove this block once sortTasksByDate is available from app.js. + +function sortTasksByDate(tasks) { + return [...tasks].sort((a, b) => { + if (!a.created_at && !b.created_at) return 0; + if (!a.created_at) return 1; + if (!b.created_at) return -1; + return new Date(a.created_at) - new Date(b.created_at); + }); +} + +// ── Helpers ──────────────────────────────────────────────────────────────────── + +function makeTask(id, created_at, state = 'PENDING') { + return { id, name: `task-${id}`, state, created_at }; +} + +// ── Tests ────────────────────────────────────────────────────────────────────── + +describe('sortTasksByDate', () => { + it('sorts tasks oldest-first by created_at', () => { + const tasks = [ + makeTask('c', '2026-03-06T12:00:00Z'), + makeTask('a', '2026-03-04T08:00:00Z'), + makeTask('b', '2026-03-05T10:00:00Z'), + ]; + const result = sortTasksByDate(tasks); + assert.equal(result[0].id, 'a', 'oldest should be first'); + assert.equal(result[1].id, 'b'); + assert.equal(result[2].id, 'c', 'newest should be last'); + }); + + it('returns a new array (does not mutate input)', () => { + const tasks = [ + makeTask('b', '2026-03-05T10:00:00Z'), + makeTask('a', '2026-03-04T08:00:00Z'), + ]; + const original = [...tasks]; + const result = sortTasksByDate(tasks); + assert.notStrictEqual(result, tasks, 'should return a new array'); + assert.deepEqual(tasks, original, 'input should not be mutated'); + }); + + it('returns an empty array when given an empty array', () => { + assert.deepEqual(sortTasksByDate([]), []); + }); + + it('returns a single-element array unchanged', () => { + const tasks = [makeTask('x', '2026-03-01T00:00:00Z')]; + const result = sortTasksByDate(tasks); + assert.equal(result.length, 1); + assert.equal(result[0].id, 'x'); + }); + + it('places tasks with null created_at after tasks with a date', () => { + const tasks = [ + makeTask('no-date', null), + makeTask('has-date', '2026-03-01T00:00:00Z'), + ]; + const result = sortTasksByDate(tasks); + assert.equal(result[0].id, 'has-date', 'task with date should come first'); + assert.equal(result[1].id, 'no-date', 'task without date should come last'); + }); + + it('works with mixed states (not just PENDING)', () => { + const tasks = [ + makeTask('r', '2026-03-06T00:00:00Z', 'RUNNING'), + makeTask('p', '2026-03-04T00:00:00Z', 'PENDING'), + makeTask('q', '2026-03-05T00:00:00Z', 'QUEUED'), + ]; + const result = sortTasksByDate(tasks); + assert.equal(result[0].id, 'p'); + assert.equal(result[1].id, 'q'); + assert.equal(result[2].id, 'r'); + }); +}); -- cgit v1.2.3