From d82adbe8c4e91cd561d16fb3dbd816eaf2a47f45 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Thu, 30 May 2019 13:28:44 -0700 Subject: [PATCH] fix(ivy): align DebugNode/DebugElement behavior with View Engine (#30756) Two issues with DebugNode/DebugElement in Ivy were causing problems in user tests. 1. The DebugNodes returned by Ivy were not actually instances of DebugNode. This was due to an issue with the Ivy switch logic in debug_node.ts. The declaration of the exported DebugNode reference was set to `DebugNode__PRE_R3__ as any`. The cast prevented the Ivy switch transform from detecting this as a switchable declaration. The transform cannot handle arbitrary syntax, and exports *must* be of the form "const x = y__PRE_R3__;" or they will not work. The cast to any in this case was not needed, so this commit removes it. 2. DebugNodes returned by Ivy multiple times for the same element were not reference-identical. This was previously considered a minor breaking change in Ivy, but testing has shown that users depend on referential equality of DebugNodes. This commit caches a DebugNode on a DOM node when first creating it, to allow returning the same instance in subsequent operations. PR Close #30756 --- packages/core/src/debug/debug_node.ts | 15 ++++++++++----- packages/core/test/debug/debug_node_spec.ts | 19 ++++++++++++++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/core/src/debug/debug_node.ts b/packages/core/src/debug/debug_node.ts index f4ecc218c2..e8547d8783 100644 --- a/packages/core/src/debug/debug_node.ts +++ b/packages/core/src/debug/debug_node.ts @@ -638,14 +638,19 @@ function getDebugNode__PRE_R3__(nativeNode: any): DebugNode|null { return _nativeNodeToDebugNode.get(nativeNode) || null; } +const NG_DEBUG_PROPERTY = '__ng_debug__'; + export function getDebugNode__POST_R3__(nativeNode: Element): DebugElement__POST_R3__; export function getDebugNode__POST_R3__(nativeNode: Node): DebugNode__POST_R3__; export function getDebugNode__POST_R3__(nativeNode: null): null; export function getDebugNode__POST_R3__(nativeNode: any): DebugNode|null { if (nativeNode instanceof Node) { - return nativeNode.nodeType == Node.ELEMENT_NODE ? - new DebugElement__POST_R3__(nativeNode as Element) : - new DebugNode__POST_R3__(nativeNode); + if (!(nativeNode.hasOwnProperty(NG_DEBUG_PROPERTY))) { + (nativeNode as any)[NG_DEBUG_PROPERTY] = nativeNode.nodeType == Node.ELEMENT_NODE ? + new DebugElement__POST_R3__(nativeNode as Element) : + new DebugNode__POST_R3__(nativeNode); + } + return (nativeNode as any)[NG_DEBUG_PROPERTY]; } return null; } @@ -678,9 +683,9 @@ export interface Predicate { (value: T): boolean; } /** * @publicApi */ -export const DebugNode: {new (...args: any[]): DebugNode} = DebugNode__PRE_R3__ as any; +export const DebugNode: {new (...args: any[]): DebugNode} = DebugNode__PRE_R3__; /** * @publicApi */ -export const DebugElement: {new (...args: any[]): DebugElement} = DebugElement__PRE_R3__ as any; +export const DebugElement: {new (...args: any[]): DebugElement} = DebugElement__PRE_R3__; diff --git a/packages/core/test/debug/debug_node_spec.ts b/packages/core/test/debug/debug_node_spec.ts index ac3773b873..b45cb43031 100644 --- a/packages/core/test/debug/debug_node_spec.ts +++ b/packages/core/test/debug/debug_node_spec.ts @@ -7,7 +7,7 @@ */ -import {Component, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core'; +import {Component, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core'; import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {By} from '@angular/platform-browser/src/dom/debug/by'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; @@ -678,5 +678,22 @@ class TestCmptWithPropBindings { expect(divB.nativeElement.getAttribute('id')).toBe('b'); }); + it('should be an instance of DebugNode', () => { + fixture = TestBed.createComponent(ParentComp); + fixture.detectChanges(); + expect(fixture.debugElement).toBeAnInstanceOf(DebugNode); + }); + + it('should return the same element when queried twice', () => { + fixture = TestBed.createComponent(ParentComp); + fixture.detectChanges(); + + const childTestElsFirst = fixture.debugElement.queryAll(By.css('child-comp')); + const childTestElsSecond = fixture.debugElement.queryAll(By.css('child-comp')); + + expect(childTestElsFirst.length).toBe(1); + expect(childTestElsSecond[0]).toBe(childTestElsFirst[0]); + }); + }); }