` 完全指南:单节点选择器匹配的判定原理与实战用法)
Enzyme ShallowWrapper.is(selector)完全指南单节点选择器匹配的判定原理与实战用法【免费下载链接】enzymeJavaScript Testing utilities for React项目地址: https://gitcode.com/gh_mirrors/en/enzyme.is(selector)是 enzyme 中用于判断当前包装节点是否匹配指定选择器的核心断言 API它只适用于shallow()渲染后得到的单节点ShallowWrapper。本文将以 docs/api/ShallowWrapper/is.md 为主体结合仓库内ShallowWrapper、ReactWrapper的源码实现与共享测试套件系统讲解其签名、约束、支持的全部选择器类型、底层判定原理与常见陷阱帮助你写出更严谨、可读性更高的 React 组件测试。方法签名与核心语义.is(selector)的完整签名如下.is(selector) Boolean其语义是返回当前包装的单个节点是否匹配所提供的选择器。文档中特别强调它必须作用于单节点 wrappera single-node wrapper。参数selectorEnzymeSelector用于匹配的选择器。enzyme 的选择器体系支持 CSS 选择器子集、React 组件构造函数、组件displayName字符串以及对象属性选择器四种形式下文会逐一展开。返回值Boolean包装节点是否匹配该选择器。官方示例const wrapper shallow(div classNamesome-class other-class /); expect(wrapper.is(.some-class)).to.equal(true);mount()全量渲染场景下的ReactWrapper也提供完全一致的 API用法与语义相同见 docs/api/ReactWrapper/is.mdconst wrapper mount(div classNamesome-class other-class /); expect(wrapper.is(.some-class)).to.equal(true);前置约束为什么必须作用于单节点 wrapperis()的名字暗示其作用对象是单个节点源码通过一个统一的single()工具方法来强制这一约束。在 packages/enzyme/src/ShallowWrapper.js#L1647-L1654 中可以看到single(name, fn) { const fnName typeof name string ? name : unknown; const callback typeof fn function ? fn : name; if (this.length ! 1) { throw new Error(Method ${fnName} is meant to be run on 1 node. ${this.length} found instead.); } return callback.call(this, this.getNodeInternal()); }而is()的实现packages/enzyme/src/ShallowWrapper.js#L1030-L1033正是借助single()完成单节点校验 谓词执行is(selector) { const predicate buildPredicate(selector); return this.single(is, (n) predicate(n)); }因此如果对包含多个节点的 wrapper 调用.is()会抛出如下异常Method is is meant to be run on 1 node. 2 found instead.同理ReactWrapper的实现packages/enzyme/src/ReactWrapper.js#L570-L573与ShallowWrapper完全对称同样依赖single()保证单节点约束。实战提示当你的shallow(Component/)顶层返回多个根节点或使用.children()、.find()得到多个匹配结果时应先通过.first()、.at(index)收敛为单节点再调用.is()断言。这也是single()在text()、props()、type()等方法中被普遍复用的原因——凡是对当前唯一节点进行操作的 API 都会走这条校验路径。支持的四种 EnzymeSelector 类型is()的匹配能力取决于 enzyme 的EnzymeSelector体系。以下内容完整继承自 docs/api/selector.md是理解.is()能匹配什么的关键。1. 有效的 CSS 选择器子集enzyme 支持有效的 CSS 选择器子集来在渲染树中定位节点支持范围如下class 语法.foo、.foo-bar等元素标签名语法input、div、span等id 语法#foo、#foo-bar等属性语法[hreffoo]、[typetext]等通配符语法*React 组件名与 propsButton、Button[typesubmit]等——但官方强烈建议通过组件构造函数/函数而非 displayName 来查找属性语法还支持按值而非字符串匹配。字符串、数字和布尔属性值均受支持例如const wrapper mount(( div span anum{3} abool{false} / span anum3 aboolfalse / /div ));选择器[anum3]数字 3 不加引号会选中第一个span而不会选中第二个选择器[anum3]引号包裹会选中第二个因为它显式查找字符串布尔值同理[aboolfalse]选中第一个[aboolfalse]选中第二个。此外enzyme 支持像 CSS 一样组合这些语法div.foo.bar input#input-name a[hreffoo] .foo .bar .foo .bar .foo .bar .foo ~ .bar .foo inputReact Key 与 Ref 例外大多数 React prop 都可以作为选择器但key和ref这两个 props永远不会生效——React 内部使用了它们。2. React 组件构造函数可以传入组件构造函数的引用进行匹配。这类选择器只检查组件类型忽略 props 与 childrenfunction MyComponent() { return div /; } // 判断当前节点是否是 MyComponent wrapper.is(MyComponent);3. React 组件的 displayName如果组件在渲染树中设置了以大写字母开头的displayName就可以用字符串进行匹配function MyComponent() { return div /; } MyComponent.displayName My Component; // 匹配 displayName 为 My Component 的组件 wrapper.is(My Component);注意仅当选择器以及组件的displayName是以大写字母开头的字符串时才生效以小写字母开头的字符串会被当作 CSS 选择器即标签名处理。HOC 包装的组件或自定义displayName即使包含小写字母例如withHOC(MyComponent)同样可以匹配。4. 对象属性选择器可以基于属性的子集查找组件与节点const wrapper mount(( div span foo{3} bar{false} titlebaz / /div )); wrapper.is({ foo: 3 }); wrapper.is({ bar: false }); wrapper.is({ title: baz });undefined 属性是禁止的对象属性选择器中不允许出现undefined值否则会抛出错误wrapper.is({ foo: 3, bar: undefined }); // TypeError: Enzyme::Props cant have undefined values. Try using findWhere() instead.如果需要按undefined属性值搜索请改用.findWhere()。源码级原理buildPredicate 如何构建匹配谓词.is()的匹配核心并非自建逻辑而是复用 selector 系统中导出的buildPredicate工厂函数。其完整实现位于 packages/enzyme/src/selectors.js#L258-L290export function buildPredicate(selector) { // 字符串按简单 CSS 选择器解析 if (typeof selector string) { const tokens safelyGenerateTokens(selector); if (isComplexSelector(tokens)) { throw new TypeError(This method does not support complex CSS selectors); } // 简单选择器只有一个 selector token return buildPredicateFromToken(tokens[0]); } // 元素类型检查节点类型是否匹配 const adapter getAdapter(); const isElementType adapter.isValidElementType ? adapter.isValidElementType(selector) : typeof selector function; if (isElementType) { return (node) adapter.matchesElementType(node, selector); } // 非空对象将键/值视为 props 进行匹配 if (typeof selector object) { if (!Array.isArray(selector) selector ! null Object.keys(selector).length 0) { const hasUndefinedValues values(selector).some((value) typeof value undefined); if (hasUndefinedValues) { throw new TypeError(Enzyme::Props can\t have undefined values. Try using \findWhere()\ instead.); } return (node) nodeMatchesObjectProps(node, selector); } throw new TypeError(Enzyme::Selector does not support an array, null, or empty object as a selector); } throw new TypeError(Enzyme::Selector expects a string, object, or valid element type (Component Constructor)); }从源码可以提炼出几条关键事实字符串选择器只支持简单CSS 选择器。isComplexSelectorpackages/enzyme/src/selectors.js#L248-L250会检测选择器中是否包含组合符combinator如、、~、后代空格一旦检测到就抛出TypeError: This method does not support complex CSS selectors。也就是说.find()可以接受的.foo .bar、.foo .bar这类复杂组合选择器不能直接传给.is()——这是因为is()只对当前单一节点做判定组合符所表达的结构关系在单节点语境下没有意义。buildPredicateFromTokenpackages/enzyme/src/selectors.js#L239-L241会要求节点同时满足该 token 主体中的每一个子 token这正是.foo.bar这类复合选择器compound selector能够工作的原因。对象选择器必须非空且非数组。传入null、数组或空对象都会抛出TypeError包含undefined值同样抛错与 docs/api/selector.md 中的文档说明完全一致。组件类型匹配委托给 adapter。经由adapter.matchesElementType判定这意味着.is(Component)的匹配能力由当前配置的 React 适配器如enzyme-adapter-react-16决定从而天然支持不同 React 版本下的组件类型识别。从共享测试套件看行为细节.is()的行为在 packages/enzyme-test-suite/test/shared/methods/is.jsx 中有系统性的覆盖该文件同时被ShallowWrapper与ReactWrapper的测试复用印证了两者语义一致。几个值得关注的行为细节复合选择器compound selectorconst wrapper Wrap(div classNamefoo bar baz /); expect(wrapper.is(.foo.bar)).to.equal(true);class 空白的精细处理——is()会忽略无关紧要的空白但会正确处理有意义的空白// 忽略无关紧要的空白 const className foo ; expect(wrapper.is(.foo)).to.equal(true); // 处理所有有意义的空白多个 class 以空白分隔 const className foo bar baz; expect(wrapper.is(.foo.bar.baz)).to.equal(true);不匹配时返回 falseconst wrapper Wrap(div classNamebar baz /); expect(wrapper.is(.foo)).to.equal(false);React 16.3 的 forwardRef 与 React.memo 组件识别测试套件通过describeIf(is( 16.3), forwardRef, ...)与is( 16.6)条件断言了.is()对forwardRef组件、memo化组件以及memo 包裹 forwardRef的嵌套组件的识别能力——例如对WrapRendered(RendersChildrenForwardRef //RendersChildren)执行wrapper.is(ForwardRef)会返回true。这表明组件构造函数选择器的匹配走的是组件类型含被memo/forwardRef包装后的引用匹配而不是简单的引用相等或 displayName 字符串比对。与相关 API 的辨析.is()常与以下 API 混淆或搭配使用明确差异有助于写出意图清晰的断言.matchesElement(reactElement)同样要求单节点 wrapper但参数是一个React 元素而非选择器且会逐一比较元素类型、props 与 children从 packages/enzyme/src/ShallowWrapper.js#L1005 的实现看其内部同样经由single()约束单节点。.equals(node)比较当前节点与给定 React 节点是否相等深度结构比对适用于验证整棵子树结构而.is()只回答是否匹配选择器这一个布尔问题。.find(selector)在渲染树中查找所有匹配节点并返回新的 wrapper支持复杂 CSS 选择器.is()只能处理简单选择器。二者在匹配谓词层面共用buildPredicate的语义基础find走的是reduceTreesBySelector见 packages/enzyme/src/ShallowWrapper.js#L1018-L1020但使用场景完全不同find用于查询is用于断言当前节点。.every(selector)/.some(selector)对多个节点执行全部匹配 / 存在匹配的批量断言不要求单节点适合替代对多节点 wrapper 逐个is()的写法。常见错误与规避建议综合文档、源码与测试使用.is()时最容易踩到四个坑多节点调用抛错对包含 0 个或 2 个节点的 wrapper 调用.is()会抛出Method is is meant to be run on 1 node. N found instead.。先用.first()或.at(i)收敛到单节点。复杂 CSS 选择器被拒绝.foo .bar、.foo .bar这类带组合符的选择器会抛TypeError: This method does not support complex CSS selectors。改用.find()定位目标节点后再.is()或用.some()/.every()批量断言。对象选择器传undefined{ foo: 3, bar: undefined }会抛TypeError需要按undefined搜索时改用.findWhere()。大小写语义误判以小写开头的字符串会被当作标签名CSS 选择器处理只有以大写开头的字符串才会被当作组件displayName。匹配原生 DOM 标签时注意不要使用大写。小结.is(selector)是 enzyme 断言体系中当前节点是否匹配选择器的最直接入口。掌握它的关键在于三点牢记单节点约束源码由single()强制执行、理解四种选择器类型各自的匹配语义与限制简单 CSS 子集、组件构造函数、displayName、对象属性以及认清它与find/matchesElement/equals/some/every等 API 的边界。结合 packages/enzyme-test-suite/test/shared/methods/is.jsx 中的测试用例你可以为自己的组件测试快速构建出覆盖 class、id、属性、组件类型及memo/forwardRef场景的健壮断言。【免费下载链接】enzymeJavaScript Testing utilities for React项目地址: https://gitcode.com/gh_mirrors/en/enzyme创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考