import { describe, expect, test } from "vitest"; import { generateRuntimeIndex, type RuntimeIndexEntry, type RuntimeIndexPropertyEntry, type RuntimeIndexConfig, } from "./generate-runtime-index"; const baseConfig: RuntimeIndexConfig = { lexiconName: "test", intrinsicReExports: { names: ["Sub", "Ref"], from: "../intrinsics" }, pseudoReExports: { names: ["Region", "AccountId"], from: "../pseudo" }, }; describe("generateRuntimeIndex", () => { test("emits resource and property exports in sorted order", () => { const resources: RuntimeIndexEntry[] = [ { tsName: "Bucket", resourceType: "AWS::S3::Bucket", attrs: { arn: "Arn" } }, { tsName: "Alarm", resourceType: "AWS::CloudWatch::Alarm", attrs: {} }, ]; const properties: RuntimeIndexPropertyEntry[] = [ { tsName: "Bucket_Versioning", resourceType: "AWS::S3::Bucket.VersioningConfiguration" }, ]; const result = generateRuntimeIndex(resources, properties, baseConfig); // Resources sorted: Alarm before Bucket expect(result).toContain('export const Alarm = createResource("AWS::CloudWatch::Alarm", "test", {});'); expect(result).toContain('export const Bucket = createResource("AWS::S3::Bucket", "test", {"arn":"Arn"});'); expect(result).toContain('export const Bucket_Versioning = createProperty("AWS::S3::Bucket.VersioningConfiguration", "test");'); // Alarm should come before Bucket in the output const alarmIdx = result.indexOf("Alarm"); const bucketIdx = result.indexOf("Bucket"); expect(alarmIdx).toBeLessThan(bucketIdx); }); test("includes re-exports", () => { const result = generateRuntimeIndex([], [], baseConfig); expect(result).toContain('export { Sub, Ref } from "../intrinsics";'); expect(result).toContain('export { Region, AccountId } from "../pseudo";'); }); test("filters colliding re-exports", () => { const resources: RuntimeIndexEntry[] = [ { tsName: "Sub", resourceType: "Test::Sub", attrs: {} }, { tsName: "Region", resourceType: "Test::Region", attrs: {} }, ]; const result = generateRuntimeIndex(resources, [], baseConfig); // "Sub" and "Region" collide with generated exports — only non-colliding survive expect(result).toContain('export { Ref } from "../intrinsics";'); expect(result).toContain('export { AccountId } from "../pseudo";'); expect(result).not.toContain("export { Sub, Ref }"); expect(result).not.toContain("export { Region, AccountId }"); }); test("omits re-export line when all names collide", () => { const resources: RuntimeIndexEntry[] = [ { tsName: "Sub", resourceType: "T::Sub", attrs: {} }, { tsName: "Ref", resourceType: "T::Ref", attrs: {} }, ]; const result = generateRuntimeIndex(resources, [], baseConfig); // Both intrinsic names collide, so no intrinsic re-export line expect(result).not.toContain("../intrinsics"); }); test("works with no re-exports configured", () => { const config: RuntimeIndexConfig = { lexiconName: "bare" }; const result = generateRuntimeIndex([], [], config); expect(result).toContain("// Re-exports for convenience"); expect(result).not.toContain("export {"); }); test("includes banner and import", () => { const result = generateRuntimeIndex([], [], baseConfig); expect(result).toContain("// Code generated by chant generate. DO NOT EDIT."); expect(result).toContain('import { createResource, createProperty } from "./runtime";'); }); });