summaryrefslogtreecommitdiff
path: root/web/test/subtask-placeholder.test.mjs
blob: b27980444744c6bd8390b16eba2001c51b6b01f3 (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
// subtask-placeholder.test.mjs — placeholder text for empty subtask rollup
//
// Run with: node --test web/test/subtask-placeholder.test.mjs

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';

// ── Logic under test ──────────────────────────────────────────────────────────
//
// When a task is BLOCKED/READY and the subtask list is empty, the rollup shows
// meaningful content instead of a generic placeholder:
//   1. task.elaboration_input (raw user prompt) if present
//   2. task.description truncated to ~120 chars (word boundary)
//   3. fallback to task.name if no description
//   4. fallback to 'Waiting for subtasks…' if neither

function truncateToWordBoundary(text, maxLen = 120) {
  if (!text || text.length <= maxLen) return text;
  const cut = text.lastIndexOf(' ', maxLen);
  return (cut > 0 ? text.slice(0, cut) : text.slice(0, maxLen)) + '…';
}

function getSubtaskPlaceholder(task) {
  const blurb = task.elaboration_input || task.description || task.name;
  return blurb ? truncateToWordBoundary(blurb) : 'Waiting for subtasks…';
}

// ── Tests ─────────────────────────────────────────────────────────────────────

describe('truncateToWordBoundary', () => {
  it('returns text unchanged when shorter than maxLen', () => {
    assert.equal(truncateToWordBoundary('hello world', 120), 'hello world');
  });

  it('returns text unchanged when exactly maxLen', () => {
    const text = 'a'.repeat(120);
    assert.equal(truncateToWordBoundary(text, 120), text);
  });

  it('truncates at word boundary and appends ellipsis', () => {
    const text = 'Fix the login bug that causes users to be logged out unexpectedly when they navigate between pages in the application user interface';
    const result = truncateToWordBoundary(text, 120);
    assert.ok(result.length <= 121, `result too long: ${result.length}`);
    assert.ok(result.endsWith('…'), 'should end with ellipsis');
    assert.ok(result.startsWith('Fix the'), 'should keep beginning of text');
  });

  it('truncates at character boundary when no word boundary exists', () => {
    const text = 'a'.repeat(200);
    const result = truncateToWordBoundary(text, 120);
    assert.equal(result, 'a'.repeat(120) + '…');
  });

  it('returns empty string unchanged', () => {
    assert.equal(truncateToWordBoundary(''), '');
  });

  it('returns null unchanged', () => {
    assert.equal(truncateToWordBoundary(null), null);
  });
});

describe('getSubtaskPlaceholder', () => {
  it('prefers task.elaboration_input over description and name', () => {
    const task = { elaboration_input: 'fix the login bug', description: 'Fix authentication issue', name: 'auth-fix' };
    assert.equal(getSubtaskPlaceholder(task), 'fix the login bug');
  });

  it('truncates task.elaboration_input at 120 chars', () => {
    const longInput = 'Please fix the login bug that causes users to be logged out unexpectedly when they navigate between pages in the application user interface';
    const task = { elaboration_input: longInput, name: 'auth-fix' };
    const result = getSubtaskPlaceholder(task);
    assert.ok(result.endsWith('…'), 'should end with ellipsis');
    assert.ok(result.length <= 122, `result too long: ${result.length}`);
  });

  it('uses task.description when elaboration_input is absent', () => {
    const task = { description: 'Fix auth bug', name: 'auth-fix' };
    assert.equal(getSubtaskPlaceholder(task), 'Fix auth bug');
  });

  it('truncates task.description at 120 chars', () => {
    const longDesc = 'This is a very long description that exceeds the limit. ' +
      'It goes on and on describing what the task is about in great detail. ' +
      'Eventually it reaches the maximum allowed length.';
    const task = { description: longDesc, name: 'long-task' };
    const result = getSubtaskPlaceholder(task);
    assert.ok(result.endsWith('…'), 'should end with ellipsis');
    assert.ok(result.length <= 122, `result too long: ${result.length}`);
  });

  it('falls back to task.name when description is absent', () => {
    const task = { name: 'deploy-frontend' };
    assert.equal(getSubtaskPlaceholder(task), 'deploy-frontend');
  });

  it('falls back to task.name when description is empty string', () => {
    const task = { description: '', name: 'deploy-frontend' };
    assert.equal(getSubtaskPlaceholder(task), 'deploy-frontend');
  });

  it('falls back to generic text when both description and name are absent', () => {
    const task = {};
    assert.equal(getSubtaskPlaceholder(task), 'Waiting for subtasks…');
  });
});