fix(ivy): Prevent errors when querying for elements outside Angular context (#32361)
DebugElement.query also searches elements that may have been created outside of Angular (ex: with `document.appendChild`). The current behavior attempts to get the LContext of these nodes but throws an error because the LContext does not exist. PR Close #32361
This commit is contained in:
parent
c8b065524e
commit
260217a800
|
@ -16,7 +16,7 @@ import {TStylingContext} from '../render3/styling_next/interfaces';
|
||||||
import {stylingMapToStringMap} from '../render3/styling_next/map_based_bindings';
|
import {stylingMapToStringMap} from '../render3/styling_next/map_based_bindings';
|
||||||
import {NodeStylingDebug} from '../render3/styling_next/styling_debug';
|
import {NodeStylingDebug} from '../render3/styling_next/styling_debug';
|
||||||
import {isStylingContext} from '../render3/styling_next/util';
|
import {isStylingContext} from '../render3/styling_next/util';
|
||||||
import {getComponent, getContext, getInjectionTokens, getInjector, getListeners, getLocalRefs, isBrowserEvents, loadLContext, loadLContextFromNode} from '../render3/util/discovery_utils';
|
import {getComponent, getContext, getInjectionTokens, getInjector, getListeners, getLocalRefs, isBrowserEvents, loadLContext} from '../render3/util/discovery_utils';
|
||||||
import {INTERPOLATION_DELIMITER, isPropMetadataString, renderStringify} from '../render3/util/misc_utils';
|
import {INTERPOLATION_DELIMITER, isPropMetadataString, renderStringify} from '../render3/util/misc_utils';
|
||||||
import {findComponentView} from '../render3/util/view_traversal_utils';
|
import {findComponentView} from '../render3/util/view_traversal_utils';
|
||||||
import {getComponentViewByIndex, getNativeByTNodeOrNull} from '../render3/util/view_utils';
|
import {getComponentViewByIndex, getNativeByTNodeOrNull} from '../render3/util/view_utils';
|
||||||
|
@ -272,7 +272,11 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
|
||||||
* - attribute bindings (e.g. `[attr.role]="menu"`)
|
* - attribute bindings (e.g. `[attr.role]="menu"`)
|
||||||
*/
|
*/
|
||||||
get properties(): {[key: string]: any;} {
|
get properties(): {[key: string]: any;} {
|
||||||
const context = loadLContext(this.nativeNode) !;
|
const context = loadLContext(this.nativeNode, false);
|
||||||
|
if (context == null) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const lView = context.lView;
|
const lView = context.lView;
|
||||||
const tData = lView[TVIEW].data;
|
const tData = lView[TVIEW].data;
|
||||||
const tNode = tData[context.nodeIndex] as TNode;
|
const tNode = tData[context.nodeIndex] as TNode;
|
||||||
|
@ -297,7 +301,11 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
|
||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = loadLContext(element);
|
const context = loadLContext(element, false);
|
||||||
|
if (context == null) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const lView = context.lView;
|
const lView = context.lView;
|
||||||
const tNodeAttrs = (lView[TVIEW].data[context.nodeIndex] as TNode).attrs;
|
const tNodeAttrs = (lView[TVIEW].data[context.nodeIndex] as TNode).attrs;
|
||||||
const lowercaseTNodeAttrs: string[] = [];
|
const lowercaseTNodeAttrs: string[] = [];
|
||||||
|
@ -413,8 +421,11 @@ class DebugElement__POST_R3__ extends DebugNode__POST_R3__ implements DebugEleme
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getStylingDebugInfo(element: any, isClassBased: boolean) {
|
function _getStylingDebugInfo(element: any, isClassBased: boolean) {
|
||||||
if (element) {
|
const context = loadLContext(element, false);
|
||||||
const context = loadLContextFromNode(element);
|
if (!context) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
const lView = context.lView;
|
const lView = context.lView;
|
||||||
const tData = lView[TVIEW].data;
|
const tData = lView[TVIEW].data;
|
||||||
const tNode = tData[context.nodeIndex] as TNode;
|
const tNode = tData[context.nodeIndex] as TNode;
|
||||||
|
@ -427,8 +438,6 @@ function _getStylingDebugInfo(element: any, isClassBased: boolean) {
|
||||||
new NodeStylingDebug(tNode.styles as TStylingContext, lView, false).values :
|
new NodeStylingDebug(tNode.styles as TStylingContext, lView, false).values :
|
||||||
stylingMapToStringMap(tNode.styles);
|
stylingMapToStringMap(tNode.styles);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
|
|
||||||
import {CommonModule, NgIfContext, ɵgetDOM as getDOM} from '@angular/common';
|
import {CommonModule, NgIfContext, ɵgetDOM as getDOM} from '@angular/common';
|
||||||
import {Component, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, OnInit, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
|
import {Component, DebugElement, DebugNode, Directive, ElementRef, EmbeddedViewRef, EventEmitter, HostBinding, Injectable, Input, NO_ERRORS_SCHEMA, OnInit, Output, Renderer2, TemplateRef, ViewChild, ViewContainerRef} from '@angular/core';
|
||||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||||
import {hasClass} from '@angular/platform-browser/testing/src/browser_util';
|
import {hasClass} from '@angular/platform-browser/testing/src/browser_util';
|
||||||
|
@ -580,6 +580,43 @@ class TestCmptWithPropBindings {
|
||||||
expect(fixture.debugElement.query(By.css('.myclass'))).toBeTruthy();
|
expect(fixture.debugElement.query(By.css('.myclass'))).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('DebugElement.query doesn\'t fail on elements outside Angular context', () => {
|
||||||
|
@Component({template: '<div></div>'})
|
||||||
|
class NativeEl {
|
||||||
|
constructor(private elementRef: ElementRef) {}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.elementRef.nativeElement.children[0].appendChild(document.createElement('p'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let el: DebugElement;
|
||||||
|
beforeEach(() => {
|
||||||
|
const fixture =
|
||||||
|
TestBed.configureTestingModule({declarations: [NativeEl]}).createComponent(NativeEl);
|
||||||
|
fixture.detectChanges();
|
||||||
|
el = fixture.debugElement;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when searching for elements by name',
|
||||||
|
() => { expect(() => el.query(e => e.name === 'any search text')).not.toThrow(); });
|
||||||
|
|
||||||
|
it('when searching for elements by their attributes', () => {
|
||||||
|
expect(() => el.query(e => e.attributes !['name'] === 'any attribute')).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when searching for elements by their classes',
|
||||||
|
() => { expect(() => el.query(e => e.classes['any class'] === true)).not.toThrow(); });
|
||||||
|
|
||||||
|
it('when searching for elements by their styles', () => {
|
||||||
|
expect(() => el.query(e => e.styles['any style'] === 'any value')).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when searching for elements by their properties', () => {
|
||||||
|
expect(() => el.query(e => e.properties['any prop'] === 'any value')).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('DebugElement.queryAll should pick up both elements inserted via the view and through Renderer2',
|
it('DebugElement.queryAll should pick up both elements inserted via the view and through Renderer2',
|
||||||
() => {
|
() => {
|
||||||
@Directive({
|
@Directive({
|
||||||
|
|
Loading…
Reference in New Issue