| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- /**
- * formatter.test.ts - Unit tests verifying context is shown in all output formats
- *
- * Run with: bun test formatter.test.ts
- */
- import { describe, test, expect } from "vitest";
- import {
- // Search result formatters
- searchResultsToJson,
- searchResultsToCsv,
- searchResultsToFiles,
- searchResultsToMarkdown,
- searchResultsToXml,
- searchResultsToMcpCsv,
- formatSearchResults,
- // Document (multi-get) formatters
- documentsToJson,
- documentsToCsv,
- documentsToFiles,
- documentsToMarkdown,
- documentsToXml,
- formatDocuments,
- // Single document formatters
- documentToJson,
- documentToMarkdown,
- documentToXml,
- formatDocument,
- type MultiGetFile,
- } from "../src/cli/formatter.js";
- import type { SearchResult, DocumentResult } from "../src/store.js";
- // =============================================================================
- // Test Fixtures
- // =============================================================================
- const TEST_CONTEXT = "Internal engineering keynotes from company summit events";
- function makeSearchResult(overrides: Partial<SearchResult> = {}): SearchResult {
- return {
- filepath: "qmd://archive/summit/keynote.md",
- displayPath: "qmd://archive/summit/keynote.md",
- title: "Summit Keynote",
- context: TEST_CONTEXT,
- hash: "dc5590abcdef",
- docid: "dc5590",
- collectionName: "archive",
- modifiedAt: "2024-01-01T00:00:00Z",
- bodyLength: 100,
- body: "---\ntitle: Summit Keynote\n---\n\nThis is the keynote content.",
- score: 0.84,
- source: "fts",
- ...overrides,
- };
- }
- function makeDocumentResult(overrides: Partial<DocumentResult> = {}): DocumentResult {
- return {
- filepath: "qmd://archive/summit/keynote.md",
- displayPath: "qmd://archive/summit/keynote.md",
- title: "Summit Keynote",
- context: TEST_CONTEXT,
- hash: "dc5590abcdef",
- docid: "dc5590",
- collectionName: "archive",
- modifiedAt: "2024-01-01T00:00:00Z",
- bodyLength: 100,
- body: "---\ntitle: Summit Keynote\n---\n\nThis is the keynote content.",
- ...overrides,
- };
- }
- function makeMultiGetFile(overrides: Partial<MultiGetFile & { skipped: false }> = {}): MultiGetFile {
- return {
- filepath: "qmd://archive/summit/keynote.md",
- displayPath: "qmd://archive/summit/keynote.md",
- title: "Summit Keynote",
- context: TEST_CONTEXT,
- body: "---\ntitle: Summit Keynote\n---\n\nThis is the keynote content.",
- skipped: false,
- ...overrides,
- };
- }
- // =============================================================================
- // Search Results: Context in Every Format
- // =============================================================================
- describe("search results include context in all formats", () => {
- const results = [makeSearchResult()];
- test("JSON format includes context", () => {
- const output = searchResultsToJson(results, { query: "keynote" });
- const parsed = JSON.parse(output);
- expect(parsed[0].context).toBe(TEST_CONTEXT);
- });
- test("JSON format includes line", () => {
- const output = searchResultsToJson(results, { query: "keynote" });
- const parsed = JSON.parse(output);
- expect(parsed[0].line).toBeTypeOf("number");
- expect(parsed[0].line).toBeGreaterThan(0);
- });
- test("JSON format includes line with --full", () => {
- const output = searchResultsToJson(results, { query: "keynote", full: true });
- const parsed = JSON.parse(output);
- expect(parsed[0].line).toBeTypeOf("number");
- expect(parsed[0].line).toBeGreaterThan(0);
- });
- test("CSV format includes context", () => {
- const output = searchResultsToCsv(results, { query: "keynote" });
- // Header should have context column
- const lines = output.split("\n");
- expect(lines[0]).toContain("context");
- // Data row should contain the context text
- expect(output).toContain(TEST_CONTEXT);
- });
- test("files format includes context", () => {
- const output = searchResultsToFiles(results);
- expect(output).toContain(TEST_CONTEXT);
- });
- test("Markdown format includes context", () => {
- const output = searchResultsToMarkdown(results, { query: "keynote" });
- expect(output).toContain(TEST_CONTEXT);
- });
- test("XML format includes context", () => {
- const output = searchResultsToXml(results, { query: "keynote" });
- expect(output).toContain(TEST_CONTEXT);
- });
- test("MCP CSV format includes context", () => {
- const mcpResults = [{
- docid: "dc5590",
- file: "qmd://archive/summit/keynote.md",
- title: "Summit Keynote",
- score: 0.84,
- context: TEST_CONTEXT,
- snippet: "This is the keynote content.",
- }];
- const output = searchResultsToMcpCsv(mcpResults);
- expect(output).toContain(TEST_CONTEXT);
- });
- test("formatSearchResults (JSON) includes context", () => {
- const output = formatSearchResults(results, "json", { query: "keynote" });
- const parsed = JSON.parse(output);
- expect(parsed[0].context).toBe(TEST_CONTEXT);
- });
- test("formatSearchResults (CSV) includes context", () => {
- const output = formatSearchResults(results, "csv", { query: "keynote" });
- expect(output).toContain(TEST_CONTEXT);
- });
- test("formatSearchResults (files) includes context", () => {
- const output = formatSearchResults(results, "files");
- expect(output).toContain(TEST_CONTEXT);
- });
- test("formatSearchResults (md) includes context", () => {
- const output = formatSearchResults(results, "md", { query: "keynote" });
- expect(output).toContain(TEST_CONTEXT);
- });
- test("formatSearchResults (xml) includes context", () => {
- const output = formatSearchResults(results, "xml", { query: "keynote" });
- expect(output).toContain(TEST_CONTEXT);
- });
- });
- // =============================================================================
- // Search Results: No Context When Absent
- // =============================================================================
- describe("search results omit context when null", () => {
- const results = [makeSearchResult({ context: null })];
- test("JSON format omits context field when null", () => {
- const output = searchResultsToJson(results, { query: "keynote" });
- const parsed = JSON.parse(output);
- expect(parsed[0].context).toBeUndefined();
- });
- test("files format does not include trailing context when null", () => {
- const output = searchResultsToFiles(results);
- // Should just be docid,score,path - no trailing comma/context
- expect(output).not.toContain(",\"");
- });
- });
- // =============================================================================
- // Multi-Get Documents: Context in Every Format
- // =============================================================================
- describe("multi-get documents include context in all formats", () => {
- const docs = [makeMultiGetFile()];
- test("JSON format includes context", () => {
- const output = documentsToJson(docs);
- const parsed = JSON.parse(output);
- expect(parsed[0].context).toBe(TEST_CONTEXT);
- });
- test("CSV format includes context", () => {
- const output = documentsToCsv(docs);
- const lines = output.split("\n");
- expect(lines[0]).toContain("context");
- expect(output).toContain(TEST_CONTEXT);
- });
- test("files format includes context", () => {
- const output = documentsToFiles(docs);
- expect(output).toContain(TEST_CONTEXT);
- });
- test("Markdown format includes context", () => {
- const output = documentsToMarkdown(docs);
- expect(output).toContain(TEST_CONTEXT);
- });
- test("XML format includes context", () => {
- const output = documentsToXml(docs);
- expect(output).toContain(TEST_CONTEXT);
- });
- test("formatDocuments (JSON) includes context", () => {
- const output = formatDocuments(docs, "json");
- const parsed = JSON.parse(output);
- expect(parsed[0].context).toBe(TEST_CONTEXT);
- });
- test("formatDocuments (md) includes context", () => {
- const output = formatDocuments(docs, "md");
- expect(output).toContain(TEST_CONTEXT);
- });
- test("formatDocuments (xml) includes context", () => {
- const output = formatDocuments(docs, "xml");
- expect(output).toContain(TEST_CONTEXT);
- });
- });
- // =============================================================================
- // Single Document: Context in Every Format
- // =============================================================================
- describe("single document includes context in all formats", () => {
- const doc = makeDocumentResult();
- test("JSON format includes context", () => {
- const output = documentToJson(doc);
- const parsed = JSON.parse(output);
- expect(parsed.context).toBe(TEST_CONTEXT);
- });
- test("Markdown format includes context", () => {
- const output = documentToMarkdown(doc);
- expect(output).toContain(TEST_CONTEXT);
- });
- test("XML format includes context", () => {
- const output = documentToXml(doc);
- expect(output).toContain(TEST_CONTEXT);
- });
- test("formatDocument (JSON) includes context", () => {
- const output = formatDocument(doc, "json");
- const parsed = JSON.parse(output);
- expect(parsed.context).toBe(TEST_CONTEXT);
- });
- test("formatDocument (md) includes context", () => {
- const output = formatDocument(doc, "md");
- expect(output).toContain(TEST_CONTEXT);
- });
- test("formatDocument (xml) includes context", () => {
- const output = formatDocument(doc, "xml");
- expect(output).toContain(TEST_CONTEXT);
- });
- });
- // =============================================================================
- // Single Document: No Context When Absent
- // =============================================================================
- describe("single document omits context when null", () => {
- const doc = makeDocumentResult({ context: null });
- test("JSON format omits context field when null", () => {
- const output = documentToJson(doc);
- const parsed = JSON.parse(output);
- expect(parsed.context).toBeUndefined();
- });
- test("Markdown format does not show Context line when null", () => {
- const output = documentToMarkdown(doc);
- expect(output).not.toContain("Context:");
- });
- test("XML format does not show context element when null", () => {
- const output = documentToXml(doc);
- expect(output).not.toContain("<context>");
- });
- });
|