###### 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 是我們預計整個系列結束後會完成的練習 ![](https://i.imgur.com/pjQk5R1.png) --> <!-- [App repo](https://github.com/bonnie/udemy-TESTING-LIBRARY/tree/main/sundae-server) --> ## ex try without copy and paste ![](https://i.imgur.com/gc8SrRq.jpg) 題目:做一個 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==