@akinon/pz-theme 2.0.37-rc.0 → 2.0.38-beta.0
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 +15 -31
- package/package.json +4 -4
- package/src/__tests__/__snapshots__/facet-filters-block.test.tsx.snap +11 -0
- package/src/__tests__/custom-css.test.ts +181 -0
- package/src/__tests__/designer-chrome-clipping.test.tsx +497 -0
- package/src/__tests__/facet-filters-block.test.tsx +707 -0
- package/src/__tests__/item-template.test.tsx +184 -0
- package/src/__tests__/listing-controls.test.tsx +961 -0
- package/src/__tests__/part-style-scope.test.tsx +229 -0
- package/src/__tests__/part-styles.test.ts +311 -0
- package/src/__tests__/registry-parity.test.ts +42 -0
- package/src/__tests__/row-visibility.test.ts +672 -0
- package/src/__tests__/sort-select-page-cursor.test.tsx +336 -0
- package/src/__tests__/theme-block-html-id.test.tsx +167 -0
- package/src/blocks/__tests__/__snapshots__/product-gallery-block.test.tsx.snap +15 -0
- package/src/blocks/__tests__/accordion-block-state.test.tsx +201 -0
- package/src/blocks/__tests__/facet-filters-block.test.tsx +190 -0
- package/src/blocks/__tests__/listing-context-card-model.test.tsx +230 -0
- package/src/blocks/__tests__/listing-lookup-hardening.test.ts +80 -0
- package/src/blocks/__tests__/product-gallery-block.test.tsx +974 -0
- package/src/blocks/__tests__/product-grid-composition.test.tsx +570 -0
- package/src/blocks/__tests__/product-grid-lookups.test.tsx +230 -0
- package/src/blocks/__tests__/product-price-block.test.tsx +248 -0
- package/src/blocks/__tests__/product-recommendations-block.test.tsx +702 -0
- package/src/blocks/__tests__/share-block.test.tsx +392 -0
- package/src/blocks/__tests__/store-locator-block.test.tsx +691 -0
- package/src/blocks/__tests__/variant-selector-block.test.tsx +817 -0
- package/src/blocks/__tests__/variant-selector-golden.json +20 -0
- package/src/blocks/account-menu-block.tsx +264 -0
- package/src/blocks/add-to-cart-block.tsx +417 -0
- package/src/blocks/address-list-block.tsx +348 -0
- package/src/blocks/applied-filters-block.tsx +288 -0
- package/src/blocks/auth-form-block.tsx +295 -0
- package/src/blocks/basket-items-block.tsx +457 -0
- package/src/blocks/basket-summary-block.tsx +404 -0
- package/src/blocks/block-renderer-registry.tsx +94 -0
- package/src/blocks/change-password-form-block.tsx +240 -0
- package/src/blocks/checkout-steps-block.tsx +288 -0
- package/src/blocks/checkout-summary-block.tsx +449 -0
- package/src/blocks/countdown-block.tsx +182 -0
- package/src/blocks/coupon-list-block.tsx +292 -0
- package/src/blocks/facet-filters-block.tsx +1686 -0
- package/src/blocks/faq-accordion-block.tsx +358 -0
- package/src/blocks/favorite-button-block.tsx +252 -0
- package/src/blocks/favourites-grid-block.tsx +510 -0
- package/src/blocks/forgot-password-block.tsx +155 -0
- package/src/blocks/item-template-utils.ts +131 -0
- package/src/blocks/item-template.tsx +164 -0
- package/src/blocks/listing-breadcrumb-block.tsx +175 -0
- package/src/blocks/listing-context.ts +850 -0
- package/src/blocks/listing-pagination-block.tsx +923 -0
- package/src/blocks/listing-result-count-block.tsx +126 -0
- package/src/blocks/listing-title-block.tsx +115 -0
- package/src/blocks/oauth-buttons-block.tsx +205 -0
- package/src/blocks/order-detail-block.tsx +474 -0
- package/src/blocks/order-list-block.tsx +274 -0
- package/src/blocks/order-success-block.tsx +641 -0
- package/src/blocks/order-tracking-block.tsx +533 -0
- package/src/blocks/product-attributes-block.tsx +307 -0
- package/src/blocks/product-block-utils.tsx +639 -0
- package/src/blocks/product-breadcrumb-block.tsx +215 -0
- package/src/blocks/product-description-block.tsx +169 -0
- package/src/blocks/product-gallery-block.tsx +1523 -0
- package/src/blocks/product-grid-block.tsx +966 -0
- package/src/blocks/product-price-block.tsx +373 -0
- package/src/blocks/product-recommendations-block.tsx +1433 -0
- package/src/blocks/product-title-block.tsx +129 -0
- package/src/blocks/profile-form-block.tsx +297 -0
- package/src/blocks/recently-viewed-store.ts +201 -0
- package/src/blocks/register-form-block.tsx +362 -0
- package/src/blocks/share-block.tsx +667 -0
- package/src/blocks/size-guide-block.tsx +313 -0
- package/src/blocks/sort-select-block.tsx +636 -0
- package/src/blocks/stock-status-block.tsx +168 -0
- package/src/blocks/store-locator-block.tsx +1184 -0
- package/src/blocks/variant-selector-block.tsx +1110 -0
- package/src/sections/__tests__/tablet-raw-bucket-readers.test.tsx +186 -0
- package/src/utils/__tests__/part-ownership-guard.test.tsx +29 -4
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
|
|
4
|
+
import { WithDesignerFeatures } from '../components/with-designer-features';
|
|
5
|
+
import SectionWrapper from '../sections/section-wrapper';
|
|
6
|
+
import { generateThemeCSS } from '../utils';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Every declaration a composition can put on `[data-block-id]` /
|
|
10
|
+
* `[data-section-id]` that establishes a clipping context for the element's
|
|
11
|
+
* descendants. The action toolbar is drawn *above* that box, so before the fix
|
|
12
|
+
* each one of these erased rename / duplicate / delete for the block or
|
|
13
|
+
* section it was set on, with no way back except undo.
|
|
14
|
+
*/
|
|
15
|
+
const CLIPPING_DECLARATIONS: Array<[string, string]> = [
|
|
16
|
+
['overflow', 'hidden'],
|
|
17
|
+
['overflow', 'clip'],
|
|
18
|
+
['overflow', 'auto'],
|
|
19
|
+
['overflow', 'scroll'],
|
|
20
|
+
['overflow-x', 'hidden'],
|
|
21
|
+
['overflow-y', 'hidden'],
|
|
22
|
+
['clip-path', 'inset(0)'],
|
|
23
|
+
['contain', 'paint'],
|
|
24
|
+
['contain', 'strict'],
|
|
25
|
+
['contain', 'content'],
|
|
26
|
+
['contain', 'layout paint']
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const ancestorsOf = (element: Element): Element[] => {
|
|
30
|
+
const chain: Element[] = [];
|
|
31
|
+
for (let node = element.parentElement; node; node = node.parentElement) {
|
|
32
|
+
chain.push(node);
|
|
33
|
+
}
|
|
34
|
+
return chain;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const stylableAncestorsOf = (element: Element): Element[] =>
|
|
38
|
+
ancestorsOf(element).filter(
|
|
39
|
+
(node) =>
|
|
40
|
+
node.hasAttribute('data-block-id') || node.hasAttribute('data-section-id')
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const block = (over: Record<string, unknown> = {}) => ({
|
|
44
|
+
id: 'b1',
|
|
45
|
+
type: 'group',
|
|
46
|
+
label: 'Promo Card',
|
|
47
|
+
properties: {},
|
|
48
|
+
styles: {},
|
|
49
|
+
order: 0,
|
|
50
|
+
hidden: false,
|
|
51
|
+
...over
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const section = (over: Record<string, unknown> = {}) =>
|
|
55
|
+
({
|
|
56
|
+
id: 's1',
|
|
57
|
+
type: 'generic-section',
|
|
58
|
+
name: 'promo',
|
|
59
|
+
label: 'Promo Section',
|
|
60
|
+
properties: {},
|
|
61
|
+
styles: {},
|
|
62
|
+
blocks: [],
|
|
63
|
+
order: 0,
|
|
64
|
+
hidden: false,
|
|
65
|
+
...over
|
|
66
|
+
}) as any;
|
|
67
|
+
|
|
68
|
+
const handlers = () => ({
|
|
69
|
+
onMoveUp: jest.fn(),
|
|
70
|
+
onMoveDown: jest.fn(),
|
|
71
|
+
onDuplicate: jest.fn(),
|
|
72
|
+
onToggleVisibility: jest.fn(),
|
|
73
|
+
onDelete: jest.fn(),
|
|
74
|
+
onRename: jest.fn()
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const renderBlock = (
|
|
78
|
+
clip: Array<[string, string]> = [],
|
|
79
|
+
actions = handlers(),
|
|
80
|
+
over: Record<string, unknown> = {}
|
|
81
|
+
) => {
|
|
82
|
+
const view = render(
|
|
83
|
+
<WithDesignerFeatures
|
|
84
|
+
block={block(over)}
|
|
85
|
+
placeholderId="ph"
|
|
86
|
+
sectionId="s1"
|
|
87
|
+
isDesigner
|
|
88
|
+
isSelected
|
|
89
|
+
{...actions}
|
|
90
|
+
>
|
|
91
|
+
<span>block content</span>
|
|
92
|
+
</WithDesignerFeatures>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const host = view.container.querySelector(
|
|
96
|
+
'[data-block-id="b1"]'
|
|
97
|
+
) as HTMLElement;
|
|
98
|
+
clip.forEach(([property, value]) => host.style.setProperty(property, value));
|
|
99
|
+
|
|
100
|
+
return { ...view, host, actions };
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const renderSection = (
|
|
104
|
+
clip: Array<[string, string]> = [],
|
|
105
|
+
actions = handlers(),
|
|
106
|
+
over: Record<string, unknown> = {}
|
|
107
|
+
) => {
|
|
108
|
+
const view = render(
|
|
109
|
+
<SectionWrapper
|
|
110
|
+
section={section(over)}
|
|
111
|
+
placeholderId="ph"
|
|
112
|
+
isDesigner
|
|
113
|
+
isSelected
|
|
114
|
+
{...actions}
|
|
115
|
+
>
|
|
116
|
+
<span>section content</span>
|
|
117
|
+
</SectionWrapper>
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const host = view.container.querySelector(
|
|
121
|
+
'[data-section-id="s1"]'
|
|
122
|
+
) as HTMLElement;
|
|
123
|
+
clip.forEach(([property, value]) => host.style.setProperty(property, value));
|
|
124
|
+
|
|
125
|
+
return { ...view, host, actions };
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
describe('designer chrome survives a clipping composition', () => {
|
|
129
|
+
describe.each(CLIPPING_DECLARATIONS)('%s: %s', (property, value) => {
|
|
130
|
+
it('keeps the block toolbar outside every element the composition styles', () => {
|
|
131
|
+
const { host } = renderBlock([[property, value]]);
|
|
132
|
+
|
|
133
|
+
const rename = screen.getByTitle('Click to rename');
|
|
134
|
+
const duplicate = screen.getByTitle('Duplicate');
|
|
135
|
+
const remove = screen.getByTitle('Delete');
|
|
136
|
+
|
|
137
|
+
[rename, duplicate, remove].forEach((control) => {
|
|
138
|
+
expect(host.contains(control)).toBe(false);
|
|
139
|
+
expect(stylableAncestorsOf(control)).toEqual([]);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('keeps the section toolbar outside every element the composition styles', () => {
|
|
144
|
+
const { host } = renderSection([[property, value]]);
|
|
145
|
+
|
|
146
|
+
const rename = screen.getByTitle('Click to rename');
|
|
147
|
+
const duplicate = screen.getByTitle('Duplicate');
|
|
148
|
+
const remove = screen.getByTitle('Delete');
|
|
149
|
+
|
|
150
|
+
[rename, duplicate, remove].forEach((control) => {
|
|
151
|
+
expect(host.contains(control)).toBe(false);
|
|
152
|
+
expect(stylableAncestorsOf(control)).toEqual([]);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('leaves rename, duplicate and delete working on the block', () => {
|
|
157
|
+
const { actions } = renderBlock([[property, value]]);
|
|
158
|
+
|
|
159
|
+
fireEvent.click(screen.getByTitle('Duplicate'));
|
|
160
|
+
expect(actions.onDuplicate).toHaveBeenCalledTimes(1);
|
|
161
|
+
|
|
162
|
+
fireEvent.click(screen.getByTitle('Delete'));
|
|
163
|
+
expect(actions.onDelete).toHaveBeenCalledTimes(1);
|
|
164
|
+
|
|
165
|
+
fireEvent.click(screen.getByTitle('Click to rename'));
|
|
166
|
+
fireEvent.change(screen.getByRole('textbox'), {
|
|
167
|
+
target: { value: 'Renamed Card' }
|
|
168
|
+
});
|
|
169
|
+
fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Enter' });
|
|
170
|
+
expect(actions.onRename).toHaveBeenCalledWith('Renamed Card');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('still emits the declaration onto the storefront element', () => {
|
|
174
|
+
const css = generateThemeCSS([
|
|
175
|
+
{
|
|
176
|
+
id: 's1',
|
|
177
|
+
styles: { [property]: value },
|
|
178
|
+
blocks: [{ id: 'b1', styles: { [property]: value } }]
|
|
179
|
+
}
|
|
180
|
+
] as any);
|
|
181
|
+
|
|
182
|
+
expect(css).toContain(`[data-section-id="s1"] {`);
|
|
183
|
+
expect(css).toContain(`[data-block-id="b1"] {`);
|
|
184
|
+
// Once for the section rule, once for the block rule.
|
|
185
|
+
expect(css.split(`${property}: ${value};`)).toHaveLength(3);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('designer chrome placement is unchanged', () => {
|
|
191
|
+
afterEach(() => jest.restoreAllMocks());
|
|
192
|
+
|
|
193
|
+
it('pins the toolbar to the host box it was drawn against before', () => {
|
|
194
|
+
const hostRect = {
|
|
195
|
+
top: 120,
|
|
196
|
+
left: 40,
|
|
197
|
+
right: 340,
|
|
198
|
+
bottom: 320,
|
|
199
|
+
width: 300,
|
|
200
|
+
height: 200,
|
|
201
|
+
x: 40,
|
|
202
|
+
y: 120
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
jest
|
|
206
|
+
.spyOn(Element.prototype, 'getBoundingClientRect')
|
|
207
|
+
.mockImplementation(function (this: Element) {
|
|
208
|
+
const rect = this.hasAttribute('data-block-id')
|
|
209
|
+
? hostRect
|
|
210
|
+
: {
|
|
211
|
+
top: 0,
|
|
212
|
+
left: 0,
|
|
213
|
+
right: 0,
|
|
214
|
+
bottom: 0,
|
|
215
|
+
width: 0,
|
|
216
|
+
height: 0,
|
|
217
|
+
x: 0,
|
|
218
|
+
y: 0
|
|
219
|
+
};
|
|
220
|
+
return { ...rect, toJSON: () => rect } as DOMRect;
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
renderBlock();
|
|
224
|
+
|
|
225
|
+
const toolbar = document.querySelector(
|
|
226
|
+
'[data-action-toolbar]'
|
|
227
|
+
) as HTMLElement;
|
|
228
|
+
expect(toolbar).not.toBeNull();
|
|
229
|
+
|
|
230
|
+
// Same offsets as before: above the host's top edge, flush with its left.
|
|
231
|
+
expect(toolbar.className).toContain('absolute');
|
|
232
|
+
expect(toolbar.className).toContain('-top-0.5');
|
|
233
|
+
expect(toolbar.className).toContain('left-0');
|
|
234
|
+
expect(toolbar.className).toContain('-translate-y-full');
|
|
235
|
+
|
|
236
|
+
const frame = toolbar.parentElement as HTMLElement;
|
|
237
|
+
expect(frame.style.position).toBe('fixed');
|
|
238
|
+
expect(frame.style.top).toBe('120px');
|
|
239
|
+
expect(frame.style.left).toBe('40px');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('leaves nothing in the host that takes layout space', () => {
|
|
243
|
+
const { host } = renderBlock();
|
|
244
|
+
|
|
245
|
+
const anchor = host.querySelector(
|
|
246
|
+
'[data-action-toolbar-anchor]'
|
|
247
|
+
) as HTMLElement;
|
|
248
|
+
|
|
249
|
+
// Anything with a box here is a flex item or grid cell the storefront does
|
|
250
|
+
// not have, i.e. a canvas/storefront parity break.
|
|
251
|
+
expect(anchor.style.display).toBe('none');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('draws no toolbar for a hidden block, which has no box to sit above', () => {
|
|
255
|
+
renderBlock([], handlers(), { hidden: true });
|
|
256
|
+
|
|
257
|
+
expect(document.querySelector('[data-action-toolbar]')).toBeNull();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* "Move Up" is a button on this toolbar. It reorders the block: the host
|
|
262
|
+
* moves to a different y, but it is not resized, it is not scrolled, and
|
|
263
|
+
* none of its own attributes change — so no scroll, resize, ResizeObserver
|
|
264
|
+
* or MutationObserver record is produced. Drawn in-tree the toolbar came
|
|
265
|
+
* along for free; measured once on mount it is left behind, floating over an
|
|
266
|
+
* unrelated part of the page until something else happens to fire a
|
|
267
|
+
* listener. The canvas re-renders for every one of these, so a re-render has
|
|
268
|
+
* to be a measuring point too.
|
|
269
|
+
*/
|
|
270
|
+
it('follows the host when a re-render moves it with no scroll, resize or attribute change', () => {
|
|
271
|
+
let hostTop = 300;
|
|
272
|
+
|
|
273
|
+
jest
|
|
274
|
+
.spyOn(Element.prototype, 'getBoundingClientRect')
|
|
275
|
+
.mockImplementation(function (this: Element) {
|
|
276
|
+
const rect = this.hasAttribute('data-block-id')
|
|
277
|
+
? {
|
|
278
|
+
top: hostTop,
|
|
279
|
+
left: 40,
|
|
280
|
+
right: 340,
|
|
281
|
+
bottom: hostTop + 200,
|
|
282
|
+
width: 300,
|
|
283
|
+
height: 200,
|
|
284
|
+
x: 40,
|
|
285
|
+
y: hostTop
|
|
286
|
+
}
|
|
287
|
+
: {
|
|
288
|
+
top: 0,
|
|
289
|
+
left: 0,
|
|
290
|
+
right: 0,
|
|
291
|
+
bottom: 0,
|
|
292
|
+
width: 0,
|
|
293
|
+
height: 0,
|
|
294
|
+
x: 0,
|
|
295
|
+
y: 0
|
|
296
|
+
};
|
|
297
|
+
return { ...rect, toJSON: () => rect } as DOMRect;
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const actions = handlers();
|
|
301
|
+
const { rerender } = renderBlock([], actions);
|
|
302
|
+
|
|
303
|
+
const frameOf = () =>
|
|
304
|
+
(document.querySelector('[data-action-toolbar]') as HTMLElement)
|
|
305
|
+
.parentElement as HTMLElement;
|
|
306
|
+
|
|
307
|
+
expect(frameOf().style.top).toBe('300px');
|
|
308
|
+
|
|
309
|
+
// The reorder the toolbar's own button triggers.
|
|
310
|
+
hostTop = 100;
|
|
311
|
+
rerender(
|
|
312
|
+
<WithDesignerFeatures
|
|
313
|
+
block={block()}
|
|
314
|
+
placeholderId="ph"
|
|
315
|
+
sectionId="s1"
|
|
316
|
+
isDesigner
|
|
317
|
+
isSelected
|
|
318
|
+
{...actions}
|
|
319
|
+
>
|
|
320
|
+
<span>block content</span>
|
|
321
|
+
</WithDesignerFeatures>
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
expect(frameOf().style.top).toBe('100px');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* The shape the canvas actually renders: a block inside the section that
|
|
329
|
+
* owns it, both able to draw chrome. Once the toolbar is resolved by walking
|
|
330
|
+
* up from an anchor instead of being handed its host, picking the *nearest*
|
|
331
|
+
* styled ancestor is the whole contract — a block toolbar pinned to its
|
|
332
|
+
* section would sit at the wrong end of a tall section.
|
|
333
|
+
*/
|
|
334
|
+
it('pins a nested block toolbar to the block, not to the section around it', () => {
|
|
335
|
+
const sectionRect = {
|
|
336
|
+
top: 0,
|
|
337
|
+
left: 0,
|
|
338
|
+
right: 1440,
|
|
339
|
+
bottom: 900,
|
|
340
|
+
width: 1440,
|
|
341
|
+
height: 900,
|
|
342
|
+
x: 0,
|
|
343
|
+
y: 0
|
|
344
|
+
};
|
|
345
|
+
const blockRect = {
|
|
346
|
+
top: 640,
|
|
347
|
+
left: 220,
|
|
348
|
+
right: 520,
|
|
349
|
+
bottom: 840,
|
|
350
|
+
width: 300,
|
|
351
|
+
height: 200,
|
|
352
|
+
x: 220,
|
|
353
|
+
y: 640
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
jest
|
|
357
|
+
.spyOn(Element.prototype, 'getBoundingClientRect')
|
|
358
|
+
.mockImplementation(function (this: Element) {
|
|
359
|
+
const rect = this.hasAttribute('data-block-id')
|
|
360
|
+
? blockRect
|
|
361
|
+
: this.hasAttribute('data-section-id')
|
|
362
|
+
? sectionRect
|
|
363
|
+
: {
|
|
364
|
+
top: 0,
|
|
365
|
+
left: 0,
|
|
366
|
+
right: 0,
|
|
367
|
+
bottom: 0,
|
|
368
|
+
width: 0,
|
|
369
|
+
height: 0,
|
|
370
|
+
x: 0,
|
|
371
|
+
y: 0
|
|
372
|
+
};
|
|
373
|
+
return { ...rect, toJSON: () => rect } as DOMRect;
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
render(
|
|
377
|
+
<SectionWrapper
|
|
378
|
+
section={section()}
|
|
379
|
+
placeholderId="ph"
|
|
380
|
+
isDesigner
|
|
381
|
+
isSelected
|
|
382
|
+
{...handlers()}
|
|
383
|
+
>
|
|
384
|
+
<WithDesignerFeatures
|
|
385
|
+
block={block()}
|
|
386
|
+
placeholderId="ph"
|
|
387
|
+
sectionId="s1"
|
|
388
|
+
isDesigner
|
|
389
|
+
isSelected
|
|
390
|
+
{...handlers()}
|
|
391
|
+
>
|
|
392
|
+
<span>block content</span>
|
|
393
|
+
</WithDesignerFeatures>
|
|
394
|
+
</SectionWrapper>
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const anchors = Array.from(
|
|
398
|
+
document.querySelectorAll('[data-action-toolbar-anchor]')
|
|
399
|
+
);
|
|
400
|
+
expect(anchors).toHaveLength(2);
|
|
401
|
+
|
|
402
|
+
const frames = Array.from(
|
|
403
|
+
document.querySelectorAll('[data-action-toolbar]')
|
|
404
|
+
).map((toolbar) => (toolbar.parentElement as HTMLElement).style);
|
|
405
|
+
|
|
406
|
+
const tops = frames.map((style) => style.top).sort();
|
|
407
|
+
const lefts = frames.map((style) => style.left).sort();
|
|
408
|
+
|
|
409
|
+
expect(tops).toEqual(['0px', '640px']);
|
|
410
|
+
expect(lefts).toEqual(['0px', '220px']);
|
|
411
|
+
|
|
412
|
+
// The two must be pinned to different boxes: one toolbar landing on both
|
|
413
|
+
// corners would mean `closest` resolved to the same host twice.
|
|
414
|
+
expect(new Set(frames.map((style) => `${style.top}/${style.left}`)).size)
|
|
415
|
+
.toBe(2);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('follows the host when a re-render moves a section', () => {
|
|
419
|
+
let hostTop = 500;
|
|
420
|
+
|
|
421
|
+
jest
|
|
422
|
+
.spyOn(Element.prototype, 'getBoundingClientRect')
|
|
423
|
+
.mockImplementation(function (this: Element) {
|
|
424
|
+
const rect = this.hasAttribute('data-section-id')
|
|
425
|
+
? {
|
|
426
|
+
top: hostTop,
|
|
427
|
+
left: 0,
|
|
428
|
+
right: 1440,
|
|
429
|
+
bottom: hostTop + 400,
|
|
430
|
+
width: 1440,
|
|
431
|
+
height: 400,
|
|
432
|
+
x: 0,
|
|
433
|
+
y: hostTop
|
|
434
|
+
}
|
|
435
|
+
: {
|
|
436
|
+
top: 0,
|
|
437
|
+
left: 0,
|
|
438
|
+
right: 0,
|
|
439
|
+
bottom: 0,
|
|
440
|
+
width: 0,
|
|
441
|
+
height: 0,
|
|
442
|
+
x: 0,
|
|
443
|
+
y: 0
|
|
444
|
+
};
|
|
445
|
+
return { ...rect, toJSON: () => rect } as DOMRect;
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const actions = handlers();
|
|
449
|
+
const { rerender } = renderSection([], actions);
|
|
450
|
+
|
|
451
|
+
const frameOf = () =>
|
|
452
|
+
(document.querySelector('[data-action-toolbar]') as HTMLElement)
|
|
453
|
+
.parentElement as HTMLElement;
|
|
454
|
+
|
|
455
|
+
expect(frameOf().style.top).toBe('500px');
|
|
456
|
+
|
|
457
|
+
hostTop = 60;
|
|
458
|
+
rerender(
|
|
459
|
+
<SectionWrapper
|
|
460
|
+
section={section()}
|
|
461
|
+
placeholderId="ph"
|
|
462
|
+
isDesigner
|
|
463
|
+
isSelected
|
|
464
|
+
{...actions}
|
|
465
|
+
>
|
|
466
|
+
<span>section content</span>
|
|
467
|
+
</SectionWrapper>
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
expect(frameOf().style.top).toBe('60px');
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
describe('storefront output is untouched', () => {
|
|
475
|
+
it('renders no designer chrome and adds nothing to the document', () => {
|
|
476
|
+
const { container } = render(
|
|
477
|
+
<WithDesignerFeatures
|
|
478
|
+
block={block()}
|
|
479
|
+
placeholderId="ph"
|
|
480
|
+
sectionId="s1"
|
|
481
|
+
isDesigner={false}
|
|
482
|
+
isSelected
|
|
483
|
+
{...handlers()}
|
|
484
|
+
>
|
|
485
|
+
<span>block content</span>
|
|
486
|
+
</WithDesignerFeatures>
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
const host = container.querySelector('[data-block-id="b1"]') as HTMLElement;
|
|
490
|
+
expect(host.children).toHaveLength(1);
|
|
491
|
+
expect(host.innerHTML).toBe('<span>block content</span>');
|
|
492
|
+
expect(document.querySelectorAll('[data-action-toolbar]')).toHaveLength(0);
|
|
493
|
+
expect(
|
|
494
|
+
document.querySelectorAll('[data-action-toolbar-anchor]')
|
|
495
|
+
).toHaveLength(0);
|
|
496
|
+
});
|
|
497
|
+
});
|