@shoppexio/builder-runtime 0.1.4 → 0.1.5

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/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export * from './standard-product-page.js';
5
5
  export * from './attributes.js';
6
6
  export * from './content.js';
7
7
  export * from './css-vars.js';
8
+ export { getThemePageBlockOrderFromManifest } from '@shoppex/builder-contracts';
8
9
  export * from './layout.js';
9
10
  export * from './react.js';
10
11
  export * from './storefront-google-fonts.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,kCAAkC,EAAE,MAAM,4BAA4B,CAAC;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export * from './standard-product-page.js';
5
5
  export * from './attributes.js';
6
6
  export * from './content.js';
7
7
  export * from './css-vars.js';
8
+ export { getThemePageBlockOrderFromManifest } from '@shoppex/builder-contracts';
8
9
  export * from './layout.js';
9
10
  export * from './react.js';
10
11
  export * from './storefront-google-fonts.js';
@@ -1 +1 @@
1
- {"version":3,"file":"preview-mode.d.ts","sourceRoot":"","sources":["../src/preview-mode.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,QAAQ,GAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAmB,GAAG,OAAO,CAOlG"}
1
+ {"version":3,"file":"preview-mode.d.ts","sourceRoot":"","sources":["../src/preview-mode.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,OAAO,CAOjF"}
@@ -1,7 +1,7 @@
1
- export function isBuilderPreviewMode(location = window.location) {
1
+ export function isBuilderPreviewMode(location) {
2
2
  if (typeof window === 'undefined') {
3
3
  return false;
4
4
  }
5
- const mode = new URLSearchParams(location.search).get('shoppex-preview-mode');
5
+ const mode = new URLSearchParams((location ?? window.location).search).get('shoppex-preview-mode');
6
6
  return mode === 'theme' || mode === 'builder';
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shoppexio/builder-runtime",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Theme-side Builder v2 runtime helpers for Shoppex storefront themes",
5
5
  "type": "module",
6
6
  "repository": {
@@ -75,11 +75,11 @@
75
75
  "author": "Shoppex",
76
76
  "license": "MIT",
77
77
  "peerDependencies": {
78
- "@shoppexio/builder-contracts": "0.1.1",
78
+ "@shoppexio/builder-contracts": "0.1.2",
79
79
  "react": "^18.0.0 || ^19.0.0"
80
80
  },
81
81
  "dependencies": {
82
- "@shoppex/builder-contracts": "npm:@shoppexio/builder-contracts@0.1.1"
82
+ "@shoppex/builder-contracts": "npm:@shoppexio/builder-contracts@0.1.2"
83
83
  },
84
84
  "devDependencies": {
85
85
  "jsdom": "^28.1.0",
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export * from './standard-product-page.js';
5
5
  export * from './attributes.js';
6
6
  export * from './content.js';
7
7
  export * from './css-vars.js';
8
+ export { getThemePageBlockOrderFromManifest } from '@shoppex/builder-contracts';
8
9
  export * from './layout.js';
9
10
  export * from './react.js';
10
11
  export * from './storefront-google-fonts.js';
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=builder-runtime.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"builder-runtime.test.d.ts","sourceRoot":"","sources":["../src/builder-runtime.test.ts"],"names":[],"mappings":""}
@@ -1,115 +0,0 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import { createBlockInstance, createEmptyBuilderSettings } from '@shoppex/builder-contracts';
3
- import { builderBlock, builderContent, builderSlot, canAddBlock, createBuilderCss, getBuilderContentList, getBuilderContentString, getPageBlocks, resolveBlockSettings, resolveStyleSlotValue, } from './index.js';
4
- function createSettings() {
5
- return {
6
- ...createEmptyBuilderSettings(1),
7
- theme: {
8
- content: {
9
- 'hero.title': 'Launch sale',
10
- faq: {
11
- items: [{ question: 'Can I edit sections?', answer: 'Yes' }],
12
- },
13
- },
14
- layout: {
15
- home: {
16
- blocks: [
17
- createBlockInstance({
18
- id: 'hero-1',
19
- type: 'hero',
20
- settings: { title: 'Hero block' },
21
- style_overrides: {
22
- 'button.radius': { base: 14 },
23
- },
24
- }),
25
- ],
26
- },
27
- },
28
- style_slots: {
29
- 'button.radius': { base: 8, md: 12 },
30
- 'color.primary': '#ff5500',
31
- },
32
- pages: [],
33
- terms: {},
34
- },
35
- };
36
- }
37
- const manifest = {
38
- id: 'default',
39
- name: 'Default',
40
- version: '2.0.0',
41
- pages: {
42
- home: {
43
- label: 'Home',
44
- allowedBlocks: ['hero'],
45
- defaultBlocks: [],
46
- },
47
- },
48
- blocks: {
49
- hero: {
50
- label: 'Hero',
51
- variants: [],
52
- settings: {
53
- title: { type: 'text', label: 'Headline', defaultValue: 'Default hero' },
54
- },
55
- exposedStyleSlots: ['button.radius'],
56
- presets: [],
57
- },
58
- },
59
- styleSlots: {},
60
- presets: {},
61
- };
62
- describe('@shoppex/builder-runtime', () => {
63
- test('reads direct and nested content values', () => {
64
- const settings = createSettings();
65
- expect(getBuilderContentString(settings, 'hero.title')).toBe('Launch sale');
66
- expect(getBuilderContentList(settings, 'faq.items')).toEqual([{ question: 'Can I edit sections?', answer: 'Yes' }]);
67
- });
68
- test('preserves intentionally empty content strings', () => {
69
- const settings = createSettings();
70
- settings.theme.content['hero.subtitle'] = '';
71
- expect(getBuilderContentString(settings, 'hero.subtitle', 'Default subtitle')).toBe('');
72
- });
73
- test('resolves page blocks', () => {
74
- const settings = createSettings();
75
- expect(getPageBlocks(settings, 'home')).toHaveLength(1);
76
- expect(getPageBlocks(settings, 'missing')).toEqual([]);
77
- });
78
- test('resolves style slots with breakpoint fallback and block override', () => {
79
- const settings = createSettings();
80
- const block = settings.theme.layout.home.blocks[0];
81
- expect(resolveStyleSlotValue(settings, 'button.radius', { breakpoint: 'lg' })).toBe(12);
82
- expect(resolveStyleSlotValue(settings, 'button.radius', { block, breakpoint: 'md' })).toBe(14);
83
- });
84
- test('emits CSS variables with responsive media blocks', () => {
85
- const css = createBuilderCss(createSettings());
86
- expect(css).toContain('--builder-button-radius: 8px;');
87
- expect(css).toContain('--builder-color-primary: #ff5500;');
88
- expect(css).toContain('@media (min-width: 768px)');
89
- expect(css).toContain('--builder-button-radius: 12px;');
90
- });
91
- test('checks block limits against the manifest', () => {
92
- const settings = createSettings();
93
- expect(canAddBlock(settings, manifest, 'home', 'hero')).toBe(true);
94
- expect(canAddBlock(settings, manifest, 'home', 'faq')).toBe(false);
95
- });
96
- test('merges block defaults from the manifest', () => {
97
- const block = createBlockInstance({
98
- id: 'hero-2',
99
- type: 'hero',
100
- settings: {},
101
- });
102
- expect(resolveBlockSettings(block, manifest)).toEqual({ title: 'Default hero' });
103
- });
104
- test('creates the three supported builder attributes', () => {
105
- expect(builderContent('hero.title')).toEqual({ 'data-builder-content': 'hero.title' });
106
- expect(builderSlot('button.radius', { blockId: 'hero-1' })).toEqual({
107
- 'data-builder-slot': 'button.radius',
108
- 'data-builder-block': 'hero-1',
109
- });
110
- expect(builderBlock('hero-1', 'hero')).toEqual({
111
- 'data-builder-block': 'hero-1',
112
- 'data-builder-block-type': 'hero',
113
- });
114
- });
115
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=react-runtime.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"react-runtime.test.d.ts","sourceRoot":"","sources":["../src/react-runtime.test.tsx"],"names":[],"mappings":""}
@@ -1,332 +0,0 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
3
- import { createEmptyBuilderSettings } from '@shoppex/builder-contracts';
4
- import { JSDOM } from 'jsdom';
5
- import { act } from 'react';
6
- import { createRoot } from 'react-dom/client';
7
- import { BuilderBlockFrame, BuilderBlockProvider, BuilderPage, BuilderRuntimePreviewProvider, useBuilderContent, useBuilderContentRecord, } from './react.js';
8
- function createSettings(revision, title) {
9
- return {
10
- ...createEmptyBuilderSettings(revision),
11
- theme: {
12
- content: {
13
- 'hero.title': title,
14
- },
15
- layout: {
16
- home: {
17
- blocks: [
18
- {
19
- id: 'hero-1',
20
- type: 'hero',
21
- visible: true,
22
- settings: {},
23
- },
24
- ],
25
- },
26
- },
27
- style_slots: {},
28
- pages: [],
29
- terms: {},
30
- },
31
- };
32
- }
33
- function Probe() {
34
- const title = useBuilderContent('hero.title', '');
35
- return (_jsx("section", { "data-builder-block": "hero-1", "data-builder-block-type": "hero", "data-page-id": "home", children: _jsx("h1", { "data-builder-content": "hero.title", children: title }) }));
36
- }
37
- function FallbackProbe() {
38
- const title = useBuilderContent('hero.title', 'Default title');
39
- return _jsx("h1", { "data-builder-content": "hero.title", children: title });
40
- }
41
- function SlotButtonProbe() {
42
- return (_jsx("section", { "data-builder-block": "hero-1", "data-builder-block-type": "hero", "data-page-id": "home", children: _jsx("button", { type: "button", "data-builder-slot": "button.background", children: "Buy now" }) }));
43
- }
44
- function ScopedProbe() {
45
- return (_jsx(BuilderBlockProvider, { block: {
46
- id: 'hero-1',
47
- type: 'hero',
48
- visible: true,
49
- settings: {
50
- title: 'Scoped title',
51
- subtitle: 'Scoped subtitle',
52
- },
53
- }, children: _jsx(ScopedProbeContent, {}) }));
54
- }
55
- function ScopedProbeContent() {
56
- const title = useBuilderContent('hero.title', '');
57
- const subtitle = useBuilderContent('hero.subtitle', '');
58
- const contentRecord = useBuilderContentRecord();
59
- return (_jsxs(_Fragment, { children: [_jsx("h1", { "data-testid": "scoped-title", children: title }), _jsx("p", { "data-testid": "scoped-subtitle", children: subtitle }), _jsx("span", { "data-testid": "scoped-record", children: String(contentRecord.hero.title) })] }));
60
- }
61
- function HeroBlock({ block }) {
62
- const title = useBuilderContent('hero.title', '');
63
- return (_jsx(BuilderBlockFrame, { as: "section", pageId: "home", block: block, children: _jsx("h1", { "data-builder-content": "hero.title", children: title }) }));
64
- }
65
- describe('BuilderRuntimePreviewProvider', () => {
66
- let dom;
67
- let root;
68
- let postedMessages;
69
- let parentWindow;
70
- beforeEach(() => {
71
- postedMessages = [];
72
- parentWindow = {
73
- postMessage: (message) => {
74
- postedMessages.push(message);
75
- },
76
- };
77
- dom = new JSDOM('<!doctype html><html><body><div id="root"></div></body></html>', {
78
- url: 'https://preview.shoppex.test/?shoppex-preview-mode=theme',
79
- referrer: 'https://dashboard.shoppex.test/theme/editor',
80
- });
81
- Object.defineProperty(dom.window, 'parent', {
82
- configurable: true,
83
- value: parentWindow,
84
- });
85
- Object.assign(globalThis, {
86
- window: dom.window,
87
- document: dom.window.document,
88
- navigator: dom.window.navigator,
89
- Element: dom.window.Element,
90
- HTMLElement: dom.window.HTMLElement,
91
- HTMLImageElement: dom.window.HTMLImageElement,
92
- Node: dom.window.Node,
93
- MouseEvent: dom.window.MouseEvent,
94
- MessageEvent: dom.window.MessageEvent,
95
- CustomEvent: dom.window.CustomEvent,
96
- CSS: {
97
- escape: (value) => value.replaceAll('"', '\\"'),
98
- },
99
- IS_REACT_ACT_ENVIRONMENT: true,
100
- });
101
- root = createRoot(dom.window.document.getElementById('root'));
102
- });
103
- afterEach(async () => {
104
- await act(async () => {
105
- root.unmount();
106
- });
107
- dom.window.close();
108
- const globals = globalThis;
109
- for (const key of [
110
- 'window',
111
- 'document',
112
- 'navigator',
113
- 'Element',
114
- 'HTMLElement',
115
- 'HTMLImageElement',
116
- 'Node',
117
- 'MouseEvent',
118
- 'MessageEvent',
119
- 'CustomEvent',
120
- 'CSS',
121
- 'IS_REACT_ACT_ENVIRONMENT',
122
- ]) {
123
- delete globals[key];
124
- }
125
- });
126
- test('sends READY with the initial revision', async () => {
127
- await act(async () => {
128
- root.render(_jsx(BuilderRuntimePreviewProvider, { initialSettings: createSettings(3, 'Initial title'), children: _jsx(Probe, {}) }));
129
- });
130
- expect(postedMessages).toContainEqual({
131
- type: 'READY',
132
- revision: 3,
133
- health: {
134
- reactMounted: true,
135
- builderRuntimeProvider: true,
136
- protocolVersion: 2,
137
- },
138
- });
139
- expect(dom.window.document.body.textContent).toContain('Initial title');
140
- });
141
- test('normalizes mixed initial builder settings before strict validation', async () => {
142
- await act(async () => {
143
- root.render(_jsx(BuilderRuntimePreviewProvider, { initialSettings: {
144
- version: 2,
145
- revision: 9,
146
- theme: {
147
- content: {
148
- 'hero.title': 'Mixed title',
149
- },
150
- layout: {
151
- home: {
152
- sections: [
153
- {
154
- id: 'hero',
155
- visible: true,
156
- },
157
- ],
158
- },
159
- },
160
- tokens_override: {
161
- colors: {
162
- primary: '#111827',
163
- },
164
- },
165
- },
166
- }, children: _jsx(Probe, {}) }));
167
- });
168
- expect(postedMessages).toContainEqual({
169
- type: 'READY',
170
- revision: 9,
171
- health: {
172
- reactMounted: true,
173
- builderRuntimeProvider: true,
174
- protocolVersion: 2,
175
- },
176
- });
177
- expect(dom.window.document.body.textContent).toContain('Mixed title');
178
- });
179
- test('preserves intentionally empty content strings in hooks', async () => {
180
- await act(async () => {
181
- root.render(_jsx(BuilderRuntimePreviewProvider, { initialSettings: createSettings(4, ''), children: _jsx(FallbackProbe, {}) }));
182
- });
183
- expect(dom.window.document.body.textContent).toBe('');
184
- });
185
- test('responds to explicit READY requests when the parent missed the initial message', async () => {
186
- await act(async () => {
187
- root.render(_jsx(BuilderRuntimePreviewProvider, { initialSettings: createSettings(3, 'Initial title'), children: _jsx(Probe, {}) }));
188
- });
189
- postedMessages.length = 0;
190
- await act(async () => {
191
- dom.window.dispatchEvent(new dom.window.MessageEvent('message', {
192
- origin: 'https://dashboard.shoppex.test',
193
- source: parentWindow,
194
- data: {
195
- type: 'REQUEST_READY',
196
- },
197
- }));
198
- });
199
- expect(postedMessages).toEqual([{
200
- type: 'READY',
201
- revision: 3,
202
- health: {
203
- reactMounted: true,
204
- builderRuntimeProvider: true,
205
- protocolVersion: 2,
206
- },
207
- }]);
208
- });
209
- test('reports iframe runtime errors to the builder parent', async () => {
210
- await act(async () => {
211
- root.render(_jsx(BuilderRuntimePreviewProvider, { initialSettings: createSettings(6, 'Initial title'), children: _jsx(Probe, {}) }));
212
- });
213
- postedMessages.length = 0;
214
- dom.window.dispatchEvent(new dom.window.ErrorEvent('error', {
215
- error: new Error('Preview crashed'),
216
- message: 'Preview crashed',
217
- }));
218
- expect(postedMessages).toEqual([expect.objectContaining({
219
- type: 'PREVIEW_ERROR',
220
- revision: 6,
221
- message: 'Preview crashed',
222
- source: 'error',
223
- })]);
224
- });
225
- test('applies preview state and acknowledges the exact revision', async () => {
226
- await act(async () => {
227
- root.render(_jsx(BuilderRuntimePreviewProvider, { initialSettings: createSettings(3, 'Initial title'), children: _jsx(Probe, {}) }));
228
- });
229
- await act(async () => {
230
- dom.window.dispatchEvent(new dom.window.MessageEvent('message', {
231
- origin: 'https://dashboard.shoppex.test',
232
- source: parentWindow,
233
- data: {
234
- type: 'APPLY_STATE',
235
- revision: 4,
236
- state: createSettings(4, 'Updated title'),
237
- },
238
- }));
239
- });
240
- expect(postedMessages).toContainEqual({ type: 'APPLIED', revision: 4 });
241
- expect(dom.window.document.body.textContent).toContain('Updated title');
242
- });
243
- test('renders page blocks through a typed registry and shared block frame', async () => {
244
- await act(async () => {
245
- root.render(_jsx(BuilderRuntimePreviewProvider, { initialSettings: createSettings(3, 'Registry title'), children: _jsx(BuilderPage, { pageId: "home", registry: {
246
- hero: HeroBlock,
247
- }, context: {} }) }));
248
- });
249
- const renderedBlock = dom.window.document.querySelector('[data-builder-block="hero-1"]');
250
- expect(renderedBlock?.getAttribute('data-page-id')).toBe('home');
251
- expect(renderedBlock?.getAttribute('data-builder-block-type')).toBe('hero');
252
- expect(dom.window.document.body.textContent).toContain('Registry title');
253
- });
254
- test('prefers scoped block settings over global builder content inside a block provider', async () => {
255
- await act(async () => {
256
- root.render(_jsx(BuilderRuntimePreviewProvider, { initialSettings: createSettings(3, 'Global title'), children: _jsx(ScopedProbe, {}) }));
257
- });
258
- expect(dom.window.document.querySelector('[data-testid="scoped-title"]')?.textContent).toBe('Scoped title');
259
- expect(dom.window.document.querySelector('[data-testid="scoped-subtitle"]')?.textContent).toBe('Scoped subtitle');
260
- expect(dom.window.document.querySelector('[data-testid="scoped-record"]')?.textContent).toBe('Scoped title');
261
- });
262
- test('rejects stale preview revisions without changing rendered content', async () => {
263
- await act(async () => {
264
- root.render(_jsx(BuilderRuntimePreviewProvider, { initialSettings: createSettings(5, 'Current title'), children: _jsx(Probe, {}) }));
265
- });
266
- await act(async () => {
267
- dom.window.dispatchEvent(new dom.window.MessageEvent('message', {
268
- origin: 'https://dashboard.shoppex.test',
269
- source: parentWindow,
270
- data: {
271
- type: 'APPLY_STATE',
272
- revision: 4,
273
- state: createSettings(4, 'Stale title'),
274
- },
275
- }));
276
- });
277
- expect(postedMessages).toContainEqual({
278
- type: 'APPLY_FAILED',
279
- revision: 4,
280
- error: 'Stale builder revision',
281
- });
282
- expect(dom.window.document.body.textContent).toContain('Current title');
283
- });
284
- test('emits block selection details from iframe clicks', async () => {
285
- await act(async () => {
286
- root.render(_jsx(BuilderRuntimePreviewProvider, { initialSettings: createSettings(7, 'Clickable title'), children: _jsx(Probe, {}) }));
287
- });
288
- const heading = dom.window.document.querySelector('h1');
289
- heading?.dispatchEvent(new dom.window.MouseEvent('click', { bubbles: true, cancelable: true }));
290
- expect(postedMessages).toContainEqual({
291
- type: 'ELEMENT_CLICKED',
292
- revision: 7,
293
- selection: {
294
- pageId: 'home',
295
- blockId: 'hero-1',
296
- blockType: 'hero',
297
- contentPath: 'hero.title',
298
- elementType: 'text',
299
- },
300
- });
301
- });
302
- test('classifies buttons before style slots for inspector clicks', async () => {
303
- await act(async () => {
304
- root.render(_jsx(BuilderRuntimePreviewProvider, { initialSettings: createSettings(7, 'Clickable title'), children: _jsx(SlotButtonProbe, {}) }));
305
- });
306
- const button = dom.window.document.querySelector('button');
307
- button?.dispatchEvent(new dom.window.MouseEvent('click', { bubbles: true, cancelable: true }));
308
- expect(postedMessages).toContainEqual({
309
- type: 'ELEMENT_CLICKED',
310
- revision: 7,
311
- selection: {
312
- pageId: 'home',
313
- blockId: 'hero-1',
314
- blockType: 'hero',
315
- slotId: 'button.background',
316
- elementType: 'button',
317
- },
318
- });
319
- });
320
- test('does not intercept clicks outside trusted builder preview embeds', async () => {
321
- dom.reconfigure({
322
- url: 'https://preview.shoppex.test/',
323
- });
324
- await act(async () => {
325
- root.render(_jsx(BuilderRuntimePreviewProvider, { initialSettings: createSettings(7, 'Clickable title'), children: _jsx(Probe, {}) }));
326
- });
327
- postedMessages.length = 0;
328
- const heading = dom.window.document.querySelector('h1');
329
- heading?.dispatchEvent(new dom.window.MouseEvent('click', { bubbles: true, cancelable: true }));
330
- expect(postedMessages).toEqual([]);
331
- });
332
- });