From 07e2b263ad4e05b8dc4401cda22bb4f4dd2ea38e Mon Sep 17 00:00:00 2001 From: "Ewe Seong, Yeoh" Date: Thu, 12 May 2022 15:29:44 +0800 Subject: [PATCH 01/18] build(npm): install testing libraries utility modules --- package-lock.json | 169 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 + 2 files changed, 171 insertions(+) diff --git a/package-lock.json b/package-lock.json index 6c3e8bf61..cbc675177 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,9 @@ "@commitlint/cli": "^16.2.3", "@commitlint/config-conventional": "^16.2.1", "@semantic-release/changelog": "^6.0.1", + "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.2.0", + "@testing-library/user-event": "^14.1.1", "@types/node": "^17.0.23", "@types/react": "^18.0.9", "@types/react-dom": "^18.0.3", @@ -7559,6 +7561,77 @@ "node": ">=6.0" } }, + "node_modules/@testing-library/jest-dom": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz", + "integrity": "sha1-k4MC17i0g5Y6Ough8cCAj4ciRc0=", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=8", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha1-IQwhqvRpYT7oyaYsf4ZSXgWNtSw=", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha1-P3PCv1JlkfV0zEksUeJFY0n4ROQ=", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha1-REek1Y/dAzZ8UWyp9krjZc7kql0=", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha1-PZ34fiNrU/FtAeWBUPx3EROOXtI=", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "license": "MIT", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + }, "node_modules/@testing-library/react": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.2.0.tgz", @@ -7578,6 +7651,20 @@ "react-dom": "^18.0.0" } }, + "node_modules/@testing-library/user-event": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.1.1.tgz", + "integrity": "sha1-4f9hGIluSyKvMeXqL52pVq3eI9g=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -8122,6 +8209,16 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/testing-library__jest-dom": { + "version": "5.14.3", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz", + "integrity": "sha1-7mx//p+FlYgu572orzOue4eJ7xc=", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jest": "*" + } + }, "node_modules/@types/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz", @@ -37693,6 +37790,62 @@ } } }, + "@testing-library/jest-dom": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz", + "integrity": "sha1-k4MC17i0g5Y6Ough8cCAj4ciRc0=", + "dev": true, + "requires": { + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "dependencies": { + "aria-query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz", + "integrity": "sha1-IQwhqvRpYT7oyaYsf4ZSXgWNtSw=", + "dev": true + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha1-P3PCv1JlkfV0zEksUeJFY0n4ROQ=", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha1-REek1Y/dAzZ8UWyp9krjZc7kql0=", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, + "source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha1-PZ34fiNrU/FtAeWBUPx3EROOXtI=", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + } + } + }, "@testing-library/react": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.2.0.tgz", @@ -37704,6 +37857,13 @@ "@types/react-dom": "^18.0.0" } }, + "@testing-library/user-event": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.1.1.tgz", + "integrity": "sha1-4f9hGIluSyKvMeXqL52pVq3eI9g=", + "dev": true, + "requires": {} + }, "@tokenizer/token": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", @@ -38225,6 +38385,15 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/testing-library__jest-dom": { + "version": "5.14.3", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.3.tgz", + "integrity": "sha1-7mx//p+FlYgu572orzOue4eJ7xc=", + "dev": true, + "requires": { + "@types/jest": "*" + } + }, "@types/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz", diff --git a/package.json b/package.json index 1a2d7bd0d..984dd93ce 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,9 @@ "@commitlint/cli": "^16.2.3", "@commitlint/config-conventional": "^16.2.1", "@semantic-release/changelog": "^6.0.1", + "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.2.0", + "@testing-library/user-event": "^14.1.1", "@types/node": "^17.0.23", "@types/react": "^18.0.9", "@types/react-dom": "^18.0.3", From 70803cd5500414c2fdb147b3e51d5941333bd8e5 Mon Sep 17 00:00:00 2001 From: "Ewe Seong, Yeoh" Date: Thu, 12 May 2022 15:33:13 +0800 Subject: [PATCH 02/18] test: setup jest-dom as default jest matcher --- lib/test/setupTests.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/test/setupTests.js b/lib/test/setupTests.js index e0b951d57..df969ed79 100644 --- a/lib/test/setupTests.js +++ b/lib/test/setupTests.js @@ -1,3 +1,4 @@ +import "@testing-library/jest-dom"; import "jest-date-mock"; import "raf/polyfill"; import "./canvas.mock"; From 47ae80ea41b6aff7fb2e2537b4085c79f474859d Mon Sep 17 00:00:00 2001 From: "Ewe Seong, Yeoh" Date: Thu, 12 May 2022 15:48:15 +0800 Subject: [PATCH 03/18] test(components): migrate accordion test cases to react testing libraries --- lib/src/Accordion/Accordion.test.tsx | 246 ++++++++++------------- lib/src/Accordion/AccordionItem.test.tsx | 76 ++----- 2 files changed, 124 insertions(+), 198 deletions(-) diff --git a/lib/src/Accordion/Accordion.test.tsx b/lib/src/Accordion/Accordion.test.tsx index 30e7f144c..494da727c 100644 --- a/lib/src/Accordion/Accordion.test.tsx +++ b/lib/src/Accordion/Accordion.test.tsx @@ -1,29 +1,22 @@ +import { render, RenderResult, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import React from "react"; -import { act, Simulate } from "react-dom/test-utils"; -import { unmountComponentAtNode, render } from "react-dom"; import { Accordion, AccordionItem } from "."; describe("Component: Accordion", () => { - let container: HTMLDivElement = null; + function getAccordionButtons(): Array { + return screen.getAllByRole("button"); + } - /** To disable Collapse setTimeout calls */ - beforeAll(() => jest.useFakeTimers()); - - beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); - }); + function getButtonExpansion(button: HTMLButtonElement): boolean { + return JSON.parse(button.getAttribute("aria-expanded")); + } - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - }); + /** To disable Collapse setTimeout calls */ + // beforeAll(() => jest.useFakeTimers()); it("Should render correctly", () => { - act(() => { - render(, container); - }); + const { container }: RenderResult = render(); expect(container.firstElementChild.classList.contains("rc")).toBeTruthy(); expect(container.firstElementChild.classList.contains("accordion")).toBeTruthy(); expect(container.firstElementChild.id).toBeDefined(); @@ -32,188 +25,153 @@ describe("Component: Accordion", () => { it("Should render with id, custom class and alternative style", () => { const id: string = "myId"; const className: string = "myClassname"; - act(() => { - render(, container); - }); + const { container }: RenderResult = render(); expect(container.firstElementChild.id).toEqual(id); expect(container.firstElementChild.classList.contains("alternative")).toBeTruthy(); expect(container.firstElementChild.classList.contains(className)).toBeTruthy(); }); - it("Should toggle on and off an accordion item", () => { - act(() => { - render( - - - - , - container - ); - }); - const firstButton: HTMLButtonElement = container.querySelectorAll("button.btn-link").item(0); - act(() => { - firstButton.click(); - }); - expect(JSON.parse(firstButton.getAttribute("aria-expanded"))).toBeTruthy(); - act(() => { - firstButton.click(); - }); - expect(JSON.parse(firstButton.getAttribute("aria-expanded"))).toBeFalsy(); + it("Should toggle on and off an accordion item", async () => { + render( + + + + + ); + const firstButton: HTMLButtonElement = getAccordionButtons()[0]; + await userEvent.click(firstButton); + expect(getButtonExpansion(firstButton)).toBeTruthy(); + await userEvent.click(firstButton); + expect(getButtonExpansion(firstButton)).toBeFalsy(); }); it("Should allow rendering none elements", () => { const text: string = "Some text"; - act(() => { - render({text}, container); - }); - expect(container.firstElementChild.innerHTML).toEqual(text); + render({text}); + expect(screen.getByText(text)).toBeInTheDocument(); }); describe("Should forward the following props to children accordion items", () => { const id: string = "myId"; + let container: HTMLElement; let firstButton: HTMLButtonElement; beforeEach(() => { - act(() => { - render( - - - - , - container - ); - }); - firstButton = container.querySelectorAll("button").item(0); + const result: RenderResult = render( + + + + + ); + container = result.container; + firstButton = getAccordionButtons()[0]; }); - test("paretnId", () => expect(container.querySelectorAll(".collapse").item(0).dataset.parent).toEqual(`#${id}`)); + test("parentId", () => expect(container.querySelectorAll(".collapse").item(0).dataset.parent).toEqual(`#${id}`)); + test("value", () => expect(firstButton.dataset.indexNumber).toEqual("0")); - test("active", () => expect(firstButton.getAttribute("aria-expanded")).toEqual("true")); - test("onToggle", () => { - act(() => { - firstButton.click(); - }); - expect(firstButton.getAttribute("aria-expanded")).toEqual("false"); + + test("active", () => expect(getButtonExpansion(firstButton)).toBeTruthy()); + + test("onToggle", async () => { + await userEvent.click(firstButton); + expect(getButtonExpansion(firstButton)).toBeFalsy(); }); }); describe("Should set default expanded using default value and default checked", () => { test("defaultValue from parent", () => { - act(() => { - render( - - - - , - container - ); - }); - expect(container.querySelector("button").getAttribute("aria-expanded")).toEqual("true"); - }); - test("defaultValue from children", () => { - act(() => { - render( - - - - , - container - ); - }); - expect(container.querySelector("button").getAttribute("aria-expanded")).toEqual("true"); - }); - }); - - it("Should fire on toggle", () => { - const mockFn: jest.Mock = jest.fn(); - act(() => { render( - + - , - container + ); + expect(getButtonExpansion(getAccordionButtons()[0])).toBeTruthy(); }); - const firstButton: HTMLButtonElement = container.querySelectorAll("button.btn-link").item(0); - act(() => { - firstButton.click(); - }); - expect(mockFn).toBeCalled(); - }); - it("Should fire accordion item callback on toggle", () => { - const mockFn: jest.Mock = jest.fn(); - act(() => { + test("defaultValue from children", () => { render( - + - , - container + ); + expect(getButtonExpansion(getAccordionButtons()[0])).toBeTruthy(); }); - const firstButton: HTMLButtonElement = container.querySelectorAll("button.btn-link").item(0); - act(() => { - firstButton.click(); - }); + }); + + it("Should fire on toggle", async () => { + const mockFn: jest.Mock = jest.fn(); + render( + + + + + ); + await userEvent.click(getAccordionButtons()[0]); + expect(mockFn).toBeCalled(); + }); + + it("Should fire accordion item callback on toggle", async () => { + const mockFn: jest.Mock = jest.fn(); + render( + + + + + ); + await userEvent.click(getAccordionButtons()[0]); expect(mockFn).toBeCalled(); }); describe("Keyboard support", () => { function renderAccordion(): void { - act(() => { - render( - - - - , - container - ); - }); - container.querySelector("button").focus(); - } - - function pressKey(key: string): void { - act(() => Simulate.keyDown(document.activeElement, { key })); + render( + + + + + ); + getAccordionButtons()[0].focus(); } function assertFocusedElement(element: HTMLElement): void { expect(document.activeElement).toEqual(element); } - it("Should focus on next header when down arrow button is pressed", () => { + it("Should focus on next header when down arrow button is pressed", async () => { renderAccordion(); - assertFocusedElement(container.querySelectorAll("button")[0]); - pressKey("ArrowDown"); - assertFocusedElement(container.querySelectorAll("button")[1]); - pressKey("ArrowDown"); - assertFocusedElement(container.querySelectorAll("button")[0]); + assertFocusedElement(getAccordionButtons()[0]); + await userEvent.keyboard("[ArrowDown]"); + assertFocusedElement(getAccordionButtons()[1]); + await userEvent.keyboard("[ArrowDown]"); + assertFocusedElement(getAccordionButtons()[0]); }); - it("Should focus on previous header when up arrow button is pressed", () => { + it("Should focus on previous header when up arrow button is pressed", async () => { renderAccordion(); - assertFocusedElement(container.querySelectorAll("button")[0]); - pressKey("ArrowUp"); - assertFocusedElement(container.querySelectorAll("button")[1]); - pressKey("ArrowUp"); - assertFocusedElement(container.querySelectorAll("button")[0]); + assertFocusedElement(getAccordionButtons()[0]); + await userEvent.keyboard("[ArrowUp]"); + assertFocusedElement(getAccordionButtons()[1]); + await userEvent.keyboard("[ArrowUp]"); + assertFocusedElement(getAccordionButtons()[0]); }); - it("Should focus on first header when home button is pressed", () => { + it("Should focus on first header when home button is pressed", async () => { renderAccordion(); - assertFocusedElement(container.querySelectorAll("button")[0]); - pressKey("ArrowDown"); - assertFocusedElement(container.querySelectorAll("button")[1]); - pressKey("Home"); - assertFocusedElement(container.querySelectorAll("button")[0]); + assertFocusedElement(getAccordionButtons()[0]); + await userEvent.keyboard("[ArrowDown]"); + assertFocusedElement(getAccordionButtons()[1]); + await userEvent.keyboard("[Home]"); + assertFocusedElement(getAccordionButtons()[0]); }); - it("Should focus on last header when end button is pressed", () => { + it("Should focus on last header when end button is pressed", async () => { renderAccordion(); - assertFocusedElement(container.querySelectorAll("button")[0]); - pressKey("End"); - assertFocusedElement(container.querySelectorAll("button")[1]); + assertFocusedElement(getAccordionButtons()[0]); + await userEvent.keyboard("[End]"); + assertFocusedElement(getAccordionButtons()[1]); }); it("Should not handle button event when button is pressed on element aside from accordion header", () => { diff --git a/lib/src/Accordion/AccordionItem.test.tsx b/lib/src/Accordion/AccordionItem.test.tsx index 08612361d..a7a70dcf1 100644 --- a/lib/src/Accordion/AccordionItem.test.tsx +++ b/lib/src/Accordion/AccordionItem.test.tsx @@ -1,30 +1,12 @@ +import { render, RenderResult, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import React from "react"; -import { act } from "react-dom/test-utils"; -import { unmountComponentAtNode, render } from "react-dom"; -import { AccordionItem } from "."; import { renderToStaticMarkup } from "react-dom/server"; +import { AccordionItem } from "."; describe("Component: Accordion", () => { - let container: HTMLDivElement = null; - - /** To disable Collapse setTimeout calls */ - beforeAll(() => jest.useFakeTimers()); - - beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - }); - it("Should render correctly", () => { - act(() => { - render(, container); - }); + const { container }: RenderResult = render(); expect(container.firstElementChild.classList.contains("card")).toBeTruthy(); expect(container.firstElementChild.classList.contains("collapsed")).toBeTruthy(); expect(container.firstElementChild.children.length).toBe(2); @@ -42,56 +24,42 @@ describe("Component: Accordion", () => { const header: string = "myHeader"; const subHeader: string = "mySubHeader"; const content: string = "myContent"; - act(() => { - render( - - {content} - , - container - ); - }); - expect(container.querySelector("h4").innerHTML).toEqual(header); - expect(container.querySelector("h6").innerHTML).toEqual(subHeader); - expect(container.querySelector(".content").innerHTML).toEqual(content); + render( + + {content} + + ); + expect(screen.getByText(header)).toBeInTheDocument(); + expect(screen.getByText(subHeader)).toBeInTheDocument(); + expect(screen.getByText(content)).toBeInTheDocument(); }); it("Should be default to expanded when defaultValue is set to true", () => { - act(() => { - render(, container); - }); + const { container }: RenderResult = render(); expect(container.firstElementChild.classList.contains("collapsed")).toBeFalsy(); expect(container.firstElementChild.lastElementChild.classList.contains("collapsed")).toBeFalsy(); }); it("Should render parent id in collapse div when available", () => { const parentId: string = "123"; - act(() => { - render(, container); - }); + const { container }: RenderResult = render(); expect(container.querySelector(".collapse").getAttribute("data-parent")).toEqual(`#${parentId}`); }); - it("Should trigger onToggle when button is clicked", () => { + it("Should trigger onToggle when button is clicked", async () => { const onToggle: jest.Mock = jest.fn(); - act(() => { - render(, container); - }); - act(() => { - container.querySelector("button").click(); - }); + render(); + await userEvent.click(screen.getByRole("button")); expect(onToggle).toBeCalled(); }); it("Should allow rendering nodes in header, subheader, and children", () => { const element: JSX.Element =

test

; - act(() => { - render( - - {element} - , - container - ); - }); + const { container }: RenderResult = render( + + {element} + + ); expect(container.querySelector("h4").innerHTML).toEqual(renderToStaticMarkup(element)); expect(container.querySelector("h6").innerHTML).toEqual(renderToStaticMarkup(element)); expect(container.querySelector(".content").innerHTML).toEqual(renderToStaticMarkup(element)); From 1e6fc1ecfe65c82cac7e3c7d060aff3eb0fd9beb Mon Sep 17 00:00:00 2001 From: "Ewe Seong, Yeoh" Date: Thu, 12 May 2022 17:08:23 +0800 Subject: [PATCH 04/18] test(components): migrate breadcrumb test cases to react testing libraries --- lib/src/Breadcrumb/Breadcrumb.test.tsx | 47 ++++--------- lib/src/Breadcrumb/BreadcrumbItem.test.tsx | 79 ++++++++-------------- 2 files changed, 41 insertions(+), 85 deletions(-) diff --git a/lib/src/Breadcrumb/Breadcrumb.test.tsx b/lib/src/Breadcrumb/Breadcrumb.test.tsx index d8cec8cef..bb1f35783 100644 --- a/lib/src/Breadcrumb/Breadcrumb.test.tsx +++ b/lib/src/Breadcrumb/Breadcrumb.test.tsx @@ -1,26 +1,10 @@ +import { render, RenderResult, screen } from "@testing-library/react"; import React from "react"; import { Breadcrumb, BreadcrumbItem } from "."; -import { unmountComponentAtNode, render } from "react-dom"; -import { act } from "react-dom/test-utils"; describe("Component: Breadcrumb", () => { - let container: HTMLDivElement = null; - - beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - }); - it("Should render", () => { - act(() => { - render(, container); - }); + const { container }: RenderResult = render(); expect(container.firstElementChild.tagName).toEqual("NAV"); expect(container.firstElementChild.getAttribute("aria-label")).toEqual("breadcrumb"); expect(container.firstElementChild.firstElementChild.tagName).toEqual("OL"); @@ -28,26 +12,19 @@ describe("Component: Breadcrumb", () => { }); it("Should render BreadcrumbItem directly", () => { - act(() => { - render( - - First - Second - , - container - ); - }); - const items: NodeListOf = container.querySelectorAll(".breadcrumb-item"); - expect(items.length).toBe(2); - expect(items[0].firstElementChild.innerHTML).toEqual("First"); - expect(items[1].firstElementChild.innerHTML).toEqual("Second"); + render( + + First + Second + + ); + expect(screen.getByText("First")).toBeInTheDocument(); + expect(screen.getByText("Second")).toBeInTheDocument(); }); it("Should allow rendering none elements", () => { const text: string = "Some text"; - act(() => { - render({text}, container); - }); - expect(container.firstElementChild.firstElementChild.innerHTML).toEqual(text); + render({text}); + expect(screen.getByText(text)).toBeInTheDocument(); }); }); diff --git a/lib/src/Breadcrumb/BreadcrumbItem.test.tsx b/lib/src/Breadcrumb/BreadcrumbItem.test.tsx index 90a1c171e..2238d89b3 100644 --- a/lib/src/Breadcrumb/BreadcrumbItem.test.tsx +++ b/lib/src/Breadcrumb/BreadcrumbItem.test.tsx @@ -1,76 +1,55 @@ +import { render, RenderResult, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import React from "react"; import { Breadcrumb, BreadcrumbItem } from "."; -import { unmountComponentAtNode, render } from "react-dom"; -import { act } from "react-dom/test-utils"; describe("Component: Breadcrumb", () => { - let container: HTMLDivElement = null; - - beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - }); - it("Should render correctly", () => { - act(() => { - render(Home, container); - }); + const { container }: RenderResult = render(Home); expect(container.firstElementChild.tagName).toEqual("LI"); expect(container.firstElementChild.firstElementChild.tagName).toEqual("A"); - expect(container.firstElementChild.firstElementChild.innerHTML).toEqual("Home"); + expect(screen.getByText("Home")); }); - it("Should pass data-index-number and title to anchor tag, pass href and onNavigate if not last item", () => { + it("Should pass data-index-number and title to anchor tag, pass href and onNavigate if not last item", async () => { const href: string = "#/home"; const title: string = "myTitle"; const onNavigate: jest.Mock = jest.fn(); - act(() => { - render( - - - First - - - Second - - , - container - ); - }); + const { container }: RenderResult = render( + + + First + + + Second + + + ); + // using `querySelectorAll` instead of `screen.getByRole('link')` because the latter does not recognize anchor without `href` as link const links: NodeListOf = container.querySelectorAll("a"); expect(links.item(0).title).toEqual(title); expect(links.item(0).hash).toEqual(href); expect(links.item(0).dataset.indexNumber).toEqual("0"); expect(links.item(1).hash).toBe(""); - act(() => { - links.forEach((link: HTMLAnchorElement) => link.click()); - }); + + for (const link of links as any) { + await userEvent.click(link); + } + expect(onNavigate).toBeCalledTimes(1); }); - it("Should change active state when another item is added", () => { - act(() => { - render(, container); - }); + it("Should change active state when another item is added", async () => { + const { container }: RenderResult = render(); let items: NodeListOf = container.querySelectorAll(".breadcrumb-item"); - expect(items[0].classList.contains("active")).toBeFalsy(); - expect(items[1].classList.contains("active")).toBeTruthy(); + expect(items[0]).not.toHaveClass("active"); + expect(items[1]).toHaveClass("active"); expect(items[2]).toBeUndefined(); - - act(() => { - container.querySelector("#trigger").click(); - }); - + await userEvent.click(screen.getByRole("checkbox")); items = container.querySelectorAll(".breadcrumb-item"); - expect(items[0].classList.contains("active")).toBeFalsy(); - expect(items[1].classList.contains("active")).toBeFalsy(); - expect(items[2].classList.contains("active")).toBeTruthy(); + expect(items[0]).not.toHaveClass("active"); + expect(items[1]).not.toHaveClass("active"); + expect(items[2]).toHaveClass("active"); }); }); From df34c2ce41bee347f1c0a118257135c121d42f64 Mon Sep 17 00:00:00 2001 From: "Ewe Seong, Yeoh" Date: Thu, 12 May 2022 18:25:48 +0800 Subject: [PATCH 05/18] test(components): migrate button test cases to react testing libraries --- lib/src/Button/Button.test.tsx | 60 +++++----------- lib/src/ButtonGroup/ButtonGroup.test.tsx | 41 +++-------- lib/src/CloseButton/CloseButton.test.tsx | 33 ++------- lib/src/RadioButton/RadioButton.test.tsx | 52 ++++---------- lib/src/RadioButton/RadioGroup.test.tsx | 91 ++++++++---------------- 5 files changed, 74 insertions(+), 203 deletions(-) diff --git a/lib/src/Button/Button.test.tsx b/lib/src/Button/Button.test.tsx index 494f2bb31..65b034312 100644 --- a/lib/src/Button/Button.test.tsx +++ b/lib/src/Button/Button.test.tsx @@ -1,39 +1,20 @@ +import { render, screen } from "@testing-library/react"; import React from "react"; -import { act } from "react-dom/test-utils"; -import { unmountComponentAtNode, render } from "react-dom"; -import { Button, ButtonTheme, ButtonSize } from "."; +import { Button, ButtonSize, ButtonTheme } from "."; type ButtonTestItem = { value: T; expected: K }; describe("Component: Button", () => { - let container: HTMLDivElement = null; - - beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - }); - it("Should render", () => { const text: string = "Test"; - act(() => { - render(, container); - }); - expect(container.firstElementChild).toBeDefined(); - expect(container.firstElementChild.innerHTML).toEqual(text); + render(); + expect(screen.getByText(text)).toBeInTheDocument(); }); it("Should render custom className", () => { const className: string = "myButtonClass"; - act(() => { - render(, - container - ); - }); - expect(container.firstElementChild.firstElementChild).toBeDefined(); - expect(container.firstElementChild.firstElementChild.id).toEqual("mySvg"); + const testId: string = "mySvg"; + render( + + ); + expect(screen.getByTestId(testId)).toBeInTheDocument(); }); }); diff --git a/lib/src/ButtonGroup/ButtonGroup.test.tsx b/lib/src/ButtonGroup/ButtonGroup.test.tsx index 24c0de86c..306e7feb4 100644 --- a/lib/src/ButtonGroup/ButtonGroup.test.tsx +++ b/lib/src/ButtonGroup/ButtonGroup.test.tsx @@ -1,39 +1,20 @@ +import { render, screen } from "@testing-library/react"; import React from "react"; -import { act } from "react-dom/test-utils"; -import { unmountComponentAtNode, render } from "react-dom"; import { ButtonGroup, ButtonGroupSizes } from "."; type ButtonTestItem = { value: T; expected: K }; describe("Component: ButtonGroup", () => { - let container: HTMLDivElement = null; - - beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - }); - it("Should render", () => { const text: string = "Test"; - act(() => { - render({text}, container); - }); - expect(container.firstElementChild).toBeDefined(); - expect(container.firstElementChild.innerHTML).toEqual(text); + render({text}); + expect(screen.getByText(text)).toBeInTheDocument(); }); it("Should render custom className", () => { const className: string = "myButtonGroupClass"; - act(() => { - render(, container); - }); - expect(container.firstElementChild.classList.contains(className)).toBeTruthy(); + render(); + expect(screen.getByRole("group")).toHaveClass(className); }); describe("Should render supported sizes", () => { @@ -44,18 +25,14 @@ describe("Component: ButtonGroup", () => { ]; list.map((item: ButtonTestItem) => { it(`Size: ${item.value} - Expected to render (${item.expected})`, () => { - act(() => { - render(, container); - }); - expect(container.firstElementChild.classList.contains(item.expected)).toBeTruthy(); + render(); + expect(screen.getByRole("group")).toHaveClass(item.expected); }); }); }); it("Should render vertical", () => { - act(() => { - render(, container); - }); - expect(container.firstElementChild.classList.contains("btn-group-vertical")).toBeTruthy(); + render(); + expect(screen.getByRole("group")).toHaveClass("btn-group-vertical"); }); }); diff --git a/lib/src/CloseButton/CloseButton.test.tsx b/lib/src/CloseButton/CloseButton.test.tsx index f20dbdbe0..de2d32721 100644 --- a/lib/src/CloseButton/CloseButton.test.tsx +++ b/lib/src/CloseButton/CloseButton.test.tsx @@ -1,39 +1,16 @@ +import { render, screen } from "@testing-library/react"; import React from "react"; -import { unmountComponentAtNode, render } from "react-dom"; import { CloseButton } from "."; -import { act } from "react-dom/test-utils"; describe("Component: CloseButton", () => { - let container: HTMLDivElement = null; - - beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - }); - it("Should render correction", () => { - act(() => { - render(, container); - }); - - expect(container.firstElementChild.tagName.toLowerCase()).toEqual("button"); - expect(container.firstElementChild.classList.contains("rc")).toBeTruthy(); - expect(container.firstElementChild.classList.contains("close-btn")).toBeTruthy(); + render(); + expect(screen.getByRole("button")).toHaveClass("rc", "close-btn"); }); it("Should allow passing a custom classname", () => { const className: string = "myClassName"; - - act(() => { - render(, container); - }); - - expect(container.firstElementChild.classList.contains(className)).toBeTruthy(); + render(); + expect(screen.getByRole("button")).toHaveClass(className); }); }); diff --git a/lib/src/RadioButton/RadioButton.test.tsx b/lib/src/RadioButton/RadioButton.test.tsx index d6829f72a..c9b62bbfd 100644 --- a/lib/src/RadioButton/RadioButton.test.tsx +++ b/lib/src/RadioButton/RadioButton.test.tsx @@ -1,61 +1,35 @@ +import { render, RenderResult, screen } from "@testing-library/react"; import React from "react"; -import { unmountComponentAtNode, render } from "react-dom"; -import { act } from "react-dom/test-utils"; import { RadioButton } from "./RadioButton"; describe("Component: RadioButton", () => { - let container: HTMLDivElement = null; - - beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - }); - it("Should render", () => { - act(() => { - render(, container); - }); - expect(container).toBeDefined(); + render(); + expect(screen.getByRole("radio")).toBeInTheDocument(); }); it("Should pass custom class and id", () => { const className: string = "myRadiobuttonClass"; const wrapperClassname: string = "myWrapperClassname"; - - act(() => { - render(, container); - }); - expect(container.querySelector(".custom-control-input").classList.contains(className)).toBeTruthy(); + const { container }: RenderResult = render(); + expect(screen.getByRole("radio")).toHaveClass(className); expect(container.firstElementChild.classList.contains(wrapperClassname)).toBeTruthy(); }); it("Should render with random id if id is not passed", () => { - act(() => { - render(label, container); - }); - expect(container.querySelector("input").hasAttribute("id")).toBeTruthy(); - expect(container.querySelector("label").getAttribute("for")).toBe(container.querySelector("input").getAttribute("id")); + const { rerender }: RenderResult = render(label); + expect(screen.getByRole("radio")).toHaveAttribute("id", expect.any(String)); + expect(screen.getByLabelText("label")).toBeInTheDocument(); const id: string = "myId"; - act(() => { - render(label, container); - }); - expect(container.querySelector("input").id).toEqual(id); - expect(container.querySelector("label").getAttribute("for")).toEqual(id); + rerender(label); + expect(screen.getByRole("radio")).toHaveAttribute("id", id); + expect(screen.getByLabelText("label")).toBeInTheDocument(); }); it("Should render and display label", () => { const label: string = "my label"; - act(() => { - render({label}, container); - }); - expect(container.querySelector(`.custom-control-label`)).not.toBeNull(); - expect(container.querySelector(`.custom-control-label`).innerHTML).toContain(label); + render({label}); + expect(screen.getByLabelText(label)).toBeInTheDocument(); }); }); diff --git a/lib/src/RadioButton/RadioGroup.test.tsx b/lib/src/RadioButton/RadioGroup.test.tsx index c72612f8e..3f3a2726d 100644 --- a/lib/src/RadioButton/RadioGroup.test.tsx +++ b/lib/src/RadioButton/RadioGroup.test.tsx @@ -1,7 +1,7 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import React from "react"; -import { unmountComponentAtNode, render } from "react-dom"; -import { act, Simulate } from "react-dom/test-utils"; -import { RadioGroup, RadioButton } from "."; +import { RadioButton, RadioGroup } from "."; const radios: React.ReactElement[] = [ @@ -13,78 +13,47 @@ const radios: React.ReactElement[] = [ ]; describe("Component: RadioGroup", () => { - let container: HTMLDivElement = null; - - beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - }); - it("Should render", () => { - act(() => { - render({radios}, container); - }); - expect(container.firstElementChild).not.toBeNull(); - expect(container.firstElementChild.classList.contains("radio-group")).toBeTruthy(); - expect(container.firstElementChild.children).toHaveLength(radios.length); + render({radios}); + expect(screen.getByRole("group")).toBeInTheDocument(); + expect(screen.getAllByRole("radio")).toHaveLength(radios.length); }); it("Should allow passing a custom classname", () => { const className: string = "myclassname"; - act(() => { - render(, container); - }); - - expect(container.firstElementChild.classList.contains(className)).toBeTruthy(); + render(); + expect(screen.getByRole("group")).toHaveClass(className); }); - it("Should capture onChange events from individual radio buttons and emit it as one event", () => { + it("Should capture onChange events from individual radio buttons and emit it as one event", async () => { const onChange: jest.Mock = jest.fn(); - - act(() => { - render( - - {radios} - , - container - ); - }); - - act(() => Simulate.change(container.querySelector("input"))); - expect(onChange).toBeCalled(); + render( + + {radios} + + ); + const radioInputs: Array = screen.getAllByRole("radio"); + await userEvent.click(radioInputs[1]); + expect(onChange).toBeCalledTimes(1); }); it("Should render any non radio button components", () => { - act(() => { - render( - - {radios} - test - , - container - ); - }); - - expect(container.firstElementChild.textContent).toContain("test"); + render( + + {radios} + test + + ); + expect(screen.getByText("test")).toBeInTheDocument(); }); it("Should render component with label", () => { const label: string = "Element label"; - act(() => { - render( - - {radios} - , - container - ); - }); - - expect(container.querySelector("legend").textContent).toContain(label); + render( + + {radios} + + ); + expect(screen.getByText(label)).toBeInTheDocument(); }); }); From f34835046b764db8c3751caad08e8f077837834a Mon Sep 17 00:00:00 2001 From: "Ewe Seong, Yeoh" Date: Fri, 13 May 2022 12:59:45 +0800 Subject: [PATCH 06/18] test(components): migrate checkbox test cases to react testing libraries --- lib/src/Checkbox/Checkbox.test.tsx | 61 +++++++----------------- lib/src/RadioButton/RadioButton.test.tsx | 8 ++-- 2 files changed, 22 insertions(+), 47 deletions(-) diff --git a/lib/src/Checkbox/Checkbox.test.tsx b/lib/src/Checkbox/Checkbox.test.tsx index 55fe04ccb..c80fa5f89 100644 --- a/lib/src/Checkbox/Checkbox.test.tsx +++ b/lib/src/Checkbox/Checkbox.test.tsx @@ -1,63 +1,38 @@ +import { render, RenderResult, screen } from "@testing-library/react"; import React from "react"; -import { act } from "react-dom/test-utils"; -import { unmountComponentAtNode, render } from "react-dom"; import { Checkbox } from "."; describe("Component: Checkbox", () => { - let container: HTMLDivElement = null; - - beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - }); - it("Should render", () => { - act(() => { - render(, container); - }); - expect(container.firstElementChild).not.toBeNull(); + render(); + expect(screen.getByRole("checkbox")).toBeInTheDocument(); }); it("Should render checkbox inline and render label", () => { const label: string = "Some label"; - act(() => { - render({label}, container); - }); - expect(container.firstElementChild.classList.contains("inline")).toBeTruthy(); - expect(container.querySelector(".custom-control").classList.contains("custom-control-inline")).toBeTruthy(); - expect(container.querySelector(".custom-control-label")).not.toBeNull(); - expect(container.querySelector(".custom-control-label").innerHTML).toEqual(label); + render({label}); + expect(screen.getByRole("checkbox").closest(".checkbox")).toHaveClass("inline"); + expect(screen.getByRole("checkbox").closest(".custom-control")).toHaveClass("custom-control-inline"); + expect(screen.getByLabelText(label)).toBeInTheDocument(); }); it("Should pass wrapper props when passed", () => { - const className: string = "wrapper-classname"; - act(() => { - render(, container); - }); - expect(container.firstElementChild.classList.contains(className)).toBeTruthy(); + const className: string = "my-custom-checkbox"; + const wrapperClassname: string = "my-custom-wrapper"; + render(); + expect(screen.getByRole("checkbox")).toHaveClass(className); + expect(screen.getByRole("checkbox").closest(".checkbox")).toHaveClass(wrapperClassname); }); it("Should render random id when there is a label and no id passed", () => { const id: string = "some-id"; - act(() => { - render(, container); - }); - expect(container.querySelector("input").id).toEqual(""); + const { rerender }: RenderResult = render(); + expect(screen.getByRole("checkbox").getAttribute("id")).toBeNull(); - act(() => { - render(some label, container); - }); - expect(container.querySelector("input").id).not.toEqual(""); + rerender(some label); + expect(screen.getByRole("checkbox")).toHaveAttribute("id", expect.any(String)); - act(() => { - render(some label, container); - }); - expect(container.querySelector("input").id).toEqual(id); + rerender(some label); + expect(screen.getByRole("checkbox")).toHaveAttribute("id", id); }); }); diff --git a/lib/src/RadioButton/RadioButton.test.tsx b/lib/src/RadioButton/RadioButton.test.tsx index c9b62bbfd..2d5fc1c55 100644 --- a/lib/src/RadioButton/RadioButton.test.tsx +++ b/lib/src/RadioButton/RadioButton.test.tsx @@ -9,11 +9,11 @@ describe("Component: RadioButton", () => { }); it("Should pass custom class and id", () => { - const className: string = "myRadiobuttonClass"; - const wrapperClassname: string = "myWrapperClassname"; - const { container }: RenderResult = render(); + const className: string = "my-custom-radio"; + const wrapperClassname: string = "my-custom-wrapper"; + render(); expect(screen.getByRole("radio")).toHaveClass(className); - expect(container.firstElementChild.classList.contains(wrapperClassname)).toBeTruthy(); + expect(screen.getByRole("radio").closest(".radio-button")).toHaveClass(wrapperClassname); }); it("Should render with random id if id is not passed", () => { From f11debc891c49c498185b6833cd246b9764f8abf Mon Sep 17 00:00:00 2001 From: "Ewe Seong, Yeoh" Date: Fri, 13 May 2022 13:28:23 +0800 Subject: [PATCH 07/18] test(components): migrate chip test cases to react testing libraries --- lib/src/Chip/Chip.test.tsx | 52 +++++++++++++------------------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/lib/src/Chip/Chip.test.tsx b/lib/src/Chip/Chip.test.tsx index bdef6afb4..d5dcffa74 100644 --- a/lib/src/Chip/Chip.test.tsx +++ b/lib/src/Chip/Chip.test.tsx @@ -1,65 +1,47 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import React from "react"; -import { render, unmountComponentAtNode } from "react-dom"; -import { act, Simulate } from "react-dom/test-utils"; import { Chip } from "."; describe("Component: Chip", () => { - let container: HTMLDivElement = null; let onClose: (e: React.MouseEvent) => void; beforeEach(() => { onClose = jest.fn(); - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; }); it("Should render correction", () => { - act(() => { - render(Test, container); - }); - expect(container).toBeDefined(); - expect(container.firstElementChild.classList.contains("rc")).toBeTruthy(); - expect(container.firstElementChild.classList.contains("chip")).toBeTruthy(); - expect(container.firstElementChild.firstElementChild.classList.contains("content")).toBeTruthy(); - expect(container.firstElementChild.lastElementChild.tagName.toLowerCase()).toEqual("button"); - expect(container.querySelector(".content").textContent).toEqual("Test"); + render(Test); + const chip: HTMLElement = screen.getByRole("button"); + expect(chip).toBeInTheDocument(); + expect(chip).toHaveClass("rc", "chip"); + expect(chip.querySelector("button")).toBeInTheDocument(); + expect(screen.getByText("Test")).toBeInTheDocument(); }); - it("Should trigger onClose callback when close button is clicked", () => { - act(() => { - render(Test, container); - }); - act(() => Simulate.click(container.querySelector("button"))); + it("Should trigger onClose callback when close button is clicked", async () => { + render(Test); + await userEvent.click(screen.getByRole("button").querySelector("button")); expect(onClose).toHaveBeenCalled(); }); describe("Keyboard support", () => { function renderChip(): void { - act(() => render(Chip, container)); - container.querySelector(".chip").focus(); - } - - function pressKey(key: string): void { - act(() => Simulate.keyDown(document.activeElement, { key })); + render(Chip); + screen.getByRole("button").focus(); } - it("Should trigger onClose when Backspace button is pressed", () => { + it("Should trigger onClose when Backspace button is pressed", async () => { renderChip(); expect(onClose).not.toHaveBeenCalled(); - pressKey("Backspace"); + await userEvent.keyboard("[Backspace]"); expect(onClose).toHaveBeenCalledTimes(1); }); - it("Should trigger onClose when Delete button is pressed", () => { + it("Should trigger onClose when Delete button is pressed", async () => { renderChip(); expect(onClose).not.toHaveBeenCalled(); - pressKey("Delete"); + await userEvent.keyboard("[Delete]"); expect(onClose).toHaveBeenCalledTimes(1); }); }); From 11a063b4a66b6fffd1659557eb818475c7d2e03c Mon Sep 17 00:00:00 2001 From: "Ewe Seong, Yeoh" Date: Fri, 13 May 2022 13:29:14 +0800 Subject: [PATCH 08/18] test(contexts): migrate translation context test cases to react testing libraries --- lib/src/contexts/translationContext.test.tsx | 126 +++++++++---------- 1 file changed, 58 insertions(+), 68 deletions(-) diff --git a/lib/src/contexts/translationContext.test.tsx b/lib/src/contexts/translationContext.test.tsx index 0fa24ceba..1b4d52096 100644 --- a/lib/src/contexts/translationContext.test.tsx +++ b/lib/src/contexts/translationContext.test.tsx @@ -1,113 +1,103 @@ +import { render, screen, waitFor } from "@testing-library/react"; import fetchMock, { enableFetchMocks } from "jest-fetch-mock"; import React from "react"; -import { render, unmountComponentAtNode } from "react-dom"; -import { act } from "react-dom/test-utils"; import { TranslationProvider, useTranslationContext } from "./translationContext"; enableFetchMocks(); const TestContext: React.FC> = ({ data, translationKey }) => { const { isLoading, t } = useTranslationContext(); - return
{isLoading ? loading... :

{t(translationKey, data) as string}

}
; + return ( +
+ {isLoading ? ( + + loading... + + ) : ( +

+ {t(translationKey, data) as string} +

+ )} +
+ ); }; const MOCK_TRANSLATION_RESULT: any = { result: { content: { title_translation: "title_translation" } } }; describe("context: translationContext", () => { - let container: HTMLDivElement = null; + const consoleErrorSpy = jest.spyOn(console, "error"); + const consoleWarnSpy = jest.spyOn(console, "warn"); beforeEach(() => { fetchMock.resetMocks(); - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; }); it("Should throw error when context used without provider", () => { - expect(() => render(, container)).toThrowError("useTranslationContext must be used within a TranslationProvider"); + consoleErrorSpy.mockImplementation(jest.fn()); + expect(() => render()).toThrowError("useTranslationContext must be used within a TranslationProvider"); + consoleErrorSpy.mockClear(); }); it("Should show loading screen when fetching translation", async () => { fetchMock.mockResponseOnce(JSON.stringify(MOCK_TRANSLATION_RESULT)); - await act(async () => { - render( - - - , - container - ); - expect(container.querySelector(".loading-screen")).toBeDefined(); - }); - expect(container.querySelector(".loading-screen")).toBeNull(); + render( + + + + ); + await screen.findByRole("progressbar"); + await waitFor(() => expect(screen.queryByRole("progressbar")).not.toBeInTheDocument()); }); it("Should get translation when key exist in translations", async () => { fetchMock.mockResponseOnce(JSON.stringify(MOCK_TRANSLATION_RESULT)); - await act(async () => { - render( - - - , - container - ); - }); - expect(container.querySelector(".translated-text").textContent).toEqual("title_translation"); + render( + + + + ); + await screen.findByText("title_translation"); }); it("Should interpolate translation when data exist", async () => { fetchMock.mockResponseOnce(JSON.stringify({ result: { content: { text_hello: "today is {date}" } } })); - await act(async () => { - render( - - - , - container - ); - }); - expect(container.querySelector(".translated-text").textContent).toEqual("today is 2020-01-01"); + render( + + + + ); + await screen.findByText("today is 2020-01-01"); }); it("Should get empty string when translation does not exist", async () => { fetchMock.mockResponseOnce(JSON.stringify({})); - await act(async () => { - render( - - - , - container - ); - }); - expect(container.querySelector(".translated-text").textContent).toEqual(""); + render( + + + + ); + await waitFor(() => expect(screen.queryByTestId("translated-text")).toHaveTextContent("")); }); it("Should map custom translation path when provided", async () => { fetchMock.mockResponseOnce(JSON.stringify({ response: MOCK_TRANSLATION_RESULT })); - await act(async () => { - render( - - - , - container - ); - }); - expect(container.querySelector(".translated-text").textContent).toEqual("title_translation"); + render( + + + + ); + await screen.findByText("title_translation"); }); it("Should get fallback translation when translation fetch fails", async () => { + consoleWarnSpy.mockImplementation(jest.fn()); fetchMock.mockRejectOnce(); - await act(async () => { - render( - - - , - container - ); - }); - expect(container.querySelector(".translated-text").textContent).toEqual("title_fallbacktranslation"); + render( + + + + ); + await screen.findByText("title_fallbacktranslation"); + consoleWarnSpy.mockClear(); }); }); From 798fedf1323f60230ed1eeab66e675839ea44bfe Mon Sep 17 00:00:00 2001 From: "Ewe Seong, Yeoh" Date: Tue, 17 May 2022 14:34:52 +0800 Subject: [PATCH 09/18] test(components): migrate date picker test cases to react testing libraries --- lib/src/Datepicker/Datepicker.test.tsx | 397 ++++++++++--------------- 1 file changed, 149 insertions(+), 248 deletions(-) diff --git a/lib/src/Datepicker/Datepicker.test.tsx b/lib/src/Datepicker/Datepicker.test.tsx index 378d92d97..9d67d77bb 100644 --- a/lib/src/Datepicker/Datepicker.test.tsx +++ b/lib/src/Datepicker/Datepicker.test.tsx @@ -1,345 +1,244 @@ +import { fireEvent, render, RenderResult, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { advanceTo, clear } from "jest-date-mock"; import React from "react"; -import { act, Simulate } from "react-dom/test-utils"; -import { unmountComponentAtNode, render } from "react-dom"; import { Datepicker, DatepickerProps } from "."; -import { advanceTo, clear } from "jest-date-mock"; describe("Component: Datepicker", () => { - let container: HTMLDivElement = null; - + const consoleWarnSpy = jest.spyOn(console, "warn"); const props: DatepickerProps = { value: new Date(), onChange: jest.fn(), + "aria-label": "Date picker", }; + async function changeValue(input: HTMLInputElement, value: string): Promise { + await userEvent.clear(input); + await userEvent.type(input, value); + } + beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); jest.clearAllMocks(); }); - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - }); - it("Should render", () => { - act(() => { - render(, container); - }); - expect(container).toBeDefined(); + render(); + expect(screen.getByLabelText(props["aria-label"])).toBeInTheDocument(); }); it("Should render and pass custom class", () => { - const className: string = "myDatepickerClass"; - act(() => { - render(, container); - }); - expect(container).toBeDefined(); - expect(container.querySelectorAll(`.${className}`).length).toEqual(1); + const className: string = "my-custom-datepicker"; + render(); + expect(screen.getByLabelText(props["aria-label"])).toHaveClass(className); }); it("Should pass any other native html prop", () => { const id: string = "my-id"; + const { rerender }: RenderResult = render(); + expect(screen.getByLabelText(props["aria-label"])).toHaveClass("seb-datepicker-native"); + expect(screen.getByLabelText(props["aria-label"])).toHaveAttribute("id", id); - act(() => { - render(, container); - }); - expect(container).toBeDefined(); - - expect(container.firstElementChild); - expect(container.firstElementChild.classList.contains("seb-datepicker-native")).toBeTruthy(); - expect(container.firstElementChild.id).toEqual(id); - - act(() => { - render(, container); - }); - - const dayPicker: HTMLInputElement = container.querySelector(`div.input-group > input[type="number"]`); - expect(dayPicker).toBeTruthy(); - expect(dayPicker.id).toEqual(id); + rerender(); + expect(screen.getAllByRole("spinbutton")).toHaveLength(2); }); it("Should pass wrapper and select props for custom datepicker and ignore for native picker", () => { - const wrapperProps: DatepickerProps["wrapperProps"] = { className: "test123" }; - const customPickerSelectProps: DatepickerProps["customPickerSelectProps"] = { className: "selectTest123" }; - act(() => { - render(, container); - }); - - const wrapperSelector: string = `div.input-group.${wrapperProps.className}`; - const selectElementSelector: string = `div.input-group.${wrapperProps.className}>select.custom-select.${customPickerSelectProps.className}`; - - expect(container.querySelectorAll(wrapperSelector).length).toBeTruthy(); - expect(container.querySelectorAll(selectElementSelector).length).toBeTruthy(); - - act(() => { - render(, container); - }); - - expect(container.querySelectorAll(wrapperSelector).length).toBeFalsy(); - expect(container.querySelectorAll(selectElementSelector).length).toBeFalsy(); + const wrapperProps: DatepickerProps["wrapperProps"] = { className: "my-custom-wrapper" }; + const customPickerSelectProps: DatepickerProps["customPickerSelectProps"] = { className: "my-custom-picker" }; + const { rerender }: RenderResult = render(); + expect(screen.getByRole("group")).toHaveClass(wrapperProps.className); + expect(screen.getByRole("combobox")).toHaveClass(customPickerSelectProps.className); + + rerender(); + expect(screen.queryByRole("group")).not.toBeInTheDocument(); + expect(screen.queryByRole("combobox")).not.toBeInTheDocument(); }); it("Should fire change event when component value is changed", () => { - act(() => { - render(, container); - }); - - const value = "2010-01-01"; - const inputElement: HTMLInputElement = container.querySelector("input.seb-datepicker-native"); - const event: any = { target: { value } }; - Simulate.change(inputElement, event); + render(); + expect(props.onChange).not.toHaveBeenCalled(); + fireEvent.change(screen.getByLabelText(props["aria-label"]), { target: { value: "2010-01-01" } }); expect(props.onChange).toHaveBeenCalled(); }); - it("Should fire change event with null when component value is out of range and with latest value when in range", () => { + it("Should fire change event with null when component value is out of range and with latest value when in range", async () => { const [min, max]: [Date, Date] = [new Date(props.value.getFullYear() - 10, 1, 1), new Date(props.value.getFullYear() + 10, 1, 1)]; const year: number = props.value.getFullYear(); - act(() => { - render(, container); - }); - const yearElement: HTMLInputElement = container.querySelector("input"); - act(() => { - Simulate.change(yearElement, { target: { value: new Date(year, 1, 1) } } as any); - }); - - expect(props.onChange).toHaveBeenCalledWith(null); - - act(() => { - render(, container); - }); - - act(() => { - Simulate.change(yearElement, { target: { value: new Date(year + 11, 1, 1) } } as any); - }); - - expect(props.onChange).toHaveBeenCalledWith(null); - - act(() => { - render(, container); - }); - - act(() => { - Simulate.change(yearElement, { target: { value: new Date(year, 1, 1) } } as any); - }); - - expect(props.onChange).toHaveBeenCalledWith(null); + const { rerender }: RenderResult = render(); + fireEvent.change(screen.getByLabelText(props["aria-label"]), { target: { value: new Date(year, 1, 1) } }); + expect(props.onChange).not.toHaveBeenCalled(); - act(() => { - render(, container); - }); - - act(() => { - Simulate.change(yearElement, { target: { value: new Date(year, 1, 1) } } as any); - }); - - expect(props.onChange).toHaveBeenCalledWith(null); + rerender(); + fireEvent.change(screen.getByLabelText(props["aria-label"]), { target: { value: new Date(year + 11, 1, 1) } }); + expect(props.onChange).not.toHaveBeenCalled(); - act(() => { - render(, container); - }); + rerender(); + fireEvent.change(screen.getByLabelText(props["aria-label"]), { target: { value: new Date(year, 1, 1) } }); + expect(props.onChange).not.toHaveBeenCalled(); - act(() => { - Simulate.change(yearElement, { target: { value: new Date(year, 1, 1) } } as any); - }); + rerender(); + fireEvent.change(screen.getByLabelText(props["aria-label"]), { target: { value: new Date(year, 1, 1) } }); + expect(props.onChange).not.toHaveBeenCalled(); - expect(props.onChange).toHaveBeenCalled(); + rerender(); + fireEvent.change(screen.getByLabelText(props["aria-label"]), { target: { value: new Date(year, 1, 1) } }); + expect(props.onChange).toHaveBeenCalledTimes(1); const tzoffset: number = new Date().getTimezoneOffset() * 60000; - const expectedDate: string = new Date(Date.now() - tzoffset).toISOString()?.substr(0, 10) || ""; - expect(container.querySelector("input").value).toEqual(expectedDate); - - act(() => { - render(, container); - }); + const expectedDate: string = new Date(Date.now() - tzoffset).toISOString()?.substring(0, 10) || ""; + expect(screen.getByLabelText(props["aria-label"])).toHaveValue(expectedDate); - act(() => { - Simulate.change(container.querySelector("input.seb-datepicker-custom-year"), { target: { value: year } } as any); - }); + jest.clearAllMocks(); + rerender(); + const [dayInput, yearInput]: Array = screen.getAllByRole("spinbutton"); + const monthInput: HTMLSelectElement = screen.getByRole("combobox"); + await changeValue(yearInput, year.toString()); expect(props.onChange).toHaveBeenCalled(); - expect(container.querySelector("input.seb-datepicker-custom-day").value).toEqual(props.value.getDate().toString()); - expect(container.querySelector("select.seb-datepicker-custom-month").value).toEqual(`${props.value.getMonth() + 1}`); - expect(container.querySelector("input.seb-datepicker-custom-year").value).toEqual(props.value.getFullYear().toString()); - }); - - it("should support fallback custom picker", () => { - act(() => { - render(, container); - }); - const dayElement: HTMLInputElement = container.querySelector("input.seb-datepicker-custom-day"); - const dayEvent: any = { target: { value: "27" } }; - act(() => { - Simulate.change(dayElement, dayEvent); - }); + expect(dayInput).toHaveValue(props.value.getDate()); + expect(monthInput).toHaveValue((props.value.getMonth() + 1).toString()); + expect(yearInput).toHaveValue(props.value.getFullYear()); + }); - expect(dayElement.value).toEqual("27"); + it("should support fallback custom picker", async () => { + render(); + const [dayInput, yearInput]: Array = screen.getAllByRole("spinbutton"); + expect(props.onChange).not.toHaveBeenCalled(); + await changeValue(dayInput, "27"); + expect(dayInput).toHaveValue(27); expect(props.onChange).toHaveBeenCalled(); - const monthElement: HTMLSelectElement = container.querySelector("select.seb-datepicker-custom-month"); - const monthEvent: any = { target: { value: "12" } }; - act(() => { - Simulate.change(monthElement, monthEvent); - }); + jest.clearAllMocks(); - expect(monthElement.value).toEqual("12"); + const monthInput: HTMLSelectElement = screen.getByRole("combobox"); + expect(props.onChange).not.toHaveBeenCalled(); + await userEvent.selectOptions(monthInput, "12"); + expect(monthInput).toHaveValue("12"); expect(props.onChange).toHaveBeenCalled(); - const yearElement: HTMLInputElement = container.querySelector("input.seb-datepicker-custom-year"); - const yearEvent: any = { target: { value: "2030" } }; - act(() => { - Simulate.change(yearElement, yearEvent); - }); + jest.clearAllMocks(); - expect(yearElement.value).toEqual("2030"); + expect(props.onChange).not.toHaveBeenCalled(); + await changeValue(yearInput, "2030"); + expect(yearInput).toHaveValue(2030); expect(props.onChange).toHaveBeenCalled(); }); - it("should fire null for invalid input on custom picker", () => { - act(() => { - render(, container); - }); - - const dayElement: HTMLInputElement = container.querySelector("input.seb-datepicker-custom-day"); - const dayEvent: any = { target: { value: "ABC" } }; - act(() => { - Simulate.change(dayElement, dayEvent); - }); - - expect(dayElement.value).toEqual(""); + it("should fire null for invalid input on custom picker", async () => { + render(); + const [dayInput]: Array = screen.getAllByRole("spinbutton"); + expect(props.onChange).not.toHaveBeenCalled(); + await changeValue(dayInput, "ABC"); expect(props.onChange).toHaveBeenCalledWith(null); }); it("Should use default locale when unknown locale code provided", () => { - act(() => { - render(, container); - }); - - const monthElement: HTMLSelectElement = container.querySelector("select.seb-datepicker-custom-month"); - - expect(monthElement.querySelectorAll("option").item(0).innerHTML.toLowerCase()).toBe("month"); + consoleWarnSpy.mockImplementation(jest.fn()); + render(); + expect(screen.getByText("month")).toBeInTheDocument(); + consoleWarnSpy.mockClear(); }); it("Should enable disabled when disabled prop is set to true", () => { - act(() => { - render(, container); - }); - expect(container.firstElementChild.hasAttribute("disabled")).toBe(true); + render(); + expect(screen.getByLabelText(props["aria-label"])).toBeDisabled(); }); it("Should support monthPicker", () => { - act(() => { - render(, container); - }); - const inputElement: HTMLInputElement = container.querySelector("input.seb-datepicker-native"); - expect(inputElement.type).toBe("month"); + const { rerender }: RenderResult = render(); + expect(screen.getByLabelText(props["aria-label"])).toBeInTheDocument(); - act(() => { - render(, container); - }); - const dayElement: HTMLInputElement = container.querySelector("input.seb-datepicker-custom-day"); - const monthElement: HTMLSelectElement = container.querySelector("select.seb-datepicker-custom-month"); - const yearElement: HTMLInputElement = container.querySelector("input.seb-datepicker-custom-year"); - expect(dayElement).toBeFalsy(); - expect(monthElement).toBeTruthy(); - expect(yearElement).toBeTruthy(); + rerender(); + expect(screen.getByRole("combobox")).toHaveClass("seb-datepicker-custom-month"); + expect(screen.getByRole("spinbutton")).toHaveClass("seb-datepicker-custom-year"); }); it("Should ignore timezone info from date and only respect the year, month and day", () => { - const [year, month, day] = [2015, 11, 25]; + const [year, month, day]: Array = [2015, 11, 25]; // mock change system date to 2015-25-11 midnight! advanceTo(new Date(year, month, day, 0, 0, 0)); const value: Date = new Date(); - act(() => { - render(, container); - }); - - expect(container.querySelector("input").value).toEqual(`${year}-${month + 1}-${day}`); + render(); + expect(screen.getByLabelText(props["aria-label"])).toHaveValue(`${year}-${month + 1}-${day}`); // change back to correct system date! clear(); }); describe("Custom picker keyboard support", () => { - function changeValue(element: Element, value: number) { - act(() => Simulate.change(element, { target: { value } } as any)); - } - - function pressKey(element: Element, key: string): void { - act(() => Simulate.keyDown(element, { key })); - } + // function changeValue(element: Element, value: number) { + // act(() => Simulate.change(element, { target: { value } } as any)); + // } function renderCustomDatepicker(customProps?: Partial): void { - act(() => { - render(, container); - }); + render(); } describe("Day picker", () => { function getDayInputElement(): HTMLInputElement { - return container.querySelector(".seb-datepicker-custom-day"); + const [dayInput]: Array = screen.getAllByRole("spinbutton"); + dayInput.focus(); + return dayInput; } beforeEach(() => { renderCustomDatepicker(); }); - it("Should decrease day value when down arrow button is pressed", () => { + it("Should decrease day value when down arrow button is pressed", async () => { const customDayInput: HTMLInputElement = getDayInputElement(); expect(customDayInput.value).toEqual("1"); - pressKey(customDayInput, "ArrowDown"); + await userEvent.keyboard("[ArrowDown]"); expect(customDayInput.value).toEqual("31"); - pressKey(customDayInput, "ArrowDown"); + await userEvent.keyboard("[ArrowDown]"); expect(customDayInput.value).toEqual("30"); }); - it("Should increase day value when up arrow button is pressed", () => { + it("Should increase day value when up arrow button is pressed", async () => { const customDayInput: HTMLInputElement = getDayInputElement(); - changeValue(customDayInput, 31); + await changeValue(customDayInput, "31"); expect(customDayInput.value).toEqual("31"); - pressKey(customDayInput, "ArrowUp"); + await userEvent.keyboard("[ArrowUp]"); expect(customDayInput.value).toEqual("1"); - pressKey(customDayInput, "ArrowUp"); + await userEvent.keyboard("[ArrowUp]"); expect(customDayInput.value).toEqual("2"); }); - it("Should decrease day value by 5 when page down arrow button is pressed", () => { + it("Should decrease day value by 5 when page down arrow button is pressed", async () => { const customDayInput: HTMLInputElement = getDayInputElement(); expect(customDayInput.value).toEqual("1"); - pressKey(customDayInput, "PageDown"); + await userEvent.keyboard("[PageDown]"); expect(customDayInput.value).toEqual("27"); - pressKey(customDayInput, "PageDown"); + await userEvent.keyboard("[PageDown]"); expect(customDayInput.value).toEqual("22"); }); - it("Should increase day value by 5 when page up arrow button is pressed", () => { + it("Should increase day value by 5 when page up arrow button is pressed", async () => { const customDayInput: HTMLInputElement = getDayInputElement(); - changeValue(customDayInput, 31); + await changeValue(customDayInput, "31"); expect(customDayInput.value).toEqual("31"); - pressKey(customDayInput, "PageUp"); + await userEvent.keyboard("[PageUp]"); expect(customDayInput.value).toEqual("5"); - pressKey(customDayInput, "PageUp"); + await userEvent.keyboard("[PageUp]"); expect(customDayInput.value).toEqual("10"); }); - it("Should decrease day value to minimum day value when home button is pressed", () => { + it("Should decrease day value to minimum day value when home button is pressed", async () => { const customDayInput: HTMLInputElement = getDayInputElement(); - changeValue(customDayInput, 15); + await changeValue(customDayInput, "15"); expect(customDayInput.value).toEqual("15"); - pressKey(customDayInput, "Home"); + await userEvent.keyboard("[Home]"); expect(customDayInput.value).toEqual("1"); - pressKey(customDayInput, "Home"); + await userEvent.keyboard("[Home]"); expect(customDayInput.value).toEqual("1"); }); - it("Should increase day value to maximum day value when end button is pressed", () => { + it("Should increase day value to maximum day value when end button is pressed", async () => { const customDayInput: HTMLInputElement = getDayInputElement(); - changeValue(customDayInput, 15); + await changeValue(customDayInput, "15"); expect(customDayInput.value).toEqual("15"); - pressKey(customDayInput, "End"); + await userEvent.keyboard("[End]"); expect(customDayInput.value).toEqual("31"); - pressKey(customDayInput, "End"); + await userEvent.keyboard("[End]"); expect(customDayInput.value).toEqual("31"); }); }); @@ -348,88 +247,90 @@ describe("Component: Datepicker", () => { const CURRENT_YEAR: number = new Date().getFullYear(); function getYearInputElement(): HTMLInputElement { - return container.querySelector(".seb-datepicker-custom-year"); + const [, yearInput]: Array = screen.getAllByRole("spinbutton"); + yearInput.focus(); + return yearInput; } - it("Should decrease year value when down arrow button is pressed", () => { + it("Should decrease year value when down arrow button is pressed", async () => { renderCustomDatepicker(); const customYearInput: HTMLInputElement = getYearInputElement(); expect(customYearInput.value).toEqual("2020"); - pressKey(customYearInput, "ArrowDown"); + await userEvent.keyboard("[ArrowDown]"); expect(customYearInput.value).toEqual("2019"); - pressKey(customYearInput, "ArrowDown"); + await userEvent.keyboard("[ArrowDown]"); expect(customYearInput.value).toEqual("2018"); }); - it("Should increase year value when up arrow button is pressed", () => { + it("Should increase year value when up arrow button is pressed", async () => { renderCustomDatepicker(); const customYearInput: HTMLInputElement = getYearInputElement(); expect(customYearInput.value).toEqual("2020"); - pressKey(customYearInput, "ArrowUp"); + await userEvent.keyboard("[ArrowUp]"); expect(customYearInput.value).toEqual("2021"); - pressKey(customYearInput, "ArrowUp"); + await userEvent.keyboard("[ArrowUp]"); expect(customYearInput.value).toEqual("2022"); }); - it("Should decrease year value by 5 when page down arrow button is pressed", () => { + it("Should decrease year value by 5 when page down arrow button is pressed", async () => { renderCustomDatepicker(); const customYearInput: HTMLInputElement = getYearInputElement(); expect(customYearInput.value).toEqual("2020"); - pressKey(customYearInput, "PageDown"); + await userEvent.keyboard("[PageDown]"); expect(customYearInput.value).toEqual("2015"); - pressKey(customYearInput, "PageDown"); + await userEvent.keyboard("[PageDown]"); expect(customYearInput.value).toEqual("2010"); }); - it("Should increase year value by 5 when page up arrow button is pressed", () => { + it("Should increase year value by 5 when page up arrow button is pressed", async () => { renderCustomDatepicker(); const customYearInput: HTMLInputElement = getYearInputElement(); expect(customYearInput.value).toEqual("2020"); - pressKey(customYearInput, "PageUp"); + await userEvent.keyboard("[PageUp]"); expect(customYearInput.value).toEqual("2025"); - pressKey(customYearInput, "PageUp"); + await userEvent.keyboard("[PageUp]"); expect(customYearInput.value).toEqual("2030"); }); - it("Should decrease year value to default minimum year value when home button is pressed", () => { + it("Should decrease year value to default minimum year value when home button is pressed", async () => { renderCustomDatepicker(); const MIN_YEAR: string = `${CURRENT_YEAR - 200}`; const customYearInput: HTMLInputElement = getYearInputElement(); expect(customYearInput.value).toEqual("2020"); - pressKey(customYearInput, "Home"); + await userEvent.keyboard("[Home]"); expect(customYearInput.value).toEqual(MIN_YEAR); - pressKey(customYearInput, "Home"); + await userEvent.keyboard("[Home]"); expect(customYearInput.value).toEqual(MIN_YEAR); }); - it("Should decrease year value to custom minimum year value when home button is pressed", () => { + it("Should decrease year value to custom minimum year value when home button is pressed", async () => { renderCustomDatepicker({ min: new Date("2000-01-01") }); const customYearInput: HTMLInputElement = getYearInputElement(); expect(customYearInput.value).toEqual("2020"); - pressKey(customYearInput, "Home"); + await userEvent.keyboard("[Home]"); expect(customYearInput.value).toEqual("2000"); - pressKey(customYearInput, "Home"); + await userEvent.keyboard("[Home]"); expect(customYearInput.value).toEqual("2000"); }); - it("Should increase year value to default maximum year value when end button is pressed", () => { + it("Should increase year value to default maximum year value when end button is pressed", async () => { renderCustomDatepicker(); const MAX_YEAR: string = `${CURRENT_YEAR + 200}`; const customYearInput: HTMLInputElement = getYearInputElement(); expect(customYearInput.value).toEqual("2020"); - pressKey(customYearInput, "End"); + await userEvent.keyboard("[End]"); expect(customYearInput.value).toEqual(MAX_YEAR); - pressKey(customYearInput, "End"); + await userEvent.keyboard("[End]"); expect(customYearInput.value).toEqual(MAX_YEAR); }); - it("Should increase year value to custom maximum year value when end button is pressed", () => { + it("Should increase year value to custom maximum year value when end button is pressed", async () => { renderCustomDatepicker({ max: new Date("2050-01-01") }); const customYearInput: HTMLInputElement = getYearInputElement(); expect(customYearInput.value).toEqual("2020"); - pressKey(customYearInput, "End"); + await userEvent.keyboard("[End]"); expect(customYearInput.value).toEqual("2050"); - pressKey(customYearInput, "End"); + await userEvent.keyboard("[End]"); expect(customYearInput.value).toEqual("2050"); }); }); From f0b458caf211e377c4dc7bce37d5d98e76398243 Mon Sep 17 00:00:00 2001 From: "Ewe Seong, Yeoh" Date: Wed, 18 May 2022 10:33:16 +0800 Subject: [PATCH 10/18] test(components): migrate drop down test cases to react testing libraries --- lib/src/Dropdown/CustomDropdownItem.test.tsx | 69 ++- lib/src/Dropdown/Dropdown.test.tsx | 464 ++++++++----------- 2 files changed, 224 insertions(+), 309 deletions(-) diff --git a/lib/src/Dropdown/CustomDropdownItem.test.tsx b/lib/src/Dropdown/CustomDropdownItem.test.tsx index d84d53bb7..8fbb46afb 100644 --- a/lib/src/Dropdown/CustomDropdownItem.test.tsx +++ b/lib/src/Dropdown/CustomDropdownItem.test.tsx @@ -1,58 +1,41 @@ +import { render, screen } from "@testing-library/react"; import React from "react"; -import { render, unmountComponentAtNode } from "react-dom"; -import { act } from "react-dom/test-utils"; import { CustomDropdownItem } from "./CustomDropdownItem"; describe("Component: CustomDropdownItem", () => { - let container: HTMLDivElement = null; - - beforeEach(() => { - container = document.createElement("div"); - document.body.appendChild(container); - }); - - afterEach(() => { - unmountComponentAtNode(container); - container.remove(); - container = null; - }); + const content: string = "my content"; it("Should render correction", () => { - const content: string = "my content"; - act(() => { - render({content}, container); - }); - expect(container.firstElementChild).not.toBeNull(); - expect(container.firstElementChild.classList.contains("custom-control")).toBeTruthy(); - // Should not show up until multiple is set to true - expect(container.firstElementChild.classList.contains("custom-checkbox")).toBeFalsy(); - expect(container.firstElementChild.classList.contains("focused")).toBeFalsy(); - expect(container.querySelector("input")).not.toBeNull(); - expect(container.querySelector("input").type).toEqual("radio"); - expect(container.querySelector("label")).not.toBeNull(); - // Should be set to radio until multiple is set to true - expect(container.querySelector("label").classList.contains("custom-radio")).toBeTruthy(); - expect(container.querySelector("label").textContent).toEqual(content); + render({content}); + // Dropdown item classes + const dropdownItem: HTMLOptionElement = screen.getByRole("option"); + expect(dropdownItem).toHaveClass("custom-control"); + expect(dropdownItem).not.toHaveClass("custom-checkbox", "focused"); + // Input element classes + const inputElement: HTMLInputElement = screen.getByLabelText(content); + expect(inputElement).toBeInTheDocument(); + expect(inputElement).toHaveAttribute("type", "radio"); + expect(inputElement).not.toHaveClass("custom-control-input"); + // Input label classes + const inputLabel: HTMLElement = screen.getByText(content); + expect(inputLabel).toBeInTheDocument(); + expect(inputLabel).toHaveClass("custom-radio"); + expect(inputLabel).not.toHaveClass("custom-control-label"); // Should have a random id - expect(container.querySelector("input").id.length > 0).toBeTruthy(); - expect(container.querySelector("label").htmlFor.length > 0).toBeTruthy(); - expect(container.querySelector("label").htmlFor).toEqual(container.querySelector("input").id); + expect(inputElement).toHaveAttribute("id", expect.any(String)); }); it("Should render correctly with multiple", () => { - act(() => { - render(, container); - }); - expect(container.firstElementChild.classList.contains("custom-checkbox")).toBeTruthy(); - expect(container.querySelector("input").type).toEqual("checkbox"); - expect(container.querySelector("input").classList.contains("custom-control-input")).toBeTruthy(); - expect(container.querySelector("label").classList.contains("custom-control-label")).toBeTruthy(); + render({content}); + expect(screen.getByRole("option")).toHaveClass("custom-checkbox"); + const inputElement: HTMLInputElement = screen.getByLabelText(content); + expect(inputElement).toHaveAttribute("type", "checkbox"); + expect(inputElement).toHaveClass("custom-control-input"); + expect(screen.getByText(content)).toHaveClass("custom-control-label"); }); it("Should render correctly with focused", () => { - act(() => { - render(, container); - }); - expect(container.firstElementChild.classList.contains("focused")).toBeTruthy(); + render(); + expect(screen.getByRole("option")).toHaveClass("focused"); }); }); diff --git a/lib/src/Dropdown/Dropdown.test.tsx b/lib/src/Dropdown/Dropdown.test.tsx index aeb31f6e1..d659ba24b 100644 --- a/lib/src/Dropdown/Dropdown.test.tsx +++ b/lib/src/Dropdown/Dropdown.test.tsx @@ -1,8 +1,9 @@ +import { render, RenderResult, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import React from "react"; -import { render, unmountComponentAtNode } from "react-dom"; -import { act, Simulate } from "react-dom/test-utils"; -import { Dropdown, DropdownProps, DropdownText, getValueOfMultipleSelect } from "."; -import { Key } from "../utils/keyboardHelper"; +import { Dropdown } from "."; +import { Key } from "../utils"; +import { DropdownProps, DropdownText, getValueOfMultipleSelect } from "./Dropdown"; const testOptions: React.ReactElement[] = [