###### tags: 今晚,就該來點前端測試 (P-3), frontend UT, test
# Note : Testing React with Jest and React Testing Library (RTL) (P-3)
結束了 atoms 的測試,讓我們先吃個前菜,先來測個簡單的 component.
學習重點:
1. screen query method
2. async situation in finding elements
3. userEvent vs fireEvent
4. waitForElementToBeRemoved
<!-- 這個 App 是我們預計整個系列結束後會完成的練習
 -->
<!-- [App repo](https://github.com/bonnie/udemy-TESTING-LIBRARY/tree/main/sundae-server) -->
## ex
try without copy and paste

題目:做一個 summaryForm 的測試,條件要滿足以下
1. checkbox is unchecked by default
2. checking checkbox enables the button
3. Unchecking checkbox disabled the button
Hint:
* render SummaryForm component
* find checkbox and button using { name } option, use mockup for name option value
* make sure to check the test fail
```js
import SummaryForm from '../summary/SummaryForm'
import {render, screen, fireEvent} from '@testing-library/react'
// SummaryForm.test.js
test('checkbox is unchecked by default', () => {
render(<SummaryForm/>);
const checkbox = screen.getByRole('checkbox', {name: /Terms and Conditions/i});
expect(checkbox).not.toBeChecked();
})
test('checking checkbox enableds the button while unchecking checkbox disabled the button', () => {
render(<SummaryForm/>);
const checkbox = screen.getByRole('checkbox', {name: /Terms and Conditions/i})
const confirmButton = screen.getByRole('button', {name: /Confirm order/i})
expect(confirmButton).toBeDisabled();
fireEvent.click(checkbox);
expect(confirmButton).toBeEnabled();
fireEvent.click(checkbox)
expect(confirmButton).toBeDisabled();
})
```
```js
import React, { useState } from "react";
import { Button, Checkbox } from "antd";
// summaryForm.js
const SummaryForm = () => {
const [isDisabled, setIsDisabled] = useState(true);
const clickCheckbox = (e) => {
setIsDisabled(!e.target.checked);
};
return (
<>
<Button disabled={isDisabled}>Confirm Order</Button>
<Checkbox onChange={clickCheckbox}>
I agree to Terms and Conditions
</Checkbox>
</>
);
};
export default SummaryForm;
```
題目:加上 popOver 在 Terms and Conditions 上
在 dev tool 觀察一下,當 popOver 消失時,他的 div 也消失了,當然也有另一種可能是他還在只是他 stays hidden. ==樣式的選擇會影響你的測試,因為他會影響你元素在頁面上的呈現與隱藏==
fireEvent is Okay, but userEvent is better. [userEvent 官方文件](https://testing-library.com/docs/ecosystem-user-event/)
```js
// getBy is not working if we want to see sth is not showing
test("popover responds to hover", () => {
// popover starts out hidden
// popover appears upon mouse over of checkbox label
// popover disappears when we mouse out
});
```
# screen query method
:::success
command[All]ByQueryType
:::
* command
* get - expect element to be in the DOM
* query - expect element not to be in the DOM
* find - expect element to be appeared async
* [All]
* include - expect more than one match
* exclude - expect one match
* QueryType - what you've been search by
* Role - most preffered
* AltText - images
* Text - display elements
* Form elements
* PlaceholderText
* LabelText
* DisplayValue
[Which query should I use ?](https://testing-library.com/docs/queries/about/#priority)
儘可能要做到測試的過程會幾乎是使用者在做操作,所以如果這些屬性不一致,很難說測試跟系統的交互作用與使用者使用的狀況是一樣的
官方參考資料:
[testing-library about query](https://testing-library.com/docs/queries/about/)
[quick-reference cheatsheet](https://testing-library.com/docs/react-testing-library/cheatsheet/)
## testing elements is not on page
async tests sometimes will scusseed even the assertion fails since the test exists before the assertion has the chance to run.
```js
test("popover responds to hover", () => {
render(<SummaryForm />);
// popover starts out hidden
const nullPopOver = screen.queryByText(
/no ice cream will actually be delievered/i
);
expect(nullPopOver).not.toBeInTheDocument();
// popover appears upon mouse over of checkbox label
const termsAndConditions = screen.getByText(/Terms and Conditions/i);
userEvent.hover(termsAndConditions);
const popOver = screen.getByText(/no ice cream will actually be delievered/i);
expect(popOver).toBeInTheDocument(); // 實際上可以不用這行,因為 getBy will throw 如果 no match,但有它可讀性比較好
// popover disappears when we mouse out
userEvent.unhover(termsAndConditions);
const nullPopOverAgain = screen.queryByText(
/no ice cream will actually be delievered/i
);
expect(nullPopOverAgain).not.toBeInTheDocument();
});
```
```js
import React, { useState } from "react";
import { Button, Checkbox, Tooltip } from "antd";
const SummaryForm = () => {
const title = "no ice cream will actually be delievered";
const [isDisabled, setIsDisabled] = useState(true);
const clickCheckbox = (e) => {
setIsDisabled(!e.target.checked);
};
return (
<>
<Button disabled={isDisabled}>Confirm Order</Button>
<Checkbox onChange={clickCheckbox}>
I agree to <Tooltip title={title}>Terms and Conditions</Tooltip>
</Checkbox>
</>
);
};
export default SummaryForm;
```
然後...錯誤就這麼華麗的出現了:
* TestingLibraryElementError: Unable to find an element with the text: /no ice cream will actually be delievered/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
* because, Tooltip is added dynamically to the DOM
* ` const popOver = await screen.findByText(
/no ice cream will actually be delievered/i
);`
* expected document not to contain element, found <div class="ant-tooltip-inner" role="tooltip">no ice cream will actually be delievered</div>
* works with toBeVisible as long as `display: none` is there. 因為節點仍然存在,只是畫面看不到隱藏起來而已.可以打開 console 玩玩看.
* ` expect(nullPopOverAgain).not.toBeVisible();`
* not warpped in act ... warning
* react updated element after test was finished,但你不會想跟著他的建議做,原因是 testing library 已經做了這件事情. 推薦閱讀:[Fix the "not wrapped in act(...)" warning](https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning)
* Hence, wait the change and assert on it. 官方文件:[Appearance and Disappearance
](https://testing-library.com/docs/guide-disappearance/)
* ` await waitForElementToBeRemoved(() =>
screen.queryByText(/no ice cream will actually be delievered/i)
);` -> react bootstrap 改成這樣會 work,因為他是整個節點 remove 掉 -> ==用途:for element was there and then be disappeared==