blob: 1671a1cf4231d84f42d82bbf9b2a10ac48f3dc68 [file] [log] [blame]
bankseancdb08a52025-07-02 20:28:29 +00001import { test, expect } from "@sand4rt/experimental-ct-web";
2import { SketchTodoPanel } from "./sketch-todo-panel";
3import { TodoItem } from "../types";
4
5// Helper function to create mock todo items
6function createMockTodoItem(props: Partial<TodoItem> = {}): TodoItem {
7 return {
8 id: props.id || "task-1",
9 status: props.status || "queued",
10 task: props.task || "Sample task description",
11 ...props,
12 };
13}
14
15test("initializes with default properties", async ({ mount }) => {
16 const component = await mount(SketchTodoPanel, {});
17
18 // Check default properties
19 const visible = await component.evaluate((el: SketchTodoPanel) => el.visible);
20 expect(visible).toBe(false);
21
22 // When not visible, component should not render content
23 const content = await component.textContent();
24 expect(content?.trim()).toBe("");
25});
26
27test("displays empty state when visible but no data", async ({ mount }) => {
28 const component = await mount(SketchTodoPanel, {
29 props: {
30 visible: true,
31 },
32 });
33
34 // Should show empty state message
35 await expect(component).toContainText("No todos available");
36});
37
38test("displays loading state correctly", async ({ mount }) => {
39 const component = await mount(SketchTodoPanel, {
40 props: {
41 visible: true,
42 },
43 });
44
45 // Set loading state through component method
46 await component.evaluate((el: SketchTodoPanel) => {
47 (el as any).loading = true;
48 });
49
50 // Should show loading message and spinner
51 await expect(component).toContainText("Loading todos...");
52 // Check for spinner element (it has animate-spin class)
53 await expect(component.locator(".animate-spin")).toBeVisible();
54});
55
56test("displays error state correctly", async ({ mount }) => {
57 const component = await mount(SketchTodoPanel, {
58 props: {
59 visible: true,
60 },
61 });
62
63 // Set error state through component method
64 await component.evaluate((el: SketchTodoPanel) => {
65 (el as any).error = "Failed to load todo data";
66 });
67
68 // Should show error message
69 await expect(component).toContainText("Error: Failed to load todo data");
70 // Error text should have red color
71 await expect(component.locator(".text-red-600")).toBeVisible();
72});
73
74test("renders todo items correctly", async ({ mount }) => {
75 const mockTodos = [
76 createMockTodoItem({
77 id: "task-1",
78 status: "completed",
79 task: "Complete the first task",
80 }),
81 createMockTodoItem({
82 id: "task-2",
83 status: "in-progress",
84 task: "Work on the second task",
85 }),
86 createMockTodoItem({
87 id: "task-3",
88 status: "queued",
89 task: "Start the third task",
90 }),
91 ];
92
93 const component = await mount(SketchTodoPanel, {
94 props: {
95 visible: true,
96 },
97 });
98
99 // Update component with todo data
100 await component.evaluate((el: SketchTodoPanel, todos) => {
101 el.updateTodoContent(JSON.stringify({ items: todos }));
102 }, mockTodos);
103
104 // Check that all tasks are rendered
105 await expect(component).toContainText("Complete the first task");
106 await expect(component).toContainText("Work on the second task");
107 await expect(component).toContainText("Start the third task");
108
109 // Check that status icons are present (emojis)
110 await expect(component).toContainText("✅"); // completed
111 await expect(component).toContainText("🦉"); // in-progress
112 await expect(component).toContainText("⚪"); // queued
113});
114
115test("displays correct todo count in header", async ({ mount }) => {
116 const mockTodos = [
117 createMockTodoItem({ id: "task-1", status: "completed" }),
118 createMockTodoItem({ id: "task-2", status: "completed" }),
119 createMockTodoItem({ id: "task-3", status: "in-progress" }),
120 createMockTodoItem({ id: "task-4", status: "queued" }),
121 ];
122
123 const component = await mount(SketchTodoPanel, {
124 props: {
125 visible: true,
126 },
127 });
128
129 await component.evaluate((el: SketchTodoPanel, todos) => {
130 el.updateTodoContent(JSON.stringify({ items: todos }));
131 }, mockTodos);
132
133 // Should show "2/4" (2 completed out of 4 total)
134 await expect(component).toContainText("2/4");
135 await expect(component).toContainText("Sketching...");
136});
137
138test("shows comment button only for non-completed items", async ({ mount }) => {
139 const mockTodos = [
140 createMockTodoItem({ id: "task-1", status: "completed" }),
141 createMockTodoItem({ id: "task-2", status: "in-progress" }),
142 createMockTodoItem({ id: "task-3", status: "queued" }),
143 ];
144
145 const component = await mount(SketchTodoPanel, {
146 props: {
147 visible: true,
148 },
149 });
150
151 await component.evaluate((el: SketchTodoPanel, todos) => {
152 el.updateTodoContent(JSON.stringify({ items: todos }));
153 }, mockTodos);
154
155 // Comment buttons (💬) should only appear for in-progress and queued items
156 const commentButtons = component.locator('button[title*="Add comment"]');
157 await expect(commentButtons).toHaveCount(2); // Only for in-progress and queued
158});
159
160test("opens comment box when comment button is clicked", async ({ mount }) => {
161 const mockTodos = [
162 createMockTodoItem({
163 id: "task-1",
164 status: "in-progress",
165 task: "Work on important task",
166 }),
167 ];
168
169 const component = await mount(SketchTodoPanel, {
170 props: {
171 visible: true,
172 },
173 });
174
175 await component.evaluate((el: SketchTodoPanel, todos) => {
176 el.updateTodoContent(JSON.stringify({ items: todos }));
177 }, mockTodos);
178
179 // Click the comment button
180 await component.locator('button[title*="Add comment"]').click();
181
182 // Comment overlay should be visible
183 await expect(component.locator(".fixed.inset-0")).toBeVisible();
184 await expect(component).toContainText("Comment on TODO Item");
185 await expect(component).toContainText("Status: In Progress");
186 await expect(component).toContainText("Work on important task");
187});
188
189test("closes comment box when cancel is clicked", async ({ mount }) => {
190 const mockTodos = [createMockTodoItem({ id: "task-1", status: "queued" })];
191
192 const component = await mount(SketchTodoPanel, {
193 props: {
194 visible: true,
195 },
196 });
197
198 await component.evaluate((el: SketchTodoPanel, todos) => {
199 el.updateTodoContent(JSON.stringify({ items: todos }));
200 }, mockTodos);
201
202 // Open comment box
203 await component.locator('button[title*="Add comment"]').click();
204 await expect(component.locator(".fixed.inset-0")).toBeVisible();
205
206 // Click cancel
207 await component.locator('button:has-text("Cancel")').click();
208
209 // Comment overlay should be hidden
210 await expect(component.locator(".fixed.inset-0")).not.toBeVisible();
211});
212
213test("dispatches todo-comment event when comment is submitted", async ({
214 mount,
215}) => {
216 const mockTodos = [
217 createMockTodoItem({
218 id: "task-1",
219 status: "in-progress",
220 task: "Important task",
221 }),
222 ];
223
224 const component = await mount(SketchTodoPanel, {
225 props: {
226 visible: true,
227 },
228 });
229
230 await component.evaluate((el: SketchTodoPanel, todos) => {
231 el.updateTodoContent(JSON.stringify({ items: todos }));
232 }, mockTodos);
233
234 // Set up event listener
235 const eventPromise = component.evaluate((el: SketchTodoPanel) => {
236 return new Promise((resolve) => {
237 (el as any).addEventListener(
238 "todo-comment",
239 (e: any) => {
240 resolve(e.detail.comment);
241 },
242 { once: true },
243 );
244 });
245 });
246
247 // Open comment box
248 await component.locator('button[title*="Add comment"]').click();
249
250 // Fill in comment
251 await component
252 .locator('textarea[placeholder*="Type your comment"]')
253 .fill("This is a test comment");
254
255 // Submit comment
256 await component.locator('button:has-text("Add Comment")').click();
257
258 // Wait for event and check content
259 const eventDetail = await eventPromise;
260 expect(eventDetail).toContain("This is a test comment");
261 expect(eventDetail).toContain("Important task");
262 expect(eventDetail).toContain("In Progress");
263
264 // Comment box should be closed after submission
265 await expect(component.locator(".fixed.inset-0")).not.toBeVisible();
266});
267
268test("handles invalid JSON gracefully", async ({ mount }) => {
269 const component = await mount(SketchTodoPanel, {
270 props: {
271 visible: true,
272 },
273 });
274
275 // Update with invalid JSON
276 await component.evaluate((el: SketchTodoPanel) => {
277 el.updateTodoContent("{invalid json}");
278 });
279
280 // Should show error state
281 await expect(component).toContainText("Error: Failed to parse todo data");
282});
283
284test("handles empty content gracefully", async ({ mount }) => {
285 const component = await mount(SketchTodoPanel, {
286 props: {
287 visible: true,
288 },
289 });
290
291 // Update with empty content
292 await component.evaluate((el: SketchTodoPanel) => {
293 el.updateTodoContent("");
294 });
295
296 // Should show empty state
297 await expect(component).toContainText("No todos available");
298});
299
300test("renders with proper Tailwind classes", async ({ mount }) => {
301 const component = await mount(SketchTodoPanel, {
302 props: {
303 visible: true,
304 },
305 });
306
307 // Check main container has correct Tailwind classes
308 const container = component.locator(".flex.flex-col.h-full");
309 await expect(container).toBeVisible();
bankseancdb08a52025-07-02 20:28:29 +0000310});
311
312test("displays todos in scrollable container", async ({ mount }) => {
313 const mockTodos = Array.from({ length: 10 }, (_, i) =>
314 createMockTodoItem({
315 id: `task-${i + 1}`,
316 status:
317 i % 3 === 0 ? "completed" : i % 3 === 1 ? "in-progress" : "queued",
318 task: `Task number ${i + 1} with some description text`,
319 }),
320 );
321
322 const component = await mount(SketchTodoPanel, {
323 props: {
324 visible: true,
325 },
326 });
327
328 await component.evaluate((el: SketchTodoPanel, todos) => {
329 el.updateTodoContent(JSON.stringify({ items: todos }));
330 }, mockTodos);
331
332 // Check that scrollable container exists
333 const scrollContainer = component.locator(".overflow-y-auto");
334 await expect(scrollContainer).toBeVisible();
335
336 // All tasks should be rendered
337 for (let i = 1; i <= 10; i++) {
338 await expect(component).toContainText(`Task number ${i}`);
339 }
340});