@formspec/build 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/__tests__/cli.test.d.ts +2 -0
  2. package/dist/__tests__/cli.test.d.ts.map +1 -0
  3. package/dist/__tests__/cli.test.js +178 -0
  4. package/dist/__tests__/cli.test.js.map +1 -0
  5. package/dist/__tests__/edge-cases.test.d.ts +7 -0
  6. package/dist/__tests__/edge-cases.test.d.ts.map +1 -0
  7. package/dist/__tests__/edge-cases.test.js +209 -0
  8. package/dist/__tests__/edge-cases.test.js.map +1 -0
  9. package/dist/__tests__/generator.test.d.ts +2 -0
  10. package/dist/__tests__/generator.test.d.ts.map +1 -0
  11. package/dist/__tests__/generator.test.js +208 -0
  12. package/dist/__tests__/generator.test.js.map +1 -0
  13. package/dist/__tests__/integration.test.d.ts +8 -0
  14. package/dist/__tests__/integration.test.d.ts.map +1 -0
  15. package/dist/__tests__/integration.test.js +163 -0
  16. package/dist/__tests__/integration.test.js.map +1 -0
  17. package/dist/__tests__/write-schemas.test.d.ts +2 -0
  18. package/dist/__tests__/write-schemas.test.d.ts.map +1 -0
  19. package/dist/__tests__/write-schemas.test.js +196 -0
  20. package/dist/__tests__/write-schemas.test.js.map +1 -0
  21. package/dist/cli.d.ts +17 -0
  22. package/dist/cli.d.ts.map +1 -0
  23. package/dist/cli.js +138 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/index.d.ts +119 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +109 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/json-schema/generator.d.ts +32 -0
  30. package/dist/json-schema/generator.d.ts.map +1 -0
  31. package/dist/json-schema/generator.js +146 -0
  32. package/dist/json-schema/generator.js.map +1 -0
  33. package/dist/json-schema/types.d.ts +60 -0
  34. package/dist/json-schema/types.d.ts.map +1 -0
  35. package/dist/json-schema/types.js +7 -0
  36. package/dist/json-schema/types.js.map +1 -0
  37. package/dist/ui-schema/generator.d.ts +48 -0
  38. package/dist/ui-schema/generator.d.ts.map +1 -0
  39. package/dist/ui-schema/generator.js +150 -0
  40. package/dist/ui-schema/generator.js.map +1 -0
  41. package/dist/ui-schema/types.d.ts +87 -0
  42. package/dist/ui-schema/types.d.ts.map +1 -0
  43. package/dist/ui-schema/types.js +8 -0
  44. package/dist/ui-schema/types.js.map +1 -0
  45. package/package.json +41 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cli.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,178 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import * as os from "node:os";
5
+ import { execSync } from "node:child_process";
6
+ describe("CLI", () => {
7
+ let tempDir;
8
+ const cliPath = path.join(__dirname, "..", "..", "dist", "cli.js");
9
+ beforeEach(() => {
10
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "formspec-cli-test-"));
11
+ });
12
+ afterEach(() => {
13
+ if (fs.existsSync(tempDir)) {
14
+ fs.rmSync(tempDir, { recursive: true });
15
+ }
16
+ });
17
+ function runCli(args) {
18
+ try {
19
+ const stdout = execSync(`node ${cliPath} ${args.join(" ")}`, {
20
+ encoding: "utf-8",
21
+ cwd: tempDir,
22
+ stdio: ["pipe", "pipe", "pipe"],
23
+ });
24
+ return { stdout, exitCode: 0 };
25
+ }
26
+ catch (error) {
27
+ const execError = error;
28
+ return {
29
+ stdout: execError.stdout || execError.stderr || "",
30
+ exitCode: execError.status || 1,
31
+ };
32
+ }
33
+ }
34
+ function createFormFile(filename, content) {
35
+ const filepath = path.join(tempDir, filename);
36
+ fs.writeFileSync(filepath, content);
37
+ return filepath;
38
+ }
39
+ describe("help", () => {
40
+ it("should show help with --help flag", () => {
41
+ const result = runCli(["--help"]);
42
+ expect(result.stdout).toContain("FormSpec Build CLI");
43
+ expect(result.stdout).toContain("Usage:");
44
+ expect(result.stdout).toContain("--out-dir");
45
+ expect(result.stdout).toContain("--name");
46
+ });
47
+ it("should show help with -h flag", () => {
48
+ const result = runCli(["-h"]);
49
+ expect(result.stdout).toContain("FormSpec Build CLI");
50
+ });
51
+ });
52
+ describe("argument parsing", () => {
53
+ it("should fail when no input file provided", () => {
54
+ const result = runCli([]);
55
+ expect(result.exitCode).not.toBe(0);
56
+ // Shows help which includes Usage information
57
+ expect(result.stdout).toContain("Usage:");
58
+ });
59
+ it("should fail for unknown option", () => {
60
+ const formFile = createFormFile("form.js", `
61
+ export default { elements: [] };
62
+ `);
63
+ const result = runCli([formFile, "--unknown-option"]);
64
+ expect(result.exitCode).not.toBe(0);
65
+ expect(result.stdout).toContain("Unknown option");
66
+ });
67
+ it("should fail when --out-dir has no value", () => {
68
+ const formFile = createFormFile("form.js", `
69
+ export default { elements: [] };
70
+ `);
71
+ const result = runCli([formFile, "--out-dir"]);
72
+ expect(result.exitCode).not.toBe(0);
73
+ expect(result.stdout).toContain("--out-dir requires a value");
74
+ });
75
+ it("should fail when --name has no value", () => {
76
+ const formFile = createFormFile("form.js", `
77
+ export default { elements: [] };
78
+ `);
79
+ const result = runCli([formFile, "--name"]);
80
+ expect(result.exitCode).not.toBe(0);
81
+ expect(result.stdout).toContain("--name requires a value");
82
+ });
83
+ });
84
+ describe("file handling", () => {
85
+ it("should fail when input file doesn't exist", () => {
86
+ const result = runCli(["nonexistent.js"]);
87
+ expect(result.exitCode).not.toBe(0);
88
+ expect(result.stdout).toContain("Error");
89
+ });
90
+ it("should fail when file has no form export", () => {
91
+ const formFile = createFormFile("no-export.js", `
92
+ const notExported = { foo: "bar" };
93
+ `);
94
+ const result = runCli([formFile]);
95
+ expect(result.exitCode).not.toBe(0);
96
+ expect(result.stdout).toContain("Error");
97
+ });
98
+ it("should fail when export is not a FormSpec", () => {
99
+ const formFile = createFormFile("not-formspec.js", `
100
+ export default { notAForm: true };
101
+ `);
102
+ const result = runCli([formFile]);
103
+ expect(result.exitCode).not.toBe(0);
104
+ expect(result.stdout).toContain("FormSpec");
105
+ });
106
+ });
107
+ describe("successful generation", () => {
108
+ it("should generate schemas from default export", () => {
109
+ const formFile = createFormFile("form-default.js", `
110
+ export default {
111
+ elements: [
112
+ { _type: "field", _field: "text", name: "name" }
113
+ ]
114
+ };
115
+ `);
116
+ const outDir = path.join(tempDir, "output");
117
+ const result = runCli([formFile, "-o", outDir, "-n", "test"]);
118
+ expect(result.exitCode).toBe(0);
119
+ expect(result.stdout).toContain("Generated");
120
+ expect(fs.existsSync(path.join(outDir, "test-schema.json"))).toBe(true);
121
+ expect(fs.existsSync(path.join(outDir, "test-uischema.json"))).toBe(true);
122
+ });
123
+ it("should generate schemas from named 'form' export", () => {
124
+ const formFile = createFormFile("form-named.js", `
125
+ export const form = {
126
+ elements: [
127
+ { _type: "field", _field: "text", name: "title" }
128
+ ]
129
+ };
130
+ `);
131
+ const outDir = path.join(tempDir, "output");
132
+ const result = runCli([formFile, "-o", outDir, "-n", "test"]);
133
+ expect(result.exitCode).toBe(0);
134
+ expect(fs.existsSync(path.join(outDir, "test-schema.json"))).toBe(true);
135
+ });
136
+ it("should derive name from input filename when not specified", () => {
137
+ const formFile = createFormFile("my-form.js", `
138
+ export default {
139
+ elements: [
140
+ { _type: "field", _field: "text", name: "name" }
141
+ ]
142
+ };
143
+ `);
144
+ const outDir = path.join(tempDir, "output");
145
+ const result = runCli([formFile, "-o", outDir]);
146
+ expect(result.exitCode).toBe(0);
147
+ expect(fs.existsSync(path.join(outDir, "my-form-schema.json"))).toBe(true);
148
+ expect(fs.existsSync(path.join(outDir, "my-form-uischema.json"))).toBe(true);
149
+ });
150
+ it("should use default output directory when not specified", () => {
151
+ const formFile = createFormFile("form.js", `
152
+ export default {
153
+ elements: [
154
+ { _type: "field", _field: "text", name: "name" }
155
+ ]
156
+ };
157
+ `);
158
+ const result = runCli([formFile, "-n", "test"]);
159
+ expect(result.exitCode).toBe(0);
160
+ // Default output is ./generated relative to cwd
161
+ expect(fs.existsSync(path.join(tempDir, "generated", "test-schema.json"))).toBe(true);
162
+ });
163
+ it("should accept short flags -o and -n", () => {
164
+ const formFile = createFormFile("form.js", `
165
+ export default {
166
+ elements: [
167
+ { _type: "field", _field: "text", name: "name" }
168
+ ]
169
+ };
170
+ `);
171
+ const outDir = path.join(tempDir, "out");
172
+ const result = runCli([formFile, "-o", outDir, "-n", "short"]);
173
+ expect(result.exitCode).toBe(0);
174
+ expect(fs.existsSync(path.join(outDir, "short-schema.json"))).toBe(true);
175
+ });
176
+ });
177
+ });
178
+ //# sourceMappingURL=cli.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.test.js","sourceRoot":"","sources":["../../src/__tests__/cli.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Edge case and error handling tests.
3
+ *
4
+ * These tests verify correct behavior in unusual or boundary situations.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=edge-cases.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edge-cases.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/edge-cases.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Edge case and error handling tests.
3
+ *
4
+ * These tests verify correct behavior in unusual or boundary situations.
5
+ */
6
+ import { describe, it, expect } from "vitest";
7
+ import { buildFormSchemas, generateJsonSchema, generateUiSchema } from "../index.js";
8
+ import { formspec, field, group, when, is } from "@formspec/dsl";
9
+ describe("Edge cases: Empty and minimal forms", () => {
10
+ it("should handle form with no fields", () => {
11
+ const emptyForm = formspec();
12
+ const { jsonSchema, uiSchema } = buildFormSchemas(emptyForm);
13
+ expect(jsonSchema.type).toBe("object");
14
+ expect(jsonSchema.properties).toEqual({});
15
+ expect(jsonSchema.required).toBeUndefined();
16
+ expect(uiSchema.type).toBe("VerticalLayout");
17
+ expect(uiSchema.elements).toEqual([]);
18
+ });
19
+ it("should handle empty group", () => {
20
+ const form = formspec(group("Empty Group"));
21
+ const { jsonSchema, uiSchema } = buildFormSchemas(form);
22
+ expect(jsonSchema.properties).toEqual({});
23
+ expect(uiSchema.elements).toHaveLength(1);
24
+ expect(uiSchema.elements[0]).toMatchObject({
25
+ type: "Group",
26
+ label: "Empty Group",
27
+ elements: [],
28
+ });
29
+ });
30
+ it("should handle empty conditional", () => {
31
+ const form = formspec(field.enum("type", ["a", "b"]), when(is("type", "a")));
32
+ const { jsonSchema, uiSchema } = buildFormSchemas(form);
33
+ // Only the enum field should exist
34
+ expect(Object.keys(jsonSchema.properties ?? {})).toEqual(["type"]);
35
+ expect(uiSchema.elements).toHaveLength(1);
36
+ });
37
+ it("should handle empty array field", () => {
38
+ const form = formspec(field.array("items"));
39
+ const { jsonSchema } = buildFormSchemas(form);
40
+ expect(jsonSchema.properties?.["items"]).toMatchObject({
41
+ type: "array",
42
+ items: {
43
+ type: "object",
44
+ properties: {},
45
+ },
46
+ });
47
+ });
48
+ it("should handle empty object field", () => {
49
+ const form = formspec(field.object("data"));
50
+ const { jsonSchema } = buildFormSchemas(form);
51
+ expect(jsonSchema.properties?.["data"]).toMatchObject({
52
+ type: "object",
53
+ properties: {},
54
+ });
55
+ });
56
+ });
57
+ describe("Edge cases: Field names", () => {
58
+ it("should handle field names with special characters", () => {
59
+ const form = formspec(field.text("field_with_underscore"), field.text("field-with-dash"), field.text("field123"));
60
+ const { jsonSchema, uiSchema } = buildFormSchemas(form);
61
+ expect(jsonSchema.properties?.["field_with_underscore"]).toBeDefined();
62
+ expect(jsonSchema.properties?.["field-with-dash"]).toBeDefined();
63
+ expect(jsonSchema.properties?.["field123"]).toBeDefined();
64
+ // All scopes should be properly formed
65
+ expect(uiSchema.elements[0]).toMatchObject({
66
+ scope: "#/properties/field_with_underscore",
67
+ });
68
+ expect(uiSchema.elements[1]).toMatchObject({
69
+ scope: "#/properties/field-with-dash",
70
+ });
71
+ });
72
+ it("should handle single character field names", () => {
73
+ const form = formspec(field.text("a"), field.number("b"), field.boolean("c"));
74
+ const { jsonSchema } = buildFormSchemas(form);
75
+ expect(Object.keys(jsonSchema.properties ?? {})).toEqual(["a", "b", "c"]);
76
+ });
77
+ });
78
+ describe("Edge cases: Number constraints", () => {
79
+ it("should handle zero as min/max", () => {
80
+ const form = formspec(field.number("zeroMin", { min: 0 }), field.number("zeroMax", { max: 0 }), field.number("zeroRange", { min: 0, max: 0 }));
81
+ const { jsonSchema } = buildFormSchemas(form);
82
+ expect(jsonSchema.properties?.["zeroMin"]).toMatchObject({
83
+ type: "number",
84
+ minimum: 0,
85
+ });
86
+ expect(jsonSchema.properties?.["zeroMax"]).toMatchObject({
87
+ type: "number",
88
+ maximum: 0,
89
+ });
90
+ expect(jsonSchema.properties?.["zeroRange"]).toMatchObject({
91
+ type: "number",
92
+ minimum: 0,
93
+ maximum: 0,
94
+ });
95
+ });
96
+ it("should handle negative min/max", () => {
97
+ const form = formspec(field.number("temperature", { min: -273, max: 1000000 }));
98
+ const { jsonSchema } = buildFormSchemas(form);
99
+ expect(jsonSchema.properties?.["temperature"]).toMatchObject({
100
+ type: "number",
101
+ minimum: -273,
102
+ maximum: 1000000,
103
+ });
104
+ });
105
+ });
106
+ describe("Edge cases: Enum options", () => {
107
+ it("should handle single option enum", () => {
108
+ const form = formspec(field.enum("singleOption", ["only"]));
109
+ const { jsonSchema } = buildFormSchemas(form);
110
+ expect(jsonSchema.properties?.["singleOption"]).toMatchObject({
111
+ type: "string",
112
+ enum: ["only"],
113
+ });
114
+ });
115
+ it("should handle enum with empty string option", () => {
116
+ const form = formspec(field.enum("withEmpty", ["", "value"]));
117
+ const { jsonSchema } = buildFormSchemas(form);
118
+ expect(jsonSchema.properties?.["withEmpty"]).toMatchObject({
119
+ type: "string",
120
+ enum: ["", "value"],
121
+ });
122
+ });
123
+ });
124
+ describe("Edge cases: Conditional values", () => {
125
+ it("should handle boolean conditional value", () => {
126
+ const form = formspec(field.boolean("enabled"), when(is("enabled", true), field.text("config")));
127
+ const { uiSchema } = buildFormSchemas(form);
128
+ const conditionalField = uiSchema.elements.find((el) => el.type === "Control" && "scope" in el && el.scope === "#/properties/config");
129
+ expect(conditionalField?.rule?.condition.schema).toMatchObject({
130
+ const: true,
131
+ });
132
+ });
133
+ it("should handle null conditional value", () => {
134
+ const form = formspec(field.text("optional"), when(is("optional", null), field.text("fallback")));
135
+ const { uiSchema } = buildFormSchemas(form);
136
+ const conditionalField = uiSchema.elements.find((el) => el.type === "Control" && "scope" in el && el.scope === "#/properties/fallback");
137
+ expect(conditionalField?.rule?.condition.schema).toMatchObject({
138
+ const: null,
139
+ });
140
+ });
141
+ it("should handle number conditional value", () => {
142
+ const form = formspec(field.number("quantity"), when(is("quantity", 0), field.text("zeroReason", { label: "Why zero?" })));
143
+ const { uiSchema } = buildFormSchemas(form);
144
+ const conditionalField = uiSchema.elements.find((el) => el.type === "Control" && "scope" in el && el.scope === "#/properties/zeroReason");
145
+ expect(conditionalField?.rule?.condition.schema).toMatchObject({
146
+ const: 0,
147
+ });
148
+ });
149
+ });
150
+ describe("Edge cases: Array min/max items", () => {
151
+ it("should handle minItems of 0", () => {
152
+ const form = formspec(field.arrayWithConfig("items", { minItems: 0 }, field.text("name")));
153
+ const { jsonSchema } = buildFormSchemas(form);
154
+ expect(jsonSchema.properties?.["items"]).toMatchObject({
155
+ type: "array",
156
+ minItems: 0,
157
+ });
158
+ });
159
+ it("should handle minItems equal to maxItems", () => {
160
+ const form = formspec(field.arrayWithConfig("exactFive", { minItems: 5, maxItems: 5 }, field.text("item")));
161
+ const { jsonSchema } = buildFormSchemas(form);
162
+ expect(jsonSchema.properties?.["exactFive"]).toMatchObject({
163
+ type: "array",
164
+ minItems: 5,
165
+ maxItems: 5,
166
+ });
167
+ });
168
+ });
169
+ describe("Edge cases: Deeply nested structures", () => {
170
+ it("should handle 3+ levels of nesting", () => {
171
+ const form = formspec(field.object("level1", field.object("level2", field.object("level3", field.text("deepValue")))));
172
+ const { jsonSchema } = buildFormSchemas(form);
173
+ expect(jsonSchema.properties?.["level1"]).toMatchObject({
174
+ type: "object",
175
+ properties: {
176
+ level2: {
177
+ type: "object",
178
+ properties: {
179
+ level3: {
180
+ type: "object",
181
+ properties: {
182
+ deepValue: { type: "string" },
183
+ },
184
+ },
185
+ },
186
+ },
187
+ },
188
+ });
189
+ });
190
+ it("should handle conditionals inside groups inside conditionals", () => {
191
+ const form = formspec(field.enum("outer", ["a", "b"]), when(is("outer", "a"), group("Inner Group", field.enum("inner", ["x", "y"]), when(is("inner", "x"), field.text("deepest")))));
192
+ const { jsonSchema, uiSchema } = buildFormSchemas(form);
193
+ // All fields should be in schema
194
+ expect(Object.keys(jsonSchema.properties ?? {})).toContain("outer");
195
+ expect(Object.keys(jsonSchema.properties ?? {})).toContain("inner");
196
+ expect(Object.keys(jsonSchema.properties ?? {})).toContain("deepest");
197
+ // Find the deepest field and verify it has combined rules
198
+ const deepestControl = uiSchema.elements.find((el) => {
199
+ if (el.type === "Group") {
200
+ return el.elements.some((inner) => inner.type === "Control" &&
201
+ "scope" in inner &&
202
+ inner.scope === "#/properties/deepest");
203
+ }
204
+ return false;
205
+ });
206
+ expect(deepestControl).toBeDefined();
207
+ });
208
+ });
209
+ //# sourceMappingURL=edge-cases.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edge-cases.test.js","sourceRoot":"","sources":["../../src/__tests__/edge-cases.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=generator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/generator.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,208 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { buildFormSchemas, generateJsonSchema, generateUiSchema } from "../index.js";
3
+ import { formspec, field, group, when, is } from "@formspec/dsl";
4
+ describe("generateJsonSchema", () => {
5
+ it("should generate schema for basic fields", () => {
6
+ const form = formspec(field.text("name", { label: "Name" }), field.number("age", { min: 0, max: 150 }), field.boolean("active"));
7
+ const schema = generateJsonSchema(form);
8
+ expect(schema.$schema).toBe("https://json-schema.org/draft-07/schema#");
9
+ expect(schema.type).toBe("object");
10
+ expect(schema.properties).toEqual({
11
+ name: { type: "string", title: "Name" },
12
+ age: { type: "number", minimum: 0, maximum: 150 },
13
+ active: { type: "boolean" },
14
+ });
15
+ });
16
+ it("should generate schema for enum fields", () => {
17
+ const form = formspec(field.enum("status", ["draft", "sent", "paid"], { label: "Status" }));
18
+ const schema = generateJsonSchema(form);
19
+ expect(schema.properties?.["status"]).toEqual({
20
+ type: "string",
21
+ title: "Status",
22
+ enum: ["draft", "sent", "paid"],
23
+ });
24
+ });
25
+ it("should handle required fields", () => {
26
+ const form = formspec(field.text("name", { required: true }), field.text("optional"));
27
+ const schema = generateJsonSchema(form);
28
+ expect(schema.required).toEqual(["name"]);
29
+ });
30
+ it("should extract fields from groups", () => {
31
+ const form = formspec(group("Customer", field.text("name"), field.text("email")));
32
+ const schema = generateJsonSchema(form);
33
+ expect(Object.keys(schema.properties ?? {})).toEqual(["name", "email"]);
34
+ });
35
+ it("should extract fields from conditionals", () => {
36
+ const form = formspec(field.enum("type", ["a", "b"]), when(is("type", "a"), field.text("extra")));
37
+ const schema = generateJsonSchema(form);
38
+ expect(Object.keys(schema.properties ?? {})).toEqual(["type", "extra"]);
39
+ });
40
+ it("should include x-formspec-source for dynamic enum fields", () => {
41
+ const form = formspec(field.dynamicEnum("country", "countries", { label: "Country" }));
42
+ const schema = generateJsonSchema(form);
43
+ expect(schema.properties?.["country"]).toMatchObject({
44
+ type: "string",
45
+ title: "Country",
46
+ "x-formspec-source": "countries",
47
+ });
48
+ });
49
+ it("should include x-formspec-params for dynamic enum with dependencies", () => {
50
+ const form = formspec(field.dynamicEnum("city", "cities", {
51
+ label: "City",
52
+ params: ["country", "state"],
53
+ }));
54
+ const schema = generateJsonSchema(form);
55
+ expect(schema.properties?.["city"]).toMatchObject({
56
+ type: "string",
57
+ "x-formspec-source": "cities",
58
+ "x-formspec-params": ["country", "state"],
59
+ });
60
+ });
61
+ it("should include x-formspec-schemaSource for dynamic schema fields", () => {
62
+ const form = formspec(field.dynamicSchema("paymentDetails", "stripe-payment-form", {
63
+ label: "Payment Details",
64
+ }));
65
+ const schema = generateJsonSchema(form);
66
+ expect(schema.properties?.["paymentDetails"]).toMatchObject({
67
+ type: "object",
68
+ title: "Payment Details",
69
+ additionalProperties: true,
70
+ "x-formspec-schemaSource": "stripe-payment-form",
71
+ });
72
+ });
73
+ });
74
+ describe("generateUiSchema", () => {
75
+ it("should generate vertical layout for basic fields", () => {
76
+ const form = formspec(field.text("name", { label: "Name" }));
77
+ const uiSchema = generateUiSchema(form);
78
+ expect(uiSchema.type).toBe("VerticalLayout");
79
+ expect(uiSchema.elements).toHaveLength(1);
80
+ expect(uiSchema.elements[0]).toEqual({
81
+ type: "Control",
82
+ scope: "#/properties/name",
83
+ label: "Name",
84
+ });
85
+ });
86
+ it("should generate groups", () => {
87
+ const form = formspec(group("Customer Info", field.text("name")));
88
+ const uiSchema = generateUiSchema(form);
89
+ expect(uiSchema.elements[0]).toMatchObject({
90
+ type: "Group",
91
+ label: "Customer Info",
92
+ elements: [
93
+ { type: "Control", scope: "#/properties/name" },
94
+ ],
95
+ });
96
+ });
97
+ it("should generate rules for conditionals", () => {
98
+ const form = formspec(field.enum("status", ["draft", "sent"]), when(is("status", "draft"), field.text("notes", { label: "Notes" })));
99
+ const uiSchema = generateUiSchema(form);
100
+ // First element is the status control
101
+ expect(uiSchema.elements[0]).toMatchObject({
102
+ type: "Control",
103
+ scope: "#/properties/status",
104
+ });
105
+ // Second element is the notes control with a rule
106
+ expect(uiSchema.elements[1]).toMatchObject({
107
+ type: "Control",
108
+ scope: "#/properties/notes",
109
+ label: "Notes",
110
+ rule: {
111
+ effect: "SHOW",
112
+ condition: {
113
+ scope: "#/properties/status",
114
+ schema: { const: "draft" },
115
+ },
116
+ },
117
+ });
118
+ });
119
+ });
120
+ describe("generateJsonSchema - array fields", () => {
121
+ it("should generate schema for array fields", () => {
122
+ const form = formspec(field.array("addresses", field.text("street"), field.text("city")));
123
+ const schema = generateJsonSchema(form);
124
+ expect(schema.properties?.["addresses"]).toEqual({
125
+ type: "array",
126
+ items: {
127
+ type: "object",
128
+ properties: {
129
+ street: { type: "string" },
130
+ city: { type: "string" },
131
+ },
132
+ },
133
+ });
134
+ });
135
+ it("should handle array fields with min/max items", () => {
136
+ const form = formspec(field.arrayWithConfig("items", { label: "Line Items", minItems: 1, maxItems: 10 }, field.text("description"), field.number("quantity")));
137
+ const schema = generateJsonSchema(form);
138
+ expect(schema.properties?.["items"]).toMatchObject({
139
+ type: "array",
140
+ title: "Line Items",
141
+ minItems: 1,
142
+ maxItems: 10,
143
+ items: {
144
+ type: "object",
145
+ properties: {
146
+ description: { type: "string" },
147
+ quantity: { type: "number" },
148
+ },
149
+ },
150
+ });
151
+ });
152
+ });
153
+ describe("generateJsonSchema - object fields", () => {
154
+ it("should generate schema for object fields", () => {
155
+ const form = formspec(field.object("address", field.text("street"), field.text("city"), field.text("zip")));
156
+ const schema = generateJsonSchema(form);
157
+ expect(schema.properties?.["address"]).toEqual({
158
+ type: "object",
159
+ properties: {
160
+ street: { type: "string" },
161
+ city: { type: "string" },
162
+ zip: { type: "string" },
163
+ },
164
+ });
165
+ });
166
+ it("should handle required fields within object fields", () => {
167
+ const form = formspec(field.object("address", field.text("street", { required: true }), field.text("city", { required: true }), field.text("zip")));
168
+ const schema = generateJsonSchema(form);
169
+ expect(schema.properties?.["address"]).toMatchObject({
170
+ type: "object",
171
+ properties: {
172
+ street: { type: "string" },
173
+ city: { type: "string" },
174
+ zip: { type: "string" },
175
+ },
176
+ required: ["street", "city"],
177
+ });
178
+ });
179
+ });
180
+ describe("generateUiSchema - nested conditionals", () => {
181
+ it("should combine rules for nested conditionals", () => {
182
+ const form = formspec(field.enum("country", ["US", "CA"]), field.enum("paymentMethod", ["card", "bank"]), when(is("country", "US"), when(is("paymentMethod", "bank"), field.text("routingNumber", { label: "Routing Number" }))));
183
+ const uiSchema = generateUiSchema(form);
184
+ // Find the routingNumber control
185
+ const routingControl = uiSchema.elements.find((el) => el.type === "Control" && "scope" in el && el.scope === "#/properties/routingNumber");
186
+ expect(routingControl).toBeDefined();
187
+ expect(routingControl?.rule).toBeDefined();
188
+ // The combined rule should use allOf to require both conditions
189
+ expect(routingControl?.rule?.condition.schema.allOf).toHaveLength(2);
190
+ expect(routingControl?.rule?.condition.schema.allOf?.[0]).toMatchObject({
191
+ properties: { country: { const: "US" } },
192
+ });
193
+ expect(routingControl?.rule?.condition.schema.allOf?.[1]).toMatchObject({
194
+ properties: { paymentMethod: { const: "bank" } },
195
+ });
196
+ });
197
+ });
198
+ describe("buildFormSchemas", () => {
199
+ it("should return both schemas", () => {
200
+ const form = formspec(field.text("name"));
201
+ const result = buildFormSchemas(form);
202
+ expect(result.jsonSchema).toBeDefined();
203
+ expect(result.jsonSchema.$schema).toBe("https://json-schema.org/draft-07/schema#");
204
+ expect(result.uiSchema).toBeDefined();
205
+ expect(result.uiSchema.type).toBe("VerticalLayout");
206
+ });
207
+ });
208
+ //# sourceMappingURL=generator.test.js.map