blob: 1671a1cf4231d84f42d82bbf9b2a10ac48f3dc68 [file] [log] [blame]
import { test, expect } from "@sand4rt/experimental-ct-web";
import { SketchTodoPanel } from "./sketch-todo-panel";
import { TodoItem } from "../types";
// Helper function to create mock todo items
function createMockTodoItem(props: Partial<TodoItem> = {}): TodoItem {
return {
id: props.id || "task-1",
status: props.status || "queued",
task: props.task || "Sample task description",
...props,
};
}
test("initializes with default properties", async ({ mount }) => {
const component = await mount(SketchTodoPanel, {});
// Check default properties
const visible = await component.evaluate((el: SketchTodoPanel) => el.visible);
expect(visible).toBe(false);
// When not visible, component should not render content
const content = await component.textContent();
expect(content?.trim()).toBe("");
});
test("displays empty state when visible but no data", async ({ mount }) => {
const component = await mount(SketchTodoPanel, {
props: {
visible: true,
},
});
// Should show empty state message
await expect(component).toContainText("No todos available");
});
test("displays loading state correctly", async ({ mount }) => {
const component = await mount(SketchTodoPanel, {
props: {
visible: true,
},
});
// Set loading state through component method
await component.evaluate((el: SketchTodoPanel) => {
(el as any).loading = true;
});
// Should show loading message and spinner
await expect(component).toContainText("Loading todos...");
// Check for spinner element (it has animate-spin class)
await expect(component.locator(".animate-spin")).toBeVisible();
});
test("displays error state correctly", async ({ mount }) => {
const component = await mount(SketchTodoPanel, {
props: {
visible: true,
},
});
// Set error state through component method
await component.evaluate((el: SketchTodoPanel) => {
(el as any).error = "Failed to load todo data";
});
// Should show error message
await expect(component).toContainText("Error: Failed to load todo data");
// Error text should have red color
await expect(component.locator(".text-red-600")).toBeVisible();
});
test("renders todo items correctly", async ({ mount }) => {
const mockTodos = [
createMockTodoItem({
id: "task-1",
status: "completed",
task: "Complete the first task",
}),
createMockTodoItem({
id: "task-2",
status: "in-progress",
task: "Work on the second task",
}),
createMockTodoItem({
id: "task-3",
status: "queued",
task: "Start the third task",
}),
];
const component = await mount(SketchTodoPanel, {
props: {
visible: true,
},
});
// Update component with todo data
await component.evaluate((el: SketchTodoPanel, todos) => {
el.updateTodoContent(JSON.stringify({ items: todos }));
}, mockTodos);
// Check that all tasks are rendered
await expect(component).toContainText("Complete the first task");
await expect(component).toContainText("Work on the second task");
await expect(component).toContainText("Start the third task");
// Check that status icons are present (emojis)
await expect(component).toContainText("✅"); // completed
await expect(component).toContainText("🦉"); // in-progress
await expect(component).toContainText("⚪"); // queued
});
test("displays correct todo count in header", async ({ mount }) => {
const mockTodos = [
createMockTodoItem({ id: "task-1", status: "completed" }),
createMockTodoItem({ id: "task-2", status: "completed" }),
createMockTodoItem({ id: "task-3", status: "in-progress" }),
createMockTodoItem({ id: "task-4", status: "queued" }),
];
const component = await mount(SketchTodoPanel, {
props: {
visible: true,
},
});
await component.evaluate((el: SketchTodoPanel, todos) => {
el.updateTodoContent(JSON.stringify({ items: todos }));
}, mockTodos);
// Should show "2/4" (2 completed out of 4 total)
await expect(component).toContainText("2/4");
await expect(component).toContainText("Sketching...");
});
test("shows comment button only for non-completed items", async ({ mount }) => {
const mockTodos = [
createMockTodoItem({ id: "task-1", status: "completed" }),
createMockTodoItem({ id: "task-2", status: "in-progress" }),
createMockTodoItem({ id: "task-3", status: "queued" }),
];
const component = await mount(SketchTodoPanel, {
props: {
visible: true,
},
});
await component.evaluate((el: SketchTodoPanel, todos) => {
el.updateTodoContent(JSON.stringify({ items: todos }));
}, mockTodos);
// Comment buttons (💬) should only appear for in-progress and queued items
const commentButtons = component.locator('button[title*="Add comment"]');
await expect(commentButtons).toHaveCount(2); // Only for in-progress and queued
});
test("opens comment box when comment button is clicked", async ({ mount }) => {
const mockTodos = [
createMockTodoItem({
id: "task-1",
status: "in-progress",
task: "Work on important task",
}),
];
const component = await mount(SketchTodoPanel, {
props: {
visible: true,
},
});
await component.evaluate((el: SketchTodoPanel, todos) => {
el.updateTodoContent(JSON.stringify({ items: todos }));
}, mockTodos);
// Click the comment button
await component.locator('button[title*="Add comment"]').click();
// Comment overlay should be visible
await expect(component.locator(".fixed.inset-0")).toBeVisible();
await expect(component).toContainText("Comment on TODO Item");
await expect(component).toContainText("Status: In Progress");
await expect(component).toContainText("Work on important task");
});
test("closes comment box when cancel is clicked", async ({ mount }) => {
const mockTodos = [createMockTodoItem({ id: "task-1", status: "queued" })];
const component = await mount(SketchTodoPanel, {
props: {
visible: true,
},
});
await component.evaluate((el: SketchTodoPanel, todos) => {
el.updateTodoContent(JSON.stringify({ items: todos }));
}, mockTodos);
// Open comment box
await component.locator('button[title*="Add comment"]').click();
await expect(component.locator(".fixed.inset-0")).toBeVisible();
// Click cancel
await component.locator('button:has-text("Cancel")').click();
// Comment overlay should be hidden
await expect(component.locator(".fixed.inset-0")).not.toBeVisible();
});
test("dispatches todo-comment event when comment is submitted", async ({
mount,
}) => {
const mockTodos = [
createMockTodoItem({
id: "task-1",
status: "in-progress",
task: "Important task",
}),
];
const component = await mount(SketchTodoPanel, {
props: {
visible: true,
},
});
await component.evaluate((el: SketchTodoPanel, todos) => {
el.updateTodoContent(JSON.stringify({ items: todos }));
}, mockTodos);
// Set up event listener
const eventPromise = component.evaluate((el: SketchTodoPanel) => {
return new Promise((resolve) => {
(el as any).addEventListener(
"todo-comment",
(e: any) => {
resolve(e.detail.comment);
},
{ once: true },
);
});
});
// Open comment box
await component.locator('button[title*="Add comment"]').click();
// Fill in comment
await component
.locator('textarea[placeholder*="Type your comment"]')
.fill("This is a test comment");
// Submit comment
await component.locator('button:has-text("Add Comment")').click();
// Wait for event and check content
const eventDetail = await eventPromise;
expect(eventDetail).toContain("This is a test comment");
expect(eventDetail).toContain("Important task");
expect(eventDetail).toContain("In Progress");
// Comment box should be closed after submission
await expect(component.locator(".fixed.inset-0")).not.toBeVisible();
});
test("handles invalid JSON gracefully", async ({ mount }) => {
const component = await mount(SketchTodoPanel, {
props: {
visible: true,
},
});
// Update with invalid JSON
await component.evaluate((el: SketchTodoPanel) => {
el.updateTodoContent("{invalid json}");
});
// Should show error state
await expect(component).toContainText("Error: Failed to parse todo data");
});
test("handles empty content gracefully", async ({ mount }) => {
const component = await mount(SketchTodoPanel, {
props: {
visible: true,
},
});
// Update with empty content
await component.evaluate((el: SketchTodoPanel) => {
el.updateTodoContent("");
});
// Should show empty state
await expect(component).toContainText("No todos available");
});
test("renders with proper Tailwind classes", async ({ mount }) => {
const component = await mount(SketchTodoPanel, {
props: {
visible: true,
},
});
// Check main container has correct Tailwind classes
const container = component.locator(".flex.flex-col.h-full");
await expect(container).toBeVisible();
});
test("displays todos in scrollable container", async ({ mount }) => {
const mockTodos = Array.from({ length: 10 }, (_, i) =>
createMockTodoItem({
id: `task-${i + 1}`,
status:
i % 3 === 0 ? "completed" : i % 3 === 1 ? "in-progress" : "queued",
task: `Task number ${i + 1} with some description text`,
}),
);
const component = await mount(SketchTodoPanel, {
props: {
visible: true,
},
});
await component.evaluate((el: SketchTodoPanel, todos) => {
el.updateTodoContent(JSON.stringify({ items: todos }));
}, mockTodos);
// Check that scrollable container exists
const scrollContainer = component.locator(".overflow-y-auto");
await expect(scrollContainer).toBeVisible();
// All tasks should be rendered
for (let i = 1; i <= 10; i++) {
await expect(component).toContainText(`Task number ${i}`);
}
});