summaryrefslogtreecommitdiff
path: root/web/test/task-panel-summary.test.mjs
blob: 63dd4835c4f6d9c530b2b2795996ef421585d90a (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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// task-panel-summary.test.mjs — verifies task summary renders exactly once in panel.
//
// Run with: node --test web/test/task-panel-summary.test.mjs

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

// ── Minimal DOM mock ──────────────────────────────────────────────────────────

function makeMockDOM() {
  const elements = {};

  function createElement(tag) {
    const el = {
      tag,
      className: '',
      textContent: '',
      innerHTML: '',
      hidden: false,
      children: [],
      _listeners: {},
      appendChild(child) { this.children.push(child); return child; },
      prepend(...nodes) { this.children.unshift(...nodes); },
      append(...nodes) { nodes.forEach(n => this.children.push(n)); },
      querySelector(sel) {
        const cls = sel.startsWith('.') ? sel.slice(1) : null;
        function search(el) {
          if (cls && el.className && el.className.split(' ').includes(cls)) return el;
          for (const c of el.children || []) {
            const found = search(c);
            if (found) return found;
          }
          return null;
        }
        return search(this);
      },
      querySelectorAll(sel) {
        const cls = sel.startsWith('.') ? sel.slice(1) : null;
        const results = [];
        function search(el) {
          if (cls && el.className && el.className.split(' ').includes(cls)) results.push(el);
          for (const c of el.children || []) search(c);
        }
        search(this);
        return results;
      },
      addEventListener(ev, fn) {},
    };
    return el;
  }

  // Named panel elements referenced by getElementById
  const panelTitle = createElement('h2');
  const panelContent = createElement('div');
  elements['task-panel-title'] = panelTitle;
  elements['task-panel-content'] = panelContent;

  const doc = {
    createElement,
    getElementById(id) { return elements[id] || null; },
  };

  return { doc, panelContent };
}

// ── Import renderTaskPanel ────────────────────────────────────────────────────

import { renderTaskPanel } from '../app.js';

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

describe('renderTaskPanel summary rendering', () => {
  it('renders task summary exactly once for a COMPLETED task', () => {
    const { doc, panelContent } = makeMockDOM();

    // Must set global document before calling renderTaskPanel
    global.document = doc;

    const task = {
      id: 'task-1',
      name: 'Fix the bug',
      state: 'COMPLETED',
      summary: 'Resolved the nil pointer in the payment handler.',
      priority: 'normal',
      created_at: '2026-03-17T10:00:00Z',
      updated_at: '2026-03-17T10:05:00Z',
      tags: [],
    };

    renderTaskPanel(task, []);

    // Count all elements with class 'task-summary' or 'task-summary-text'
    const summaryEls = panelContent.querySelectorAll('.task-summary');
    const summaryTextEls = panelContent.querySelectorAll('.task-summary-text');

    const total = summaryEls.length + summaryTextEls.length;
    assert.equal(total, 1, `Expected exactly 1 summary element, got ${total} (task-summary: ${summaryEls.length}, task-summary-text: ${summaryTextEls.length})`);
  });

  it('uses task-summary class (not task-summary-text) for good contrast', () => {
    const { doc, panelContent } = makeMockDOM();
    global.document = doc;

    const task = {
      id: 'task-2',
      name: 'Another task',
      state: 'COMPLETED',
      summary: 'All done.',
      priority: 'high',
      created_at: '2026-03-17T10:00:00Z',
      updated_at: '2026-03-17T10:05:00Z',
      tags: [],
    };

    renderTaskPanel(task, []);

    const summaryEls = panelContent.querySelectorAll('.task-summary');
    assert.equal(summaryEls.length, 1, 'Expected .task-summary element');
    assert.equal(summaryEls[0].textContent, task.summary);
  });

  it('renders no summary section when task has no summary', () => {
    const { doc, panelContent } = makeMockDOM();
    global.document = doc;

    const task = {
      id: 'task-3',
      name: 'Pending task',
      state: 'PENDING',
      summary: null,
      priority: 'normal',
      created_at: '2026-03-17T10:00:00Z',
      updated_at: '2026-03-17T10:00:00Z',
      tags: [],
    };

    renderTaskPanel(task, []);

    const summaryEls = panelContent.querySelectorAll('.task-summary');
    const summaryTextEls = panelContent.querySelectorAll('.task-summary-text');
    assert.equal(summaryEls.length + summaryTextEls.length, 0, 'Expected no summary when task.summary is null');
  });
});