summaryrefslogtreecommitdiff
path: root/web/test/tasks-board.test.mjs
blob: 85bb04a48c12e205805264b69a2919e149d8612b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// tasks-board.test.mjs — Unit tests for the Tasks tab's pure column logic:
// column-mapping completeness, fallback-state behaviour, and per-column
// grouping/sorting correctness.
//
// Run with: node --test web/test/tasks-board.test.mjs

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
  TASK_COLUMNS,
  columnForTaskState,
  groupTasksByColumn,
} from '../app.js';

describe('columnForTaskState', () => {
  it('maps every documented task state to a column', () => {
    assert.equal(columnForTaskState('PENDING'), 'queue');
    assert.equal(columnForTaskState('QUEUED'), 'queue');
    assert.equal(columnForTaskState('RUNNING'), 'running');
    assert.equal(columnForTaskState('BLOCKED'), 'running');
    assert.equal(columnForTaskState('READY'), 'ready');
    assert.equal(columnForTaskState('FAILED'), 'interrupted');
    assert.equal(columnForTaskState('TIMED_OUT'), 'interrupted');
    assert.equal(columnForTaskState('CANCELLED'), 'interrupted');
    assert.equal(columnForTaskState('BUDGET_EXCEEDED'), 'interrupted');
    assert.equal(columnForTaskState('COMPLETED'), 'done');
  });

  it('falls back to queue for an unrecognized/empty state', () => {
    assert.equal(columnForTaskState(''), 'queue');
    assert.equal(columnForTaskState(undefined), 'queue');
    assert.equal(columnForTaskState('SOME_FUTURE_STATE'), 'queue');
  });

  it('every column key is unique and covers all 10 task states', () => {
    const keys = TASK_COLUMNS.map(c => c.key);
    assert.equal(new Set(keys).size, keys.length, 'column keys must be unique');
    const allStates = TASK_COLUMNS.flatMap(c => c.states);
    for (const s of [
      'PENDING', 'QUEUED', 'RUNNING', 'BLOCKED', 'READY',
      'FAILED', 'TIMED_OUT', 'CANCELLED', 'BUDGET_EXCEEDED', 'COMPLETED',
    ]) {
      assert.ok(allStates.includes(s), `${s} missing from any column`);
    }
  });
});

describe('groupTasksByColumn', () => {
  it('returns an object with a key per column, all initially empty arrays', () => {
    const groups = groupTasksByColumn([]);
    for (const col of TASK_COLUMNS) {
      assert.ok(Array.isArray(groups[col.key]), `groups.${col.key} should be an array`);
      assert.equal(groups[col.key].length, 0);
    }
  });

  it('places tasks in the correct column', () => {
    const tasks = [
      { id: '1', state: 'PENDING',   created_at: '2024-01-01T00:00:00Z' },
      { id: '2', state: 'RUNNING',   created_at: '2024-01-02T00:00:00Z' },
      { id: '3', state: 'READY',     created_at: '2024-01-03T00:00:00Z' },
      { id: '4', state: 'FAILED',    created_at: '2024-01-04T00:00:00Z' },
      { id: '5', state: 'COMPLETED', created_at: '2024-01-05T00:00:00Z' },
    ];
    const groups = groupTasksByColumn(tasks);
    assert.equal(groups.queue.length, 1);
    assert.equal(groups.queue[0].id, '1');
    assert.equal(groups.running.length, 1);
    assert.equal(groups.running[0].id, '2');
    assert.equal(groups.ready.length, 1);
    assert.equal(groups.ready[0].id, '3');
    assert.equal(groups.interrupted.length, 1);
    assert.equal(groups.interrupted[0].id, '4');
    assert.equal(groups.done.length, 1);
    assert.equal(groups.done[0].id, '5');
  });

  it('sorts queue oldest-first (ascending created_at)', () => {
    const tasks = [
      { id: 'newer', state: 'QUEUED',  created_at: '2024-02-01T00:00:00Z' },
      { id: 'older', state: 'PENDING', created_at: '2024-01-01T00:00:00Z' },
    ];
    const groups = groupTasksByColumn(tasks);
    assert.deepEqual(groups.queue.map(t => t.id), ['older', 'newer']);
  });

  it('sorts ready oldest-first (ascending created_at)', () => {
    const tasks = [
      { id: 'newer', state: 'READY', created_at: '2024-02-01T00:00:00Z' },
      { id: 'older', state: 'READY', created_at: '2024-01-01T00:00:00Z' },
    ];
    const groups = groupTasksByColumn(tasks);
    assert.deepEqual(groups.ready.map(t => t.id), ['older', 'newer']);
  });

  it('sorts interrupted newest-first (descending created_at)', () => {
    const tasks = [
      { id: 'older', state: 'FAILED',    created_at: '2024-01-01T00:00:00Z' },
      { id: 'newer', state: 'CANCELLED', created_at: '2024-02-01T00:00:00Z' },
    ];
    const groups = groupTasksByColumn(tasks);
    assert.deepEqual(groups.interrupted.map(t => t.id), ['newer', 'older']);
  });

  it('sorts done newest-first (descending created_at)', () => {
    const tasks = [
      { id: 'older', state: 'COMPLETED', created_at: '2024-01-01T00:00:00Z' },
      { id: 'newer', state: 'COMPLETED', created_at: '2024-02-01T00:00:00Z' },
    ];
    const groups = groupTasksByColumn(tasks);
    assert.deepEqual(groups.done.map(t => t.id), ['newer', 'older']);
  });

  it('sorts running newest-first (descending created_at)', () => {
    const tasks = [
      { id: 'older', state: 'RUNNING',  created_at: '2024-01-01T00:00:00Z' },
      { id: 'newer', state: 'BLOCKED',  created_at: '2024-02-01T00:00:00Z' },
    ];
    const groups = groupTasksByColumn(tasks);
    assert.deepEqual(groups.running.map(t => t.id), ['newer', 'older']);
  });

  it('handles null/undefined gracefully', () => {
    const groups = groupTasksByColumn(null);
    for (const col of TASK_COLUMNS) {
      assert.equal(groups[col.key].length, 0);
    }
  });
});