mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(core): prevents event replay from being called on comment nodes (#60130)
In some rare cases with directives, it is possible that the stash function might be called on a comment node. This actually verifies that the node is an element and exits otherwise. fixes: #60070 PR Close #60130
This commit is contained in:
committed by
Miles Malerba
parent
8a73327ba0
commit
7ab0a8d1e7
@@ -6,7 +6,15 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {APP_ID, Component, destroyPlatform, ErrorHandler, PLATFORM_ID} from '@angular/core';
|
||||
import {
|
||||
APP_ID,
|
||||
Component,
|
||||
destroyPlatform,
|
||||
Directive,
|
||||
ErrorHandler,
|
||||
HostListener,
|
||||
PLATFORM_ID,
|
||||
} from '@angular/core';
|
||||
import {withEventReplay} from '@angular/platform-browser';
|
||||
|
||||
import {EventPhase} from '@angular/core/primitives/event-dispatch';
|
||||
@@ -183,6 +191,128 @@ describe('event replay', () => {
|
||||
expect(outerOnClickSpy).toHaveBeenCalledBefore(innerOnClickSpy);
|
||||
});
|
||||
|
||||
describe('host bindings', () => {
|
||||
it('should not error when when binding to document:click on a container', async () => {
|
||||
const clickSpy = jasmine.createSpy();
|
||||
@Directive({
|
||||
selector: '[add-listener]',
|
||||
})
|
||||
class AddGlobalListener {
|
||||
@HostListener('document:click')
|
||||
handleClick = clickSpy;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
template: `
|
||||
<ng-container add-listener>
|
||||
<button id="click-me">Click me!</button>
|
||||
</ng-container>`,
|
||||
imports: [AddGlobalListener],
|
||||
})
|
||||
class AppComponent {}
|
||||
|
||||
const appId = 'custom-app-id';
|
||||
const providers = [{provide: APP_ID, useValue: appId}];
|
||||
const hydrationFeatures = () => [withEventReplay()];
|
||||
|
||||
const html = await ssr(AppComponent, {envProviders: providers, hydrationFeatures});
|
||||
const ssrContents = getAppContents(html);
|
||||
const doc = getDocument();
|
||||
|
||||
prepareEnvironment(doc, ssrContents);
|
||||
resetTViewsFor(AppComponent);
|
||||
const clickMe = doc.getElementById('click-me')!;
|
||||
clickMe.click();
|
||||
await hydrate(doc, AppComponent, {
|
||||
envProviders: [{provide: PLATFORM_ID, useValue: 'browser'}, ...providers],
|
||||
hydrationFeatures,
|
||||
});
|
||||
|
||||
expect(clickSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not error when when binding to window:click on a container', async () => {
|
||||
const clickSpy = jasmine.createSpy();
|
||||
@Directive({
|
||||
selector: '[add-listener]',
|
||||
})
|
||||
class AddGlobalListener {
|
||||
@HostListener('window:click')
|
||||
handleClick = clickSpy;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
template: `
|
||||
<ng-container add-listener>
|
||||
<button id="click-me">Click me!</button>
|
||||
</ng-container>`,
|
||||
imports: [AddGlobalListener],
|
||||
})
|
||||
class AppComponent {}
|
||||
|
||||
const appId = 'custom-app-id';
|
||||
const providers = [{provide: APP_ID, useValue: appId}];
|
||||
const hydrationFeatures = () => [withEventReplay()];
|
||||
|
||||
const html = await ssr(AppComponent, {envProviders: providers, hydrationFeatures});
|
||||
const ssrContents = getAppContents(html);
|
||||
const doc = getDocument();
|
||||
|
||||
prepareEnvironment(doc, ssrContents);
|
||||
resetTViewsFor(AppComponent);
|
||||
const clickMe = doc.getElementById('click-me')!;
|
||||
clickMe.click();
|
||||
await hydrate(doc, AppComponent, {
|
||||
envProviders: [{provide: PLATFORM_ID, useValue: 'browser'}, ...providers],
|
||||
hydrationFeatures,
|
||||
});
|
||||
|
||||
expect(clickSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not error when when binding to body:click on a container', async () => {
|
||||
const clickSpy = jasmine.createSpy();
|
||||
@Directive({
|
||||
selector: '[add-listener]',
|
||||
})
|
||||
class AddGlobalListener {
|
||||
@HostListener('body:click')
|
||||
handleClick = clickSpy;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
template: `
|
||||
<ng-container add-listener>
|
||||
<button id="click-me">Click me!</button>
|
||||
</ng-container>`,
|
||||
imports: [AddGlobalListener],
|
||||
})
|
||||
class AppComponent {}
|
||||
|
||||
const appId = 'custom-app-id';
|
||||
const providers = [{provide: APP_ID, useValue: appId}];
|
||||
const hydrationFeatures = () => [withEventReplay()];
|
||||
|
||||
const html = await ssr(AppComponent, {envProviders: providers, hydrationFeatures});
|
||||
const ssrContents = getAppContents(html);
|
||||
const doc = getDocument();
|
||||
|
||||
prepareEnvironment(doc, ssrContents);
|
||||
resetTViewsFor(AppComponent);
|
||||
const clickMe = doc.getElementById('click-me')!;
|
||||
clickMe.click();
|
||||
await hydrate(doc, AppComponent, {
|
||||
envProviders: [{provide: PLATFORM_ID, useValue: 'browser'}, ...providers],
|
||||
hydrationFeatures,
|
||||
});
|
||||
|
||||
expect(clickSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove jsaction attributes, but continue listening to events.', async () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
|
||||
Reference in New Issue
Block a user