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
This commit is contained in:
Alex Rickabaugh 2019-05-30 13:28:44 -07:00 committed by Misko Hevery
parent 09c57ecf95
commit d82adbe8c4
2 changed files with 28 additions and 6 deletions

View File

@ -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<T> { (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__;

View File

@ -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]);
});
});
}