多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

enzyme Selectors 全面指南:CSS、组件构造器、displayName 与对象属性四种选择器用法详解

enzyme Selectors 全面指南:CSS、组件构造器、displayName 与对象属性四种选择器用法详解 enzyme Selectors 全面指南CSS、组件构造器、displayName 与对象属性四种选择器用法详解【免费下载链接】enzymeJavaScript Testing utilities for React项目地址: https://gitcode.com/gh_mirrors/en/enzyme导读enzyme 是 React 生态中经典的 JavaScript 测试工具库其 API 中大量方法如find、filter、is、some、every等都接受一个selector选择器作为参数用来在渲染树中精准定位节点。本文以仓库中的 docs/api/selector.md 文档为主体结合 packages/enzyme/src/selectors.js 的解析实现与 packages/enzyme-test-suite/test/selector-spec.jsx 的测试用例系统讲解 enzyme 支持的四类选择器CSS 选择器子集、React 组件构造器、组件 displayName、对象属性选择器。读完本文你将能在测试中准确、高效地定位任意节点并理解其背后的匹配原理与边界限制。一、选择器概述enzyme 的 Selector 体系enzyme 中“选择器”泛指一切可以作为参数传给find()、filter()、is()、some()、every()、closest()、parents()等方法的匹配描述。它可以是一段合法的 CSS 选择器字符串一个 React 组件构造器函数或类一个组件的大写开头的displayName字符串一个属性对象Object Property Selector。在源码层面所有这些输入最终都会被统一处理字符串走 CSS 解析与树遍历构造器与对象走谓词函数。核心实现在 packages/enzyme/src/selectors.js 的buildPredicate与reduceTreeBySelector两个导出函数中前者负责把简单选择器编译成单节点匹配谓词后者负责把复杂选择器含组合器的 CSS应用于整棵渲染树。从源码结构可以推断选择器体系与 wrapper 类型无关——ReactWrapper与ShallowWrapper的find都统一调用reduceTreesBySelector见 ReactWrapper.js因此下文绝大部分用法在mount、shallow、render三种渲染方式下均适用。二、方式一合法的 CSS 选择器enzyme 支持一部分合法的 CSS 选择器语法用来在渲染树内查找节点。支持范围如下class 语法.foo、.foo-bar等元素标签名语法input、div、span等id 语法#foo、#foo-bar等属性语法[hreffoo]、[typetext]以及属性选择器家族的其他写法通配语法*React 组件名 propsButton、Button[typesubmit]等——但官方强烈建议优先用组件构造器/函数而不是 displayName 来查找。这些规则在 selectors.js 中对应为明确的 token 类型常量TYPE_SELECTOR类型选择器、CLASS_SELECTORclass、ID_SELECTORid、UNIVERSAL_SELECTOR通配、ATTRIBUTE_PRESENCE属性存在、ATTRIBUTE_VALUE属性值。2.1 属性语法按值匹配数字、布尔与字符串属性语法不仅支持字符串还支持**按值value**匹配即字符串、数字、布尔属性值都可以作为匹配目标。官方文档给出了如下示例const wrapper mount(( div span anum{3} abool{false} / span anum3 aboolfalse / /div ));选择器[anum3]会选中第一个span数字 3但不会选中第二个因为 3 没有加引号表示按数字值精确匹配选择器[anum3]会选中第二个span因为带引号表示显式查找字符串 3布尔值同理[aboolfalse]选中第一个而不会选中第二个。这一行为在源码matchAttributeSelector中得到了精确印证selectors.js只有精确值操作符才能匹配非字符串代码中专门有if (typeof nodePropValue ! string || typeof value ! string)且operator ! EXACT_ATTRIBUTE_OPERATOR时直接返回false的守卫逻辑最终用object-is做严格相等比较is(nodePropValue, value)。测试用例attribute names with numbersselector-spec.jsx也验证了[data-foo-22]只命中数字 2、[data-foo-22]只命中字符串 2 的区分行为。2.2 属性操作符全集源码级补充除了文档明确列出的基础写法从 selectors.js 可以看到 enzyme 实际实现了完整的 CSS 属性操作符集合操作符含义源码常量示例值精确等于EXACT_ATTRIBUTE_OPERATOR[hreffoo]~值是空格分隔词列表其中一词精确等于WHITELIST_ATTRIBUTE_OPERATOR[rel~copyright]可命中relcopyright other\|值恰好等于或以值-开头HYPHENATED_ATTRIBUTE_OPERATOR[hreflang\|en]命中hreflangen-US^值以前缀开头空前缀不匹配任何节点PREFIX_ATTRIBUTE_OPERATOR[type^image]命中typeimageobject$值以后缀结尾空后缀不匹配任何节点SUFFIX_ATTRIBUTE_OPERATOR[type$image]*值包含至少一个子串空子串不匹配任何节点SUBSTRING_ATTRIBUTE_OPERATOR[title*hello]命中titlewell hello there此外属性存在性选择器如[disabled]不关心值也受支持只要节点 props 中拥有该属性hasOwn(nodeProps, token.name)即匹配selectors.js。2.3 组合语法与 CSS 一致的组合器enzyme 支持将上述任意语法像 CSS 一样组合使用div.foo.bar /* 同时满足 tag、多个 class */ input#input-name /* tag id */ a[hreffoo] /* tag 属性 */ .foo .bar /* 后代descendant组合器 */ .foo .bar /* 直接子元素组合器 */ .foo .bar /* 相邻兄弟组合器 */ .foo ~ .bar /* 一般兄弟组合器 */ .foo input /* 后代组合器 */源码中用四种常量区分组合器CHILD、ADJACENT_SIBLING、GENERAL_SIBLING~、DESCENDANT空格selectors.js并通过reduceTreeBySelector中的状态机逐步精炼已匹配结果集matchAdjacentSiblings只取紧邻的下一个兄弟selectors.jsmatchGeneralSibling取之后的所有兄弟selectors.jsmatchDirectChild只取直接子节点selectors.jsmatchDescendant取任意后代selectors.js。需要留意的是复杂选择器含组合器只在find等方法中可用。像is、filter、not这类基于buildPredicate的方法只支持简单选择器——buildPredicate检测到复杂选择器时会抛出TypeError: This method does not support complex CSS selectorsselectors.js测试 selector-spec.jsx 对此有专门断言。2.4 React key 与 ref 属性永远不会生效大多数 React prop 都可以用作选择器但有两个例外key和ref永远不会生效因为 React 在内部使用这两个 props。这一点在matchAttributeSelector的实现中也有体现——当节点 props 中存在带 getter 的描述符React 内部属性常见形态时直接返回falseselectors.js。2.5 伪类/伪元素源码中的扩展支持原文档未提及但从 selectors.js 的matchPseudoSelector可以看到 enzyme 实际上额外支持部分伪类选择器:not(...)取反匹配参数可传多个选择器全部不命中才算匹配:empty没有任何渲染子节点:first-child/:last-child父节点下的第一个/最后一个子元素:focus需要全局document环境在 jsdom 等环境下可用基于document.activeElement判断。其余伪类/伪元素如:hover、::after会抛出Enzyme::Selector does not support the xxx pseudo-element or pseudo-class selectors.错误测试见 selector-spec.jsx。2.6 想要更多 CSS 支持官方文档明确说明欢迎提交实现更多 CSS 选择器支持的 PR这也是 enzyme 未来重点发展的方向之一。这意味着目前的能力边界不支持完整 CSS 规范是已知的、开放的遇到不支持的语法时可考虑改用对象属性选择器或findWhere()作为替代方案。三、方式二React 组件构造器enzyme 允许直接传入组件的构造器引用来查找组件。这种选择器只检查组件类型完全忽略 props 和 childrenfunction MyComponent() { return div /; } // 查找所有 MyComponent 实例 const myComponents wrapper.find(MyComponent);在源码中构造器选择器由 adapter 的能力判定adapter.isValidElementType(selector)无此能力时退化为typeof selector function随后通过adapter.matchesElementType(node, selector)匹配节点selectors.js。这是官方强烈推荐的组件查找方式因为它与显示名无关、不存在字符串大小写歧义也不会因 HOC 包装改名而失效。四、方式三React 组件的 displayNameenzyme 允许用组件的displayName字符串来查找组件。只要组件在渲染树中存在且其displayName已设置、首字符为大写字母就可以用字符串查找function MyComponent() { return div /; } MyComponent.displayName My Component; // 查找 MyComponent 实例 const myComponents wrapper.find(My Component);注意只有当选择器字符串以大写字母开头时才会被当作组件displayName处理小写字母开头的字符串会被当作 CSS 选择器即标签名HOC 包装过的组件、或带有自定义displayName的组件同样可以这样查找即使含小写字母也可以例如withHOC(MyComponent)。其底层匹配逻辑在 Utils.js 的nodeHasType中优先使用 adapter 的displayNameOfNode能力做比较否则对函数组件用function.prototype.name取函数名、对类组件取type.name并同时检查type.displayName。测试用例parens in displayNameselector-spec.jsx验证了Wrapped(Foo)、Wrapped(Twice(Bar))这类带括号的 displayName 也能被精确匹配。五、方式四对象属性选择器enzyme 允许基于节点属性子集来查找组件和节点const wrapper mount(( div span foo{3} bar{false} titlebaz / /div )); wrapper.find({ foo: 3 }); wrapper.find({ bar: false }); wrapper.find({ title: baz });对象中的键值对会被当作要匹配的 props 子集只要节点 props 是传入对象作为子集的超集即命中。源码实现位于 RSTTraversal.js 的nodeMatchesObjectProps它使用is-subset库做子集判断buildPredicate中还会先做校验数组、null和空对象都不被支持会抛出TypeError: Enzyme::Selector does not support an array, null, or empty object as a selector见 selectors.js。5.1 undefined 属性会报错对象属性选择器不允许出现undefined值否则直接抛错wrapper.find({ foo: 3, bar: undefined }); // TypeError: Enzyme::Props cant have undefined values. Try using findWhere() instead.这条校验在buildPredicate中完成——它会预先扫描对象值一旦发现undefined立即抛出上述错误selectors.js。之所以禁止是因为渲染树节点 props 中的undefined会被过滤掉见 Utils.js 的propsOfNode此时再要求匹配 undefined在语义上自相矛盾。如果需要按undefined属性值查找请改用.findWhere()它接受自定义谓词函数可以自由表达这类边界条件。六、选择器方法一览与调用链同一选择器字符串在 enzyme 的不同方法中承担不同角色理解其分发逻辑有助于准确选用方法底层实现支持复杂 CSS 组合器find(selector)reduceTreesBySelectorReactWrapper.js✅filter(selector)/not(selector)buildPredicateReactWrapper.js❌is(selector)buildPredicateReactWrapper.js❌find内部的处理顺序是非字符串且命中 HTML 元素映射表html-element-map/byConstructor时先展开为对应标签名再递归匹配selectors.js函数/对象选择器直接走treeFilter(root, buildPredicate(selector))字符串选择器则走 token 流解析 组合器状态机selectors.js。任何不支持的输入类型都会抛出TypeError: Enzyme::Selector expects a string, object, or Component Constructor。ShallowWrapper的对应方法实现与上述完全一致见 ShallowWrapper.js 附近的buildPredicate调用因此本文所有选择器写法在shallow与mount下行为统一。七、实战要点小结组件查找优先传构造器不要依赖 displayName 字符串易受大小写规则与命名变化影响属性值匹配注意引号语义[anum3]匹配数字 3[anum3]匹配字符串 3布尔值同理key/ref永远无法作为选择器属性复杂组合器、、~、空格只能用于findis/filter/not只接受简单选择器对象属性选择器不能含undefined数组/null/空对象也不支持边界场景请改用findWhere()官方已确认未来会继续扩展 CSS 选择器支持遇到不支持的语法优先用对象属性选择器或findWhere()兜底。相关资源官方选择器文档docs/api/selector.md选择器解析与匹配核心实现packages/enzyme/src/selectors.js渲染树遍历辅助函数class/id/props 子集匹配packages/enzyme/src/RSTTraversal.js节点类型与 props 工具函数packages/enzyme/src/Utils.js选择器完整测试套件组合器、伪类、属性操作符、displayName 等packages/enzyme-test-suite/test/selector-spec.jsxfindWhere()用法docs/api/ShallowWrapper/findWhere.md【免费下载链接】enzymeJavaScript Testing utilities for React项目地址: https://gitcode.com/gh_mirrors/en/enzyme创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表