@akinon/pz-theme 2.0.37-rc.0 → 2.0.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +7 -31
  2. package/package.json +4 -4
  3. package/src/__tests__/__snapshots__/facet-filters-block.test.tsx.snap +11 -0
  4. package/src/__tests__/custom-css.test.ts +181 -0
  5. package/src/__tests__/designer-chrome-clipping.test.tsx +497 -0
  6. package/src/__tests__/facet-filters-block.test.tsx +707 -0
  7. package/src/__tests__/item-template.test.tsx +184 -0
  8. package/src/__tests__/listing-controls.test.tsx +961 -0
  9. package/src/__tests__/part-style-scope.test.tsx +229 -0
  10. package/src/__tests__/part-styles.test.ts +311 -0
  11. package/src/__tests__/registry-parity.test.ts +42 -0
  12. package/src/__tests__/row-visibility.test.ts +672 -0
  13. package/src/__tests__/sort-select-page-cursor.test.tsx +336 -0
  14. package/src/__tests__/theme-block-html-id.test.tsx +167 -0
  15. package/src/blocks/__tests__/__snapshots__/product-gallery-block.test.tsx.snap +15 -0
  16. package/src/blocks/__tests__/accordion-block-state.test.tsx +201 -0
  17. package/src/blocks/__tests__/facet-filters-block.test.tsx +190 -0
  18. package/src/blocks/__tests__/listing-context-card-model.test.tsx +230 -0
  19. package/src/blocks/__tests__/listing-lookup-hardening.test.ts +80 -0
  20. package/src/blocks/__tests__/product-gallery-block.test.tsx +974 -0
  21. package/src/blocks/__tests__/product-grid-composition.test.tsx +570 -0
  22. package/src/blocks/__tests__/product-grid-lookups.test.tsx +230 -0
  23. package/src/blocks/__tests__/product-price-block.test.tsx +248 -0
  24. package/src/blocks/__tests__/product-recommendations-block.test.tsx +702 -0
  25. package/src/blocks/__tests__/share-block.test.tsx +392 -0
  26. package/src/blocks/__tests__/store-locator-block.test.tsx +691 -0
  27. package/src/blocks/__tests__/variant-selector-block.test.tsx +817 -0
  28. package/src/blocks/__tests__/variant-selector-golden.json +20 -0
  29. package/src/blocks/account-menu-block.tsx +264 -0
  30. package/src/blocks/add-to-cart-block.tsx +417 -0
  31. package/src/blocks/address-list-block.tsx +348 -0
  32. package/src/blocks/applied-filters-block.tsx +288 -0
  33. package/src/blocks/auth-form-block.tsx +295 -0
  34. package/src/blocks/basket-items-block.tsx +457 -0
  35. package/src/blocks/basket-summary-block.tsx +404 -0
  36. package/src/blocks/block-renderer-registry.tsx +94 -0
  37. package/src/blocks/change-password-form-block.tsx +240 -0
  38. package/src/blocks/checkout-steps-block.tsx +288 -0
  39. package/src/blocks/checkout-summary-block.tsx +449 -0
  40. package/src/blocks/countdown-block.tsx +182 -0
  41. package/src/blocks/coupon-list-block.tsx +292 -0
  42. package/src/blocks/facet-filters-block.tsx +1686 -0
  43. package/src/blocks/faq-accordion-block.tsx +358 -0
  44. package/src/blocks/favorite-button-block.tsx +252 -0
  45. package/src/blocks/favourites-grid-block.tsx +510 -0
  46. package/src/blocks/forgot-password-block.tsx +155 -0
  47. package/src/blocks/item-template-utils.ts +131 -0
  48. package/src/blocks/item-template.tsx +164 -0
  49. package/src/blocks/listing-breadcrumb-block.tsx +175 -0
  50. package/src/blocks/listing-context.ts +850 -0
  51. package/src/blocks/listing-pagination-block.tsx +923 -0
  52. package/src/blocks/listing-result-count-block.tsx +126 -0
  53. package/src/blocks/listing-title-block.tsx +115 -0
  54. package/src/blocks/oauth-buttons-block.tsx +205 -0
  55. package/src/blocks/order-detail-block.tsx +474 -0
  56. package/src/blocks/order-list-block.tsx +274 -0
  57. package/src/blocks/order-success-block.tsx +641 -0
  58. package/src/blocks/order-tracking-block.tsx +533 -0
  59. package/src/blocks/product-attributes-block.tsx +307 -0
  60. package/src/blocks/product-block-utils.tsx +639 -0
  61. package/src/blocks/product-breadcrumb-block.tsx +215 -0
  62. package/src/blocks/product-description-block.tsx +169 -0
  63. package/src/blocks/product-gallery-block.tsx +1523 -0
  64. package/src/blocks/product-grid-block.tsx +966 -0
  65. package/src/blocks/product-price-block.tsx +373 -0
  66. package/src/blocks/product-recommendations-block.tsx +1433 -0
  67. package/src/blocks/product-title-block.tsx +129 -0
  68. package/src/blocks/profile-form-block.tsx +297 -0
  69. package/src/blocks/recently-viewed-store.ts +201 -0
  70. package/src/blocks/register-form-block.tsx +362 -0
  71. package/src/blocks/share-block.tsx +667 -0
  72. package/src/blocks/size-guide-block.tsx +313 -0
  73. package/src/blocks/sort-select-block.tsx +636 -0
  74. package/src/blocks/stock-status-block.tsx +168 -0
  75. package/src/blocks/store-locator-block.tsx +1184 -0
  76. package/src/blocks/variant-selector-block.tsx +1110 -0
  77. package/src/sections/__tests__/tablet-raw-bucket-readers.test.tsx +186 -0
  78. package/src/utils/__tests__/part-ownership-guard.test.tsx +29 -4
@@ -0,0 +1,184 @@
1
+ import { buildIteratorBlock } from '../utils/iterator-utils';
2
+ import {
3
+ findItemTemplate,
4
+ hasItemTemplate,
5
+ publishesProductItems,
6
+ resolveItemProduct
7
+ } from '../blocks/item-template-utils';
8
+ import { sliceIteratorItems } from '../utils/iterator-utils';
9
+ import type { Block } from '../theme-block';
10
+
11
+ const templateChild = (overrides: Partial<Block> = {}): Block =>
12
+ ({
13
+ id: 'tpl',
14
+ type: 'group',
15
+ label: 'Card',
16
+ order: 0,
17
+ hidden: false,
18
+ value: '',
19
+ properties: { role: { desktop: 'item-template' } },
20
+ styles: {},
21
+ blocks: [
22
+ {
23
+ id: 'tpl-name',
24
+ type: 'text',
25
+ label: 'Name',
26
+ order: 0,
27
+ hidden: false,
28
+ value: '',
29
+ properties: { dataBinding: 'item.name' },
30
+ styles: {},
31
+ blocks: []
32
+ }
33
+ ],
34
+ ...overrides
35
+ } as Block);
36
+
37
+ const listBlock = (children: Block[]): Block =>
38
+ ({
39
+ id: 'grid',
40
+ type: 'product-grid',
41
+ label: 'Product grid',
42
+ order: 0,
43
+ hidden: false,
44
+ value: '',
45
+ properties: {},
46
+ styles: {},
47
+ blocks: children
48
+ } as Block);
49
+
50
+ describe('finding the item template', () => {
51
+ it('picks the child marked as the row layout', () => {
52
+ const block = listBlock([templateChild()]);
53
+
54
+ expect(hasItemTemplate(block)).toBe(true);
55
+ expect(findItemTemplate(block)?.id).toBe('tpl');
56
+ });
57
+
58
+ it('ignores children that are ordinary content', () => {
59
+ // Announcement messages and FAQ entries are children too; only a marked
60
+ // child means "repeat me per row".
61
+ const block = listBlock([
62
+ {
63
+ ...templateChild(),
64
+ id: 'plain',
65
+ properties: {}
66
+ } as Block
67
+ ]);
68
+
69
+ expect(hasItemTemplate(block)).toBe(false);
70
+ expect(findItemTemplate(block)).toBeNull();
71
+ });
72
+
73
+ it('reads the marker per breakpoint', () => {
74
+ const block = listBlock([
75
+ {
76
+ ...templateChild(),
77
+ properties: { role: { desktop: '', mobile: 'item-template' } }
78
+ } as Block
79
+ ]);
80
+
81
+ expect(hasItemTemplate(block, 'desktop')).toBe(false);
82
+ expect(hasItemTemplate(block, 'mobile')).toBe(true);
83
+ });
84
+ });
85
+
86
+ describe('repeating the template over caller-supplied rows', () => {
87
+ // The iterator repeats blocks[0], so the row layout is wrapped exactly the
88
+ // way ItemTemplateList wraps it — passing the card directly would repeat
89
+ // only its first element.
90
+ const expand = (items: Record<string, unknown>[]) =>
91
+ buildIteratorBlock({
92
+ iteratorBlock: {
93
+ ...listBlock([templateChild()]),
94
+ isIterator: true,
95
+ blocks: [templateChild()]
96
+ } as Block,
97
+ items,
98
+ isDesigner: false
99
+ });
100
+
101
+ it('produces one clone per row', () => {
102
+ const result = expand([{ name: 'A' }, { name: 'B' }, { name: 'C' }]);
103
+
104
+ expect(result.blocks).toHaveLength(3);
105
+ });
106
+
107
+ it('resolves item bindings inside each clone', () => {
108
+ const result = expand([{ name: 'Sneaker' }, { name: 'Boot' }]);
109
+ // The card is the repeated unit; the binding sits on the element inside it.
110
+ const values = (result.blocks || []).map(
111
+ (clone) => (clone.blocks || [])[0]?.value
112
+ );
113
+
114
+ expect(values).toEqual(['Sneaker', 'Boot']);
115
+ });
116
+
117
+ it('keeps the template style identity on every clone', () => {
118
+ // The generated stylesheet only knows the template id; the DOM writes
119
+ // styleSourceId || id. Losing it drops every style authored on the row.
120
+ const result = expand([{ name: 'A' }, { name: 'B' }]);
121
+
122
+ (result.blocks || []).forEach((clone) => {
123
+ expect(clone.styleSourceId).toBe('tpl');
124
+ expect(clone.id).not.toBe('tpl');
125
+ });
126
+ });
127
+
128
+ it('does not repeat when the block is not an iterator', () => {
129
+ const plain = templateChild();
130
+ const result = buildIteratorBlock({
131
+ iteratorBlock: plain,
132
+ items: [{ name: 'A' }, { name: 'B' }]
133
+ });
134
+
135
+ expect(result).toBe(plain);
136
+ });
137
+ });
138
+
139
+ describe('the product row scope', () => {
140
+ it('opens only for lists whose rows are products', () => {
141
+ expect(publishesProductItems('product-grid')).toBe(true);
142
+ expect(publishesProductItems('favourites-grid')).toBe(true);
143
+ // An address row carries `pk` and a `name` too — scoping it would point a
144
+ // wishlist toggle at an address id.
145
+ expect(publishesProductItems('address-list')).toBe(false);
146
+ expect(publishesProductItems('product-attributes')).toBe(false);
147
+ expect(publishesProductItems(undefined)).toBe(false);
148
+ });
149
+
150
+ it('falls through rather than shadowing the page product with nothing', () => {
151
+ // A row with no id cannot be acted on, so it must NOT claim the scope:
152
+ // claiming it would silence blocks that read the page product today — the
153
+ // recommendation strip on a PDP before its data arrives.
154
+ expect(resolveItemProduct('product-grid', {})).toBeNull();
155
+ expect(resolveItemProduct('product-grid', { name: 'No id' })).toBeNull();
156
+ expect(resolveItemProduct('product-grid', { pk: 'product-3' })).toBeNull();
157
+ expect(resolveItemProduct('product-grid', undefined)).toBeNull();
158
+ });
159
+
160
+ it('addresses the product, not the wishlist entry', () => {
161
+ // A favourites row carries both: `pk` is the entry, `productPk` the
162
+ // product behind it.
163
+ const scope = resolveItemProduct('favourites-grid', {
164
+ pk: 900,
165
+ productPk: 42,
166
+ name: 'Sneaker'
167
+ });
168
+
169
+ expect(scope?.pk).toBe(42);
170
+ expect(scope?.name).toBe('Sneaker');
171
+ });
172
+
173
+ it('hands each clone the row that clone was built from', () => {
174
+ // With the count properties set, clone N is not items[N]. The scope reads
175
+ // the rows through the same slice, so the two cannot drift apart.
176
+ const rows = [{ n: 0 }, { n: 1 }, { n: 2 }, { n: 3 }];
177
+ const block = {
178
+ ...templateChild(),
179
+ properties: { useIteratorCount: true, iteratorCount: 2, iteratorOffset: 1 }
180
+ } as Block;
181
+
182
+ expect(sliceIteratorItems(block, rows).items).toEqual([{ n: 1 }, { n: 2 }]);
183
+ });
184
+ });