@urateam/dashboard 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/layout.test.d.ts +2 -0
- package/dist/__tests__/layout.test.d.ts.map +1 -0
- package/dist/__tests__/layout.test.js +138 -0
- package/dist/__tests__/layout.test.js.map +1 -0
- package/dist/__tests__/security.repro.test.d.ts +16 -0
- package/dist/__tests__/security.repro.test.d.ts.map +1 -0
- package/dist/__tests__/security.repro.test.js +249 -0
- package/dist/__tests__/security.repro.test.js.map +1 -0
- package/dist/__tests__/server.test.d.ts +2 -0
- package/dist/__tests__/server.test.d.ts.map +1 -0
- package/dist/__tests__/server.test.js +268 -0
- package/dist/__tests__/server.test.js.map +1 -0
- package/dist/__tests__/ui-refresh.test.d.ts +2 -0
- package/dist/__tests__/ui-refresh.test.d.ts.map +1 -0
- package/dist/__tests__/ui-refresh.test.js +860 -0
- package/dist/__tests__/ui-refresh.test.js.map +1 -0
- package/dist/routes/config.d.ts +4 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +12 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/coordination.d.ts +4 -0
- package/dist/routes/coordination.d.ts.map +1 -0
- package/dist/routes/coordination.js +22 -0
- package/dist/routes/coordination.js.map +1 -0
- package/dist/routes/errors.d.ts +4 -0
- package/dist/routes/errors.d.ts.map +1 -0
- package/dist/routes/errors.js +36 -0
- package/dist/routes/errors.js.map +1 -0
- package/dist/routes/runs.d.ts +4 -0
- package/dist/routes/runs.d.ts.map +1 -0
- package/dist/routes/runs.js +72 -0
- package/dist/routes/runs.js.map +1 -0
- package/dist/routes/tokens.d.ts +4 -0
- package/dist/routes/tokens.d.ts.map +1 -0
- package/dist/routes/tokens.js +47 -0
- package/dist/routes/tokens.js.map +1 -0
- package/dist/server.d.ts +19 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +107 -0
- package/dist/server.js.map +1 -0
- package/dist/views/config.d.ts +3 -0
- package/dist/views/config.d.ts.map +1 -0
- package/dist/views/config.js +47 -0
- package/dist/views/config.js.map +1 -0
- package/dist/views/coordination.d.ts +3 -0
- package/dist/views/coordination.d.ts.map +1 -0
- package/dist/views/coordination.js +63 -0
- package/dist/views/coordination.js.map +1 -0
- package/dist/views/errors.d.ts +14 -0
- package/dist/views/errors.d.ts.map +1 -0
- package/dist/views/errors.js +52 -0
- package/dist/views/errors.js.map +1 -0
- package/dist/views/layout.d.ts +21 -0
- package/dist/views/layout.d.ts.map +1 -0
- package/dist/views/layout.js +82 -0
- package/dist/views/layout.js.map +1 -0
- package/dist/views/run-detail.d.ts +35 -0
- package/dist/views/run-detail.d.ts.map +1 -0
- package/dist/views/run-detail.js +129 -0
- package/dist/views/run-detail.js.map +1 -0
- package/dist/views/run-feed.d.ts +14 -0
- package/dist/views/run-feed.d.ts.map +1 -0
- package/dist/views/run-feed.js +80 -0
- package/dist/views/run-feed.js.map +1 -0
- package/dist/views/tokens.d.ts +13 -0
- package/dist/views/tokens.d.ts.map +1 -0
- package/dist/views/tokens.js +66 -0
- package/dist/views/tokens.js.map +1 -0
- package/package.json +28 -0
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { layout } from "../views/layout.js";
|
|
3
|
+
import { runDetailView } from "../views/run-detail.js";
|
|
4
|
+
import { runFeedView } from "../views/run-feed.js";
|
|
5
|
+
import { tokensView } from "../views/tokens.js";
|
|
6
|
+
import { coordinationView } from "../views/coordination.js";
|
|
7
|
+
describe("Dashboard UI Refresh — Visual & Accessibility", () => {
|
|
8
|
+
// These tests verify visual/structural properties of the layout at the root
|
|
9
|
+
// path (no basePath prefix). Clear DASHBOARD_BASE_PATH so that env-var
|
|
10
|
+
// contamination from other test files does not affect results.
|
|
11
|
+
let savedBasePath;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
savedBasePath = process.env.DASHBOARD_BASE_PATH;
|
|
14
|
+
delete process.env.DASHBOARD_BASE_PATH;
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
if (savedBasePath === undefined) {
|
|
18
|
+
delete process.env.DASHBOARD_BASE_PATH;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
process.env.DASHBOARD_BASE_PATH = savedBasePath;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
describe("Modern Typography & Font Stack", () => {
|
|
25
|
+
it("should include Inter font from Google Fonts", () => {
|
|
26
|
+
const html = layout("Test", "");
|
|
27
|
+
expect(html).toContain("fonts.googleapis.com");
|
|
28
|
+
expect(html).toContain("fonts.gstatic.com");
|
|
29
|
+
expect(html).toContain("family=Inter");
|
|
30
|
+
});
|
|
31
|
+
it("should use system-ui fallback in CSS", () => {
|
|
32
|
+
const html = layout("Test", "");
|
|
33
|
+
// The font is loaded via Google Fonts and styled in style.css
|
|
34
|
+
expect(html).toContain("Inter:wght@400;500;600;700");
|
|
35
|
+
});
|
|
36
|
+
it("should apply preconnect hints for font loading performance", () => {
|
|
37
|
+
const html = layout("Test", "");
|
|
38
|
+
expect(html).toContain('<link rel="preconnect" href="https://fonts.googleapis.com">');
|
|
39
|
+
expect(html).toContain('<link rel="preconnect" href="https://fonts.gstatic.com"');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe("Dark Mode Support", () => {
|
|
43
|
+
it("should include viewport meta tag for responsive design", () => {
|
|
44
|
+
const html = layout("Test", "");
|
|
45
|
+
expect(html).toContain('name="viewport"');
|
|
46
|
+
expect(html).toContain("width=device-width");
|
|
47
|
+
expect(html).toContain("initial-scale=1");
|
|
48
|
+
});
|
|
49
|
+
it("should have dark mode CSS with prefers-color-scheme media query", () => {
|
|
50
|
+
const html = layout("Test", "");
|
|
51
|
+
// The CSS link should be present; dark mode is handled in style.css
|
|
52
|
+
expect(html).toContain('href="/static/style.css"');
|
|
53
|
+
// The tests should verify style.css contains @media (prefers-color-scheme: dark)
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe("Favicon Implementation", () => {
|
|
57
|
+
it("should include SVG emoji favicon", () => {
|
|
58
|
+
const html = layout("Test", "");
|
|
59
|
+
expect(html).toContain("data:image/svg+xml");
|
|
60
|
+
expect(html).toContain("⚡"); // Lightning emoji favicon
|
|
61
|
+
});
|
|
62
|
+
it("should use data-URI favicon to avoid extra HTTP requests", () => {
|
|
63
|
+
const html = layout("Test", "");
|
|
64
|
+
expect(html).toContain('<link rel="icon" href="data:image/svg');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe("Status Badges & Colors", () => {
|
|
68
|
+
const mockRun = {
|
|
69
|
+
id: "run-1",
|
|
70
|
+
issueId: "BEC-102",
|
|
71
|
+
issueTitle: "UI Refresh",
|
|
72
|
+
pipelineKey: "auto-implement",
|
|
73
|
+
repoUrl: "https://github.com/test/repo",
|
|
74
|
+
branch: "feature/ui",
|
|
75
|
+
status: "completed",
|
|
76
|
+
startedAt: new Date("2024-01-01T10:00:00Z"),
|
|
77
|
+
completedAt: new Date("2024-01-01T10:05:00Z"),
|
|
78
|
+
prUrl: "https://github.com/test/repo/pull/1",
|
|
79
|
+
totalInputTokens: 1000,
|
|
80
|
+
totalOutputTokens: 2000,
|
|
81
|
+
errorMessage: null,
|
|
82
|
+
};
|
|
83
|
+
const mockStage = {
|
|
84
|
+
id: "stage-1",
|
|
85
|
+
stage: "implement",
|
|
86
|
+
status: "completed",
|
|
87
|
+
startedAt: new Date("2024-01-01T10:00:00Z"),
|
|
88
|
+
completedAt: new Date("2024-01-01T10:05:00Z"),
|
|
89
|
+
inputTokens: 500,
|
|
90
|
+
outputTokens: 1000,
|
|
91
|
+
turns: 3,
|
|
92
|
+
handoffArtifact: null,
|
|
93
|
+
errorMessage: null,
|
|
94
|
+
};
|
|
95
|
+
it("should render status badges with correct CSS classes", () => {
|
|
96
|
+
const html = runDetailView(mockRun, [mockStage], [], 1, 0);
|
|
97
|
+
expect(html).toContain('class="badge badge-completed"');
|
|
98
|
+
});
|
|
99
|
+
it("should display status badges for all pipeline stages", () => {
|
|
100
|
+
const stages = [
|
|
101
|
+
{ ...mockStage, status: "completed" },
|
|
102
|
+
{ ...mockStage, status: "running", id: "stage-2", stage: "test" },
|
|
103
|
+
{ ...mockStage, status: "failed", id: "stage-3", stage: "review" },
|
|
104
|
+
];
|
|
105
|
+
const html = runDetailView(mockRun, stages, [], 1, 0);
|
|
106
|
+
expect(html).toContain('badge-completed');
|
|
107
|
+
expect(html).toContain('badge-running');
|
|
108
|
+
expect(html).toContain('badge-failed');
|
|
109
|
+
});
|
|
110
|
+
it("should show all status badge colors in feed view", () => {
|
|
111
|
+
const run = {
|
|
112
|
+
id: "run-1",
|
|
113
|
+
issueId: "BEC-102",
|
|
114
|
+
issueTitle: "UI Refresh",
|
|
115
|
+
pipelineKey: "auto-implement",
|
|
116
|
+
repoUrl: "https://github.com/test/repo",
|
|
117
|
+
status: "running",
|
|
118
|
+
startedAt: new Date("2024-01-01T10:00:00Z"),
|
|
119
|
+
completedAt: null,
|
|
120
|
+
totalInputTokens: 1000,
|
|
121
|
+
totalOutputTokens: 2000,
|
|
122
|
+
};
|
|
123
|
+
const html = runFeedView([run]);
|
|
124
|
+
expect(html).toContain('badge-running');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
describe("Progress Indicators & Animations", () => {
|
|
128
|
+
it("should render animated badge for running status", () => {
|
|
129
|
+
const mockRun = {
|
|
130
|
+
id: "run-1",
|
|
131
|
+
issueId: "BEC-102",
|
|
132
|
+
issueTitle: "UI Refresh",
|
|
133
|
+
pipelineKey: "auto-implement",
|
|
134
|
+
repoUrl: "https://github.com/test/repo",
|
|
135
|
+
branch: "feature/ui",
|
|
136
|
+
status: "running",
|
|
137
|
+
startedAt: new Date(Date.now() - 5000), // 5 seconds ago
|
|
138
|
+
completedAt: null,
|
|
139
|
+
prUrl: null,
|
|
140
|
+
totalInputTokens: 1000,
|
|
141
|
+
totalOutputTokens: 2000,
|
|
142
|
+
errorMessage: null,
|
|
143
|
+
};
|
|
144
|
+
const mockStage = {
|
|
145
|
+
id: "stage-1",
|
|
146
|
+
stage: "implement",
|
|
147
|
+
status: "running",
|
|
148
|
+
startedAt: new Date(Date.now() - 5000),
|
|
149
|
+
completedAt: null,
|
|
150
|
+
inputTokens: 500,
|
|
151
|
+
outputTokens: 1000,
|
|
152
|
+
turns: 3,
|
|
153
|
+
handoffArtifact: null,
|
|
154
|
+
errorMessage: null,
|
|
155
|
+
};
|
|
156
|
+
const html = runDetailView(mockRun, [mockStage], [], 1, 0);
|
|
157
|
+
expect(html).toContain('badge-running');
|
|
158
|
+
expect(html).toContain("(running)"); // Duration badge should show running state
|
|
159
|
+
});
|
|
160
|
+
it("should have timeline visual indicators for different stages", () => {
|
|
161
|
+
const stages = [
|
|
162
|
+
{
|
|
163
|
+
id: "stage-1",
|
|
164
|
+
stage: "triage",
|
|
165
|
+
status: "completed",
|
|
166
|
+
startedAt: new Date("2024-01-01T10:00:00Z"),
|
|
167
|
+
completedAt: new Date("2024-01-01T10:01:00Z"),
|
|
168
|
+
inputTokens: 100,
|
|
169
|
+
outputTokens: 200,
|
|
170
|
+
turns: 1,
|
|
171
|
+
handoffArtifact: null,
|
|
172
|
+
errorMessage: null,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "stage-2",
|
|
176
|
+
stage: "implement",
|
|
177
|
+
status: "running",
|
|
178
|
+
startedAt: new Date("2024-01-01T10:02:00Z"),
|
|
179
|
+
completedAt: null,
|
|
180
|
+
inputTokens: 500,
|
|
181
|
+
outputTokens: 1000,
|
|
182
|
+
turns: 3,
|
|
183
|
+
handoffArtifact: null,
|
|
184
|
+
errorMessage: null,
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
const mockRun = {
|
|
188
|
+
id: "run-1",
|
|
189
|
+
issueId: "BEC-102",
|
|
190
|
+
issueTitle: "UI Refresh",
|
|
191
|
+
pipelineKey: "auto-implement",
|
|
192
|
+
repoUrl: "https://github.com/test/repo",
|
|
193
|
+
branch: "feature/ui",
|
|
194
|
+
status: "running",
|
|
195
|
+
startedAt: new Date("2024-01-01T10:00:00Z"),
|
|
196
|
+
completedAt: null,
|
|
197
|
+
prUrl: null,
|
|
198
|
+
totalInputTokens: 600,
|
|
199
|
+
totalOutputTokens: 1200,
|
|
200
|
+
errorMessage: null,
|
|
201
|
+
};
|
|
202
|
+
const html = runDetailView(mockRun, stages, [], 1, 0);
|
|
203
|
+
expect(html).toContain("timeline-item completed");
|
|
204
|
+
expect(html).toContain("timeline-item running");
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
describe("Responsive Layout", () => {
|
|
208
|
+
it("should wrap tables in responsive container", () => {
|
|
209
|
+
const run = {
|
|
210
|
+
id: "run-1",
|
|
211
|
+
issueId: "BEC-102",
|
|
212
|
+
issueTitle: "UI Refresh",
|
|
213
|
+
pipelineKey: "auto-implement",
|
|
214
|
+
repoUrl: "https://github.com/test/repo",
|
|
215
|
+
status: "completed",
|
|
216
|
+
startedAt: new Date(),
|
|
217
|
+
completedAt: new Date(),
|
|
218
|
+
totalInputTokens: 1000,
|
|
219
|
+
totalOutputTokens: 2000,
|
|
220
|
+
};
|
|
221
|
+
const html = runFeedView([run]);
|
|
222
|
+
expect(html).toContain('class="table-wrapper"');
|
|
223
|
+
});
|
|
224
|
+
it("should include viewport meta tag for mobile responsiveness", () => {
|
|
225
|
+
const html = layout("Test", "");
|
|
226
|
+
expect(html).toContain('<meta name="viewport"');
|
|
227
|
+
expect(html).toContain("width=device-width");
|
|
228
|
+
});
|
|
229
|
+
it("should use responsive table with proper overflow handling", () => {
|
|
230
|
+
const run = {
|
|
231
|
+
id: "run-1",
|
|
232
|
+
issueId: "BEC-102",
|
|
233
|
+
issueTitle: "Very Long Title That Might Overflow on Mobile Devices",
|
|
234
|
+
pipelineKey: "auto-implement",
|
|
235
|
+
repoUrl: "https://github.com/test/very-long-repo-name/repository",
|
|
236
|
+
status: "completed",
|
|
237
|
+
startedAt: new Date(),
|
|
238
|
+
completedAt: new Date(),
|
|
239
|
+
totalInputTokens: 1000,
|
|
240
|
+
totalOutputTokens: 2000,
|
|
241
|
+
};
|
|
242
|
+
const html = runFeedView([run]);
|
|
243
|
+
expect(html).toContain('class="table-wrapper"');
|
|
244
|
+
// CSS will handle overflow-x styling via style.css
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
describe("Card-Style Layout", () => {
|
|
248
|
+
it("should use card layout on run detail page", () => {
|
|
249
|
+
const mockRun = {
|
|
250
|
+
id: "run-1",
|
|
251
|
+
issueId: "BEC-102",
|
|
252
|
+
issueTitle: "UI Refresh",
|
|
253
|
+
pipelineKey: "auto-implement",
|
|
254
|
+
repoUrl: "https://github.com/test/repo",
|
|
255
|
+
branch: "feature/ui",
|
|
256
|
+
status: "completed",
|
|
257
|
+
startedAt: new Date(),
|
|
258
|
+
completedAt: new Date(),
|
|
259
|
+
prUrl: null,
|
|
260
|
+
totalInputTokens: 1000,
|
|
261
|
+
totalOutputTokens: 2000,
|
|
262
|
+
errorMessage: null,
|
|
263
|
+
};
|
|
264
|
+
const mockStage = {
|
|
265
|
+
id: "stage-1",
|
|
266
|
+
stage: "implement",
|
|
267
|
+
status: "completed",
|
|
268
|
+
startedAt: new Date(),
|
|
269
|
+
completedAt: new Date(),
|
|
270
|
+
inputTokens: 500,
|
|
271
|
+
outputTokens: 1000,
|
|
272
|
+
turns: 3,
|
|
273
|
+
handoffArtifact: null,
|
|
274
|
+
errorMessage: null,
|
|
275
|
+
};
|
|
276
|
+
const html = runDetailView(mockRun, [mockStage], [], 1, 0);
|
|
277
|
+
expect(html).toContain('class="card"');
|
|
278
|
+
expect(html.match(/class="card"/g)?.length).toBeGreaterThanOrEqual(3); // Multiple cards
|
|
279
|
+
});
|
|
280
|
+
it("should use card layout on tokens page", () => {
|
|
281
|
+
const html = tokensView([{ date: "2024-01-01", inputTokens: 1000, outputTokens: 2000 }], [{ key: "auto-implement", inputTokens: 1000, outputTokens: 2000 }], [{ key: "implement", inputTokens: 500, outputTokens: 1000 }]);
|
|
282
|
+
expect(html).toContain('class="card"');
|
|
283
|
+
expect(html.match(/class="card"/g)?.length).toBeGreaterThanOrEqual(3);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
describe("Sticky Navigation", () => {
|
|
287
|
+
it("should have sticky positioned navigation", () => {
|
|
288
|
+
const html = layout("Test", "");
|
|
289
|
+
expect(html).toContain("<nav>");
|
|
290
|
+
// CSS will handle position: sticky via style.css
|
|
291
|
+
});
|
|
292
|
+
it("should include navigation links", () => {
|
|
293
|
+
const html = layout("Test", "");
|
|
294
|
+
expect(html).toContain('href="/">Runs</a>');
|
|
295
|
+
expect(html).toContain('href="/tokens">Tokens</a>');
|
|
296
|
+
expect(html).toContain('href="/errors">Errors</a>');
|
|
297
|
+
expect(html).toContain('href="/config">Config</a>');
|
|
298
|
+
expect(html).toContain('href="/coordination">Coordination</a>');
|
|
299
|
+
});
|
|
300
|
+
it("should include branded logo in nav", () => {
|
|
301
|
+
const html = layout("Test", "");
|
|
302
|
+
expect(html).toContain("⚡ urateam");
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
describe("Relative Timestamps", () => {
|
|
306
|
+
it("should display relative time in run feed", () => {
|
|
307
|
+
const now = Date.now();
|
|
308
|
+
const oneHourAgo = new Date(now - 60 * 60 * 1000);
|
|
309
|
+
const run = {
|
|
310
|
+
id: "run-1",
|
|
311
|
+
issueId: "BEC-102",
|
|
312
|
+
issueTitle: "UI Refresh",
|
|
313
|
+
pipelineKey: "auto-implement",
|
|
314
|
+
repoUrl: "https://github.com/test/repo",
|
|
315
|
+
status: "completed",
|
|
316
|
+
startedAt: oneHourAgo,
|
|
317
|
+
completedAt: new Date(now - 30 * 60 * 1000),
|
|
318
|
+
totalInputTokens: 1000,
|
|
319
|
+
totalOutputTokens: 2000,
|
|
320
|
+
};
|
|
321
|
+
const html = runFeedView([run]);
|
|
322
|
+
expect(html).toMatch(/\dh ago/); // Should contain "1h ago" or similar
|
|
323
|
+
});
|
|
324
|
+
it("should display relative time in run detail page", () => {
|
|
325
|
+
const now = Date.now();
|
|
326
|
+
const twoHoursAgo = new Date(now - 2 * 60 * 60 * 1000);
|
|
327
|
+
const mockRun = {
|
|
328
|
+
id: "run-1",
|
|
329
|
+
issueId: "BEC-102",
|
|
330
|
+
issueTitle: "UI Refresh",
|
|
331
|
+
pipelineKey: "auto-implement",
|
|
332
|
+
repoUrl: "https://github.com/test/repo",
|
|
333
|
+
branch: "feature/ui",
|
|
334
|
+
status: "completed",
|
|
335
|
+
startedAt: twoHoursAgo,
|
|
336
|
+
completedAt: new Date(now - 60 * 60 * 1000),
|
|
337
|
+
prUrl: null,
|
|
338
|
+
totalInputTokens: 1000,
|
|
339
|
+
totalOutputTokens: 2000,
|
|
340
|
+
errorMessage: null,
|
|
341
|
+
};
|
|
342
|
+
const mockStage = {
|
|
343
|
+
id: "stage-1",
|
|
344
|
+
stage: "implement",
|
|
345
|
+
status: "completed",
|
|
346
|
+
startedAt: twoHoursAgo,
|
|
347
|
+
completedAt: new Date(now - 60 * 60 * 1000),
|
|
348
|
+
inputTokens: 500,
|
|
349
|
+
outputTokens: 1000,
|
|
350
|
+
turns: 3,
|
|
351
|
+
handoffArtifact: null,
|
|
352
|
+
errorMessage: null,
|
|
353
|
+
};
|
|
354
|
+
const html = runDetailView(mockRun, [mockStage], [], 1, 0);
|
|
355
|
+
expect(html).toMatch(/\dh ago/); // Should show relative time
|
|
356
|
+
});
|
|
357
|
+
it("should display relative time in logs", () => {
|
|
358
|
+
const now = Date.now();
|
|
359
|
+
const logTime = new Date(now - 5 * 60 * 1000); // 5 minutes ago
|
|
360
|
+
const mockRun = {
|
|
361
|
+
id: "run-1",
|
|
362
|
+
issueId: "BEC-102",
|
|
363
|
+
issueTitle: "UI Refresh",
|
|
364
|
+
pipelineKey: "auto-implement",
|
|
365
|
+
repoUrl: "https://github.com/test/repo",
|
|
366
|
+
branch: "feature/ui",
|
|
367
|
+
status: "completed",
|
|
368
|
+
startedAt: new Date(now - 10 * 60 * 1000),
|
|
369
|
+
completedAt: new Date(now - 1 * 60 * 1000),
|
|
370
|
+
prUrl: null,
|
|
371
|
+
totalInputTokens: 1000,
|
|
372
|
+
totalOutputTokens: 2000,
|
|
373
|
+
errorMessage: null,
|
|
374
|
+
};
|
|
375
|
+
const mockStage = {
|
|
376
|
+
id: "stage-1",
|
|
377
|
+
stage: "implement",
|
|
378
|
+
status: "completed",
|
|
379
|
+
startedAt: new Date(now - 10 * 60 * 1000),
|
|
380
|
+
completedAt: new Date(now - 1 * 60 * 1000),
|
|
381
|
+
inputTokens: 500,
|
|
382
|
+
outputTokens: 1000,
|
|
383
|
+
turns: 3,
|
|
384
|
+
handoffArtifact: null,
|
|
385
|
+
errorMessage: null,
|
|
386
|
+
};
|
|
387
|
+
const logs = [
|
|
388
|
+
{
|
|
389
|
+
id: "log-1",
|
|
390
|
+
timestamp: logTime,
|
|
391
|
+
type: "message",
|
|
392
|
+
content: "Starting agent execution",
|
|
393
|
+
},
|
|
394
|
+
];
|
|
395
|
+
const html = runDetailView(mockRun, [mockStage], logs, 1, 1);
|
|
396
|
+
expect(html).toContain("ago"); // Should show relative time in logs
|
|
397
|
+
});
|
|
398
|
+
it("should display relative time in coordination view", () => {
|
|
399
|
+
const now = Date.now();
|
|
400
|
+
const tenMinutesAgo = new Date(now - 10 * 60 * 1000);
|
|
401
|
+
const entries = [
|
|
402
|
+
{
|
|
403
|
+
id: "work-1",
|
|
404
|
+
runId: "run-1",
|
|
405
|
+
issueId: "BEC-102",
|
|
406
|
+
stage: "implement",
|
|
407
|
+
filesModified: ["src/main.ts", "src/utils.ts"],
|
|
408
|
+
startedAt: tenMinutesAgo,
|
|
409
|
+
updatedAt: new Date(now - 2 * 60 * 1000),
|
|
410
|
+
},
|
|
411
|
+
];
|
|
412
|
+
const html = coordinationView(entries);
|
|
413
|
+
expect(html).toMatch(/\dm ago/); // Should show relative time
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
describe("Run Duration Display", () => {
|
|
417
|
+
it("should display duration prominently in run detail", () => {
|
|
418
|
+
const startTime = new Date("2024-01-01T10:00:00Z");
|
|
419
|
+
const endTime = new Date("2024-01-01T10:05:00Z");
|
|
420
|
+
const mockRun = {
|
|
421
|
+
id: "run-1",
|
|
422
|
+
issueId: "BEC-102",
|
|
423
|
+
issueTitle: "UI Refresh",
|
|
424
|
+
pipelineKey: "auto-implement",
|
|
425
|
+
repoUrl: "https://github.com/test/repo",
|
|
426
|
+
branch: "feature/ui",
|
|
427
|
+
status: "completed",
|
|
428
|
+
startedAt: startTime,
|
|
429
|
+
completedAt: endTime,
|
|
430
|
+
prUrl: null,
|
|
431
|
+
totalInputTokens: 1000,
|
|
432
|
+
totalOutputTokens: 2000,
|
|
433
|
+
errorMessage: null,
|
|
434
|
+
};
|
|
435
|
+
const mockStage = {
|
|
436
|
+
id: "stage-1",
|
|
437
|
+
stage: "implement",
|
|
438
|
+
status: "completed",
|
|
439
|
+
startedAt: startTime,
|
|
440
|
+
completedAt: endTime,
|
|
441
|
+
inputTokens: 500,
|
|
442
|
+
outputTokens: 1000,
|
|
443
|
+
turns: 3,
|
|
444
|
+
handoffArtifact: null,
|
|
445
|
+
errorMessage: null,
|
|
446
|
+
};
|
|
447
|
+
const html = runDetailView(mockRun, [mockStage], [], 1, 0);
|
|
448
|
+
expect(html).toContain("duration-badge");
|
|
449
|
+
expect(html).toContain("5m"); // Should show 5 minute duration
|
|
450
|
+
});
|
|
451
|
+
it("should display duration in feed view", () => {
|
|
452
|
+
const startTime = new Date("2024-01-01T10:00:00Z");
|
|
453
|
+
const endTime = new Date("2024-01-01T10:03:30Z");
|
|
454
|
+
const run = {
|
|
455
|
+
id: "run-1",
|
|
456
|
+
issueId: "BEC-102",
|
|
457
|
+
issueTitle: "UI Refresh",
|
|
458
|
+
pipelineKey: "auto-implement",
|
|
459
|
+
repoUrl: "https://github.com/test/repo",
|
|
460
|
+
status: "completed",
|
|
461
|
+
startedAt: startTime,
|
|
462
|
+
completedAt: endTime,
|
|
463
|
+
totalInputTokens: 1000,
|
|
464
|
+
totalOutputTokens: 2000,
|
|
465
|
+
};
|
|
466
|
+
const html = runFeedView([run]);
|
|
467
|
+
expect(html).toContain("3m 30s");
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
describe("Collapsible Log Sections", () => {
|
|
471
|
+
it("should render collapsible details section for logs", () => {
|
|
472
|
+
const mockRun = {
|
|
473
|
+
id: "run-1",
|
|
474
|
+
issueId: "BEC-102",
|
|
475
|
+
issueTitle: "UI Refresh",
|
|
476
|
+
pipelineKey: "auto-implement",
|
|
477
|
+
repoUrl: "https://github.com/test/repo",
|
|
478
|
+
branch: "feature/ui",
|
|
479
|
+
status: "completed",
|
|
480
|
+
startedAt: new Date(),
|
|
481
|
+
completedAt: new Date(),
|
|
482
|
+
prUrl: null,
|
|
483
|
+
totalInputTokens: 1000,
|
|
484
|
+
totalOutputTokens: 2000,
|
|
485
|
+
errorMessage: null,
|
|
486
|
+
};
|
|
487
|
+
const mockStage = {
|
|
488
|
+
id: "stage-1",
|
|
489
|
+
stage: "implement",
|
|
490
|
+
status: "completed",
|
|
491
|
+
startedAt: new Date(),
|
|
492
|
+
completedAt: new Date(),
|
|
493
|
+
inputTokens: 500,
|
|
494
|
+
outputTokens: 1000,
|
|
495
|
+
turns: 3,
|
|
496
|
+
handoffArtifact: null,
|
|
497
|
+
errorMessage: null,
|
|
498
|
+
};
|
|
499
|
+
const html = runDetailView(mockRun, [mockStage], [], 1, 0);
|
|
500
|
+
expect(html).toContain("<details");
|
|
501
|
+
expect(html).toContain("log-section");
|
|
502
|
+
});
|
|
503
|
+
it("should show log section open by default", () => {
|
|
504
|
+
const mockRun = {
|
|
505
|
+
id: "run-1",
|
|
506
|
+
issueId: "BEC-102",
|
|
507
|
+
issueTitle: "UI Refresh",
|
|
508
|
+
pipelineKey: "auto-implement",
|
|
509
|
+
repoUrl: "https://github.com/test/repo",
|
|
510
|
+
branch: "feature/ui",
|
|
511
|
+
status: "completed",
|
|
512
|
+
startedAt: new Date(),
|
|
513
|
+
completedAt: new Date(),
|
|
514
|
+
prUrl: null,
|
|
515
|
+
totalInputTokens: 1000,
|
|
516
|
+
totalOutputTokens: 2000,
|
|
517
|
+
errorMessage: null,
|
|
518
|
+
};
|
|
519
|
+
const mockStage = {
|
|
520
|
+
id: "stage-1",
|
|
521
|
+
stage: "implement",
|
|
522
|
+
status: "completed",
|
|
523
|
+
startedAt: new Date(),
|
|
524
|
+
completedAt: new Date(),
|
|
525
|
+
inputTokens: 500,
|
|
526
|
+
outputTokens: 1000,
|
|
527
|
+
turns: 3,
|
|
528
|
+
handoffArtifact: null,
|
|
529
|
+
errorMessage: null,
|
|
530
|
+
};
|
|
531
|
+
const html = runDetailView(mockRun, [mockStage], [], 1, 0);
|
|
532
|
+
expect(html).toContain("<details class=\"log-section\" open>");
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
describe("Token Usage Bar Chart", () => {
|
|
536
|
+
it("should display bar chart on tokens page", () => {
|
|
537
|
+
const html = tokensView([{ date: "2024-01-01", inputTokens: 5000, outputTokens: 10000 }], [{ key: "auto-implement", inputTokens: 5000, outputTokens: 10000 }], [{ key: "implement", inputTokens: 3000, outputTokens: 6000 }]);
|
|
538
|
+
expect(html).toContain("bar-chart");
|
|
539
|
+
expect(html).toContain("bar-fill");
|
|
540
|
+
});
|
|
541
|
+
it("should show input and output bars separately", () => {
|
|
542
|
+
const html = tokensView([{ date: "2024-01-01", inputTokens: 5000, outputTokens: 10000 }], [], []);
|
|
543
|
+
expect(html).toContain("bar-fill-input");
|
|
544
|
+
expect(html).toContain("bar-fill-output");
|
|
545
|
+
});
|
|
546
|
+
it("should display legend for bar colors", () => {
|
|
547
|
+
const html = tokensView([{ date: "2024-01-01", inputTokens: 5000, outputTokens: 10000 }], [], []);
|
|
548
|
+
expect(html).toContain("Input");
|
|
549
|
+
expect(html).toContain("Output");
|
|
550
|
+
});
|
|
551
|
+
it("should scale bars proportionally", () => {
|
|
552
|
+
const html = tokensView([
|
|
553
|
+
{ date: "2024-01-01", inputTokens: 1000, outputTokens: 2000 },
|
|
554
|
+
{ date: "2024-01-02", inputTokens: 2000, outputTokens: 4000 },
|
|
555
|
+
], [], []);
|
|
556
|
+
expect(html).toContain("width:");
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
describe("Table Row Hover Effects", () => {
|
|
560
|
+
it("should have table elements for hover styling", () => {
|
|
561
|
+
const run = {
|
|
562
|
+
id: "run-1",
|
|
563
|
+
issueId: "BEC-102",
|
|
564
|
+
issueTitle: "UI Refresh",
|
|
565
|
+
pipelineKey: "auto-implement",
|
|
566
|
+
repoUrl: "https://github.com/test/repo",
|
|
567
|
+
status: "completed",
|
|
568
|
+
startedAt: new Date(),
|
|
569
|
+
completedAt: new Date(),
|
|
570
|
+
totalInputTokens: 1000,
|
|
571
|
+
totalOutputTokens: 2000,
|
|
572
|
+
};
|
|
573
|
+
const html = runFeedView([run]);
|
|
574
|
+
expect(html).toContain("<tbody>");
|
|
575
|
+
expect(html).toContain("<tr>");
|
|
576
|
+
// CSS will handle hover effects via style.css (tbody tr:hover td)
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
describe("Pure CSS & No Build Step", () => {
|
|
580
|
+
it("should not use Tailwind classes", () => {
|
|
581
|
+
const mockRun = {
|
|
582
|
+
id: "run-1",
|
|
583
|
+
issueId: "BEC-102",
|
|
584
|
+
issueTitle: "UI Refresh",
|
|
585
|
+
pipelineKey: "auto-implement",
|
|
586
|
+
repoUrl: "https://github.com/test/repo",
|
|
587
|
+
branch: "feature/ui",
|
|
588
|
+
status: "completed",
|
|
589
|
+
startedAt: new Date(),
|
|
590
|
+
completedAt: new Date(),
|
|
591
|
+
prUrl: null,
|
|
592
|
+
totalInputTokens: 1000,
|
|
593
|
+
totalOutputTokens: 2000,
|
|
594
|
+
errorMessage: null,
|
|
595
|
+
};
|
|
596
|
+
const html = layout("Test", "<div>content</div>");
|
|
597
|
+
// Check that no Tailwind utility classes are present
|
|
598
|
+
const tailwindClasses = ["m-1", "p-4", "text-lg", "flex", "grid"];
|
|
599
|
+
for (const cls of tailwindClasses) {
|
|
600
|
+
expect(html).not.toContain(`class="${cls}`);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
it("should use semantic HTML only", () => {
|
|
604
|
+
const html = layout("Test", "");
|
|
605
|
+
expect(html).toContain("<!DOCTYPE html>");
|
|
606
|
+
expect(html).toContain("<html lang=\"en\">");
|
|
607
|
+
expect(html).toContain("<head>");
|
|
608
|
+
expect(html).toContain("<body>");
|
|
609
|
+
});
|
|
610
|
+
it("should use only static CSS file", () => {
|
|
611
|
+
const html = layout("Test", "");
|
|
612
|
+
expect(html).toContain('href="/static/style.css"');
|
|
613
|
+
// Should not reference dynamic CSS or CSS-in-JS
|
|
614
|
+
expect(html).not.toMatch(/href=.*\.module\.css/);
|
|
615
|
+
});
|
|
616
|
+
it("should not include client-side JS frameworks (except HTMX)", () => {
|
|
617
|
+
const html = layout("Test", "");
|
|
618
|
+
expect(html).not.toContain("react");
|
|
619
|
+
expect(html).not.toContain("vue");
|
|
620
|
+
expect(html).not.toContain("angular");
|
|
621
|
+
expect(html).toContain("htmx.org");
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
describe("HTMX Functionality", () => {
|
|
625
|
+
it("should include HTMX CDN link", () => {
|
|
626
|
+
const html = layout("Test", "");
|
|
627
|
+
expect(html).toContain("https://unpkg.com/htmx.org");
|
|
628
|
+
});
|
|
629
|
+
it("should preserve HTMX attributes in run feed", () => {
|
|
630
|
+
const run = {
|
|
631
|
+
id: "run-1",
|
|
632
|
+
issueId: "BEC-102",
|
|
633
|
+
issueTitle: "UI Refresh",
|
|
634
|
+
pipelineKey: "auto-implement",
|
|
635
|
+
repoUrl: "https://github.com/test/repo",
|
|
636
|
+
status: "completed",
|
|
637
|
+
startedAt: new Date(),
|
|
638
|
+
completedAt: new Date(),
|
|
639
|
+
totalInputTokens: 1000,
|
|
640
|
+
totalOutputTokens: 2000,
|
|
641
|
+
};
|
|
642
|
+
const html = runFeedView([run]);
|
|
643
|
+
expect(html).toContain("hx-get");
|
|
644
|
+
expect(html).toContain("hx-trigger");
|
|
645
|
+
expect(html).toContain("hx-swap");
|
|
646
|
+
});
|
|
647
|
+
it("should preserve HTMX attributes in coordination view", () => {
|
|
648
|
+
const entries = [
|
|
649
|
+
{
|
|
650
|
+
id: "work-1",
|
|
651
|
+
runId: "run-1",
|
|
652
|
+
issueId: "BEC-102",
|
|
653
|
+
stage: "implement",
|
|
654
|
+
filesModified: ["src/main.ts"],
|
|
655
|
+
startedAt: new Date(),
|
|
656
|
+
updatedAt: new Date(),
|
|
657
|
+
},
|
|
658
|
+
];
|
|
659
|
+
const html = coordinationView(entries);
|
|
660
|
+
expect(html).toContain("hx-get");
|
|
661
|
+
expect(html).toContain("hx-trigger");
|
|
662
|
+
expect(html).toContain("hx-swap");
|
|
663
|
+
});
|
|
664
|
+
it("should have auto-refresh intervals set correctly", () => {
|
|
665
|
+
const run = {
|
|
666
|
+
id: "run-1",
|
|
667
|
+
issueId: "BEC-102",
|
|
668
|
+
issueTitle: "UI Refresh",
|
|
669
|
+
pipelineKey: "auto-implement",
|
|
670
|
+
repoUrl: "https://github.com/test/repo",
|
|
671
|
+
status: "completed",
|
|
672
|
+
startedAt: new Date(),
|
|
673
|
+
completedAt: new Date(),
|
|
674
|
+
totalInputTokens: 1000,
|
|
675
|
+
totalOutputTokens: 2000,
|
|
676
|
+
};
|
|
677
|
+
const html = runFeedView([run]);
|
|
678
|
+
expect(html).toContain('hx-trigger="every 5s"');
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
describe("Accessibility — Keyboard Navigation", () => {
|
|
682
|
+
it("should include focus-visible styles in HTML structure", () => {
|
|
683
|
+
const html = layout("Test", "");
|
|
684
|
+
expect(html).toContain("<nav>");
|
|
685
|
+
expect(html).toContain("<a");
|
|
686
|
+
// CSS will handle focus-visible styling
|
|
687
|
+
});
|
|
688
|
+
it("should have proper semantic link elements", () => {
|
|
689
|
+
const html = layout("Test", "");
|
|
690
|
+
expect(html).toMatch(/<a[^>]*href="[^"]*"[^>]*>/);
|
|
691
|
+
});
|
|
692
|
+
it("should have collapsible sections with proper semantics", () => {
|
|
693
|
+
const mockRun = {
|
|
694
|
+
id: "run-1",
|
|
695
|
+
issueId: "BEC-102",
|
|
696
|
+
issueTitle: "UI Refresh",
|
|
697
|
+
pipelineKey: "auto-implement",
|
|
698
|
+
repoUrl: "https://github.com/test/repo",
|
|
699
|
+
branch: "feature/ui",
|
|
700
|
+
status: "completed",
|
|
701
|
+
startedAt: new Date(),
|
|
702
|
+
completedAt: new Date(),
|
|
703
|
+
prUrl: null,
|
|
704
|
+
totalInputTokens: 1000,
|
|
705
|
+
totalOutputTokens: 2000,
|
|
706
|
+
errorMessage: null,
|
|
707
|
+
};
|
|
708
|
+
const mockStage = {
|
|
709
|
+
id: "stage-1",
|
|
710
|
+
stage: "implement",
|
|
711
|
+
status: "completed",
|
|
712
|
+
startedAt: new Date(),
|
|
713
|
+
completedAt: new Date(),
|
|
714
|
+
inputTokens: 500,
|
|
715
|
+
outputTokens: 1000,
|
|
716
|
+
turns: 3,
|
|
717
|
+
handoffArtifact: null,
|
|
718
|
+
errorMessage: null,
|
|
719
|
+
};
|
|
720
|
+
const html = runDetailView(mockRun, [mockStage], [], 1, 0);
|
|
721
|
+
expect(html).toContain("<details");
|
|
722
|
+
expect(html).toContain("<summary");
|
|
723
|
+
});
|
|
724
|
+
it("should have proper heading hierarchy", () => {
|
|
725
|
+
const mockRun = {
|
|
726
|
+
id: "run-1",
|
|
727
|
+
issueId: "BEC-102",
|
|
728
|
+
issueTitle: "UI Refresh",
|
|
729
|
+
pipelineKey: "auto-implement",
|
|
730
|
+
repoUrl: "https://github.com/test/repo",
|
|
731
|
+
branch: "feature/ui",
|
|
732
|
+
status: "completed",
|
|
733
|
+
startedAt: new Date(),
|
|
734
|
+
completedAt: new Date(),
|
|
735
|
+
prUrl: null,
|
|
736
|
+
totalInputTokens: 1000,
|
|
737
|
+
totalOutputTokens: 2000,
|
|
738
|
+
errorMessage: null,
|
|
739
|
+
};
|
|
740
|
+
const mockStage = {
|
|
741
|
+
id: "stage-1",
|
|
742
|
+
stage: "implement",
|
|
743
|
+
status: "completed",
|
|
744
|
+
startedAt: new Date(),
|
|
745
|
+
completedAt: new Date(),
|
|
746
|
+
inputTokens: 500,
|
|
747
|
+
outputTokens: 1000,
|
|
748
|
+
turns: 3,
|
|
749
|
+
handoffArtifact: null,
|
|
750
|
+
errorMessage: null,
|
|
751
|
+
};
|
|
752
|
+
const html = runDetailView(mockRun, [mockStage], [], 1, 0);
|
|
753
|
+
// The run detail view uses h2 for section titles (h1 is added by layout)
|
|
754
|
+
expect(html).toMatch(/<h2[^>]*>/); // Section titles
|
|
755
|
+
expect(html).toContain("Run Details"); // Verify headings exist
|
|
756
|
+
});
|
|
757
|
+
});
|
|
758
|
+
describe("Accessibility — Contrast Ratios", () => {
|
|
759
|
+
it("should use high contrast text colors", () => {
|
|
760
|
+
const html = layout("Test", "");
|
|
761
|
+
// The CSS should have proper contrast ratios defined in color variables
|
|
762
|
+
expect(html).toContain('href="/static/style.css"');
|
|
763
|
+
});
|
|
764
|
+
it("should provide sufficient contrast for status badges", () => {
|
|
765
|
+
const mockRun = {
|
|
766
|
+
id: "run-1",
|
|
767
|
+
issueId: "BEC-102",
|
|
768
|
+
issueTitle: "UI Refresh",
|
|
769
|
+
pipelineKey: "auto-implement",
|
|
770
|
+
repoUrl: "https://github.com/test/repo",
|
|
771
|
+
branch: "feature/ui",
|
|
772
|
+
status: "failed",
|
|
773
|
+
startedAt: new Date(),
|
|
774
|
+
completedAt: new Date(),
|
|
775
|
+
prUrl: null,
|
|
776
|
+
totalInputTokens: 1000,
|
|
777
|
+
totalOutputTokens: 2000,
|
|
778
|
+
errorMessage: "Test error",
|
|
779
|
+
};
|
|
780
|
+
const mockStage = {
|
|
781
|
+
id: "stage-1",
|
|
782
|
+
stage: "implement",
|
|
783
|
+
status: "failed",
|
|
784
|
+
startedAt: new Date(),
|
|
785
|
+
completedAt: new Date(),
|
|
786
|
+
inputTokens: 500,
|
|
787
|
+
outputTokens: 1000,
|
|
788
|
+
turns: 3,
|
|
789
|
+
handoffArtifact: null,
|
|
790
|
+
errorMessage: "Stage failed",
|
|
791
|
+
};
|
|
792
|
+
const html = runDetailView(mockRun, [mockStage], [], 1, 0);
|
|
793
|
+
expect(html).toContain('badge-failed');
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
describe("Empty State Handling", () => {
|
|
797
|
+
it("should display empty state for coordination view", () => {
|
|
798
|
+
const html = coordinationView([]);
|
|
799
|
+
expect(html).toContain("No agents currently active");
|
|
800
|
+
expect(html).toContain("empty");
|
|
801
|
+
});
|
|
802
|
+
it("should display empty state for token feed", () => {
|
|
803
|
+
const html = tokensView([], [], []);
|
|
804
|
+
expect(html).toContain("No data");
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
describe("URL-safe run ID encoding in href attributes", () => {
|
|
808
|
+
it("run-feed: run ID with '+' produces %2B in href", () => {
|
|
809
|
+
const run = {
|
|
810
|
+
id: "run+special",
|
|
811
|
+
issueId: "BEC-108",
|
|
812
|
+
issueTitle: "URL encoding test",
|
|
813
|
+
pipelineKey: "auto-implement",
|
|
814
|
+
repoUrl: "https://github.com/test/repo",
|
|
815
|
+
status: "done",
|
|
816
|
+
startedAt: new Date("2024-01-01T10:00:00Z"),
|
|
817
|
+
completedAt: new Date("2024-01-01T10:05:00Z"),
|
|
818
|
+
totalInputTokens: 100,
|
|
819
|
+
totalOutputTokens: 200,
|
|
820
|
+
};
|
|
821
|
+
const html = runFeedView([run]);
|
|
822
|
+
expect(html).toContain("/runs/run%2Bspecial");
|
|
823
|
+
expect(html).not.toContain("/runs/run+special");
|
|
824
|
+
});
|
|
825
|
+
it("run-detail: run ID with '/' produces %2F in pagination hrefs", () => {
|
|
826
|
+
const run = {
|
|
827
|
+
id: "run/special",
|
|
828
|
+
issueId: "BEC-108",
|
|
829
|
+
issueTitle: "URL encoding test",
|
|
830
|
+
pipelineKey: "auto-implement",
|
|
831
|
+
repoUrl: "https://github.com/test/repo",
|
|
832
|
+
branch: "feature/test",
|
|
833
|
+
status: "done",
|
|
834
|
+
startedAt: new Date("2024-01-01T10:00:00Z"),
|
|
835
|
+
completedAt: new Date("2024-01-01T10:05:00Z"),
|
|
836
|
+
prUrl: null,
|
|
837
|
+
totalInputTokens: 100,
|
|
838
|
+
totalOutputTokens: 200,
|
|
839
|
+
errorMessage: null,
|
|
840
|
+
};
|
|
841
|
+
const mockStage = {
|
|
842
|
+
id: "stage-1",
|
|
843
|
+
stage: "implement",
|
|
844
|
+
status: "done",
|
|
845
|
+
startedAt: new Date("2024-01-01T10:00:00Z"),
|
|
846
|
+
completedAt: new Date("2024-01-01T10:04:00Z"),
|
|
847
|
+
inputTokens: 100,
|
|
848
|
+
outputTokens: 200,
|
|
849
|
+
turns: 3,
|
|
850
|
+
handoffArtifact: null,
|
|
851
|
+
errorMessage: null,
|
|
852
|
+
};
|
|
853
|
+
// Use enough total logs to trigger pagination (>50 per page)
|
|
854
|
+
const html = runDetailView(run, [mockStage], [], 1, 200);
|
|
855
|
+
expect(html).toContain("/runs/run%2Fspecial");
|
|
856
|
+
expect(html).not.toContain("/runs/run/special");
|
|
857
|
+
});
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
//# sourceMappingURL=ui-refresh.test.js.map
|