zcb 0.0.6
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.
- package/LICENSE +21 -0
- package/README.md +8 -0
- package/bin/zcb.mjs +3 -0
- package/package.json +129 -0
- package/src/__testUtils__/builtConfig.ts +74 -0
- package/src/__testUtils__/config.ts +32 -0
- package/src/__testUtils__/configBuilder.ts +58 -0
- package/src/__testUtils__/schema.ts +56 -0
- package/src/cli.ts +86 -0
- package/src/createConfigBuilder.test.ts +523 -0
- package/src/createConfigBuilder.ts +234 -0
- package/src/createConfigReader.test.ts +45 -0
- package/src/createConfigReader.ts +12 -0
- package/src/data/countryCodes.ts +247 -0
- package/src/data/countryNames.ts +207 -0
- package/src/data/distanceUnits.ts +1 -0
- package/src/data/languageCodes.ts +185 -0
- package/src/data/timezones.ts +419 -0
- package/src/index.ts +6 -0
- package/src/templates/config.ts.hbs +3 -0
- package/src/transformers/cloneNonEnumerableValues.ts +24 -0
- package/src/transformers/removeDisabledSlices.ts +17 -0
- package/src/transformers/setupExperiments.ts +24 -0
- package/src/types.ts +58 -0
- package/src/utils/__snapshots__/transformConfig.test.ts.snap +151 -0
- package/src/utils/arrayHasInvalidDefaults.ts +6 -0
- package/src/utils/isDerivedValueCallback.ts +3 -0
- package/src/utils/isInvalidPropertyOverride.ts +3 -0
- package/src/utils/isPropertyReservedWord.ts +3 -0
- package/src/utils/isSchemaValid.ts +3 -0
- package/src/utils/isValidPropertyDefinition.ts +5 -0
- package/src/utils/isValidValue.ts +27 -0
- package/src/utils/objectHasInvalidDefaults.ts +5 -0
- package/src/utils/recordHasInvalidDefaults.ts +6 -0
- package/src/utils/transformConfig.test.ts +19 -0
- package/src/utils/transformConfig.ts +119 -0
- package/src/utils/writeConfig.ts +34 -0
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
import kebabCase from 'lodash/kebabCase.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import {
|
|
4
|
+
type ConfigType,
|
|
5
|
+
type PageType,
|
|
6
|
+
type RouteType,
|
|
7
|
+
configSchema,
|
|
8
|
+
pageSchema,
|
|
9
|
+
routeSchema,
|
|
10
|
+
} from './__testUtils__/schema.ts';
|
|
11
|
+
|
|
12
|
+
describe('createConfigBuilder', () => {
|
|
13
|
+
describe('when a user passes in a schema with a root type other than "object"', () => {
|
|
14
|
+
it('should throw an error', async () => {
|
|
15
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
16
|
+
const invalidSchema = z.array(z.string());
|
|
17
|
+
|
|
18
|
+
expect(() => createConfigBuilder<z.infer<typeof invalidSchema>>(invalidSchema)).toThrow(
|
|
19
|
+
'The root type of a config schema must be "object", but received "array"'
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('when a user uses a key in the schema that is a reserved keyword', () => {
|
|
25
|
+
it('should throw an error', async () => {
|
|
26
|
+
const { RESERVED_KEYWORDS, createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
27
|
+
|
|
28
|
+
const invalidSchema = z.object({
|
|
29
|
+
values: z.string(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(() => createConfigBuilder<z.infer<typeof invalidSchema>>(invalidSchema)).toThrow(
|
|
33
|
+
`"values" is a reserved keyword within the config builder. Please use a different property name. The full list of reserved keywords is: ${[
|
|
34
|
+
...RESERVED_KEYWORDS,
|
|
35
|
+
].join(', ')}`
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('when a user disables the config', () => {
|
|
41
|
+
it('should add the disabled flag to the config', async () => {
|
|
42
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
43
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
44
|
+
config.disable().name('alpha');
|
|
45
|
+
// @ts-expect-error private property
|
|
46
|
+
expect(config.values().__disabled).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('when a user adds a experiment to the config', () => {
|
|
51
|
+
it('should add the experiment to the config', async () => {
|
|
52
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
53
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
54
|
+
config.experiment('FEAT_ALPHA@0.0.1').name('alpha');
|
|
55
|
+
// @ts-expect-error private property
|
|
56
|
+
expect(config.values().__experiment).toBe('FEAT_ALPHA@0.0.1');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('when a user extends from a config', () => {
|
|
61
|
+
it('should copy over all values to the new config', async () => {
|
|
62
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
63
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
64
|
+
const route = createConfigBuilder<RouteType>(routeSchema, { path: ({ page }) => kebabCase(page) });
|
|
65
|
+
const page = createConfigBuilder<PageType>(pageSchema);
|
|
66
|
+
|
|
67
|
+
config
|
|
68
|
+
.name('alpha')
|
|
69
|
+
.pages({ contactDetails: page.name('contactDetails').flush() })
|
|
70
|
+
.routes([route.page('contactDetails').flush()]);
|
|
71
|
+
|
|
72
|
+
const childConfig = createConfigBuilder<ConfigType>(configSchema);
|
|
73
|
+
childConfig.extend(config);
|
|
74
|
+
|
|
75
|
+
expect(childConfig.values()).toEqual({
|
|
76
|
+
name: 'alpha',
|
|
77
|
+
pages: {
|
|
78
|
+
contactDetails: { name: 'contactDetails' },
|
|
79
|
+
},
|
|
80
|
+
routes: [{ page: 'contactDetails', path: 'contact-details' }],
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should copy over all experiments to the new config', async () => {
|
|
85
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
86
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
87
|
+
const route = createConfigBuilder<RouteType>(routeSchema, { path: ({ page }) => kebabCase(page) });
|
|
88
|
+
const page = createConfigBuilder<PageType>(pageSchema);
|
|
89
|
+
|
|
90
|
+
config
|
|
91
|
+
.experiment('FEAT_ALPHA@0.0.1')
|
|
92
|
+
.name('alpha')
|
|
93
|
+
.pages({ contactDetails: page.experiment('FEAT_BRAVO@0.0.1').name('contactDetails').flush() })
|
|
94
|
+
.routes([route.experiment('FEAT_CHARLIE@0.0.1').page('contactDetails').flush()]);
|
|
95
|
+
|
|
96
|
+
const childConfig = createConfigBuilder<ConfigType>(configSchema);
|
|
97
|
+
childConfig.extend(config);
|
|
98
|
+
|
|
99
|
+
expect(childConfig.values()).toEqual(
|
|
100
|
+
expect.objectContaining({
|
|
101
|
+
__experiment: 'FEAT_ALPHA@0.0.1',
|
|
102
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
103
|
+
pages: expect.objectContaining({
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
105
|
+
contactDetails: expect.objectContaining({
|
|
106
|
+
__experiment: 'FEAT_BRAVO@0.0.1',
|
|
107
|
+
}),
|
|
108
|
+
}),
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
110
|
+
routes: expect.arrayContaining([
|
|
111
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
112
|
+
expect.objectContaining({
|
|
113
|
+
__experiment: 'FEAT_CHARLIE@0.0.1',
|
|
114
|
+
}),
|
|
115
|
+
]),
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should copy over all disabled flags to the new config', async () => {
|
|
121
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
122
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
123
|
+
const route = createConfigBuilder<RouteType>(routeSchema, { path: ({ page }) => kebabCase(page) });
|
|
124
|
+
const page = createConfigBuilder<PageType>(pageSchema);
|
|
125
|
+
|
|
126
|
+
config
|
|
127
|
+
.disable()
|
|
128
|
+
.name('alpha')
|
|
129
|
+
.pages({ contactDetails: page.disable().name('contactDetails').flush() })
|
|
130
|
+
.routes([route.disable().page('contactDetails').flush()]);
|
|
131
|
+
|
|
132
|
+
const childConfig = createConfigBuilder<ConfigType>(configSchema);
|
|
133
|
+
childConfig.extend(config);
|
|
134
|
+
|
|
135
|
+
expect(childConfig.values()).toEqual(
|
|
136
|
+
expect.objectContaining({
|
|
137
|
+
__disabled: true,
|
|
138
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
139
|
+
pages: expect.objectContaining({
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
141
|
+
contactDetails: expect.objectContaining({
|
|
142
|
+
__disabled: true,
|
|
143
|
+
}),
|
|
144
|
+
}),
|
|
145
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
146
|
+
routes: expect.arrayContaining([
|
|
147
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
148
|
+
expect.objectContaining({
|
|
149
|
+
__disabled: true,
|
|
150
|
+
}),
|
|
151
|
+
]),
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should copy over all derived value callbacks to the new config builder', async () => {
|
|
157
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
158
|
+
const route = createConfigBuilder<RouteType>(routeSchema, { path: ({ page }) => kebabCase(page) });
|
|
159
|
+
route.page('personalDetails');
|
|
160
|
+
const childRoute = createConfigBuilder<RouteType>(routeSchema);
|
|
161
|
+
childRoute.extend(route);
|
|
162
|
+
childRoute.page('contactDetails', true);
|
|
163
|
+
expect(childRoute.values()).toEqual({ page: 'contactDetails', path: 'contact-details' });
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('when the user does not add a value to the config', () => {
|
|
168
|
+
describe('and a config has a valid default value', () => {
|
|
169
|
+
describe('and that value is a string', () => {
|
|
170
|
+
it('should add the default value to the config', async () => {
|
|
171
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
172
|
+
|
|
173
|
+
const extendedSchema = configSchema.extend({
|
|
174
|
+
description: z.string().optional().default('This is the description.'),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const config = createConfigBuilder<z.infer<typeof extendedSchema>>(extendedSchema);
|
|
178
|
+
expect(config.values()).toEqual({ description: 'This is the description.' });
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('and that value is a record of booleans', () => {
|
|
183
|
+
it('should add the default value to the config', async () => {
|
|
184
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
185
|
+
|
|
186
|
+
const extendedSchema = configSchema.extend({
|
|
187
|
+
flags: z.record(z.boolean()).optional().default({ alpha: true, bravo: false, charlie: false }),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const config = createConfigBuilder<z.infer<typeof extendedSchema>>(extendedSchema);
|
|
191
|
+
expect(config.values()).toEqual({ flags: { alpha: true, bravo: false, charlie: false } });
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('and that value is an array of strings', () => {
|
|
196
|
+
it('should add the default value to the config', async () => {
|
|
197
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
198
|
+
|
|
199
|
+
const extendedSchema = configSchema.extend({
|
|
200
|
+
colors: z.array(z.string()).optional().default(['red', 'yellow', 'pink', 'green']),
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const config = createConfigBuilder<z.infer<typeof extendedSchema>>(extendedSchema);
|
|
204
|
+
expect(config.values()).toEqual({ colors: ['red', 'yellow', 'pink', 'green'] });
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('and a config has an invalid default value', () => {
|
|
210
|
+
describe('and that value is a record of booleans', () => {
|
|
211
|
+
it('should throw an error', async () => {
|
|
212
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
213
|
+
|
|
214
|
+
const extendedSchema = configSchema.extend({
|
|
215
|
+
flags: z.record(z.boolean().default(true)).optional(),
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
expect(() => createConfigBuilder<z.infer<typeof extendedSchema>>(extendedSchema)).toThrow(
|
|
219
|
+
'When setting schema property defaults for the object assigned to "flags", set them on the object and not the property.'
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('and that value is an object of key/value pairs', () => {
|
|
225
|
+
it('should throw an error', async () => {
|
|
226
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
227
|
+
|
|
228
|
+
const extendedSchema = configSchema.extend({
|
|
229
|
+
flags: z
|
|
230
|
+
.object({
|
|
231
|
+
alpha: z.boolean().default(true),
|
|
232
|
+
bravo: z.boolean().default(true),
|
|
233
|
+
})
|
|
234
|
+
.optional(),
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
expect(() => createConfigBuilder<z.infer<typeof extendedSchema>>(extendedSchema)).toThrow(
|
|
238
|
+
'When setting schema property defaults for the object assigned to "flags", set them on the object and not the property.'
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('and that value is an array of strings', () => {
|
|
244
|
+
it('should add the default value to the config', async () => {
|
|
245
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
246
|
+
|
|
247
|
+
const extendedSchema = configSchema.extend({
|
|
248
|
+
colors: z.array(z.string().default('white')).optional(),
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
expect(() => createConfigBuilder<z.infer<typeof extendedSchema>>(extendedSchema)).toThrow(
|
|
252
|
+
'When setting schema array defaults for the array assigned to "colors", set them on the array and not the item.'
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('when a user adds a value to the config', () => {
|
|
260
|
+
describe('and the property already has a value', () => {
|
|
261
|
+
it('should throw an error', async () => {
|
|
262
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
263
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
264
|
+
config.name('alpha');
|
|
265
|
+
|
|
266
|
+
expect(() => config.name('bravo')).toThrow(
|
|
267
|
+
'A value already exists for "name". You may be trying to add a new values before flushing the old one. If you intended to override the existing value, pass in true as the second argument.'
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('and that value is an enum', () => {
|
|
273
|
+
it('should add the value to the config', async () => {
|
|
274
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
275
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
276
|
+
config.countryCode('GB');
|
|
277
|
+
expect(config.values()).toEqual({ countryCode: 'GB' });
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should be a valid config', async () => {
|
|
281
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
282
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
283
|
+
config.countryCode('GB');
|
|
284
|
+
expect(config.validate()).toBe(true);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('and that value is an array of enums', () => {
|
|
289
|
+
it('should add the value to the config', async () => {
|
|
290
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
291
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
292
|
+
config.languageCodes(['en']);
|
|
293
|
+
expect(config.values()).toEqual({ languageCodes: ['en'] });
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should be a valid config', async () => {
|
|
297
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
298
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
299
|
+
config.languageCodes(['en']);
|
|
300
|
+
expect(config.validate()).toBe(true);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe('and that value is a record', () => {
|
|
305
|
+
it('should add the value to the config', async () => {
|
|
306
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
307
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
308
|
+
config.timeouts({ apollo: 120_000 });
|
|
309
|
+
expect(config.values()).toEqual({ timeouts: { apollo: 120_000 } });
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should be a valid config', async () => {
|
|
313
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
314
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
315
|
+
config.timeouts({ apollo: 120_000 });
|
|
316
|
+
expect(config.validate()).toBe(true);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('and that value is an record of configs', () => {
|
|
321
|
+
describe('and the configs are valid configs', () => {
|
|
322
|
+
it('should add the value to the config', async () => {
|
|
323
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
324
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
325
|
+
const page = createConfigBuilder<PageType>(pageSchema);
|
|
326
|
+
|
|
327
|
+
config.pages({
|
|
328
|
+
contactDetails: page.name('contactDetails').flush(),
|
|
329
|
+
personalDetails: page.name('personalDetails').flush(),
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
expect(config.values()).toEqual({
|
|
333
|
+
pages: {
|
|
334
|
+
contactDetails: {
|
|
335
|
+
name: 'contactDetails',
|
|
336
|
+
},
|
|
337
|
+
personalDetails: {
|
|
338
|
+
name: 'personalDetails',
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('should be a valid config', async () => {
|
|
345
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
346
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
347
|
+
const page = createConfigBuilder<PageType>(pageSchema);
|
|
348
|
+
|
|
349
|
+
config.pages({
|
|
350
|
+
contactDetails: page.name('contactDetails').flush(),
|
|
351
|
+
personalDetails: page.name('personalDetails').flush(),
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
expect(config.validate()).toBe(true);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
describe('and the configs are not valid configs', () => {
|
|
359
|
+
it('should throw an error', async () => {
|
|
360
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
361
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
362
|
+
|
|
363
|
+
expect(() =>
|
|
364
|
+
config.pages({
|
|
365
|
+
contactDetails: {
|
|
366
|
+
name: 'contactDetails',
|
|
367
|
+
},
|
|
368
|
+
personalDetails: {
|
|
369
|
+
name: 'personalDetails',
|
|
370
|
+
},
|
|
371
|
+
})
|
|
372
|
+
).toThrow(
|
|
373
|
+
'"pages" value has a depth greater than 1. To pass in objects with a depth greater than 1, create a builder for that config slice.'
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('should not add the value to the config', async () => {
|
|
378
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
379
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
config.pages({
|
|
383
|
+
contactDetails: {
|
|
384
|
+
name: 'contactDetails',
|
|
385
|
+
},
|
|
386
|
+
personalDetails: {
|
|
387
|
+
name: 'personalDetails',
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
} catch {
|
|
391
|
+
// no catch
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
expect(config.values()).toEqual({});
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe('and that value is an array of configs', () => {
|
|
400
|
+
describe('and the configs are valid configs', () => {
|
|
401
|
+
it('should add the value to the config', async () => {
|
|
402
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
403
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
404
|
+
const route = createConfigBuilder<RouteType>(routeSchema, { path: ({ page }) => kebabCase(page) });
|
|
405
|
+
const subRoute = route.fork();
|
|
406
|
+
|
|
407
|
+
config.routes([
|
|
408
|
+
route.page('personalDetails').flush(),
|
|
409
|
+
route
|
|
410
|
+
.page('contactDetails')
|
|
411
|
+
.routes([subRoute.page('deliveryAddress').flush(), subRoute.page('billingAddress').flush()])
|
|
412
|
+
.flush(),
|
|
413
|
+
]);
|
|
414
|
+
|
|
415
|
+
expect(config.values()).toEqual({
|
|
416
|
+
routes: [
|
|
417
|
+
{ page: 'personalDetails', path: 'personal-details' },
|
|
418
|
+
{
|
|
419
|
+
page: 'contactDetails',
|
|
420
|
+
path: 'contact-details',
|
|
421
|
+
routes: [
|
|
422
|
+
{ page: 'deliveryAddress', path: 'delivery-address' },
|
|
423
|
+
{ page: 'billingAddress', path: 'billing-address' },
|
|
424
|
+
],
|
|
425
|
+
},
|
|
426
|
+
],
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('should be a valid config', async () => {
|
|
431
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
432
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
433
|
+
const route = createConfigBuilder<RouteType>(routeSchema, { path: ({ page }) => kebabCase(page) });
|
|
434
|
+
|
|
435
|
+
config.routes([
|
|
436
|
+
route.page('personalDetails').flush(),
|
|
437
|
+
route
|
|
438
|
+
.page('contactDetails')
|
|
439
|
+
.routes(() => {
|
|
440
|
+
const subRoute = route.fork();
|
|
441
|
+
return [subRoute.page('deliveryAddress').flush(), subRoute.page('billingAddress').flush()];
|
|
442
|
+
})
|
|
443
|
+
.flush(),
|
|
444
|
+
]);
|
|
445
|
+
|
|
446
|
+
expect(config.validate()).toBe(true);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe('and the configs are not valid configs', () => {
|
|
451
|
+
it('should throw an error', async () => {
|
|
452
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
453
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
454
|
+
|
|
455
|
+
expect(() => config.routes([{ page: 'personalDetails', path: 'personal-details' }])).toThrow(
|
|
456
|
+
'"routes" value has a depth greater than 1. To pass in objects with a depth greater than 1, create a builder for that config slice.'
|
|
457
|
+
);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('should not add the value to the config', async () => {
|
|
461
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
462
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
config.routes([{ page: 'personalDetails', path: 'personal-details' }]);
|
|
466
|
+
} catch {
|
|
467
|
+
// no catch
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
expect(config.values()).toEqual({});
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
describe('and that value is a derived value', () => {
|
|
476
|
+
it('should add the value to the config automatically', async () => {
|
|
477
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
478
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
479
|
+
|
|
480
|
+
config
|
|
481
|
+
.countryCode('GB')
|
|
482
|
+
.languageCodes(['en'])
|
|
483
|
+
.locales(({ countryCode, languageCodes }) =>
|
|
484
|
+
// eslint-disable-next-line jest/no-conditional-in-test
|
|
485
|
+
languageCodes?.length && countryCode ? languageCodes.map(code => `${code}_${countryCode}`) : []
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
expect(config.values()).toEqual({ countryCode: 'GB', languageCodes: ['en'], locales: ['en_GB'] });
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it('should update the value to the config automatically', async () => {
|
|
492
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
493
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
494
|
+
|
|
495
|
+
config
|
|
496
|
+
.countryCode('GB')
|
|
497
|
+
.languageCodes(['en'])
|
|
498
|
+
.locales(({ countryCode, languageCodes }) =>
|
|
499
|
+
// eslint-disable-next-line jest/no-conditional-in-test
|
|
500
|
+
languageCodes?.length && countryCode ? languageCodes.map(code => `${code}_${countryCode}`) : []
|
|
501
|
+
)
|
|
502
|
+
.languageCodes(['fr'], true);
|
|
503
|
+
|
|
504
|
+
expect(config.values()).toEqual({ countryCode: 'GB', languageCodes: ['fr'], locales: ['fr_GB'] });
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('should be a valid config', async () => {
|
|
508
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
509
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
510
|
+
|
|
511
|
+
config
|
|
512
|
+
.countryCode('GB')
|
|
513
|
+
.languageCodes(['en'])
|
|
514
|
+
.locales(({ countryCode, languageCodes }) =>
|
|
515
|
+
// eslint-disable-next-line jest/no-conditional-in-test
|
|
516
|
+
languageCodes?.length && countryCode ? languageCodes.map(code => `${code}_${countryCode}`) : []
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
expect(config.validate()).toBe(true);
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
});
|