-
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.
- Loading branch information
1 parent
1a9a367
commit 5d4db74
Showing
5 changed files
with
372 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { createFetchSearchClient } from "./fetchSearchClient"; | ||
import z from "zod"; | ||
import { Adapter } from "./Adapter"; | ||
|
||
const createFetchMock = (data: unknown) => | ||
jest.fn().mockResolvedValue({ json: jest.fn().mockReturnValue(data) }); | ||
const schema = z.array(z.object({ test: z.string() })); | ||
type ServerSchema = z.infer<typeof schema>; | ||
|
||
describe("fetchSearchClient", () => { | ||
it("should properly fetch and pass validation", async () => { | ||
const fetchMock = createFetchMock([{ test: "a" }, { test: "b" }]); | ||
const adapter: Adapter<ServerSchema, ServerSchema[number]> = { | ||
parse: schema.parse, | ||
serverToClient: (x) => x, | ||
}; | ||
const client = createFetchSearchClient( | ||
(query) => `mock/${query}`, | ||
"token", | ||
adapter, | ||
fetchMock | ||
); | ||
|
||
const result = await client.search("givemerepo"); | ||
|
||
expect(fetchMock).toBeCalledWith( | ||
"mock/givemerepo", | ||
expect.objectContaining({ | ||
headers: expect.objectContaining({ Authorization: "Bearer token" }), | ||
}) | ||
); | ||
expect(result).toEqual([{ test: "a" }, { test: "b" }]); | ||
}); | ||
|
||
it("should extract zod error", async () => { | ||
const fetchMock = createFetchMock([{ foo: "a" }, { test: 3 }]); | ||
const adapter: Adapter<ServerSchema, ServerSchema[number]> = { | ||
parse: schema.parse, | ||
serverToClient: (x) => x, | ||
}; | ||
const client = createFetchSearchClient( | ||
(query) => `mock/${query}`, | ||
"token", | ||
adapter, | ||
fetchMock | ||
); | ||
|
||
await expect(client.search("givemerepo")).rejects.toThrowError( | ||
"0,test: Required\n1,test: Expected string, received number" | ||
); | ||
}); | ||
|
||
it("should rethrow throw any other error other than zod", async () => { | ||
const fetchMock = createFetchMock([{ test: "a" }, { test: "b" }]); | ||
const adapter: Adapter<ServerSchema, ServerSchema[number]> = { | ||
parse: schema.parse, | ||
serverToClient: jest.fn().mockImplementation(() => { | ||
throw new Error("Try me"); | ||
}), | ||
}; | ||
const client = createFetchSearchClient( | ||
(query) => `mock/${query}`, | ||
"token", | ||
adapter, | ||
fetchMock | ||
); | ||
|
||
await expect(client.search("givemerepo")).rejects.toThrowError("Try me"); | ||
}); | ||
}); |
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 |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { cancelableWithAbortController } from "./cancelablePromise"; | ||
|
||
describe("cancelableWithAbortController", () => { | ||
it("should resolve when not cancelled", async () => { | ||
const abortController = new AbortController(); | ||
const promise = cancelableWithAbortController( | ||
abortController, | ||
Promise.resolve("success") | ||
); | ||
await expect(promise).resolves.toEqual("success"); | ||
}); | ||
|
||
it("should not resolve and fire signal when cancelled", async () => { | ||
const abortController = new AbortController(); | ||
const promise = cancelableWithAbortController( | ||
abortController, | ||
Promise.resolve("success") | ||
); | ||
promise.cancel(); | ||
|
||
expect(abortController.signal.aborted).toBeTruthy(); | ||
expect(promise.isCanceled()).toBeTruthy(); | ||
}); | ||
}); |
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 |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { render, screen } from "@testing-library/react"; | ||
import { AppLayout } from "./AppLayout"; | ||
import { IntlProvider } from "react-intl"; | ||
|
||
describe("AppLayout", () => { | ||
it("should render proper structure", () => { | ||
render( | ||
<AppLayout> | ||
<div>Foo</div> | ||
</AppLayout>, | ||
{ | ||
wrapper: (props) => <IntlProvider locale="en" {...props} />, | ||
} | ||
); | ||
expect(screen.getByText("Repositories Search Demo")).toBeVisible(); | ||
expect(screen.getByText("Foo")).toBeVisible(); | ||
expect(screen.getByTitle("Change theme")).toBeVisible(); | ||
}); | ||
}); |
106 changes: 106 additions & 0 deletions
106
src/features/repositories/pages/search/RepositorySearchPage.connected.test.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 |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { inMemorySearchClient, renderOptions } from "./__test__/test-common"; | ||
import { act, render, screen, waitFor } from "@testing-library/react"; | ||
import { RepositorySearchPageConnected } from "./RepositorySearchPage.connected"; | ||
import userEvent from "@testing-library/user-event"; | ||
import { withFailure } from "../../../../common/api/clients/development/withFailure"; | ||
|
||
describe("RepositorySearchPage.connected", () => { | ||
|
||
it("should allow to search with query and re-search after changing the query", async () => { | ||
render( | ||
<RepositorySearchPageConnected searchClient={inMemorySearchClient} />, | ||
renderOptions | ||
); | ||
|
||
// loading initial search results | ||
expect(screen.getByTestId("spinner")).toBeVisible(); | ||
await waitFor(() => { | ||
expect(screen.queryByTestId("spinner")).not.toBeInTheDocument(); | ||
}); | ||
|
||
// it should have all 4 repositories having 'react' in name | ||
expect(screen.getAllByTestId("repository-card")).toHaveLength(4); | ||
|
||
// change query to "12" and search | ||
userEvent.click(screen.getByDisplayValue("react")); | ||
userEvent.keyboard("{selectall}12"); | ||
act(() => void userEvent.click(screen.getByText("Search"))); | ||
|
||
// now search button should be disabled and we should wait for results | ||
expect(screen.getByText("Search")).toBeDisabled(); | ||
expect(screen.getByTestId("spinner")).toBeVisible(); | ||
await waitFor(() => { | ||
expect(screen.queryByTestId("spinner")).not.toBeInTheDocument(); | ||
}); | ||
|
||
// we should see only two repositories in results | ||
expect(screen.getAllByTestId("repository-card")).toHaveLength(2); | ||
|
||
// after successful search, change '12' to '123' in the query and try to search again | ||
userEvent.click(screen.getByDisplayValue("12")); | ||
userEvent.keyboard("3"); | ||
act(() => void userEvent.click(screen.getByText("Search"))); | ||
await waitFor(() => { | ||
expect(screen.queryByTestId("spinner")).not.toBeInTheDocument(); | ||
}); | ||
|
||
// now it should only show one repository | ||
expect(screen.getAllByTestId("repository-card")).toHaveLength(1); | ||
|
||
// if we don't change the query, clicking Search should do nothing | ||
userEvent.click(screen.getByText("Search")); | ||
expect(screen.queryByTestId("spinner")).not.toBeInTheDocument(); | ||
}); | ||
|
||
it("should allow to search with query and re-search in error state", async () => { | ||
render( | ||
<RepositorySearchPageConnected | ||
searchClient={withFailure(inMemorySearchClient, 1)} | ||
/>, | ||
renderOptions | ||
); | ||
|
||
// loading initial search results | ||
expect(screen.getByTestId("spinner")).toBeVisible(); | ||
await waitFor(() => { | ||
expect(screen.queryByTestId("spinner")).not.toBeInTheDocument(); | ||
}); | ||
|
||
// we should see an error page | ||
expect(screen.queryAllByTestId("repository-card")).toHaveLength(0); | ||
|
||
// since we have an error, just click search again, it should be enabled | ||
act(() => void userEvent.click(screen.getByText("Search"))); | ||
|
||
// now search button should be disabled, and we should wait for results | ||
expect(screen.getByText("Search")).toBeDisabled(); | ||
expect(screen.getByTestId("spinner")).toBeVisible(); | ||
await waitFor(() => { | ||
expect(screen.queryByTestId("spinner")).not.toBeInTheDocument(); | ||
}); | ||
|
||
// we should see an error page again | ||
expect(screen.queryAllByTestId("repository-card")).toHaveLength(0); | ||
}); | ||
|
||
it("should abort previous request when searching fast", async () => { | ||
const searchClient = { search: jest.fn() }; | ||
render( | ||
<RepositorySearchPageConnected searchClient={searchClient} />, | ||
renderOptions | ||
); | ||
|
||
// we don't wait for results, we want to search again very soon | ||
// change query to "12" and search again | ||
userEvent.click(screen.getByDisplayValue("react")); | ||
userEvent.keyboard("{selectall}12"); | ||
act(() => void userEvent.click(screen.getByText("Search"))); | ||
|
||
expect( | ||
(searchClient.search.mock.calls[0][1] as AbortController).signal.aborted | ||
).toBeTruthy(); | ||
expect( | ||
(searchClient.search.mock.calls[1][1] as AbortController).signal.aborted | ||
).toBeFalsy(); | ||
}); | ||
}); |
153 changes: 153 additions & 0 deletions
153
src/features/repositories/pages/search/RepositorySearchPage.pure.test.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 |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import { render, screen, within } from "@testing-library/react"; | ||
import { RepositorySearchPagePure } from "./RepositorySearchPage.pure"; | ||
import userEvent from "@testing-library/user-event"; | ||
import { renderOptions } from "./__test__/test-common"; | ||
|
||
describe("RepositorySearchPage.pure", () => { | ||
it("should render proper structure in loading mode", function () { | ||
render( | ||
<RepositorySearchPagePure | ||
query="test query" | ||
repositories={{ type: "pending" }} | ||
onQueryChange={jest.fn()} | ||
/>, | ||
renderOptions | ||
); | ||
expect(screen.getByTestId("spinner")).toBeVisible(); | ||
}); | ||
|
||
it("should render proper structure in error mode", function () { | ||
render( | ||
<RepositorySearchPagePure | ||
query="test query" | ||
repositories={{ type: "error", message: "Test error" }} | ||
onQueryChange={jest.fn()} | ||
/>, | ||
renderOptions | ||
); | ||
expect(screen.getByText(/Test error/)).toBeVisible(); | ||
}); | ||
|
||
it("should render proper structure in loaded mode, no results", function () { | ||
render( | ||
<RepositorySearchPagePure | ||
query="test query" | ||
repositories={{ type: "loaded", data: [] }} | ||
onQueryChange={jest.fn()} | ||
/>, | ||
renderOptions | ||
); | ||
expect(screen.getByText("No repositories found")).toBeVisible(); | ||
}); | ||
|
||
it("should render proper structure in loaded mode, few results", function () { | ||
render( | ||
<RepositorySearchPagePure | ||
query="test query" | ||
repositories={{ | ||
type: "loaded", | ||
data: [ | ||
{ | ||
id: 0, | ||
name: "Foo", | ||
description: "first repo", | ||
linkUrl: "https://google.com", | ||
owner: "Primo", | ||
numStars: 10, | ||
numForks: 100, | ||
}, | ||
{ | ||
id: 1, | ||
name: "Bar", | ||
description: "second repo", | ||
linkUrl: "https://google.com", | ||
owner: "Secondo", | ||
numStars: 20, | ||
numForks: 200, | ||
}, | ||
], | ||
}} | ||
onQueryChange={jest.fn()} | ||
/>, | ||
renderOptions | ||
); | ||
|
||
const cardViews = screen.getAllByTestId("repository-card"); | ||
|
||
const firstCardView = within(cardViews[0]); | ||
expect(firstCardView.getByText(/Foo/)).toBeVisible(); | ||
expect(firstCardView.getByText("first repo")).toBeVisible(); | ||
expect(firstCardView.getByText(/Primo/)).toBeVisible(); | ||
expect(firstCardView.getByTitle("Stars and forks")).toHaveTextContent( | ||
"10100" | ||
); | ||
|
||
const secondCardView = within(cardViews[1]); | ||
expect(secondCardView.getByText(/Bar/)).toBeVisible(); | ||
expect(secondCardView.getByText("second repo")).toBeVisible(); | ||
expect(secondCardView.getByText(/Secondo/)).toBeVisible(); | ||
expect(secondCardView.getByTitle("Stars and forks")).toHaveTextContent( | ||
"20200" | ||
); | ||
}); | ||
|
||
it("should show provided query in the search box", () => { | ||
render( | ||
<RepositorySearchPagePure | ||
query="findme123" | ||
repositories={{ type: "pending" }} | ||
onQueryChange={jest.fn()} | ||
/>, | ||
renderOptions | ||
); | ||
expect(screen.getByDisplayValue("findme123")).toBeVisible(); | ||
}); | ||
|
||
it("should allow to write query and click search", () => { | ||
const onSubmitMock = jest.fn(); | ||
render( | ||
<RepositorySearchPagePure | ||
query="react" | ||
repositories={{ type: "pending" }} | ||
onQueryChange={onSubmitMock} | ||
/>, | ||
renderOptions | ||
); | ||
userEvent.click(screen.getByDisplayValue("react")); | ||
userEvent.keyboard("ish"); | ||
userEvent.click(screen.getByText("Search")); | ||
|
||
expect(onSubmitMock).toBeCalledWith("reactish"); | ||
}); | ||
|
||
it("should allow to click search where the query is unchanged and there is an error", () => { | ||
const onSubmitMock = jest.fn(); | ||
render( | ||
<RepositorySearchPagePure | ||
query="react" | ||
repositories={{ type: "error", message: "try again" }} | ||
onQueryChange={onSubmitMock} | ||
/>, | ||
renderOptions | ||
); | ||
userEvent.click(screen.getByText("Search")); | ||
|
||
expect(onSubmitMock).toBeCalledWith("react"); | ||
}); | ||
|
||
it("should forbit to click search where the query is unchanged and there no error", () => { | ||
const onSubmitMock = jest.fn(); | ||
render( | ||
<RepositorySearchPagePure | ||
query="react" | ||
repositories={{ type: "loaded", data: [] }} | ||
onQueryChange={onSubmitMock} | ||
/>, | ||
renderOptions | ||
); | ||
expect(screen.getByText("Search")).toBeDisabled(); | ||
userEvent.click(screen.getByText("Search")); // click anyway | ||
expect(onSubmitMock).not.toBeCalled(); | ||
}); | ||
|
||
}); |