fix(ivy): Prevent errors when querying DebugElement roots that were outside angular context (#34687)
DebugElement.query also matches elements that may have been created outside of Angular (ex: with `document.appendChild`). If those matched DebugElements are in turn used to query for more elements, an error occurs because the first step in queryAll is to load the LContext. PR Close #34687
This commit is contained in:
parent
58f10026c4
commit
7d401853b5
|
@ -470,10 +470,16 @@ function _queryAllR3(
|
||||||
function _queryAllR3(
|
function _queryAllR3(
|
||||||
parentElement: DebugElement, predicate: Predicate<DebugElement>| Predicate<DebugNode>,
|
parentElement: DebugElement, predicate: Predicate<DebugElement>| Predicate<DebugNode>,
|
||||||
matches: DebugElement[] | DebugNode[], elementsOnly: boolean) {
|
matches: DebugElement[] | DebugNode[], elementsOnly: boolean) {
|
||||||
const context = loadLContext(parentElement.nativeNode) !;
|
const context = loadLContext(parentElement.nativeNode, false);
|
||||||
const parentTNode = context.lView[TVIEW].data[context.nodeIndex] as TNode;
|
if (context !== null) {
|
||||||
_queryNodeChildrenR3(
|
const parentTNode = context.lView[TVIEW].data[context.nodeIndex] as TNode;
|
||||||
parentTNode, context.lView, predicate, matches, elementsOnly, parentElement.nativeNode);
|
_queryNodeChildrenR3(
|
||||||
|
parentTNode, context.lView, predicate, matches, elementsOnly, parentElement.nativeNode);
|
||||||
|
} else {
|
||||||
|
// If the context is null, then `parentElement` was either created with Renderer2 or native DOM
|
||||||
|
// APIs.
|
||||||
|
_queryNativeNodeDescendants(parentElement.nativeNode, predicate, matches, elementsOnly);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -577,38 +577,51 @@ class TestCmptWithPropInterpolation {
|
||||||
expect(fixture.debugElement.query(By.css('.myclass'))).toBeTruthy();
|
expect(fixture.debugElement.query(By.css('.myclass'))).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('DebugElement.query should work with dynamically created descendant elements', () => {
|
describe('DebugElement.query with dynamically created descendant elements', () => {
|
||||||
@Directive({
|
let fixture: ComponentFixture<{}>;
|
||||||
selector: '[dir]',
|
beforeEach(() => {
|
||||||
})
|
|
||||||
class MyDir {
|
|
||||||
@Input('dir') dir: number|undefined;
|
|
||||||
|
|
||||||
constructor(renderer: Renderer2, element: ElementRef) {
|
@Directive({
|
||||||
const outerDiv = renderer.createElement('div');
|
selector: '[dir]',
|
||||||
const innerDiv = renderer.createElement('div');
|
})
|
||||||
const div = renderer.createElement('div');
|
class MyDir {
|
||||||
|
@Input('dir') dir: number|undefined;
|
||||||
|
|
||||||
div.classList.add('myclass');
|
constructor(renderer: Renderer2, element: ElementRef) {
|
||||||
|
const outerDiv = renderer.createElement('div');
|
||||||
|
const innerDiv = renderer.createElement('div');
|
||||||
|
innerDiv.classList.add('inner');
|
||||||
|
const div = renderer.createElement('div');
|
||||||
|
|
||||||
renderer.appendChild(innerDiv, div);
|
div.classList.add('myclass');
|
||||||
renderer.appendChild(outerDiv, innerDiv);
|
|
||||||
renderer.appendChild(element.nativeElement, outerDiv);
|
renderer.appendChild(innerDiv, div);
|
||||||
|
renderer.appendChild(outerDiv, innerDiv);
|
||||||
|
renderer.appendChild(element.nativeElement, outerDiv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-test',
|
selector: 'app-test',
|
||||||
template: '<div dir></div>',
|
template: '<div dir></div>',
|
||||||
})
|
})
|
||||||
class MyComponent {
|
class MyComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
TestBed.configureTestingModule({declarations: [MyComponent, MyDir]});
|
TestBed.configureTestingModule({declarations: [MyComponent, MyDir]});
|
||||||
const fixture = TestBed.createComponent(MyComponent);
|
fixture = TestBed.createComponent(MyComponent);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(fixture.debugElement.query(By.css('.myclass'))).toBeTruthy();
|
});
|
||||||
|
|
||||||
|
it('should find the dynamic elements from fixture root',
|
||||||
|
() => { expect(fixture.debugElement.query(By.css('.myclass'))).toBeTruthy(); });
|
||||||
|
|
||||||
|
it('can use a dynamic element as root for another query', () => {
|
||||||
|
const inner = fixture.debugElement.query(By.css('.inner'));
|
||||||
|
expect(inner).toBeTruthy();
|
||||||
|
expect(inner.query(By.css('.myclass'))).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('DebugElement.query doesn\'t fail on elements outside Angular context', () => {
|
describe('DebugElement.query doesn\'t fail on elements outside Angular context', () => {
|
||||||
|
@ -617,7 +630,9 @@ class TestCmptWithPropInterpolation {
|
||||||
constructor(private elementRef: ElementRef) {}
|
constructor(private elementRef: ElementRef) {}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
this.elementRef.nativeElement.children[0].appendChild(document.createElement('p'));
|
const ul = document.createElement('ul');
|
||||||
|
ul.appendChild(document.createElement('li'));
|
||||||
|
this.elementRef.nativeElement.children[0].appendChild(ul);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -664,6 +679,13 @@ class TestCmptWithPropInterpolation {
|
||||||
|
|
||||||
it('when searching by injector',
|
it('when searching by injector',
|
||||||
() => { expect(() => el.query(e => e.injector === null)).not.toThrow(); });
|
() => { expect(() => el.query(e => e.injector === null)).not.toThrow(); });
|
||||||
|
|
||||||
|
onlyInIvy('VE does not match elements created outside Angular context')
|
||||||
|
.it('when using the out-of-context element as the DebugElement query root', () => {
|
||||||
|
const debugElOutsideAngularContext = el.query(By.css('ul'));
|
||||||
|
expect(debugElOutsideAngularContext.queryAll(By.css('li')).length).toBe(1);
|
||||||
|
expect(debugElOutsideAngularContext.query(By.css('li'))).toBeDefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
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',
|
||||||
|
|
Loading…
Reference in New Issue