diff --git a/.eslintrc b/.eslintrc index 5b906f11..a8001569 100644 --- a/.eslintrc +++ b/.eslintrc @@ -22,6 +22,7 @@ "max-len": ["error", { "code": 120 }], "react/button-has-type": "off", "react/react-in-jsx-scope": "off", + "react/prop-types": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/explicit-function-return-type": "off", diff --git a/src/components/AuthenticatedRoute.spec.tsx b/src/components/AuthenticatedRoute.spec.tsx index 64acb893..2764e7ef 100644 --- a/src/components/AuthenticatedRoute.spec.tsx +++ b/src/components/AuthenticatedRoute.spec.tsx @@ -1,32 +1,41 @@ -import { withMockedProviders } from "../spec_helper"; import AuthenticatedRoute from "./AuthenticatedRoute"; import { Auth } from "../support"; -import { render, screen } from "@testing-library/react"; +import { screen } from "@testing-library/react"; +import { setTestSubject } from "../support/testing/testSubject"; +import { PATH_CHOOSE_TEAM, PATH_LOGIN } from "../routes"; +import { routingDecorator } from "../support/testing/testDecorators"; jest.mock("../support/auth"); -const fakeComponent = () =>

Fake component

; - -const setup = (allowNoTeam: boolean) => { - render( - withMockedProviders( - , - ), +describe("", () => { + const { renderComponent, updateDecorator, updateProps } = setTestSubject( + AuthenticatedRoute, + { + decorators: [routingDecorator()], + props: { + allowNoTeam: false, + component: () =>

Fake component

, + }, + }, ); -}; -describe("", () => { afterEach(() => { jest.clearAllMocks(); jest.resetAllMocks(); }); + beforeEach(() => { + updateDecorator("routing", { + paths: { + [PATH_LOGIN]: "Login Page", + [PATH_CHOOSE_TEAM]: "Choose team Page", + }, + }); + }); + it("does not render the page if the user is not logged in", () => { Auth.isLoggedIn = jest.fn(() => false); - setup(false); + renderComponent(); expect(screen.getByText("Login Page")).toBeInTheDocument(); }); @@ -34,7 +43,7 @@ describe("", () => { it("does not render the page if the user has no team and allowNoTeam is false", () => { Auth.isLoggedIn = jest.fn(() => true); Auth.hasTeam = jest.fn(() => false); - setup(false); + renderComponent(); expect(screen.getByText("Choose team Page")).toBeInTheDocument(); }); @@ -42,14 +51,16 @@ describe("", () => { it("does render the page if the user has no team and allowNoTeam is true", () => { Auth.isLoggedIn = jest.fn(() => true); Auth.hasTeam = jest.fn(() => false); - setup(true); + updateProps({ allowNoTeam: true }); + renderComponent(); + expect(screen.getByText("Fake component")).toBeInTheDocument(); }); it("does render the page if the user is logged in and has a team", () => { Auth.isLoggedIn = jest.fn(() => true); Auth.hasTeam = jest.fn(() => true); - setup(false); + renderComponent(); expect(screen.getByText("Fake component")).toBeInTheDocument(); }); diff --git a/src/components/AuthenticatedRoute.tsx b/src/components/AuthenticatedRoute.tsx index 1e3bfb22..599ba398 100644 --- a/src/components/AuthenticatedRoute.tsx +++ b/src/components/AuthenticatedRoute.tsx @@ -1,26 +1,35 @@ -import { Redirect, Route } from "react-router-dom"; +import { + Redirect, + Route, + RouteComponentProps, + RouteProps, +} from "react-router-dom"; import { PATH_CHOOSE_TEAM, PATH_LOGIN } from "../routes"; import { Auth } from "../support"; -export default function AuthenticatedRoute({ +type Props = { + allowNoTeam?: boolean; + component: React.ComponentType; +} & RouteProps; + +const AuthenticatedRoute: React.FC = ({ allowNoTeam, component: Component, ...rest -}: any) { - return ( - { - if (!Auth.isLoggedIn()) { - return ; - } +}) => ( + { + if (!Auth.isLoggedIn()) { + return ; + } - if (Auth.hasTeam() || allowNoTeam) { - return ; - } + if (Auth.hasTeam() || allowNoTeam) { + return ; + } - return ; - }} - /> - ); -} + return ; + }} + /> +); +export default AuthenticatedRoute; diff --git a/src/components/Circle/Circle.spec.tsx b/src/components/Circle/Circle.spec.tsx index d1fd56d1..1b5d60c5 100644 --- a/src/components/Circle/Circle.spec.tsx +++ b/src/components/Circle/Circle.spec.tsx @@ -1,19 +1,18 @@ -import { withMockedProviders } from "../../spec_helper"; +import { setTestSubject } from "../../support/testing/testSubject"; import CustomCircle from "./Circle"; -import { render, screen } from "@testing-library/react"; +import { screen } from "@testing-library/react"; describe("", () => { + const { setProps, renderComponent } = setTestSubject(CustomCircle); + setProps({ + percent: 50, + currentKudos: 200, + neededKudos: 500, + goal: "Some goal", + }); + beforeEach(() => { - render( - withMockedProviders( - , - ), - ); + renderComponent(); }); it("renders the correct current kudo amount", () => { diff --git a/src/components/navigation/Desktop.spec.tsx b/src/components/navigation/Desktop.spec.tsx index 8bb5fcc8..3d816342 100644 --- a/src/components/navigation/Desktop.spec.tsx +++ b/src/components/navigation/Desktop.spec.tsx @@ -1,6 +1,11 @@ -import { render, RenderResult, screen } from "@testing-library/react"; -import { mockLocalstorage, withMockedProviders } from "../../spec_helper"; +import { screen } from "@testing-library/react"; +import { mockLocalstorage } from "../../spec_helper"; import Desktop, { GET_USER } from "./Desktop"; +import { setTestSubject } from "../../support/testing/testSubject"; +import { + dataDecorator, + routingDecorator, +} from "../../support/testing/testDecorators"; export const mocks = () => [ { @@ -20,24 +25,29 @@ export const mocks = () => [ ]; describe("", () => { + const { renderComponent } = setTestSubject(Desktop, { + decorators: [dataDecorator(mocks()), routingDecorator()], + props: {}, + }); + it("renders the users name", async () => { - render(withMockedProviders(, mocks())); + renderComponent(); + const node = await screen.findByText("Max"); expect(node).toBeInTheDocument(); }); it("should have a link to the home page", async () => { - render(withMockedProviders(, mocks())); + renderComponent(); const button = await screen.findByTestId("home-button"); expect(button).toBeInTheDocument(); }); describe("profile menu", () => { - let renderResult: RenderResult; - beforeEach(async () => { - renderResult = render(withMockedProviders(, mocks())); + renderComponent(); + const button = await screen.findByRole("button", { name: "Max" }); button.click(); }); @@ -58,21 +68,23 @@ describe("", () => { }); describe("as admin", () => { - beforeEach(() => { + beforeEach(async () => { mockLocalstorage("admin"); - renderResult.rerender(withMockedProviders(, mocks())); + renderComponent(); + await screen.findByText("Max"); }); it("has a manage team button", () => { - const profileLink = screen.queryByTestId("manage-team-button"); + const profileLink = screen.findByTestId("manage-team-button"); expect(profileLink).not.toBeNull(); }); }); describe("as member", () => { - beforeEach(() => { + beforeEach(async () => { mockLocalstorage("member"); - renderResult.rerender(withMockedProviders(, mocks())); + renderComponent(); + await screen.findByText("Max"); }); it("does not have a manage team button", () => { diff --git a/src/components/navigation/Mobile.spec.tsx b/src/components/navigation/Mobile.spec.tsx index c0fbf65e..9c70e4de 100644 --- a/src/components/navigation/Mobile.spec.tsx +++ b/src/components/navigation/Mobile.spec.tsx @@ -1,21 +1,20 @@ -import { mockLocalstorage, withMockedProviders } from "../../spec_helper"; +import { mockLocalstorage } from "../../spec_helper"; import Mobile from "./Mobile"; import { Auth } from "../../support"; -import { RenderResult, render, screen } from "@testing-library/react"; - -let result: RenderResult | null = null; -const setup = () => { - if (result) { - result.unmount(); - } - result = render(withMockedProviders()); -}; +import { screen } from "@testing-library/react"; +import { setTestSubject } from "../../support/testing/testSubject"; +import { routingDecorator } from "../../support/testing/testDecorators"; describe("", () => { mockLocalstorage("fakeToken"); + const { renderComponent } = setTestSubject(Mobile, { + decorators: [routingDecorator()], + props: {}, + }); + beforeEach(() => { - setup(); + renderComponent(); }); it("has a button to the settings page", () => { @@ -44,7 +43,7 @@ describe("", () => { it("has no buttons to the feed, statistics and notifications page if the user has no team", () => { Auth.hasTeam = jest.fn(() => false); - setup(); + renderComponent(); expect(screen.queryByRole("link", { name: "monitoring" })).toBeNull(); expect(screen.queryByRole("link", { name: "notifications" })).toBeNull(); diff --git a/src/components/organisms/RepoList/RepoList.spec.tsx b/src/components/organisms/RepoList/RepoList.spec.tsx index 5ebee4cf..e89674ef 100644 --- a/src/components/organisms/RepoList/RepoList.spec.tsx +++ b/src/components/organisms/RepoList/RepoList.spec.tsx @@ -1,7 +1,9 @@ -import { render, screen } from "@testing-library/react"; +import { screen } from "@testing-library/react"; import { GET_POSTS } from "../../../modules/feed/queries"; -import { mockLocalstorage, withMockedProviders } from "../../../spec_helper"; +import { mockLocalstorage } from "../../../spec_helper"; import { RepoList } from "./RepoList"; +import { setTestSubject } from "../../../support/testing/testSubject"; +import { dataDecorator } from "../../../support/testing/testDecorators"; export const mocks = (hasNextPage: boolean) => [ { @@ -78,16 +80,19 @@ const mocksWithError = [ }, ]; -const setup = (mock: any) => - render(withMockedProviders(, mock, undefined, true)); - describe("", () => { + const { renderComponent, updateDecorator } = setTestSubject(RepoList, { + decorators: [dataDecorator(mocks(false))], + props: {}, + }); + beforeEach(() => { mockLocalstorage("1"); - setup(mocks(false)); }); it("shows loading when the query is loading", async () => { + renderComponent(); + const element = screen.getByText("Loading.."); expect(element).toBeInTheDocument(); @@ -96,23 +101,31 @@ describe("", () => { }); it("shows show the error message when there is an error", async () => { - setup(mocksWithError); + updateDecorator("application", { mocks: mocksWithError }); + renderComponent(); + const element = await screen.findByText("It broke"); expect(element).toBeInTheDocument(); }); it("renders all the posts", async () => { + renderComponent(); + const elements = await screen.findAllByTestId("kudo-transaction"); expect(elements).toHaveLength(1); }); it("shows a load next button when there are more posts", async () => { - setup(mocks(true)); + updateDecorator("application", { mocks: mocks(true) }); + renderComponent(); + const nextPageButton = await screen.findByTestId("next-page-button"); expect(nextPageButton).toBeInTheDocument(); }); it("should not show a load next button when there are no more posts", async () => { + renderComponent(); + const nextPageButton = screen.queryByTestId("next-page-button"); expect(nextPageButton).toBeNull(); diff --git a/src/components/upload/ImageUpload.spec.tsx b/src/components/upload/ImageUpload.spec.tsx index 27703945..8b1edc76 100644 --- a/src/components/upload/ImageUpload.spec.tsx +++ b/src/components/upload/ImageUpload.spec.tsx @@ -1,6 +1,6 @@ -import { withMockedProviders } from "../../spec_helper"; +import { setTestSubject } from "../../support/testing/testSubject"; import { ImageUpload } from "./ImageUpload"; -import { render, screen, waitFor } from "@testing-library/react"; +import { screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; const createFile = (name: string, size: number, type: string): File => { @@ -16,17 +16,16 @@ const createFile = (name: string, size: number, type: string): File => { describe("", () => { let selectedFiles: any = []; + const { setProps, renderComponent } = setTestSubject(ImageUpload); + setProps({ + onChange: (files) => { + selectedFiles = files; + }, + }); + beforeEach(() => { window.URL.createObjectURL = jest.fn(); - render( - withMockedProviders( - { - selectedFiles = files; - }} - />, - ), - ); + renderComponent(); }); it("can select files", async () => { diff --git a/src/modules/choose-team/ChooseTeamPage.spec.tsx b/src/modules/choose-team/ChooseTeamPage.spec.tsx index 0023065f..cf012455 100644 --- a/src/modules/choose-team/ChooseTeamPage.spec.tsx +++ b/src/modules/choose-team/ChooseTeamPage.spec.tsx @@ -1,8 +1,9 @@ -import { withMockedProviders } from "../../spec_helper"; import { Content } from "./ChooseTeamPage"; -import { render, screen, fireEvent } from "@testing-library/react"; +import { screen, fireEvent } from "@testing-library/react"; import { GET_INVITES } from "./components/InviteList"; import { GET_TEAMS } from "./components/TeamList"; +import { setTestSubject } from "../../support/testing/testSubject"; +import { dataDecorator } from "../../support/testing/testDecorators"; const mockHistoryPush = jest.fn(); @@ -85,22 +86,27 @@ const mockWithInvites = [ ]; describe("", () => { + const { renderComponent } = setTestSubject(Content, { + decorators: [dataDecorator(mockWithInvites)], + props: {}, + }); + it("renders the invite list", async () => { - render(withMockedProviders(, mockWithInvites)); + renderComponent(); const inviteList = await screen.findByTestId("invite-list"); expect(inviteList).toBeInTheDocument(); }); it("renders the team list", async () => { - render(withMockedProviders(, mockWithInvites)); + renderComponent(); const teamInvites = await screen.findByTestId("kudo-team-invites"); expect(teamInvites).toBeInTheDocument(); }); it("renders the create team button", () => { - render(withMockedProviders(, mockWithInvites)); + renderComponent(); const createTeamButton = screen.getByRole("button", { name: "Create team", @@ -109,9 +115,7 @@ describe("", () => { }); it("navigates to the create team page", () => { - const { unmount } = render( - withMockedProviders(, mockWithInvites), - ); + renderComponent(); const createTeamButton = screen.getByRole("button", { name: "Create team", @@ -119,6 +123,5 @@ describe("", () => { fireEvent.click(createTeamButton); expect(mockHistoryPush).toHaveBeenCalledWith("/create-team"); - unmount(); }); }); diff --git a/src/modules/choose-team/CreateTeamPage.spec.tsx b/src/modules/choose-team/CreateTeamPage.spec.tsx index 6c01bc5b..479c6b85 100644 --- a/src/modules/choose-team/CreateTeamPage.spec.tsx +++ b/src/modules/choose-team/CreateTeamPage.spec.tsx @@ -1,9 +1,12 @@ import { GraphQLError } from "graphql"; -import { withMockedProviders } from "../../spec_helper"; import CreateTeamPage, { MUTATION_CREATE_TEAM } from "./CreateTeamPage"; import { Storage } from "../../support/storage"; -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; +import { fireEvent, screen, waitFor } from "@testing-library/react"; +import { setTestSubject } from "../../support/testing/testSubject"; +import { + dataDecorator, + routingDecorator, +} from "../../support/testing/testDecorators"; let mutationCalled = false; const mocks = [ @@ -32,20 +35,26 @@ const mocksWithError = [ ]; describe("", () => { + const { renderComponent, updateDecorator } = setTestSubject(CreateTeamPage, { + decorators: [dataDecorator(mocks), routingDecorator()], + props: {}, + }); + beforeEach(() => { mutationCalled = false; Storage.setItem = jest.fn(); }); it("renders a name field", () => { - render(withMockedProviders(, mocks)); + renderComponent(); + const input = screen.getByPlaceholderText("Team name"); expect(input).toBeInTheDocument(); }); it("renders the create team button", () => { - render(withMockedProviders(, mocks)); + renderComponent(); expect( screen.getByRole("button", { name: "Create team" }), @@ -53,7 +62,8 @@ describe("", () => { }); it("handles input correctly", () => { - render(withMockedProviders(, mocks)); + renderComponent(); + const input = screen.getByPlaceholderText("Team name"); expect(input).toHaveValue(""); @@ -63,16 +73,17 @@ describe("", () => { }); it("returns an error if the name is null", () => { - render(withMockedProviders(, mocks)); - const button = screen.getByRole("button", { name: "Create team" }); + renderComponent(); - userEvent.click(button); + const button = screen.getByRole("button", { name: "Create team" }); + button.click(); expect(screen.queryByText("Name can't be blank.")); }); it("disables the button during creation", async () => { - render(withMockedProviders(, mocks)); + renderComponent(); + const input = screen.getByPlaceholderText("Team name"); const button = screen.getByRole("button", { name: "Create team" }); @@ -82,34 +93,43 @@ describe("", () => { await waitFor(() => expect(button).toBeDisabled()); }); - it("shows when there is an error", () => { - render(withMockedProviders(, mocksWithError)); + it("shows when there is an error", async () => { + updateDecorator("application", { mocks: mocksWithError }); + renderComponent(); + const button = screen.getByRole("button", { name: "Create team" }); - userEvent.click(button); + const input = screen.getByPlaceholderText("Team name"); + fireEvent.change(input, { target: { value: "Kabisa" } }); + + button.click(); - expect(screen.queryByText("It broke")); + expect(await screen.findByText("It broke")).not.toBeNull(); }); it("calls the mutation if the name is not null", async () => { - render(withMockedProviders(, mocks)); + renderComponent(); + const input = screen.getByPlaceholderText("Team name"); const button = screen.getByRole("button", { name: "Create team" }); fireEvent.change(input, { target: { value: "Kabisa" } }); - userEvent.click(button); + button.click(); await waitFor(() => expect(mutationCalled).toBe(true)); }); it("sets the team id in local storage if the mutation is successful", async () => { - render(withMockedProviders(, mocks)); + renderComponent(); + const input = screen.getByPlaceholderText("Team name"); const button = screen.getByRole("button", { name: "Create team" }); fireEvent.change(input, { target: { value: "Kabisa" } }); - userEvent.click(button); + button.click(); - await waitFor(() => expect(Storage.setItem).toBeCalledWith("team_id", "1")); + await waitFor(() => + expect(Storage.setItem).toHaveBeenCalledWith("team_id", "1"), + ); }); }); diff --git a/src/modules/choose-team/CreateTeamPage.tsx b/src/modules/choose-team/CreateTeamPage.tsx index 98e69676..4d984347 100644 --- a/src/modules/choose-team/CreateTeamPage.tsx +++ b/src/modules/choose-team/CreateTeamPage.tsx @@ -9,7 +9,7 @@ import { Storage } from "../../support/storage"; import { Button, Input, Label } from "@kabisa/ui-components"; import Segment from "../../components/atoms/Segment"; import Page from "../../components/templates/Page"; -import MessageBox from '../../ui/MessageBox'; +import MessageBox from "../../ui/MessageBox"; export const MUTATION_CREATE_TEAM = gql` mutation CreateTeam($name: String!) { @@ -72,7 +72,12 @@ const CreateTeamPage = () => { }; const content = ( -
+ { + e.preventDefault(); + }} + >

Create new team