Enzyme 选择器 #
选择器概述 #
Enzyme 提供了强大的选择器系统,可以灵活地查找组件中的元素。选择器语法类似于 jQuery 和 CSS,让开发者可以快速上手。
选择器类型 #
text
┌─────────────────────────────────────────────────────────────┐
│ Enzyme 选择器类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. CSS 选择器 │
│ ├── 标签选择器: 'div', 'span', 'button' │
│ ├── 类选择器: '.class-name' │
│ ├── ID 选择器: '#element-id' │
│ └── 属性选择器: '[type="submit"]' │
│ │
│ 2. 组件选择器 │
│ ├── 组件引用: MyComponent │
│ └── 组件名称: 'MyComponent' │
│ │
│ 3. 复合选择器 │
│ ├── 后代选择器: 'div .class' │
│ ├── 子选择器: 'div > .class' │
│ └── 组合选择器: 'div.class#id' │
│ │
│ 4. 伪类选择器 │
│ └── ':first-child', ':last-child', ':not()' │
│ │
└─────────────────────────────────────────────────────────────┘
基本选择器 #
标签选择器 #
通过 HTML 标签名查找元素:
javascript
const wrapper = shallow(<MyComponent />);
// 查找所有 div 元素
wrapper.find('div');
// 查找所有 button 元素
wrapper.find('button');
// 查找所有 input 元素
wrapper.find('input');
// 查找所有 span 元素
wrapper.find('span');
类选择器 #
通过 CSS 类名查找元素:
javascript
const wrapper = shallow(<MyComponent />);
// 查找具有 'btn' 类的元素
wrapper.find('.btn');
// 查找具有 'container' 类的元素
wrapper.find('.container');
// 查找具有多个类的元素
wrapper.find('.btn.primary.active');
ID 选择器 #
通过元素 ID 查找:
javascript
const wrapper = shallow(<MyComponent />);
// 查找 ID 为 'header' 的元素
wrapper.find('#header');
// 查找 ID 为 'submit-button' 的元素
wrapper.find('#submit-button');
属性选择器 #
通过元素属性查找:
javascript
const wrapper = shallow(<MyComponent />);
// 查找具有 type 属性的元素
wrapper.find('[type]');
// 查找 type="submit" 的元素
wrapper.find('[type="submit"]');
// 查找 data-testid 属性
wrapper.find('[data-testid="submit-button"]');
// 查找 disabled 属性
wrapper.find('[disabled]');
// 查找 name 属性
wrapper.find('[name="email"]');
组件选择器 #
通过组件引用查找 #
javascript
import ChildComponent from './ChildComponent';
function ParentComponent() {
return (
<div>
<ChildComponent title="Hello" />
<ChildComponent title="World" />
</div>
);
}
describe('ParentComponent', () => {
it('finds child components', () => {
const wrapper = shallow(<ParentComponent />);
// 通过组件引用查找
const children = wrapper.find(ChildComponent);
expect(children.length).toBe(2);
// 访问第一个子组件的 props
expect(children.at(0).prop('title')).toBe('Hello');
});
});
通过组件名称查找 #
javascript
describe('ParentComponent', () => {
it('finds by component name', () => {
const wrapper = shallow(<ParentComponent />);
// 通过组件名称字符串查找
const children = wrapper.find('ChildComponent');
expect(children.length).toBe(2);
});
});
组件显示名称 #
javascript
// 组件显示名称
const MyButton = () => <button>Click</button>;
MyButton.displayName = 'MyButton';
function App() {
return (
<div>
<MyButton />
</div>
);
}
describe('App', () => {
it('finds by display name', () => {
const wrapper = shallow(<App />);
// 通过显示名称查找
expect(wrapper.find('MyButton').length).toBe(1);
expect(wrapper.find(MyButton).length).toBe(1);
});
});
复合选择器 #
后代选择器 #
查找后代元素(任意层级):
javascript
const wrapper = shallow(<MyComponent />);
// 查找 div 内的所有 button
wrapper.find('div button');
// 查找 .container 内的所有 .item
wrapper.find('.container .item');
// 查找 form 内的所有 input
wrapper.find('form input');
子选择器 #
查找直接子元素:
javascript
const wrapper = shallow(<MyComponent />);
// 查找 div 的直接子元素 button
wrapper.find('div > button');
// 查找 ul 的直接子元素 li
wrapper.find('ul > li');
// 查找 .container 的直接子元素 .item
wrapper.find('.container > .item');
组合选择器 #
同时匹配多个条件:
javascript
const wrapper = shallow(<MyComponent />);
// 同时匹配标签和类名
wrapper.find('button.primary');
// 同时匹配标签、类名和 ID
wrapper.find('button.primary#submit');
// 同时匹配标签和属性
wrapper.find('input[type="email"]');
// 复杂组合
wrapper.find('button.primary[type="submit"][disabled]');
伪类选择器 #
:first-child 和 :last-child #
javascript
const wrapper = shallow(<MyComponent />);
// 查找第一个子元素
wrapper.find('li:first-child');
// 查找最后一个子元素
wrapper.find('li:last-child');
:not() #
javascript
const wrapper = shallow(<MyComponent />);
// 查找不具有 disabled 类的 button
wrapper.find('button:not(.disabled)');
// 查找不具有 active 类的 item
wrapper.find('.item:not(.active)');
:nth-child() #
javascript
const wrapper = shallow(<MyComponent />);
// 查找第 n 个子元素
wrapper.find('li:nth-child(2)');
// 查找奇数位置的子元素
wrapper.find('li:nth-child(odd)');
// 查找偶数位置的子元素
wrapper.find('li:nth-child(even)');
选择器方法 #
find() #
查找匹配选择器的所有元素:
javascript
const wrapper = shallow(<MyComponent />);
// 返回所有匹配的元素
const buttons = wrapper.find('button');
console.log(buttons.length); // 匹配数量
filter() #
过滤当前集合中的元素:
javascript
const wrapper = shallow(<MyComponent />);
// 先找所有 li,再过滤出 active 的
const activeItems = wrapper.find('li').filter('.active');
// 使用函数过滤
const longItems = wrapper.find('li').filterWhere(node => {
return node.text().length > 10;
});
filterWhere() #
使用自定义函数过滤:
javascript
const wrapper = shallow(<MyComponent />);
// 过滤文本长度大于 5 的元素
const filtered = wrapper.find('li').filterWhere(node => {
return node.text().length > 5;
});
// 过滤包含特定属性的元素
const withDataId = wrapper.find('*').filterWhere(node => {
return node.prop('data-testid') !== undefined;
});
not() #
排除匹配的元素:
javascript
const wrapper = shallow(<MyComponent />);
// 排除 disabled 类的元素
const enabledButtons = wrapper.find('button').not('.disabled');
// 排除特定属性
const visibleItems = wrapper.find('.item').not('[hidden]');
遍历方法 #
at() #
获取指定索引的元素:
javascript
const wrapper = shallow(<MyComponent />);
const items = wrapper.find('li');
// 获取第一个元素(索引 0)
const firstItem = items.at(0);
// 获取第三个元素
const thirdItem = items.at(2);
first() 和 last() #
javascript
const wrapper = shallow(<MyComponent />);
const items = wrapper.find('li');
// 获取第一个元素
const first = items.first();
// 获取最后一个元素
const last = items.last();
parent() #
获取父元素:
javascript
const wrapper = shallow(<MyComponent />);
const button = wrapper.find('button');
// 获取 button 的父元素
const parent = button.parent();
parents() #
获取所有祖先元素:
javascript
const wrapper = shallow(<MyComponent />);
const button = wrapper.find('button');
// 获取所有祖先元素
const parents = button.parents();
// 获取特定类型的祖先
const formParent = button.parents('form');
closest() #
获取最近的匹配祖先:
javascript
const wrapper = shallow(<MyComponent />);
const button = wrapper.find('button');
// 获取最近的 form 祖先
const form = button.closest('form');
// 获取最近的 .container 祖先
const container = button.closest('.container');
children() #
获取所有子元素:
javascript
const wrapper = shallow(<MyComponent />);
const container = wrapper.find('.container');
// 获取所有子元素
const children = container.children();
// 获取特定选择器的子元素
const activeChildren = container.children('.active');
childAt() #
获取指定索引的子元素:
javascript
const wrapper = shallow(<MyComponent />);
const container = wrapper.find('.container');
// 获取第一个子元素
const firstChild = container.childAt(0);
// 获取第三个子元素
const thirdChild = container.childAt(2);
siblings() #
获取兄弟元素:
javascript
const wrapper = shallow(<MyComponent />);
const activeItem = wrapper.find('.item.active');
// 获取所有兄弟元素
const siblings = activeItem.siblings();
// 获取特定选择器的兄弟元素
const inactiveSiblings = activeItem.siblings('.item:not(.active)');
实用示例 #
测试表单元素 #
javascript
function LoginForm() {
return (
<form className="login-form">
<input
type="email"
name="email"
className="input-email"
data-testid="email-input"
/>
<input
type="password"
name="password"
className="input-password"
data-testid="password-input"
/>
<button
type="submit"
className="btn-submit"
data-testid="submit-button"
>
Login
</button>
</form>
);
}
describe('LoginForm selectors', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<LoginForm />);
});
it('finds inputs by type', () => {
expect(wrapper.find('input[type="email"]').length).toBe(1);
expect(wrapper.find('input[type="password"]').length).toBe(1);
});
it('finds inputs by name', () => {
expect(wrapper.find('input[name="email"]').length).toBe(1);
expect(wrapper.find('input[name="password"]').length).toBe(1);
});
it('finds inputs by className', () => {
expect(wrapper.find('.input-email').length).toBe(1);
expect(wrapper.find('.input-password').length).toBe(1);
});
it('finds inputs by data-testid', () => {
expect(wrapper.find('[data-testid="email-input"]').length).toBe(1);
expect(wrapper.find('[data-testid="password-input"]').length).toBe(1);
});
it('finds submit button', () => {
expect(wrapper.find('button[type="submit"]').length).toBe(1);
expect(wrapper.find('.btn-submit').length).toBe(1);
expect(wrapper.find('[data-testid="submit-button"]').length).toBe(1);
});
});
测试列表组件 #
javascript
function TodoList({ items }) {
return (
<ul className="todo-list">
{items.map((item, index) => (
<li
key={item.id}
className={`todo-item ${item.completed ? 'completed' : ''}`}
data-id={item.id}
>
<span className="todo-text">{item.text}</span>
<button
className="delete-btn"
data-action="delete"
>
Delete
</button>
</li>
))}
</ul>
);
}
describe('TodoList selectors', () => {
const items = [
{ id: 1, text: 'Task 1', completed: false },
{ id: 2, text: 'Task 2', completed: true },
{ id: 3, text: 'Task 3', completed: false }
];
let wrapper;
beforeEach(() => {
wrapper = shallow(<TodoList items={items} />);
});
it('finds all list items', () => {
expect(wrapper.find('li').length).toBe(3);
expect(wrapper.find('.todo-item').length).toBe(3);
});
it('finds completed items', () => {
expect(wrapper.find('.todo-item.completed').length).toBe(1);
});
it('finds incomplete items', () => {
expect(wrapper.find('.todo-item:not(.completed)').length).toBe(2);
});
it('finds item by data-id', () => {
expect(wrapper.find('[data-id="1"]').length).toBe(1);
expect(wrapper.find('[data-id="2"]').length).toBe(1);
});
it('finds delete buttons', () => {
expect(wrapper.find('.delete-btn').length).toBe(3);
expect(wrapper.find('[data-action="delete"]').length).toBe(3);
});
it('finds text spans', () => {
const texts = wrapper.find('.todo-text').map(node => node.text());
expect(texts).toEqual(['Task 1', 'Task 2', 'Task 3']);
});
});
测试嵌套组件 #
javascript
function Card({ title, children }) {
return (
<div className="card">
<header className="card-header">
<h2 className="card-title">{title}</h2>
</header>
<section className="card-body">
{children}
</section>
</div>
);
}
function App() {
return (
<div className="app">
<Card title="Card 1">
<p>Content 1</p>
</Card>
<Card title="Card 2">
<p>Content 2</p>
</Card>
</div>
);
}
describe('Nested components', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<App />);
});
it('finds all cards', () => {
expect(wrapper.find(Card).length).toBe(2);
expect(wrapper.find('Card').length).toBe(2);
});
it('finds first card', () => {
const firstCard = wrapper.find(Card).first();
expect(firstCard.prop('title')).toBe('Card 1');
});
it('finds card by index', () => {
const secondCard = wrapper.find(Card).at(1);
expect(secondCard.prop('title')).toBe('Card 2');
});
});
describe('Card internal structure', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<Card title="Test Card">Content</Card>);
});
it('finds header and body', () => {
expect(wrapper.find('.card-header').length).toBe(1);
expect(wrapper.find('.card-body').length).toBe(1);
});
it('finds title', () => {
expect(wrapper.find('.card-title').text()).toBe('Test Card');
});
it('finds children in body', () => {
const body = wrapper.find('.card-body');
expect(body.text()).toBe('Content');
});
});
选择器最佳实践 #
1. 使用 data-testid #
javascript
// ✅ 推荐:使用 data-testid
wrapper.find('[data-testid="submit-button"]');
// ❌ 避免:依赖 CSS 类名(可能变化)
wrapper.find('.btn-primary-submit');
2. 选择器优先级 #
javascript
// 优先级从高到低:
// 1. data-testid(最稳定)
wrapper.find('[data-testid="submit-button"]');
// 2. 语义化的属性选择器
wrapper.find('button[type="submit"]');
wrapper.find('input[name="email"]');
// 3. 组件选择器
wrapper.find(MyComponent);
// 4. 语义化的类名
wrapper.find('.submit-button');
// 5. 标签选择器(最后选择)
wrapper.find('button');
3. 避免脆弱的选择器 #
javascript
// ❌ 脆弱的选择器
wrapper.find('div > div > div > button'); // 依赖嵌套结构
wrapper.find('.css-1a2b3c'); // 自动生成的类名
wrapper.find('#root > div:nth-child(3)'); // 依赖位置
// ✅ 稳健的选择器
wrapper.find('[data-testid="submit-button"]');
wrapper.find('button[type="submit"]');
wrapper.find('.submit-button');
4. 组合使用选择器 #
javascript
// 组合使用提高精确度
wrapper.find('button.primary[type="submit"][data-testid="submit"]');
// 但不要过度组合
// ❌ 太复杂
wrapper.find('div.container > form.login-form .fields .field input[type="text"][name="email"]');
// ✅ 适度组合
wrapper.find('input[name="email"]');
wrapper.find('[data-testid="email-input"]');
下一步 #
现在你已经掌握了选择器的使用方法,接下来学习 交互模拟 了解如何模拟用户交互!
最后更新:2026-03-28