forked from
slices.network/slices
Highly ambitious ATProtocol AppView service and sdks
1import { assertEquals, assertStringIncludes } from "jsr:@std/assert";
2import { Project } from "ts-morph";
3import { generateInterfaces } from "../src/interfaces.ts";
4import type { Lexicon } from "../src/mod.ts";
5
6function createTestProject() {
7 return new Project({ useInMemoryFileSystem: true });
8}
9
10Deno.test("generateInterfaces - creates record interfaces", () => {
11 const project = createTestProject();
12 const sourceFile = project.createSourceFile("test.ts", "");
13
14 const lexicons: Lexicon[] = [
15 {
16 id: "com.example.post",
17 definitions: {
18 main: {
19 type: "record",
20 record: {
21 type: "record",
22 properties: {
23 text: { type: "string" },
24 createdAt: { type: "string", format: "datetime" },
25 likes: { type: "integer" },
26 },
27 required: ["text", "createdAt"],
28 },
29 },
30 },
31 },
32 ];
33
34 generateInterfaces(sourceFile, lexicons);
35 const result = sourceFile.getFullText();
36
37 // Should create interface for the record
38 assertStringIncludes(result, "export interface ComExamplePost");
39 assertStringIncludes(result, "text: string;");
40 assertStringIncludes(result, "createdAt: string;");
41 assertStringIncludes(result, "likes?: number;");
42
43 // Should create sort fields type alias
44 assertStringIncludes(result, "export type ComExamplePostSortFields");
45 assertStringIncludes(result, '"text" | "createdAt" | "likes"');
46});
47
48Deno.test("generateInterfaces - creates object interfaces", () => {
49 const project = createTestProject();
50 const sourceFile = project.createSourceFile("test.ts", "");
51
52 const lexicons: Lexicon[] = [
53 {
54 id: "app.bsky.embed.defs",
55 definitions: {
56 aspectRatio: {
57 type: "object",
58 properties: {
59 width: { type: "integer" },
60 height: { type: "integer" },
61 },
62 required: ["width", "height"],
63 },
64 },
65 },
66 ];
67
68 generateInterfaces(sourceFile, lexicons);
69 const result = sourceFile.getFullText();
70
71 assertStringIncludes(result, "export interface AppBskyEmbedDefsAspectRatio");
72 assertStringIncludes(result, "width: number;");
73 assertStringIncludes(result, "height: number;");
74});
75
76Deno.test("generateInterfaces - creates union type aliases", () => {
77 const project = createTestProject();
78 const sourceFile = project.createSourceFile("test.ts", "");
79
80 const lexicons: Lexicon[] = [
81 {
82 id: "app.bsky.embed.defs",
83 definitions: {
84 view: {
85 type: "union",
86 refs: ["#imageView", "#videoView"],
87 closed: false,
88 },
89 imageView: {
90 type: "object",
91 properties: { alt: { type: "string" } },
92 },
93 videoView: {
94 type: "object",
95 properties: { duration: { type: "integer" } },
96 },
97 },
98 },
99 ];
100
101 generateInterfaces(sourceFile, lexicons);
102 const result = sourceFile.getFullText();
103
104 assertStringIncludes(result, "export type AppBskyEmbedDefsView");
105 assertStringIncludes(result, "AppBskyEmbedDefs[\"ImageView\"]");
106 assertStringIncludes(result, "AppBskyEmbedDefs[\"VideoView\"]");
107 // Open union should include unknown type
108 assertStringIncludes(result, "{ $type: string; [key: string]: unknown }");
109});
110
111Deno.test("generateInterfaces - creates known values types", () => {
112 const project = createTestProject();
113 const sourceFile = project.createSourceFile("test.ts", "");
114
115 const lexicons: Lexicon[] = [
116 {
117 id: "com.example.post",
118 definitions: {
119 main: {
120 type: "record",
121 record: {
122 type: "record",
123 properties: {
124 status: {
125 type: "string",
126 knownValues: ["draft", "published", "archived"],
127 },
128 },
129 },
130 },
131 },
132 },
133 ];
134
135 generateInterfaces(sourceFile, lexicons);
136 const result = sourceFile.getFullText();
137
138 assertStringIncludes(result, "export type ComExamplePostStatus");
139 assertStringIncludes(result, "'draft'");
140 assertStringIncludes(result, "'published'");
141 assertStringIncludes(result, "'archived'");
142 assertStringIncludes(result, "(string & Record<string, never>)");
143});
144
145Deno.test("generateInterfaces - creates namespace interfaces", () => {
146 const project = createTestProject();
147 const sourceFile = project.createSourceFile("test.ts", "");
148
149 const lexicons: Lexicon[] = [
150 {
151 id: "app.bsky.embed.defs",
152 definitions: {
153 aspectRatio: {
154 type: "object",
155 properties: { width: { type: "integer" } },
156 },
157 view: {
158 type: "union",
159 refs: ["#aspectRatio"],
160 },
161 },
162 },
163 ];
164
165 generateInterfaces(sourceFile, lexicons);
166 const result = sourceFile.getFullText();
167
168 // Should create namespace interface for multiple definitions
169 assertStringIncludes(result, "export interface AppBskyEmbedDefs");
170 assertStringIncludes(result, "readonly AspectRatio: AppBskyEmbedDefsAspectRatio;");
171 assertStringIncludes(result, "readonly View: AppBskyEmbedDefsView;");
172});
173
174Deno.test("generateInterfaces - generates from network.slices lexicons", () => {
175 const project = createTestProject();
176 const sourceFile = project.createSourceFile("test.ts", "");
177
178 const lexicons = [
179 {
180 id: "network.slices.slice",
181 definitions: {
182 main: {
183 type: "record",
184 record: {
185 type: "record",
186 properties: {
187 name: { type: "string" },
188 },
189 required: ["name"],
190 },
191 },
192 },
193 },
194 ];
195
196 generateInterfaces(sourceFile, lexicons);
197 const result = sourceFile.getFullText();
198
199 assertStringIncludes(result, "export interface NetworkSlicesSlice");
200 assertStringIncludes(result, "name: string;");
201});
202
203Deno.test("generateInterfaces - generates empty output for empty lexicons", () => {
204 const project = createTestProject();
205 const sourceFile = project.createSourceFile("test.ts", "");
206
207 generateInterfaces(sourceFile, []);
208 const result = sourceFile.getFullText();
209
210 // Should have minimal output for empty lexicons
211 assertEquals(result.trim(), "");
212});
213
214Deno.test("generateInterfaces - handles single definition lexicons", () => {
215 const project = createTestProject();
216 const sourceFile = project.createSourceFile("test.ts", "");
217
218 const lexicons: Lexicon[] = [
219 {
220 id: "com.atproto.repo.strongRef",
221 definitions: {
222 main: {
223 type: "object",
224 properties: {
225 uri: { type: "string" },
226 cid: { type: "string" },
227 },
228 required: ["uri", "cid"],
229 },
230 },
231 },
232 ];
233
234 generateInterfaces(sourceFile, lexicons);
235 const result = sourceFile.getFullText();
236
237 // Should use clean name for single definition
238 assertStringIncludes(result, "export interface ComAtprotoRepoStrongRef");
239 assertStringIncludes(result, "uri: string;");
240 assertStringIncludes(result, "cid: string;");
241
242 // Should NOT create namespace interface for single definition
243 assertEquals(result.includes("readonly Main:"), false);
244});