Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit4d42c07

Browse files
authored
chore(site): update and refactor all custom hook tests that rely on React Router (#12219)
* chore: rename useTab to useSearchParamsKey and add test* chore: mark old renderHookWithAuth as deprecated (temp)* fix: update imports for useResourcesNav* refactor: change API for useSearchParamsKey* chore: let user pass in their own URLSearchParams value* refactor: clean up comments for clarity* fix: update import* wip: commit progress on useWorkspaceDuplication revamp* chore: migrate duplication test to new helper* refactor: update code for clarity* refactor: reorder test cases for clarity* refactor: split off hook helper into separate file* refactor: remove reliance on internal React Router state property* refactor: move variables around for more clarity* refactor: more updates for clarity* refactor: reorganize test cases for clarity* refactor: clean up test cases for useWorkspaceDupe* refactor: clean up test cases for useWorkspaceDupe
1 parentcf4f56d commit4d42c07

File tree

11 files changed

+437
-157
lines changed

11 files changed

+437
-157
lines changed

‎site/src/hooks/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,3 @@ export * from "./useClickable";
22
export*from"./useClickableTableRow";
33
export*from"./useClipboard";
44
export*from"./usePagination";
5-
export*from"./useTab";

‎site/src/hooks/usePaginatedQuery.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import{waitFor}from"@testing-library/react";
2-
import{renderHookWithAuth}from"testHelpers/renderHelpers";
2+
import{renderHookWithAuth}from"testHelpers/hooks";
33
import{
44
typePaginatedData,
55
typeUsePaginatedQueryOptions,
@@ -23,9 +23,13 @@ function render<
2323
route?: `/?page=${string}`,
2424
){
2525
returnrenderHookWithAuth(({ options})=>usePaginatedQuery(options),{
26-
route,
27-
path:"/",
28-
initialProps:{ options},
26+
routingOptions:{
27+
route,
28+
path:"/",
29+
},
30+
renderOptions:{
31+
initialProps:{ options},
32+
},
2933
});
3034
}
3135

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import{act,waitFor}from"@testing-library/react";
2+
import{renderHookWithAuth}from"testHelpers/hooks";
3+
import{useSearchParamsKey}from"./useSearchParamsKey";
4+
5+
/**
6+
* Tried to extract the setup logic into one place, but it got surprisingly
7+
* messy. Went with straightforward approach of calling things individually
8+
*
9+
*@todo See if there's a way to test the interaction with the history object
10+
* (particularly, for replace behavior). It's traditionally very locked off, and
11+
* React Router gives you no way of interacting with it directly.
12+
*/
13+
describe(useSearchParamsKey.name,()=>{
14+
describe("Render behavior",()=>{
15+
it("Returns empty string if hook key does not exist in URL, and there is no default value",async()=>{
16+
const{ result}=awaitrenderHookWithAuth(
17+
()=>useSearchParamsKey({key:"blah"}),
18+
{routingOptions:{route:`/`}},
19+
);
20+
21+
expect(result.current.value).toEqual("");
22+
});
23+
24+
it("Returns out 'defaultValue' property if defined while hook key does not exist in URL",async()=>{
25+
constdefaultValue="dogs";
26+
const{ result}=awaitrenderHookWithAuth(
27+
()=>useSearchParamsKey({key:"blah", defaultValue}),
28+
{routingOptions:{route:`/`}},
29+
);
30+
31+
expect(result.current.value).toEqual(defaultValue);
32+
});
33+
34+
it("Returns out URL value if key exists in URL (always ignoring default value)",async()=>{
35+
constkey="blah";
36+
constvalue="cats";
37+
38+
const{ result}=awaitrenderHookWithAuth(
39+
()=>useSearchParamsKey({ key,defaultValue:"I don't matter"}),
40+
{routingOptions:{route:`/?${key}=${value}`}},
41+
);
42+
43+
expect(result.current.value).toEqual(value);
44+
});
45+
46+
it("Does not have methods change previous values if 'key' argument changes during re-renders",async()=>{
47+
constreadonlyKey="readonlyKey";
48+
constmutableKey="mutableKey";
49+
constinitialReadonlyValue="readonly";
50+
constinitialMutableValue="mutable";
51+
52+
const{ result, rerender, getLocationSnapshot}=
53+
awaitrenderHookWithAuth(({ key})=>useSearchParamsKey({ key}),{
54+
routingOptions:{
55+
route:`/?${readonlyKey}=${initialReadonlyValue}&${mutableKey}=${initialMutableValue}`,
56+
},
57+
renderOptions:{initialProps:{key:readonlyKey}},
58+
});
59+
60+
constswapValue="dogs";
61+
awaitrerender({key:mutableKey});
62+
act(()=>result.current.setValue(swapValue));
63+
awaitwaitFor(()=>expect(result.current.value).toEqual(swapValue));
64+
65+
constsnapshot1=getLocationSnapshot();
66+
expect(snapshot1.search.get(readonlyKey)).toEqual(initialReadonlyValue);
67+
expect(snapshot1.search.get(mutableKey)).toEqual(swapValue);
68+
69+
act(()=>result.current.deleteValue());
70+
awaitwaitFor(()=>expect(result.current.value).toEqual(""));
71+
72+
constsnapshot2=getLocationSnapshot();
73+
expect(snapshot2.search.get(readonlyKey)).toEqual(initialReadonlyValue);
74+
expect(snapshot2.search.get(mutableKey)).toEqual(null);
75+
});
76+
});
77+
78+
describe("setValue method",()=>{
79+
it("Updates state and URL when called with a new value",async()=>{
80+
constkey="blah";
81+
constinitialValue="cats";
82+
83+
const{ result, getLocationSnapshot}=awaitrenderHookWithAuth(
84+
()=>useSearchParamsKey({ key}),
85+
{routingOptions:{route:`/?${key}=${initialValue}`}},
86+
);
87+
88+
constnewValue="dogs";
89+
act(()=>result.current.setValue(newValue));
90+
awaitwaitFor(()=>expect(result.current.value).toEqual(newValue));
91+
92+
const{ search}=getLocationSnapshot();
93+
expect(search.get(key)).toEqual(newValue);
94+
});
95+
});
96+
97+
describe("deleteValue method",()=>{
98+
it("Clears value for the given key from the state and URL when called",async()=>{
99+
constkey="blah";
100+
constinitialValue="cats";
101+
102+
const{ result, getLocationSnapshot}=awaitrenderHookWithAuth(
103+
()=>useSearchParamsKey({ key}),
104+
{routingOptions:{route:`/?${key}=${initialValue}`}},
105+
);
106+
107+
act(()=>result.current.deleteValue());
108+
awaitwaitFor(()=>expect(result.current.value).toEqual(""));
109+
110+
const{ search}=getLocationSnapshot();
111+
expect(search.get(key)).toEqual(null);
112+
});
113+
});
114+
115+
describe("Override behavior",()=>{
116+
it("Will dispatch state changes through custom URLSearchParams value if provided",async()=>{
117+
constkey="love";
118+
constinitialValue="dogs";
119+
constcustomParams=newURLSearchParams({[key]:initialValue});
120+
121+
const{ result}=awaitrenderHookWithAuth(
122+
({ key})=>useSearchParamsKey({ key,searchParams:customParams}),
123+
{
124+
routingOptions:{route:`/?=${key}=${initialValue}`},
125+
renderOptions:{initialProps:{ key}},
126+
},
127+
);
128+
129+
constnewValue="all animals";
130+
act(()=>result.current.setValue(newValue));
131+
awaitwaitFor(()=>expect(customParams.get(key)).toEqual(newValue));
132+
});
133+
});
134+
});

‎site/src/hooks/useSearchParamsKey.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import{useSearchParams}from"react-router-dom";
2+
3+
exporttypeUseSearchParamsKeyConfig=Readonly<{
4+
key:string;
5+
searchParams?:URLSearchParams;
6+
defaultValue?:string;
7+
replace?:boolean;
8+
}>;
9+
10+
exporttypeUseSearchParamKeyResult=Readonly<{
11+
value:string;
12+
setValue:(newValue:string)=>void;
13+
deleteValue:()=>void;
14+
}>;
15+
16+
exportconstuseSearchParamsKey=(
17+
config:UseSearchParamsKeyConfig,
18+
):UseSearchParamKeyResult=>{
19+
// Cannot use function update form for setSearchParams, because by default, it
20+
// will always be linked to innerSearchParams, ignoring the config's params
21+
const[innerSearchParams,setSearchParams]=useSearchParams();
22+
23+
const{
24+
key,
25+
searchParams=innerSearchParams,
26+
defaultValue="",
27+
replace=true,
28+
}=config;
29+
30+
return{
31+
value:searchParams.get(key)??defaultValue,
32+
setValue:(newValue)=>{
33+
searchParams.set(key,newValue);
34+
setSearchParams(searchParams,{ replace});
35+
},
36+
deleteValue:()=>{
37+
searchParams.delete(key);
38+
setSearchParams(searchParams,{ replace});
39+
},
40+
};
41+
};

‎site/src/hooks/useTab.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,45 @@
1-
import{waitFor}from"@testing-library/react";
1+
import{act,waitFor}from"@testing-library/react";
22
importtype{Workspace}from"api/typesGenerated";
3-
import*asMfrom"testHelpers/entities";
4-
import{renderHookWithAuth}from"testHelpers/renderHelpers";
3+
import{MockWorkspace}from"testHelpers/entities";
4+
import{
5+
typeGetLocationSnapshot,
6+
renderHookWithAuth,
7+
}from"testHelpers/hooks";
8+
import*asMfrom"../../testHelpers/entities";
59
importCreateWorkspacePagefrom"./CreateWorkspacePage";
610
import{useWorkspaceDuplication}from"./useWorkspaceDuplication";
711

812
functionrender(workspace?:Workspace){
913
returnrenderHookWithAuth(
10-
({ workspace})=>{
11-
returnuseWorkspaceDuplication(workspace);
12-
},
14+
({ workspace})=>useWorkspaceDuplication(workspace),
1315
{
14-
initialProps:{ workspace},
15-
extraRoutes:[
16-
{
17-
path:"/templates/:template/workspace",
18-
element:<CreateWorkspacePage/>,
19-
},
20-
],
16+
renderOptions:{initialProps:{ workspace}},
17+
routingOptions:{
18+
extraRoutes:[
19+
{
20+
path:"/templates/:template/workspace",
21+
element:<CreateWorkspacePage/>,
22+
},
23+
],
24+
},
2125
},
2226
);
2327
}
2428

2529
typeRenderResult=Awaited<ReturnType<typeofrender>>;
30+
typeRenderHookResult=RenderResult["result"];
2631

2732
asyncfunctionperformNavigation(
28-
result:RenderResult["result"],
29-
router:RenderResult["router"],
33+
result:RenderHookResult,
34+
getLocationSnapshot:GetLocationSnapshot,
3035
){
3136
awaitwaitFor(()=>expect(result.current.isDuplicationReady).toBe(true));
32-
result.current.duplicateWorkspace();
37+
act(()=>result.current.duplicateWorkspace());
3338

39+
consttemplateName=MockWorkspace.template_name;
3440
returnwaitFor(()=>{
35-
expect(router.state.location.pathname).toEqual(
36-
`/templates/${M.MockWorkspace.template_name}/workspace`,
37-
);
41+
const{ pathname}=getLocationSnapshot();
42+
expect(pathname).toEqual(`/templates/${templateName}/workspace`);
3843
});
3944
}
4045

@@ -44,28 +49,23 @@ describe(`${useWorkspaceDuplication.name}`, () => {
4449
expect(result.current.isDuplicationReady).toBe(false);
4550

4651
for(leti=0;i<10;i++){
47-
rerender({workspace:undefined});
52+
awaitrerender({workspace:undefined});
4853
expect(result.current.isDuplicationReady).toBe(false);
4954
}
5055
});
5156

5257
it("Will become ready when workspace is provided and build params are successfully fetched",async()=>{
53-
const{ result}=awaitrender(M.MockWorkspace);
54-
58+
const{ result}=awaitrender(MockWorkspace);
5559
expect(result.current.isDuplicationReady).toBe(false);
5660
awaitwaitFor(()=>expect(result.current.isDuplicationReady).toBe(true));
5761
});
5862

5963
it("Is able to navigate the user to the workspace creation page",async()=>{
60-
const{ result,router}=awaitrender(M.MockWorkspace);
61-
awaitperformNavigation(result,router);
64+
const{ result,getLocationSnapshot}=awaitrender(MockWorkspace);
65+
awaitperformNavigation(result,getLocationSnapshot);
6266
});
6367

6468
test("Navigating populates the URL search params with the workspace's build params",async()=>{
65-
const{ result, router}=awaitrender(M.MockWorkspace);
66-
awaitperformNavigation(result,router);
67-
68-
constparsedParams=newURLSearchParams(router.state.location.search);
6969
constmockBuildParams=[
7070
M.MockWorkspaceBuildParameter1,
7171
M.MockWorkspaceBuildParameter2,
@@ -74,25 +74,29 @@ describe(`${useWorkspaceDuplication.name}`, () => {
7474
M.MockWorkspaceBuildParameter5,
7575
];
7676

77+
const{ result, getLocationSnapshot}=awaitrender(MockWorkspace);
78+
awaitperformNavigation(result,getLocationSnapshot);
79+
80+
const{ search}=getLocationSnapshot();
7781
for(const{ name, value}ofmockBuildParams){
7882
constkey=`param.${name}`;
79-
expect(parsedParams.get(key)).toEqual(value);
83+
expect(search.get(key)).toEqual(value);
8084
}
8185
});
8286

8387
test("Navigating appends other necessary metadata to the search params",async()=>{
84-
const{ result, router}=awaitrender(M.MockWorkspace);
85-
awaitperformNavigation(result,router);
86-
87-
constparsedParams=newURLSearchParams(router.state.location.search);
88-
constextraMetadataEntries=[
88+
constextraMetadataEntries:readonly[string,string][]=[
8989
["mode","duplicate"],
90-
["name",`${M.MockWorkspace.name}-copy`],
91-
["version",M.MockWorkspace.template_active_version_id],
92-
]asconst;
90+
["name",`${MockWorkspace.name}-copy`],
91+
["version",MockWorkspace.template_active_version_id],
92+
];
93+
94+
const{ result, getLocationSnapshot}=awaitrender(MockWorkspace);
95+
awaitperformNavigation(result,getLocationSnapshot);
9396

97+
const{ search}=getLocationSnapshot();
9498
for(const[key,value]ofextraMetadataEntries){
95-
expect(parsedParams.get(key)).toBe(value);
99+
expect(search.get(key)).toBe(value);
96100
}
97101
});
98102
});

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp