fix(core): handle elements with local refs in event replay serialization logic (#56076)

Previously, the event replay serialization logic was located before we verify that a `TNode` exists. `TNode`s may not exist in `tView.data` array in several cases, including cases when there is a local ref used on an element: in this case an extra slot in `LView` contains a reference to the same element and `TNode` is not needed. This commit moves the event replay serialization logic a bit lower, after we check for TNode presence.

Resolves #56073.

PR Close #56076
This commit is contained in:
Andrew Kushnir
2024-05-24 15:04:00 -07:00
committed by Pawel Kozlowski
parent 031e40e6dd
commit ae83646704
2 changed files with 33 additions and 3 deletions
+7 -3
View File
@@ -393,9 +393,6 @@ function serializeLView(lView: LView, context: HydrationContext): SerializedView
for (let i = HEADER_OFFSET; i < tView.bindingStartIndex; i++) {
const tNode = tView.data[i] as TNode;
const noOffsetIndex = i - HEADER_OFFSET;
if (nativeElementsToEventTypes) {
setJSActionAttribute(tNode, lView[i], nativeElementsToEventTypes);
}
// Attempt to serialize any i18n data for the given slot. We do this first, as i18n
// has its own process for serialization.
@@ -434,6 +431,13 @@ function serializeLView(lView: LView, context: HydrationContext): SerializedView
appendDisconnectedNodeIndex(ngh, tNode);
continue;
}
if (nativeElementsToEventTypes) {
// Attach `jsaction` attribute to elements that have registered listeners,
// thus potentially having a need to do an event replay.
setJSActionAttribute(tNode, lView[i], nativeElementsToEventTypes);
}
if (Array.isArray(tNode.projection)) {
for (const projectionHeadTNode of tNode.projection) {
// We may have `null`s in slots with no projected content.
@@ -103,6 +103,32 @@ describe('event replay', () => {
}
}
it('should work for elements with local refs', async () => {
const onClickSpy = jasmine.createSpy();
@Component({
selector: 'app',
standalone: true,
template: `
<button id="btn" (click)="onClick()" #localRef></button>
`,
})
class AppComponent {
onClick = onClickSpy;
}
const html = await ssr(AppComponent);
const ssrContents = getAppContents(html);
render(doc, ssrContents);
resetTViewsFor(AppComponent);
const btn = doc.getElementById('btn')!;
btn.click();
const appRef = await hydrate(doc, AppComponent, {
hydrationFeatures: [withEventReplay()],
});
appRef.tick();
expect(onClickSpy).toHaveBeenCalled();
});
it('should route to the appropriate component with content projection', async () => {
const outerOnClickSpy = jasmine.createSpy();
const innerOnClickSpy = jasmine.createSpy();