- Notifications
You must be signed in to change notification settings - Fork928
chore(site): update and refactor all custom hook tests that rely on React Router#12219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
0b186bf
a7a0944
ec3ba4f
6d7ef9f
afffb26
590f02b
9e00ea6
d7110c5
2cc63d7
26089e3
8057397
f8f87a3
dcc89dc
1ab6f03
b32603f
b0fabde
b94decc
3e41877
1fcf9e2
13f5e53
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import { act, waitFor } from "@testing-library/react"; | ||
import { renderHookWithAuth } from "testHelpers/hooks"; | ||
import { useSearchParamsKey } from "./useSearchParamsKey"; | ||
/** | ||
* Tried to extract the setup logic into one place, but it got surprisingly | ||
* messy. Went with straightforward approach of calling things individually | ||
* | ||
* @todo See if there's a way to test the interaction with the history object | ||
* (particularly, for replace behavior). It's traditionally very locked off, and | ||
* React Router gives you no way of interacting with it directly. | ||
*/ | ||
describe(useSearchParamsKey.name, () => { | ||
describe("Render behavior", () => { | ||
it("Returns empty string if hook key does not exist in URL, and there is no default value", async () => { | ||
const { result } = await renderHookWithAuth( | ||
() => useSearchParamsKey({ key: "blah" }), | ||
{ routingOptions: { route: `/` } }, | ||
); | ||
expect(result.current.value).toEqual(""); | ||
}); | ||
it("Returns out 'defaultValue' property if defined while hook key does not exist in URL", async () => { | ||
const defaultValue = "dogs"; | ||
const { result } = await renderHookWithAuth( | ||
() => useSearchParamsKey({ key: "blah", defaultValue }), | ||
{ routingOptions: { route: `/` } }, | ||
); | ||
expect(result.current.value).toEqual(defaultValue); | ||
}); | ||
it("Returns out URL value if key exists in URL (always ignoring default value)", async () => { | ||
const key = "blah"; | ||
const value = "cats"; | ||
const { result } = await renderHookWithAuth( | ||
() => useSearchParamsKey({ key, defaultValue: "I don't matter" }), | ||
{ routingOptions: { route: `/?${key}=${value}` } }, | ||
); | ||
expect(result.current.value).toEqual(value); | ||
}); | ||
it("Does not have methods change previous values if 'key' argument changes during re-renders", async () => { | ||
const readonlyKey = "readonlyKey"; | ||
const mutableKey = "mutableKey"; | ||
const initialReadonlyValue = "readonly"; | ||
const initialMutableValue = "mutable"; | ||
const { result, rerender, getLocationSnapshot } = | ||
await renderHookWithAuth(({ key }) => useSearchParamsKey({ key }), { | ||
routingOptions: { | ||
route: `/?${readonlyKey}=${initialReadonlyValue}&${mutableKey}=${initialMutableValue}`, | ||
}, | ||
renderOptions: { initialProps: { key: readonlyKey } }, | ||
}); | ||
const swapValue = "dogs"; | ||
await rerender({ key: mutableKey }); | ||
act(() => result.current.setValue(swapValue)); | ||
await waitFor(() => expect(result.current.value).toEqual(swapValue)); | ||
const snapshot1 = getLocationSnapshot(); | ||
expect(snapshot1.search.get(readonlyKey)).toEqual(initialReadonlyValue); | ||
expect(snapshot1.search.get(mutableKey)).toEqual(swapValue); | ||
act(() => result.current.deleteValue()); | ||
await waitFor(() => expect(result.current.value).toEqual("")); | ||
const snapshot2 = getLocationSnapshot(); | ||
expect(snapshot2.search.get(readonlyKey)).toEqual(initialReadonlyValue); | ||
expect(snapshot2.search.get(mutableKey)).toEqual(null); | ||
}); | ||
}); | ||
describe("setValue method", () => { | ||
it("Updates state and URL when called with a new value", async () => { | ||
const key = "blah"; | ||
const initialValue = "cats"; | ||
const { result, getLocationSnapshot } = await renderHookWithAuth( | ||
() => useSearchParamsKey({ key }), | ||
{ routingOptions: { route: `/?${key}=${initialValue}` } }, | ||
); | ||
const newValue = "dogs"; | ||
act(() => result.current.setValue(newValue)); | ||
await waitFor(() => expect(result.current.value).toEqual(newValue)); | ||
const { search } = getLocationSnapshot(); | ||
expect(search.get(key)).toEqual(newValue); | ||
}); | ||
}); | ||
describe("deleteValue method", () => { | ||
it("Clears value for the given key from the state and URL when called", async () => { | ||
const key = "blah"; | ||
const initialValue = "cats"; | ||
const { result, getLocationSnapshot } = await renderHookWithAuth( | ||
() => useSearchParamsKey({ key }), | ||
{ routingOptions: { route: `/?${key}=${initialValue}` } }, | ||
); | ||
act(() => result.current.deleteValue()); | ||
await waitFor(() => expect(result.current.value).toEqual("")); | ||
const { search } = getLocationSnapshot(); | ||
expect(search.get(key)).toEqual(null); | ||
}); | ||
}); | ||
describe("Override behavior", () => { | ||
it("Will dispatch state changes through custom URLSearchParams value if provided", async () => { | ||
const key = "love"; | ||
const initialValue = "dogs"; | ||
const customParams = new URLSearchParams({ [key]: initialValue }); | ||
const { result } = await renderHookWithAuth( | ||
({ key }) => useSearchParamsKey({ key, searchParams: customParams }), | ||
{ | ||
routingOptions: { route: `/?=${key}=${initialValue}` }, | ||
renderOptions: { initialProps: { key } }, | ||
}, | ||
); | ||
const newValue = "all animals"; | ||
act(() => result.current.setValue(newValue)); | ||
await waitFor(() => expect(customParams.get(key)).toEqual(newValue)); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { useSearchParams } from "react-router-dom"; | ||
export type UseSearchParamsKeyConfig = Readonly<{ | ||
key: string; | ||
searchParams?: URLSearchParams; | ||
defaultValue?: string; | ||
replace?: boolean; | ||
}>; | ||
export type UseSearchParamKeyResult = Readonly<{ | ||
value: string; | ||
setValue: (newValue: string) => void; | ||
deleteValue: () => void; | ||
}>; | ||
export const useSearchParamsKey = ( | ||
config: UseSearchParamsKeyConfig, | ||
): UseSearchParamKeyResult => { | ||
// Cannot use function update form for setSearchParams, because by default, it | ||
// will always be linked to innerSearchParams, ignoring the config's params | ||
const [innerSearchParams, setSearchParams] = useSearchParams(); | ||
const { | ||
key, | ||
searchParams = innerSearchParams, | ||
defaultValue = "", | ||
replace = true, | ||
} = config; | ||
return { | ||
value: searchParams.get(key) ?? defaultValue, | ||
setValue: (newValue) => { | ||
searchParams.set(key, newValue); | ||
setSearchParams(searchParams, { replace }); | ||
}, | ||
deleteValue: () => { | ||
searchParams.delete(key); | ||
setSearchParams(searchParams, { replace }); | ||
}, | ||
}; | ||
}; |
This file was deleted.
Uh oh!
There was an error while loading.Please reload this page.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,40 +1,45 @@ | ||
import {act,waitFor } from "@testing-library/react"; | ||
import type { Workspace } from "api/typesGenerated"; | ||
import { MockWorkspace } from "testHelpers/entities"; | ||
import { | ||
type GetLocationSnapshot, | ||
renderHookWithAuth, | ||
} from "testHelpers/hooks"; | ||
import * as M from "../../testHelpers/entities"; | ||
import CreateWorkspacePage from "./CreateWorkspacePage"; | ||
import { useWorkspaceDuplication } from "./useWorkspaceDuplication"; | ||
function render(workspace?: Workspace) { | ||
return renderHookWithAuth( | ||
({ workspace }) => useWorkspaceDuplication(workspace), | ||
{ | ||
renderOptions: { initialProps: { workspace } }, | ||
routingOptions: { | ||
extraRoutes: [ | ||
{ | ||
path: "/templates/:template/workspace", | ||
element: <CreateWorkspacePage />, | ||
}, | ||
], | ||
}, | ||
}, | ||
); | ||
} | ||
type RenderResult = Awaited<ReturnType<typeof render>>; | ||
type RenderHookResult = RenderResult["result"]; | ||
async function performNavigation( | ||
result:RenderHookResult, | ||
getLocationSnapshot: GetLocationSnapshot, | ||
) { | ||
await waitFor(() => expect(result.current.isDuplicationReady).toBe(true)); | ||
act(() =>result.current.duplicateWorkspace()); | ||
const templateName = MockWorkspace.template_name; | ||
return waitFor(() => { | ||
const { pathname } = getLocationSnapshot(); | ||
expect(pathname).toEqual(`/templates/${templateName}/workspace`); | ||
}); | ||
} | ||
@@ -44,28 +49,23 @@ describe(`${useWorkspaceDuplication.name}`, () => { | ||
expect(result.current.isDuplicationReady).toBe(false); | ||
for (let i = 0; i < 10; i++) { | ||
awaitrerender({ workspace: undefined }); | ||
expect(result.current.isDuplicationReady).toBe(false); | ||
} | ||
}); | ||
it("Will become ready when workspace is provided and build params are successfully fetched", async () => { | ||
const { result } = await render(MockWorkspace); | ||
expect(result.current.isDuplicationReady).toBe(false); | ||
await waitFor(() => expect(result.current.isDuplicationReady).toBe(true)); | ||
}); | ||
it("Is able to navigate the user to the workspace creation page", async () => { | ||
const { result,getLocationSnapshot } = await render(MockWorkspace); | ||
await performNavigation(result,getLocationSnapshot); | ||
}); | ||
test("Navigating populates the URL search params with the workspace's build params", async () => { | ||
const mockBuildParams = [ | ||
M.MockWorkspaceBuildParameter1, | ||
M.MockWorkspaceBuildParameter2, | ||
@@ -74,25 +74,29 @@ describe(`${useWorkspaceDuplication.name}`, () => { | ||
M.MockWorkspaceBuildParameter5, | ||
]; | ||
const { result, getLocationSnapshot } = await render(MockWorkspace); | ||
await performNavigation(result, getLocationSnapshot); | ||
const { search } = getLocationSnapshot(); | ||
for (const { name, value } of mockBuildParams) { | ||
const key = `param.${name}`; | ||
expect(search.get(key)).toEqual(value); | ||
} | ||
}); | ||
test("Navigating appends other necessary metadata to the search params", async () => { | ||
const extraMetadataEntries: readonly [string, string][] = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. why the switch from MemberAuthor
| ||
["mode", "duplicate"], | ||
["name", `${MockWorkspace.name}-copy`], | ||
["version", MockWorkspace.template_active_version_id], | ||
]; | ||
const { result, getLocationSnapshot } = await render(MockWorkspace); | ||
await performNavigation(result, getLocationSnapshot); | ||
const { search } = getLocationSnapshot(); | ||
for (const [key, value] of extraMetadataEntries) { | ||
expect(search.get(key)).toBe(value); | ||
} | ||
}); | ||
}); |
Uh oh!
There was an error while loading.Please reload this page.