import { describe, test, expect } from "vitest"; import { writeResourceClass, writePropertyClass, writeConstructor, writeEnumType, resolveConstructorType, } from "./generate-typescript"; describe("writeResourceClass", () => { test("generates class with constructor and attributes", () => { const lines: string[] = []; writeResourceClass( lines, "Bucket", [{ name: "BucketName", type: "string", required: true }], [{ name: "Arn", type: "string" }], ); const output = lines.join("\n"); expect(output).toContain("export declare class Bucket {"); expect(output).toContain("BucketName: string;"); expect(output).toContain("readonly Arn: string;"); expect(output).toContain("}"); }); test("applies remap to attribute types", () => { const lines: string[] = []; const remap = new Map([["CustomType", "BucketConfig"]]); writeResourceClass( lines, "Bucket", [], [{ name: "Config", type: "CustomType" }], remap, ); const output = lines.join("\n"); expect(output).toContain("readonly Config: BucketConfig;"); }); }); describe("writePropertyClass", () => { test("generates class with constructor only", () => { const lines: string[] = []; writePropertyClass( lines, "BucketConfig", [{ name: "Enabled", type: "boolean", required: false }], ); const output = lines.join("\n"); expect(output).toContain("export declare class BucketConfig {"); expect(output).toContain("Enabled?: boolean;"); expect(output).toContain("}"); }); }); describe("writeConstructor", () => { test("empty props produces Record constructor", () => { const lines: string[] = []; writeConstructor(lines, [], undefined); expect(lines.join("\n")).toContain("constructor(props: Record);"); }); test("sorts required before optional", () => { const lines: string[] = []; writeConstructor( lines, [ { name: "Optional", type: "string", required: false }, { name: "Required", type: "string", required: true }, ], undefined, ); const output = lines.join("\n"); const reqIdx = output.indexOf("Required:"); const optIdx = output.indexOf("Optional?:"); expect(reqIdx).toBeLessThan(optIdx); }); test("includes description as JSDoc", () => { const lines: string[] = []; writeConstructor( lines, [{ name: "Name", type: "string", required: true, description: "The bucket name" }], undefined, ); const output = lines.join("\n"); expect(output).toContain("/** The bucket name */"); }); }); describe("writeConstructor with resourceAttributesType", () => { test("empty props with attributes type emits both params", () => { const lines: string[] = []; writeConstructor(lines, [], undefined, "CFResourceAttributes"); expect(lines.join("\n")).toContain( "constructor(props: Record, attributes?: CFResourceAttributes);", ); }); test("props with attributes type emits second param after closing brace", () => { const lines: string[] = []; writeConstructor( lines, [{ name: "BucketName", type: "string", required: true }], undefined, "CFResourceAttributes", ); const output = lines.join("\n"); expect(output).toContain("constructor(props: {"); expect(output).toContain("BucketName: string;"); expect(output).toContain("}, attributes?: CFResourceAttributes);"); }); test("without attributes type, closing brace has no second param", () => { const lines: string[] = []; writeConstructor( lines, [{ name: "Name", type: "string", required: true }], undefined, ); const output = lines.join("\n"); expect(output).toContain(" });"); expect(output).not.toContain("attributes?"); }); }); describe("writeResourceClass with resourceAttributesType", () => { test("resource class constructor includes attributes param", () => { const lines: string[] = []; writeResourceClass( lines, "Bucket", [{ name: "BucketName", type: "string", required: false }], [{ name: "Arn", type: "string" }], undefined, "CFResourceAttributes", ); const output = lines.join("\n"); expect(output).toContain("}, attributes?: CFResourceAttributes);"); expect(output).toContain("readonly Arn: string;"); }); test("property class does not get attributes param", () => { const lines: string[] = []; writePropertyClass( lines, "BucketConfig", [{ name: "Enabled", type: "boolean", required: false }], ); const output = lines.join("\n"); expect(output).not.toContain("attributes?"); }); }); describe("writeEnumType", () => { test("writes single-line for short enum", () => { const lines: string[] = []; writeEnumType(lines, "Status", ["active", "inactive"]); const output = lines.join("\n"); expect(output).toContain('export type Status = "active" | "inactive";'); }); test("writes multi-line for long enum", () => { const lines: string[] = []; writeEnumType(lines, "VeryLongTypeName", [ "value-one-is-very-long", "value-two-is-very-long", "value-three-is-very-long", "value-four-is-very-long", ]); const output = lines.join("\n"); expect(output).toContain("export type VeryLongTypeName ="); expect(output).toContain(' | "value-one-is-very-long"'); }); test("sorts values alphabetically", () => { const lines: string[] = []; writeEnumType(lines, "Order", ["z", "a", "m"]); const output = lines.join("\n"); expect(output).toContain('"a" | "m" | "z"'); }); }); describe("resolveConstructorType", () => { test("passes through primitives", () => { expect(resolveConstructorType("string", undefined)).toBe("string"); expect(resolveConstructorType("number", undefined)).toBe("number"); expect(resolveConstructorType("boolean", undefined)).toBe("boolean"); expect(resolveConstructorType("any", undefined)).toBe("any"); }); test("normalizes Record", () => { expect(resolveConstructorType("Record", undefined)).toBe("Record"); }); test("handles array types recursively", () => { const remap = new Map([["Foo", "Bar"]]); expect(resolveConstructorType("Foo[]", remap)).toBe("Bar[]"); expect(resolveConstructorType("string[]", undefined)).toBe("string[]"); }); test("applies remap", () => { const remap = new Map([["InternalName", "PublicName"]]); expect(resolveConstructorType("InternalName", remap)).toBe("PublicName"); }); test("returns type as-is when no remap match", () => { expect(resolveConstructorType("UnknownType", new Map())).toBe("UnknownType"); }); });