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

Commit6dbfe06

Browse files
authored
chore: add navigation test for workspace details page (#14629)
* chore: add tests for WorkspacePage cross-page navigation* fix: update story to use mock organizations menu
1 parent5db1400 commit6dbfe06

File tree

5 files changed

+116
-12
lines changed

5 files changed

+116
-12
lines changed

‎site/src/contexts/auth/RequireAuth.tsx‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,30 @@
11
import{API}from"api/api";
22
import{isApiError}from"api/errors";
33
import{Loader}from"components/Loader/Loader";
4-
import{ProxyProvider}from"contexts/ProxyContext";
5-
import{DashboardProvider}from"modules/dashboard/DashboardProvider";
4+
import{ProxyProviderasProductionProxyProvider}from"contexts/ProxyContext";
5+
import{DashboardProviderasProductionDashboardProvider}from"modules/dashboard/DashboardProvider";
66
import{typeFC,useEffect}from"react";
77
import{Navigate,Outlet,useLocation}from"react-router-dom";
88
import{embedRedirect}from"utils/redirect";
99
import{typeAuthContextValue,useAuthContext}from"./AuthProvider";
1010

11-
exportconstRequireAuth:FC=()=>{
11+
typeRequireAuthProps=Readonly<{
12+
ProxyProvider?:typeofProductionProxyProvider;
13+
DashboardProvider?:typeofProductionDashboardProvider;
14+
}>;
15+
16+
/**
17+
* Wraps any component and ensures that the user has been authenticated before
18+
* they can access the component's contents.
19+
*
20+
* In production, it is assumed that this component will not be called with any
21+
* props at all. But to make testing easier, you can call this component with
22+
* specific providers to mock them out.
23+
*/
24+
exportconstRequireAuth:FC<RequireAuthProps>=({
25+
DashboardProvider=ProductionDashboardProvider,
26+
ProxyProvider=ProductionProxyProvider,
27+
})=>{
1228
constlocation=useLocation();
1329
const{ signOut, isSigningOut, isSignedOut, isSignedIn, isLoading}=
1430
useAuthContext();

‎site/src/pages/WorkspacePage/WorkspacePage.test.tsx‎

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,19 @@ import userEvent from "@testing-library/user-event";
33
import*asapiModulefrom"api/api";
44
importtype{TemplateVersionParameter,Workspace}from"api/typesGenerated";
55
importEventSourceMockfrom"eventsourcemock";
6+
import{
7+
DashboardContext,
8+
typeDashboardProvider,
9+
}from"modules/dashboard/DashboardProvider";
610
import{http,HttpResponse}from"msw";
11+
importtype{FC}from"react";
12+
import{typeLocation,useLocation}from"react-router-dom";
713
import{
14+
MockAppearanceConfig,
815
MockDeploymentConfig,
16+
MockEntitlements,
917
MockFailedWorkspace,
18+
MockOrganization,
1019
MockOutdatedWorkspace,
1120
MockStartingWorkspace,
1221
MockStoppedWorkspace,
@@ -18,14 +27,22 @@ import {
1827
MockWorkspaceBuild,
1928
MockWorkspaceBuildDelete,
2029
}from"testHelpers/entities";
21-
import{renderWithAuth}from"testHelpers/renderHelpers";
30+
import{
31+
typeRenderWithAuthOptions,
32+
renderWithAuth,
33+
}from"testHelpers/renderHelpers";
2234
import{server}from"testHelpers/server";
2335
import{WorkspacePage}from"./WorkspacePage";
2436

2537
const{API, MissingBuildParameters}=apiModule;
2638

39+
typeRenderWorkspacePageOptions=Omit<RenderWithAuthOptions,"route"|"path">;
40+
2741
// Renders the workspace page and waits for it be loaded
28-
constrenderWorkspacePage=async(workspace:Workspace)=>{
42+
constrenderWorkspacePage=async(
43+
workspace:Workspace,
44+
options:RenderWorkspacePageOptions={},
45+
)=>{
2946
jest.spyOn(API,"getWorkspaceByOwnerAndName").mockResolvedValue(workspace);
3047
jest.spyOn(API,"getTemplate").mockResolvedValueOnce(MockTemplate);
3148
jest.spyOn(API,"getTemplateVersionRichParameters").mockResolvedValueOnce([]);
@@ -40,6 +57,7 @@ const renderWorkspacePage = async (workspace: Workspace) => {
4057
});
4158

4259
renderWithAuth(<WorkspacePage/>,{
60+
...options,
4361
route:`/@${workspace.owner_name}/${workspace.name}`,
4462
path:"/:username/:workspace",
4563
});
@@ -527,4 +545,69 @@ describe("WorkspacePage", () => {
527545
);
528546
});
529547
});
548+
549+
describe("Navigation to other pages",()=>{
550+
it("Shows a quota link when quota budget is greater than 0. Link navigates user to /workspaces route with the URL params populated with the corresponding organization",async()=>{
551+
jest.spyOn(API,"getWorkspaceQuota").mockResolvedValueOnce({
552+
budget:25,
553+
credits_consumed:2,
554+
});
555+
556+
constMockDashboardProvider:typeofDashboardProvider=({
557+
children,
558+
})=>(
559+
<DashboardContext.Provider
560+
value={{
561+
appearance:MockAppearanceConfig,
562+
entitlements:MockEntitlements,
563+
experiments:[],
564+
organizations:[MockOrganization],
565+
showOrganizations:true,
566+
}}
567+
>
568+
{children}
569+
</DashboardContext.Provider>
570+
);
571+
572+
letdestinationLocation!:Location;
573+
constMockWorkspacesPage:FC=()=>{
574+
destinationLocation=useLocation();
575+
returnnull;
576+
};
577+
578+
constworkspace:Workspace={
579+
...MockWorkspace,
580+
organization_name:MockOrganization.name,
581+
};
582+
583+
awaitrenderWorkspacePage(workspace,{
584+
mockAuthProviders:{
585+
DashboardProvider:MockDashboardProvider,
586+
},
587+
extraRoutes:[
588+
{
589+
path:"/workspaces",
590+
element:<MockWorkspacesPage/>,
591+
},
592+
],
593+
});
594+
595+
constquotaLink=awaitscreen.findByRole<HTMLAnchorElement>("link",{
596+
name:/\d+creditsof\d+/i,
597+
});
598+
599+
constorgName=encodeURIComponent(MockOrganization.name);
600+
expect(
601+
quotaLink.href.endsWith(`/workspaces?filter=organization:${orgName}`),
602+
).toBe(true);
603+
604+
constuser=userEvent.setup();
605+
awaituser.click(quotaLink);
606+
607+
expect(destinationLocation.pathname).toBe("/workspaces");
608+
expect(destinationLocation.search).toBe(
609+
`?filter=organization:${orgName}`,
610+
);
611+
});
612+
});
530613
});

‎site/src/pages/WorkspacePage/WorkspaceTopbar.tsx‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,7 @@ export const WorkspaceTopbar: FC<WorkspaceProps> = ({
147147
<OrganizationBreadcrumb
148148
orgName={orgDisplayName}
149149
orgIconUrl={activeOrg?.icon}
150-
orgPageUrl={
151-
showOrganizations
152-
?`/organizations/${encodeURIComponent(workspace.organization_name)}`
153-
:undefined
154-
}
150+
orgPageUrl={`/organizations/${encodeURIComponent(workspace.organization_name)}`}
155151
/>
156152
</>
157153
)}

‎site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ const defaultFilterProps = getDefaultFilterProps<FilterProps>({
9898
user:MockMenu,
9999
template:MockMenu,
100100
status:MockMenu,
101+
organizations:MockMenu,
101102
},
102103
values:{
103104
owner:MockUser.username,

‎site/src/testHelpers/renderHelpers.tsx‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import {
44
waitFor,
55
}from"@testing-library/react";
66
import{AppProviders}from"App";
7+
importtype{ProxyProvider}from"contexts/ProxyContext";
78
import{ThemeProvider}from"contexts/ThemeProvider";
89
import{RequireAuth}from"contexts/auth/RequireAuth";
910
import{DashboardLayout}from"modules/dashboard/DashboardLayout";
11+
importtype{DashboardProvider}from"modules/dashboard/DashboardProvider";
1012
import{ManagementSettingsLayout}from"pages/ManagementSettingsPage/ManagementSettingsLayout";
1113
import{TemplateSettingsLayout}from"pages/TemplateSettingsPage/TemplateSettingsLayout";
1214
import{WorkspaceSettingsLayout}from"pages/WorkspaceSettingsPage/WorkspaceSettingsLayout";
@@ -83,6 +85,11 @@ export type RenderWithAuthOptions = {
8385
nonAuthenticatedRoutes?:RouteObject[];
8486
// In case you want to render a layout inside of it
8587
children?:RouteObject["children"];
88+
89+
mockAuthProviders?:Readonly<{
90+
DashboardProvider?:typeofDashboardProvider;
91+
ProxyProvider?:typeofProxyProvider;
92+
}>;
8693
};
8794

8895
exportfunctionrenderWithAuth(
@@ -92,12 +99,13 @@ export function renderWithAuth(
9299
route="/",
93100
extraRoutes=[],
94101
nonAuthenticatedRoutes=[],
102+
mockAuthProviders={},
95103
children,
96104
}:RenderWithAuthOptions={},
97105
){
98106
constroutes:RouteObject[]=[
99107
{
100-
element:<RequireAuth/>,
108+
element:<RequireAuth{...mockAuthProviders}/>,
101109
children:[{ path, element, children}, ...extraRoutes],
102110
},
103111
...nonAuthenticatedRoutes,
@@ -108,8 +116,8 @@ export function renderWithAuth(
108116
);
109117

110118
return{
111-
user:MockUser,
112119
...renderResult,
120+
user:MockUser,
113121
};
114122
}
115123

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp