@akinon/pz-theme 2.0.37-beta.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 +4 -4
  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,186 @@
1
+ /**
2
+ * The readers that go at a per-breakpoint bucket by hand.
3
+ *
4
+ * `getResponsiveValue` knows where a breakpoint looks when it has nothing of
5
+ * its own. A handful of renderers never asked it — they index the bucket
6
+ * themselves and fall straight to `desktop`. That was harmless while `mobile`
7
+ * was the only other key there was, and it is a live regression the moment a
8
+ * viewport starts reporting `tablet`: at 900px these readers stop seeing the
9
+ * mobile value they saw the day before and hand back the desktop one.
10
+ *
11
+ * Every case below is a composition that authored desktop and mobile and
12
+ * nothing else. The assertion is always the same shape — what 900px renders
13
+ * must be what 767px renders, because that is what it rendered before this
14
+ * breakpoint existed.
15
+ */
16
+ import React from 'react';
17
+ import { render, screen } from '@testing-library/react';
18
+
19
+ jest.mock('../../theme-block', () => ({
20
+ __esModule: true,
21
+ default: ({ block }: { block: { id?: string } }) => (
22
+ <div data-testid="tb" data-id={block?.id} />
23
+ )
24
+ }));
25
+
26
+ import TabsSection from '../tabs-section';
27
+ import ContactFormSection from '../contact-form-section';
28
+ import OrderTrackingLookupSection from '../order-tracking-lookup-section';
29
+ import ImageGalleryBlock from '../../blocks/image-gallery-block';
30
+ import type { Section } from '../../theme-section';
31
+
32
+ const makeSection = (overrides: Record<string, unknown>): Section =>
33
+ ({
34
+ id: 'section-1',
35
+ name: 'S',
36
+ label: 'S',
37
+ properties: {},
38
+ styles: {},
39
+ blocks: [],
40
+ order: 0,
41
+ hidden: false,
42
+ ...overrides
43
+ } as unknown as Section);
44
+
45
+ describe('tabs-section reads the max-width bucket', () => {
46
+ // `max-width: full` on a phone, `narrow` on a desktop: the container is
47
+ // edge-to-edge below 1024 and centred above it.
48
+ const section = makeSection({
49
+ type: 'tabs',
50
+ styles: { 'max-width': { desktop: 'narrow', mobile: 'full' } },
51
+ blocks: [
52
+ {
53
+ id: 't1',
54
+ type: 'tab',
55
+ label: 'One',
56
+ blocks: [
57
+ { id: 't1-header', label: 'Tab Header', blocks: [] },
58
+ { id: 't1-content', label: 'Tab Content', blocks: [] }
59
+ ]
60
+ }
61
+ ]
62
+ });
63
+
64
+ const containerClassAt = (breakpoint: string): string => {
65
+ const { container, unmount } = render(
66
+ <TabsSection section={section} currentBreakpoint={breakpoint} />
67
+ );
68
+ const className = (container.firstChild as HTMLElement).className;
69
+ unmount();
70
+ return className;
71
+ };
72
+
73
+ it('keeps the mobile width across the whole band below 1024', () => {
74
+ expect(containerClassAt('tablet')).toBe(containerClassAt('mobile'));
75
+ });
76
+
77
+ it('does not centre a container the composition asked to run full-width', () => {
78
+ const className = containerClassAt('tablet');
79
+ expect(className).toContain('w-full');
80
+ expect(className).not.toContain('mx-auto');
81
+ expect(className).not.toContain('max-w-4xl');
82
+ });
83
+
84
+ it('still reads the desktop value on a desktop', () => {
85
+ expect(containerClassAt('desktop')).toContain('max-w-4xl');
86
+ });
87
+ });
88
+
89
+ describe('image-gallery-block reads the alt bucket', () => {
90
+ const block = {
91
+ id: 'gallery-1',
92
+ blocks: [
93
+ {
94
+ id: 'img-1',
95
+ type: 'image',
96
+ value: 'https://cdn/a.jpg',
97
+ properties: { alt: { desktop: 'Full product shot', mobile: 'Product' } }
98
+ }
99
+ ]
100
+ };
101
+
102
+ const altAt = (breakpoint: string): string => {
103
+ const { unmount } = render(
104
+ <ImageGalleryBlock
105
+ {...({
106
+ block,
107
+ placeholderId: 'ph',
108
+ sectionId: 'sec',
109
+ isDesigner: false,
110
+ selectedBlockId: null,
111
+ currentBreakpoint: breakpoint
112
+ } as never)}
113
+ />
114
+ );
115
+ const alt = (screen.getByRole('img') as HTMLImageElement).alt;
116
+ unmount();
117
+ return alt;
118
+ };
119
+
120
+ it('keeps the mobile alt text across the whole band below 1024', () => {
121
+ expect(altAt('tablet')).toBe(altAt('mobile'));
122
+ expect(altAt('tablet')).toBe('Product');
123
+ });
124
+
125
+ it('still reads the desktop alt text on a desktop', () => {
126
+ expect(altAt('desktop')).toBe('Full product shot');
127
+ });
128
+ });
129
+
130
+ describe('contact-form-section reads a property bucket', () => {
131
+ const section = makeSection({
132
+ type: 'contact-form',
133
+ properties: {
134
+ 'button-text': { desktop: 'Send us a message', mobile: 'Send' }
135
+ }
136
+ });
137
+
138
+ const buttonTextAt = (breakpoint: string): string => {
139
+ const { unmount } = render(
140
+ <ContactFormSection section={section} currentBreakpoint={breakpoint} />
141
+ );
142
+ const text = screen.getByRole('button', { name: /send/i }).textContent ?? '';
143
+ unmount();
144
+ return text;
145
+ };
146
+
147
+ it('keeps the mobile label across the whole band below 1024', () => {
148
+ expect(buttonTextAt('tablet')).toBe(buttonTextAt('mobile'));
149
+ expect(buttonTextAt('tablet')).toBe('Send');
150
+ });
151
+
152
+ it('still reads the desktop label on a desktop', () => {
153
+ expect(buttonTextAt('desktop')).toBe('Send us a message');
154
+ });
155
+ });
156
+
157
+ describe('order-tracking-lookup-section reads a property bucket', () => {
158
+ const section = makeSection({
159
+ type: 'order-tracking-lookup',
160
+ properties: {
161
+ 'button-text': { desktop: 'Track my order', mobile: 'Track' }
162
+ }
163
+ });
164
+
165
+ const buttonTextAt = (breakpoint: string): string => {
166
+ const { unmount } = render(
167
+ <OrderTrackingLookupSection
168
+ section={section}
169
+ currentBreakpoint={breakpoint}
170
+ />
171
+ );
172
+ const text =
173
+ screen.getByRole('button', { name: /track/i }).textContent ?? '';
174
+ unmount();
175
+ return text;
176
+ };
177
+
178
+ it('keeps the mobile label across the whole band below 1024', () => {
179
+ expect(buttonTextAt('tablet')).toBe(buttonTextAt('mobile'));
180
+ expect(buttonTextAt('tablet')).toBe('Track');
181
+ });
182
+
183
+ it('still reads the desktop label on a desktop', () => {
184
+ expect(buttonTextAt('desktop')).toBe('Track my order');
185
+ });
186
+ });
@@ -62,6 +62,21 @@ const accordion = (
62
62
  ]
63
63
  });
64
64
 
65
+ const faq = (id: string) => ({
66
+ id,
67
+ type: 'faq-accordion',
68
+ label: 'FAQ',
69
+ value: '',
70
+ properties: { searchable: true },
71
+ styles: {},
72
+ order: 0,
73
+ hidden: false,
74
+ blocks: [
75
+ group(`${id}-q1`, 'First question', [text(`${id}-a1`)]),
76
+ group(`${id}-q2`, 'Second question', [text(`${id}-a2`)])
77
+ ]
78
+ });
79
+
65
80
  const sectionOf = (root: unknown): Section[] =>
66
81
  [
67
82
  {
@@ -194,10 +209,20 @@ describe('the guard reaches every level, not just the first', () => {
194
209
  });
195
210
 
196
211
  describe('the guard never costs a block a part it owns', () => {
197
- // The breadth case — one tree built from many part-stamped renderers — lands
198
- // with the renderer vocabulary itself. Only a handful of renderers name their
199
- // internals at this point, so asserting a wide sweep here would either be
200
- // vacuous or force the whole block catalogue into this change.
212
+ it('across nested containers built from shipped renderers', () => {
213
+ const { container } = renderRoot(
214
+ accordion('outer', {}, [
215
+ faq('faq-1'),
216
+ accordion('nested', {}, [faq('faq-2')])
217
+ ])
218
+ );
219
+
220
+ // Premise: the sweep below is meaningless if nothing rendered a part.
221
+ expect(
222
+ container.querySelectorAll('[data-part]').length
223
+ ).toBeGreaterThanOrEqual(20);
224
+ expect(partsTheirOwnBlockCannotReach(container)).toEqual([]);
225
+ });
201
226
 
202
227
  it('across repeated rows, which share one style identity', () => {
203
228
  const expanded = buildIteratorBlock({