Skip to content

Commit 344a174

Browse files
dcramerclaude
andcommitted
test(archive): Add comprehensive archive integration tests
Add tests for query integration with archives: - list --archived flag to view archived tasks with filtering - show command displaying archived task details - JSON output support for archived tasks Extract createArchivedTask helper to test-helpers.ts to reduce duplication across test files. Refs #69 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4f46e1a commit 344a174

File tree

4 files changed

+327
-23
lines changed

4 files changed

+327
-23
lines changed

src/cli/list.test.ts

Lines changed: 152 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import { runCli } from "./index.js";
44
import {
55
captureOutput,
66
createTempStorage,
7+
createArchivedTask,
78
CapturedOutput,
9+
TASK_ID_REGEX,
810
} from "./test-helpers.js";
11+
import { ArchiveStorage } from "../core/storage/archive-storage.js";
912

1013
describe("list command", () => {
1114
let storage: FileStorage;
@@ -94,7 +97,7 @@ describe("list command", () => {
9497
await runCli(["create", "-n", "Parent task", "--description", "ctx"], {
9598
storage,
9699
});
97-
const parentId = output.stdout.join("\n").match(/\b([a-z0-9]{8})\b/)?.[1];
100+
const parentId = output.stdout.join("\n").match(TASK_ID_REGEX)?.[1];
98101
expect(parentId).toBeDefined();
99102

100103
await runCli(
@@ -140,7 +143,7 @@ describe("list command", () => {
140143
it("shows full tree with 3 levels", async () => {
141144
// Create epic -> task -> subtask hierarchy
142145
await runCli(["create", "-n", "Epic", "--description", "ctx"], { storage });
143-
const epicId = output.stdout.join("\n").match(/\b([a-z0-9]{8})\b/)?.[1];
146+
const epicId = output.stdout.join("\n").match(TASK_ID_REGEX)?.[1];
144147

145148
await runCli(
146149
[
@@ -154,7 +157,7 @@ describe("list command", () => {
154157
],
155158
{ storage },
156159
);
157-
const taskId = output.stdout.join("\n").match(/\b([a-z0-9]{8})\b/)?.[1];
160+
const taskId = output.stdout.join("\n").match(TASK_ID_REGEX)?.[1];
158161

159162
await runCli(
160163
["create", "-n", "Subtask", "--description", "ctx", "--parent", taskId!],
@@ -175,7 +178,7 @@ describe("list command", () => {
175178
await runCli(["create", "-n", "Task A", "--description", "ctx"], {
176179
storage,
177180
});
178-
const blockerMatch = output.stdout.join("\n").match(/\b([a-z0-9]{8})\b/);
181+
const blockerMatch = output.stdout.join("\n").match(TASK_ID_REGEX);
179182
const blockerId = blockerMatch?.[1];
180183
expect(blockerId).toBeDefined();
181184

@@ -207,7 +210,7 @@ describe("list command", () => {
207210
await runCli(["create", "-n", "Task A", "--description", "ctx"], {
208211
storage,
209212
});
210-
const blockerMatch = output.stdout.join("\n").match(/\b([a-z0-9]{8})\b/);
213+
const blockerMatch = output.stdout.join("\n").match(TASK_ID_REGEX);
211214
const blockerId = blockerMatch?.[1];
212215

213216
// Create blocked task
@@ -237,7 +240,7 @@ describe("list command", () => {
237240
await runCli(["create", "-n", "Task A", "--description", "ctx"], {
238241
storage,
239242
});
240-
const blockerMatch = output.stdout.join("\n").match(/\b([a-z0-9]{8})\b/);
243+
const blockerMatch = output.stdout.join("\n").match(TASK_ID_REGEX);
241244
const blockerId = blockerMatch?.[1];
242245

243246
// Create blocked task
@@ -267,7 +270,7 @@ describe("list command", () => {
267270
await runCli(["create", "-n", "GitHub task", "--description", "ctx"], {
268271
storage,
269272
});
270-
const taskId = output.stdout.join("\n").match(/\b([a-z0-9]{8})\b/)?.[1];
273+
const taskId = output.stdout.join("\n").match(TASK_ID_REGEX)?.[1];
271274
expect(taskId).toBeDefined();
272275

273276
// Add GitHub metadata via store read/write
@@ -295,7 +298,7 @@ describe("list command", () => {
295298
await runCli(["create", "-n", "Parent task", "--description", "ctx"], {
296299
storage,
297300
});
298-
const parentId = output.stdout.join("\n").match(/\b([a-z0-9]{8})\b/)?.[1];
301+
const parentId = output.stdout.join("\n").match(TASK_ID_REGEX)?.[1];
299302
expect(parentId).toBeDefined();
300303

301304
// Add GitHub metadata to parent via store read/write
@@ -333,4 +336,145 @@ describe("list command", () => {
333336
// Both parent and subtask should show the GitHub indicator
334337
expect(out).toContain("[GH-456]");
335338
});
339+
340+
describe("--archived flag", () => {
341+
it("lists archived tasks with --archived flag", async () => {
342+
// Add archived task directly to archive storage
343+
const archiveStorage = new ArchiveStorage({
344+
path: storage.getIdentifier(),
345+
});
346+
const archivedTask = createArchivedTask({
347+
id: "arch1234",
348+
name: "Old completed task",
349+
result: "Was done successfully",
350+
});
351+
archiveStorage.appendArchive([archivedTask]);
352+
353+
await runCli(["list", "--archived"], { storage });
354+
355+
const out = output.stdout.join("\n");
356+
expect(out).toContain("Old completed task");
357+
expect(out).toContain("arch1234");
358+
});
359+
360+
it("shows empty state when no archived tasks", async () => {
361+
await runCli(["list", "--archived"], { storage });
362+
363+
const out = output.stdout.join("\n");
364+
expect(out).toContain("No archived tasks");
365+
});
366+
367+
it("filters archived tasks by query", async () => {
368+
const archiveStorage = new ArchiveStorage({
369+
path: storage.getIdentifier(),
370+
});
371+
archiveStorage.appendArchive([
372+
createArchivedTask({ id: "auth1234", name: "Fix authentication bug" }),
373+
createArchivedTask({ id: "feat5678", name: "Add new feature" }),
374+
]);
375+
376+
await runCli(["list", "--archived", "-q", "auth"], { storage });
377+
378+
const out = output.stdout.join("\n");
379+
expect(out).toContain("Fix authentication bug");
380+
expect(out).not.toContain("Add new feature");
381+
});
382+
383+
it("outputs archived tasks as JSON with --json flag", async () => {
384+
const archiveStorage = new ArchiveStorage({
385+
path: storage.getIdentifier(),
386+
});
387+
const archivedTask = createArchivedTask({
388+
id: "json1234",
389+
name: "JSON archived task",
390+
result: "Done",
391+
});
392+
archiveStorage.appendArchive([archivedTask]);
393+
394+
await runCli(["list", "--archived", "--json"], { storage });
395+
396+
const parsed = JSON.parse(output.stdout.join("\n"));
397+
expect(Array.isArray(parsed)).toBe(true);
398+
expect(parsed[0].name).toBe("JSON archived task");
399+
expect(parsed[0].archived_at).toBeDefined();
400+
});
401+
402+
it("does not mix active and archived tasks", async () => {
403+
// Create an active task
404+
await runCli(["create", "-n", "Active task", "--description", "ctx"], {
405+
storage,
406+
});
407+
output.stdout.length = 0;
408+
409+
// Create an archived task
410+
const archiveStorage = new ArchiveStorage({
411+
path: storage.getIdentifier(),
412+
});
413+
archiveStorage.appendArchive([
414+
createArchivedTask({ id: "arch0001", name: "Archived task" }),
415+
]);
416+
417+
// List active tasks (no --archived)
418+
await runCli(["list"], { storage });
419+
let out = output.stdout.join("\n");
420+
expect(out).toContain("Active task");
421+
expect(out).not.toContain("Archived task");
422+
423+
// List archived tasks
424+
output.stdout.length = 0;
425+
await runCli(["list", "--archived"], { storage });
426+
out = output.stdout.join("\n");
427+
expect(out).toContain("Archived task");
428+
expect(out).not.toContain("Active task");
429+
});
430+
431+
it("shows archived children count for parent tasks", async () => {
432+
const archiveStorage = new ArchiveStorage({
433+
path: storage.getIdentifier(),
434+
});
435+
archiveStorage.appendArchive([
436+
createArchivedTask({
437+
id: "parent01",
438+
name: "Epic with children",
439+
archived_children: [
440+
{
441+
id: "child-1",
442+
name: "Subtask 1",
443+
description: "",
444+
result: "Done",
445+
},
446+
{
447+
id: "child-2",
448+
name: "Subtask 2",
449+
description: "",
450+
result: "Done",
451+
},
452+
],
453+
}),
454+
]);
455+
456+
await runCli(["list", "--archived"], { storage });
457+
458+
const out = output.stdout.join("\n");
459+
expect(out).toContain("Epic with children");
460+
// Should indicate it has children
461+
expect(out).toMatch(/2\s*(subtask|children)/i);
462+
});
463+
464+
it("includes all archived tasks with --all flag", async () => {
465+
const archiveStorage = new ArchiveStorage({
466+
path: storage.getIdentifier(),
467+
});
468+
archiveStorage.appendArchive([
469+
createArchivedTask({ id: "task0001", name: "First archived" }),
470+
createArchivedTask({ id: "task0002", name: "Second archived" }),
471+
]);
472+
473+
await runCli(["list", "--archived", "--all"], { storage });
474+
475+
const out = output.stdout.join("\n");
476+
expect(out).toContain("First archived");
477+
expect(out).toContain("Second archived");
478+
});
479+
});
336480
});

src/cli/show.test.ts

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import { runCli } from "./index.js";
44
import {
55
captureOutput,
66
createTempStorage,
7+
createArchivedTask,
78
CapturedOutput,
89
TASK_ID_REGEX,
910
} from "./test-helpers.js";
11+
import { ArchiveStorage } from "../core/storage/archive-storage.js";
1012

1113
describe("show command", () => {
1214
let storage: FileStorage;
@@ -302,4 +304,154 @@ describe("show command", () => {
302304
expect(out).toContain("#99 (owner/repo)");
303305
expect(out).toContain("(via parent)");
304306
});
307+
308+
describe("archived tasks", () => {
309+
it("shows archived task when not found in active tasks", async () => {
310+
const archiveStorage = new ArchiveStorage({
311+
path: storage.getIdentifier(),
312+
});
313+
archiveStorage.appendArchive([
314+
createArchivedTask({
315+
id: "arch1234",
316+
name: "Old completed task",
317+
description: "This task was done long ago",
318+
result: "Finished with success",
319+
}),
320+
]);
321+
322+
await runCli(["show", "arch1234"], { storage });
323+
324+
const out = output.stdout.join("\n");
325+
expect(out).toContain("Old completed task");
326+
expect(out).toContain("This task was done long ago");
327+
expect(out).toContain("Finished with success");
328+
expect(out).toContain("Archived:");
329+
});
330+
331+
it("shows archived task with GitHub metadata", async () => {
332+
const archiveStorage = new ArchiveStorage({
333+
path: storage.getIdentifier(),
334+
});
335+
archiveStorage.appendArchive([
336+
createArchivedTask({
337+
id: "gh123456",
338+
name: "GitHub linked task",
339+
metadata: {
340+
github: {
341+
issueNumber: 42,
342+
issueUrl: "https://github.com/owner/repo/issues/42",
343+
repo: "owner/repo",
344+
},
345+
},
346+
}),
347+
]);
348+
349+
await runCli(["show", "gh123456"], { storage });
350+
351+
const out = output.stdout.join("\n");
352+
expect(out).toContain("GitHub Issue:");
353+
expect(out).toContain("#42");
354+
expect(out).toContain("owner/repo");
355+
});
356+
357+
it("shows archived task with archived children", async () => {
358+
const archiveStorage = new ArchiveStorage({
359+
path: storage.getIdentifier(),
360+
});
361+
archiveStorage.appendArchive([
362+
createArchivedTask({
363+
id: "parent01",
364+
name: "Parent task",
365+
archived_children: [
366+
{
367+
id: "child-1",
368+
name: "First subtask",
369+
description: "",
370+
result: "Done",
371+
},
372+
{
373+
id: "child-2",
374+
name: "Second subtask",
375+
description: "",
376+
result: "Also done",
377+
},
378+
],
379+
}),
380+
]);
381+
382+
await runCli(["show", "parent01"], { storage });
383+
384+
const out = output.stdout.join("\n");
385+
expect(out).toContain("Parent task");
386+
expect(out).toContain("Archived Subtasks:");
387+
expect(out).toContain("First subtask");
388+
expect(out).toContain("Second subtask");
389+
});
390+
391+
it("outputs archived task as JSON with --json flag", async () => {
392+
const archiveStorage = new ArchiveStorage({
393+
path: storage.getIdentifier(),
394+
});
395+
archiveStorage.appendArchive([
396+
createArchivedTask({
397+
id: "json1234",
398+
name: "JSON archived task",
399+
}),
400+
]);
401+
402+
await runCli(["show", "json1234", "--json"], { storage });
403+
404+
const parsed = JSON.parse(output.stdout.join("\n"));
405+
expect(parsed.id).toBe("json1234");
406+
expect(parsed.name).toBe("JSON archived task");
407+
expect(parsed.archived).toBe(true);
408+
expect(parsed.archived_at).toBeDefined();
409+
});
410+
411+
it("shows full archived task details with --full flag", async () => {
412+
const archiveStorage = new ArchiveStorage({
413+
path: storage.getIdentifier(),
414+
});
415+
archiveStorage.appendArchive([
416+
createArchivedTask({
417+
id: "full1234",
418+
name: "Full details task",
419+
description:
420+
"This is a very long description that might be truncated in normal view but should show in full with the --full flag",
421+
}),
422+
]);
423+
424+
await runCli(["show", "full1234", "--full"], { storage });
425+
426+
const out = output.stdout.join("\n");
427+
expect(out).toContain("Full details task");
428+
expect(out).toContain("should show in full");
429+
});
430+
431+
it("prefers active task over archived task with same ID", async () => {
432+
// Create active task
433+
await runCli(["create", "-n", "Active version", "--description", "ctx"], {
434+
storage,
435+
});
436+
const taskId = output.stdout.join("\n").match(TASK_ID_REGEX)?.[1];
437+
output.stdout.length = 0;
438+
439+
// Create archived task with same ID (edge case)
440+
const archiveStorage = new ArchiveStorage({
441+
path: storage.getIdentifier(),
442+
});
443+
archiveStorage.appendArchive([
444+
createArchivedTask({
445+
id: taskId!,
446+
name: "Archived version",
447+
}),
448+
]);
449+
450+
await runCli(["show", taskId!], { storage });
451+
452+
const out = output.stdout.join("\n");
453+
expect(out).toContain("Active version");
454+
expect(out).not.toContain("Archived version");
455+
});
456+
});
305457
});

0 commit comments

Comments
 (0)