Skip to content

Commit

Permalink
test: add more coverage (#158)
Browse files Browse the repository at this point in the history
  • Loading branch information
metonym authored Feb 8, 2025
1 parent a56b4d4 commit 2bb0ad9
Show file tree
Hide file tree
Showing 4 changed files with 316 additions and 13 deletions.
148 changes: 148 additions & 0 deletions tests/ComponentParser.test.ts
Original file line number Diff line number Diff line change
@@ -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 = `
<script>
export let label;
export let disabled = false;
</script>
<button {disabled}>{label}</button>
`;

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 = `
<script>
export let size = 'medium';
export const buttonSizes = ['small', 'medium', 'large'];
</script>
<button class={size}>
<slot />
</button>
`;

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 = `
<script>
/** The text to display in the button */
export let label;
/** Controls the disabled state */
export let disabled = false;
</script>
<button {disabled}>{label}</button>
`;

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 = `
<script>
export let items = [];
</script>
<div>
<slot name="header" {items} />
<slot />
<slot name="footer" count={items.length} />
</div>
`;

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 = `
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
function handleClick() {
dispatch('click', { detail: 'clicked' });
}
</script>
<button on:click={handleClick}>Click me</button>
`;

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 = `
<!-- @component
A button component with customizable styles and behaviors.
Use this component for consistent button styling across the app.
-->
<script>
export let label;
</script>
<button>{label}</button>
`;

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 = `
<script>
export default function {
// Missing function name and parameters
return <div />;
}
</script>
`;

expect(() => parser.parseSvelteComponent(invalidSource, diagnostics)).toThrow();
});
});
9 changes: 9 additions & 0 deletions tests/Writer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
80 changes: 67 additions & 13 deletions tests/WriterMarkdown.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
92 changes: 92 additions & 0 deletions tests/get-svelte-entry.test.ts
Original file line number Diff line number Diff line change
@@ -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"));
});
});

0 comments on commit 2bb0ad9

Please sign in to comment.