fix(ivy): DebugNode.queryAll matching sibling nodes (#30788)
Inside of `DebugNode.queryAll` we walk through all of the descendants of the node that we're querying against, however the logic that walks sideways through a node siblings also applies to the root node. This means that eventually we'll match against its nodes as well which will give us invalid results. These changes add an extra check to ensure that we aren't matching against the siblings of the root node. This PR resolves FW-1353. PR Close #30788
This commit is contained in:
parent
876cd603f1
commit
f936590573
|
@ -423,24 +423,24 @@ function _queryAllR3(
|
||||||
function _queryNodeChildrenR3(
|
function _queryNodeChildrenR3(
|
||||||
tNode: TNode, lView: LView, predicate: Predicate<DebugNode>, matches: DebugNode[],
|
tNode: TNode, lView: LView, predicate: Predicate<DebugNode>, matches: DebugNode[],
|
||||||
elementsOnly: boolean, rootNativeNode: any) {
|
elementsOnly: boolean, rootNativeNode: any) {
|
||||||
|
const nativeNode = getNativeByTNode(tNode, lView);
|
||||||
// For each type of TNode, specific logic is executed.
|
// For each type of TNode, specific logic is executed.
|
||||||
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) {
|
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) {
|
||||||
// Case 1: the TNode is an element
|
// Case 1: the TNode is an element
|
||||||
// The native node has to be checked.
|
// The native node has to be checked.
|
||||||
_addQueryMatchR3(
|
_addQueryMatchR3(nativeNode, predicate, matches, elementsOnly, rootNativeNode);
|
||||||
getNativeByTNode(tNode, lView), predicate, matches, elementsOnly, rootNativeNode);
|
|
||||||
if (isComponent(tNode)) {
|
if (isComponent(tNode)) {
|
||||||
// If the element is the host of a component, then all nodes in its view have to be processed.
|
// If the element is the host of a component, then all nodes in its view have to be processed.
|
||||||
// Note: the component's content (tNode.child) will be processed from the insertion points.
|
// Note: the component's content (tNode.child) will be processed from the insertion points.
|
||||||
const componentView = getComponentViewByIndex(tNode.index, lView);
|
const componentView = getComponentViewByIndex(tNode.index, lView);
|
||||||
if (componentView && componentView[TVIEW].firstChild)
|
if (componentView && componentView[TVIEW].firstChild) {
|
||||||
_queryNodeChildrenR3(
|
_queryNodeChildrenR3(
|
||||||
componentView[TVIEW].firstChild !, componentView, predicate, matches, elementsOnly,
|
componentView[TVIEW].firstChild !, componentView, predicate, matches, elementsOnly,
|
||||||
rootNativeNode);
|
rootNativeNode);
|
||||||
} else {
|
}
|
||||||
|
} else if (tNode.child) {
|
||||||
// Otherwise, its children have to be processed.
|
// Otherwise, its children have to be processed.
|
||||||
if (tNode.child)
|
_queryNodeChildrenR3(tNode.child, lView, predicate, matches, elementsOnly, rootNativeNode);
|
||||||
_queryNodeChildrenR3(tNode.child, lView, predicate, matches, elementsOnly, rootNativeNode);
|
|
||||||
}
|
}
|
||||||
// In all cases, if a dynamic container exists for this node, each view inside it has to be
|
// In all cases, if a dynamic container exists for this node, each view inside it has to be
|
||||||
// processed.
|
// processed.
|
||||||
|
@ -468,25 +468,24 @@ function _queryNodeChildrenR3(
|
||||||
for (let nativeNode of head) {
|
for (let nativeNode of head) {
|
||||||
_addQueryMatchR3(nativeNode, predicate, matches, elementsOnly, rootNativeNode);
|
_addQueryMatchR3(nativeNode, predicate, matches, elementsOnly, rootNativeNode);
|
||||||
}
|
}
|
||||||
} else {
|
} else if (head) {
|
||||||
if (head) {
|
const nextLView = componentView[PARENT] !as LView;
|
||||||
const nextLView = componentView[PARENT] !as LView;
|
const nextTNode = nextLView[TVIEW].data[head.index] as TNode;
|
||||||
const nextTNode = nextLView[TVIEW].data[head.index] as TNode;
|
_queryNodeChildrenR3(nextTNode, nextLView, predicate, matches, elementsOnly, rootNativeNode);
|
||||||
_queryNodeChildrenR3(
|
|
||||||
nextTNode, nextLView, predicate, matches, elementsOnly, rootNativeNode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else if (tNode.child) {
|
||||||
// Case 4: the TNode is a view.
|
// Case 4: the TNode is a view.
|
||||||
if (tNode.child) {
|
_queryNodeChildrenR3(tNode.child, lView, predicate, matches, elementsOnly, rootNativeNode);
|
||||||
_queryNodeChildrenR3(tNode.child, lView, predicate, matches, elementsOnly, rootNativeNode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// To determine the next node to be processed, we need to use the next or the projectionNext link,
|
|
||||||
// depending on whether the current node has been projected.
|
// We don't want to go to the next sibling of the root node.
|
||||||
const nextTNode = (tNode.flags & TNodeFlags.isProjected) ? tNode.projectionNext : tNode.next;
|
if (rootNativeNode !== nativeNode) {
|
||||||
if (nextTNode) {
|
// To determine the next node to be processed, we need to use the next or the projectionNext
|
||||||
_queryNodeChildrenR3(nextTNode, lView, predicate, matches, elementsOnly, rootNativeNode);
|
// link, depending on whether the current node has been projected.
|
||||||
|
const nextTNode = (tNode.flags & TNodeFlags.isProjected) ? tNode.projectionNext : tNode.next;
|
||||||
|
if (nextTNode) {
|
||||||
|
_queryNodeChildrenR3(nextTNode, lView, predicate, matches, elementsOnly, rootNativeNode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -695,5 +695,46 @@ class TestCmptWithPropBindings {
|
||||||
expect(childTestElsSecond[0]).toBe(childTestElsFirst[0]);
|
expect(childTestElsSecond[0]).toBe(childTestElsFirst[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not query the descendants of a sibling node', () => {
|
||||||
|
@Component({
|
||||||
|
selector: 'my-comp',
|
||||||
|
template: `
|
||||||
|
<div class="div.1">
|
||||||
|
<p class="p.1">
|
||||||
|
<span class="span.1">span.1</span>
|
||||||
|
<span class="span.2">span.2</span>
|
||||||
|
</p>
|
||||||
|
<p class="p.2">
|
||||||
|
<span class="span.3">span.3</span>
|
||||||
|
<span class="span.4">span.4</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="div.2">
|
||||||
|
<p class="p.3">
|
||||||
|
<span class="span.5">span.5</span>
|
||||||
|
<span class="span.6">span.6</span>
|
||||||
|
</p>
|
||||||
|
<p class="p.4">
|
||||||
|
<span class="span.7">span.7</span>
|
||||||
|
<span class="span.8">span.8</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class MyComp {
|
||||||
|
}
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({declarations: [MyComp]});
|
||||||
|
const fixture = TestBed.createComponent(MyComp);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const firstDiv = fixture.debugElement.query(By.css('div'));
|
||||||
|
const firstDivChildren = firstDiv.queryAll(By.css('span'));
|
||||||
|
|
||||||
|
expect(firstDivChildren.map(child => child.nativeNode.textContent.trim())).toEqual([
|
||||||
|
'span.1', 'span.2', 'span.3', 'span.4'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue