Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import {ActionResolver} from './action_resolver';
import {Dispatcher} from './dispatcher';
import {EventInfo, EventInfoWrapper} from './event_info';
import {isCaptureEventType} from './event_type';
import {UnrenamedEventContract} from './eventcontract';
import {Restriction} from './restriction';

Expand Down Expand Up @@ -81,6 +82,13 @@ export class EventDispatcher {
prepareEventForBubbling(eventInfoWrapper);
while (eventInfoWrapper.getAction()) {
prepareEventForDispatch(eventInfoWrapper);
// If this is a capture event, ONLY dispatch if the action element is the target.
if (
isCaptureEventType(eventInfoWrapper.getEventType()) &&
eventInfoWrapper.getAction()!.element !== eventInfoWrapper.getTargetElement()
) {
return;
}
this.dispatchDelegate(eventInfoWrapper.getEvent(), eventInfoWrapper.getAction()!.name);
if (propagationStopped(eventInfoWrapper)) {
return;
Expand Down
60 changes: 28 additions & 32 deletions packages/core/test/event_dispatch/event_dispatch_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,38 +109,6 @@ describe('event dispatch', () => {
inner.click();
expect(outerOnClickSpy).toHaveBeenCalledBefore(innerOnClickSpy);
});
it('should serialize event types to be listened to and jsaction cache entry', async () => {
const clickSpy = jasmine.createSpy('onClick');
const focusSpy = jasmine.createSpy('onFocus');
@Component({
standalone: true,
selector: 'app',
template: `
<div (click)="onClick()" id="click-element">
<div id="focus-container">
<div id="focus-action-element" (focus)="onFocus()">
<button id="focus-target-element">Focus Button</button>
</div>
</div>
</div>
`,
})
class SimpleComponent {
onClick = clickSpy;
onFocus = focusSpy;
}
configureTestingModule([SimpleComponent]);
fixture = TestBed.createComponent(SimpleComponent);
const nativeElement = fixture.debugElement.nativeElement;
const el = nativeElement.querySelector('#click-element')!;
const button = nativeElement.querySelector('#focus-target-element')!;
const clickEvent = new CustomEvent('click', {bubbles: true});
el.dispatchEvent(clickEvent);
const focusEvent = new CustomEvent('focus');
button.dispatchEvent(focusEvent);
expect(clickSpy).toHaveBeenCalled();
expect(focusSpy).toHaveBeenCalled();
});

describe('bubbling behavior', () => {
it('should propagate events', async () => {
Expand Down Expand Up @@ -305,3 +273,31 @@ describe('event dispatch', () => {
});
});
});

describe('capture behavior', () => {
let fixture: ComponentFixture<unknown>;
it('should not bubble', async () => {
const onFocusSpy = jasmine.createSpy();
@Component({
standalone: true,
selector: 'app',
template: `
<div id="top" (focus)="onFocus()">
<div id="bottom"></div>
</div>
`,
})
class SimpleComponent {
onFocus = onFocusSpy;
}
configureTestingModule([SimpleComponent]);
fixture = TestBed.createComponent(SimpleComponent);
const nativeElement = fixture.debugElement.nativeElement;
const bottomEl = nativeElement.querySelector('#bottom')!;
const topEl = nativeElement.querySelector('#top')!;
bottomEl.dispatchEvent(new FocusEvent('focus'));
expect(onFocusSpy).toHaveBeenCalledTimes(0);
topEl.dispatchEvent(new FocusEvent('focus'));
expect(onFocusSpy).toHaveBeenCalledTimes(1);
});
});
40 changes: 0 additions & 40 deletions packages/platform-server/test/event_replay_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,46 +208,6 @@ describe('event replay', () => {
expect(outerOnClickSpy).toHaveBeenCalledBefore(innerOnClickSpy);
});

it('should serialize event types to be listened to and jsaction attribute', async () => {
const clickSpy = jasmine.createSpy('onClick');
const focusSpy = jasmine.createSpy('onFocus');
@Component({
standalone: true,
selector: 'app',
template: `
<div (click)="onClick()" id="click-element">
<div id="focus-container">
<div id="focus-action-element" (focus)="onFocus()">
<button id="focus-target-element">Focus Button</button>
</div>
</div>
</div>
`,
})
class SimpleComponent {
onClick = clickSpy;
onFocus = focusSpy;
}
const html = await ssr(SimpleComponent);
const ssrContents = getAppContents(html);

render(doc, ssrContents);
const el = doc.getElementById('click-element')!;
const button = doc.getElementById('focus-target-element')!;
const clickEvent = new CustomEvent('click', {bubbles: true});
el.dispatchEvent(clickEvent);
const focusEvent = new CustomEvent('focus');
button.dispatchEvent(focusEvent);
expect(clickSpy).not.toHaveBeenCalled();
expect(focusSpy).not.toHaveBeenCalled();
resetTViewsFor(SimpleComponent);
await hydrate(doc, SimpleComponent, {
hydrationFeatures: [withEventReplay()],
});
expect(clickSpy).toHaveBeenCalled();
expect(focusSpy).toHaveBeenCalled();
});

it('should remove jsaction attributes, but continue listening to events.', async () => {
@Component({
standalone: true,
Expand Down