{"version":3,"file":"sidebar-todo-panel.test.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/sidebar-todo-panel.test.ts"],"names":[],"mappings":"","sourcesContent":["import { beforeAll, describe, expect, it } from \"vitest\";\nimport { initTheme } from \"../theme/theme.js\";\nimport { buildSidebarTaskData, buildSidebarTodoData, SidebarTodoPanel } from \"./sidebar-todo-panel.js\";\n\nbeforeAll(() => {\n\tinitTheme(\"dark\");\n});\n\n/**\n * These tests cover the todo list component that is mounted directly\n * beneath the Working Context panel in interactive mode. They exercise\n * both the pure data mapping from `session.getTodos()` and the renderer.\n */\ndescribe(\"SidebarTodoPanel\", () => {\n\tit(\"renders nothing when the panel has no data (empty state)\", () => {\n\t\tconst panel = new SidebarTodoPanel();\n\t\texpect(panel.render(80)).toEqual([]);\n\t});\n\n\tit(\"renders nothing after clear() (todos removed)\", () => {\n\t\tconst panel = new SidebarTodoPanel();\n\t\tpanel.update({\n\t\t\titems: [{ id: \"todo-0\", content: \"Ship it\", status: \"pending\" }],\n\t\t\ttotalCount: 1,\n\t\t\tcompletedCount: 0,\n\t\t});\n\t\tpanel.clear();\n\t\texpect(panel.render(80)).toEqual([]);\n\t});\n\n\tit(\"renders each status (pending / in_progress / completed) with distinct markers\", () => {\n\t\tconst panel = new SidebarTodoPanel();\n\t\tpanel.update({\n\t\t\titems: [\n\t\t\t\t{ id: \"todo-0\", content: \"First task\", activeForm: \"Working on first task\", status: \"in_progress\" },\n\t\t\t\t{ id: \"todo-1\", content: \"Second task\", activeForm: \"Working on second task\", status: \"pending\" },\n\t\t\t\t{ id: \"todo-2\", content: \"Third task\", activeForm: \"Working on third task\", status: \"completed\" },\n\t\t\t],\n\t\t\ttotalCount: 3,\n\t\t\tcompletedCount: 1,\n\t\t});\n\n\t\tconst lines = panel.render(80);\n\t\t// Header + 3 items\n\t\texpect(lines.length).toBeGreaterThanOrEqual(4);\n\t\tconst joined = lines.join(\"\\n\");\n\t\t// Header shows progress count with the default \"Todos\" label\n\t\texpect(joined).toContain(\"Todos\");\n\t\texpect(joined).toContain(\"1/3\");\n\t\t// Status icons from sidebar-todo-panel\n\t\texpect(joined).toContain(\"[-]\"); // in_progress\n\t\texpect(joined).toContain(\"[ ]\"); // pending\n\t\texpect(joined).toContain(\"[x]\"); // completed\n\t\t// in_progress renders activeForm at wider widths; pending/completed render content\n\t\texpect(joined).toContain(\"Working on first task\");\n\t\texpect(joined).toContain(\"Second task\");\n\t\texpect(joined).toContain(\"Third task\");\n\t});\n\n\tit(\"sorts in_progress above pending above completed\", () => {\n\t\tconst panel = new SidebarTodoPanel();\n\t\tpanel.update({\n\t\t\titems: [\n\t\t\t\t{ id: \"a\", content: \"DONE ITEM\", status: \"completed\" },\n\t\t\t\t{ id: \"b\", content: \"PENDING ITEM\", status: \"pending\" },\n\t\t\t\t{ id: \"c\", content: \"ACTIVE ITEM\", activeForm: \"Doing active thing\", status: \"in_progress\" },\n\t\t\t],\n\t\t\ttotalCount: 3,\n\t\t\tcompletedCount: 1,\n\t\t});\n\n\t\tconst lines = panel.render(80);\n\t\t// Skip leading blank spacer line; join rest for ordering checks\n\t\tconst body = lines.slice(1).join(\"\\n\");\n\t\tconst activeIdx = body.indexOf(\"Doing active thing\");\n\t\tconst pendingIdx = body.indexOf(\"PENDING ITEM\");\n\t\tconst doneIdx = body.indexOf(\"DONE ITEM\");\n\t\texpect(activeIdx).toBeGreaterThanOrEqual(0);\n\t\texpect(pendingIdx).toBeGreaterThan(activeIdx);\n\t\texpect(doneIdx).toBeGreaterThan(pendingIdx);\n\t});\n});\n\ndescribe(\"buildSidebarTaskData\", () => {\n\tit(\"returns undefined for an empty task list\", () => {\n\t\texpect(buildSidebarTaskData([])).toBeUndefined();\n\t});\n\n\tit(\"maps session.getTasks() output (id/subject/activeForm/status) to SidebarTodoData\", () => {\n\t\tconst data = buildSidebarTaskData([\n\t\t\t{ id: \"task_1\", subject: \"Fix the bug\", activeForm: \"Fixing the bug\", status: \"in_progress\" },\n\t\t\t{ id: \"task_2\", subject: \"Write docs\", status: \"pending\" },\n\t\t\t{ id: \"task_3\", subject: \"Ship it\", activeForm: \"Shipping\", status: \"completed\" },\n\t\t]);\n\n\t\texpect(data).toBeDefined();\n\t\tif (!data) return;\n\t\texpect(data.totalCount).toBe(3);\n\t\texpect(data.completedCount).toBe(1);\n\t\texpect(data.items).toHaveLength(3);\n\t\t// Subjects become the panel's content field so the existing renderer works unchanged\n\t\texpect(data.items[0]).toMatchObject({\n\t\t\tid: \"task_1\",\n\t\t\tcontent: \"Fix the bug\",\n\t\t\tactiveForm: \"Fixing the bug\",\n\t\t\tstatus: \"in_progress\",\n\t\t});\n\t\t// Preserves original task id (not synthetic index-based id)\n\t\texpect(data.items[1].id).toBe(\"task_2\");\n\t\texpect(data.current?.content).toBe(\"Fix the bug\");\n\t});\n\n\tit(\"renders through the panel end-to-end from session-shaped task data under a 'Tasks' header\", () => {\n\t\tconst panel = new SidebarTodoPanel({ title: \"Tasks\" });\n\t\tconst data = buildSidebarTaskData([\n\t\t\t{ id: \"task_1\", subject: \"Fix the bug\", activeForm: \"Fixing the bug\", status: \"in_progress\" },\n\t\t\t{ id: \"task_2\", subject: \"Write docs\", status: \"pending\" },\n\t\t]);\n\t\tif (!data) throw new Error(\"expected data\");\n\t\tpanel.update(data);\n\n\t\tconst joined = panel.render(80).join(\"\\n\");\n\t\texpect(joined).toContain(\"Tasks\");\n\t\texpect(joined).toContain(\"0/2\");\n\t\texpect(joined).toContain(\"Fixing the bug\");\n\t\texpect(joined).toContain(\"Write docs\");\n\t});\n});\n\ndescribe(\"buildSidebarTodoData\", () => {\n\tit(\"returns undefined for an empty todo list\", () => {\n\t\texpect(buildSidebarTodoData([])).toBeUndefined();\n\t});\n\n\tit(\"maps session.getTodos() output to SidebarTodoData\", () => {\n\t\tconst data = buildSidebarTodoData([\n\t\t\t{ content: \"First task\", activeForm: \"Working on first task\", status: \"in_progress\" },\n\t\t\t{ content: \"Second task\", activeForm: \"Working on second task\", status: \"pending\" },\n\t\t\t{ content: \"Third task\", activeForm: \"Working on third task\", status: \"pending\" },\n\t\t]);\n\n\t\texpect(data).toBeDefined();\n\t\tif (!data) return;\n\t\texpect(data.totalCount).toBe(3);\n\t\texpect(data.completedCount).toBe(0);\n\t\texpect(data.items).toHaveLength(3);\n\t\texpect(data.items[0]).toMatchObject({\n\t\t\tcontent: \"First task\",\n\t\t\tactiveForm: \"Working on first task\",\n\t\t\tstatus: \"in_progress\",\n\t\t});\n\t\texpect(data.items[0].id).toBeTruthy();\n\t\texpect(data.current?.content).toBe(\"First task\");\n\t});\n\n\tit(\"counts completed items\", () => {\n\t\tconst data = buildSidebarTodoData([\n\t\t\t{ content: \"A\", activeForm: \"Doing A\", status: \"completed\" },\n\t\t\t{ content: \"B\", activeForm: \"Doing B\", status: \"completed\" },\n\t\t\t{ content: \"C\", activeForm: \"Doing C\", status: \"in_progress\" },\n\t\t]);\n\t\texpect(data?.completedCount).toBe(2);\n\t\texpect(data?.totalCount).toBe(3);\n\t});\n\n\tit(\"renders with a custom title when configured (e.g. 'Tasks' vs 'Todos')\", () => {\n\t\tconst todoPanel = new SidebarTodoPanel({ title: \"Todos\" });\n\t\tconst taskPanel = new SidebarTodoPanel({ title: \"Tasks\" });\n\t\tconst data = {\n\t\t\titems: [{ id: \"x\", content: \"something\", status: \"pending\" as const }],\n\t\t\ttotalCount: 1,\n\t\t\tcompletedCount: 0,\n\t\t};\n\t\ttodoPanel.update(data);\n\t\ttaskPanel.update(data);\n\n\t\tconst todoJoined = todoPanel.render(80).join(\"\\n\");\n\t\tconst taskJoined = taskPanel.render(80).join(\"\\n\");\n\t\texpect(todoJoined).toContain(\"Todos\");\n\t\texpect(todoJoined).not.toContain(\"Tasks\");\n\t\texpect(taskJoined).toContain(\"Tasks\");\n\t\texpect(taskJoined).not.toContain(\"Todos\");\n\t});\n\n\tit(\"renders through the panel end-to-end from session-shaped data\", () => {\n\t\t// Simulates the exact path: session.getTodos() -> buildSidebarTodoData() -> panel.update() -> panel.render()\n\t\tconst panel = new SidebarTodoPanel();\n\t\tconst data = buildSidebarTodoData([\n\t\t\t{ content: \"First task\", activeForm: \"Working on first task\", status: \"in_progress\" },\n\t\t\t{ content: \"Second task\", activeForm: \"Working on second task\", status: \"pending\" },\n\t\t\t{ content: \"Third task\", activeForm: \"Working on third task\", status: \"pending\" },\n\t\t]);\n\t\tif (!data) throw new Error(\"expected data\");\n\t\tpanel.update(data);\n\n\t\tconst lines = panel.render(80);\n\t\texpect(lines.length).toBeGreaterThan(0);\n\t\tconst joined = lines.join(\"\\n\");\n\t\texpect(joined).toContain(\"0/3\");\n\t\texpect(joined).toContain(\"Working on first task\");\n\t\texpect(joined).toContain(\"Second task\");\n\t\texpect(joined).toContain(\"Third task\");\n\t});\n});\n"]}