Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1304ffc

Browse files
Stoutstout-ni
andauthored
fix(mock-doc): ensure event bubbling follows shadow DOM boundaries (#6301)
* fix(mock-doc): ensure event bubbling follows shadow DOM boundaries * test(mock-doc): supply event bubbling test cases --------- Co-authored-by: Stout Ni <[email protected]>
1 parent 0d43336 commit 1304ffc

File tree

2 files changed

+116
-1
lines changed

2 files changed

+116
-1
lines changed

src/mock-doc/event.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,30 @@ function triggerEventListener(elm: any, ev: MockEvent) {
218218
} else if (elm.parentElement == null && elm.tagName === 'HTML') {
219219
triggerEventListener(elm.ownerDocument, ev);
220220
} else {
221-
triggerEventListener(elm.parentElement, ev);
221+
const nextTarget = getNextEventTarget(elm, ev);
222+
triggerEventListener(nextTarget, ev);
222223
}
223224
}
224225

226+
function getNextEventTarget(elm: any, ev: MockEvent) {
227+
// If current element has a parent, bubble to parent
228+
if (elm.parentElement) {
229+
return elm.parentElement;
230+
}
231+
232+
// If current element is a Shadow Root (has host property), bubble to the host
233+
if (elm.host && ev.composed) {
234+
return elm.host;
235+
}
236+
237+
// If we're at a Shadow Root boundary and event is composed, bubble to Shadow Host
238+
if (ev.composed && elm.parentNode && elm.parentNode.host) {
239+
return elm.parentNode.host;
240+
}
241+
242+
return null;
243+
}
244+
225245
export function dispatchEvent(currentTarget: any, ev: MockEvent) {
226246
ev.target = currentTarget;
227247
triggerEventListener(currentTarget, ev);
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { MockDocument } from '../document';
2+
import { MockWindow } from '../window';
3+
4+
describe('Shadow DOM event bubbling', () => {
5+
let win: MockWindow;
6+
let doc: MockDocument;
7+
8+
beforeEach(() => {
9+
win = new MockWindow();
10+
doc = win.document as unknown as MockDocument;
11+
});
12+
13+
it('should allow events to bubble from shadow DOM children to shadow host when composed: true', () => {
14+
const parentHost = doc.createElement('my-parent');
15+
const parentShadow = parentHost.attachShadow({ mode: 'open' });
16+
17+
const childHost = doc.createElement('my-child');
18+
childHost.attachShadow({ mode: 'open' });
19+
20+
doc.body.appendChild(parentHost);
21+
parentShadow.appendChild(childHost);
22+
23+
let parentEventReceived = false;
24+
let receivedEventType = '';
25+
let receivedComposed = false;
26+
27+
parentHost.addEventListener('custom-event', (event: any) => {
28+
parentEventReceived = true;
29+
receivedEventType = event.type;
30+
receivedComposed = event.composed;
31+
});
32+
33+
const customEvent = new Event('custom-event', {
34+
bubbles: true,
35+
composed: true,
36+
});
37+
38+
childHost.dispatchEvent(customEvent);
39+
40+
expect(parentEventReceived).toBe(true);
41+
expect(receivedEventType).toBe('custom-event');
42+
expect(receivedComposed).toBe(true);
43+
});
44+
45+
it('should NOT allow events to bubble across shadow boundaries when composed: false', () => {
46+
const parentHost = doc.createElement('my-parent');
47+
const parentShadow = parentHost.attachShadow({ mode: 'open' });
48+
49+
const childHost = doc.createElement('my-child');
50+
51+
doc.body.appendChild(parentHost);
52+
parentShadow.appendChild(childHost);
53+
54+
let parentEventReceived = false;
55+
56+
parentHost.addEventListener('custom-event', () => {
57+
parentEventReceived = true;
58+
});
59+
60+
const customEvent = new Event('custom-event', {
61+
bubbles: true,
62+
composed: false, // This should NOT cross shadow boundaries
63+
});
64+
65+
childHost.dispatchEvent(customEvent);
66+
67+
expect(parentEventReceived).toBe(false);
68+
});
69+
70+
it('should work with CustomEvent and detail property', () => {
71+
const parentHost = doc.createElement('my-parent');
72+
const parentShadow = parentHost.attachShadow({ mode: 'open' });
73+
74+
const childHost = doc.createElement('my-child');
75+
76+
doc.body.appendChild(parentHost);
77+
parentShadow.appendChild(childHost);
78+
79+
let receivedDetail: any = null;
80+
81+
parentHost.addEventListener('custom-event', (event: any) => {
82+
receivedDetail = event.detail;
83+
});
84+
85+
const customEvent = new CustomEvent('custom-event', {
86+
bubbles: true,
87+
composed: true,
88+
detail: { message: 'test data', value: 42 },
89+
});
90+
91+
childHost.dispatchEvent(customEvent);
92+
93+
expect(receivedDetail).toEqual({ message: 'test data', value: 42 });
94+
});
95+
});

0 commit comments

Comments
 (0)