@tanstack/cta-engine 0.27.1 → 0.29.0

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.
@@ -0,0 +1,332 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { populateAddOnOptionsDefaults } from '../src/add-ons.js'
3
+ import { AddOnOptionSchema, AddOnOptionsSchema, AddOnSelectOptionSchema } from '../src/types.js'
4
+ import type { AddOn } from '../src/types.js'
5
+
6
+ // Helper function to create test AddOn objects
7
+ function createTestAddOn(overrides: Partial<AddOn>): AddOn {
8
+ return {
9
+ id: 'test-addon',
10
+ name: 'Test Addon',
11
+ description: 'Test addon description',
12
+ type: 'add-on',
13
+ modes: ['file-router'],
14
+ phase: 'add-on',
15
+ files: {},
16
+ deletedFiles: [],
17
+ getFiles: () => Promise.resolve([]),
18
+ getFileContents: () => Promise.resolve(''),
19
+ getDeletedFiles: () => Promise.resolve([]),
20
+ ...overrides
21
+ }
22
+ }
23
+
24
+ describe('Add-on Options', () => {
25
+ describe('Option Schema Validation', () => {
26
+ it('should validate a valid select option', () => {
27
+ const validSelectOption = {
28
+ type: 'select',
29
+ label: 'Database Provider',
30
+ description: 'Choose your database provider',
31
+ default: 'postgres',
32
+ options: [
33
+ { value: 'postgres', label: 'PostgreSQL' },
34
+ { value: 'mysql', label: 'MySQL' },
35
+ { value: 'sqlite', label: 'SQLite' }
36
+ ]
37
+ }
38
+
39
+ expect(() => AddOnSelectOptionSchema.parse(validSelectOption)).not.toThrow()
40
+ })
41
+
42
+ it('should reject select option without required fields', () => {
43
+ const invalidSelectOption = {
44
+ type: 'select',
45
+ // Missing required 'label' field
46
+ options: [{ value: 'test', label: 'Test' }]
47
+ }
48
+
49
+ expect(() => AddOnSelectOptionSchema.parse(invalidSelectOption)).toThrow()
50
+ })
51
+
52
+ it('should reject select option with invalid option format', () => {
53
+ const invalidSelectOption = {
54
+ type: 'select',
55
+ label: 'Test',
56
+ options: [
57
+ { value: 'test' } // Missing 'label' field
58
+ ]
59
+ }
60
+
61
+ expect(() => AddOnSelectOptionSchema.parse(invalidSelectOption)).toThrow()
62
+ })
63
+
64
+ it('should reject select option with empty options array', () => {
65
+ const invalidSelectOption = {
66
+ type: 'select',
67
+ label: 'Test',
68
+ options: []
69
+ }
70
+
71
+ expect(() => AddOnSelectOptionSchema.parse(invalidSelectOption)).toThrow()
72
+ })
73
+
74
+ it('should validate AddOnOption discriminated union', () => {
75
+ const validOption = {
76
+ type: 'select',
77
+ label: 'Theme',
78
+ default: 'dark',
79
+ options: [
80
+ { value: 'dark', label: 'Dark' },
81
+ { value: 'light', label: 'Light' }
82
+ ]
83
+ }
84
+
85
+ expect(() => AddOnOptionSchema.parse(validOption)).not.toThrow()
86
+ })
87
+
88
+ it('should validate AddOnOptions record', () => {
89
+ const validOptions = {
90
+ database: {
91
+ type: 'select',
92
+ label: 'Database Provider',
93
+ default: 'postgres',
94
+ options: [
95
+ { value: 'postgres', label: 'PostgreSQL' },
96
+ { value: 'mysql', label: 'MySQL' }
97
+ ]
98
+ },
99
+ theme: {
100
+ type: 'select',
101
+ label: 'Theme',
102
+ default: 'dark',
103
+ options: [
104
+ { value: 'dark', label: 'Dark' },
105
+ { value: 'light', label: 'Light' }
106
+ ]
107
+ }
108
+ }
109
+
110
+ expect(() => AddOnOptionsSchema.parse(validOptions)).not.toThrow()
111
+ })
112
+ })
113
+
114
+ describe('populateAddOnOptionsDefaults', () => {
115
+ it('should populate defaults for add-ons with options', () => {
116
+ const addOns = [
117
+ createTestAddOn({
118
+ id: 'testAddon',
119
+ name: 'Test Addon',
120
+ options: {
121
+ database: {
122
+ type: 'select' as const,
123
+ label: 'Database Provider',
124
+ default: 'postgres',
125
+ options: [
126
+ { value: 'postgres', label: 'PostgreSQL' },
127
+ { value: 'mysql', label: 'MySQL' },
128
+ { value: 'sqlite', label: 'SQLite' }
129
+ ]
130
+ }
131
+ }
132
+ }),
133
+ createTestAddOn({
134
+ id: 'shadcn',
135
+ name: 'shadcn/ui',
136
+ options: {
137
+ theme: {
138
+ type: 'select' as const,
139
+ label: 'Theme',
140
+ default: 'neutral',
141
+ options: [
142
+ { value: 'neutral', label: 'Neutral' },
143
+ { value: 'slate', label: 'Slate' }
144
+ ]
145
+ }
146
+ }
147
+ })
148
+ ]
149
+
150
+ const result = populateAddOnOptionsDefaults(addOns)
151
+
152
+ expect(result).toEqual({
153
+ testAddon: {
154
+ database: 'postgres'
155
+ },
156
+ shadcn: {
157
+ theme: 'neutral'
158
+ }
159
+ })
160
+ })
161
+
162
+ it('should handle add-ons without options', () => {
163
+ const addOns = [
164
+ createTestAddOn({
165
+ id: 'simple-addon',
166
+ name: 'Simple Add-on'
167
+ // No options property
168
+ })
169
+ ]
170
+
171
+ const result = populateAddOnOptionsDefaults(addOns)
172
+
173
+ expect(result).toEqual({})
174
+ })
175
+
176
+ it('should only populate defaults for enabled add-ons', () => {
177
+ const addOns = [
178
+ createTestAddOn({
179
+ id: 'testAddon',
180
+ name: 'Test Addon',
181
+ options: {
182
+ database: {
183
+ type: 'select' as const,
184
+ label: 'Database Provider',
185
+ default: 'postgres',
186
+ options: [
187
+ { value: 'postgres', label: 'PostgreSQL' },
188
+ { value: 'mysql', label: 'MySQL' }
189
+ ]
190
+ }
191
+ }
192
+ }),
193
+ createTestAddOn({
194
+ id: 'shadcn',
195
+ name: 'shadcn/ui',
196
+ options: {
197
+ theme: {
198
+ type: 'select' as const,
199
+ label: 'Theme',
200
+ default: 'neutral',
201
+ options: [
202
+ { value: 'neutral', label: 'Neutral' },
203
+ { value: 'slate', label: 'Slate' }
204
+ ]
205
+ }
206
+ }
207
+ })
208
+ ]
209
+
210
+ const enabledAddOns = [addOns[0]] // Only testAddon
211
+ const result = populateAddOnOptionsDefaults(enabledAddOns)
212
+
213
+ expect(result).toEqual({
214
+ testAddon: {
215
+ database: 'postgres'
216
+ }
217
+ // shadcn should not be included
218
+ })
219
+ })
220
+
221
+ it('should handle empty enabled add-ons array', () => {
222
+ const enabledAddOns: Array<any> = []
223
+ const result = populateAddOnOptionsDefaults(enabledAddOns)
224
+
225
+ expect(result).toEqual({})
226
+ })
227
+
228
+ it('should handle add-ons with multiple options', () => {
229
+ const addOns = [
230
+ createTestAddOn({
231
+ id: 'complex-addon',
232
+ name: 'Complex Add-on',
233
+ options: {
234
+ database: {
235
+ type: 'select' as const,
236
+ label: 'Database',
237
+ default: 'postgres',
238
+ options: [
239
+ { value: 'postgres', label: 'PostgreSQL' },
240
+ { value: 'mysql', label: 'MySQL' }
241
+ ]
242
+ },
243
+ theme: {
244
+ type: 'select' as const,
245
+ label: 'Theme',
246
+ default: 'dark',
247
+ options: [
248
+ { value: 'dark', label: 'Dark' },
249
+ { value: 'light', label: 'Light' }
250
+ ]
251
+ }
252
+ }
253
+ })
254
+ ]
255
+
256
+ const result = populateAddOnOptionsDefaults(addOns)
257
+
258
+ expect(result).toEqual({
259
+ 'complex-addon': {
260
+ database: 'postgres',
261
+ theme: 'dark'
262
+ }
263
+ })
264
+ })
265
+
266
+ it('should handle options without default values', () => {
267
+ const addOns = [
268
+ createTestAddOn({
269
+ id: 'no-default',
270
+ name: 'No Default Add-on',
271
+ options: {
272
+ database: {
273
+ type: 'select' as const,
274
+ label: 'Database',
275
+ default: 'postgres', // We need a default for valid schema
276
+ options: [
277
+ { value: 'postgres', label: 'PostgreSQL' },
278
+ { value: 'mysql', label: 'MySQL' }
279
+ ]
280
+ }
281
+ }
282
+ })
283
+ ]
284
+
285
+ // Test the case where an addon has no default by manually modifying the option
286
+ if (addOns[0].options?.database) {
287
+ delete (addOns[0].options.database as any).default
288
+ }
289
+
290
+ const result = populateAddOnOptionsDefaults(addOns)
291
+
292
+ expect(result).toEqual({
293
+ 'no-default': {
294
+ database: undefined
295
+ }
296
+ })
297
+ })
298
+ })
299
+
300
+ describe('Error Handling', () => {
301
+ it('should handle malformed option definitions gracefully', () => {
302
+ const malformedOptions = {
303
+ invalid: {
304
+ type: 'unknown-type', // Invalid type
305
+ label: 'Test'
306
+ }
307
+ }
308
+
309
+ expect(() => AddOnOptionsSchema.parse(malformedOptions)).toThrow()
310
+ })
311
+
312
+ it('should validate option value types', () => {
313
+ const invalidOption = {
314
+ type: 'select',
315
+ label: 123, // Should be string
316
+ options: [{ value: 'test', label: 'Test' }]
317
+ }
318
+
319
+ expect(() => AddOnSelectOptionSchema.parse(invalidOption)).toThrow()
320
+ })
321
+
322
+ it('should require non-empty option arrays', () => {
323
+ const emptyOptionsArray = {
324
+ type: 'select',
325
+ label: 'Test',
326
+ options: []
327
+ }
328
+
329
+ expect(() => AddOnSelectOptionSchema.parse(emptyOptionsArray)).toThrow()
330
+ })
331
+ })
332
+ })