From 1da323e778dd29ff37b4de1fefdd87c1aa5a8734 Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Wed, 15 May 2024 16:40:04 +0200 Subject: [PATCH 01/54] Fix UserDropdown specs --- .../components/CreatePost/CreatePost.spec.tsx | 2 +- .../GuidelineInput/GuidelineInput.spec.tsx | 12 +- .../UserDropdown/UserDropdown.spec.tsx | 145 ++++++------------ src/support/testing/reactSelectHelpers.ts | 4 +- 4 files changed, 60 insertions(+), 103 deletions(-) diff --git a/src/modules/feed/components/CreatePost/CreatePost.spec.tsx b/src/modules/feed/components/CreatePost/CreatePost.spec.tsx index 37c76a41..76026c0c 100644 --- a/src/modules/feed/components/CreatePost/CreatePost.spec.tsx +++ b/src/modules/feed/components/CreatePost/CreatePost.spec.tsx @@ -143,7 +143,7 @@ describe("", () => { description: "Kudos amount", }); openSelect(selectElement); - const options = await getSelectOptions(selectElement); + const options = getSelectOptions(selectElement); const kudoAmountOption = options[index]; kudoAmountOption.click(); }; diff --git a/src/modules/feed/components/GuidelineInput/GuidelineInput.spec.tsx b/src/modules/feed/components/GuidelineInput/GuidelineInput.spec.tsx index aa02c422..4e3f540b 100644 --- a/src/modules/feed/components/GuidelineInput/GuidelineInput.spec.tsx +++ b/src/modules/feed/components/GuidelineInput/GuidelineInput.spec.tsx @@ -81,7 +81,7 @@ describe("", () => { description: "Kudos amount", }); openSelect(selectElement); - const options = await getSelectOptions(selectElement); + const options = getSelectOptions(selectElement); options[0].click(); expect(getSelectedItemsText(selectElement)).toEqual("10"); @@ -134,7 +134,7 @@ describe("", () => { }); openSelect(selectElement); - const options = await getSelectOptions(selectElement); + const options = getSelectOptions(selectElement); expect(options).toHaveLength(3); options[1].click(); expect(getSelectedItemsText(selectElement)).toEqual("11"); @@ -143,7 +143,7 @@ describe("", () => { const updatedSelectElement = await screen.findByRole("combobox"); openSelect(updatedSelectElement); - const updatedOptions = await getSelectOptions(updatedSelectElement); + const updatedOptions = getSelectOptions(updatedSelectElement); // Only shows 2 rows because 14 is within range of 10 expect(updatedOptions).toHaveLength(2); }); @@ -160,12 +160,12 @@ describe("", () => { const selectElement = await screen.findByRole("combobox", { description: "Kudos amount", }); - const options = await getSelectOptions(selectElement); + const options = getSelectOptions(selectElement); expect(options).toHaveLength(0); openSelect(selectElement); - const updatedOptions = await getSelectOptions(selectElement); + const updatedOptions = getSelectOptions(selectElement); expect(updatedOptions).toHaveLength(2); }); @@ -239,7 +239,7 @@ describe("", () => { description: "Kudos amount", }); openSelect(selectElement); - const options = await getSelectOptions(selectElement); + const options = getSelectOptions(selectElement); options[1].click(); expect(handleChangeMock).toBeCalledWith(15); diff --git a/src/modules/feed/components/UserDropdown/UserDropdown.spec.tsx b/src/modules/feed/components/UserDropdown/UserDropdown.spec.tsx index 16c0fd09..a1abde97 100644 --- a/src/modules/feed/components/UserDropdown/UserDropdown.spec.tsx +++ b/src/modules/feed/components/UserDropdown/UserDropdown.spec.tsx @@ -1,13 +1,12 @@ -import { mount, ReactWrapper } from "enzyme"; -import { act } from "react-dom/test-utils"; -import { - findByTestId, - mockLocalstorage, - wait, - withMockedProviders, -} from "../../../../spec_helper"; +import { mockLocalstorage, withMockedProviders } from "../../../../spec_helper"; import DropdownRemote from "./UserDropdown"; import { GET_USERS } from "../../queries"; +import { render, screen, waitFor } from "@testing-library/react"; +import { + getSelectOptions, + openSelect, +} from "../../../../support/testing/reactSelectHelpers"; +import userEvent from "@testing-library/user-event"; export const mocksWithData = (teamId: string) => [ { @@ -32,7 +31,7 @@ export const mocksWithData = (teamId: string) => [ virtualUser: false, }, { - id: "3", + id: "wrong-id-3", name: "Kabisa", virtualUser: true, }, @@ -53,15 +52,17 @@ const mocksWithError = [ }, ]; -const mocksWithoutData = [ +const mocksWithoutData = (teamId: string) => [ { request: { query: GET_USERS, - variables: { team_id: "1" }, + variables: { team_id: teamId }, }, result: { data: { teamById: { + id: teamId, + __typename: "Team", users: [], }, }, @@ -69,11 +70,10 @@ const mocksWithoutData = [ }, ]; -let wrapper: ReactWrapper; const handleChangeMock = jest.fn(); const setup = (mocks: any) => { - wrapper = mount( + render( withMockedProviders( , mocks, @@ -81,110 +81,67 @@ const setup = (mocks: any) => { ); }; -describe.skip("", () => { +describe("", () => { beforeEach(() => { mockLocalstorage("1"); - setup(mocksWithData("1")); }); it("shows when the users are loading", async () => { - expect( - findByTestId(wrapper, "user-dropdown").hostNodes().hasClass("loading"), - ).toBe(true); + setup(mocksWithData("1")); + const input = screen.getByRole("combobox", { + description: "Receivers", + hidden: true, + }); + expect(input).toBeDisabled(); + openSelect(input); + + // Waiting for loading to finish before unmount + await screen.findByRole("combobox", { + description: "Receivers", + }); }); - it("shows when there is an error", async () => { + it.skip("shows when there is an error", async () => { setup(mocksWithError); - - await act(async () => { - await wait(0); - await wrapper.update(); - - expect( - findByTestId(wrapper, "user-dropdown").hostNodes().hasClass("error"), - ).toBe(true); + const input = screen.getByRole("combobox", { + description: "Receivers", + hidden: true, }); + expect(input).toBeDisabled(); }); it("shows when there are no users", async () => { - setup(mocksWithoutData); - - await act(async () => { - await wait(0); - await wrapper.update(); - - expect( - findByTestId(wrapper, "user-dropdown").find("div.item").length, - ).toBe(0); + setup(mocksWithoutData("1")); + const input = await screen.findByRole("combobox", { + description: "Receivers", }); + openSelect(input); + const options = getSelectOptions(input); + expect(options).toHaveLength(0); }); it("creates an option for each user", async () => { - await act(async () => { - await wait(0); - await wrapper.update(); - - expect( - findByTestId(wrapper, "user-dropdown").find("div.item").length, - ).toBe(3); + setup(mocksWithData("1")); + const input = await screen.findByRole("combobox", { + description: "Receivers", }); + openSelect(input); + const options = getSelectOptions(input); + expect(options).toHaveLength(3); }); it("handles change correctly", async () => { - const component: any = wrapper.find("Dropdown").instance(); - - await act(async () => { - expect(component.state.value).toStrictEqual([]); - - await wait(0); - await wrapper.update(); - - // @ts-ignore - wrapper.find("Dropdown").prop("onChange")(undefined, { value: ["1"] }); - - await wrapper.update(); - - expect(component.state.value).toStrictEqual(["1"]); - }); - }); - - it("handles change correctly when an id is not a number", async () => { - const component: any = wrapper.find("Dropdown").instance(); - - await act(async () => { - expect(component.state.value).toStrictEqual([]); - - await wait(0); - await wrapper.update(); - - // @ts-ignore - wrapper.find("Dropdown").prop("onChange")(undefined, { - value: ["NotAValidNumber"], - }); - - await wrapper.update(); - - expect(component.state.value).toStrictEqual([]); + setup(mocksWithData("1")); + const input = await screen.findByRole("combobox", { + description: "Receivers", }); - }); - - it("handles change correctly when there is no value", async () => { - const component: any = wrapper.find("Dropdown").instance(); - - await act(async () => { - expect(component.state.value).toStrictEqual([]); - - await wait(0); - await wrapper.update(); - - // @ts-ignore - wrapper.find("Dropdown").prop("onChange")(undefined, { - value: undefined, - }); + openSelect(input); + const options = getSelectOptions(input); - await wrapper.update(); + userEvent.click(options[1]); - expect(component.state.value).toStrictEqual([]); + await waitFor(() => { + expect(handleChangeMock).toBeCalledWith([{ label: "Egon", value: "2" }]); }); }); }); diff --git a/src/support/testing/reactSelectHelpers.ts b/src/support/testing/reactSelectHelpers.ts index 654f4d49..25309897 100644 --- a/src/support/testing/reactSelectHelpers.ts +++ b/src/support/testing/reactSelectHelpers.ts @@ -15,10 +15,10 @@ export const openSelect = async (selectElement: HTMLElement) => { * Helper to retrieve all selection options of a 'react-select' element * This allows for selecting an option by its index instead of a name. */ -export const getSelectOptions = async (selectElement: HTMLElement) => { +export const getSelectOptions = (selectElement: HTMLElement): HTMLElement[] => { const selectId = selectElement.getAttribute("id"); const selectPrefix = selectId?.slice(0, -6); - const options = []; + const options: HTMLElement[] = []; const domArea = getDomAreaOfSelect(selectElement); let found = true; From c1f777f6a5d040a40dbadc9ad62b39747c8a69ba Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Fri, 17 May 2024 11:47:44 +0200 Subject: [PATCH 02/54] Migrate test for FinishPasswordPage --- .../login/FinishForgotPasswordPage.spec.tsx | 190 +++++++++--------- .../login/FinishForgotPasswordPage.tsx | 2 +- src/modules/login/index.ts | 5 +- 3 files changed, 97 insertions(+), 100 deletions(-) diff --git a/src/modules/login/FinishForgotPasswordPage.spec.tsx b/src/modules/login/FinishForgotPasswordPage.spec.tsx index c260afdb..46dd5a9b 100644 --- a/src/modules/login/FinishForgotPasswordPage.spec.tsx +++ b/src/modules/login/FinishForgotPasswordPage.spec.tsx @@ -1,14 +1,15 @@ -import { mount, ReactWrapper } from "enzyme"; -import { act } from "react-dom/test-utils"; import { GraphQLError } from "graphql"; -import { - findByTestId, - simulateInputChange, - wait, - withMockedProviders, -} from "../../spec_helper"; +import { withMockedProviders } from "../../spec_helper"; import { MUTATION_NEW_PASSWORD } from "./FinishForgotPasswordPage"; -import { FinishForgotPasswordPage } from "./index"; +import { RouterBypassFinishForgotPasswordPage as FinishForgotPasswordPage } from "./index"; +import { + RenderResult, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; +import { createBrowserHistory } from "history"; let mutationCalled = false; const mocks = [ @@ -16,9 +17,9 @@ const mocks = [ request: { query: MUTATION_NEW_PASSWORD, variables: { - reset_password_token: "1", - password: "password", - password_confirmation: "password", + reset_password_token: "19810531", + password: "L0r3m1psum!", + password_confirmation: "L0r3m1psum!", }, }, result: () => { @@ -40,7 +41,7 @@ const mocksWithError = [ request: { query: MUTATION_NEW_PASSWORD, variables: { - reset_password_token: "1", + reset_password_token: "90210", password: "password", password_confirmation: "password", }, @@ -51,133 +52,126 @@ const mocksWithError = [ }, ]; -describe.skip("", () => { - let wrapper: ReactWrapper; +describe("", () => { + const createPropsWithToken = (token: string) => ({ + location: { + search: `reset_password_token=${token}`, + pathname: "", + state: "", + hash: "", + }, + history: createBrowserHistory(), + match: { + params: "", + isExact: false, + path: "", + url: "", + }, + }); + + let renderResult: RenderResult; beforeEach(() => { - const props = { - reset_password_token: "1", - }; + const props = createPropsWithToken("19810531"); mutationCalled = false; - wrapper = mount( + renderResult = render( withMockedProviders(, mocks), ); }); - it("handles input correctly", async () => { - const component: any = wrapper.find("FinishForgotPasswordPage").instance(); - - await act(async () => { - expect(component.state.password).toBe(""); + it("Displays a reset password header", () => { + screen.getByRole("heading", { name: "Reset password" }); + }); - simulateInputChange(wrapper, "password-input", "password", "password"); + it("handles input correctly", async () => { + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "L0r3m1psum!" } }); - await wrapper.update(); + const passwordConfirmField = screen.getByLabelText("Confirm password"); + fireEvent.change(passwordConfirmField, { + target: { value: "L0r3m1psum!" }, + }); - expect(component.state.password).toBe("password"); + const submitButton = screen.getByRole("button"); + submitButton.click(); + waitFor(() => { + expect(mutationCalled).toBe(true); }); }); it("shows a message if the password field is empty", async () => { - const component: any = wrapper.find("FinishForgotPasswordPage").instance(); + const submitButton = screen.getByRole("button"); + submitButton.click(); - await act(async () => { - expect(component.state.password).toBe(""); - - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); - - await wait(0); - await wrapper.update(); - - expect( - wrapper.containsMatchingElement(

Fields can't be empty.

), - ).toBe(true); + const heading = screen.getByRole("heading", { + name: "Unable to reset password", }); + expect(heading).toBeInTheDocument(); + const message = screen.getByText("Fields can't be empty."); + expect(message).toBeInTheDocument(); }); it("shows a message if the confirm password field is empty", async () => { - const component = wrapper.find("FinishForgotPasswordPage").instance(); - - await act(async () => { - component.setState({ password: "password" }); + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "L0r3m1psum!" } }); - await wrapper.update(); + const submitButton = screen.getByRole("button"); + submitButton.click(); - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); - - await wait(0); - await wrapper.update(); - - expect( - wrapper.containsMatchingElement(

Fields can't be empty.

), - ).toBe(true); + const heading = screen.getByRole("heading", { + name: "Unable to reset password", }); - }); - - it("shows a message if the passwords arent the same", async () => { - const component = wrapper.find("FinishForgotPasswordPage").instance(); - - await act(async () => { - component.setState({ - password: "password", - passwordConfirm: "otherPassword", - }); - - await wrapper.update(); - - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); - - await wait(0); - await wrapper.update(); + expect(heading).toBeInTheDocument(); - expect( - wrapper.containsMatchingElement(

Passwords don't match.

), - ).toBe(true); - }); + const message = screen.getByText("Fields can't be empty."); + expect(message).toBeInTheDocument(); }); - it("calls the mutation", async () => { - const component = wrapper.find("FinishForgotPasswordPage").instance(); - - await act(async () => { - component.setState({ password: "password", passwordConfirm: "password" }); + it("shows a message if the passwords are not the same", async () => { + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "L0r3m1psum!" } }); - await wrapper.update(); - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); + const passwordConfirmField = screen.getByLabelText("Confirm password"); + fireEvent.change(passwordConfirmField, { + target: { value: "Some other value" }, + }); - await wait(0); - await wrapper.update(); + const submitButton = screen.getByRole("button"); + submitButton.click(); - expect(mutationCalled).toBe(true); + const heading = screen.getByRole("heading", { + name: "Unable to reset password", }); + expect(heading).toBeInTheDocument(); + + const message = screen.getByText("Passwords don't match."); + expect(message).toBeInTheDocument(); }); it("shows when there is an error", async () => { - const props = { - reset_password_token: "1", - }; + renderResult.unmount(); - wrapper = mount( + const props = createPropsWithToken("90210"); + render( withMockedProviders( , mocksWithError, ), ); - const component = wrapper.find("FinishForgotPasswordPage").instance(); - - await act(async () => { - component.setState({ password: "password", passwordConfirm: "password" }); - await wrapper.update(); - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "password" } }); - await wait(0); - await wrapper.update(); + const passwordConfirmField = screen.getByLabelText("Confirm password"); + fireEvent.change(passwordConfirmField, { + target: { value: "password" }, + }); - expect(findByTestId(wrapper, "error-message").find("p").text()).toBe( - "It broke", - ); + const submitButton = screen.getByRole("button"); + submitButton.click(); + waitFor(() => { + expect(mutationCalled).toBe(true); }); }); }); diff --git a/src/modules/login/FinishForgotPasswordPage.tsx b/src/modules/login/FinishForgotPasswordPage.tsx index 26692dcf..363bc202 100644 --- a/src/modules/login/FinishForgotPasswordPage.tsx +++ b/src/modules/login/FinishForgotPasswordPage.tsx @@ -61,7 +61,7 @@ export interface State { error: string; } -class FinishForgotPasswordPage extends Component { +export class FinishForgotPasswordPage extends Component { token: string; constructor(props: Props) { diff --git a/src/modules/login/index.ts b/src/modules/login/index.ts index db3104e0..4e1d14e9 100644 --- a/src/modules/login/index.ts +++ b/src/modules/login/index.ts @@ -1,4 +1,7 @@ export { default as LoginPage } from "./LoginPage"; export { default as RegisterPage } from "./RegisterPage"; export { default as ForgotPasswordPage } from "./ForgotPasswordPage"; -export { default as FinishForgotPasswordPage } from "./FinishForgotPasswordPage"; +export { + default as FinishForgotPasswordPage, + FinishForgotPasswordPage as RouterBypassFinishForgotPasswordPage, +} from "./FinishForgotPasswordPage"; From 42c1153d27ee1db3d8833c77da81da3f76869476 Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Fri, 17 May 2024 12:00:14 +0200 Subject: [PATCH 03/54] Migrate Forgot Password page --- .../login/FinishForgotPasswordPage.spec.tsx | 9 +- src/modules/login/ForgotPasswordPage.spec.tsx | 125 ++++++------------ 2 files changed, 44 insertions(+), 90 deletions(-) diff --git a/src/modules/login/FinishForgotPasswordPage.spec.tsx b/src/modules/login/FinishForgotPasswordPage.spec.tsx index 46dd5a9b..011c63c3 100644 --- a/src/modules/login/FinishForgotPasswordPage.spec.tsx +++ b/src/modules/login/FinishForgotPasswordPage.spec.tsx @@ -35,8 +35,6 @@ const mocks = [ }; }, }, -]; -const mocksWithError = [ { request: { query: MUTATION_NEW_PASSWORD, @@ -153,12 +151,7 @@ describe("", () => { renderResult.unmount(); const props = createPropsWithToken("90210"); - render( - withMockedProviders( - , - mocksWithError, - ), - ); + render(withMockedProviders(, mocks)); const passwordField = screen.getByLabelText("Password"); fireEvent.change(passwordField, { target: { value: "password" } }); diff --git a/src/modules/login/ForgotPasswordPage.spec.tsx b/src/modules/login/ForgotPasswordPage.spec.tsx index 9561676e..8776cbf6 100644 --- a/src/modules/login/ForgotPasswordPage.spec.tsx +++ b/src/modules/login/ForgotPasswordPage.spec.tsx @@ -1,14 +1,8 @@ -import { mount, ReactWrapper } from "enzyme"; -import { act } from "react-dom/test-utils"; import { GraphQLError } from "graphql"; -import { - findByTestId, - simulateInputChange, - wait, - withMockedProviders, -} from "../../spec_helper"; +import { withMockedProviders } from "../../spec_helper"; import { ForgotPasswordPage } from "./index"; import { MUTATION_FORGOT_PASSWORD } from "./ForgotPasswordPage"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; let mutationCalled = false; const mocks = [ @@ -30,14 +24,11 @@ const mocks = [ }; }, }, -]; - -const mocksWithErrors = [ { request: { query: MUTATION_FORGOT_PASSWORD, variables: { - email: "max@example.com", + email: "broken@example.com", }, }, result: { @@ -46,97 +37,67 @@ const mocksWithErrors = [ }, ]; -describe.skip("", () => { - let wrapper: ReactWrapper; - +describe("", () => { beforeEach(() => { mutationCalled = false; - wrapper = mount(withMockedProviders(, mocks)); + render(withMockedProviders(, mocks)); }); - it("handles input correctly", async () => { - const component: any = wrapper.find("ForgotPasswordPage").instance(); - - await act(async () => { - expect(component.state.email).toBe(""); - - simulateInputChange(wrapper, "email-input", "email", "max@example.com"); - - await wrapper.update(); - - expect(component.state.email).toBe("max@example.com"); - }); + it("displays a header for the page", async () => { + const header = screen.getByRole("heading", { name: "Forgot password" }); + expect(header).toBeInTheDocument(); }); - it("shows a message if the email in invalid", async () => { - const component: any = wrapper.find("ForgotPasswordPage").instance(); - - await act(async () => { - component.setState({ email: "invalidEmail" }); - - await wrapper.update(); - - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); - - await wrapper.update(); - - expect(component.state.error).toBe("Invalid email address"); - expect( - wrapper.containsMatchingElement(

Invalid email address

), - ).toBe(true); + it("handles input correctly", async () => { + const inputField = screen.getByPlaceholderText("E-mail address"); + fireEvent.change(inputField, { + target: { value: "max@example.com" }, }); - }); - - it("calls the mutation", async () => { - const component = wrapper.find("ForgotPasswordPage").instance(); - - await act(async () => { - component.setState({ email: "max@example.com" }); - await wrapper.update(); - - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); - - await wait(0); - await wrapper.update(); + const submit = screen.getByRole("button", { name: "Reset password" }); + submit.click(); + await waitFor(() => { expect(mutationCalled).toBe(true); }); - }); - it("shows a message is the mutation is successful", async () => { - const component = wrapper.find("ForgotPasswordPage").instance(); + const result = await screen.findByText("Reset password instructions sent"); + expect(result).toBeInTheDocument(); + }); - await act(async () => { - component.setState({ success: true }); + it("shows a message if the email in invalid", async () => { + const inputField = screen.getByPlaceholderText("E-mail address"); + fireEvent.change(inputField, { + target: { value: "invalidEmail" }, + }); - await wrapper.update(); + const submit = screen.getByRole("button", { name: "Reset password" }); + submit.click(); - expect( - wrapper.containsMatchingElement( -

Reset password instructions sent

, - ), - ); + const header = await screen.findByRole("heading", { + name: "Unable to reset the password", }); + expect(header).toBeInTheDocument(); + + const result = await screen.findByText("Invalid email address"); + expect(result).toBeInTheDocument(); }); it("shows when there is an error", async () => { - wrapper = mount( - withMockedProviders(, mocksWithErrors), - ); - const component = wrapper.find("ForgotPasswordPage").instance(); - - await act(async () => { - component.setState({ email: "max@example.com" }); - - await wrapper.update(); - - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); + const inputField = screen.getByPlaceholderText("E-mail address"); + fireEvent.change(inputField, { + target: { value: "broken@example.com" }, + }); - await wait(0); - await wrapper.update(); + const submit = screen.getByRole("button", { name: "Reset password" }); + submit.click(); - expect(findByTestId(wrapper, "error-message").text()).toBe("It broke"); + const header = await screen.findByRole("heading", { + name: "Unable to reset the password", }); + expect(header).toBeInTheDocument(); + + const result = await screen.findByText("It broke"); + expect(result).toBeInTheDocument(); }); }); From c91e421f9b50ceae6b7b8e56493413412cf1a988 Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Fri, 17 May 2024 12:44:11 +0200 Subject: [PATCH 04/54] Fix links in login page and fix tests --- src/components/Link.tsx | 18 ++++ src/modules/login/LoginPage.spec.tsx | 146 +++++++++++---------------- src/modules/login/LoginPage.tsx | 24 +++-- src/modules/login/helper.spec.tsx | 2 +- 4 files changed, 92 insertions(+), 98 deletions(-) create mode 100644 src/components/Link.tsx diff --git a/src/components/Link.tsx b/src/components/Link.tsx new file mode 100644 index 00000000..db9a3dc6 --- /dev/null +++ b/src/components/Link.tsx @@ -0,0 +1,18 @@ +import { ComponentProps } from "react"; +import { Link as RouterLink } from "react-router-dom"; +import styles from "@kabisa/ui-components/src/atoms/Link/index.module.css"; + +export type Props = ComponentProps & { + theme: "light" | "dark"; +}; + +export const Link: React.FC = ({ theme, children, ...props }: Props) => { + return ( + + {children} + + ); +}; diff --git a/src/modules/login/LoginPage.spec.tsx b/src/modules/login/LoginPage.spec.tsx index 7e12bbe4..05bc8d43 100644 --- a/src/modules/login/LoginPage.spec.tsx +++ b/src/modules/login/LoginPage.spec.tsx @@ -1,14 +1,8 @@ -import { mount, ReactWrapper } from "enzyme"; -import { act } from "react-dom/test-utils"; import { GraphQLError } from "graphql"; import { LoginPage } from "./index"; -import { - findByTestId, - simulateInputChange, - wait, - withMockedProviders, -} from "../../spec_helper"; +import { withMockedProviders } from "../../spec_helper"; import { MUTATION_LOGIN } from "./LoginPage"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; let mutationCalled = false; const mocks = [ @@ -36,14 +30,11 @@ const mocks = [ }; }, }, -]; - -const mocksWithError = [ { request: { query: MUTATION_LOGIN, variables: { - email: "max@example.com", + email: "min@example.com", password: "password", }, }, @@ -53,119 +44,102 @@ const mocksWithError = [ }, ]; -describe.skip("", () => { - let wrapper: ReactWrapper; - +describe("", () => { beforeEach(() => { mutationCalled = false; - wrapper = mount(withMockedProviders(, mocks)); + render(withMockedProviders(, mocks)); }); - it("has a password forgot button", () => { - expect(findByTestId(wrapper, "forgot-button").hostNodes().length).toBe(1); + it("has a forgot password button", () => { + const forgotPassword = screen.getByRole("link", { + name: "Forgot password?", + }); + expect(forgotPassword).toBeInTheDocument(); }); it("has a register button", () => { - expect(findByTestId(wrapper, "sign-up-button").hostNodes().length).toBe(1); + const signUp = screen.getByRole("link", { + name: "Sign Up", + }); + expect(signUp).toBeInTheDocument(); }); it("handles input correctly", async () => { - const component: any = wrapper.find("LoginPage").instance(); - - await act(async () => { - expect(component.state.email).toBe(""); - expect(component.state.password).toBe(""); + const emailField = screen.getByLabelText("Email"); + fireEvent.change(emailField, { target: { value: "max@example.com" } }); - simulateInputChange(wrapper, "email-input", "email", "max@example.com"); - simulateInputChange(wrapper, "password-input", "password", "password"); + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "password" } }); - await wrapper.update(); + const submitButton = screen.getByRole("button", { name: "Login" }); + submitButton.click(); - expect(component.state.email).toBe("max@example.com"); - expect(component.state.password).toBe("password"); - }); + await waitFor(() => expect(mutationCalled).toBe(true)); }); it("shows a message if the email is empty", async () => { - const component: any = wrapper.find("LoginPage").instance(); + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "password" } }); - await act(async () => { - component.setState({ email: "" }); - await wrapper.update(); + const submitButton = screen.getByRole("button", { name: "Login" }); + submitButton.click(); - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); + const header = screen.getByRole("heading", { name: "Unable to login" }); + expect(header).toBeInTheDocument(); - await wrapper.update(); - - expect(component.state.error).toBe("You need to fill all fields."); - expect( - wrapper.containsMatchingElement(

You need to fill all fields.

), - ).toBe(true); - }); + const message = screen.getByText("You need to fill all fields."); + expect(message).toBeInTheDocument(); }); it("shows a message if the password is empty", async () => { - const component: any = wrapper.find("LoginPage").instance(); + const emailField = screen.getByLabelText("Email"); + fireEvent.change(emailField, { target: { value: "max@example.com" } }); - await act(async () => { - component.setState({ email: "max@example.com", password: "" }); - await wrapper.update(); + const submitButton = screen.getByRole("button", { name: "Login" }); + submitButton.click(); - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); + const header = screen.getByRole("heading", { name: "Unable to login" }); + expect(header).toBeInTheDocument(); - await wrapper.update(); - - expect(component.state.error).toBe("You need to fill all fields."); - expect( - wrapper.containsMatchingElement(

You need to fill all fields.

), - ).toBe(true); - }); + const message = screen.getByText("You need to fill all fields."); + expect(message).toBeInTheDocument(); }); it("shows when there is an error message", async () => { - wrapper = mount(withMockedProviders(, mocksWithError)); - const component = wrapper.find("LoginPage").instance(); - await act(async () => { - component.setState({ email: "max@example.com", password: "password" }); - await wrapper.update(); + const emailField = screen.getByLabelText("Email"); + fireEvent.change(emailField, { target: { value: "min@example.com" } }); - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "password" } }); - await wait(0); - await wrapper.update(); + const submitButton = screen.getByRole("button", { name: "Login" }); + submitButton.click(); - expect(findByTestId(wrapper, "error-message").text()).toBe("It broke"); + const header = await screen.findByRole("heading", { + name: "Unable to login", }); + expect(header).toBeInTheDocument(); + + const message = screen.getByText("It broke"); + expect(message).toBeInTheDocument(); }); it("shows a message if the email is invalid", async () => { - const component: any = wrapper.find("LoginPage").instance(); + const emailField = screen.getByLabelText("Email"); + fireEvent.change(emailField, { target: { value: "invalidEmail" } }); - await act(async () => { - component.setState({ email: "invalidEmail", password: "password" }); - await wrapper.update(); + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "password" } }); - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); + const submitButton = screen.getByRole("button", { name: "Login" }); + submitButton.click(); - await wrapper.update(); - - expect(component.state.error).toBe("Invalid email."); - expect(wrapper.containsMatchingElement(

Invalid email.

)).toBe(true); + const header = await screen.findByRole("heading", { + name: "Unable to login", }); - }); + expect(header).toBeInTheDocument(); - it("calls the mutation", async () => { - const component = wrapper.find("LoginPage").instance(); - await act(async () => { - component.setState({ email: "max@example.com", password: "password" }); - await wrapper.update(); - - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); - - await wait(0); - await wrapper.update(); - - expect(mutationCalled).toBe(true); - }); + const message = screen.getByText("Invalid email."); + expect(message).toBeInTheDocument(); }); }); diff --git a/src/modules/login/LoginPage.tsx b/src/modules/login/LoginPage.tsx index 9ce2b187..7a9dfa5d 100644 --- a/src/modules/login/LoginPage.tsx +++ b/src/modules/login/LoginPage.tsx @@ -1,5 +1,5 @@ import { Component, FormEvent } from "react"; -import { Link, withRouter } from "react-router-dom"; +import { withRouter } from "react-router-dom"; import { History } from "history"; import { Mutation } from "@apollo/client/react/components"; import { ApolloError, gql } from "@apollo/client"; @@ -20,15 +20,11 @@ import { FormWrapper } from "../../components"; import { loginSuccess } from "./helper"; import s from "./LoginPage.module.css"; -import { - Button, - Input, - Link as KabisaLink, - Label, -} from "@kabisa/ui-components"; +import { Button, Input, Label } from "@kabisa/ui-components"; import Segment from "../../components/atoms/Segment"; import BasePage from "./BasePage"; -import MessageBox from '../../ui/MessageBox'; +import MessageBox from "../../ui/MessageBox"; +import { Link } from "../../components/Link"; export const MUTATION_LOGIN = gql` mutation SignInUser($email: EmailAddress!, $password: String!) { @@ -182,7 +178,11 @@ class LoginPage extends Component { {displayError && ( - + )}
@@ -190,15 +190,17 @@ class LoginPage extends Component { data-testid="sign-up-button" to={PATH_REGISTER} className={s.left} + theme="dark" > - Sign Up + Sign Up - Forgot password? + Forgot password?
diff --git a/src/modules/login/helper.spec.tsx b/src/modules/login/helper.spec.tsx index e6110649..738c3a2e 100644 --- a/src/modules/login/helper.spec.tsx +++ b/src/modules/login/helper.spec.tsx @@ -1,7 +1,7 @@ import { loginSuccess, LoginSuccessParams } from "./helper"; import { Storage } from "../../support/storage"; -describe.skip("login helper", () => { +describe("login helper", () => { it("calls localstorage", () => { Storage.setItem = jest.fn(); From ce3e424b2a4476817478e11d70e61b31ecc8ed5a Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Fri, 17 May 2024 14:27:44 +0200 Subject: [PATCH 05/54] Migrate RegisterPage to RTL --- src/modules/login/RegisterPage.spec.tsx | 206 +++++++++--------------- 1 file changed, 77 insertions(+), 129 deletions(-) diff --git a/src/modules/login/RegisterPage.spec.tsx b/src/modules/login/RegisterPage.spec.tsx index 7bddc945..1df6450c 100644 --- a/src/modules/login/RegisterPage.spec.tsx +++ b/src/modules/login/RegisterPage.spec.tsx @@ -1,14 +1,8 @@ -import { mount, ReactWrapper } from "enzyme"; -import { act } from "react-dom/test-utils"; import { GraphQLError } from "graphql"; -import { - findByTestId, - simulateInputChange, - wait, - withMockedProviders, -} from "../../spec_helper"; +import { withMockedProviders } from "../../spec_helper"; import { RegisterPage } from "./index"; import { MUTATION_REGISTER } from "./RegisterPage"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; let mutationCalled = false; const mocks = [ @@ -37,14 +31,12 @@ const mocks = [ }; }, }, -]; -const mocksWithError = [ { request: { query: MUTATION_REGISTER, variables: { - name: "Max", - email: "max@example.com", + name: "Min", + email: "min@example.com", password: "password", }, }, @@ -54,161 +46,117 @@ const mocksWithError = [ }, ]; -describe.skip("", () => { - let wrapper: ReactWrapper; - +describe("", () => { beforeEach(() => { mutationCalled = false; - wrapper = mount(withMockedProviders(, mocks)); + render(withMockedProviders(, mocks)); }); it("handles input correctly", async () => { - const component: any = wrapper.find("RegisterPage").instance(); + const nameField = screen.getByLabelText("Name"); + fireEvent.change(nameField, { target: { value: "Max" } }); - await act(async () => { - expect(component.state.name).toBe(""); - expect(component.state.email).toBe(""); - expect(component.state.password).toBe(""); + const emailField = screen.getByLabelText("Email"); + fireEvent.change(emailField, { target: { value: "max@example.com" } }); - simulateInputChange(wrapper, "name-input", "name", "Max"); - simulateInputChange(wrapper, "email-input", "email", "max@example.com"); - simulateInputChange(wrapper, "password-input", "password", "password"); + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "password" } }); - await wrapper.update(); - expect(component.state.name).toBe("Max"); - expect(component.state.email).toBe("max@example.com"); - expect(component.state.password).toBe("password"); - }); + const submitButton = screen.getByRole("button", { name: "Register" }); + submitButton.click(); + + await waitFor(() => expect(mutationCalled).toBe(true)); }); it("shows a message if the name is empty", async () => { - const component: any = wrapper.find("RegisterPage").instance(); + const emailField = screen.getByLabelText("Email"); + fireEvent.change(emailField, { target: { value: "max@example.com" } }); - await act(async () => { - component.setState({ name: "", email: "email", password: "password" }); - await wrapper.update(); + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "password" } }); - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); + const submitButton = screen.getByRole("button", { name: "Register" }); + submitButton.click(); - expect(component.state.error).toBe("You need to fill all fields."); - expect( - wrapper.containsMatchingElement(

You need to fill all fields.

), - ).toBe(true); - }); + const header = screen.getByRole("heading", { name: "Unable to register" }); + expect(header).toBeInTheDocument(); + const message = screen.getByText("You need to fill all fields."); + expect(message).toBeInTheDocument(); }); it("shows a message if the email is empty", async () => { - const component: any = wrapper.find("RegisterPage").instance(); + const nameField = screen.getByLabelText("Name"); + fireEvent.change(nameField, { target: { value: "Max" } }); - await act(async () => { - component.setState({ name: "Max", email: "", password: "password" }); - await wrapper.update(); + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "password" } }); - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); + const submitButton = screen.getByRole("button", { name: "Register" }); + submitButton.click(); - expect(component.state.error).toBe("You need to fill all fields."); - expect( - wrapper.containsMatchingElement(

You need to fill all fields.

), - ).toBe(true); - }); - }); - - it("shows a message if the password is empty", async () => { - const component: any = wrapper.find("RegisterPage").instance(); - - await act(async () => { - component.setState({ name: "Max", email: "email", password: "" }); - await wrapper.update(); - - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); - - expect(component.state.error).toBe("You need to fill all fields."); - expect( - wrapper.containsMatchingElement(

You need to fill all fields.

), - ).toBe(true); - }); + const header = screen.getByRole("heading", { name: "Unable to register" }); + expect(header).toBeInTheDocument(); + const message = screen.getByText("You need to fill all fields."); + expect(message).toBeInTheDocument(); }); it("shows a message if the email is invalid", async () => { - const component: any = wrapper.find("RegisterPage").instance(); - - await act(async () => { - component.setState({ - name: "Max", - email: "fakeEmail", - password: "password", - }); - await wrapper.update(); + const nameField = screen.getByLabelText("Name"); + fireEvent.change(nameField, { target: { value: "Max" } }); - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); + const emailField = screen.getByLabelText("Email"); + fireEvent.change(emailField, { target: { value: "invalidEmail" } }); - expect(component.state.error).toBe("Invalid email."); - expect(wrapper.containsMatchingElement(

Invalid email.

)).toBe(true); - }); - }); + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "password" } }); - it("shows a message if the password is too short", async () => { - const component: any = wrapper.find("RegisterPage").instance(); + const submitButton = screen.getByRole("button", { name: "Register" }); + submitButton.click(); - await act(async () => { - component.setState({ - name: "Max", - email: "max@example.com", - password: "short", - }); - await wrapper.update(); - - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); - - expect(component.state.error).toBe( - "Password needs to have a minimum of 8 characters.", - ); - expect( - wrapper.containsMatchingElement( -

Password needs to have a minimum of 8 characters.

, - ), - ).toBe(true); - }); + const header = screen.getByRole("heading", { name: "Unable to register" }); + expect(header).toBeInTheDocument(); + const message = screen.getByText("Invalid email."); + expect(message).toBeInTheDocument(); }); - it("shows when there is an error", async () => { - wrapper = mount(withMockedProviders(, mocksWithError)); - const component = wrapper.find("RegisterPage").instance(); + it("shows a message if the password is too short", async () => { + const nameField = screen.getByLabelText("Name"); + fireEvent.change(nameField, { target: { value: "Max" } }); - await act(async () => { - component.setState({ - name: "Max", - email: "max@example.com", - password: "password", - }); - await wrapper.update(); + const emailField = screen.getByLabelText("Email"); + fireEvent.change(emailField, { target: { value: "max@example.com" } }); - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "pass" } }); - await wait(0); - await wrapper.update(); + const submitButton = screen.getByRole("button", { name: "Register" }); + submitButton.click(); - expect(findByTestId(wrapper, "error-message").text()).toBe("It broke"); - }); + const header = screen.getByRole("heading", { name: "Unable to register" }); + expect(header).toBeInTheDocument(); + const message = screen.getByText( + "Password needs to have a minimum of 8 characters.", + ); + expect(message).toBeInTheDocument(); }); - it("calls the mutation", async () => { - const component = wrapper.find("RegisterPage").instance(); - - await act(async () => { - component.setState({ - name: "Max", - email: "max@example.com", - password: "password", - }); - await wrapper.update(); + it("shows when there is an error", async () => { + const nameField = screen.getByLabelText("Name"); + fireEvent.change(nameField, { target: { value: "Min" } }); - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); + const emailField = screen.getByLabelText("Email"); + fireEvent.change(emailField, { target: { value: "min@example.com" } }); - await wait(0); - await wrapper.update(); + const passwordField = screen.getByLabelText("Password"); + fireEvent.change(passwordField, { target: { value: "password" } }); - expect(mutationCalled).toBe(true); + const submitButton = screen.getByRole("button", { name: "Register" }); + submitButton.click(); + const header = await screen.findByRole("heading", { + name: "Unable to register", }); + expect(header).toBeInTheDocument(); + const message = screen.getByText("It broke"); + expect(message).toBeInTheDocument(); }); }); From 9222928981bcfffdfaee6a81a6f07d25f05694b3 Mon Sep 17 00:00:00 2001 From: Matthijs Groen Date: Tue, 21 May 2024 10:18:07 +0200 Subject: [PATCH 06/54] Migrate General spec --- .../manage-team/sections/General.spec.tsx | 70 +++++++------------ src/modules/manage-team/sections/General.tsx | 7 +- 2 files changed, 31 insertions(+), 46 deletions(-) diff --git a/src/modules/manage-team/sections/General.spec.tsx b/src/modules/manage-team/sections/General.spec.tsx index 3728bf32..86f64fe2 100644 --- a/src/modules/manage-team/sections/General.spec.tsx +++ b/src/modules/manage-team/sections/General.spec.tsx @@ -8,6 +8,8 @@ import { withMockedProviders, } from "../../../spec_helper"; import GeneralSection, { GET_TEAM_NAME, UPDATE_TEAM } from "./General"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import exp from "constants"; let mutationCalled = false; const mocks = [ @@ -67,72 +69,50 @@ const mocksWithError = [ }, ]; -describe.skip("", () => { +describe("", () => { let wrapper: ReactWrapper; beforeEach(() => { mockLocalstorage("1"); mutationCalled = false; - wrapper = mount(withMockedProviders(, mocks)); }); - it("shows when the query is loading", () => { - expect(wrapper.containsMatchingElement(

Loading...

)).toBe(true); + it("shows when the query is loading", async () => { + render(withMockedProviders(, mocks)); + + expect(screen.getByText("Loading...")).toBeInTheDocument(); + expect( + await screen.findByRole("heading", { name: "Kabisa" }), + ).toBeInTheDocument(); }); it("shows when there is an error", async () => { - wrapper = mount(withMockedProviders(, mocksWithError)); - - await act(async () => { - await wait(0); - await wrapper.update(); + render(withMockedProviders(, mocksWithError)); - expect( - wrapper.containsMatchingElement(

Error! something went wrong

), - ); - }); + expect( + await screen.findByText("Error! something went wrong"), + ).toBeInTheDocument(); }); it("renders the team name", async () => { - await act(async () => { - await wait(0); - await wrapper.update(); + render(withMockedProviders(, mocks)); - expect(wrapper.containsMatchingElement(

Kabisa

)).toBe(true); - }); + expect( + await screen.findByRole("heading", { name: "Kabisa", level: 1 }), + ).toBeInTheDocument(); }); it("handles input correctly", async () => { - const component: any = wrapper.find("GeneralSection").instance(); - - await act(async () => { - await wait(0); - await wrapper.update(); - expect(component.state.name).toBe(""); - - simulateInputChange(wrapper, "name-input", "name", "Dovetail"); - - await wrapper.update(); - expect(component.state.name).toBe("Dovetail"); - }); - }); - - it("calls the update mutation", async () => { - const component = wrapper.find("GeneralSection").instance(); - - await act(async () => { - await wait(0); - await wrapper.update(); + render(withMockedProviders(, mocks)); - component.setState({ name: "Dovetail" }); + await screen.findByRole("heading", { name: "Kabisa", level: 1 }); - await wrapper.update(); - findByTestId(wrapper, "submit-button").hostNodes().simulate("submit"); + const input = await screen.findByLabelText("New team name"); + fireEvent.change(input, { target: { value: "Dovetail" } }); - await wait(0); - await wrapper.update(); + const submit = screen.getByRole("button", { name: "Update" }); + fireEvent.click(submit); - expect(mutationCalled).toBe(true); - }); + await waitFor(() => expect(mutationCalled).toBe(true)); }); }); diff --git a/src/modules/manage-team/sections/General.tsx b/src/modules/manage-team/sections/General.tsx index bfed94d2..5f53c1f0 100644 --- a/src/modules/manage-team/sections/General.tsx +++ b/src/modules/manage-team/sections/General.tsx @@ -110,7 +110,12 @@ export default class GeneralSection extends Component { {(mutate, { loading }) => ( <>

{data && data.teamById ? data.teamById.name : "-"}

-
this.updateTeam(mutate)}> + { + e.preventDefault(); + this.updateTeam(mutate); + }} + >