Skip to content

Commit 8f1bc55

Browse files
fix(runtime): ensure event listener are not registered twice (#6052)
* fix(runtime): ensure event listener are not registered twice * prettier
1 parent dffb49d commit 8f1bc55

File tree

5 files changed

+102
-4
lines changed

5 files changed

+102
-4
lines changed

src/runtime/bootstrap-custom-element.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,17 @@ export const proxyCustomElement = (Cstr: any, compactMeta: d.ComponentRuntimeMet
6767

6868
const originalConnectedCallback = Cstr.prototype.connectedCallback;
6969
const originalDisconnectedCallback = Cstr.prototype.disconnectedCallback;
70+
let hasHostListenerAttached = false;
7071
Object.assign(Cstr.prototype, {
7172
__registerHost() {
7273
registerHost(this, cmpMeta);
7374
},
7475
connectedCallback() {
75-
const hostRef = getHostRef(this);
76-
addHostEventListeners(this, hostRef, cmpMeta.$listeners$, false);
76+
if (!hasHostListenerAttached) {
77+
const hostRef = getHostRef(this);
78+
addHostEventListeners(this, hostRef, cmpMeta.$listeners$, false);
79+
hasHostListenerAttached = true;
80+
}
7781

7882
connectedCallback(this);
7983
if (BUILD.connectedCallback && originalConnectedCallback) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
:host {
2+
display: block;
3+
padding: 5px;
4+
background: bisque;
5+
cursor: pointer;
6+
max-width: 300px;
7+
}
8+
:host(:focus) {
9+
outline: 2px solid blue;
10+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// @ts-expect-error will be resolved by WDIO
2+
import { defineCustomElement } from '/test-components/event-re-register.js';
3+
4+
defineCustomElement();
5+
6+
describe('event-listener-capture using lazy load components', function () {
7+
const eventListenerCaptureCmp = () => $('event-re-register');
8+
9+
afterEach(() => {
10+
const elem = document.querySelector('event-re-register') as HTMLElement;
11+
if (elem) {
12+
elem.remove();
13+
}
14+
});
15+
16+
it('should only attach keydown event listener once', async () => {
17+
const elem = document.createElement('event-re-register') as HTMLElement;
18+
document.body.appendChild(elem);
19+
20+
const reattach = eventListenerCaptureCmp();
21+
await expect(reattach).toBePresent();
22+
23+
// focus on element
24+
await reattach.click();
25+
await browser.action('key').down('a').pause(100).up('a').perform();
26+
await browser.action('key').down('a').pause(100).up('a').perform();
27+
await browser.action('key').down('a').pause(100).up('a').perform();
28+
29+
// check if event fired 3 times
30+
await expect(reattach).toHaveText(expect.stringContaining('Event fired times: 3'));
31+
32+
// remove node from DOM
33+
elem.remove();
34+
35+
// reattach node to DOM
36+
document.body.appendChild(elem);
37+
38+
// retrigger event
39+
await reattach.click();
40+
await browser.action('key').down('a').pause(100).up('a').perform();
41+
await browser.action('key').down('a').pause(100).up('a').perform();
42+
await browser.action('key').down('a').pause(100).up('a').perform();
43+
44+
// check if event fired 6 times
45+
await expect(reattach).toHaveText(expect.stringContaining('Event fired times: 6'));
46+
});
47+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Component, ComponentInterface, h, Host, Listen, State } from '@stencil/core';
2+
3+
@Component({
4+
tag: 'event-re-register',
5+
styleUrl: 'event-re-register.css',
6+
shadow: true,
7+
})
8+
export class EventReRegister implements ComponentInterface {
9+
@State() eventFiredTimes: number = 0;
10+
@Listen('keydown')
11+
handleKeydown(event: KeyboardEvent) {
12+
this.eventFiredTimes++;
13+
console.log(event);
14+
}
15+
16+
connectedCallback() {
17+
console.log('connected');
18+
}
19+
disconnectedCallback() {
20+
console.log('disconnected');
21+
}
22+
render() {
23+
return (
24+
<Host tabindex="1">
25+
<ul id="reattach">
26+
<li>Focus this component;</li>
27+
<li>Press key;</li>
28+
<li>See console output</li>
29+
<li>Press 'Reconnect' button</li>
30+
<li>Repeat steps 1-3</li>
31+
</ul>
32+
<p>Event fired times: {this.eventFiredTimes}</p>
33+
</Host>
34+
);
35+
}
36+
}

test/wdio/setup.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ const testRequiresManualSetup =
1616
window.__wdioSpec__.includes('no-external-runtime') ||
1717
window.__wdioSpec__.includes('global-script') ||
1818
window.__wdioSpec__.endsWith('custom-tag-name.test.tsx') ||
19-
window.__wdioSpec__.endsWith('page-list.test.ts');
19+
window.__wdioSpec__.endsWith('page-list.test.ts') ||
20+
window.__wdioSpec__.endsWith('event-re-register.test.tsx');
2021

2122
/**
22-
* setup all components defined in tests except for those where we want ot manually setup
23+
* setup all components defined in tests except for those where we want to manually setup
2324
* the components in the test
2425
*/
2526
if (!testRequiresManualSetup) {

0 commit comments

Comments
 (0)