1import * as Schema from "effect/Schema";
2import { describe, expect, it } from "vite-plus/test";
3
4import {
5 buildT3ProjectFileJsonSchema,
6 parseT3ProjectFile,
7 T3ProjectFileFromJson,
8} from "./t3ProjectFile.ts";
9
10const decodeJson = Schema.decodeUnknownSync(T3ProjectFileFromJson);
11
12describe("buildT3ProjectFileJsonSchema", () => {
13 it("emits a draft 2020-12 schema with the published $id", () => {
14 const schema = buildT3ProjectFileJsonSchema();
15
16 expect(schema.$schema).toBe("https://json-schema.org/draft/2020-12/schema");
17 expect(schema.$id).toBe("https://t3.codes/schema/t3.json");
18 expect(schema.type).toBe("object");
19 expect(schema.additionalProperties).toBe(false);
20 });
21
22 it("documents every supported field", () => {
23 const schema = buildT3ProjectFileJsonSchema() as {
24 properties: Record<
25 string,
26 {
27 description?: string;
28 items?: { properties: Record<string, unknown>; required: ReadonlyArray<string> };
29 }
30 >;
31 required?: ReadonlyArray<string>;
32 };
33
34 expect(Object.keys(schema.properties).sort()).toEqual([
35 "$schema",
36 "defaultThreadEnvMode",
37 "iconPath",
38 "scripts",
39 "worktreeSubmodules",
40 ]);
41 expect(schema.required).toBeUndefined();
42 expect(schema.properties.iconPath?.description).toContain("Workspace-relative path");
43 expect(schema.properties.defaultThreadEnvMode?.description).toContain("new threads start");
44
45 const script = schema.properties.scripts?.items;
46 expect(script?.required).toEqual(["name", "command"]);
47 expect(Object.keys(script?.properties ?? {}).sort()).toEqual([
48 "async",
49 "autoOpenPreview",
50 "command",
51 "icon",
52 "name",
53 "previewUrl",
54 "runOnSettle",
55 "runOnWorktreeCreate",
56 ]);
57 });
58
59 it("stays JSON-serializable", () => {
60 const schema = buildT3ProjectFileJsonSchema();
61 expect(JSON.parse(JSON.stringify(schema))).toEqual(schema);
62 });
63});
64
65describe("T3ProjectFileFromJson", () => {
66 it("decodes lenient JSONC with comments and trailing commas", () => {
67 const decoded = decodeJson(`{
68 // team scripts
69 "iconPath": "assets/logo.svg",
70 "scripts": [
71 { "name": "Dev", "command": "pnpm dev", },
72 ],
73 }`);
74
75 expect(decoded.iconPath).toBe("assets/logo.svg");
76 expect(decoded.scripts?.[0]).toEqual({ name: "Dev", command: "pnpm dev" });
77 });
78
79 it("fails on malformed JSON", () => {
80 expect(() => decodeJson("{ not json")).toThrow();
81 });
82});
83
84describe("parseT3ProjectFile", () => {
85 it("returns the decoded file for valid contents", () => {
86 expect(parseT3ProjectFile('{ "defaultThreadEnvMode": "worktree" }')).toEqual({
87 defaultThreadEnvMode: "worktree",
88 });
89 });
90
91 it("returns null for malformed or invalid contents", () => {
92 expect(parseT3ProjectFile("{ not json")).toBeNull();
93 expect(parseT3ProjectFile('{ "defaultThreadEnvMode": "spaceship" }')).toBeNull();
94 });
95});