From 2bb0ad9a40a35086e1181da6359676b3376fb611 Mon Sep 17 00:00:00 2001 From: Eric Liu Date: Sat, 8 Feb 2025 10:55:01 -0800 Subject: [PATCH] test: add more coverage (#158) --- tests/ComponentParser.test.ts | 148 +++++++++++++++++++++++++++++++++ tests/Writer.test.ts | 9 ++ tests/WriterMarkdown.test.ts | 80 +++++++++++++++--- tests/get-svelte-entry.test.ts | 92 ++++++++++++++++++++ 4 files changed, 316 insertions(+), 13 deletions(-) create mode 100644 tests/ComponentParser.test.ts create mode 100644 tests/get-svelte-entry.test.ts diff --git a/tests/ComponentParser.test.ts b/tests/ComponentParser.test.ts new file mode 100644 index 00000000..95fbb337 --- /dev/null +++ b/tests/ComponentParser.test.ts @@ -0,0 +1,148 @@ +import ComponentParser from "../src/ComponentParser"; + +describe("ComponentParser", () => { + const diagnostics = { + moduleName: "TestComponent", + filePath: "/test/TestComponent.svelte", + }; + + test("parses basic component exports", () => { + const parser = new ComponentParser(); + const source = ` + + + + `; + + const result = parser.parseSvelteComponent(source, diagnostics); + expect(result.props).toHaveLength(2); + const propNames = result.props.map((p) => p.name); + expect(propNames).toContain("label"); + expect(propNames).toContain("disabled"); + }); + + test("parses component with multiple exports", () => { + const parser = new ComponentParser(); + const source = ` + + + + `; + + const result = parser.parseSvelteComponent(source, diagnostics); + expect(result.props).toHaveLength(2); + expect(result.props[0].name).toBe("size"); + expect(result.props[1].name).toBe("buttonSizes"); + }); + + test("parses component with JSDoc comments", () => { + const parser = new ComponentParser(); + const source = ` + + + + `; + + const result = parser.parseSvelteComponent(source, diagnostics); + expect(result.props).toHaveLength(2); + + const labelProp = result.props.find((p) => p.name === "label"); + expect(labelProp?.description).toBe("The text to display in the button"); + + const disabledProp = result.props.find((p) => p.name === "disabled"); + expect(disabledProp?.description).toBe("Controls the disabled state"); + }); + + test("handles slots and slot props", () => { + const parser = new ComponentParser(); + const source = ` + + +
+ + + +
+ `; + + const result = parser.parseSvelteComponent(source, diagnostics); + expect(result.slots).toHaveLength(3); + + const defaultSlot = result.slots.find((s) => s.default); + expect(defaultSlot).toBeTruthy(); + + const headerSlot = result.slots.find((s) => s.name === "header"); + expect(headerSlot?.slot_props).toContain("items"); + + const footerSlot = result.slots.find((s) => s.name === "footer"); + expect(footerSlot?.slot_props).toContain("count"); + }); + + test("handles dispatched events", () => { + const parser = new ComponentParser(); + const source = ` + + + + `; + + const result = parser.parseSvelteComponent(source, diagnostics); + expect(result.events).toHaveLength(1); + expect(result.events[0].type).toBe("dispatched"); + expect(result.events[0].name).toBe("click"); + }); + + test("handles component comments", () => { + const parser = new ComponentParser(); + const source = ` + + + + + `; + + const result = parser.parseSvelteComponent(source, diagnostics); + expect(result.componentComment).toContain("A button component with customizable styles"); + }); + + test("throws an error for malformed source code", () => { + const parser = new ComponentParser(); + const invalidSource = ` + + `; + + expect(() => parser.parseSvelteComponent(invalidSource, diagnostics)).toThrow(); + }); +}); diff --git a/tests/Writer.test.ts b/tests/Writer.test.ts index a2f5fc9b..9aa2ca42 100644 --- a/tests/Writer.test.ts +++ b/tests/Writer.test.ts @@ -35,4 +35,13 @@ describe("Writer", () => { expect(await writer.format("## text")).toEqual("## text\n"); expect(await writer.format({ a: null })).toEqual({ a: null }); }); + + test("format handles non-string inputs", async () => { + const writer = new Writer({ parser: "json", printWidth: 80 }); + + expect(await writer.format({ complex: "object" })).toEqual({ complex: "object" }); + expect(await writer.format(123)).toEqual(123); + expect(await writer.format(null)).toEqual(null); + expect(await writer.format(undefined)).toEqual(undefined); + }); }); diff --git a/tests/WriterMarkdown.test.ts b/tests/WriterMarkdown.test.ts index fa24d37a..f0754420 100644 --- a/tests/WriterMarkdown.test.ts +++ b/tests/WriterMarkdown.test.ts @@ -1,21 +1,75 @@ -import WriterMarkdown from "../src/writer/WriterMarkdown"; import type { AppendType } from "../src/writer/WriterMarkdown"; +import WriterMarkdown from "../src/writer/WriterMarkdown"; + +describe("WriterMarkdown", () => { + test("basic functionality", () => { + const types: AppendType[] = []; + const document = new WriterMarkdown({ + onAppend: (type) => types.push(type), + }); + + document.append("h1", "Component Index"); + document.append("h2", "Components").tableOfContents(); + document.append("divider"); + + expect(document.end()).toEqual("# Component Index\n\n## Components\n\n\n\n---\n\n"); -test("WriterMarkdown", () => { - const types: AppendType[] = []; - const document = new WriterMarkdown({ - onAppend: (type) => types.push(type), + document.append("raw", "> Quote"); + document.append("p", "Text"); + + expect(document.end()).toEqual("# Component Index\n\n## Components\n\n\n\n---\n\n> QuoteText\n\n"); + expect(types).toEqual(["h1", "h2", "divider", "raw", "p"]); }); - document.append("h1", "Component Index"); - document.append("h2", "Components").tableOfContents(); - document.append("divider"); + test("heading levels and table of contents", () => { + const document = new WriterMarkdown({}); + + document.append("h1", "Main Title"); + document.append("h2", "Section 1"); + document.append("h3", "Subsection 1.1"); - expect(document.end()).toEqual("# Component Index\n\n## Components\n\n\n\n---\n\n"); + const output = document.end(); + expect(output).toContain("# Main Title"); + expect(output).toContain("## Section 1"); + expect(output).toContain("### Subsection 1.1"); + }); - document.append("raw", "> Quote"); - document.append("p", "Text"); + test("quote formatting", () => { + const document = new WriterMarkdown({}); - expect(document.end()).toEqual("# Component Index\n\n## Components\n\n\n\n---\n\n> QuoteText\n\n"); - expect(types).toEqual(["h1", "h2", "divider", "raw", "p"]); + document.append("quote", "This is a quote"); + document.append("p", "This is a paragraph"); + document.append("quote", "Multi\nline\nquote"); + + const output = document.end(); + expect(output).toEqual("> This is a quote\n\nThis is a paragraph\n\n> Multi\nline\nquote\n\n"); + }); + + test("raw content and dividers", () => { + const document = new WriterMarkdown({}); + + document.append("raw", "No line break"); + document.append("raw", " after this."); + document.append("divider"); + document.append("p", "New paragraph"); + + expect(document.end()).toEqual("No line break after this.---\n\nNew paragraph\n\n"); + }); + + test("onAppend callback receives correct arguments", () => { + let lastType: AppendType | undefined; + let lastDocument: WriterMarkdown | undefined; + + const document = new WriterMarkdown({ + onAppend: (type, doc) => { + lastType = type; + lastDocument = doc; + }, + }); + + document.append("h1", "Title"); + + expect(lastType).toBe("h1"); + expect(lastDocument).toBe(document); + }); }); diff --git a/tests/get-svelte-entry.test.ts b/tests/get-svelte-entry.test.ts new file mode 100644 index 00000000..1c16190d --- /dev/null +++ b/tests/get-svelte-entry.test.ts @@ -0,0 +1,92 @@ +import * as fs from "fs"; +import * as path from "path"; +import { getSvelteEntry } from "../src/get-svelte-entry"; + +describe("getSvelteEntry", () => { + const mockCwd = "/mock/project"; + + beforeEach(() => { + jest.clearAllMocks(); + jest.spyOn(process, "cwd").mockReturnValue(mockCwd); + jest.spyOn(path, "join").mockImplementation((...args) => args.join("/")); + jest.spyOn(fs, "existsSync"); + jest.spyOn(fs, "readFileSync"); + jest.spyOn(console, "log").mockImplementation(() => {}); + jest.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test("returns explicit entry point if valid", () => { + jest.spyOn(fs, "existsSync").mockReturnValue(true); + + const result = getSvelteEntry("src/Component.svelte"); + + expect(result).toBe("src/Component.svelte"); + expect(fs.existsSync).toHaveBeenCalledWith(`${mockCwd}/src/Component.svelte`); + }); + + test("returns null if explicit entry point is invalid", () => { + jest.spyOn(fs, "existsSync").mockReturnValue(false); + + const result = getSvelteEntry("src/NonExistent.svelte"); + + expect(result).toBeNull(); + expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Invalid entry point")); + }); + + test("returns svelte field from package.json if no entry point provided", () => { + jest.spyOn(fs, "existsSync").mockReturnValue(true); + jest.spyOn(fs, "readFileSync").mockReturnValue(JSON.stringify({ svelte: "src/index.js" })); + + const result = getSvelteEntry(); + + expect(result).toBe("src/index.js"); + expect(fs.readFileSync).toHaveBeenCalledWith(`${mockCwd}/package.json`, "utf-8"); + }); + + test("returns null if package.json exists but has no svelte field", () => { + jest.spyOn(fs, "existsSync").mockReturnValue(true); + jest.spyOn(fs, "readFileSync").mockReturnValue(JSON.stringify({ main: "index.js" })); + + const result = getSvelteEntry(); + + expect(result).toBeNull(); + expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Could not determine an entry point")); + }); + + test("returns null if package.json does not exist", () => { + jest.spyOn(fs, "existsSync").mockReturnValue(false); + + const result = getSvelteEntry(); + + expect(result).toBeNull(); + expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Could not locate a package.json file")); + }); + + test.skip("handles malformed package.json", () => { + jest.spyOn(fs, "existsSync").mockReturnValue(true); + jest.spyOn(fs, "readFileSync").mockReturnValue("invalid json"); + + expect(() => getSvelteEntry()).toThrow(); + expect(console.error).toHaveBeenCalled(); + }); + + test("handles empty string entry point", () => { + const result = getSvelteEntry(""); + + expect(result).toBeNull(); + expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Could not locate a package.json file.")); + }); + + test("handles undefined svelte field in package.json", () => { + jest.spyOn(fs, "existsSync").mockReturnValue(true); + jest.spyOn(fs, "readFileSync").mockReturnValue(JSON.stringify({ svelte: undefined })); + + const result = getSvelteEntry(); + expect(result).toBeNull(); + expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Could not determine an entry point")); + }); +});