Skip to content

Commit

Permalink
fix: workspace provider to use the correct last used workspace
Browse files Browse the repository at this point in the history
  • Loading branch information
sahil143 committed Dec 13, 2024
1 parent 61e9ed9 commit 253acdd
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 13 deletions.
207 changes: 207 additions & 0 deletions src/components/Workspace/__tests__/workspace-context.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { useContext } from 'react';
import { useQuery } from '@tanstack/react-query';
import { render, screen, waitFor } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { createReactRouterMock } from '../../../utils/test-utils';
import { getLastUsedWorkspace, setLastUsedWorkspace } from '../utils';
import { WorkspaceProvider, WorkspaceContext } from '../workspace-context';

jest.mock('@tanstack/react-query', () => ({
...jest.requireActual('@tanstack/react-query'),
useQuery: jest.fn(),
}));

jest.mock('../utils', () => ({
...jest.requireActual('../utils'),
createWorkspaceQueryOptions: jest.fn(),
getLastUsedWorkspace: jest.fn(),
setLastUsedWorkspace: jest.fn(),
}));

// Test data
const mockWorkspaces = [
{
metadata: {
name: 'workspace-1',
},
status: {
namespaces: [{ type: 'default', name: 'test-namespace' }],
type: 'home',
},
},
{
metadata: {
name: 'workspace-2',
},
status: {
namespaces: [{ type: 'default', name: 'test-namespace-2' }],
},
},
];

const mockNamespace = 'test-namespace';

const mockUseNavigate = createReactRouterMock('useNavigate');
const mockUseParams = createReactRouterMock('useParams');
const mockUseQuery = useQuery as jest.Mock;
const mockGetLastUsedWorkspace = getLastUsedWorkspace as jest.Mock;

describe('WorkspaceProvider', () => {
const mockNavigate = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
mockUseNavigate.mockReturnValue(mockNavigate);
mockUseParams.mockReturnValue({});
mockGetLastUsedWorkspace.mockReturnValue('workspace-1');
});

it('should renders loading spinner when data is being fetched', () => {
mockUseQuery
.mockReturnValueOnce({
data: undefined,
isLoading: true,
})
.mockReturnValueOnce({
data: undefined,
isLoading: true,
});

render(
<WorkspaceProvider>
<div>Child content</div>
</WorkspaceProvider>,
);

expect(screen.getByRole('progressbar')).toBeInTheDocument();
expect(screen.queryByText('Child content')).not.toBeInTheDocument();
});

it('should renders children when data is loaded', async () => {
mockUseQuery
.mockReturnValueOnce({
data: mockWorkspaces,
isLoading: false,
})
.mockReturnValue({
data: mockWorkspaces[0],
isLoading: false,
});

render(
<WorkspaceProvider>
<div>Child content</div>
</WorkspaceProvider>,
);

await waitFor(() => {
expect(screen.getByText('Child content')).toBeInTheDocument();
});
});

it('handles error state correctly', () => {
const errorMessage = 'Failed to load workspace';
mockUseQuery
.mockReturnValueOnce({
data: mockWorkspaces,
isLoading: false,
})
.mockReturnValueOnce({
error: new Error(errorMessage),
isLoading: false,
});

render(
<WorkspaceProvider>
<div>Child content</div>
</WorkspaceProvider>,
);

expect(screen.getByText(`Unable to access workspace workspace-1`)).toBeInTheDocument();
expect(screen.getByText(errorMessage)).toBeInTheDocument();
});

it('provides correct context values', async () => {
mockUseQuery
.mockReturnValueOnce({
data: mockWorkspaces,
isLoading: false,
})
.mockReturnValueOnce({
data: mockWorkspaces[0],
isLoading: false,
});

const TestConsumer = () => {
const context = useContext(WorkspaceContext);
return (
<div>
<div data-test="namespace">{context.namespace}</div>
<div data-test="workspace">{context.workspace}</div>
<div data-test="workspaces-loaded">{String(context.workspacesLoaded)}</div>
</div>
);
};

render(
<WorkspaceProvider>
<TestConsumer />
</WorkspaceProvider>,
);

await waitFor(() => {
expect(screen.getByTestId('namespace')).toHaveTextContent(mockNamespace);
expect(screen.getByTestId('workspace')).toHaveTextContent('workspace-1');
expect(screen.getByTestId('workspaces-loaded')).toHaveTextContent('true');
});
});

it('updates last used workspace when active workspace changes', async () => {
mockUseQuery
.mockReturnValueOnce({
data: mockWorkspaces,
isLoading: false,
})
.mockReturnValueOnce({
data: mockWorkspaces[0],
isLoading: false,
});

mockUseParams.mockReturnValue({ workspaceName: 'workspace-2' });

render(
<WorkspaceProvider>
<div>Child content</div>
</WorkspaceProvider>,
);

await waitFor(() => {
expect(setLastUsedWorkspace).toHaveBeenCalledWith('workspace-2');
});
});

it('navigates to home workspace when error occurs and home button is clicked', async () => {
const errorMessage = 'Failed to load workspace';
mockUseQuery
.mockReturnValueOnce({
data: mockWorkspaces,
isLoading: false,
})
.mockReturnValueOnce({
error: new Error(errorMessage),
isLoading: false,
});

render(
<WorkspaceProvider>
<div>Child content</div>
</WorkspaceProvider>,
);

const homeButton = screen.getByText('Go to workspace-1 workspace');
await userEvent.click(homeButton);

expect(setLastUsedWorkspace).toHaveBeenCalledWith('workspace-1');
expect(mockNavigate).toHaveBeenCalledWith('/workspaces/workspace-1/applications');
});
});
51 changes: 39 additions & 12 deletions src/components/Workspace/workspace-context.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as React from 'react';
import { useParams } from 'react-router-dom';
import { Bullseye, Spinner } from '@patternfly/react-core';
import { useNavigate, useParams } from 'react-router-dom';
import { Bullseye, Button, Spinner } from '@patternfly/react-core';
import { useQuery } from '@tanstack/react-query';
import { RouterParams } from '../../routes/utils';
import ErrorEmptyState from '../../shared/components/empty-state/ErrorEmptyState';
import { Workspace } from '../../types';
import {
createWorkspaceQueryOptions,
Expand Down Expand Up @@ -33,26 +34,52 @@ export const WorkspaceContext = React.createContext<WorkspaceContextData>({
export const WorkspaceProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
const { data: workspaces, isLoading: workspaceLoading } = useQuery(createWorkspaceQueryOptions());
const params = useParams<RouterParams>();
const navigate = useNavigate();

const homeWorkspace = React.useMemo(
() => (!workspaceLoading ? getHomeWorkspace(workspaces) ?? workspaces[0] : null),
[workspaces, workspaceLoading],

Check warning on line 41 in src/components/Workspace/workspace-context.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/Workspace/workspace-context.tsx#L41

Added line #L41 was not covered by tests
);

const activeWorkspaceName =
params.workspaceName ??
getLastUsedWorkspace() ??
getHomeWorkspace(workspaces)?.metadata?.name ??
workspaces[0]?.metadata?.name;
params.workspaceName ?? getLastUsedWorkspace() ?? homeWorkspace?.metadata?.name;

const { data: workspaceResource, isLoading: activeWorkspaceLoading } = useQuery(
createWorkspaceQueryOptions(activeWorkspaceName),
);
const {
data: workspaceResource,
isLoading: activeWorkspaceLoading,
error,

Check warning on line 50 in src/components/Workspace/workspace-context.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/Workspace/workspace-context.tsx#L50

Added line #L50 was not covered by tests
} = useQuery({ ...createWorkspaceQueryOptions(activeWorkspaceName), retry: false });

const namespace = !activeWorkspaceLoading
? getDefaultNsForWorkspace(workspaceResource)?.name
: undefined;

React.useEffect(() => {
if (getLastUsedWorkspace() !== activeWorkspaceName) {
if (!error && getLastUsedWorkspace() !== activeWorkspaceName) {
setLastUsedWorkspace(activeWorkspaceName);
}
}, [activeWorkspaceName]);
}, [activeWorkspaceName, error]);

if (error) {
return (
<ErrorEmptyState
title={`Unable to access workspace ${activeWorkspaceName}`}
body={error.message}
>
{homeWorkspace ? (
<Button
variant="primary"
onClick={() => {
setLastUsedWorkspace(homeWorkspace.metadata.name);
navigate(`/workspaces/${homeWorkspace.metadata.name}/applications`);
}}
>
Go to {homeWorkspace.metadata.name} workspace
</Button>
) : null}
</ErrorEmptyState>
);
}

return (
<WorkspaceContext.Provider
Expand All @@ -65,7 +92,7 @@ export const WorkspaceProvider: React.FC<React.PropsWithChildren> = ({ children
lastUsedWorkspace: getLastUsedWorkspace(),
}}
>
{!(workspaceLoading && activeWorkspaceLoading) ? (
{!(workspaceLoading || activeWorkspaceLoading) ? (
children
) : (
<Bullseye>
Expand Down
2 changes: 1 addition & 1 deletion src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export const router = createBrowserRouter([
element: <ImportForm />,
},
{
path: `/workspaces/:${RouterParams.workspaceName}/applications`,
path: `workspaces/:${RouterParams.workspaceName}/applications`,
loader: applicationPageLoader,
element: <ApplicationListView />,
errorElement: <RouteErrorBoundry />,
Expand Down

0 comments on commit 253acdd

Please sign in to comment.