@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,470 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
|
|
4
|
+
// Stub the recursive ThemeBlock so a slide is one countable marker.
|
|
5
|
+
jest.mock('../../theme-block', () => ({
|
|
6
|
+
__esModule: true,
|
|
7
|
+
default: ({ block }: { block: { id: string } }) => (
|
|
8
|
+
<div data-testid="slide" data-id={block.id} />
|
|
9
|
+
)
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Stand in for react-multi-carousel, which measures its container and renders
|
|
14
|
+
* an empty track at a jsdom width of zero — so neither a slide nor an arrow
|
|
15
|
+
* reaches the DOM through the real one. The stub renders exactly what the
|
|
16
|
+
* section hands it, which is the thing under test: which blocks became slides,
|
|
17
|
+
* and which arrows were passed in.
|
|
18
|
+
*/
|
|
19
|
+
jest.mock('react-multi-carousel', () => ({
|
|
20
|
+
__esModule: true,
|
|
21
|
+
default: ({
|
|
22
|
+
children,
|
|
23
|
+
customLeftArrow,
|
|
24
|
+
customRightArrow,
|
|
25
|
+
arrows
|
|
26
|
+
}: {
|
|
27
|
+
children?: React.ReactNode;
|
|
28
|
+
customLeftArrow?: React.ReactNode;
|
|
29
|
+
customRightArrow?: React.ReactNode;
|
|
30
|
+
arrows?: boolean;
|
|
31
|
+
}) => (
|
|
32
|
+
<div data-testid="carousel" data-arrows={String(arrows)}>
|
|
33
|
+
{customLeftArrow}
|
|
34
|
+
{customRightArrow}
|
|
35
|
+
{children}
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
import PostsSliderSection from '../posts-slider-section';
|
|
41
|
+
import {
|
|
42
|
+
isSliderArrowIconBlock,
|
|
43
|
+
LEFT_ARROW_ICON_LABEL,
|
|
44
|
+
RIGHT_ARROW_ICON_LABEL
|
|
45
|
+
} from '../../utils/slider-arrows';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The slider sections used to hand `arrows` straight to react-multi-carousel and
|
|
49
|
+
* let the library draw its own buttons, so an arrow was a thing a brand could
|
|
50
|
+
* switch off and nothing else. It is now drawn from two icon blocks the section
|
|
51
|
+
* carries — the same layers the `slider` BLOCK has always had, which is what
|
|
52
|
+
* makes a Featured Brand Collection's arrows selectable.
|
|
53
|
+
*
|
|
54
|
+
* What these protect, in order: that the arrow blocks never appear as slides,
|
|
55
|
+
* that the whole authored style map lands (the block's version reads a
|
|
56
|
+
* hand-picked ten keys and drops the rest), and that a section with no arrow
|
|
57
|
+
* blocks still behaves exactly as it did.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
const slide = (id: string) => ({
|
|
61
|
+
id,
|
|
62
|
+
type: 'group',
|
|
63
|
+
label: id,
|
|
64
|
+
styles: {},
|
|
65
|
+
order: 0,
|
|
66
|
+
blocks: []
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const arrow = (label: string, styles: Record<string, unknown> = {}) => ({
|
|
70
|
+
id: `arrow-${label === LEFT_ARROW_ICON_LABEL ? 'left' : 'right'}`,
|
|
71
|
+
type: 'icon',
|
|
72
|
+
label,
|
|
73
|
+
value: { url: '', alt: label },
|
|
74
|
+
properties: {},
|
|
75
|
+
styles,
|
|
76
|
+
order: 9
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const renderSection = (blocks: any[], over: Record<string, unknown> = {}) =>
|
|
80
|
+
render(
|
|
81
|
+
<PostsSliderSection
|
|
82
|
+
{...({
|
|
83
|
+
section: {
|
|
84
|
+
id: 'sec',
|
|
85
|
+
type: 'flash-deals-slider',
|
|
86
|
+
label: 'sec',
|
|
87
|
+
// Two visible slides against one item per view, so the arrows are
|
|
88
|
+
// shown at all: `showArrows` also requires something to scroll to.
|
|
89
|
+
properties: { 'items-per-slide': 1, 'show-arrows': true },
|
|
90
|
+
styles: {},
|
|
91
|
+
blocks
|
|
92
|
+
},
|
|
93
|
+
currentBreakpoint: 'desktop',
|
|
94
|
+
placeholderId: 'ph',
|
|
95
|
+
isDesigner: false,
|
|
96
|
+
selectedBlockId: null,
|
|
97
|
+
...over
|
|
98
|
+
} as any)}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const renderSectionWithStyles = (styles: Record<string, unknown>) =>
|
|
103
|
+
render(
|
|
104
|
+
<PostsSliderSection
|
|
105
|
+
{...({
|
|
106
|
+
section: {
|
|
107
|
+
id: 'sec',
|
|
108
|
+
type: 'flash-deals-slider',
|
|
109
|
+
label: 'sec',
|
|
110
|
+
properties: { 'items-per-slide': 1, 'show-arrows': true },
|
|
111
|
+
styles,
|
|
112
|
+
blocks: [slide('a'), slide('b')]
|
|
113
|
+
},
|
|
114
|
+
currentBreakpoint: 'desktop',
|
|
115
|
+
placeholderId: 'ph',
|
|
116
|
+
isDesigner: false,
|
|
117
|
+
selectedBlockId: null
|
|
118
|
+
} as any)}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const arrowButtons = (container: HTMLElement) =>
|
|
123
|
+
Array.from(
|
|
124
|
+
container.querySelectorAll('button[data-block-id^="arrow-"]')
|
|
125
|
+
) as HTMLButtonElement[];
|
|
126
|
+
|
|
127
|
+
describe('identifying an arrow block', () => {
|
|
128
|
+
it('matches an icon block carrying either arrow label', () => {
|
|
129
|
+
expect(isSliderArrowIconBlock(arrow(LEFT_ARROW_ICON_LABEL))).toBe(true);
|
|
130
|
+
expect(isSliderArrowIconBlock(arrow(RIGHT_ARROW_ICON_LABEL))).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('leaves an icon block a designer added as a real slide alone', () => {
|
|
134
|
+
// The section now allows `icon` blocks, so type alone cannot mean "arrow".
|
|
135
|
+
expect(
|
|
136
|
+
isSliderArrowIconBlock({ type: 'icon', label: 'Payment Badge' })
|
|
137
|
+
).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('does not match a non-icon block that happens to share the label', () => {
|
|
141
|
+
expect(
|
|
142
|
+
isSliderArrowIconBlock({ type: 'text', label: LEFT_ARROW_ICON_LABEL })
|
|
143
|
+
).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('the arrow blocks are chrome, not slides', () => {
|
|
148
|
+
it('renders only the real slides', () => {
|
|
149
|
+
const { container } = renderSection([
|
|
150
|
+
slide('a'),
|
|
151
|
+
slide('b'),
|
|
152
|
+
arrow(LEFT_ARROW_ICON_LABEL),
|
|
153
|
+
arrow(RIGHT_ARROW_ICON_LABEL)
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
const ids = Array.from(
|
|
157
|
+
container.querySelectorAll('[data-testid="slide"]')
|
|
158
|
+
).map((node) => node.getAttribute('data-id'));
|
|
159
|
+
|
|
160
|
+
expect(ids).not.toContain('arrow-left');
|
|
161
|
+
expect(ids).not.toContain('arrow-right');
|
|
162
|
+
expect(ids).toContain('a');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('still renders an icon block that is not an arrow as a slide', () => {
|
|
166
|
+
const { container } = renderSection([
|
|
167
|
+
slide('a'),
|
|
168
|
+
slide('b'),
|
|
169
|
+
{ ...slide('badge'), type: 'icon', label: 'Payment Badge' }
|
|
170
|
+
]);
|
|
171
|
+
|
|
172
|
+
const ids = Array.from(
|
|
173
|
+
container.querySelectorAll('[data-testid="slide"]')
|
|
174
|
+
).map((node) => node.getAttribute('data-id'));
|
|
175
|
+
|
|
176
|
+
expect(ids).toContain('badge');
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('the arrow wears everything the designer authored', () => {
|
|
181
|
+
it('applies style keys the block renderer used to drop', () => {
|
|
182
|
+
const { container } = renderSection([
|
|
183
|
+
slide('a'),
|
|
184
|
+
slide('b'),
|
|
185
|
+
arrow(LEFT_ARROW_ICON_LABEL, {
|
|
186
|
+
// None of these three are in the `slider` block's allowlist, which is
|
|
187
|
+
// the gap this closes.
|
|
188
|
+
'box-shadow': { desktop: '0 2px 8px rgba(0,0,0,0.4)' },
|
|
189
|
+
border: { desktop: '2px solid #ff0000' },
|
|
190
|
+
transition: { desktop: 'all 0.3s ease' }
|
|
191
|
+
})
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
const [button] = arrowButtons(container);
|
|
195
|
+
|
|
196
|
+
expect(button).toBeTruthy();
|
|
197
|
+
expect(button.style.boxShadow).toBe('0 2px 8px rgba(0,0,0,0.4)');
|
|
198
|
+
expect(button.style.border).toBe('2px solid #ff0000');
|
|
199
|
+
expect(button.style.transition).toBe('all 0.3s ease');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('lets an authored value replace the plate default rather than lose to it', () => {
|
|
203
|
+
const { container } = renderSection([
|
|
204
|
+
slide('a'),
|
|
205
|
+
slide('b'),
|
|
206
|
+
arrow(LEFT_ARROW_ICON_LABEL, {
|
|
207
|
+
'border-radius': { desktop: '4px' },
|
|
208
|
+
'background-color': { desktop: '#ffffff' },
|
|
209
|
+
width: { desktop: '64px' }
|
|
210
|
+
})
|
|
211
|
+
]);
|
|
212
|
+
|
|
213
|
+
const [button] = arrowButtons(container);
|
|
214
|
+
|
|
215
|
+
// The circle was the one thing the library button never let go of.
|
|
216
|
+
expect(button.style.borderRadius).toBe('4px');
|
|
217
|
+
// jsdom normalises the authored hex; what matters is that it replaced the
|
|
218
|
+
// default translucent black rather than losing to it.
|
|
219
|
+
expect(button.style.backgroundColor).toBe('rgb(255, 255, 255)');
|
|
220
|
+
expect(button.style.width).toBe('64px');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('keeps the defaults for everything left unauthored', () => {
|
|
224
|
+
const { container } = renderSection([
|
|
225
|
+
slide('a'),
|
|
226
|
+
slide('b'),
|
|
227
|
+
arrow(LEFT_ARROW_ICON_LABEL)
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
const [button] = arrowButtons(container);
|
|
231
|
+
|
|
232
|
+
expect(button.style.borderRadius).toBe('50%');
|
|
233
|
+
expect(button.style.width).toBe('40px');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('carries data-block-id, which is how the generated stylesheet reaches it', () => {
|
|
237
|
+
// States, part keys and the custom-CSS hatch are all emitted against
|
|
238
|
+
// `[data-block-id="…"]`, so without the attribute half the editor's
|
|
239
|
+
// controls would write CSS that matched nothing.
|
|
240
|
+
const { container } = renderSection([
|
|
241
|
+
slide('a'),
|
|
242
|
+
slide('b'),
|
|
243
|
+
arrow(RIGHT_ARROW_ICON_LABEL)
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
expect(
|
|
247
|
+
container.querySelector('button[data-block-id="arrow-right"]')
|
|
248
|
+
).toBeTruthy();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('reads the size control onto the glyph, not the plate', () => {
|
|
252
|
+
const { container } = renderSection([
|
|
253
|
+
slide('a'),
|
|
254
|
+
slide('b'),
|
|
255
|
+
arrow(LEFT_ARROW_ICON_LABEL, { size: { desktop: 32 } })
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
const [button] = arrowButtons(container);
|
|
259
|
+
const glyph = button.querySelector('img') as HTMLImageElement;
|
|
260
|
+
|
|
261
|
+
expect(glyph.style.width).toBe('32px');
|
|
262
|
+
// `size` is not a CSS property; it must not reach the button.
|
|
263
|
+
expect(button.getAttribute('style')).not.toContain('size:');
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* The section's own declarations belong to the `<section>` element.
|
|
269
|
+
*
|
|
270
|
+
* `generateThemeCSS` emits them against `[data-section-id="…"]`, and this
|
|
271
|
+
* component used to apply the very same map inline to the box it renders inside
|
|
272
|
+
* that section — so each of them landed on two nested elements. The compounding
|
|
273
|
+
* ones are what a designer actually saw: 68px of side padding came out as 136,
|
|
274
|
+
* and the band's background was painted full-width and then again inside itself.
|
|
275
|
+
*/
|
|
276
|
+
describe('the section is dressed once, by the section element', () => {
|
|
277
|
+
it('puts no section declaration on the box inside it', () => {
|
|
278
|
+
const { container } = renderSectionWithStyles({
|
|
279
|
+
'padding-left': { desktop: '68px' },
|
|
280
|
+
'background-color': { desktop: '#f8f9fb' },
|
|
281
|
+
border: { desktop: '2px solid #000000' }
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const box = container.firstElementChild as HTMLElement;
|
|
285
|
+
|
|
286
|
+
// Not "the right subset" — none of it. The section element carries all of
|
|
287
|
+
// it, and anything here would be a second copy.
|
|
288
|
+
expect(box.getAttribute('style')).toBeNull();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('leaves padding to the stylesheet rather than adding a second copy', () => {
|
|
292
|
+
const { container } = renderSectionWithStyles({
|
|
293
|
+
'padding-left': { desktop: '68px' },
|
|
294
|
+
'padding-right': { desktop: '68px' }
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const box = container.firstElementChild as HTMLElement;
|
|
298
|
+
|
|
299
|
+
expect(box.style.paddingLeft).toBe('');
|
|
300
|
+
expect(box.style.paddingRight).toBe('');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('does not repaint the band background inside the band', () => {
|
|
304
|
+
const { container } = renderSectionWithStyles({
|
|
305
|
+
'background-color': { desktop: '#f8f9fb' }
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const box = container.firstElementChild as HTMLElement;
|
|
309
|
+
|
|
310
|
+
expect(box.style.backgroundColor).toBe('');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('keeps its own centring and content width, which are the box\'s to own', () => {
|
|
314
|
+
const { container } = renderSectionWithStyles({});
|
|
315
|
+
|
|
316
|
+
const box = container.firstElementChild as HTMLElement;
|
|
317
|
+
|
|
318
|
+
expect(box.className).toContain('mx-auto');
|
|
319
|
+
expect(box.className).toContain('max-w-7xl');
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Where the arrow is in the DOM decides what a designer can ask of it.
|
|
325
|
+
*
|
|
326
|
+
* react-multi-carousel renders a `customArrow` inside
|
|
327
|
+
* `.react-multi-carousel-list`, which carries `overflow: hidden` — that is what
|
|
328
|
+
* hides the off-screen slides, so it cannot be opened. An arrow in there is
|
|
329
|
+
* clipped the moment it is offset past the container, which is exactly the
|
|
330
|
+
* design that asks for the arrow flush to the page edge while the cards stay
|
|
331
|
+
* inset. So the arrows are siblings of the carousel instead, in the box that
|
|
332
|
+
* positions it and clips nothing.
|
|
333
|
+
*/
|
|
334
|
+
describe('the arrow sits outside the carousel, not inside its clipped list', () => {
|
|
335
|
+
it('is not a descendant of the carousel', () => {
|
|
336
|
+
const { container } = renderSection([
|
|
337
|
+
slide('a'),
|
|
338
|
+
slide('b'),
|
|
339
|
+
arrow(LEFT_ARROW_ICON_LABEL)
|
|
340
|
+
]);
|
|
341
|
+
|
|
342
|
+
const carousel = container.querySelector('[data-testid="carousel"]')!;
|
|
343
|
+
const [button] = arrowButtons(container);
|
|
344
|
+
|
|
345
|
+
expect(carousel.contains(button)).toBe(false);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('turns the library arrows off, so there is only one pair', () => {
|
|
349
|
+
const { container } = renderSection([
|
|
350
|
+
slide('a'),
|
|
351
|
+
slide('b'),
|
|
352
|
+
arrow(LEFT_ARROW_ICON_LABEL)
|
|
353
|
+
]);
|
|
354
|
+
|
|
355
|
+
expect(
|
|
356
|
+
container
|
|
357
|
+
.querySelector('[data-testid="carousel"]')
|
|
358
|
+
?.getAttribute('data-arrows')
|
|
359
|
+
).toBe('false');
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('keeps the library arrows when the section brought none of its own', () => {
|
|
363
|
+
const { container } = renderSection([slide('a'), slide('b')]);
|
|
364
|
+
|
|
365
|
+
expect(
|
|
366
|
+
container
|
|
367
|
+
.querySelector('[data-testid="carousel"]')
|
|
368
|
+
?.getAttribute('data-arrows')
|
|
369
|
+
).toBe('true');
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('carries a negative offset through instead of falling back to the default', () => {
|
|
373
|
+
// The whole point: -124px is how the arrow reaches past the band's padding.
|
|
374
|
+
const { container } = renderSection([
|
|
375
|
+
slide('a'),
|
|
376
|
+
slide('b'),
|
|
377
|
+
arrow(LEFT_ARROW_ICON_LABEL, { left: { desktop: '-124px' } })
|
|
378
|
+
]);
|
|
379
|
+
|
|
380
|
+
const [button] = arrowButtons(container);
|
|
381
|
+
|
|
382
|
+
expect(button.style.left).toBe('-124px');
|
|
383
|
+
// The `left-4` class default must step aside, or the two would fight.
|
|
384
|
+
expect(button.className).not.toContain('left-4');
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* The band and the slides are two boxes now.
|
|
390
|
+
*
|
|
391
|
+
* They used to be one, so there was nowhere to say "inset the cards from the
|
|
392
|
+
* left, leave the arrows at the edge". Padding the section moved the arrows with
|
|
393
|
+
* the cards — they are positioned inside it — and the only way out was a
|
|
394
|
+
* negative offset on each arrow kept equal to that padding by hand, at every
|
|
395
|
+
* breakpoint.
|
|
396
|
+
*/
|
|
397
|
+
describe('the slides have their own box, which the arrows are not in', () => {
|
|
398
|
+
it('wraps the carousel in the slides part', () => {
|
|
399
|
+
const { container } = renderSection([slide('a'), slide('b')]);
|
|
400
|
+
|
|
401
|
+
const slides = container.querySelector('[data-part="slides"]');
|
|
402
|
+
|
|
403
|
+
expect(slides).toBeTruthy();
|
|
404
|
+
expect(slides!.querySelector('[data-testid="carousel"]')).toBeTruthy();
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('leaves the arrows outside it, so insetting the cards cannot move them', () => {
|
|
408
|
+
const { container } = renderSection([
|
|
409
|
+
slide('a'),
|
|
410
|
+
slide('b'),
|
|
411
|
+
arrow(LEFT_ARROW_ICON_LABEL),
|
|
412
|
+
arrow(RIGHT_ARROW_ICON_LABEL)
|
|
413
|
+
]);
|
|
414
|
+
|
|
415
|
+
const slides = container.querySelector('[data-part="slides"]')!;
|
|
416
|
+
|
|
417
|
+
arrowButtons(container).forEach((button) => {
|
|
418
|
+
expect(slides.contains(button)).toBe(false);
|
|
419
|
+
});
|
|
420
|
+
expect(arrowButtons(container)).toHaveLength(2);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('keeps the arrows in the box that positions them', () => {
|
|
424
|
+
// Siblings of the slides box, children of the box that is `relative` — that
|
|
425
|
+
// is what makes an authored offset resolve against the band.
|
|
426
|
+
const { container } = renderSection([
|
|
427
|
+
slide('a'),
|
|
428
|
+
slide('b'),
|
|
429
|
+
arrow(LEFT_ARROW_ICON_LABEL)
|
|
430
|
+
]);
|
|
431
|
+
|
|
432
|
+
const box = container.firstElementChild as HTMLElement;
|
|
433
|
+
const [button] = arrowButtons(container);
|
|
434
|
+
|
|
435
|
+
expect(button.parentElement).toBe(box);
|
|
436
|
+
expect(box.className).toContain('relative');
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
describe('a section without arrow blocks', () => {
|
|
441
|
+
it('draws no custom arrow at all', () => {
|
|
442
|
+
const { container } = renderSection([slide('a'), slide('b')]);
|
|
443
|
+
|
|
444
|
+
expect(arrowButtons(container)).toHaveLength(0);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
describe('the arrow in the designer', () => {
|
|
449
|
+
it('is marked when its own layer is the selected one', () => {
|
|
450
|
+
const { container } = renderSection(
|
|
451
|
+
[slide('a'), slide('b'), arrow(LEFT_ARROW_ICON_LABEL)],
|
|
452
|
+
{ isDesigner: true, selectedBlockId: 'arrow-left' }
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
const [button] = arrowButtons(container);
|
|
456
|
+
|
|
457
|
+
expect(button.className).toContain('ring-2');
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('is not marked when something else is selected', () => {
|
|
461
|
+
const { container } = renderSection(
|
|
462
|
+
[slide('a'), slide('b'), arrow(LEFT_ARROW_ICON_LABEL)],
|
|
463
|
+
{ isDesigner: true, selectedBlockId: 'a' }
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
const [button] = arrowButtons(container);
|
|
467
|
+
|
|
468
|
+
expect(button.className).not.toContain('ring-2');
|
|
469
|
+
});
|
|
470
|
+
});
|