refactor(tests): wrap tests explicitly with provider [EE-6686] (#11090)

pull/11350/head
Chaim Lev-Ari 9 months ago committed by GitHub
parent 27aaf322b2
commit f8e3d75797
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -45,6 +45,12 @@ rules:
pathGroupsExcludedImportTypes: ['internal'],
},
]
no-restricted-imports:
- error
- patterns:
- group:
- '@/react/test-utils/*'
message: 'These utils are just for test files'
settings:
'import/resolver':
@ -113,6 +119,12 @@ overrides:
'no-await-in-loop': 'off'
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }]
'regex/invalid': ['error', [{ 'regex': '<Icon icon="(.*)"', 'message': 'Please directly import the `lucide-react` icon instead of using the string' }]]
'@typescript-eslint/no-restricted-imports':
- error
- patterns:
- group:
- '@/react/test-utils/*'
message: 'These utils are just for test files'
overrides: # allow props spreading for hoc files
- files:
- app/**/with*.ts{,x}
@ -126,7 +138,11 @@ overrides:
'vitest/env': true
rules:
'react/jsx-no-constructed-context-values': off
'@typescript-eslint/no-restricted-imports': off
no-restricted-imports: off
- files:
- app/**/*.stories.*
rules:
'no-alert': off
'@typescript-eslint/no-restricted-imports': off
no-restricted-imports: off

@ -1,37 +0,0 @@
import 'vitest-dom/extend-expect';
import { render, RenderOptions } from '@testing-library/react';
import { UIRouter, pushStateLocationPlugin } from '@uirouter/react';
import { PropsWithChildren, ReactElement } from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
function Provider({ children }: PropsWithChildren<unknown>) {
return <UIRouter plugins={[pushStateLocationPlugin]}>{children}</UIRouter>;
}
function customRender(ui: ReactElement, options?: RenderOptions) {
return render(ui, { wrapper: Provider, ...options });
}
// re-export everything
export * from '@testing-library/react';
// override render method
export { customRender as render };
export function renderWithQueryClient(ui: React.ReactElement) {
const testQueryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
const { rerender, ...result } = customRender(
<QueryClientProvider client={testQueryClient}>{ui}</QueryClientProvider>
);
return {
...result,
rerender: (rerenderUi: React.ReactElement) =>
rerender(
<QueryClientProvider client={testQueryClient}>
{rerenderUi}
</QueryClientProvider>
),
};
}

@ -1,13 +1,15 @@
import { http, HttpResponse } from 'msw';
import { render, within } from '@testing-library/react';
import { renderWithQueryClient, within } from '@/react-tools/test-utils';
import { UserContext } from '@/react/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user';
import { server } from '@/setup-tests/server';
import {
createMockResourceGroups,
createMockSubscriptions,
} from '@/react-tools/test-mocks';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { DashboardView } from './DashboardView';
@ -105,7 +107,6 @@ async function renderComponent(
resourceGroupsStatus = 200
) {
const user = new UserViewModel({ Username: 'user' });
const state = { user };
server.use(
http.get('/api/endpoints/1', () => HttpResponse.json({})),
@ -135,12 +136,13 @@ async function renderComponent(
}
)
);
const renderResult = renderWithQueryClient(
<UserContext.Provider value={state}>
<DashboardView />
</UserContext.Provider>
const Wrapped = withTestQueryProvider(
withUserProvider(withTestRouter(DashboardView), user)
);
const renderResult = render(<Wrapped />);
await expect(renderResult.findByText(/Home/)).resolves.toBeVisible();
return renderResult;

@ -1,10 +1,12 @@
import userEvent from '@testing-library/user-event';
import { HttpResponse } from 'msw';
import { HttpResponse, http } from 'msw';
import { render } from '@testing-library/react';
import { UserContext } from '@/react/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { http, server } from '@/setup-tests/server';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { server } from '@/setup-tests/server';
import { CreateContainerInstanceForm } from './CreateContainerInstanceForm';
@ -19,12 +21,10 @@ test('submit button should be disabled when name or image is missing', async ()
server.use(http.get('/api/endpoints/5', () => HttpResponse.json({})));
const user = new UserViewModel({ Username: 'user' });
const { findByText, getByText, getByLabelText } = renderWithQueryClient(
<UserContext.Provider value={{ user }}>
<CreateContainerInstanceForm />
</UserContext.Provider>
const Wrapped = withTestQueryProvider(
withUserProvider(withTestRouter(CreateContainerInstanceForm), user)
);
const { findByText, getByText, getByLabelText } = render(<Wrapped />);
await expect(findByText(/Azure settings/)).resolves.toBeVisible();

@ -1,4 +1,4 @@
import { render } from '@/react-tools/test-utils';
import { render } from '@testing-library/react';
import { Badge } from './Badge';

@ -1,6 +1,5 @@
import { Rocket } from 'lucide-react';
import { render, fireEvent } from '@/react-tools/test-utils';
import { render, fireEvent } from '@testing-library/react';
import { BoxSelector } from './BoxSelector';
import { BoxSelectorOption, Value } from './types';

@ -1,6 +1,5 @@
import { User } from 'lucide-react';
import { render } from '@/react-tools/test-utils';
import { render } from '@testing-library/react';
import { DashboardItem } from './DashboardItem';

@ -1,4 +1,4 @@
import { render } from '@/react-tools/test-utils';
import { render } from '@testing-library/react';
import { DetailsTable } from './index';

@ -1,5 +1,8 @@
import { render } from '@testing-library/react';
import { createMockEnvironment } from '@/react-tools/test-mocks';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { withTestQueryProvider } from '../test-utils/withTestQuery';
import { EdgeIndicator } from './EdgeIndicator';
@ -31,8 +34,10 @@ async function renderComponent(
environment.EdgeCheckinInterval = checkInInterval;
environment.QueryDate = queryDate;
const queries = renderWithQueryClient(
<EdgeIndicator environment={environment} showLastCheckInDate />
const Wrapped = withTestQueryProvider(EdgeIndicator);
const queries = render(
<Wrapped environment={environment} showLastCheckInDate />
);
await expect(queries.findByRole('status')).resolves.toBeVisible();

@ -1,9 +1,10 @@
import { FormikErrors } from 'formik';
import { ComponentProps } from 'react';
import { HttpResponse } from 'msw';
import { render, fireEvent } from '@testing-library/react';
import { renderWithQueryClient, fireEvent } from '@/react-tools/test-utils';
import { http, server } from '@/setup-tests/server';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { ImageConfigFieldset } from './ImageConfigFieldset';
import { Values } from './types';
@ -16,20 +17,20 @@ vi.mock('@uirouter/react', async (importOriginal: () => Promise<object>) => ({
}));
it('should render SimpleForm when useRegistry is true', () => {
const { getByText } = render({ values: { useRegistry: true } });
const { getByText } = renderComponent({ values: { useRegistry: true } });
expect(getByText('Advanced mode')).toBeInTheDocument();
});
it('should render AdvancedForm when useRegistry is false', () => {
const { getByText } = render({ values: { useRegistry: false } });
const { getByText } = renderComponent({ values: { useRegistry: false } });
expect(getByText('Simple mode')).toBeInTheDocument();
});
it('should call setFieldValue with useRegistry set to false when "Advanced mode" button is clicked', () => {
const setFieldValue = vi.fn();
const { getByText } = render({
const { getByText } = renderComponent({
values: { useRegistry: true },
setFieldValue,
});
@ -41,7 +42,7 @@ it('should call setFieldValue with useRegistry set to false when "Advanced mode"
it('should call setFieldValue with useRegistry set to true when "Simple mode" button is clicked', () => {
const setFieldValue = vi.fn();
const { getByText } = render({
const { getByText } = renderComponent({
values: { useRegistry: false },
setFieldValue,
});
@ -51,7 +52,7 @@ it('should call setFieldValue with useRegistry set to true when "Simple mode" bu
expect(setFieldValue).toHaveBeenCalledWith('useRegistry', true);
});
function render({
function renderComponent({
values = {
useRegistry: true,
registryId: 123,
@ -73,8 +74,10 @@ function render({
http.get('/api/endpoints/:id', () => HttpResponse.json({}))
);
return renderWithQueryClient(
<ImageConfigFieldset
const Wrapped = withTestQueryProvider(ImageConfigFieldset);
return render(
<Wrapped
values={{
useRegistry: true,
registryId: 123,

@ -1,5 +1,7 @@
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from '@testing-library/react';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { NavTabs, Option } from './NavTabs';
@ -32,6 +34,7 @@ test('should show selected id content', async () => {
});
test('should call onSelect when clicked with id', async () => {
const user = userEvent.setup();
const options = [
{ children: 'Content 1', id: 'option1', label: 'Option 1' },
{ children: 'Content 2', id: 'option2', label: 'Option 2' },
@ -42,7 +45,7 @@ test('should call onSelect when clicked with id', async () => {
const { findByText } = renderComponent(options, options[1].id, onSelect);
const heading = await findByText(options[0].label);
await userEvent.click(heading);
await user.click(heading);
expect(onSelect).toHaveBeenCalledWith(options[0].id);
});
@ -52,7 +55,9 @@ function renderComponent(
selectedId?: string | number,
onSelect?: (id: string | number) => void
) {
const Wrapped = withTestRouter(NavTabs);
return render(
<NavTabs options={options} selectedId={selectedId} onSelect={onSelect} />
<Wrapped options={options} selectedId={selectedId} onSelect={onSelect} />
);
}

@ -1,4 +1,4 @@
import { render } from '@/react-tools/test-utils';
import { render } from '@testing-library/react';
import { Breadcrumbs } from './Breadcrumbs';

@ -1,8 +1,9 @@
import { QueryClient, QueryClientProvider } from 'react-query';
import { render } from '@testing-library/react';
import { UserContext } from '@/react/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user';
import { render } from '@/react-tools/test-utils';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { HeaderContainer } from './HeaderContainer';
import { HeaderTitle } from './HeaderTitle';
@ -14,7 +15,8 @@ test('should not render without a wrapping HeaderContainer', async () => {
const title = 'title';
function renderComponent() {
return render(<HeaderTitle title={title} />);
const Wrapped = withTestQueryProvider(HeaderTitle);
return render(<Wrapped title={title} />);
}
expect(renderComponent).toThrowErrorMatchingSnapshot();
@ -25,19 +27,22 @@ test('should not render without a wrapping HeaderContainer', async () => {
test('should display a HeaderTitle', async () => {
const username = 'username';
const user = new UserViewModel({ Username: username });
const queryClient = new QueryClient();
const title = 'title';
const { queryByText } = render(
<QueryClientProvider client={queryClient}>
<UserContext.Provider value={{ user }}>
const Wrapped = withTestQueryProvider(
withUserProvider(
withTestRouter(() => (
<HeaderContainer>
<HeaderTitle title={title} />
</HeaderContainer>
</UserContext.Provider>
</QueryClientProvider>
)),
user
)
);
const { queryByText } = render(<Wrapped />);
const heading = queryByText(title);
expect(heading).toBeVisible();

@ -1,6 +1,9 @@
import { UserContext } from '@/react/hooks/useUser';
import { render } from '@testing-library/react';
import { UserViewModel } from '@/portainer/models/user';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { PageHeader } from './PageHeader';
@ -8,13 +11,13 @@ test('should display a PageHeader', async () => {
const username = 'username';
const user = new UserViewModel({ Username: username });
const title = 'title';
const { queryByText } = renderWithQueryClient(
<UserContext.Provider value={{ user }}>
<PageHeader title={title} />
</UserContext.Provider>
const Wrapped = withTestQueryProvider(
withUserProvider(withTestRouter(PageHeader), user)
);
const title = 'title';
const { queryByText } = render(<Wrapped title={title} />);
const heading = queryByText(title);
expect(heading).toBeVisible();

@ -1,9 +1,11 @@
import { http, HttpResponse } from 'msw';
import { Mock } from 'vitest';
import { render } from '@testing-library/react';
import { Tag, TagId } from '@/portainer/tags/types';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { server } from '@/setup-tests/server';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { TagSelector } from './TagSelector';
@ -54,8 +56,10 @@ async function renderComponent(
) {
server.use(http.get('/api/tags', () => HttpResponse.json(tags)));
const queries = renderWithQueryClient(
<TagSelector value={value} allowCreate={allowCreate} onChange={onChange} />
const Wrapped = withTestQueryProvider(withTestRouter(TagSelector));
const queries = render(
<Wrapped value={value} allowCreate={allowCreate} onChange={onChange} />
);
const tagElement = await queries.findAllByText('tags', { exact: false });

@ -1,11 +1,34 @@
import { render } from '@/react-tools/test-utils';
import { UIView } from '@uirouter/react';
import { render } from '@testing-library/react';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { AddButton } from './AddButton';
function renderDefault({
label = 'default label',
}: Partial<{ label: string }> = {}) {
return render(<AddButton to="">{label}</AddButton>);
const Wrapped = withTestRouter(AddButton, {
stateConfig: [
{
name: 'root',
url: '/',
component: () => (
<>
<div>Root</div>
<UIView />
</>
),
},
{
name: 'root.new',
url: 'new',
},
],
route: 'root',
});
return render(<Wrapped to="">{label}</Wrapped>);
}
test('should display a AddButton component', async () => {

@ -1,5 +1,5 @@
import { fireEvent, render } from '@testing-library/react';
import { PropsWithChildren } from 'react';
import { fireEvent, render } from '@testing-library/react';
import { Button, Props } from './Button';

@ -1,5 +1,5 @@
import { render } from '@testing-library/react';
import { PropsWithChildren } from 'react';
import { render } from '@testing-library/react';
import { ButtonGroup, Props } from './ButtonGroup';

@ -1,4 +1,4 @@
import { render } from '@/react-tools/test-utils';
import { render } from '@testing-library/react';
import { LoadingButton } from './LoadingButton';

@ -1,4 +1,4 @@
import { fireEvent, render } from '@/react-tools/test-utils';
import { fireEvent, render } from '@testing-library/react';
import { FileUploadField } from './FileUploadField';

@ -1,4 +1,4 @@
import { render } from '@/react-tools/test-utils';
import { render } from '@testing-library/react';
import { FileUploadForm } from './FileUploadForm';

@ -1,4 +1,4 @@
import { render } from '@/react-tools/test-utils';
import { render } from '@testing-library/react';
import { Slider, Props } from './Slider';

@ -1,5 +1,5 @@
import { render } from '@testing-library/react';
import { PropsWithChildren } from 'react';
import { render } from '@testing-library/react';
import { Switch, Props } from './Switch';

@ -1,4 +1,4 @@
import { render, fireEvent } from '@/react-tools/test-utils';
import { render, fireEvent } from '@testing-library/react';
import { SwitchField, Props } from './SwitchField';

@ -0,0 +1,39 @@
import { UISref, UIView } from '@uirouter/react';
import { render, screen } from '@testing-library/react';
import { withTestRouter } from '@/react/test-utils/withRouter';
function RelativePathLink() {
return (
<UISref to=".custom">
<span>Link</span>
</UISref>
);
}
test.todo('should render a link with relative path', () => {
const WrappedComponent = withTestRouter(RelativePathLink, {
stateConfig: [
{
name: 'parent',
url: '/',
component: () => (
<>
<div>parent</div>
<UIView />
</>
),
},
{
name: 'parent.custom',
url: 'custom',
},
],
route: 'parent',
});
render(<WrappedComponent />);
expect(screen.getByText('Link')).toBeInTheDocument();
});

@ -1,9 +1,11 @@
import { HttpResponse } from 'msw';
import { render } from '@testing-library/react';
import { http, HttpResponse } from 'msw';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { UserContext } from '@/react/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user';
import { http, server } from '@/setup-tests/server';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { server } from '@/setup-tests/server';
import { NetworkContainer } from '../types';
@ -32,15 +34,18 @@ test('Network container values should be visible and the link should be valid',
server.use(http.get('/api/endpoints/1', () => HttpResponse.json({})));
const user = new UserViewModel({ Username: 'test', Role: 1 });
const { findByText } = renderWithQueryClient(
<UserContext.Provider value={{ user }}>
<NetworkContainersTable
networkContainers={networkContainers}
nodeName=""
environmentId={1}
networkId="pc8xc9s6ot043vl1q5iz4zhfs"
/>
</UserContext.Provider>
const Wrapped = withTestQueryProvider(
withUserProvider(withTestRouter(NetworkContainersTable), user)
);
const { findByText } = render(
<Wrapped
networkContainers={networkContainers}
nodeName=""
environmentId={1}
networkId="pc8xc9s6ot043vl1q5iz4zhfs"
/>
);
await expect(findByText('Containers in network')).resolves.toBeVisible();

@ -1,9 +1,10 @@
import { HttpResponse, http } from 'msw';
import { render } from '@testing-library/react';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { UserContext } from '@/react/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user';
import { server } from '@/setup-tests/server';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { DockerNetwork } from '../types';
@ -26,7 +27,9 @@ test('Network details values should be visible', async () => {
await expect(findByText(network.Driver)).resolves.toBeVisible();
await expect(findByText(network.Scope)).resolves.toBeVisible();
await expect(
findByText(network.IPAM?.Config[0].Gateway || 'not found', { exact: false })
findByText(network.IPAM?.Config[0].Gateway || 'not found', {
exact: false,
})
).resolves.toBeVisible();
await expect(
findByText(network.IPAM?.Config[0].Subnet || 'not found', { exact: false })
@ -55,13 +58,12 @@ async function renderComponent(isAdmin: boolean, network: DockerNetwork) {
const user = new UserViewModel({ Username: 'test', Role: isAdmin ? 1 : 2 });
const queries = renderWithQueryClient(
<UserContext.Provider value={{ user }}>
<NetworkDetailsTable
network={network}
onRemoveNetworkClicked={() => {}}
/>
</UserContext.Provider>
const Wrapped = withTestQueryProvider(
withUserProvider(NetworkDetailsTable, user)
);
const queries = render(
<Wrapped network={network} onRemoveNetworkClicked={() => {}} />
);
await expect(queries.findByText('Network details')).resolves.toBeVisible();

@ -1,4 +1,4 @@
import { render } from '@/react-tools/test-utils';
import { render } from '@testing-library/react';
import { NetworkOptions } from '../types';

@ -1,4 +1,5 @@
import { render, screen } from '@/react-tools/test-utils';
import { render, screen } from '@testing-library/react';
import {
EnvVarType,
TemplateViewModel,

@ -1,7 +1,6 @@
import { vi } from 'vitest';
import userEvent from '@testing-library/user-event';
import { render, screen } from '@/react-tools/test-utils';
import { render, screen } from '@testing-library/react';
import {
EnvVarsFieldset,

@ -1,6 +1,5 @@
import { vi } from 'vitest';
import { render, screen } from '@/react-tools/test-utils';
import { render, screen } from '@testing-library/react';
import { TemplateNote } from './TemplateNote';

@ -1,17 +1,18 @@
import { vi } from 'vitest';
import { HttpResponse, http } from 'msw';
import { render, screen } from '@testing-library/react';
import { renderWithQueryClient, screen } from '@/react-tools/test-utils';
import { AppTemplate } from '@/react/portainer/templates/app-templates/types';
import { CustomTemplate } from '@/react/portainer/templates/custom-templates/types';
import { server } from '@/setup-tests/server';
import selectEvent from '@/react/test-utils/react-select';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { SelectedTemplateValue } from './types';
import { TemplateSelector } from './TemplateSelector';
test('renders TemplateSelector component', async () => {
render();
renderComponent();
const templateSelectorElement = screen.getByLabelText('Template');
expect(templateSelectorElement).toBeInTheDocument();
@ -30,7 +31,7 @@ test.skip('selects an edge app template', async () => {
categories: ['edge'],
};
const { select } = render({
const { select } = renderComponent({
onChange,
appTemplates: [
{
@ -59,7 +60,7 @@ test.skip('selects an edge custom template', async () => {
Id: 2,
};
const { select } = render({
const { select } = renderComponent({
onChange,
customTemplates: [
{
@ -75,7 +76,7 @@ test.skip('selects an edge custom template', async () => {
});
test('renders with error', async () => {
render({
renderComponent({
error: 'Invalid template',
});
@ -87,7 +88,7 @@ test('renders with error', async () => {
});
test.skip('renders TemplateSelector component with no custom templates available', async () => {
render({
renderComponent({
customTemplates: [],
});
@ -102,7 +103,7 @@ test.skip('renders TemplateSelector component with no custom templates available
expect(noCustomTemplatesElement).toBeInTheDocument();
});
function render({
function renderComponent({
onChange = vi.fn(),
appTemplates = [],
customTemplates = [],
@ -123,8 +124,10 @@ function render({
)
);
renderWithQueryClient(
<TemplateSelector
const Wrapped = withTestQueryProvider(TemplateSelector);
render(
<Wrapped
value={{ template: undefined, type: undefined }}
onChange={onChange}
error={error}

@ -1,20 +1,17 @@
import { http, HttpResponse } from 'msw';
import { render } from '@testing-library/react';
import { server } from '@/setup-tests/server';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { isoDate } from '@/portainer/filters/filters';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { BackupFailedPanel } from './BackupFailedPanel';
test('when backup failed, should show message', async () => {
const timestamp = 1500;
server.use(
http.get('/api/backup/s3/status', () =>
HttpResponse.json({ Failed: true, TimestampUTC: timestamp })
)
);
const { findByText } = renderWithQueryClient(<BackupFailedPanel />);
const { findByText } = renderComponent({ failed: true, timestamp });
await expect(
findByText(
@ -27,14 +24,27 @@ test('when backup failed, should show message', async () => {
});
test("when user is using less nodes then allowed he shouldn't see message", async () => {
server.use(
http.get('/api/backup/s3/status', () =>
HttpResponse.json({ Failed: false })
)
);
const { findByText } = renderWithQueryClient(<BackupFailedPanel />);
const { findByText } = renderComponent({ failed: false });
await expect(
findByText('The latest automated backup has failed at', { exact: false })
).rejects.toBeTruthy();
});
function renderComponent({
failed,
timestamp,
}: {
failed: boolean;
timestamp?: number;
}) {
server.use(
http.get('/api/backup/s3/status', () =>
HttpResponse.json({ Failed: failed, TimestampUTC: timestamp })
)
);
const Wrapped = withTestQueryProvider(withTestRouter(BackupFailedPanel));
return render(<Wrapped />);
}

@ -1,16 +1,18 @@
import { http, HttpResponse } from 'msw';
import { render } from '@testing-library/react';
import {
EnvironmentGroup,
EnvironmentGroupId,
} from '@/react/portainer/environments/environment-groups/types';
import { Environment } from '@/react/portainer/environments/types';
import { UserContext } from '@/react/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user';
import { Tag } from '@/portainer/tags/types';
import { createMockEnvironment } from '@/react-tools/test-mocks';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { server } from '@/setup-tests/server';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { EnvironmentItem } from './EnvironmentItem';
@ -43,15 +45,17 @@ function renderComponent(
server.use(http.get('/api/tags', () => HttpResponse.json(tags)));
return renderWithQueryClient(
<UserContext.Provider value={{ user }}>
<EnvironmentItem
isActive={false}
onClickBrowse={() => {}}
onClickDisconnect={() => {}}
environment={env}
groupName={group.Name}
/>
</UserContext.Provider>
const Wrapped = withTestQueryProvider(
withTestRouter(withUserProvider(EnvironmentItem, user))
);
return render(
<Wrapped
isActive={false}
onClickBrowse={() => {}}
onClickDisconnect={() => {}}
environment={env}
groupName={group.Name}
/>
);
}

@ -1,10 +1,12 @@
import { http, HttpResponse } from 'msw';
import { render } from '@testing-library/react';
import { Environment } from '@/react/portainer/environments/types';
import { UserContext } from '@/react/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { server } from '@/setup-tests/server';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { EnvironmentList } from './EnvironmentList';
@ -49,10 +51,12 @@ async function renderComponent(
)
);
const queries = renderWithQueryClient(
<UserContext.Provider value={{ user }}>
<EnvironmentList onClickBrowse={vi.fn()} onRefresh={vi.fn()} />
</UserContext.Provider>
const Wrapped = withTestQueryProvider(
withUserProvider(withTestRouter(EnvironmentList), user)
);
const queries = render(
<Wrapped onClickBrowse={vi.fn()} onRefresh={vi.fn()} />
);
await expect(queries.findByText('Environments')).resolves.toBeVisible();

@ -1,7 +1,8 @@
import { http, HttpResponse } from 'msw';
import { render } from '@testing-library/react';
import { server } from '@/setup-tests/server';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { LicenseType } from '../licenses/types';
@ -17,7 +18,7 @@ test('when user is using more nodes then allowed he should see message', async (
http.get('/api/system/nodes', () => HttpResponse.json({ nodes: used }))
);
const { findByText } = renderWithQueryClient(<LicenseNodePanel />);
const { findByText } = renderComponent();
await expect(
findByText(
@ -36,7 +37,7 @@ test("when user is using less nodes then allowed he shouldn't see message", asyn
http.get('/api/system/nodes', () => HttpResponse.json({ nodes: used }))
);
const { findByText } = renderWithQueryClient(<LicenseNodePanel />);
const { findByText } = renderComponent();
await expect(
findByText(
@ -44,3 +45,9 @@ test("when user is using less nodes then allowed he shouldn't see message", asyn
)
).rejects.toBeTruthy();
});
function renderComponent() {
const Wrapped = withTestQueryProvider(LicenseNodePanel);
return render(<Wrapped />);
}

@ -1,9 +1,10 @@
import { Meta, Story } from '@storybook/react';
import { useMemo, useState } from 'react';
import { QueryClient, QueryClientProvider } from 'react-query';
import { useState } from 'react';
import { UserContext } from '@/react/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user';
import { Role, User } from '@/portainer/users/types';
import { isPureAdmin } from '@/portainer/users/user.helpers';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { parseAccessControlFormData } from '../utils';
@ -16,41 +17,25 @@ const meta: Meta = {
export default meta;
enum Role {
Admin = 1,
User,
}
const testQueryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
interface Args {
userRole: Role;
}
function Template({ userRole }: Args) {
const isAdmin = userRole === Role.Admin;
const defaults = parseAccessControlFormData(isAdmin, 0);
const defaults = parseAccessControlFormData(
isPureAdmin({ Role: userRole } as User),
0
);
const [value, setValue] = useState(defaults);
const userProviderState = useMemo(
() => ({ user: new UserViewModel({ Role: userRole }) }),
[userRole]
const Wrapped = withUserProvider(
AccessControlForm,
new UserViewModel({ Role: userRole })
);
return (
<QueryClientProvider client={testQueryClient}>
<UserContext.Provider value={userProviderState}>
<AccessControlForm
values={value}
onChange={setValue}
errors={{}}
environmentId={1}
/>
</UserContext.Provider>
</QueryClientProvider>
<Wrapped values={value} onChange={setValue} errors={{}} environmentId={1} />
);
}
@ -61,5 +46,5 @@ AdminAccessControl.args = {
export const NonAdminAccessControl: Story<Args> = Template.bind({});
NonAdminAccessControl.args = {
userRole: Role.User,
userRole: Role.Standard,
};

@ -1,12 +1,14 @@
import { http, HttpResponse } from 'msw';
import { render, within } from '@testing-library/react';
import { server } from '@/setup-tests/server';
import { UserContext } from '@/react/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user';
import { renderWithQueryClient, within } from '@/react-tools/test-utils';
import { Team, TeamId } from '@/react/portainer/users/teams/types';
import { createMockTeams } from '@/react-tools/test-mocks';
import { UserId } from '@/portainer/users/types';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { ResourceControlOwnership, AccessControlFormData } from '../types';
import { ResourceControlViewModel } from '../models/ResourceControlViewModel';
@ -304,7 +306,6 @@ async function renderComponent(
{ isAdmin = false, hideTitle = false, teams, users }: AdditionalProps = {}
) {
const user = new UserViewModel({ Username: 'user', Role: isAdmin ? 1 : 2 });
const state = { user };
if (teams) {
server.use(http.get('/api/teams', () => HttpResponse.json(teams)));
@ -314,16 +315,18 @@ async function renderComponent(
server.use(http.get('/api/users', () => HttpResponse.json(users)));
}
const renderResult = renderWithQueryClient(
<UserContext.Provider value={state}>
<AccessControlForm
environmentId={1}
errors={{}}
values={values}
onChange={onChange}
hideTitle={hideTitle}
/>
</UserContext.Provider>
const Wrapped = withTestRouter(
withTestQueryProvider(withUserProvider(AccessControlForm, user))
);
const renderResult = render(
<Wrapped
environmentId={1}
errors={{}}
values={values}
onChange={onChange}
hideTitle={hideTitle}
/>
);
await expect(

@ -1,11 +1,13 @@
import _ from 'lodash';
import { http, HttpResponse } from 'msw';
import { render } from '@testing-library/react';
import { createMockTeams, createMockUsers } from '@/react-tools/test-mocks';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { server } from '@/setup-tests/server';
import { Role } from '@/portainer/users/types';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import {
ResourceControlOwnership,
@ -145,9 +147,11 @@ async function renderComponent(
resourceType: ResourceControlType = ResourceControlType.Container,
resourceControl?: ResourceControlViewModel
) {
const WithUser = withUserProvider(AccessControlPanelDetails);
const queries = renderWithQueryClient(
<WithUser resourceControl={resourceControl} resourceType={resourceType} />
const Wrapped = withTestQueryProvider(
withTestRouter(withUserProvider(AccessControlPanelDetails))
);
const queries = render(
<Wrapped resourceControl={resourceControl} resourceType={resourceType} />
);
await expect(queries.findByText('Ownership')).resolves.toBeVisible();

@ -1,8 +1,10 @@
import userEvent from '@testing-library/user-event';
import { render, waitFor } from '@testing-library/react';
import { renderWithQueryClient, waitFor } from '@/react-tools/test-utils';
import { UserViewModel } from '@/portainer/models/user';
import { UserContext } from '@/react/hooks/useUser';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { CreateUserAccessToken } from './CreateUserAccessToken';
@ -33,9 +35,9 @@ test('the button is disabled when all fields are blank and enabled when all fiel
function renderComponent() {
const user = new UserViewModel({ Username: 'user' });
return renderWithQueryClient(
<UserContext.Provider value={{ user }}>
<CreateUserAccessToken />
</UserContext.Provider>
const Wrapped = withTestQueryProvider(
withUserProvider(withTestRouter(CreateUserAccessToken), user)
);
return render(<Wrapped />);
}

@ -1,7 +1,6 @@
import { vi } from 'vitest';
import userEvent from '@testing-library/user-event';
import { render, screen } from '@/react-tools/test-utils';
import { render, screen } from '@testing-library/react';
import {
CustomTemplatesVariablesField,

@ -1,7 +1,6 @@
import { vi } from 'vitest';
import userEvent from '@testing-library/user-event';
import { render, screen } from '@/react-tools/test-utils';
import { render, screen } from '@testing-library/react';
import { VariableFieldItem } from './VariableFieldItem';

@ -1,7 +1,6 @@
import userEvent from '@testing-library/user-event';
import { PropsWithChildren } from 'react';
import { render } from '@/react-tools/test-utils';
import { render } from '@testing-library/react';
import { AppTemplatesListItem } from './AppTemplatesListItem';
import { TemplateViewModel } from './view-model';

@ -1,6 +1,8 @@
import { UserContext } from '@/react/hooks/useUser';
import { render } from '@testing-library/react';
import { UserViewModel } from '@/portainer/models/user';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { TeamAssociationSelector } from './TeamAssociationSelector';
@ -13,9 +15,9 @@ test('renders correctly', () => {
function renderComponent() {
const user = new UserViewModel({ Username: 'user' });
return renderWithQueryClient(
<UserContext.Provider value={{ user }}>
<TeamAssociationSelector users={[]} memberships={[]} teamId={3} />
</UserContext.Provider>
const Wrapped = withTestQueryProvider(
withUserProvider(TeamAssociationSelector, user)
);
return render(<Wrapped users={[]} memberships={[]} teamId={3} />);
}

@ -1,6 +1,8 @@
import { UserContext } from '@/react/hooks/useUser';
import { render } from '@testing-library/react';
import { UserViewModel } from '@/portainer/models/user';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { TeamMembersList } from './TeamMembersList';
@ -13,11 +15,11 @@ test('renders correctly', () => {
function renderComponent() {
const user = new UserViewModel({ Username: 'user' });
return renderWithQueryClient(
<UserContext.Provider value={{ user }}>
<TeamMembersList users={[]} roles={{}} teamId={3} />
</UserContext.Provider>
const Wrapped = withTestQueryProvider(
withUserProvider(TeamMembersList, user)
);
return render(<Wrapped users={[]} roles={{}} teamId={3} />);
}
test.todo('when users list is empty, add all users button is disabled');

@ -1,6 +1,8 @@
import { UserContext } from '@/react/hooks/useUser';
import { render } from '@testing-library/react';
import { UserViewModel } from '@/portainer/models/user';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { UsersList } from './UsersList';
@ -12,11 +14,10 @@ test('renders correctly', () => {
function renderComponent() {
const user = new UserViewModel({ Username: 'user' });
return renderWithQueryClient(
<UserContext.Provider value={{ user }}>
<UsersList users={[]} teamId={3} />
</UserContext.Provider>
);
const Wrapped = withTestQueryProvider(withUserProvider(UsersList, user));
return render(<Wrapped users={[]} teamId={3} />);
}
test.todo('when users list is empty, add all users button is disabled');

@ -1,12 +1,15 @@
import userEvent from '@testing-library/user-event';
import { render, waitFor } from '@testing-library/react';
import { renderWithQueryClient, waitFor } from '@/react-tools/test-utils';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { CreateTeamForm } from './CreateTeamForm';
test('filling the name should make the submit button clickable and emptying it should make it disabled', async () => {
const { findByLabelText, findByText } = renderWithQueryClient(
<CreateTeamForm users={[]} teams={[]} />
const Wrapped = withTestQueryProvider(CreateTeamForm);
const { findByLabelText, findByText } = render(
<Wrapped users={[]} teams={[]} />
);
const button = await findByText('Create team');

@ -1,6 +1,8 @@
import { UserContext } from '@/react/hooks/useUser';
import { render, within } from '@testing-library/react';
import { UserViewModel } from '@/portainer/models/user';
import { render, within } from '@/react-tools/test-utils';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { TestSidebarProvider } from '../useSidebarState';
@ -30,11 +32,11 @@ test('dashboard items should render correctly', () => {
function renderComponent() {
const user = new UserViewModel({ Username: 'user' });
const Wrapped = withUserProvider(withTestRouter(AzureSidebar), user);
return render(
<UserContext.Provider value={{ user }}>
<TestSidebarProvider>
<AzureSidebar environmentId={1} />
</TestSidebarProvider>
</UserContext.Provider>
<TestSidebarProvider>
<Wrapped environmentId={1} />
</TestSidebarProvider>
);
}

@ -0,0 +1,49 @@
import { ComponentType } from 'react';
import {
ReactStateDeclaration,
UIRouter,
UIRouterReact,
UIView,
hashLocationPlugin,
servicesPlugin,
} from '@uirouter/react';
/**
* A helper function to wrap a component with a UIRouter Provider.
*
* should only be used in tests
*/
export function withTestRouter<T>(
WrappedComponent: ComponentType<T>,
{
route = '/',
stateConfig = [],
}: { route?: string; stateConfig?: Array<ReactStateDeclaration> } = {}
): ComponentType<T> {
const router = new UIRouterReact();
// router.trace.enable(Category.TRANSITION);
router.plugin(servicesPlugin);
router.plugin(hashLocationPlugin);
// Set up your custom state configuration
stateConfig.forEach((state) => router.stateRegistry.register(state));
router.urlService.rules.initial({ state: route });
// Try to create a nice displayName for React Dev Tools.
const displayName =
WrappedComponent.displayName || WrappedComponent.name || 'Component';
function WrapperComponent(props: T & JSX.IntrinsicAttributes) {
return (
<UIRouter router={router}>
<UIView />
<WrappedComponent {...props} />
</UIRouter>
);
}
WrapperComponent.displayName = `withTestRouter(${displayName})`;
return WrapperComponent;
}

@ -0,0 +1,14 @@
import { ComponentType } from 'react';
import { QueryClient } from 'react-query';
import { withReactQuery } from '@/react-tools/withReactQuery';
export function withTestQueryProvider<T>(
WrappedComponent: ComponentType<T & JSX.IntrinsicAttributes>
) {
const testQueryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
return withReactQuery(WrappedComponent, testQueryClient);
}

@ -11,6 +11,7 @@ import { StatusResponse } from '@/react/portainer/system/useSystemStatus';
import { createMockTeams } from '@/react-tools/test-mocks';
import { PublicSettingsResponse } from '@/react/portainer/settings/types';
import { UserId } from '@/portainer/users/types';
import { VersionResponse } from '@/react/portainer/system/useSystemVersion';
import { azureHandlers } from './setup-handlers/azure';
import { dockerHandlers } from './setup-handlers/docker';
@ -85,6 +86,9 @@ export const handlers = [
http.get<never, never, Partial<StatusResponse>>('/api/status', () =>
HttpResponse.json({})
),
http.get<never, never, Partial<VersionResponse>>('/api/system/version', () =>
HttpResponse.json({ ServerVersion: 'v2.10.0' })
),
http.get('/api/teams/:id/memberships', () => HttpResponse.json([])),
http.get('/api/endpoints/agent_versions', () => HttpResponse.json([])),
];

@ -0,0 +1 @@
import 'vitest-dom/extend-expect';

@ -7,7 +7,7 @@ export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./app/setup-tests/setup-msw.ts', './app/setup-tests/stub-modules.ts'],
setupFiles: ['./app/setup-tests/setup-msw.ts', './app/setup-tests/stub-modules.ts', './app/setup-tests/setup.ts'],
coverage: {
reporter: ['text', 'html'],
exclude: ['node_modules/', 'app/setup-tests/global-setup.js'],

Loading…
Cancel
Save