import { assertEquals, assertStringIncludes } from "jsr:@std/assert"; import { Project } from "ts-morph"; import { generateInterfaces } from "../src/interfaces.ts"; import type { Lexicon } from "../src/mod.ts"; function createTestProject() { return new Project({ useInMemoryFileSystem: true }); } Deno.test("generateInterfaces - creates record interfaces", () => { const project = createTestProject(); const sourceFile = project.createSourceFile("test.ts", ""); const lexicons: Lexicon[] = [ { id: "com.example.post", definitions: { main: { type: "record", record: { type: "record", properties: { text: { type: "string" }, createdAt: { type: "string", format: "datetime" }, likes: { type: "integer" }, }, required: ["text", "createdAt"], }, }, }, }, ]; generateInterfaces(sourceFile, lexicons); const result = sourceFile.getFullText(); // Should create interface for the record assertStringIncludes(result, "export interface ComExamplePost"); assertStringIncludes(result, "text: string;"); assertStringIncludes(result, "createdAt: string;"); assertStringIncludes(result, "likes?: number;"); // Should create sort fields type alias assertStringIncludes(result, "export type ComExamplePostSortFields"); assertStringIncludes(result, '"text" | "createdAt" | "likes"'); }); Deno.test("generateInterfaces - creates object interfaces", () => { const project = createTestProject(); const sourceFile = project.createSourceFile("test.ts", ""); const lexicons: Lexicon[] = [ { id: "app.bsky.embed.defs", definitions: { aspectRatio: { type: "object", properties: { width: { type: "integer" }, height: { type: "integer" }, }, required: ["width", "height"], }, }, }, ]; generateInterfaces(sourceFile, lexicons); const result = sourceFile.getFullText(); assertStringIncludes(result, "export interface AppBskyEmbedDefsAspectRatio"); assertStringIncludes(result, "width: number;"); assertStringIncludes(result, "height: number;"); }); Deno.test("generateInterfaces - creates union type aliases", () => { const project = createTestProject(); const sourceFile = project.createSourceFile("test.ts", ""); const lexicons: Lexicon[] = [ { id: "app.bsky.embed.defs", definitions: { view: { type: "union", refs: ["#imageView", "#videoView"], closed: false, }, imageView: { type: "object", properties: { alt: { type: "string" } }, }, videoView: { type: "object", properties: { duration: { type: "integer" } }, }, }, }, ]; generateInterfaces(sourceFile, lexicons); const result = sourceFile.getFullText(); assertStringIncludes(result, "export type AppBskyEmbedDefsView"); assertStringIncludes(result, "AppBskyEmbedDefs[\"ImageView\"]"); assertStringIncludes(result, "AppBskyEmbedDefs[\"VideoView\"]"); // Open union should include unknown type assertStringIncludes(result, "{ $type: string; [key: string]: unknown }"); }); Deno.test("generateInterfaces - creates known values types", () => { const project = createTestProject(); const sourceFile = project.createSourceFile("test.ts", ""); const lexicons: Lexicon[] = [ { id: "com.example.post", definitions: { main: { type: "record", record: { type: "record", properties: { status: { type: "string", knownValues: ["draft", "published", "archived"], }, }, }, }, }, }, ]; generateInterfaces(sourceFile, lexicons); const result = sourceFile.getFullText(); assertStringIncludes(result, "export type ComExamplePostStatus"); assertStringIncludes(result, "'draft'"); assertStringIncludes(result, "'published'"); assertStringIncludes(result, "'archived'"); assertStringIncludes(result, "(string & Record)"); }); Deno.test("generateInterfaces - creates namespace interfaces", () => { const project = createTestProject(); const sourceFile = project.createSourceFile("test.ts", ""); const lexicons: Lexicon[] = [ { id: "app.bsky.embed.defs", definitions: { aspectRatio: { type: "object", properties: { width: { type: "integer" } }, }, view: { type: "union", refs: ["#aspectRatio"], }, }, }, ]; generateInterfaces(sourceFile, lexicons); const result = sourceFile.getFullText(); // Should create namespace interface for multiple definitions assertStringIncludes(result, "export interface AppBskyEmbedDefs"); assertStringIncludes(result, "readonly AspectRatio: AppBskyEmbedDefsAspectRatio;"); assertStringIncludes(result, "readonly View: AppBskyEmbedDefsView;"); }); Deno.test("generateInterfaces - generates from network.slices lexicons", () => { const project = createTestProject(); const sourceFile = project.createSourceFile("test.ts", ""); const lexicons = [ { id: "network.slices.slice", definitions: { main: { type: "record", record: { type: "record", properties: { name: { type: "string" }, }, required: ["name"], }, }, }, }, ]; generateInterfaces(sourceFile, lexicons); const result = sourceFile.getFullText(); assertStringIncludes(result, "export interface NetworkSlicesSlice"); assertStringIncludes(result, "name: string;"); }); Deno.test("generateInterfaces - generates empty output for empty lexicons", () => { const project = createTestProject(); const sourceFile = project.createSourceFile("test.ts", ""); generateInterfaces(sourceFile, []); const result = sourceFile.getFullText(); // Should have minimal output for empty lexicons assertEquals(result.trim(), ""); }); Deno.test("generateInterfaces - handles single definition lexicons", () => { const project = createTestProject(); const sourceFile = project.createSourceFile("test.ts", ""); const lexicons: Lexicon[] = [ { id: "com.atproto.repo.strongRef", definitions: { main: { type: "object", properties: { uri: { type: "string" }, cid: { type: "string" }, }, required: ["uri", "cid"], }, }, }, ]; generateInterfaces(sourceFile, lexicons); const result = sourceFile.getFullText(); // Should use clean name for single definition assertStringIncludes(result, "export interface ComAtprotoRepoStrongRef"); assertStringIncludes(result, "uri: string;"); assertStringIncludes(result, "cid: string;"); // Should NOT create namespace interface for single definition assertEquals(result.includes("readonly Main:"), false); });