Skip to content

Commit 7d40185

Browse files
committed
fix(ivy): Prevent errors when querying DebugElement roots that were outside angular context (angular#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 angular#34687
1 parent 58f1002 commit 7d40185

File tree

2 files changed

+58
-30
lines changed

2 files changed

+58
-30
lines changed

packages/core/src/debug/debug_node.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -470,10 +470,16 @@ function _queryAllR3(
470470
function _queryAllR3(
471471
parentElement: DebugElement, predicate: Predicate<DebugElement>| Predicate<DebugNode>,
472472
matches: DebugElement[] | DebugNode[], elementsOnly: boolean) {
473-
const context = loadLContext(parentElement.nativeNode) !;
474-
const parentTNode = context.lView[TVIEW].data[context.nodeIndex] as TNode;
475-
_queryNodeChildrenR3(
476-
parentTNode, context.lView, predicate, matches, elementsOnly, parentElement.nativeNode);
473+
const context = loadLContext(parentElement.nativeNode, false);
474+
if (context !== null) {
475+
const parentTNode = context.lView[TVIEW].data[context.nodeIndex] as TNode;
476+
_queryNodeChildrenR3(
477+
parentTNode, context.lView, predicate, matches, elementsOnly, parentElement.nativeNode);
478+
} else {
479+
// If the context is null, then `parentElement` was either created with Renderer2 or native DOM
480+
// APIs.
481+
_queryNativeNodeDescendants(parentElement.nativeNode, predicate, matches, elementsOnly);
482+
}
477483
}
478484

479485
/**

packages/core/test/debug/debug_node_spec.ts

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -577,38 +577,51 @@ class TestCmptWithPropInterpolation {
577577
expect(fixture.debugElement.query(By.css('.myclass'))).toBeTruthy();
578578
});
579579

580-
it('DebugElement.query should work with dynamically created descendant elements', () => {
581-
@Directive({
582-
selector: '[dir]',
583-
})
584-
class MyDir {
585-
@Input('dir') dir: number|undefined;
580+
describe('DebugElement.query with dynamically created descendant elements', () => {
581+
let fixture: ComponentFixture<{}>;
582+
beforeEach(() => {
586583

587-
constructor(renderer: Renderer2, element: ElementRef) {
588-
const outerDiv = renderer.createElement('div');
589-
const innerDiv = renderer.createElement('div');
590-
const div = renderer.createElement('div');
584+
@Directive({
585+
selector: '[dir]',
586+
})
587+
class MyDir {
588+
@Input('dir') dir: number|undefined;
591589

592-
div.classList.add('myclass');
590+
constructor(renderer: Renderer2, element: ElementRef) {
591+
const outerDiv = renderer.createElement('div');
592+
const innerDiv = renderer.createElement('div');
593+
innerDiv.classList.add('inner');
594+
const div = renderer.createElement('div');
595+
596+
div.classList.add('myclass');
593597

594-
renderer.appendChild(innerDiv, div);
595-
renderer.appendChild(outerDiv, innerDiv);
596-
renderer.appendChild(element.nativeElement, outerDiv);
598+
renderer.appendChild(innerDiv, div);
599+
renderer.appendChild(outerDiv, innerDiv);
600+
renderer.appendChild(element.nativeElement, outerDiv);
601+
}
597602
}
598-
}
599603

600-
@Component({
601-
selector: 'app-test',
602-
template: '<div dir></div>',
603-
})
604-
class MyComponent {
605-
}
604+
@Component({
605+
selector: 'app-test',
606+
template: '<div dir></div>',
607+
})
608+
class MyComponent {
609+
}
606610

607-
TestBed.configureTestingModule({declarations: [MyComponent, MyDir]});
608-
const fixture = TestBed.createComponent(MyComponent);
609-
fixture.detectChanges();
611+
TestBed.configureTestingModule({declarations: [MyComponent, MyDir]});
612+
fixture = TestBed.createComponent(MyComponent);
613+
fixture.detectChanges();
610614

611-
expect(fixture.debugElement.query(By.css('.myclass'))).toBeTruthy();
615+
});
616+
617+
it('should find the dynamic elements from fixture root',
618+
() => { expect(fixture.debugElement.query(By.css('.myclass'))).toBeTruthy(); });
619+
620+
it('can use a dynamic element as root for another query', () => {
621+
const inner = fixture.debugElement.query(By.css('.inner'));
622+
expect(inner).toBeTruthy();
623+
expect(inner.query(By.css('.myclass'))).toBeTruthy();
624+
});
612625
});
613626

614627
describe('DebugElement.query doesn\'t fail on elements outside Angular context', () => {
@@ -617,7 +630,9 @@ class TestCmptWithPropInterpolation {
617630
constructor(private elementRef: ElementRef) {}
618631

619632
ngAfterViewInit() {
620-
this.elementRef.nativeElement.children[0].appendChild(document.createElement('p'));
633+
const ul = document.createElement('ul');
634+
ul.appendChild(document.createElement('li'));
635+
this.elementRef.nativeElement.children[0].appendChild(ul);
621636
}
622637
}
623638

@@ -664,6 +679,13 @@ class TestCmptWithPropInterpolation {
664679

665680
it('when searching by injector',
666681
() => { expect(() => el.query(e => e.injector === null)).not.toThrow(); });
682+
683+
onlyInIvy('VE does not match elements created outside Angular context')
684+
.it('when using the out-of-context element as the DebugElement query root', () => {
685+
const debugElOutsideAngularContext = el.query(By.css('ul'));
686+
expect(debugElOutsideAngularContext.queryAll(By.css('li')).length).toBe(1);
687+
expect(debugElOutsideAngularContext.query(By.css('li'))).toBeDefined();
688+
});
667689
});
668690

669691
it('DebugElement.queryAll should pick up both elements inserted via the view and through Renderer2',

0 commit comments

Comments
 (0)