@akinon/pz-theme 2.0.162-beta.0 → 2.0.162
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/CHANGELOG.md +4 -4
- package/package.json +4 -4
- package/readme.md +22 -0
- package/src/__tests__/custom-catalog-announce.test.ts +110 -0
- package/src/chrome/composition-source.ts +7 -2
- package/src/chrome/preview-snapshot-source.ts +5 -1
- package/src/content-capabilities.ts +41 -0
- package/src/load-placeholder-sections.ts +13 -3
- package/src/placeholder-registry.ts +42 -1
- package/src/region/__tests__/editor-handshake.test.ts +144 -0
- package/src/region/__tests__/region-contract.test.ts +500 -0
- package/src/region/__tests__/region-placeholder-readers.test.ts +431 -0
- package/src/region/__tests__/region-source.test.ts +621 -0
- package/src/region/editor-handshake.ts +60 -0
- package/src/region/region-contract.ts +184 -0
- package/src/region/region-source.ts +360 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
emptyRegionRegistry,
|
|
6
|
+
isRegionRegistrableSlug,
|
|
7
|
+
isRegionReservedSlug,
|
|
8
|
+
isValidRegionCode,
|
|
9
|
+
isWritableRegionRegistry,
|
|
10
|
+
REGION_CONTENT_CAPABILITY,
|
|
11
|
+
REGION_DATA_SOURCES_ATTRIBUTE,
|
|
12
|
+
REGION_REGISTRY_ATTRIBUTE,
|
|
13
|
+
REGION_REGISTRY_SLUG,
|
|
14
|
+
REGION_REGISTRY_VERSION,
|
|
15
|
+
REGION_SLUG_INFIX,
|
|
16
|
+
regionFromLocale,
|
|
17
|
+
regionHasPlaceholder,
|
|
18
|
+
marketPlaceholderSlugs,
|
|
19
|
+
regionSlug,
|
|
20
|
+
readRegionDataSources,
|
|
21
|
+
readRegionRegistry,
|
|
22
|
+
serializeRegionRegistry
|
|
23
|
+
} from '../region-contract';
|
|
24
|
+
import type { RegionRegistry, RegionRegistryRead } from '../region-contract';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* ZERO-5391 — the contract both sides of per-country ("region") content read
|
|
28
|
+
* from: which country a locale belongs to, where a country's copy of an area
|
|
29
|
+
* lives, and how the `theme-regions` registry and a copy's own data-source
|
|
30
|
+
* records are parsed. The storefront and the editor must agree on every one of
|
|
31
|
+
* these, so the file is kept as two byte-identical copies and the last test
|
|
32
|
+
* compares them.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const registryOf = (read: RegionRegistryRead): RegionRegistry => {
|
|
36
|
+
if (read.kind !== 'found') {
|
|
37
|
+
throw new Error(`expected a found registry, got ${JSON.stringify(read)}`);
|
|
38
|
+
}
|
|
39
|
+
return read.registry;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const SAMPLE = {
|
|
43
|
+
version: 1,
|
|
44
|
+
regions: {
|
|
45
|
+
ae: {
|
|
46
|
+
placeholders: {
|
|
47
|
+
header: { updatedAt: '2026-11-02T10:00:00.000Z' },
|
|
48
|
+
'theme-homepage-body': {}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
describe('region contract — wire names', () => {
|
|
55
|
+
it('pins every name the storefront and the editor exchange', () => {
|
|
56
|
+
expect(REGION_SLUG_INFIX).toBe('-region-');
|
|
57
|
+
expect(REGION_REGISTRY_SLUG).toBe('theme-regions');
|
|
58
|
+
expect(REGION_REGISTRY_ATTRIBUTE).toBe('theme_regions');
|
|
59
|
+
expect(REGION_REGISTRY_VERSION).toBe(1);
|
|
60
|
+
expect(REGION_CONTENT_CAPABILITY).toBe('region-content');
|
|
61
|
+
expect(REGION_DATA_SOURCES_ATTRIBUTE).toBe('theme_region_dataSources');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('regionFromLocale', () => {
|
|
66
|
+
it.each([
|
|
67
|
+
['en-ae', 'ae'],
|
|
68
|
+
['ar-ae', 'ae'],
|
|
69
|
+
['AR-AE', 'ae'],
|
|
70
|
+
[' en-ae ', 'ae'],
|
|
71
|
+
['en-us', 'us'],
|
|
72
|
+
['zh-hant-tw', 'tw']
|
|
73
|
+
])('reads the country of %p as %p', (locale: string, region: string) => {
|
|
74
|
+
expect(regionFromLocale(locale)).toBe(region);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it.each([['en'], ['tr'], ['zh-hant'], ['es-419'], ['en-'], ['']])(
|
|
78
|
+
'gives no region for %p',
|
|
79
|
+
(locale: string) => {
|
|
80
|
+
expect(regionFromLocale(locale)).toBe('');
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
it.each([[undefined], [null], [42], [{ value: 'en-ae' }]])(
|
|
85
|
+
'gives no region for the non-string %p',
|
|
86
|
+
(locale: unknown) => {
|
|
87
|
+
expect(regionFromLocale(locale)).toBe('');
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('isValidRegionCode', () => {
|
|
93
|
+
it('accepts two lowercase letters', () => {
|
|
94
|
+
expect(isValidRegionCode('ae')).toBe(true);
|
|
95
|
+
expect(isValidRegionCode('tr')).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it.each([
|
|
99
|
+
['AE'],
|
|
100
|
+
['uae'],
|
|
101
|
+
['a1'],
|
|
102
|
+
['__proto__'],
|
|
103
|
+
['a'],
|
|
104
|
+
[''],
|
|
105
|
+
[undefined],
|
|
106
|
+
[7]
|
|
107
|
+
])('rejects %p', (code: unknown) => {
|
|
108
|
+
expect(isValidRegionCode(code)).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('regionSlug', () => {
|
|
113
|
+
it('suffixes the canonical slug with the region', () => {
|
|
114
|
+
expect(regionSlug('header', 'ae')).toBe('header-region-ae');
|
|
115
|
+
expect(regionSlug('page-faq', 'sa')).toBe('page-faq-region-sa');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('composes with a preview suffix outermost', () => {
|
|
119
|
+
expect(`${regionSlug('header', 'ae')}-preview-draft`).toBe(
|
|
120
|
+
'header-region-ae-preview-draft'
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('isRegionReservedSlug', () => {
|
|
126
|
+
it('reserves every slug carrying the infix', () => {
|
|
127
|
+
expect(isRegionReservedSlug(regionSlug('header', 'ae'))).toBe(true);
|
|
128
|
+
expect(isRegionReservedSlug('page-super-region-deals')).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('leaves slugs without the full infix alone', () => {
|
|
132
|
+
expect(isRegionReservedSlug('header')).toBe(false);
|
|
133
|
+
expect(isRegionReservedSlug('superregion')).toBe(false);
|
|
134
|
+
expect(isRegionReservedSlug('header-regions')).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('sees a custom page through its placeholder slug', () => {
|
|
138
|
+
// The page slug passes; the placeholder `page-<slug>` does not, which is
|
|
139
|
+
// why custom page slugs are validated as `page-${slug}`.
|
|
140
|
+
expect(isRegionReservedSlug('region-days')).toBe(false);
|
|
141
|
+
expect(isRegionReservedSlug('page-region-days')).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('isRegionRegistrableSlug', () => {
|
|
146
|
+
const REGISTRABLE = [
|
|
147
|
+
'header',
|
|
148
|
+
'footer',
|
|
149
|
+
'theme-homepage-body',
|
|
150
|
+
'page-faq',
|
|
151
|
+
'modal-1a2b3c',
|
|
152
|
+
'server-error-body',
|
|
153
|
+
'0-leading-digit'
|
|
154
|
+
];
|
|
155
|
+
const UNREGISTRABLE = [
|
|
156
|
+
'page-region-days',
|
|
157
|
+
'header-region-ae',
|
|
158
|
+
'promo_bar',
|
|
159
|
+
'Header',
|
|
160
|
+
'-header',
|
|
161
|
+
'page faq',
|
|
162
|
+
'',
|
|
163
|
+
'__proto__'
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
it.each(REGISTRABLE.map((slug: string) => [slug]))(
|
|
167
|
+
'accepts %p',
|
|
168
|
+
(slug: string) => {
|
|
169
|
+
expect(isRegionRegistrableSlug(slug)).toBe(true);
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
it.each(UNREGISTRABLE.map((slug: string) => [slug]))(
|
|
174
|
+
'refuses %p',
|
|
175
|
+
(slug: string) => {
|
|
176
|
+
expect(isRegionRegistrableSlug(slug)).toBe(false);
|
|
177
|
+
}
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
it.each([[undefined], [null], [42], [['header']]])(
|
|
181
|
+
'refuses the non-string %p',
|
|
182
|
+
(slug: unknown) => {
|
|
183
|
+
expect(isRegionRegistrableSlug(slug)).toBe(false);
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
it('accepts exactly the slugs a registry read keeps', () => {
|
|
188
|
+
const all = [...REGISTRABLE, ...UNREGISTRABLE];
|
|
189
|
+
// Own keys, `__proto__` included (an assignment would set the prototype
|
|
190
|
+
// instead), serialised the way the widget stores them.
|
|
191
|
+
const placeholders = Object.fromEntries(
|
|
192
|
+
all.map((slug: string) => [slug, {}])
|
|
193
|
+
);
|
|
194
|
+
const registry = registryOf(
|
|
195
|
+
readRegionRegistry(
|
|
196
|
+
JSON.stringify({ version: 1, regions: { ae: { placeholders } } })
|
|
197
|
+
)
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
for (const slug of all) {
|
|
201
|
+
expect([slug, regionHasPlaceholder(registry, 'ae', slug)]).toEqual([
|
|
202
|
+
slug,
|
|
203
|
+
isRegionRegistrableSlug(slug)
|
|
204
|
+
]);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('readRegionRegistry', () => {
|
|
210
|
+
it.each([[undefined], [null], [''], [{ value: '' }], [{ value: null }]])(
|
|
211
|
+
'reads %p as absent',
|
|
212
|
+
(raw: unknown) => {
|
|
213
|
+
expect(readRegionRegistry(raw)).toEqual({ kind: 'absent' });
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
it('reads a JSON string', () => {
|
|
218
|
+
expect(readRegionRegistry(JSON.stringify(SAMPLE))).toEqual({
|
|
219
|
+
kind: 'found',
|
|
220
|
+
registry: SAMPLE
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('reads the commerce {value, kwargs} wrapping', () => {
|
|
225
|
+
expect(
|
|
226
|
+
readRegionRegistry({ value: JSON.stringify(SAMPLE), kwargs: {} })
|
|
227
|
+
).toEqual({ kind: 'found', registry: SAMPLE });
|
|
228
|
+
expect(readRegionRegistry({ value: SAMPLE, kwargs: {} })).toEqual({
|
|
229
|
+
kind: 'found',
|
|
230
|
+
registry: SAMPLE
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('reads an already parsed object without keeping a reference to it', () => {
|
|
235
|
+
const read = readRegionRegistry(SAMPLE);
|
|
236
|
+
|
|
237
|
+
expect(read).toEqual({ kind: 'found', registry: SAMPLE });
|
|
238
|
+
expect(registryOf(read)).not.toBe(SAMPLE);
|
|
239
|
+
expect(registryOf(read).regions.ae).not.toBe(SAMPLE.regions.ae);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('reads malformed JSON as unreadable', () => {
|
|
243
|
+
expect(readRegionRegistry('{nope')).toEqual({
|
|
244
|
+
kind: 'unreadable',
|
|
245
|
+
reason: 'malformed-json'
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it.each([['[]'], ['"text"'], [42], [true]])(
|
|
250
|
+
'reads the non-object %p as unreadable',
|
|
251
|
+
(raw: unknown) => {
|
|
252
|
+
expect(readRegionRegistry(raw)).toEqual({
|
|
253
|
+
kind: 'unreadable',
|
|
254
|
+
reason: 'not-an-object'
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
it.each([
|
|
260
|
+
[{ regions: {} }],
|
|
261
|
+
[{ version: 0, regions: {} }],
|
|
262
|
+
[{ version: '1', regions: {} }],
|
|
263
|
+
[{ version: NaN, regions: {} }]
|
|
264
|
+
])('reads %p as a bad version', (raw: unknown) => {
|
|
265
|
+
expect(readRegionRegistry(raw)).toEqual({
|
|
266
|
+
kind: 'unreadable',
|
|
267
|
+
reason: 'bad-version'
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it.each([
|
|
272
|
+
[{ version: 1 }],
|
|
273
|
+
[{ version: 1, regions: null }],
|
|
274
|
+
[{ version: 1, regions: [] }],
|
|
275
|
+
[{ version: 1, regions: 'ae' }]
|
|
276
|
+
])('reads %p as having no regions', (raw: unknown) => {
|
|
277
|
+
expect(readRegionRegistry(raw)).toEqual({
|
|
278
|
+
kind: 'unreadable',
|
|
279
|
+
reason: 'no-regions'
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('drops invalid codes and slugs while keeping their siblings', () => {
|
|
284
|
+
const raw = JSON.stringify({
|
|
285
|
+
version: 1,
|
|
286
|
+
regions: {
|
|
287
|
+
ae: {
|
|
288
|
+
placeholders: {
|
|
289
|
+
header: {},
|
|
290
|
+
Header: {},
|
|
291
|
+
'header-region-ae': {},
|
|
292
|
+
['__proto__']: {},
|
|
293
|
+
footer: {}
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
AE: { placeholders: { header: {} } },
|
|
297
|
+
uae: { placeholders: { header: {} } },
|
|
298
|
+
['__proto__']: { placeholders: { header: {} } },
|
|
299
|
+
sa: { placeholders: { 'page-faq': {} } },
|
|
300
|
+
tr: 'not-an-entry',
|
|
301
|
+
us: { placeholders: ['header'] },
|
|
302
|
+
de: {}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
const registry = registryOf(readRegionRegistry(raw));
|
|
306
|
+
|
|
307
|
+
expect(registry).toEqual({
|
|
308
|
+
version: 1,
|
|
309
|
+
regions: {
|
|
310
|
+
ae: { placeholders: { header: {}, footer: {} } },
|
|
311
|
+
sa: { placeholders: { 'page-faq': {} } }
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
expect(Object.getPrototypeOf(registry.regions)).toBe(Object.prototype);
|
|
315
|
+
expect(Object.getPrototypeOf(registry.regions.ae.placeholders)).toBe(
|
|
316
|
+
Object.prototype
|
|
317
|
+
);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('keeps updatedAt only when it is a string, and drops other entry keys', () => {
|
|
321
|
+
const registry = registryOf(
|
|
322
|
+
readRegionRegistry({
|
|
323
|
+
version: 1,
|
|
324
|
+
regions: {
|
|
325
|
+
ae: {
|
|
326
|
+
placeholders: {
|
|
327
|
+
header: { updatedAt: '2026-11-02T10:00:00.000Z', live: true },
|
|
328
|
+
footer: { updatedAt: 1762077600000 },
|
|
329
|
+
'page-faq': { updatedAt: null },
|
|
330
|
+
'modal-1a2b3c': 'not-an-object'
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
expect(registry.regions.ae.placeholders).toEqual({
|
|
338
|
+
header: { updatedAt: '2026-11-02T10:00:00.000Z' },
|
|
339
|
+
footer: {},
|
|
340
|
+
'page-faq': {},
|
|
341
|
+
'modal-1a2b3c': {}
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('reads a later version that only added keys', () => {
|
|
346
|
+
const read = readRegionRegistry(
|
|
347
|
+
JSON.stringify({
|
|
348
|
+
version: 2,
|
|
349
|
+
drafts: { ae: { placeholders: { footer: {} } } },
|
|
350
|
+
regions: {
|
|
351
|
+
ae: {
|
|
352
|
+
placeholders: { header: { updatedAt: 'x', live: false } },
|
|
353
|
+
locales: ['en-ae']
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
expect(read).toEqual({
|
|
360
|
+
kind: 'found',
|
|
361
|
+
registry: {
|
|
362
|
+
version: 2,
|
|
363
|
+
regions: { ae: { placeholders: { header: { updatedAt: 'x' } } } }
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe('regionHasPlaceholder and marketPlaceholderSlugs', () => {
|
|
370
|
+
const registry = registryOf(readRegionRegistry(SAMPLE));
|
|
371
|
+
|
|
372
|
+
it('list the placeholders a region has its own copy of', () => {
|
|
373
|
+
expect(marketPlaceholderSlugs(registry, 'ae')).toEqual([
|
|
374
|
+
'header',
|
|
375
|
+
'theme-homepage-body'
|
|
376
|
+
]);
|
|
377
|
+
expect(regionHasPlaceholder(registry, 'ae', 'header')).toBe(true);
|
|
378
|
+
expect(regionHasPlaceholder(registry, 'ae', 'footer')).toBe(false);
|
|
379
|
+
expect(marketPlaceholderSlugs(registry, 'sa')).toEqual([]);
|
|
380
|
+
expect(regionHasPlaceholder(registry, 'sa', 'header')).toBe(false);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('use own properties only', () => {
|
|
384
|
+
for (const key of ['constructor', 'toString', '__proto__']) {
|
|
385
|
+
expect(marketPlaceholderSlugs(registry, key)).toEqual([]);
|
|
386
|
+
expect(regionHasPlaceholder(registry, key, 'header')).toBe(false);
|
|
387
|
+
expect(regionHasPlaceholder(registry, 'ae', key)).toBe(false);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
describe('isWritableRegionRegistry and serializeRegionRegistry', () => {
|
|
393
|
+
it('writes only registries of this version or older', () => {
|
|
394
|
+
expect(isWritableRegionRegistry(emptyRegionRegistry())).toBe(true);
|
|
395
|
+
expect(isWritableRegionRegistry({ version: 1, regions: {} })).toBe(true);
|
|
396
|
+
expect(isWritableRegionRegistry({ version: 2, regions: {} })).toBe(false);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('stamps the current version, which is why a later one must not be written', () => {
|
|
400
|
+
expect(
|
|
401
|
+
JSON.parse(serializeRegionRegistry({ version: 2, regions: {} })).version
|
|
402
|
+
).toBe(REGION_REGISTRY_VERSION);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it('serialises the empty registry', () => {
|
|
406
|
+
expect(serializeRegionRegistry(emptyRegionRegistry())).toBe(
|
|
407
|
+
'{"version":1,"regions":{}}'
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('round-trips through readRegionRegistry', () => {
|
|
412
|
+
const registry = registryOf(readRegionRegistry(SAMPLE));
|
|
413
|
+
const serialized = serializeRegionRegistry(registry);
|
|
414
|
+
|
|
415
|
+
expect(readRegionRegistry(serialized)).toEqual({ kind: 'found', registry });
|
|
416
|
+
expect(readRegionRegistry({ value: serialized, kwargs: {} })).toEqual({
|
|
417
|
+
kind: 'found',
|
|
418
|
+
registry
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe('readRegionDataSources', () => {
|
|
424
|
+
const RECORDS = [
|
|
425
|
+
{ id: 'ds-9', name: 'Bestsellers (AE)', region: 'ae' },
|
|
426
|
+
{ id: 'ds-10', name: 'New in (AE)', region: 'ae' }
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
it('reads a JSON string', () => {
|
|
430
|
+
expect(readRegionDataSources(JSON.stringify(RECORDS))).toEqual(RECORDS);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('reads the commerce {value, kwargs} wrapping', () => {
|
|
434
|
+
expect(
|
|
435
|
+
readRegionDataSources({ value: JSON.stringify(RECORDS), kwargs: {} })
|
|
436
|
+
).toEqual(RECORDS);
|
|
437
|
+
expect(readRegionDataSources({ value: RECORDS, kwargs: {} })).toEqual(
|
|
438
|
+
RECORDS
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('reads an already parsed array into a new array', () => {
|
|
443
|
+
const read = readRegionDataSources(RECORDS);
|
|
444
|
+
|
|
445
|
+
expect(read).toEqual(RECORDS);
|
|
446
|
+
expect(read).not.toBe(RECORDS);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('returns the elements unvalidated', () => {
|
|
450
|
+
expect(readRegionDataSources('[1,null,"x",{"id":"ds-1"}]')).toEqual([
|
|
451
|
+
1,
|
|
452
|
+
null,
|
|
453
|
+
'x',
|
|
454
|
+
{ id: 'ds-1' }
|
|
455
|
+
]);
|
|
456
|
+
expect(readRegionDataSources('[]')).toEqual([]);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it.each([
|
|
460
|
+
[undefined],
|
|
461
|
+
[null],
|
|
462
|
+
[''],
|
|
463
|
+
['{nope'],
|
|
464
|
+
['{}'],
|
|
465
|
+
['"text"'],
|
|
466
|
+
['null'],
|
|
467
|
+
[42],
|
|
468
|
+
[true],
|
|
469
|
+
[{}],
|
|
470
|
+
[{ id: 'ds-1' }],
|
|
471
|
+
[{ value: null }],
|
|
472
|
+
[{ value: '{nope' }],
|
|
473
|
+
[{ value: { id: 'ds-1' } }]
|
|
474
|
+
])('reads %p as no records', (raw: unknown) => {
|
|
475
|
+
expect(readRegionDataSources(raw)).toEqual([]);
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
describe('region contract — the editor copy', () => {
|
|
480
|
+
const SOURCE = path.resolve(__dirname, '../region-contract.ts');
|
|
481
|
+
// Sibling checkouts: this machine's name for the editor first, then the
|
|
482
|
+
// name a default clone of its repository gets.
|
|
483
|
+
const EDITOR = [
|
|
484
|
+
'../../../../../../pz-dynamic-widget/src/services/region/region-contract.ts',
|
|
485
|
+
'../../../../../../pz-theme-editor/src/services/region/region-contract.ts'
|
|
486
|
+
]
|
|
487
|
+
.map((relative: string) => path.resolve(__dirname, relative))
|
|
488
|
+
.find((candidate: string) => fs.existsSync(candidate));
|
|
489
|
+
|
|
490
|
+
// Skipped, not failed, where the editor is not checked out beside pz-next
|
|
491
|
+
// (CI builds this repo alone). The reads stay inside it() so a missing file
|
|
492
|
+
// can never fail the collection of the tests above.
|
|
493
|
+
(EDITOR ? it : it.skip)('is byte-identical to this copy', () => {
|
|
494
|
+
const storefront = fs.readFileSync(SOURCE);
|
|
495
|
+
const editor = fs.readFileSync(EDITOR as string);
|
|
496
|
+
|
|
497
|
+
expect(editor.toString('utf8')).toBe(storefront.toString('utf8'));
|
|
498
|
+
expect(editor.equals(storefront)).toBe(true);
|
|
499
|
+
});
|
|
500
|
+
});
|