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(
|
||||
parentElement: DebugElement, predicate: Predicate<DebugElement>| Predicate<DebugNode>,
|
||||
matches: DebugElement[] | DebugNode[], elementsOnly: boolean) {
|
||||
const context = loadLContext(parentElement.nativeNode) !;
|
||||
const parentTNode = context.lView[TVIEW].data[context.nodeIndex] as TNode;
|
||||
_queryNodeChildrenR3(
|
||||
parentTNode, context.lView, predicate, matches, elementsOnly, parentElement.nativeNode);
|
||||
const context = loadLContext(parentElement.nativeNode, false);
|
||||
if (context !== null) {
|
||||
const parentTNode = context.lView[TVIEW].data[context.nodeIndex] as TNode;
|
||||
_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();
|
||||
});
|
||||
|
||||
it('DebugElement.query should work with dynamically created descendant elements', () => {
|
||||
@Directive({
|
||||
selector: '[dir]',
|
||||
})
|
||||
class MyDir {
|
||||
@Input('dir') dir: number|undefined;
|
||||
describe('DebugElement.query with dynamically created descendant elements', () => {
|
||||
let fixture: ComponentFixture<{}>;
|
||||
beforeEach(() => {
|
||||
|
||||
constructor(renderer: Renderer2, element: ElementRef) {
|
||||
const outerDiv = renderer.createElement('div');
|
||||
const innerDiv = renderer.createElement('div');
|
||||
const div = renderer.createElement('div');
|
||||
@Directive({
|
||||
selector: '[dir]',
|
||||
})
|
||||
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);
|
||||
renderer.appendChild(outerDiv, innerDiv);
|
||||
renderer.appendChild(element.nativeElement, outerDiv);
|
||||
div.classList.add('myclass');
|
||||
|
||||
renderer.appendChild(innerDiv, div);
|
||||
renderer.appendChild(outerDiv, innerDiv);
|
||||
renderer.appendChild(element.nativeElement, outerDiv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-test',
|
||||
template: '<div dir></div>',
|
||||
})
|
||||
class MyComponent {
|
||||
}
|
||||
@Component({
|
||||
selector: 'app-test',
|
||||
template: '<div dir></div>',
|
||||
})
|
||||
class MyComponent {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComponent, MyDir]});
|
||||
const fixture = TestBed.createComponent(MyComponent);
|
||||
fixture.detectChanges();
|
||||
TestBed.configureTestingModule({declarations: [MyComponent, MyDir]});
|
||||
fixture = TestBed.createComponent(MyComponent);
|
||||
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', () => {
|
||||
|
@ -617,7 +630,9 @@ class TestCmptWithPropInterpolation {
|
|||
constructor(private elementRef: ElementRef) {}
|
||||
|
||||
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',
|
||||
() => { 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',
|
||||
|
|
Loading…
Reference in New Issue