我对带有两个按钮和一些其他元素的组件进行了一个非常简单的测试。
一个最小的例子是:
const { useState, useMemo } = React;
const { Button } = window['MaterialUI'];
const MyExample = () => {
const [lightBulbs, setLightBulbs] = useState(Array.from(Array(10)).map((id, index) => {
return { id: index.toString(), status: 'off'}
}));
const allOn = useMemo(
() => lightBulbs.every(bulb => bulb.status === 'on'),
[lightBulbs]
);
const allOff = useMemo(
() => lightBulbs.every(bulb => bulb.status === 'off'),
[lightBulbs]
);
const switchOn = () =>
setLightBulbs(prevBulbs =>
prevBulbs.map(bulb => ({ ...bulb, status: 'on' }))
);
const switchOff = () =>
setLightBulbs(prevBulbs =>
prevBulbs.map(bulb => ({ ...bulb, status: 'off' }))
);
const switchBulb = bulbId => () =>
setLightBulbs(prevBulbs =>
prevBulbs.map(bulb =>
bulb.id === bulbId
? { ...bulb, status: bulb.status === 'off' ? 'on' : 'off' }
: bulb
)
);
return (
<div>
<div>
<Button id='firstButton' onClick={() => alert('wrong button')}>
Alert
</Button>
<Button id='secondButton' disabled={allOn} onClick={switchOn}>
All On
</Button>
<Button id='thirdButton' disabled={allOff} onClick={switchOff}>
All Off
</Button>
</div>
<ul>
{lightBulbs.map(bulb => (
<li key={bulb.id}>
<span>{"bulb with id "+bulb.id+" is "+bulb.status}</span>
<Button onClick={switchBulb(bulb.id)}>
Switch bulb {bulb.status === 'off' ? 'on' : 'off'}
</Button>
</li>
))}
</ul>
</div>
);
};
ReactDOM.render(React.createElement(MyExample), document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@mui/material@latest/umd/material-ui.development.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="root"></div>
</body>
</html>
在我的测试中我想要:
userEvent.tab()
从“firstButton”切换到 All On 按钮userEvent.keyboard('{Enter}')
userEvent.keyboard('{Enter}')
在浏览器中这是可行的,但是当在测试套件中使用
rect-testing-library
时,第二个选项卡返回到“firstButton”:
test('can click all the buttons', () => {
expect(document.body).toHaveFocus(); // sanity check
userEvent.tab(); // tab past the 'first button'
expect(screen.queryByRole('button', { name: 'All On' })).toHaveFocus()
userEvent.keyboard('{Enter}'); // press button
expect(screen.queryByRole('button', { name: 'All On' })).toBeDisabled() // should now be disabled
userEvent.tab(); // tab off the 'second button', named 'All On'
expect(screen.queryByRole('button', { name: 'All Off' })).toHaveFocus()
userEvent.keyboard('{Enter}');
expect(screen.queryByRole('button', { name: 'All Off' })).toBeDisabled() // should now be disabled
});
我不明白为什么他们的行为不同?
这一定与焦点下的按钮变成
disabled
有关。
这不是一个好的模式。 焦点需要始终可见,但是
disabled
按钮无法接收焦点,在这种情况下,由于 Material UI 中缺少样式,焦点变得不可见。
一旦按下全部开启,您会发现焦点消失了。
不存在单一的“浏览器”。有很多浏览器,用技术术语来说就是“用户代理”,它们的行为基于“Web 标准”。反过来,这包括像react测试库这样的测试库,它试图模拟浏览器的行为。 由于演示的解决方案违反了可访问性规则,因此 Web 标准很可能没有定义焦点元素变得不活动的情况。 当规范不清楚时,用户代理可以决定如何处理未定义的情况,并且可能决定采取与其他用户代理/浏览器不同的方式。
您的浏览器似乎始终将焦点放在该按钮上,即使该按钮不可见。一旦按钮被禁用,
React 测试库可能会简单地
失去焦点。这将导致下一个 Tab 将焦点放在第一个交互元素上,即 Alert 按钮。 缺乏可以使禁用按钮可见的 Material UI 样式,这是另一个提示,表明这是一个未定义的情况。 更容易获得的解决方案
单击后以编程方式将焦点设置到另一个按钮
switch
,因为它意味着一个动作,而不是简单地检查状态,但它不允许只有一些灯泡打开时才需要 mixed
状态。