diff --git a/src/commands/document/document-list.ts b/src/commands/document/document-list.ts index 071cc202..0043c2c4 100644 --- a/src/commands/document/document-list.ts +++ b/src/commands/document/document-list.ts @@ -1,5 +1,6 @@ import { Command } from "@cliffy/command" import { gql } from "../../__codegen__/gql.ts" +import type { DocumentFilter } from "../../__codegen__/graphql.ts" import { getGraphQLClient } from "../../utils/graphql.ts" import { getTimeAgo, padDisplay } from "../../utils/display.ts" import { shouldShowSpinner } from "../../utils/hyperlink.ts" @@ -49,23 +50,14 @@ export const listCommand = new Command() spinner?.start() try { - // Build filter based on options - // deno-lint-ignore no-explicit-any - let filter: any = undefined - - if (project) { - filter = { - ...filter, - project: { slugId: { eq: project } }, + // Build filter based on options. Stays undefined when neither flag is + // passed so the query sends no filter at all. + const filter: DocumentFilter | undefined = project || issue + ? { + project: project ? { slugId: { eq: project } } : undefined, + issue: issue ? { id: { eq: issue.toUpperCase() } } : undefined, } - } - - if (issue) { - filter = { - ...filter, - issue: { identifier: { eq: issue.toUpperCase() } }, - } - } + : undefined const client = getGraphQLClient() const result = await client.request(ListDocuments, { diff --git a/test/commands/document/__snapshots__/document-list.test.ts.snap b/test/commands/document/__snapshots__/document-list.test.ts.snap index 8c15110d..aa44ba58 100644 --- a/test/commands/document/__snapshots__/document-list.test.ts.snap +++ b/test/commands/document/__snapshots__/document-list.test.ts.snap @@ -52,6 +52,36 @@ stderr: "" `; +snapshot[`Document List Command - Filter By Issue JSON Output 1`] = ` +stdout: +'{ + "nodes": [ + { + "id": "doc-2", + "title": "Migration Runbook", + "slugId": "a1c27f6d8e04", + "url": "https://linear.app/test/document/migration-runbook-a1c27f6d8e04", + "updatedAt": "2026-01-20T14:15:00Z", + "project": null, + "issue": { + "identifier": "TC-123", + "title": "Plan the migration" + }, + "creator": { + "name": "Jane Smith" + } + } + ], + "pageInfo": { + "hasNextPage": false, + "endCursor": null + } +} +' +stderr: +"" +`; + snapshot[`Document List Command - Empty Results 1`] = ` stdout: "No documents found. diff --git a/test/commands/document/document-list.test.ts b/test/commands/document/document-list.test.ts index 986afe3a..39949b1c 100644 --- a/test/commands/document/document-list.test.ts +++ b/test/commands/document/document-list.test.ts @@ -15,10 +15,12 @@ await snapshotTest({ }, }) -// NOTE: Tests for "List All Documents", "Filter By Project", and "Filter By Issue" -// have been removed because they display relative timestamps (e.g., "3 days ago") -// which are inherently non-deterministic. The fakeTime solution causes hangs with -// mock servers (see project-list.test.ts for similar issue). +// NOTE: The human-readable table tests for "List All Documents", "Filter By Project", +// and "Filter By Issue" have been removed because they display relative timestamps +// (e.g., "3 days ago") which are inherently non-deterministic. The fakeTime solution +// causes hangs with mock servers (see project-list.test.ts for similar issue). +// Issue filtering is covered below via the --json path, which prints raw timestamps +// and is therefore deterministic. // Test JSON output (uses raw timestamps, not relative - deterministic) await snapshotTest({ @@ -69,6 +71,61 @@ await snapshotTest({ }, }) +// Regression test: --issue must filter on IssueFilter.id, not a non-existent +// `identifier` field. The mock declares the exact request variables, so a wrong +// filter shape matches no mock, falls through to the NO_MOCK_CONFIGURED error and +// fails the test rather than quietly producing different output. +await snapshotTest({ + name: "Document List Command - Filter By Issue JSON Output", + meta: import.meta, + colors: false, + args: ["--issue", "TC-123", "--json"], + denoArgs: commonDenoArgs, + async fn() { + const server = new MockLinearServer([ + { + queryName: "ListDocuments", + variables: { + filter: { issue: { id: { eq: "TC-123" } } }, + first: 50, + }, + response: { + data: { + documents: { + nodes: [ + { + id: "doc-2", + title: "Migration Runbook", + slugId: "a1c27f6d8e04", + url: + "https://linear.app/test/document/migration-runbook-a1c27f6d8e04", + updatedAt: "2026-01-20T14:15:00Z", + project: null, + issue: { identifier: "TC-123", title: "Plan the migration" }, + creator: { name: "Jane Smith" }, + }, + ], + pageInfo: { hasNextPage: false, endCursor: null }, + }, + }, + }, + }, + ]) + + try { + await server.start() + Deno.env.set("LINEAR_GRAPHQL_ENDPOINT", server.getEndpoint()) + Deno.env.set("LINEAR_API_KEY", "Bearer test-token") + + await listCommand.parse() + } finally { + await server.stop() + Deno.env.delete("LINEAR_GRAPHQL_ENDPOINT") + Deno.env.delete("LINEAR_API_KEY") + } + }, +}) + // Test empty results await snapshotTest({ name: "Document List Command - Empty Results",