fontdue-js 2.19.0 → 2.19.2

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,1634 @@
1
+ "use strict";
2
+
3
+ var _vitest = require("vitest");
4
+ var _utils = require("../utils");
5
+ var _reducer = _interopRequireWildcard(require("../reducer"));
6
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
7
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
8
+ // Mock react-relay's graphql tag since productState.ts uses it at import time
9
+ _vitest.vi.mock('react-relay', () => ({
10
+ graphql: () => null,
11
+ fetchQuery: _vitest.vi.fn()
12
+ }));
13
+ // Helper to build a minimal FontdueState for testing
14
+ function makeState() {
15
+ let overrides = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
16
+ return {
17
+ stylesheets: [],
18
+ cartOpen: false,
19
+ precartOpen: false,
20
+ selectedSkuIds: {},
21
+ licenseOptions: [],
22
+ collectionStyleSkus: {},
23
+ collectionSkus: {},
24
+ skuPrices: {},
25
+ fetchedCollectionIds: [],
26
+ storeModalRoute: {
27
+ name: 'index'
28
+ },
29
+ storeModalHistory: [],
30
+ orderVariableSelections: [],
31
+ licenseeIsBillingIdentity: null,
32
+ ...overrides
33
+ };
34
+ }
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Shared fixtures — reusable across tests that need the full
38
+ // "superfamily + 3 families + 3 bundles" topology
39
+ // ---------------------------------------------------------------------------
40
+ function makeThunderState() {
41
+ let overrides = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
42
+ return makeState({
43
+ collectionStyleSkus: {
44
+ // Superfamily
45
+ 'super-sku': {
46
+ fontStyleSkuIds: ['s1', 's2', 'ds1', 'ds2', 'cs1', 'cs2', 'vs1', 'vis1', 'dvs1', 'dvis1', 'cvs1', 'cvis1'],
47
+ fontStyleIds: ['f1', 'f2', 'df1', 'df2', 'cf1', 'cf2', 'vf1', 'vif1', 'dvf1', 'dvif1', 'cvf1', 'cvif1'],
48
+ childrenSkuIds: ['text-sku', 'display-sku', 'caption-sku', 'text-var-sku', 'text-var-italic-sku', 'display-var-sku', 'display-var-italic-sku', 'caption-var-sku', 'caption-var-italic-sku', 'text-bundle-sku', 'display-bundle-sku', 'caption-bundle-sku'],
49
+ name: 'Thunder Text Collection'
50
+ },
51
+ // Static families
52
+ 'text-sku': {
53
+ fontStyleSkuIds: ['s1', 's2'],
54
+ fontStyleIds: ['f1', 'f2'],
55
+ childrenSkuIds: [],
56
+ name: 'Thunder Text'
57
+ },
58
+ 'display-sku': {
59
+ fontStyleSkuIds: ['ds1', 'ds2'],
60
+ fontStyleIds: ['df1', 'df2'],
61
+ childrenSkuIds: [],
62
+ name: 'Thunder Display'
63
+ },
64
+ 'caption-sku': {
65
+ fontStyleSkuIds: ['cs1', 'cs2'],
66
+ fontStyleIds: ['cf1', 'cf2'],
67
+ childrenSkuIds: [],
68
+ name: 'Thunder Caption'
69
+ },
70
+ // Variable families
71
+ 'text-var-sku': {
72
+ fontStyleSkuIds: ['vs1'],
73
+ fontStyleIds: ['vf1'],
74
+ childrenSkuIds: [],
75
+ name: 'Thunder Text Variable'
76
+ },
77
+ 'text-var-italic-sku': {
78
+ fontStyleSkuIds: ['vis1'],
79
+ fontStyleIds: ['vif1'],
80
+ childrenSkuIds: [],
81
+ name: 'Thunder Text Italic Variable'
82
+ },
83
+ 'display-var-sku': {
84
+ fontStyleSkuIds: ['dvs1'],
85
+ fontStyleIds: ['dvf1'],
86
+ childrenSkuIds: [],
87
+ name: 'Thunder Display Variable'
88
+ },
89
+ 'display-var-italic-sku': {
90
+ fontStyleSkuIds: ['dvis1'],
91
+ fontStyleIds: ['dvif1'],
92
+ childrenSkuIds: [],
93
+ name: 'Thunder Display Italic Variable'
94
+ },
95
+ 'caption-var-sku': {
96
+ fontStyleSkuIds: ['cvs1'],
97
+ fontStyleIds: ['cvf1'],
98
+ childrenSkuIds: [],
99
+ name: 'Thunder Caption Variable'
100
+ },
101
+ 'caption-var-italic-sku': {
102
+ fontStyleSkuIds: ['cvis1'],
103
+ fontStyleIds: ['cvif1'],
104
+ childrenSkuIds: [],
105
+ name: 'Thunder Caption Italic Variable'
106
+ },
107
+ // Collection bundles
108
+ 'text-bundle-sku': {
109
+ fontStyleSkuIds: ['s1', 's2', 'vs1', 'vis1'],
110
+ fontStyleIds: ['f1', 'f2', 'vf1', 'vif1'],
111
+ childrenSkuIds: ['text-sku', 'text-var-sku', 'text-var-italic-sku'],
112
+ name: 'Thunder Text Family + Variable'
113
+ },
114
+ 'display-bundle-sku': {
115
+ fontStyleSkuIds: ['ds1', 'ds2', 'dvs1', 'dvis1'],
116
+ fontStyleIds: ['df1', 'df2', 'dvf1', 'dvif1'],
117
+ childrenSkuIds: ['display-sku', 'display-var-sku', 'display-var-italic-sku'],
118
+ name: 'Thunder Display Family + Variable'
119
+ },
120
+ 'caption-bundle-sku': {
121
+ fontStyleSkuIds: ['cs1', 'cs2', 'cvs1', 'cvis1'],
122
+ fontStyleIds: ['cf1', 'cf2', 'cvf1', 'cvif1'],
123
+ childrenSkuIds: ['caption-sku', 'caption-var-sku', 'caption-var-italic-sku'],
124
+ name: 'Thunder Caption Family + Variable'
125
+ }
126
+ },
127
+ skuPrices: {
128
+ 'super-sku': 750,
129
+ 'text-sku': 500,
130
+ 'display-sku': 500,
131
+ 'caption-sku': 500,
132
+ 'text-var-sku': 300,
133
+ 'text-var-italic-sku': 300,
134
+ 'display-var-sku': 300,
135
+ 'display-var-italic-sku': 300,
136
+ 'caption-var-sku': 300,
137
+ 'caption-var-italic-sku': 300,
138
+ 'text-bundle-sku': 500,
139
+ 'display-bundle-sku': 500,
140
+ 'caption-bundle-sku': 500,
141
+ s1: 40,
142
+ s2: 40,
143
+ ds1: 40,
144
+ ds2: 40,
145
+ cs1: 40,
146
+ cs2: 40,
147
+ vs1: 40,
148
+ vis1: 40,
149
+ dvs1: 40,
150
+ dvis1: 40,
151
+ cvs1: 40,
152
+ cvis1: 40
153
+ },
154
+ ...overrides
155
+ });
156
+ }
157
+
158
+ // =========================================================================
159
+ // collContainsSkuId
160
+ // =========================================================================
161
+ (0, _vitest.describe)('collContainsSkuId', () => {
162
+ (0, _vitest.it)('returns true when skuId is in childrenSkuIds', () => {
163
+ const coll = {
164
+ fontStyleSkuIds: [],
165
+ fontStyleIds: [],
166
+ childrenSkuIds: ['child-sku-1', 'child-sku-2'],
167
+ name: 'Test'
168
+ };
169
+ (0, _vitest.expect)((0, _utils.collContainsSkuId)(coll, 'child-sku-1')).toBe(true);
170
+ });
171
+ (0, _vitest.it)('returns true when skuId is in fontStyleSkuIds', () => {
172
+ const coll = {
173
+ fontStyleSkuIds: ['style-sku-1'],
174
+ fontStyleIds: [],
175
+ childrenSkuIds: [],
176
+ name: 'Test'
177
+ };
178
+ (0, _vitest.expect)((0, _utils.collContainsSkuId)(coll, 'style-sku-1')).toBe(true);
179
+ });
180
+ (0, _vitest.it)('returns true when id is in fontStyleIds', () => {
181
+ const coll = {
182
+ fontStyleSkuIds: [],
183
+ fontStyleIds: ['style-id-1'],
184
+ childrenSkuIds: [],
185
+ name: 'Test'
186
+ };
187
+ (0, _vitest.expect)((0, _utils.collContainsSkuId)(coll, 'style-id-1')).toBe(true);
188
+ });
189
+ (0, _vitest.it)('returns false for unknown skuId', () => {
190
+ const coll = {
191
+ fontStyleSkuIds: ['style-sku-1'],
192
+ fontStyleIds: ['style-1'],
193
+ childrenSkuIds: ['child-sku-1'],
194
+ name: 'Test'
195
+ };
196
+ (0, _vitest.expect)((0, _utils.collContainsSkuId)(coll, 'unknown-sku')).toBe(false);
197
+ });
198
+ (0, _vitest.it)('returns false for undefined collection', () => {
199
+ (0, _vitest.expect)((0, _utils.collContainsSkuId)(undefined, 'any-sku')).toBe(false);
200
+ });
201
+ });
202
+
203
+ // =========================================================================
204
+ // isSelected
205
+ // =========================================================================
206
+ (0, _vitest.describe)('isSelected', () => {
207
+ (0, _vitest.it)('returns true when the SKU is directly selected', () => {
208
+ const state = makeState({
209
+ selectedSkuIds: {
210
+ 'sku-1': true
211
+ }
212
+ });
213
+ (0, _vitest.expect)((0, _utils.isSelected)('sku-1')(state)).toBe(true);
214
+ });
215
+ (0, _vitest.it)('returns false when the SKU is explicitly deselected (false)', () => {
216
+ const state = makeState({
217
+ selectedSkuIds: {
218
+ 'sku-1': false
219
+ }
220
+ });
221
+ (0, _vitest.expect)((0, _utils.isSelected)('sku-1')(state)).toBe(false);
222
+ });
223
+ (0, _vitest.it)('returns true when a parent collection is selected', () => {
224
+ const state = makeState({
225
+ selectedSkuIds: {
226
+ 'bundle-sku': true
227
+ },
228
+ collectionStyleSkus: {
229
+ 'bundle-sku': {
230
+ fontStyleSkuIds: ['s1'],
231
+ fontStyleIds: ['f1'],
232
+ childrenSkuIds: ['family-sku'],
233
+ name: 'Bundle'
234
+ }
235
+ }
236
+ });
237
+ // Style SKU contained via fontStyleSkuIds
238
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(state)).toBe(true);
239
+ // Family SKU contained via childrenSkuIds
240
+ (0, _vitest.expect)((0, _utils.isSelected)('family-sku')(state)).toBe(true);
241
+ // Font style ID contained via fontStyleIds
242
+ (0, _vitest.expect)((0, _utils.isSelected)('f1')(state)).toBe(true);
243
+ });
244
+ (0, _vitest.it)('returns false when a parent collection is deselected', () => {
245
+ const state = makeState({
246
+ selectedSkuIds: {
247
+ 'bundle-sku': false,
248
+ 's1': false
249
+ },
250
+ collectionStyleSkus: {
251
+ 'bundle-sku': {
252
+ fontStyleSkuIds: ['s1'],
253
+ fontStyleIds: ['f1'],
254
+ childrenSkuIds: ['family-sku'],
255
+ name: 'Bundle'
256
+ }
257
+ }
258
+ });
259
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(state)).toBe(false);
260
+ (0, _vitest.expect)((0, _utils.isSelected)('family-sku')(state)).toBe(false);
261
+ });
262
+ (0, _vitest.it)('returns false for null skuId', () => {
263
+ const state = makeState();
264
+ (0, _vitest.expect)((0, _utils.isSelected)(null)(state)).toBe(false);
265
+ });
266
+ (0, _vitest.it)('returns true for member when bundle is selected, even if member is explicitly false', () => {
267
+ // After auto-selection, members are set to false but should still appear
268
+ // selected because the parent bundle is true
269
+ const state = makeState({
270
+ selectedSkuIds: {
271
+ 'bundle-sku': true,
272
+ 'family-sku': false,
273
+ 'var-sku': false
274
+ },
275
+ collectionStyleSkus: {
276
+ 'bundle-sku': {
277
+ fontStyleSkuIds: ['s1', 'vs1'],
278
+ fontStyleIds: ['f1', 'vf1'],
279
+ childrenSkuIds: ['family-sku', 'var-sku'],
280
+ name: 'Bundle'
281
+ }
282
+ }
283
+ });
284
+ (0, _vitest.expect)((0, _utils.isSelected)('family-sku')(state)).toBe(true);
285
+ (0, _vitest.expect)((0, _utils.isSelected)('var-sku')(state)).toBe(true);
286
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(state)).toBe(true);
287
+ });
288
+ });
289
+
290
+ // =========================================================================
291
+ // collectionSkuIdsDifferences
292
+ // =========================================================================
293
+ (0, _vitest.describe)('collectionSkuIdsDifferences', () => {
294
+ (0, _vitest.it)('includes self when skuId matches a collection', () => {
295
+ const state = makeState({
296
+ collectionStyleSkus: {
297
+ 'family-sku': {
298
+ fontStyleSkuIds: ['s1', 's2'],
299
+ fontStyleIds: ['f1', 'f2'],
300
+ childrenSkuIds: [],
301
+ name: 'Family'
302
+ }
303
+ },
304
+ skuPrices: {
305
+ 'family-sku': 500,
306
+ s1: 40,
307
+ s2: 40
308
+ }
309
+ });
310
+ const diffs = (0, _utils.collectionSkuIdsDifferences)(state, 'family-sku');
311
+ (0, _vitest.expect)(diffs).toHaveProperty('family-sku');
312
+ (0, _vitest.expect)(diffs['family-sku']).toBe(500);
313
+ });
314
+ (0, _vitest.it)('includes parent collections that contain the skuId', () => {
315
+ const state = makeState({
316
+ collectionStyleSkus: {
317
+ 'family-sku': {
318
+ fontStyleSkuIds: ['s1', 's2'],
319
+ fontStyleIds: ['f1', 'f2'],
320
+ childrenSkuIds: [],
321
+ name: 'Family'
322
+ },
323
+ 'bundle-sku': {
324
+ fontStyleSkuIds: ['s1', 's2', 'vs1'],
325
+ fontStyleIds: ['f1', 'f2', 'vf1'],
326
+ childrenSkuIds: ['family-sku', 'var-family-sku'],
327
+ name: 'Bundle'
328
+ }
329
+ },
330
+ skuPrices: {
331
+ 'family-sku': 500,
332
+ 'bundle-sku': 500,
333
+ 'var-family-sku': 300,
334
+ s1: 40,
335
+ s2: 40,
336
+ vs1: 40
337
+ }
338
+ });
339
+ const diffs = (0, _utils.collectionSkuIdsDifferences)(state, 'family-sku');
340
+ (0, _vitest.expect)(diffs).toHaveProperty('family-sku');
341
+ (0, _vitest.expect)(diffs).toHaveProperty('bundle-sku');
342
+ });
343
+ (0, _vitest.it)('subtracts already-selected items from the difference', () => {
344
+ const state = makeState({
345
+ selectedSkuIds: {
346
+ 'family-sku': true
347
+ },
348
+ collectionStyleSkus: {
349
+ 'family-sku': {
350
+ fontStyleSkuIds: ['s1', 's2'],
351
+ fontStyleIds: ['f1', 'f2'],
352
+ childrenSkuIds: [],
353
+ name: 'Family'
354
+ },
355
+ 'bundle-sku': {
356
+ fontStyleSkuIds: ['s1', 's2', 'vs1'],
357
+ fontStyleIds: ['f1', 'f2', 'vf1'],
358
+ childrenSkuIds: ['family-sku', 'var-family-sku'],
359
+ name: 'Bundle'
360
+ }
361
+ },
362
+ skuPrices: {
363
+ 'family-sku': 500,
364
+ 'bundle-sku': 500,
365
+ 'var-family-sku': 300
366
+ }
367
+ });
368
+ const diffs = (0, _utils.collectionSkuIdsDifferences)(state, 'var-family-sku');
369
+ (0, _vitest.expect)(diffs['bundle-sku']).toBe(0);
370
+ });
371
+ (0, _vitest.it)('treats missing prices as 0 rather than producing NaN', () => {
372
+ const state = makeState({
373
+ selectedSkuIds: {
374
+ 'orphan-sku': true
375
+ },
376
+ collectionStyleSkus: {
377
+ 'family-sku': {
378
+ fontStyleSkuIds: ['orphan-sku'],
379
+ fontStyleIds: [],
380
+ childrenSkuIds: [],
381
+ name: 'Family'
382
+ }
383
+ },
384
+ skuPrices: {
385
+ 'family-sku': 500
386
+ // 'orphan-sku' deliberately missing
387
+ }
388
+ });
389
+ const diffs = (0, _utils.collectionSkuIdsDifferences)(state, 'orphan-sku');
390
+ // Missing price treated as 0, so difference = 500 - 0 = 500
391
+ (0, _vitest.expect)(diffs['family-sku']).toBe(500);
392
+ });
393
+ (0, _vitest.it)('only counts items that are selected true, not false', () => {
394
+ const state = makeState({
395
+ selectedSkuIds: {
396
+ 'family-sku': false
397
+ },
398
+ collectionStyleSkus: {
399
+ 'bundle-sku': {
400
+ fontStyleSkuIds: [],
401
+ fontStyleIds: [],
402
+ childrenSkuIds: ['family-sku'],
403
+ name: 'Bundle'
404
+ },
405
+ 'family-sku': {
406
+ fontStyleSkuIds: [],
407
+ fontStyleIds: [],
408
+ childrenSkuIds: [],
409
+ name: 'Family'
410
+ }
411
+ },
412
+ skuPrices: {
413
+ 'bundle-sku': 500,
414
+ 'family-sku': 300
415
+ }
416
+ });
417
+ const diffs = (0, _utils.collectionSkuIdsDifferences)(state, 'family-sku');
418
+ // family-sku is false, not true, so it shouldn't be subtracted
419
+ (0, _vitest.expect)(diffs['bundle-sku']).toBe(500);
420
+ });
421
+ (0, _vitest.it)('returns empty object when no collections contain the skuId', () => {
422
+ const state = makeState({
423
+ collectionStyleSkus: {
424
+ 'other-sku': {
425
+ fontStyleSkuIds: ['x1'],
426
+ fontStyleIds: ['xf1'],
427
+ childrenSkuIds: [],
428
+ name: 'Other'
429
+ }
430
+ },
431
+ skuPrices: {
432
+ 'other-sku': 100,
433
+ 'lonely-style': 40
434
+ }
435
+ });
436
+ const diffs = (0, _utils.collectionSkuIdsDifferences)(state, 'lonely-style');
437
+ (0, _vitest.expect)(Object.keys(diffs)).toHaveLength(0);
438
+ });
439
+ });
440
+
441
+ // =========================================================================
442
+ // collectionSkuIdWithDiscount
443
+ // =========================================================================
444
+ (0, _vitest.describe)('collectionSkuIdWithDiscount', () => {
445
+ (0, _vitest.it)('returns null when no collection is a better deal', () => {
446
+ const state = makeState({
447
+ collectionStyleSkus: {
448
+ 'family-sku': {
449
+ fontStyleSkuIds: ['s1'],
450
+ fontStyleIds: ['f1'],
451
+ childrenSkuIds: [],
452
+ name: 'Family'
453
+ }
454
+ },
455
+ skuPrices: {
456
+ s1: 40,
457
+ 'family-sku': 500
458
+ }
459
+ });
460
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 's1');
461
+ (0, _vitest.expect)(collId).toBe(null);
462
+ (0, _vitest.expect)(amount).toBe(null);
463
+ });
464
+ (0, _vitest.it)('returns parent collection when it is cheaper for individual style', () => {
465
+ const state = makeState({
466
+ selectedSkuIds: {
467
+ s1: true,
468
+ s2: true,
469
+ s3: true,
470
+ s4: true
471
+ },
472
+ collectionStyleSkus: {
473
+ 'family-sku': {
474
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
475
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
476
+ childrenSkuIds: [],
477
+ name: 'Family'
478
+ }
479
+ },
480
+ skuPrices: {
481
+ 'family-sku': 200,
482
+ s1: 40,
483
+ s2: 40,
484
+ s3: 40,
485
+ s4: 40,
486
+ s5: 40
487
+ }
488
+ });
489
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 's5');
490
+ (0, _vitest.expect)(collId).toBe('family-sku');
491
+ (0, _vitest.expect)(amount).toBe(40);
492
+ });
493
+ (0, _vitest.it)('prefers collection bundle over self when prices are equal', () => {
494
+ const state = makeState({
495
+ collectionStyleSkus: {
496
+ 'family-sku': {
497
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
498
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
499
+ childrenSkuIds: [],
500
+ name: 'Thunder Text'
501
+ },
502
+ 'var-family-sku': {
503
+ fontStyleSkuIds: ['vs1'],
504
+ fontStyleIds: ['vf1'],
505
+ childrenSkuIds: [],
506
+ name: 'Thunder Text Variable'
507
+ },
508
+ 'var-italic-sku': {
509
+ fontStyleSkuIds: ['vis1'],
510
+ fontStyleIds: ['vif1'],
511
+ childrenSkuIds: [],
512
+ name: 'Thunder Text Italic Variable'
513
+ },
514
+ 'bundle-sku': {
515
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'vs1', 'vis1'],
516
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'vf1', 'vif1'],
517
+ childrenSkuIds: ['family-sku', 'var-family-sku', 'var-italic-sku'],
518
+ name: 'Thunder Text Family + Variable'
519
+ }
520
+ },
521
+ skuPrices: {
522
+ 'family-sku': 500,
523
+ 'bundle-sku': 500,
524
+ 'var-family-sku': 300,
525
+ 'var-italic-sku': 300,
526
+ s1: 40,
527
+ s2: 40,
528
+ s3: 40,
529
+ s4: 40,
530
+ s5: 40,
531
+ vs1: 40,
532
+ vis1: 40
533
+ }
534
+ });
535
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'family-sku');
536
+ (0, _vitest.expect)(collId).toBe('bundle-sku');
537
+ (0, _vitest.expect)(amount).toBe(500);
538
+ });
539
+ (0, _vitest.it)('prefers collection bundle regardless of object key order', () => {
540
+ const state = makeState({
541
+ collectionStyleSkus: {
542
+ 'bundle-sku': {
543
+ fontStyleSkuIds: ['s1', 's2', 'vs1'],
544
+ fontStyleIds: ['f1', 'f2', 'vf1'],
545
+ childrenSkuIds: ['family-sku', 'var-family-sku'],
546
+ name: 'Bundle'
547
+ },
548
+ 'family-sku': {
549
+ fontStyleSkuIds: ['s1', 's2'],
550
+ fontStyleIds: ['f1', 'f2'],
551
+ childrenSkuIds: [],
552
+ name: 'Family'
553
+ }
554
+ },
555
+ skuPrices: {
556
+ 'family-sku': 500,
557
+ 'bundle-sku': 500,
558
+ 'var-family-sku': 300
559
+ }
560
+ });
561
+ const [collId] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'family-sku');
562
+ (0, _vitest.expect)(collId).toBe('bundle-sku');
563
+ });
564
+ (0, _vitest.it)('still returns self for display pricing (superfamily with no parent)', () => {
565
+ const state = makeState({
566
+ selectedSkuIds: {
567
+ 'family-sku': true
568
+ },
569
+ collectionStyleSkus: {
570
+ 'superfamily-sku': {
571
+ fontStyleSkuIds: ['s1', 's2', 'ds1', 'ds2'],
572
+ fontStyleIds: ['f1', 'f2', 'df1', 'df2'],
573
+ childrenSkuIds: ['family-sku', 'display-family-sku'],
574
+ name: 'Superfamily'
575
+ },
576
+ 'family-sku': {
577
+ fontStyleSkuIds: ['s1', 's2'],
578
+ fontStyleIds: ['f1', 'f2'],
579
+ childrenSkuIds: [],
580
+ name: 'Family'
581
+ },
582
+ 'display-family-sku': {
583
+ fontStyleSkuIds: ['ds1', 'ds2'],
584
+ fontStyleIds: ['df1', 'df2'],
585
+ childrenSkuIds: [],
586
+ name: 'Display'
587
+ }
588
+ },
589
+ skuPrices: {
590
+ 'superfamily-sku': 750,
591
+ 'family-sku': 500,
592
+ 'display-family-sku': 500
593
+ }
594
+ });
595
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'superfamily-sku');
596
+ (0, _vitest.expect)(collId).toBe('superfamily-sku');
597
+ (0, _vitest.expect)(amount).toBe(250);
598
+ });
599
+ (0, _vitest.it)('prefers bundle at $0 when family is already selected', () => {
600
+ const state = makeState({
601
+ selectedSkuIds: {
602
+ 'family-sku': true
603
+ },
604
+ collectionStyleSkus: {
605
+ 'family-sku': {
606
+ fontStyleSkuIds: ['s1', 's2'],
607
+ fontStyleIds: ['f1', 'f2'],
608
+ childrenSkuIds: [],
609
+ name: 'Family'
610
+ },
611
+ 'var-family-sku': {
612
+ fontStyleSkuIds: ['vs1'],
613
+ fontStyleIds: ['vf1'],
614
+ childrenSkuIds: [],
615
+ name: 'Variable'
616
+ },
617
+ 'bundle-sku': {
618
+ fontStyleSkuIds: ['s1', 's2', 'vs1'],
619
+ fontStyleIds: ['f1', 'f2', 'vf1'],
620
+ childrenSkuIds: ['family-sku', 'var-family-sku'],
621
+ name: 'Bundle'
622
+ }
623
+ },
624
+ skuPrices: {
625
+ 'family-sku': 500,
626
+ 'var-family-sku': 300,
627
+ 'bundle-sku': 500
628
+ }
629
+ });
630
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'var-family-sku');
631
+ (0, _vitest.expect)(collId).toBe('bundle-sku');
632
+ (0, _vitest.expect)(amount).toBe(0);
633
+ });
634
+ (0, _vitest.it)('does not upgrade when collection bundle is more expensive', () => {
635
+ const state = makeState({
636
+ collectionStyleSkus: {
637
+ 'family-sku': {
638
+ fontStyleSkuIds: ['s1', 's2'],
639
+ fontStyleIds: ['f1', 'f2'],
640
+ childrenSkuIds: [],
641
+ name: 'Family'
642
+ },
643
+ 'bundle-sku': {
644
+ fontStyleSkuIds: ['s1', 's2', 'vs1'],
645
+ fontStyleIds: ['f1', 'f2', 'vf1'],
646
+ childrenSkuIds: ['family-sku', 'var-family-sku'],
647
+ name: 'Bundle'
648
+ }
649
+ },
650
+ skuPrices: {
651
+ 'family-sku': 500,
652
+ 'bundle-sku': 600
653
+ }
654
+ });
655
+ const [collId] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'family-sku');
656
+ (0, _vitest.expect)(collId).toBe('family-sku');
657
+ });
658
+ (0, _vitest.it)('returns null when the selected SKU has no price (undefined)', () => {
659
+ // If skuPrices[skuId] is undefined, the filter comparison
660
+ // `difference <= undefined` is always false → no collection found
661
+ const state = makeState({
662
+ collectionStyleSkus: {
663
+ 'family-sku': {
664
+ fontStyleSkuIds: ['s1'],
665
+ fontStyleIds: ['f1'],
666
+ childrenSkuIds: [],
667
+ name: 'Family'
668
+ }
669
+ },
670
+ skuPrices: {
671
+ 'family-sku': 500
672
+ } // s1 deliberately missing
673
+ });
674
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 's1');
675
+ (0, _vitest.expect)(collId).toBe(null);
676
+ (0, _vitest.expect)(amount).toBe(null);
677
+ });
678
+ (0, _vitest.it)('treats collections with missing price as price 0', () => {
679
+ // A collection with no price entry is treated as $0, meaning its
680
+ // difference = 0 - sum_selected. With nothing selected, diff = 0.
681
+ const state = makeState({
682
+ collectionStyleSkus: {
683
+ 'ghost-bundle': {
684
+ fontStyleSkuIds: ['s1'],
685
+ fontStyleIds: ['f1'],
686
+ childrenSkuIds: ['family-sku'],
687
+ name: 'Ghost Bundle'
688
+ },
689
+ 'family-sku': {
690
+ fontStyleSkuIds: ['s1'],
691
+ fontStyleIds: ['f1'],
692
+ childrenSkuIds: [],
693
+ name: 'Family'
694
+ }
695
+ },
696
+ skuPrices: {
697
+ 'family-sku': 500,
698
+ s1: 40
699
+ }
700
+ // 'ghost-bundle' price deliberately missing → treated as 0
701
+ });
702
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'family-sku');
703
+ // ghost-bundle diff = 0, family diff = 500. ghost-bundle is cheaper.
704
+ // 0 <= 500 ✓ → ghost-bundle wins
705
+ (0, _vitest.expect)(collId).toBe('ghost-bundle');
706
+ (0, _vitest.expect)(amount).toBe(0);
707
+ });
708
+ (0, _vitest.it)('handles zero-price SKUs without errors', () => {
709
+ const state = makeState({
710
+ collectionStyleSkus: {
711
+ 'free-family': {
712
+ fontStyleSkuIds: ['fs1'],
713
+ fontStyleIds: ['ff1'],
714
+ childrenSkuIds: [],
715
+ name: 'Free Family'
716
+ },
717
+ 'free-bundle': {
718
+ fontStyleSkuIds: ['fs1', 'fvs1'],
719
+ fontStyleIds: ['ff1', 'fvf1'],
720
+ childrenSkuIds: ['free-family', 'free-var'],
721
+ name: 'Free Bundle'
722
+ }
723
+ },
724
+ skuPrices: {
725
+ 'free-family': 0,
726
+ 'free-bundle': 0,
727
+ 'free-var': 0,
728
+ fs1: 0,
729
+ fvs1: 0
730
+ }
731
+ });
732
+
733
+ // Both have diff = 0, filter: 0 <= 0 ✓, tie-break prefers bundle
734
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'free-family');
735
+ (0, _vitest.expect)(collId).toBe('free-bundle');
736
+ (0, _vitest.expect)(amount).toBe(0);
737
+ });
738
+ (0, _vitest.it)('handles negative difference (collection cheaper than sum of selected)', () => {
739
+ // Unusual but possible: if prices change or a collection is deeply discounted
740
+ const state = makeState({
741
+ selectedSkuIds: {
742
+ s1: true,
743
+ s2: true,
744
+ s3: true
745
+ },
746
+ collectionStyleSkus: {
747
+ 'family-sku': {
748
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4'],
749
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4'],
750
+ childrenSkuIds: [],
751
+ name: 'Family'
752
+ }
753
+ },
754
+ skuPrices: {
755
+ 'family-sku': 100,
756
+ s1: 40,
757
+ s2: 40,
758
+ s3: 40,
759
+ s4: 40
760
+ }
761
+ });
762
+
763
+ // Family costs $100, 3 styles selected = $120, diff = $100 - $120 = -$20
764
+ // This means the collection is $20 cheaper than what's already selected
765
+ const [collId, amount] = (0, _reducer.collectionSkuIdWithDiscount)(state, 's4');
766
+ (0, _vitest.expect)(collId).toBe('family-sku');
767
+ (0, _vitest.expect)(amount).toBe(-20);
768
+ });
769
+ });
770
+
771
+ // =========================================================================
772
+ // reducer SELECT_SKU_ID — core auto-selection
773
+ // =========================================================================
774
+ (0, _vitest.describe)('reducer SELECT_SKU_ID with collection bundles', () => {
775
+ (0, _vitest.it)('auto-selects collection bundle when selecting a family at the same price', () => {
776
+ const state = makeThunderState();
777
+ const newState = (0, _reducer.default)(state, {
778
+ type: 'SELECT_SKU_ID',
779
+ skuId: 'text-sku',
780
+ selected: true
781
+ });
782
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(true);
783
+ (0, _vitest.expect)(newState.selectedSkuIds['text-sku']).toBe(false);
784
+ (0, _vitest.expect)(newState.selectedSkuIds['text-var-sku']).toBe(false);
785
+ (0, _vitest.expect)(newState.selectedSkuIds['text-var-italic-sku']).toBe(false);
786
+ });
787
+ (0, _vitest.it)('does not auto-select bundle when bundle is more expensive', () => {
788
+ const state = makeThunderState({
789
+ skuPrices: {
790
+ ...makeThunderState().skuPrices,
791
+ 'text-bundle-sku': 600 // more expensive than text-sku ($500)
792
+ }
793
+ });
794
+ const newState = (0, _reducer.default)(state, {
795
+ type: 'SELECT_SKU_ID',
796
+ skuId: 'text-sku',
797
+ selected: true
798
+ });
799
+ (0, _vitest.expect)(newState.selectedSkuIds['text-sku']).toBe(true);
800
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBeUndefined();
801
+ });
802
+ (0, _vitest.it)('auto-selects bundle when clicking variable family with static already selected', () => {
803
+ const state = makeThunderState({
804
+ selectedSkuIds: {
805
+ 'text-sku': true
806
+ }
807
+ });
808
+ const newState = (0, _reducer.default)(state, {
809
+ type: 'SELECT_SKU_ID',
810
+ skuId: 'text-var-sku',
811
+ selected: true
812
+ });
813
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(true);
814
+ (0, _vitest.expect)(newState.selectedSkuIds['text-sku']).toBe(false);
815
+ (0, _vitest.expect)(newState.selectedSkuIds['text-var-sku']).toBe(false);
816
+ });
817
+ });
818
+
819
+ // =========================================================================
820
+ // reducer SELECT_SKU_ID — multiple bundles don't interfere
821
+ // =========================================================================
822
+ (0, _vitest.describe)('reducer SELECT_SKU_ID with multiple bundles', () => {
823
+ (0, _vitest.it)('only selects the correct bundle for the family being clicked', () => {
824
+ const state = makeThunderState();
825
+ const newState = (0, _reducer.default)(state, {
826
+ type: 'SELECT_SKU_ID',
827
+ skuId: 'display-sku',
828
+ selected: true
829
+ });
830
+
831
+ // Display bundle should be selected
832
+ (0, _vitest.expect)(newState.selectedSkuIds['display-bundle-sku']).toBe(true);
833
+ (0, _vitest.expect)(newState.selectedSkuIds['display-sku']).toBe(false);
834
+ // Text and caption bundles should be untouched
835
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBeUndefined();
836
+ (0, _vitest.expect)(newState.selectedSkuIds['caption-bundle-sku']).toBeUndefined();
837
+ });
838
+ (0, _vitest.it)('upgrades to superfamily when selecting a second family bundle', () => {
839
+ // Selecting two families ($500+$500) costs more than the superfamily ($750),
840
+ // so the system correctly upgrades to the superfamily on the second click
841
+ const state = makeThunderState();
842
+ let newState = (0, _reducer.default)(state, {
843
+ type: 'SELECT_SKU_ID',
844
+ skuId: 'text-sku',
845
+ selected: true
846
+ });
847
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(true);
848
+ newState = (0, _reducer.default)(newState, {
849
+ type: 'SELECT_SKU_ID',
850
+ skuId: 'display-sku',
851
+ selected: true
852
+ });
853
+
854
+ // Superfamily ($750) beats two bundles ($500 + $500 = $1000)
855
+ (0, _vitest.expect)(newState.selectedSkuIds['super-sku']).toBe(true);
856
+ // Previous bundle should be deselected (subsumed by superfamily)
857
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(false);
858
+ (0, _vitest.expect)(newState.selectedSkuIds['display-bundle-sku']).toBe(false);
859
+ });
860
+ });
861
+
862
+ // =========================================================================
863
+ // reducer SELECT_SKU_ID — deselection flow
864
+ // =========================================================================
865
+ (0, _vitest.describe)('reducer deselection', () => {
866
+ (0, _vitest.it)('deselecting a bundle member deselects the entire bundle', () => {
867
+ // Start with the text bundle selected (members are false)
868
+ const state = makeThunderState({
869
+ selectedSkuIds: {
870
+ 'text-bundle-sku': true,
871
+ 'text-sku': false,
872
+ 'text-var-sku': false,
873
+ 'text-var-italic-sku': false,
874
+ s1: false,
875
+ s2: false,
876
+ vs1: false,
877
+ vis1: false
878
+ }
879
+ });
880
+
881
+ // User clicks to deselect "Thunder Text Variable"
882
+ const newState = (0, _reducer.default)(state, {
883
+ type: 'SELECT_SKU_ID',
884
+ skuId: 'text-var-sku',
885
+ selected: false
886
+ });
887
+
888
+ // The bundle should be deselected
889
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(false);
890
+ });
891
+ (0, _vitest.it)('deselecting a font style deselects the parent bundle', () => {
892
+ const state = makeThunderState({
893
+ selectedSkuIds: {
894
+ 'text-bundle-sku': true,
895
+ 'text-sku': false,
896
+ 'text-var-sku': false,
897
+ 'text-var-italic-sku': false,
898
+ s1: false,
899
+ s2: false,
900
+ vs1: false,
901
+ vis1: false
902
+ }
903
+ });
904
+
905
+ // User clicks to deselect individual style "s1"
906
+ const newState = (0, _reducer.default)(state, {
907
+ type: 'SELECT_SKU_ID',
908
+ skuId: 's1',
909
+ selected: false
910
+ });
911
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(false);
912
+ });
913
+ (0, _vitest.it)('deselecting a family bundle deselects the parent collection bundle (transitive)', () => {
914
+ // Collection bundle is selected. A family bundle (style package) within
915
+ // it is shown as selected via transitive isSelected. Clicking to deselect
916
+ // it should find and deselect the collection bundle.
917
+ const state = makeState({
918
+ selectedSkuIds: {
919
+ 'coll-bundle-sku': true,
920
+ 'family-sku': false,
921
+ 'var-sku': false,
922
+ 'pkg-upright-sku': false,
923
+ 'pkg-italic-sku': false,
924
+ s1: false,
925
+ s2: false,
926
+ si1: false,
927
+ si2: false,
928
+ vs1: false
929
+ },
930
+ collectionStyleSkus: {
931
+ 'coll-bundle-sku': {
932
+ fontStyleSkuIds: ['s1', 's2', 'si1', 'si2', 'vs1'],
933
+ fontStyleIds: ['f1', 'f2', 'fi1', 'fi2', 'vf1'],
934
+ childrenSkuIds: ['family-sku', 'var-sku'],
935
+ name: 'Text + Variable'
936
+ },
937
+ 'family-sku': {
938
+ fontStyleSkuIds: ['s1', 's2', 'si1', 'si2'],
939
+ fontStyleIds: ['f1', 'f2', 'fi1', 'fi2'],
940
+ childrenSkuIds: ['pkg-upright-sku', 'pkg-italic-sku'],
941
+ name: 'Text Family'
942
+ },
943
+ 'var-sku': {
944
+ fontStyleSkuIds: ['vs1'],
945
+ fontStyleIds: ['vf1'],
946
+ childrenSkuIds: [],
947
+ name: 'Variable'
948
+ },
949
+ 'pkg-upright-sku': {
950
+ fontStyleSkuIds: ['s1', 's2'],
951
+ fontStyleIds: ['f1', 'f2'],
952
+ childrenSkuIds: [],
953
+ name: 'Package Text'
954
+ },
955
+ 'pkg-italic-sku': {
956
+ fontStyleSkuIds: ['si1', 'si2'],
957
+ fontStyleIds: ['fi1', 'fi2'],
958
+ childrenSkuIds: [],
959
+ name: 'Package Italic'
960
+ }
961
+ },
962
+ skuPrices: {
963
+ 'coll-bundle-sku': 500,
964
+ 'family-sku': 500,
965
+ 'var-sku': 300,
966
+ 'pkg-upright-sku': 250,
967
+ 'pkg-italic-sku': 250,
968
+ s1: 100,
969
+ s2: 100,
970
+ si1: 100,
971
+ si2: 100,
972
+ vs1: 100
973
+ }
974
+ });
975
+
976
+ // Bundle shows as selected via transitive isSelected
977
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-upright-sku')(state)).toBe(true);
978
+
979
+ // Click to deselect the bundle
980
+ const newState = (0, _reducer.default)(state, {
981
+ type: 'SELECT_SKU_ID',
982
+ skuId: 'pkg-upright-sku',
983
+ selected: false
984
+ });
985
+
986
+ // Collection bundle should be deselected
987
+ (0, _vitest.expect)(newState.selectedSkuIds['coll-bundle-sku']).toBe(false);
988
+ // Nothing should be selected anymore
989
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-upright-sku')(newState)).toBe(false);
990
+ (0, _vitest.expect)((0, _utils.isSelected)('family-sku')(newState)).toBe(false);
991
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(newState)).toBe(false);
992
+ });
993
+ (0, _vitest.it)('deselecting a bundle does not affect other bundles', () => {
994
+ const state = makeThunderState({
995
+ selectedSkuIds: {
996
+ 'text-bundle-sku': true,
997
+ 'text-sku': false,
998
+ 'text-var-sku': false,
999
+ 'text-var-italic-sku': false,
1000
+ s1: false,
1001
+ s2: false,
1002
+ vs1: false,
1003
+ vis1: false,
1004
+ 'display-bundle-sku': true,
1005
+ 'display-sku': false,
1006
+ 'display-var-sku': false,
1007
+ 'display-var-italic-sku': false,
1008
+ ds1: false,
1009
+ ds2: false,
1010
+ dvs1: false,
1011
+ dvis1: false
1012
+ }
1013
+ });
1014
+ const newState = (0, _reducer.default)(state, {
1015
+ type: 'SELECT_SKU_ID',
1016
+ skuId: 'text-var-sku',
1017
+ selected: false
1018
+ });
1019
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(false);
1020
+ // Display bundle should be unaffected
1021
+ (0, _vitest.expect)(newState.selectedSkuIds['display-bundle-sku']).toBe(true);
1022
+ });
1023
+ });
1024
+
1025
+ // =========================================================================
1026
+ // reducer SELECT_SKU_ID — re-selection after deselection
1027
+ // =========================================================================
1028
+ (0, _vitest.describe)('reducer re-selection cycle', () => {
1029
+ (0, _vitest.it)('re-selecting family after bundle was deselected re-selects the bundle', () => {
1030
+ // 1. Start with bundle selected
1031
+ const state = makeThunderState({
1032
+ selectedSkuIds: {
1033
+ 'text-bundle-sku': true,
1034
+ 'text-sku': false,
1035
+ 'text-var-sku': false,
1036
+ 'text-var-italic-sku': false,
1037
+ s1: false,
1038
+ s2: false,
1039
+ vs1: false,
1040
+ vis1: false
1041
+ }
1042
+ });
1043
+
1044
+ // 2. Deselect a member → bundle goes away
1045
+ const afterDeselect = (0, _reducer.default)(state, {
1046
+ type: 'SELECT_SKU_ID',
1047
+ skuId: 'text-var-sku',
1048
+ selected: false
1049
+ });
1050
+ (0, _vitest.expect)(afterDeselect.selectedSkuIds['text-bundle-sku']).toBe(false);
1051
+
1052
+ // 3. Re-select the family → bundle should come back
1053
+ const afterReselect = (0, _reducer.default)(afterDeselect, {
1054
+ type: 'SELECT_SKU_ID',
1055
+ skuId: 'text-sku',
1056
+ selected: true
1057
+ });
1058
+ (0, _vitest.expect)(afterReselect.selectedSkuIds['text-bundle-sku']).toBe(true);
1059
+ (0, _vitest.expect)(afterReselect.selectedSkuIds['text-sku']).toBe(false);
1060
+ (0, _vitest.expect)(afterReselect.selectedSkuIds['text-var-sku']).toBe(false);
1061
+ });
1062
+ });
1063
+
1064
+ // =========================================================================
1065
+ // reducer SELECT_SKU_ID — superfamily upgrade
1066
+ // =========================================================================
1067
+ (0, _vitest.describe)('reducer superfamily upgrade', () => {
1068
+ (0, _vitest.it)('upgrades to superfamily when cumulative selections make it cheaper', () => {
1069
+ // With two bundles selected ($500 each = $1000 total), the superfamily
1070
+ // at $750 should be a better deal when selecting the third family
1071
+ const state = makeThunderState({
1072
+ selectedSkuIds: {
1073
+ 'text-bundle-sku': true,
1074
+ 'text-sku': false,
1075
+ 'text-var-sku': false,
1076
+ 'text-var-italic-sku': false,
1077
+ s1: false,
1078
+ s2: false,
1079
+ vs1: false,
1080
+ vis1: false,
1081
+ 'display-bundle-sku': true,
1082
+ 'display-sku': false,
1083
+ 'display-var-sku': false,
1084
+ 'display-var-italic-sku': false,
1085
+ ds1: false,
1086
+ ds2: false,
1087
+ dvs1: false,
1088
+ dvis1: false
1089
+ }
1090
+ });
1091
+ const newState = (0, _reducer.default)(state, {
1092
+ type: 'SELECT_SKU_ID',
1093
+ skuId: 'caption-sku',
1094
+ selected: true
1095
+ });
1096
+
1097
+ // Superfamily ($750) - two bundles ($500+$500) = -$250 difference
1098
+ // Caption-sku costs $500, so -$250 <= $500 → superfamily is preferred
1099
+ (0, _vitest.expect)(newState.selectedSkuIds['super-sku']).toBe(true);
1100
+ });
1101
+ });
1102
+
1103
+ // =========================================================================
1104
+ // reducer SELECT_SKU_ID — individual style to family upgrade
1105
+ // =========================================================================
1106
+ (0, _vitest.describe)('reducer individual style upgrade', () => {
1107
+ (0, _vitest.it)('upgrades to family when enough styles are individually selected', () => {
1108
+ const state = makeState({
1109
+ selectedSkuIds: {
1110
+ s1: true,
1111
+ s2: true,
1112
+ s3: true,
1113
+ s4: true
1114
+ },
1115
+ collectionStyleSkus: {
1116
+ 'family-sku': {
1117
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1118
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1119
+ childrenSkuIds: [],
1120
+ name: 'Family'
1121
+ }
1122
+ },
1123
+ skuPrices: {
1124
+ 'family-sku': 200,
1125
+ s1: 40,
1126
+ s2: 40,
1127
+ s3: 40,
1128
+ s4: 40,
1129
+ s5: 40
1130
+ }
1131
+ });
1132
+
1133
+ // Family is $200, 4 selected = $160, diff = $40. Style s5 costs $40.
1134
+ // $40 <= $40 → family is a better deal
1135
+ const newState = (0, _reducer.default)(state, {
1136
+ type: 'SELECT_SKU_ID',
1137
+ skuId: 's5',
1138
+ selected: true
1139
+ });
1140
+ (0, _vitest.expect)(newState.selectedSkuIds['family-sku']).toBe(true);
1141
+ // Individual styles should be deselected (included via family)
1142
+ (0, _vitest.expect)(newState.selectedSkuIds['s1']).toBe(false);
1143
+ (0, _vitest.expect)(newState.selectedSkuIds['s5']).toBe(false);
1144
+ });
1145
+ (0, _vitest.it)('does not upgrade to family when not yet cost-effective', () => {
1146
+ const state = makeState({
1147
+ selectedSkuIds: {
1148
+ s1: true
1149
+ },
1150
+ collectionStyleSkus: {
1151
+ 'family-sku': {
1152
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1153
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1154
+ childrenSkuIds: [],
1155
+ name: 'Family'
1156
+ }
1157
+ },
1158
+ skuPrices: {
1159
+ 'family-sku': 200,
1160
+ s1: 40,
1161
+ s2: 40,
1162
+ s3: 40,
1163
+ s4: 40,
1164
+ s5: 40
1165
+ }
1166
+ });
1167
+
1168
+ // Family is $200, 1 selected = $40, diff = $160. Style s2 costs $40.
1169
+ // $160 > $40 → not worth upgrading
1170
+ const newState = (0, _reducer.default)(state, {
1171
+ type: 'SELECT_SKU_ID',
1172
+ skuId: 's2',
1173
+ selected: true
1174
+ });
1175
+ (0, _vitest.expect)(newState.selectedSkuIds['family-sku']).toBeUndefined();
1176
+ (0, _vitest.expect)(newState.selectedSkuIds['s1']).toBe(true);
1177
+ (0, _vitest.expect)(newState.selectedSkuIds['s2']).toBe(true);
1178
+ });
1179
+ });
1180
+
1181
+ // =========================================================================
1182
+ // reducer SELECT_SKU_ID — edge cases with missing data
1183
+ // =========================================================================
1184
+ (0, _vitest.describe)('reducer edge cases', () => {
1185
+ (0, _vitest.it)('handles selection when collectionStyleSkus is empty', () => {
1186
+ const state = makeState({
1187
+ skuPrices: {
1188
+ 'some-sku': 100
1189
+ }
1190
+ });
1191
+ const newState = (0, _reducer.default)(state, {
1192
+ type: 'SELECT_SKU_ID',
1193
+ skuId: 'some-sku',
1194
+ selected: true
1195
+ });
1196
+ (0, _vitest.expect)(newState.selectedSkuIds['some-sku']).toBe(true);
1197
+ });
1198
+ (0, _vitest.it)('handles selection when skuPrices is empty', () => {
1199
+ const state = makeState({
1200
+ collectionStyleSkus: {
1201
+ 'family-sku': {
1202
+ fontStyleSkuIds: ['s1'],
1203
+ fontStyleIds: ['f1'],
1204
+ childrenSkuIds: [],
1205
+ name: 'Family'
1206
+ }
1207
+ }
1208
+ });
1209
+
1210
+ // No prices → no collection upgrade possible, just select directly
1211
+ const newState = (0, _reducer.default)(state, {
1212
+ type: 'SELECT_SKU_ID',
1213
+ skuId: 's1',
1214
+ selected: true
1215
+ });
1216
+ (0, _vitest.expect)(newState.selectedSkuIds['s1']).toBe(true);
1217
+ (0, _vitest.expect)(newState.selectedSkuIds['family-sku']).toBeUndefined();
1218
+ });
1219
+ (0, _vitest.it)('re-selecting a bundle member may upgrade to superfamily', () => {
1220
+ // When the text bundle is selected and the user clicks text-sku (a member),
1221
+ // the system re-evaluates: the superfamily sees the text bundle ($500) as
1222
+ // already selected, making its difference only $250 — cheaper than text-sku
1223
+ // at $500. So it upgrades to the superfamily.
1224
+ //
1225
+ // This is a documented consequence of the greedy price-optimization: every
1226
+ // SELECT_SKU_ID re-evaluates the cheapest collection, even for members
1227
+ // that are already effectively selected.
1228
+ const state = makeThunderState({
1229
+ selectedSkuIds: {
1230
+ 'text-bundle-sku': true,
1231
+ 'text-sku': false,
1232
+ 'text-var-sku': false,
1233
+ 'text-var-italic-sku': false,
1234
+ s1: false,
1235
+ s2: false,
1236
+ vs1: false,
1237
+ vis1: false
1238
+ }
1239
+ });
1240
+ const newState = (0, _reducer.default)(state, {
1241
+ type: 'SELECT_SKU_ID',
1242
+ skuId: 'text-sku',
1243
+ selected: true
1244
+ });
1245
+ (0, _vitest.expect)(newState.selectedSkuIds['super-sku']).toBe(true);
1246
+ (0, _vitest.expect)(newState.selectedSkuIds['text-bundle-sku']).toBe(false);
1247
+ });
1248
+ (0, _vitest.it)('UNSELECT_SKUS clears all selections cleanly', () => {
1249
+ const state = makeThunderState({
1250
+ selectedSkuIds: {
1251
+ 'text-bundle-sku': true,
1252
+ 'text-sku': false,
1253
+ 'text-var-sku': false
1254
+ }
1255
+ });
1256
+ const newState = (0, _reducer.default)(state, {
1257
+ type: 'UNSELECT_SKUS'
1258
+ });
1259
+ (0, _vitest.expect)(Object.keys(newState.selectedSkuIds)).toHaveLength(0);
1260
+ });
1261
+ });
1262
+
1263
+ // =========================================================================
1264
+ // isSelected — family bundles (style packages within a family)
1265
+ // =========================================================================
1266
+ (0, _vitest.describe)('isSelected with family bundles (style packages)', () => {
1267
+ // Helper: a family with two style bundles (upright + italic packages)
1268
+ function makeFamilyWithBundlesState() {
1269
+ let overrides = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
1270
+ return makeState({
1271
+ collectionStyleSkus: {
1272
+ 'family-sku': {
1273
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5'],
1274
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1275
+ childrenSkuIds: ['pkg-upright-sku', 'pkg-italic-sku'],
1276
+ name: 'Thunder Text Family'
1277
+ },
1278
+ 'pkg-upright-sku': {
1279
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1280
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1281
+ childrenSkuIds: [],
1282
+ name: 'Package Text'
1283
+ },
1284
+ 'pkg-italic-sku': {
1285
+ fontStyleSkuIds: ['si1', 'si2', 'si3', 'si4', 'si5'],
1286
+ fontStyleIds: ['fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1287
+ childrenSkuIds: [],
1288
+ name: 'Package Text Italic'
1289
+ }
1290
+ },
1291
+ skuPrices: {
1292
+ 'family-sku': 500,
1293
+ 'pkg-upright-sku': 250,
1294
+ 'pkg-italic-sku': 250,
1295
+ s1: 100,
1296
+ s2: 100,
1297
+ s3: 100,
1298
+ s4: 100,
1299
+ s5: 100,
1300
+ si1: 100,
1301
+ si2: 100,
1302
+ si3: 100,
1303
+ si4: 100,
1304
+ si5: 100
1305
+ },
1306
+ ...overrides
1307
+ });
1308
+ }
1309
+ (0, _vitest.it)('bundles show selected when family is directly selected', () => {
1310
+ const state = makeFamilyWithBundlesState({
1311
+ selectedSkuIds: {
1312
+ 'family-sku': true,
1313
+ 'pkg-upright-sku': false,
1314
+ 'pkg-italic-sku': false,
1315
+ s1: false,
1316
+ s2: false,
1317
+ s3: false,
1318
+ s4: false,
1319
+ s5: false,
1320
+ si1: false,
1321
+ si2: false,
1322
+ si3: false,
1323
+ si4: false,
1324
+ si5: false
1325
+ }
1326
+ });
1327
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-upright-sku')(state)).toBe(true);
1328
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-italic-sku')(state)).toBe(true);
1329
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(state)).toBe(true);
1330
+ (0, _vitest.expect)((0, _utils.isSelected)('si1')(state)).toBe(true);
1331
+ });
1332
+ (0, _vitest.it)('bundles show selected when collection bundle is selected (transitive)', () => {
1333
+ // Collection bundle is selected → family is false → family bundles should
1334
+ // still show selected because the collection bundle covers all styles
1335
+ const state = makeFamilyWithBundlesState({
1336
+ collectionStyleSkus: {
1337
+ // Collection bundle: groups family + variable family
1338
+ 'coll-bundle-sku': {
1339
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5', 'vs1'],
1340
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5', 'vf1'],
1341
+ childrenSkuIds: ['family-sku', 'var-sku'],
1342
+ name: 'Thunder Text + Variable'
1343
+ },
1344
+ 'family-sku': {
1345
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5'],
1346
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1347
+ childrenSkuIds: ['pkg-upright-sku', 'pkg-italic-sku'],
1348
+ name: 'Thunder Text Family'
1349
+ },
1350
+ 'var-sku': {
1351
+ fontStyleSkuIds: ['vs1'],
1352
+ fontStyleIds: ['vf1'],
1353
+ childrenSkuIds: [],
1354
+ name: 'Thunder Text Variable'
1355
+ },
1356
+ 'pkg-upright-sku': {
1357
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1358
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1359
+ childrenSkuIds: [],
1360
+ name: 'Package Text'
1361
+ },
1362
+ 'pkg-italic-sku': {
1363
+ fontStyleSkuIds: ['si1', 'si2', 'si3', 'si4', 'si5'],
1364
+ fontStyleIds: ['fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1365
+ childrenSkuIds: [],
1366
+ name: 'Package Text Italic'
1367
+ }
1368
+ },
1369
+ selectedSkuIds: {
1370
+ 'coll-bundle-sku': true,
1371
+ 'family-sku': false,
1372
+ 'var-sku': false,
1373
+ 'pkg-upright-sku': false,
1374
+ 'pkg-italic-sku': false,
1375
+ s1: false,
1376
+ s2: false,
1377
+ s3: false,
1378
+ s4: false,
1379
+ s5: false,
1380
+ si1: false,
1381
+ si2: false,
1382
+ si3: false,
1383
+ si4: false,
1384
+ si5: false,
1385
+ vs1: false
1386
+ }
1387
+ });
1388
+
1389
+ // Family shows selected (direct child of collection bundle)
1390
+ (0, _vitest.expect)((0, _utils.isSelected)('family-sku')(state)).toBe(true);
1391
+ // Bundles should show selected (grandchildren — family is child of
1392
+ // collection bundle, bundles are children of family)
1393
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-upright-sku')(state)).toBe(true);
1394
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-italic-sku')(state)).toBe(true);
1395
+ // Individual styles should also show selected
1396
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(state)).toBe(true);
1397
+ });
1398
+ (0, _vitest.it)('selecting one bundle then another upgrades to family via reducer', () => {
1399
+ const state = makeFamilyWithBundlesState();
1400
+
1401
+ // Select first bundle
1402
+ let newState = (0, _reducer.default)(state, {
1403
+ type: 'SELECT_SKU_ID',
1404
+ skuId: 'pkg-upright-sku',
1405
+ selected: true
1406
+ });
1407
+ (0, _vitest.expect)(newState.selectedSkuIds['pkg-upright-sku']).toBe(true);
1408
+
1409
+ // Select second bundle — should upgrade to family
1410
+ newState = (0, _reducer.default)(newState, {
1411
+ type: 'SELECT_SKU_ID',
1412
+ skuId: 'pkg-italic-sku',
1413
+ selected: true
1414
+ });
1415
+
1416
+ // Family should be selected since family ($500) = two bundles ($250+$250)
1417
+ (0, _vitest.expect)(newState.selectedSkuIds['family-sku']).toBe(true);
1418
+ // Both bundles should report as selected
1419
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-upright-sku')(newState)).toBe(true);
1420
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-italic-sku')(newState)).toBe(true);
1421
+ });
1422
+ (0, _vitest.it)('auto-upgrades transitively: bundle → family → collection bundle', () => {
1423
+ // When a collection bundle exists at the same price as the family,
1424
+ // selecting both bundles should upgrade all the way to the collection
1425
+ // bundle (not stop at the family)
1426
+ const state = makeFamilyWithBundlesState({
1427
+ collectionStyleSkus: {
1428
+ 'coll-bundle-sku': {
1429
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5', 'vs1'],
1430
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5', 'vf1'],
1431
+ childrenSkuIds: ['family-sku', 'var-sku'],
1432
+ name: 'Thunder Text + Variable'
1433
+ },
1434
+ 'family-sku': {
1435
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5'],
1436
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1437
+ childrenSkuIds: ['pkg-upright-sku', 'pkg-italic-sku'],
1438
+ name: 'Thunder Text Family'
1439
+ },
1440
+ 'var-sku': {
1441
+ fontStyleSkuIds: ['vs1'],
1442
+ fontStyleIds: ['vf1'],
1443
+ childrenSkuIds: [],
1444
+ name: 'Thunder Text Variable'
1445
+ },
1446
+ 'pkg-upright-sku': {
1447
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1448
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1449
+ childrenSkuIds: [],
1450
+ name: 'Package Text'
1451
+ },
1452
+ 'pkg-italic-sku': {
1453
+ fontStyleSkuIds: ['si1', 'si2', 'si3', 'si4', 'si5'],
1454
+ fontStyleIds: ['fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1455
+ childrenSkuIds: [],
1456
+ name: 'Package Text Italic'
1457
+ }
1458
+ },
1459
+ skuPrices: {
1460
+ 'coll-bundle-sku': 500,
1461
+ 'family-sku': 500,
1462
+ 'var-sku': 300,
1463
+ 'pkg-upright-sku': 250,
1464
+ 'pkg-italic-sku': 250,
1465
+ s1: 100,
1466
+ s2: 100,
1467
+ s3: 100,
1468
+ s4: 100,
1469
+ s5: 100,
1470
+ si1: 100,
1471
+ si2: 100,
1472
+ si3: 100,
1473
+ si4: 100,
1474
+ si5: 100,
1475
+ vs1: 100
1476
+ },
1477
+ selectedSkuIds: {
1478
+ 'pkg-upright-sku': true
1479
+ }
1480
+ });
1481
+ const newState = (0, _reducer.default)(state, {
1482
+ type: 'SELECT_SKU_ID',
1483
+ skuId: 'pkg-italic-sku',
1484
+ selected: true
1485
+ });
1486
+
1487
+ // Should transitively upgrade: bundle → family → collection bundle
1488
+ (0, _vitest.expect)(newState.selectedSkuIds['coll-bundle-sku']).toBe(true);
1489
+ (0, _vitest.expect)(newState.selectedSkuIds['family-sku']).toBe(false);
1490
+ (0, _vitest.expect)(newState.selectedSkuIds['pkg-upright-sku']).toBe(false);
1491
+ (0, _vitest.expect)(newState.selectedSkuIds['pkg-italic-sku']).toBe(false);
1492
+
1493
+ // All items should report as selected via isSelected
1494
+ (0, _vitest.expect)((0, _utils.isSelected)('family-sku')(newState)).toBe(true);
1495
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-upright-sku')(newState)).toBe(true);
1496
+ (0, _vitest.expect)((0, _utils.isSelected)('pkg-italic-sku')(newState)).toBe(true);
1497
+ (0, _vitest.expect)((0, _utils.isSelected)('s1')(newState)).toBe(true);
1498
+ (0, _vitest.expect)((0, _utils.isSelected)('vs1')(newState)).toBe(true);
1499
+ });
1500
+ (0, _vitest.it)('prefers collection bundle over family when family has duplicate fontStyleSkuIds from bundles', () => {
1501
+ // In production data, flattenSkuData produces a family entry whose
1502
+ // fontStyleSkuIds includes BOTH the family's own styles AND the
1503
+ // overlapping styles from its child bundles, creating duplicates.
1504
+ // The tie-breaker must use non-self preference (not raw array length)
1505
+ // to avoid the family's inflated count beating the collection bundle.
1506
+ const state = makeState({
1507
+ collectionStyleSkus: {
1508
+ 'family-sku': {
1509
+ // 10 unique styles, but listed 20 times (family's own + bundle overlap)
1510
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5', 's1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5'],
1511
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1512
+ childrenSkuIds: ['pkg-upright-sku', 'pkg-italic-sku'],
1513
+ name: 'Thunder Caption Family'
1514
+ },
1515
+ 'coll-bundle-sku': {
1516
+ // 12 unique styles (family 10 + variable 2)
1517
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5', 'vs1', 'vs2'],
1518
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5', 'vf1', 'vf2'],
1519
+ childrenSkuIds: ['family-sku', 'var-sku'],
1520
+ name: 'Thunder Caption Family + Variable'
1521
+ },
1522
+ 'var-sku': {
1523
+ fontStyleSkuIds: ['vs1', 'vs2'],
1524
+ fontStyleIds: ['vf1', 'vf2'],
1525
+ childrenSkuIds: [],
1526
+ name: 'Thunder Caption Variable'
1527
+ },
1528
+ 'pkg-upright-sku': {
1529
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1530
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1531
+ childrenSkuIds: [],
1532
+ name: 'Package Caption'
1533
+ },
1534
+ 'pkg-italic-sku': {
1535
+ fontStyleSkuIds: ['si1', 'si2', 'si3', 'si4', 'si5'],
1536
+ fontStyleIds: ['fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1537
+ childrenSkuIds: [],
1538
+ name: 'Package Caption Italic'
1539
+ }
1540
+ },
1541
+ skuPrices: {
1542
+ 'family-sku': 500,
1543
+ 'coll-bundle-sku': 500,
1544
+ 'var-sku': 500,
1545
+ 'pkg-upright-sku': 300,
1546
+ 'pkg-italic-sku': 300,
1547
+ s1: 100,
1548
+ s2: 100,
1549
+ s3: 100,
1550
+ s4: 100,
1551
+ s5: 100,
1552
+ si1: 100,
1553
+ si2: 100,
1554
+ si3: 100,
1555
+ si4: 100,
1556
+ si5: 100,
1557
+ vs1: 100,
1558
+ vs2: 100
1559
+ }
1560
+ });
1561
+
1562
+ // family-sku fontStyleSkuIds.length is 20 (duplicates), coll-bundle is 12.
1563
+ // A naive .length comparison would pick the family. The non-self
1564
+ // preference must win so the collection bundle is auto-selected.
1565
+ const [collId] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'family-sku');
1566
+ (0, _vitest.expect)(collId).toBe('coll-bundle-sku');
1567
+ });
1568
+ (0, _vitest.it)('transitive upgrade prefers the collection with the most font styles', () => {
1569
+ // When two collections have the same price difference, prefer the one
1570
+ // covering more font styles
1571
+ const state = makeFamilyWithBundlesState({
1572
+ collectionStyleSkus: {
1573
+ 'small-bundle-sku': {
1574
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5'],
1575
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1576
+ childrenSkuIds: ['family-sku'],
1577
+ name: 'Small Bundle'
1578
+ },
1579
+ 'big-bundle-sku': {
1580
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5', 'vs1'],
1581
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5', 'vf1'],
1582
+ childrenSkuIds: ['family-sku', 'var-sku'],
1583
+ name: 'Big Bundle'
1584
+ },
1585
+ 'family-sku': {
1586
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5', 'si1', 'si2', 'si3', 'si4', 'si5'],
1587
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5', 'fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1588
+ childrenSkuIds: ['pkg-upright-sku', 'pkg-italic-sku'],
1589
+ name: 'Thunder Text Family'
1590
+ },
1591
+ 'var-sku': {
1592
+ fontStyleSkuIds: ['vs1'],
1593
+ fontStyleIds: ['vf1'],
1594
+ childrenSkuIds: [],
1595
+ name: 'Variable'
1596
+ },
1597
+ 'pkg-upright-sku': {
1598
+ fontStyleSkuIds: ['s1', 's2', 's3', 's4', 's5'],
1599
+ fontStyleIds: ['f1', 'f2', 'f3', 'f4', 'f5'],
1600
+ childrenSkuIds: [],
1601
+ name: 'Package Text'
1602
+ },
1603
+ 'pkg-italic-sku': {
1604
+ fontStyleSkuIds: ['si1', 'si2', 'si3', 'si4', 'si5'],
1605
+ fontStyleIds: ['fi1', 'fi2', 'fi3', 'fi4', 'fi5'],
1606
+ childrenSkuIds: [],
1607
+ name: 'Package Text Italic'
1608
+ }
1609
+ },
1610
+ skuPrices: {
1611
+ 'small-bundle-sku': 500,
1612
+ 'big-bundle-sku': 500,
1613
+ 'family-sku': 500,
1614
+ 'var-sku': 300,
1615
+ 'pkg-upright-sku': 250,
1616
+ 'pkg-italic-sku': 250,
1617
+ s1: 100,
1618
+ s2: 100,
1619
+ s3: 100,
1620
+ s4: 100,
1621
+ s5: 100,
1622
+ si1: 100,
1623
+ si2: 100,
1624
+ si3: 100,
1625
+ si4: 100,
1626
+ si5: 100,
1627
+ vs1: 100
1628
+ }
1629
+ });
1630
+ const [collId] = (0, _reducer.collectionSkuIdWithDiscount)(state, 'family-sku');
1631
+ // Both bundles have the same price, but big-bundle has more styles
1632
+ (0, _vitest.expect)(collId).toBe('big-bundle-sku');
1633
+ });
1634
+ });