@akinon/pz-theme 2.0.82-beta.0 → 2.0.82
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 +8 -4
- package/package.json +4 -4
- package/src/blocks/__tests__/link-block.test.tsx +87 -0
- package/src/blocks/__tests__/text-block.test.tsx +88 -0
- package/src/blocks/__tests__/video-block.test.tsx +207 -2
- package/src/blocks/item-template-utils.ts +51 -5
- package/src/blocks/link-block.tsx +43 -0
- package/src/blocks/slider-block.tsx +92 -4
- package/src/blocks/text-block.tsx +42 -4
- package/src/blocks/video-block.tsx +208 -80
- package/src/sections/__tests__/posts-slider-arrows.test.tsx +470 -0
- package/src/sections/__tests__/posts-slider-edges.test.tsx +394 -0
- package/src/sections/__tests__/posts-slider-quick-view-no-fetch.test.tsx +205 -0
- package/src/sections/__tests__/posts-slider-quick-view.test.tsx +660 -0
- package/src/sections/posts-slider-section.tsx +482 -115
- package/src/sections/slider-quick-view.tsx +375 -0
- package/src/utils/__tests__/line-breaks.test.ts +215 -0
- package/src/utils/index.ts +12 -1
- package/src/utils/iterator-utils.ts +46 -0
- package/src/utils/line-breaks.ts +142 -0
- package/src/utils/slider-arrows.tsx +252 -0
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { act, render, screen } from '@testing-library/react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Stub the recursive ThemeBlock as the thing a deal card actually is: one
|
|
6
|
+
* anchor to the product page, with a control inside it. The card being a link
|
|
7
|
+
* is the whole point — quick view has to swallow that navigation, and only
|
|
8
|
+
* for a plain left click. The inner button is there because a designer may
|
|
9
|
+
* drop one in (`button` is in this section's `allowedBlocks`), and it must go
|
|
10
|
+
* on working.
|
|
11
|
+
*/
|
|
12
|
+
jest.mock('../../theme-block', () => ({
|
|
13
|
+
__esModule: true,
|
|
14
|
+
default: ({ block }: { block: { id: string; __qvProduct?: unknown } }) => (
|
|
15
|
+
<a data-testid="card" data-id={block.id} href={`/product/${block.id}/`}>
|
|
16
|
+
card
|
|
17
|
+
<button
|
|
18
|
+
type="button"
|
|
19
|
+
data-testid="card-button"
|
|
20
|
+
onClick={() => {
|
|
21
|
+
(globalThis as Record<string, unknown>).__cardButtonClicks =
|
|
22
|
+
(((globalThis as Record<string, unknown>)
|
|
23
|
+
.__cardButtonClicks as number) ?? 0) + 1;
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
add
|
|
27
|
+
</button>
|
|
28
|
+
</a>
|
|
29
|
+
)
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* react-multi-carousel measures its container and renders an empty track at a
|
|
34
|
+
* jsdom width of zero, so no slide would reach the DOM through the real one.
|
|
35
|
+
* The stub renders exactly what the section hands it.
|
|
36
|
+
*/
|
|
37
|
+
jest.mock('react-multi-carousel', () => ({
|
|
38
|
+
__esModule: true,
|
|
39
|
+
default: ({ children }: { children?: React.ReactNode }) => (
|
|
40
|
+
<div data-testid="carousel">{children}</div>
|
|
41
|
+
)
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
import PostsSliderSection from '../posts-slider-section';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Quick view: the switch that decides whether tapping a product card opens a
|
|
48
|
+
* panel or leaves for the product page. It exists because some brands' designs
|
|
49
|
+
* want the panel and some do not, so the two things these lock hardest are
|
|
50
|
+
* that it is OFF unless asked for — every composition published before it
|
|
51
|
+
* carries no such key and must keep navigating byte for byte as it did — and
|
|
52
|
+
* that a slide with no real product behind it never becomes a dead card.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
const product = (pk: number, name: string, price: number) => ({
|
|
56
|
+
pk,
|
|
57
|
+
name,
|
|
58
|
+
absolute_url: `/product/${pk}/`,
|
|
59
|
+
productimage_set: [{ image: `/img/${pk}.jpg` }],
|
|
60
|
+
active_price: { price, currency_type: 'TRY' }
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const ITERATOR = {
|
|
64
|
+
id: 'iter',
|
|
65
|
+
label: 'Iterator',
|
|
66
|
+
isIterator: true,
|
|
67
|
+
order: 0,
|
|
68
|
+
properties: {},
|
|
69
|
+
styles: {},
|
|
70
|
+
blocks: [
|
|
71
|
+
{
|
|
72
|
+
id: 'card',
|
|
73
|
+
type: 'group',
|
|
74
|
+
label: 'Deal Card 1',
|
|
75
|
+
properties: { tag: 'a' },
|
|
76
|
+
styles: {},
|
|
77
|
+
blocks: []
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
interface SectionOptions {
|
|
83
|
+
properties?: Record<string, unknown>;
|
|
84
|
+
products?: Record<string, unknown>[] | null;
|
|
85
|
+
type?: string;
|
|
86
|
+
isDesigner?: boolean;
|
|
87
|
+
breakpoint?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The section as the page really nests it: `SectionWrapper` puts a
|
|
92
|
+
* `relative z-10` box around every custom renderer, and that box — not the
|
|
93
|
+
* band the renderer itself draws — is the stacking context the overlay has to
|
|
94
|
+
* escape. Rendering the section bare would let a z-index fix that does
|
|
95
|
+
* nothing on a real page look like it worked.
|
|
96
|
+
*/
|
|
97
|
+
const sectionTree = ({
|
|
98
|
+
properties = {},
|
|
99
|
+
products = [product(11, 'Blue Stool', 2790), product(22, 'Red Chair', 1490)],
|
|
100
|
+
type = 'flash-deals-slider',
|
|
101
|
+
isDesigner = false,
|
|
102
|
+
breakpoint = 'desktop'
|
|
103
|
+
}: SectionOptions = {}) => (
|
|
104
|
+
<div data-section-id="sec">
|
|
105
|
+
<div data-section-content="" className="relative z-10">
|
|
106
|
+
<PostsSliderSection
|
|
107
|
+
{...({
|
|
108
|
+
section: {
|
|
109
|
+
id: 'sec',
|
|
110
|
+
type,
|
|
111
|
+
label: 'sec',
|
|
112
|
+
properties: { 'items-per-slide': 1, ...properties },
|
|
113
|
+
styles: {},
|
|
114
|
+
blocks: [ITERATOR],
|
|
115
|
+
dataSource: products
|
|
116
|
+
? { details: { collection: { products } } }
|
|
117
|
+
: undefined
|
|
118
|
+
},
|
|
119
|
+
currentBreakpoint: breakpoint,
|
|
120
|
+
placeholderId: 'ph',
|
|
121
|
+
isDesigner
|
|
122
|
+
} as any)}
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const renderSection = (options: SectionOptions = {}) =>
|
|
129
|
+
render(sectionTree(options));
|
|
130
|
+
|
|
131
|
+
const sectionContent = () =>
|
|
132
|
+
document.querySelector('[data-section-content]') as HTMLElement;
|
|
133
|
+
|
|
134
|
+
/** The band the renderer itself draws, inside the wrapper's box. */
|
|
135
|
+
const band = () =>
|
|
136
|
+
document.querySelector('[data-section-content] > div') as HTMLElement;
|
|
137
|
+
|
|
138
|
+
/** A real MouseEvent, so `defaultPrevented` is readable afterwards. */
|
|
139
|
+
const clickCard = (element: Element, init: MouseEventInit = {}) => {
|
|
140
|
+
const event = new MouseEvent('click', {
|
|
141
|
+
bubbles: true,
|
|
142
|
+
cancelable: true,
|
|
143
|
+
button: 0,
|
|
144
|
+
...init
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
act(() => {
|
|
148
|
+
element.dispatchEvent(event);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return event;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const cards = () => screen.getAllByTestId('card');
|
|
155
|
+
const panel = () => document.querySelector('[data-part="quick-view-panel"]');
|
|
156
|
+
|
|
157
|
+
describe('posts slider quick view', () => {
|
|
158
|
+
describe('when the section never asked for it', () => {
|
|
159
|
+
it('leaves the card a plain link and renders no panel', () => {
|
|
160
|
+
renderSection();
|
|
161
|
+
|
|
162
|
+
const event = clickCard(cards()[0]);
|
|
163
|
+
|
|
164
|
+
expect(event.defaultPrevented).toBe(false);
|
|
165
|
+
expect(panel()).toBeNull();
|
|
166
|
+
expect(document.querySelector('[data-slider-quick-view]')).toBeNull();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('leaves the section unlifted and the band unfocusable', () => {
|
|
170
|
+
renderSection();
|
|
171
|
+
|
|
172
|
+
expect(sectionContent().style.zIndex).toBe('');
|
|
173
|
+
expect(band()).not.toHaveAttribute('tabindex');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('when it is on', () => {
|
|
178
|
+
const ON = { 'show-quick-view': true };
|
|
179
|
+
|
|
180
|
+
it('swallows the card click and opens the panel', () => {
|
|
181
|
+
renderSection({ properties: ON });
|
|
182
|
+
|
|
183
|
+
const event = clickCard(cards()[0]);
|
|
184
|
+
|
|
185
|
+
expect(event.defaultPrevented).toBe(true);
|
|
186
|
+
expect(panel()).not.toBeNull();
|
|
187
|
+
expect(panel()).toHaveAttribute('role', 'dialog');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('shows the clicked row, not the first one', () => {
|
|
191
|
+
renderSection({ properties: ON });
|
|
192
|
+
|
|
193
|
+
clickCard(cards()[1]);
|
|
194
|
+
|
|
195
|
+
expect(screen.getByText('Red Chair')).toBeInTheDocument();
|
|
196
|
+
expect(screen.queryByText('Blue Stool')).not.toBeInTheDocument();
|
|
197
|
+
expect(
|
|
198
|
+
document.querySelector('[data-part="quick-view-media"]')
|
|
199
|
+
).toHaveAttribute('src', '/img/22.jpg');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('prints the row price with its currency', () => {
|
|
203
|
+
renderSection({ properties: ON });
|
|
204
|
+
|
|
205
|
+
clickCard(cards()[0]);
|
|
206
|
+
|
|
207
|
+
expect(
|
|
208
|
+
document.querySelector('[data-part="quick-view-price"]')?.textContent
|
|
209
|
+
).toContain('TL');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('links to the clicked product with the authored label', () => {
|
|
213
|
+
renderSection({
|
|
214
|
+
properties: { ...ON, 'quick-view-link-label': 'ÜRÜNÜ GÖR' }
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
clickCard(cards()[1]);
|
|
218
|
+
|
|
219
|
+
const link = document.querySelector('[data-part="quick-view-link"]');
|
|
220
|
+
expect(link).toHaveAttribute('href', '/product/22/');
|
|
221
|
+
expect(link).toHaveTextContent('ÜRÜNÜ GÖR');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('gives the favourite button the clicked row, so it renders at all', () => {
|
|
225
|
+
renderSection({ properties: ON });
|
|
226
|
+
|
|
227
|
+
clickCard(cards()[1]);
|
|
228
|
+
|
|
229
|
+
// Off a resolved product the button renders nothing on a storefront, so
|
|
230
|
+
// its presence is the proof the row reached the item scope.
|
|
231
|
+
expect(
|
|
232
|
+
document.querySelector(
|
|
233
|
+
'[data-part="quick-view-favorite"] [data-favorite-button]'
|
|
234
|
+
)
|
|
235
|
+
).not.toBeNull();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('drops the favourite button when the section turns it off', () => {
|
|
239
|
+
renderSection({
|
|
240
|
+
properties: { ...ON, 'quick-view-show-favorite': false }
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
clickCard(cards()[0]);
|
|
244
|
+
|
|
245
|
+
expect(
|
|
246
|
+
document.querySelector('[data-part="quick-view-favorite"]')
|
|
247
|
+
).toBeNull();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('always draws a close control, so the panel is never a trap', () => {
|
|
251
|
+
renderSection({
|
|
252
|
+
properties: { ...ON, 'quick-view-close-label': 'Kapat' }
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
clickCard(cards()[0]);
|
|
256
|
+
|
|
257
|
+
const close = document.querySelector('[data-part="quick-view-close"]');
|
|
258
|
+
expect(close).toHaveAttribute('aria-label', 'Kapat');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('lifts the wrapper that actually owns the stacking context', () => {
|
|
262
|
+
// Not the band the renderer draws: SectionWrapper's own `relative z-10`
|
|
263
|
+
// sits ABOVE it, so lifting the inner one leaves the section competing
|
|
264
|
+
// with a sticky header at 40 and losing.
|
|
265
|
+
renderSection({ properties: ON });
|
|
266
|
+
|
|
267
|
+
clickCard(cards()[0]);
|
|
268
|
+
|
|
269
|
+
expect(sectionContent().style.zIndex).toBe('1000');
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('closes on the backdrop and puts the section back down', () => {
|
|
273
|
+
renderSection({ properties: ON });
|
|
274
|
+
|
|
275
|
+
clickCard(cards()[0]);
|
|
276
|
+
const backdrop = document.querySelector(
|
|
277
|
+
'[data-part="quick-view-backdrop"]'
|
|
278
|
+
) as Element;
|
|
279
|
+
|
|
280
|
+
clickCard(backdrop);
|
|
281
|
+
|
|
282
|
+
expect(panel()).toBeNull();
|
|
283
|
+
expect(sectionContent().style.zIndex).toBe('');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('takes focus into the panel and gives it back to the clicked card', () => {
|
|
287
|
+
renderSection({ properties: ON });
|
|
288
|
+
const card = cards()[1];
|
|
289
|
+
|
|
290
|
+
clickCard(card);
|
|
291
|
+
// Into the panel's first focusable thing, which is the close control.
|
|
292
|
+
expect(
|
|
293
|
+
document.querySelector('[data-part="quick-view-close"]')
|
|
294
|
+
).toHaveFocus();
|
|
295
|
+
expect(panel()).toHaveAttribute('aria-modal', 'true');
|
|
296
|
+
|
|
297
|
+
act(() => {
|
|
298
|
+
document.dispatchEvent(
|
|
299
|
+
new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })
|
|
300
|
+
);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
expect(panel()).toBeNull();
|
|
304
|
+
// The card LINK, not the slide wrapper the handler sits on — a plain
|
|
305
|
+
// div cannot hold focus, and handing it one drops the shopper at the
|
|
306
|
+
// top of the page.
|
|
307
|
+
expect(card).toHaveFocus();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('lands focus in the section when the card it opened from is gone', () => {
|
|
311
|
+
const properties = { ...ON };
|
|
312
|
+
const { rerender } = render(sectionTree({ properties }));
|
|
313
|
+
|
|
314
|
+
// A real pointer click focuses the link it lands on; jsdom's synthetic
|
|
315
|
+
// dispatch does not, and `useOverlay` falls back to whatever held focus
|
|
316
|
+
// when the panel opened — which would be `document.body`, and body
|
|
317
|
+
// satisfies `focusIfConnected`, so the fallback would never be reached.
|
|
318
|
+
(cards()[1] as HTMLElement).focus();
|
|
319
|
+
clickCard(cards()[1]);
|
|
320
|
+
|
|
321
|
+
// The collection refreshes and the card the shopper opened from is no
|
|
322
|
+
// longer rendered; `focusIfConnected` refuses a detached node, so this
|
|
323
|
+
// is what the fallback exists for.
|
|
324
|
+
rerender(
|
|
325
|
+
sectionTree({ properties, products: [product(99, 'Something', 10)] })
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
act(() => {
|
|
329
|
+
document.dispatchEvent(
|
|
330
|
+
new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
expect(panel()).toBeNull();
|
|
335
|
+
expect(
|
|
336
|
+
document.querySelector('[data-section-content] > div')
|
|
337
|
+
).toHaveFocus();
|
|
338
|
+
expect(document.body).not.toHaveFocus();
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('does not close when the click lands on the panel itself', () => {
|
|
342
|
+
renderSection({ properties: ON });
|
|
343
|
+
|
|
344
|
+
clickCard(cards()[0]);
|
|
345
|
+
clickCard(panel() as Element);
|
|
346
|
+
|
|
347
|
+
expect(panel()).not.toBeNull();
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe('the modifier keys the anchor is for', () => {
|
|
352
|
+
const ON = { 'show-quick-view': true };
|
|
353
|
+
|
|
354
|
+
it.each([
|
|
355
|
+
['meta', { metaKey: true }],
|
|
356
|
+
['ctrl', { ctrlKey: true }],
|
|
357
|
+
['shift', { shiftKey: true }],
|
|
358
|
+
['alt', { altKey: true }]
|
|
359
|
+
])('leaves a %s-click to the browser', (_label, init) => {
|
|
360
|
+
renderSection({ properties: ON });
|
|
361
|
+
|
|
362
|
+
const event = clickCard(cards()[0], init);
|
|
363
|
+
|
|
364
|
+
expect(event.defaultPrevented).toBe(false);
|
|
365
|
+
expect(panel()).toBeNull();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('leaves a middle click to the browser', () => {
|
|
369
|
+
renderSection({ properties: ON });
|
|
370
|
+
|
|
371
|
+
const event = clickCard(cards()[0], { button: 1 });
|
|
372
|
+
|
|
373
|
+
expect(event.defaultPrevented).toBe(false);
|
|
374
|
+
expect(panel()).toBeNull();
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
describe('controls the designer put inside the card', () => {
|
|
379
|
+
const ON = { 'show-quick-view': true };
|
|
380
|
+
|
|
381
|
+
beforeEach(() => {
|
|
382
|
+
(globalThis as Record<string, unknown>).__cardButtonClicks = 0;
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const buttonClicks = () =>
|
|
386
|
+
((globalThis as Record<string, unknown>).__cardButtonClicks as number) ??
|
|
387
|
+
0;
|
|
388
|
+
|
|
389
|
+
it('keeps working, because they own their own click', () => {
|
|
390
|
+
// The handler runs in the capture phase, so a blanket takeover would
|
|
391
|
+
// stop the click before anything inside the card ever saw it — and
|
|
392
|
+
// nothing downstream could object.
|
|
393
|
+
renderSection({ properties: ON });
|
|
394
|
+
|
|
395
|
+
clickCard(screen.getAllByTestId('card-button')[0]);
|
|
396
|
+
|
|
397
|
+
expect(buttonClicks()).toBe(1);
|
|
398
|
+
expect(panel()).toBeNull();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('still let the card itself open the panel', () => {
|
|
402
|
+
renderSection({ properties: ON });
|
|
403
|
+
|
|
404
|
+
clickCard(cards()[0]);
|
|
405
|
+
|
|
406
|
+
expect(panel()).not.toBeNull();
|
|
407
|
+
expect(buttonClicks()).toBe(0);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('are unaffected when quick view is off', () => {
|
|
411
|
+
renderSection();
|
|
412
|
+
|
|
413
|
+
clickCard(screen.getAllByTestId('card-button')[0]);
|
|
414
|
+
|
|
415
|
+
expect(buttonClicks()).toBe(1);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
describe('the panel never outlives its own switch', () => {
|
|
420
|
+
it('drops the row and puts the section back down at a breakpoint where it is off', () => {
|
|
421
|
+
const properties = {
|
|
422
|
+
'show-quick-view': { desktop: true, mobile: false }
|
|
423
|
+
};
|
|
424
|
+
const { rerender } = render(sectionTree({ properties }));
|
|
425
|
+
|
|
426
|
+
clickCard(cards()[0]);
|
|
427
|
+
expect(panel()).not.toBeNull();
|
|
428
|
+
expect(sectionContent().style.zIndex).toBe('1000');
|
|
429
|
+
|
|
430
|
+
// The shopper rotates the phone, or the editor switches viewport.
|
|
431
|
+
rerender(sectionTree({ properties, breakpoint: 'mobile' }));
|
|
432
|
+
|
|
433
|
+
expect(panel()).toBeNull();
|
|
434
|
+
expect(sectionContent().style.zIndex).toBe('');
|
|
435
|
+
|
|
436
|
+
// And it does not come back by itself on the way back.
|
|
437
|
+
rerender(sectionTree({ properties, breakpoint: 'desktop' }));
|
|
438
|
+
|
|
439
|
+
expect(panel()).toBeNull();
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
describe('rows that are not products', () => {
|
|
444
|
+
it('keeps the card a link when the data source returned nothing', () => {
|
|
445
|
+
// `products[index] || {}` upstream makes every clone truthy, so this is
|
|
446
|
+
// the case a truthiness gate would turn into a dead card.
|
|
447
|
+
renderSection({ properties: { 'show-quick-view': true }, products: [] });
|
|
448
|
+
|
|
449
|
+
const event = clickCard(cards()[0]);
|
|
450
|
+
|
|
451
|
+
expect(event.defaultPrevented).toBe(false);
|
|
452
|
+
expect(panel()).toBeNull();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('keeps the card a link when the row carries no usable pk', () => {
|
|
456
|
+
renderSection({
|
|
457
|
+
properties: { 'show-quick-view': true },
|
|
458
|
+
products: [{ pk: 'product-3', name: 'No id' }]
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const event = clickCard(cards()[0]);
|
|
462
|
+
|
|
463
|
+
expect(event.defaultPrevented).toBe(false);
|
|
464
|
+
expect(panel()).toBeNull();
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('keeps a posts slider a posts slider', () => {
|
|
468
|
+
// Same renderer, but its rows are blog posts — a favourite heart
|
|
469
|
+
// pointed at a post id is what the section-type declaration prevents.
|
|
470
|
+
renderSection({
|
|
471
|
+
properties: { 'show-quick-view': true },
|
|
472
|
+
type: 'posts-slider'
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
const event = clickCard(cards()[0]);
|
|
476
|
+
|
|
477
|
+
expect(event.defaultPrevented).toBe(false);
|
|
478
|
+
expect(panel()).toBeNull();
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
describe('the value it is given', () => {
|
|
483
|
+
it('refuses a value it does not know', () => {
|
|
484
|
+
renderSection({ properties: { 'show-quick-view': 'maybe' } });
|
|
485
|
+
|
|
486
|
+
expect(clickCard(cards()[0]).defaultPrevented).toBe(false);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it('resolves per breakpoint', () => {
|
|
490
|
+
renderSection({
|
|
491
|
+
properties: {
|
|
492
|
+
'show-quick-view': { desktop: true, tablet: true, mobile: false }
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
expect(clickCard(cards()[0]).defaultPrevented).toBe(true);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('is off at a breakpoint the author left off', () => {
|
|
500
|
+
render(
|
|
501
|
+
<PostsSliderSection
|
|
502
|
+
{...({
|
|
503
|
+
section: {
|
|
504
|
+
id: 'sec',
|
|
505
|
+
type: 'flash-deals-slider',
|
|
506
|
+
label: 'sec',
|
|
507
|
+
properties: {
|
|
508
|
+
'items-per-slide': 1,
|
|
509
|
+
'show-quick-view': { desktop: true, mobile: false }
|
|
510
|
+
},
|
|
511
|
+
styles: {},
|
|
512
|
+
blocks: [ITERATOR],
|
|
513
|
+
dataSource: {
|
|
514
|
+
details: { collection: { products: [product(11, 'A', 10)] } }
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
currentBreakpoint: 'mobile',
|
|
518
|
+
placeholderId: 'ph',
|
|
519
|
+
isDesigner: false
|
|
520
|
+
} as any)}
|
|
521
|
+
/>
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
expect(clickCard(cards()[0]).defaultPrevented).toBe(false);
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
describe('in the designer', () => {
|
|
529
|
+
it('lets a click select the block instead of opening the panel', () => {
|
|
530
|
+
renderSection({
|
|
531
|
+
properties: { 'show-quick-view': true },
|
|
532
|
+
isDesigner: true
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
const event = clickCard(cards()[0]);
|
|
536
|
+
|
|
537
|
+
expect(event.defaultPrevented).toBe(false);
|
|
538
|
+
expect(panel()).toBeNull();
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('draws the panel in the flow when the preview toggle is on', () => {
|
|
542
|
+
renderSection({
|
|
543
|
+
properties: { 'show-quick-view': true, 'quick-view-preview': true },
|
|
544
|
+
isDesigner: true
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
const backdrop = document.querySelector(
|
|
548
|
+
'[data-part="quick-view-backdrop"]'
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
expect(panel()).not.toBeNull();
|
|
552
|
+
// Nothing fixed over the workspace, and no dimming layer to click
|
|
553
|
+
// through: the author has to keep working around it.
|
|
554
|
+
expect(backdrop).toHaveStyle({ position: 'static' });
|
|
555
|
+
expect(panel()).not.toHaveAttribute('aria-modal');
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('holds the preview open when the backdrop is clicked', () => {
|
|
559
|
+
renderSection({
|
|
560
|
+
properties: { 'show-quick-view': true, 'quick-view-preview': true },
|
|
561
|
+
isDesigner: true
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
clickCard(
|
|
565
|
+
document.querySelector('[data-part="quick-view-backdrop"]') as Element
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
expect(panel()).not.toBeNull();
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('falls back to the sample product before a data source is attached', () => {
|
|
572
|
+
renderSection({
|
|
573
|
+
properties: { 'show-quick-view': true, 'quick-view-preview': true },
|
|
574
|
+
products: null,
|
|
575
|
+
isDesigner: true
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
expect(screen.getByText('Sample product')).toBeInTheDocument();
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it('still draws the link and the photo frame the author came to style', () => {
|
|
582
|
+
// The sample row carries neither a URL nor an image, so a panel that
|
|
583
|
+
// only draws what the row can fill hands the author a preview missing
|
|
584
|
+
// the two parts they turned it on for.
|
|
585
|
+
renderSection({
|
|
586
|
+
properties: {
|
|
587
|
+
'show-quick-view': true,
|
|
588
|
+
'quick-view-preview': true,
|
|
589
|
+
'quick-view-link-label': 'ÜRÜNÜ GÖR'
|
|
590
|
+
},
|
|
591
|
+
products: null,
|
|
592
|
+
isDesigner: true
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
const link = document.querySelector('[data-part="quick-view-link"]');
|
|
596
|
+
expect(link).toHaveTextContent('ÜRÜNÜ GÖR');
|
|
597
|
+
expect(
|
|
598
|
+
document.querySelector('[data-part="quick-view-media"]')
|
|
599
|
+
).not.toBeNull();
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
it('cancels a click on that stand-in link', () => {
|
|
603
|
+
renderSection({
|
|
604
|
+
properties: { 'show-quick-view': true, 'quick-view-preview': true },
|
|
605
|
+
products: null,
|
|
606
|
+
isDesigner: true
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const event = clickCard(
|
|
610
|
+
document.querySelector('[data-part="quick-view-link"]') as Element
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
// Following '#' would scroll the editor iframe to the top.
|
|
614
|
+
expect(event.defaultPrevented).toBe(true);
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
describe('a row missing pieces on a storefront', () => {
|
|
619
|
+
const ON = { 'show-quick-view': true };
|
|
620
|
+
|
|
621
|
+
it('draws no link to nowhere and no empty photo frame', () => {
|
|
622
|
+
renderSection({
|
|
623
|
+
properties: ON,
|
|
624
|
+
products: [{ pk: 7, name: 'Bare row', active_price: { price: 10 } }]
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
clickCard(cards()[0]);
|
|
628
|
+
|
|
629
|
+
expect(screen.getByText('Bare row')).toBeInTheDocument();
|
|
630
|
+
expect(
|
|
631
|
+
document.querySelector('[data-part="quick-view-link"]')
|
|
632
|
+
).toBeNull();
|
|
633
|
+
expect(
|
|
634
|
+
document.querySelector('[data-part="quick-view-media"]')
|
|
635
|
+
).toBeNull();
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
it('accepts a row that carries `url` instead of `absolute_url`', () => {
|
|
639
|
+
renderSection({
|
|
640
|
+
properties: ON,
|
|
641
|
+
products: [{ pk: 7, name: 'Aliased', url: '/product/7/' }]
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
clickCard(cards()[0]);
|
|
645
|
+
|
|
646
|
+
expect(
|
|
647
|
+
document.querySelector('[data-part="quick-view-link"]')
|
|
648
|
+
).toHaveAttribute('href', '/product/7/');
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
it('draws nothing extra when the preview toggle is off', () => {
|
|
652
|
+
renderSection({
|
|
653
|
+
properties: { 'show-quick-view': true },
|
|
654
|
+
isDesigner: true
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
expect(panel()).toBeNull();
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
});
|