- 测试
- 前端
【免费下载链接】enzyme
JavaScript Testing utilities for React
本篇指南围绕 Enzyme 中ShallowWrapper的.parent()方法展开,讲解如何从一个已选中的 React 节点向上回溯,获取其在渲染树中的直接父节点。该方法常用于断言 DOM 结构层级、验证组件包裹关系,以及配合.find()、.hasClass()、.is()等 API 编写精确的浅渲染(shallow rendering)测试。读完本文,你将掌握.parent()的签名、返回值、多节点行为、源码实现原理,以及它与.parents()、.closest()、.children()的差异与搭配用法。
方法签名与返回值
.parent()是ShallowWrapper提供的一个零参数遍历方法,其 API 定义如下:
.parent() => ShallowWrapper- 返回值:一个新的
ShallowWrapper,包装了当前 wrapper 中所有节点的直接父节点(每个节点恰好对应一个直接父节点)。 - 不接收任何参数:与
.parents([selector])不同,.parent()不支持传入选择器过滤;若需要带选择器过滤祖先,应使用.parents(selector)。
基本用法与官方示例
官方文档给出的典型用法是:先用.find()定位到目标节点,再通过.parent()回溯一层,最后用.is()断言父节点的类型:
const wrapper = shallow(<ToDoList />); expect(wrapper.find('ul').parent().is('div')).to.equal(true);这段测试的含义是:<ToDoList />渲染出的结构中,ul元素的直接父元素是一个div。.parent()返回的依然是一个ShallowWrapper,因此可以继续链式调用.is()、.hasClass()、.prop()、.text()等任意ShallowWrapper方法。
更完整的断言示例
结合.hasClass()可以验证父节点的样式类,从而断言组件的外层包裹结构:
const wrapper = shallow(<ToDoList />); expect(wrapper.find('.task-item').parent().hasClass('task-list')).to.equal(true);多节点场景
当当前 wrapper 中同时包含多个节点时,.parent()会对每一个节点分别求其直接父节点,并将结果合并进新的 wrapper。仓库中的共享测试用例(parent.jsx)对此有明确验证:
const wrapper = Wrap(( <div> <div className="foo"> <div className="baz" /> </div> <div className="bar"> <div className="baz" /> </div> <div className="bax"> <div className="baz" /> </div> </div> )); const parents = wrapper.find('.baz').parent(); expect(parents).to.have.lengthOf(3); expect(parents.at(0).hasClass('foo')).to.equal(true); expect(parents.at(1).hasClass('bar')).to.equal(true); expect(parents.at(2).hasClass('bax')).to.equal(true);可以看到,三个.baz节点分别对应foo、bar、bax三个直接父节点,顺序与find结果一致,可通过.at(index)逐个访问。
源码实现剖析
.parent()的实现非常简洁,位于 ShallowWrapper.js:
/** * Returns a wrapper around the immediate parent of the current node. * * @returns {ShallowWrapper} */ parent() { return this.flatMap((n) => [n.parents().get(0)]); }其核心逻辑可以拆解为两步:
n.parents():先求出当前节点的全部祖先节点(不包含自身)。parents()在 ShallowWrapper.js 中实现,通过single约束只能作用于单节点 wrapper:parents(selector) { return this.single('parents', (n) => { const allParents = this.wrap(nodeParents(this, n)); return selector ? allParents.filter(selector) : allParents; }); }.get(0):从祖先列表取出第一个元素。由于parents()返回的祖先顺序是从近到远(最近祖先在最前),第 0 项就是直接父节点。
parentsOfNode与pathToNode:祖先链的求法
nodeParents最终调用的是 RSTTraversal.js 中导出的parentsOfNode:
export function parentsOfNode(node, root) { return (pathToNode(node, root) || []).reverse(); }而pathToNode从渲染树的根节点出发,通过广度优先搜索(BFS)找到目标节点,并记录从根到该节点的完整路径(见 RSTTraversal.js):
export function pathToNode(node, root) { const queue = [root]; const path = []; const hasNode = (testNode) => node === testNode; while (queue.length) { const current = queue.pop(); const children = childrenOfNode(current); if (current === node) return pathFilter(path, hasNode); path.push(current); if (children.length === 0) { // leaf node. if it isn't the node we are looking for, we pop. path.pop(); } queue.push(...children); } return null; }得到从根到目标的路径后,parentsOfNode再对其reverse(),于是祖先顺序变为「最近的祖先在前」——这正是.parent()能够用.get(0)直接取到直接父节点的原因。
flatMap:多节点统一处理的关键
.parent()之所以能一次处理 wrapper 中的多个节点,依赖的是 flatMap:
flatMap(fn) { const nodes = this.getNodesInternal().map((n, i) => fn.call(this, this.wrap(n), i)); const flattened = flat(nodes, 1); return this.wrap(flattened.filter(Boolean)); }flatMap对每个节点调用映射函数,将返回的节点数组展平一层,并过滤掉空值后重新包装。这意味着:即使某个节点没有父节点(映射结果为undefined),也不会破坏整个结果 wrapper 的结构。
浅渲染中的边界行为:自定义组件
在shallow()浅渲染模式下,.parent()的行为与完整挂载(.mount())存在差异。这一点在共享测试用例 parent.jsx 中有专门区分:
const Foo = createClass({ render() { return <div className="bar" />; }, }); const wrapper = Wrap(<Foo />);浅渲染(isShallow 为 true):
wrapper.find('.bar')能找到div.bar,但它的直接父节点并不在浅渲染树中(因为Foo本身没有渲染出可被遍历的祖先结构),因此:expect(wrapper.find('.bar').parent()).to.have.lengthOf(0); expect(wrapper.parent()).to.have.lengthOf(0);完整挂载(isShallow 为 false,即
mount渲染):wrapper.find('.bar').parent()能正常返回Foo组件,因为完整渲染树中.bar之上确实存在父级:expect(wrapper.find('.bar').parent()).to.have.lengthOf(1); expect(wrapper.parent()).to.have.lengthOf(0); // 根节点之上没有父节点
从源码结构看,这一差异源于浅渲染只渲染根组件的一层输出,自定义组件在浅渲染树中不作为可回溯的「祖先」节点存在。此外,测试还验证了兄弟节点带子节点时不会干扰父节点判定(见 parent.jsx),因为pathToNode是通过节点引用精确匹配路径的。
与相关遍历方法的对比
.parent()经常与下面几个方法一起使用,它们在 官方 API 文档 中互为「Related Methods」:
| 方法 | 签名 | 返回内容 | 是否支持选择器 | 是否包含自身 |
|---|---|---|---|---|
.parent() | parent() => ShallowWrapper | 当前节点的直接父节点(每个节点一个) | 否 | 否 |
.parents([selector]) | parents([selector]) => ShallowWrapper | 当前节点的全部祖先,从近到远 | 是 | 否 |
.closest(selector) | closest(selector) => ShallowWrapper | 沿祖先链向上第一个匹配选择器的节点 | 是(必填) | 是(先检查自身再向上) |
.children([selector]) | children([selector]) => ShallowWrapper | 当前节点的所有直接子节点 | 是 | 否 |
三者的源码关系非常直观(见 ShallowWrapper.js):
.children()向下遍历,调用childrenOfNode;.parent()向上只走一步,等价于parents().get(0);.closest(selector)向上走到匹配为止:先判断自身是否匹配this.is(selector),否则在parents()中过滤选择器并取第一个匹配项;没有匹配时返回空的findWhere(() => false)wrapper。
实战组合示例
场景一:断言某个按钮一定在指定的表单容器内(只查一层)
const wrapper = shallow(<MyForm />); expect(wrapper.find('button[type="submit"]').parent().hasClass('form-actions')).to.equal(true);场景二:跨多层结构断言最近的包裹容器(closest更合适)
// 需要向上回溯多层时,直接 parent() 不够用 const wrapper = shallow(<CommentList />); expect(wrapper.find('.comment-item').closest('.comment-section')).to.have.lengthOf(1);场景三:统计某个节点的全部祖先层级(parents更合适)
const wrapper = shallow(<ToDoList />); expect(wrapper.find('ul').parents()).to.have.lengthOf(2);使用注意事项
- 链式安全性:
.parent()返回的始终是ShallowWrapper,即使找不到父节点也只会返回空 wrapper,不会抛异常。可通过.length或.exists()判断是否为空,例如浅渲染下自定义组件的子节点调用.parent()会得到长度为 0 的结果。 - 与
.parents()的取舍:只需直接父节点时用.parent()(返回单层);需要过滤祖先时用.parents(selector);需要「最近且匹配选择器」的祖先时用.closest(selector)。 - 浅渲染 vs 完整挂载:在
shallow()中,自定义组件之间的层级关系不会通过.parent()暴露;若必须断言跨组件的父级关系,请改用.mount()生成完整渲染树(此时对应实现见 ReactWrapper.js,逻辑与ShallowWrapper一致)。 - 选择器能力:
.parent()不接受参数,所有基于选择器的祖先查询都应交给.parents()或.closest(),它们支持的选择器类型(CSS 选择器、构造函数、组件显示名、对象属性匹配等)详见 selector 文档。
总结
.parent()是 Enzyme 中层级遍历的最小原语之一,它通过「parents()求祖先链 +.get(0)取最近祖先 +flatMap统一处理多节点」的简洁实现,为断言节点与直接父节点之间的关系提供了可靠手段。理解其底层依赖的parentsOfNode/pathToNode路径搜索机制,以及它与.parents()、.closest()、.children()的分工,能帮助你在编写 React 组件测试时更准确地选择遍历 API,写出结构清晰、断言精准的测试用例。
- 测试
- 前端
【免费下载链接】enzyme
JavaScript Testing utilities for React
相关推荐
enzyme ReactWrapper `.parent()` 详解:获取节点的直接父级并构建可断言的 ReactWrapper
enzyme ReactWrapper .parent 详解:获取节点的直接父级并构建可断言的 ReactWrapper 导读 .parent 是 enzyme
测试前端Enzyme ShallowWrapper `.childAt(index)` 详解:按索引精确获取子节点
Enzyme ShallowWrapper .childAt index 详解:按索引精确获取子节点 Enzyme 的 .childAt index 是 Sha
测试前端Enzyme `.hostNodes()` 方法详解:在 ReactWrapper 与 ShallowWrapper 中提取真实 DOM 宿主节点
Enzyme .hostNodes 方法详解:在 ReactWrapper 与 ShallowWrapper 中提取真实 DOM 宿主节点 .hostNodes
测试前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考