-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from rrecaredo/unit-tests
Unit tests
- Loading branch information
Showing
11 changed files
with
834 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
226 changes: 207 additions & 19 deletions
226
src/lib/components/cancellable-request-button/CancellableRequestButton.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,222 @@ | ||
import React from "react"; | ||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; | ||
import { ThemeProvider } from "styled-components"; | ||
import { rest } from "msw"; | ||
import { setupServer } from "msw/node"; | ||
import "whatwg-fetch"; | ||
|
||
import { | ||
cleanup, | ||
render, | ||
screen, | ||
fireEvent, | ||
waitFor, | ||
} from "@testing-library/react"; | ||
|
||
import userEvent from "@testing-library/user-event"; | ||
|
||
import { CancellableRequestButton } from "./CancellableRequestButton"; | ||
import { theme } from "@common/theme"; | ||
import { ButtonState } from '@components/smart-button'; | ||
|
||
global.ResizeObserver = require('resize-observer-polyfill') | ||
|
||
const worker = setupServer( | ||
rest.get("/api/rocket-launcher", (req, res, ctx) => { | ||
return res( | ||
ctx.json({ | ||
count: 500, | ||
firstName: "Foo", | ||
lastName: "Bar", | ||
}) | ||
); | ||
}), | ||
rest.post("/api/timeout", (req, res, ctx) => { | ||
return res( | ||
ctx.delay(2000), | ||
ctx.json({ | ||
count: 500, | ||
firstName: "Foo", | ||
lastName: "Bar", | ||
}) | ||
); | ||
}) | ||
); | ||
|
||
beforeAll(() => worker.listen()); | ||
afterEach(() => worker.resetHandlers()); | ||
afterAll(() => worker.close()); | ||
|
||
export const STRINGS = { | ||
LaunchRocketLabel: "Launch Rocket", | ||
LaunchRocketTooltip: "Ignites the fuel", | ||
IgnitionErrorTooltip: "Ignition error", | ||
LaunchingLabel: "Launching", | ||
CancelLaunchTooltip: "Cancel launch", | ||
}; | ||
|
||
const queryClient = new QueryClient(); | ||
|
||
const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( | ||
<ThemeProvider theme={theme}> | ||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider> | ||
</ThemeProvider> | ||
); | ||
|
||
const labelProps = { | ||
defaultLabel: STRINGS.LaunchRocketLabel, | ||
workingLabel: STRINGS.LaunchingLabel, | ||
errorLabel: STRINGS.LaunchRocketLabel, | ||
defaultTooltip: STRINGS.LaunchRocketTooltip, | ||
workingTooltip: STRINGS.CancelLaunchTooltip, | ||
errorTooltip: STRINGS.IgnitionErrorTooltip, | ||
}; | ||
|
||
const onSuccess = jest.fn(); | ||
|
||
const renderButton = (state: ButtonState, url: string) => { | ||
render( | ||
<CancellableRequestButton | ||
url={url} | ||
state={state} | ||
onSuccess={onSuccess} | ||
{...labelProps} | ||
/>, | ||
{ | ||
wrapper, | ||
} | ||
); | ||
}; | ||
|
||
const fastApiUrl = "/api/rocket-launcher"; | ||
const slowApiUrl = "/api/timeout"; | ||
|
||
describe("Components > CancellableRequestButton", () => { | ||
test("It should make a network request to a URL passed as props", () => { | ||
throw new Error("Not implemented"); | ||
afterAll(cleanup); | ||
|
||
beforeEach(jest.resetAllMocks); | ||
|
||
test("It should make a network request to a URL passed as props", async () => { | ||
renderButton("ready", fastApiUrl) | ||
|
||
fireEvent.click(screen.getByRole("button")); | ||
|
||
await waitFor(() => expect(onSuccess).toHaveBeenCalledTimes(1)); | ||
}); | ||
test("It should show the 'Working' state for the duration of the network request", () => { | ||
throw new Error("Not implemented"); | ||
|
||
test("It should show the 'Working' state for the duration of the network request", async () => { | ||
renderButton("ready", slowApiUrl) | ||
|
||
fireEvent.click(screen.getByRole("button")); | ||
|
||
await waitFor(() => | ||
expect(screen.queryByText(/Launching/)).toBeInTheDocument() | ||
); | ||
}); | ||
test("It should optionally timeout the network request after a max duration passed as props", () => { | ||
throw new Error("Not implemented"); | ||
|
||
test("It should optionally timeout the network request after a max duration passed as props", async () => { | ||
jest.useFakeTimers(); | ||
const onSuccess = jest.fn(); | ||
|
||
renderButton("ready", slowApiUrl) | ||
|
||
fireEvent.click(screen.getByRole("button")); | ||
|
||
jest.advanceTimersByTime(3000); | ||
|
||
expect(onSuccess).not.toHaveBeenCalled(); | ||
|
||
jest.useRealTimers(); | ||
|
||
// @TODO: There is an async operation happening after the component unmounts, likely coming from React Query | ||
// which is causing a warning and potentially a memory leak that needs further investigation. | ||
}); | ||
test("It should show the error state after the max duration is exceeded and the network request is cancelled", () => { | ||
throw new Error("Not implemented"); | ||
|
||
test("It should show the error state after the max duration is exceeded and the network request is cancelled", async () => { | ||
jest.useFakeTimers(); | ||
|
||
renderButton("ready", slowApiUrl) | ||
|
||
fireEvent.click(screen.getByRole("button")); | ||
|
||
jest.advanceTimersByTime(3000); | ||
|
||
await waitFor(() => | ||
expect(screen.queryByText(/Ignition error/)).toBeInTheDocument() | ||
); | ||
|
||
jest.useRealTimers(); | ||
}); | ||
test("It should return to the default state after the network request completes if there is no timeout provided", () => { | ||
throw new Error("Not implemented"); | ||
|
||
test("It should return to the default state after the network request completes", async () => { | ||
renderButton("ready", fastApiUrl) | ||
|
||
fireEvent.click(screen.getByRole("button")); | ||
|
||
// This is a bit of a hack. An enhancement would be to have a data-state attribute on the button | ||
// and verify that it is set back to 'ready' after the network request completes. | ||
userEvent.hover(screen.getByRole("button")); | ||
|
||
await waitFor(() => | ||
expect(screen.queryByText(/Ignites the fuel/)).toBeInTheDocument() | ||
); | ||
}); | ||
test("A second click of the button should abort a request that is in-flight and show the error state", () => { | ||
throw new Error("Not implemented"); | ||
test("A second click of the button should abort a request that is in-flight and show the error state", async () => { | ||
jest.useFakeTimers(); | ||
|
||
renderButton("ready", slowApiUrl) | ||
|
||
const button = screen.getByRole("button"); | ||
|
||
fireEvent.click(button); | ||
|
||
jest.advanceTimersByTime(500); | ||
|
||
// Second click while the response has not yet been received | ||
fireEvent.click(button); | ||
|
||
await waitFor(() => | ||
expect(screen.queryByText(/Ignition error/)).toBeInTheDocument() | ||
); | ||
|
||
jest.useRealTimers(); | ||
}); | ||
test("It should be possible to put the button into each state via props ", () => { | ||
throw new Error("Not implemented"); | ||
|
||
describe("It should be possible to put the button into each state via props", () => { | ||
test("ready", async () => { | ||
renderButton("ready", slowApiUrl) | ||
|
||
expect(screen.queryByText(/Launch Rocket/)).toBeInTheDocument() | ||
expect(screen.queryByText(/Ignites the fuel/)).toBeNull(); | ||
}); | ||
|
||
test("working", async () => { | ||
renderButton("working", slowApiUrl); | ||
|
||
expect(screen.queryByText(/Launching/)).toBeInTheDocument() | ||
}); | ||
|
||
test("errored", async () => { | ||
renderButton("error", slowApiUrl); | ||
|
||
expect(screen.queryByText(/Ignition error/)).toBeInTheDocument() | ||
}); | ||
|
||
test("disabled", async () => { | ||
renderButton("disabled", slowApiUrl); | ||
expect(screen.getByRole("button")).toBeDisabled(); | ||
|
||
}); | ||
}); | ||
|
||
test("The tooltip should not show if the button is disabled ", () => { | ||
throw new Error("Not implemented"); | ||
}); | ||
test("The error state should not show if the button is disabled or working ", () => { | ||
throw new Error("Not implemented"); | ||
renderButton("disabled", slowApiUrl); | ||
userEvent.hover(screen.getByRole("button")); | ||
expect(screen.queryByText(/Ignites the fuel/)).toBeNull(); | ||
}); | ||
|
||
test("The tooltip should always show when in the error state ", () => { | ||
throw new Error("Not implemented"); | ||
renderButton("error", slowApiUrl); | ||
expect(screen.queryByText(/Ignition error/)).toBeInTheDocument(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
67410c7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
rocket-launcher – ./
rocket-launcher.vercel.app
rocket-launcher-rrecaredo.vercel.app
rocket-launcher-git-main-rrecaredo.vercel.app