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'], pathGroupsExcludedImportTypes: ['internal'],
}, },
] ]
no-restricted-imports:
- error
- patterns:
- group:
- '@/react/test-utils/*'
message: 'These utils are just for test files'
settings: settings:
'import/resolver': 'import/resolver':
@ -113,6 +119,12 @@ overrides:
'no-await-in-loop': 'off' 'no-await-in-loop': 'off'
'react/jsx-no-useless-fragment': ['error', { allowExpressions: true }] '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' }]] '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 overrides: # allow props spreading for hoc files
- files: - files:
- app/**/with*.ts{,x} - app/**/with*.ts{,x}
@ -126,7 +138,11 @@ overrides:
'vitest/env': true 'vitest/env': true
rules: rules:
'react/jsx-no-constructed-context-values': off 'react/jsx-no-constructed-context-values': off
'@typescript-eslint/no-restricted-imports': off
no-restricted-imports: off
- files: - files:
- app/**/*.stories.* - app/**/*.stories.*
rules: rules:
'no-alert': off '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 { 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 { UserViewModel } from '@/portainer/models/user';
import { server } from '@/setup-tests/server'; import { server } from '@/setup-tests/server';
import { import {
createMockResourceGroups, createMockResourceGroups,
createMockSubscriptions, createMockSubscriptions,
} from '@/react-tools/test-mocks'; } 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'; import { DashboardView } from './DashboardView';
@ -105,7 +107,6 @@ async function renderComponent(
resourceGroupsStatus = 200 resourceGroupsStatus = 200
) { ) {
const user = new UserViewModel({ Username: 'user' }); const user = new UserViewModel({ Username: 'user' });
const state = { user };
server.use( server.use(
http.get('/api/endpoints/1', () => HttpResponse.json({})), http.get('/api/endpoints/1', () => HttpResponse.json({})),
@ -135,12 +136,13 @@ async function renderComponent(
} }
) )
); );
const renderResult = renderWithQueryClient(
<UserContext.Provider value={state}> const Wrapped = withTestQueryProvider(
<DashboardView /> withUserProvider(withTestRouter(DashboardView), user)
</UserContext.Provider>
); );
const renderResult = render(<Wrapped />);
await expect(renderResult.findByText(/Home/)).resolves.toBeVisible(); await expect(renderResult.findByText(/Home/)).resolves.toBeVisible();
return renderResult; return renderResult;

@ -1,10 +1,12 @@
import userEvent from '@testing-library/user-event'; 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 { UserViewModel } from '@/portainer/models/user';
import { renderWithQueryClient } from '@/react-tools/test-utils'; import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { http, server } from '@/setup-tests/server'; import { withTestRouter } from '@/react/test-utils/withRouter';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { server } from '@/setup-tests/server';
import { CreateContainerInstanceForm } from './CreateContainerInstanceForm'; 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({}))); server.use(http.get('/api/endpoints/5', () => HttpResponse.json({})));
const user = new UserViewModel({ Username: 'user' }); const user = new UserViewModel({ Username: 'user' });
const Wrapped = withTestQueryProvider(
const { findByText, getByText, getByLabelText } = renderWithQueryClient( withUserProvider(withTestRouter(CreateContainerInstanceForm), user)
<UserContext.Provider value={{ user }}>
<CreateContainerInstanceForm />
</UserContext.Provider>
); );
const { findByText, getByText, getByLabelText } = render(<Wrapped />);
await expect(findByText(/Azure settings/)).resolves.toBeVisible(); 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'; import { Badge } from './Badge';

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

@ -1,6 +1,5 @@
import { User } from 'lucide-react'; import { User } from 'lucide-react';
import { render } from '@testing-library/react';
import { render } from '@/react-tools/test-utils';
import { DashboardItem } from './DashboardItem'; 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'; import { DetailsTable } from './index';

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

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

@ -1,5 +1,7 @@
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; 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'; 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 () => { test('should call onSelect when clicked with id', async () => {
const user = userEvent.setup();
const options = [ const options = [
{ children: 'Content 1', id: 'option1', label: 'Option 1' }, { children: 'Content 1', id: 'option1', label: 'Option 1' },
{ children: 'Content 2', id: 'option2', label: 'Option 2' }, { 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 { findByText } = renderComponent(options, options[1].id, onSelect);
const heading = await findByText(options[0].label); const heading = await findByText(options[0].label);
await userEvent.click(heading); await user.click(heading);
expect(onSelect).toHaveBeenCalledWith(options[0].id); expect(onSelect).toHaveBeenCalledWith(options[0].id);
}); });
@ -52,7 +55,9 @@ function renderComponent(
selectedId?: string | number, selectedId?: string | number,
onSelect?: (id: string | number) => void onSelect?: (id: string | number) => void
) { ) {
const Wrapped = withTestRouter(NavTabs);
return render( 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'; 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 { 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 { HeaderContainer } from './HeaderContainer';
import { HeaderTitle } from './HeaderTitle'; import { HeaderTitle } from './HeaderTitle';
@ -14,7 +15,8 @@ test('should not render without a wrapping HeaderContainer', async () => {
const title = 'title'; const title = 'title';
function renderComponent() { function renderComponent() {
return render(<HeaderTitle title={title} />); const Wrapped = withTestQueryProvider(HeaderTitle);
return render(<Wrapped title={title} />);
} }
expect(renderComponent).toThrowErrorMatchingSnapshot(); expect(renderComponent).toThrowErrorMatchingSnapshot();
@ -25,19 +27,22 @@ test('should not render without a wrapping HeaderContainer', async () => {
test('should display a HeaderTitle', async () => { test('should display a HeaderTitle', async () => {
const username = 'username'; const username = 'username';
const user = new UserViewModel({ Username: username }); const user = new UserViewModel({ Username: username });
const queryClient = new QueryClient();
const title = 'title'; const title = 'title';
const { queryByText } = render(
<QueryClientProvider client={queryClient}> const Wrapped = withTestQueryProvider(
<UserContext.Provider value={{ user }}> withUserProvider(
withTestRouter(() => (
<HeaderContainer> <HeaderContainer>
<HeaderTitle title={title} /> <HeaderTitle title={title} />
</HeaderContainer> </HeaderContainer>
</UserContext.Provider> )),
</QueryClientProvider> user
)
); );
const { queryByText } = render(<Wrapped />);
const heading = queryByText(title); const heading = queryByText(title);
expect(heading).toBeVisible(); 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 { 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'; import { PageHeader } from './PageHeader';
@ -8,13 +11,13 @@ test('should display a PageHeader', async () => {
const username = 'username'; const username = 'username';
const user = new UserViewModel({ Username: username }); const user = new UserViewModel({ Username: username });
const title = 'title'; const Wrapped = withTestQueryProvider(
const { queryByText } = renderWithQueryClient( withUserProvider(withTestRouter(PageHeader), user)
<UserContext.Provider value={{ user }}>
<PageHeader title={title} />
</UserContext.Provider>
); );
const title = 'title';
const { queryByText } = render(<Wrapped title={title} />);
const heading = queryByText(title); const heading = queryByText(title);
expect(heading).toBeVisible(); expect(heading).toBeVisible();

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

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

@ -1,5 +1,5 @@
import { render } from '@testing-library/react';
import { PropsWithChildren } from 'react'; import { PropsWithChildren } from 'react';
import { render } from '@testing-library/react';
import { ButtonGroup, Props } from './ButtonGroup'; 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'; 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'; 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'; 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'; import { Slider, Props } from './Slider';

@ -1,5 +1,5 @@
import { render } from '@testing-library/react';
import { PropsWithChildren } from 'react'; import { PropsWithChildren } from 'react';
import { render } from '@testing-library/react';
import { Switch, Props } from './Switch'; 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'; 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 { 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'; 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({}))); server.use(http.get('/api/endpoints/1', () => HttpResponse.json({})));
const user = new UserViewModel({ Username: 'test', Role: 1 }); const user = new UserViewModel({ Username: 'test', Role: 1 });
const { findByText } = renderWithQueryClient(
<UserContext.Provider value={{ user }}> const Wrapped = withTestQueryProvider(
<NetworkContainersTable withUserProvider(withTestRouter(NetworkContainersTable), user)
networkContainers={networkContainers} );
nodeName=""
environmentId={1} const { findByText } = render(
networkId="pc8xc9s6ot043vl1q5iz4zhfs" <Wrapped
/> networkContainers={networkContainers}
</UserContext.Provider> nodeName=""
environmentId={1}
networkId="pc8xc9s6ot043vl1q5iz4zhfs"
/>
); );
await expect(findByText('Containers in network')).resolves.toBeVisible(); await expect(findByText('Containers in network')).resolves.toBeVisible();

@ -1,9 +1,10 @@
import { HttpResponse, http } from 'msw'; 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 { UserViewModel } from '@/portainer/models/user';
import { server } from '@/setup-tests/server'; import { server } from '@/setup-tests/server';
import { withUserProvider } from '@/react/test-utils/withUserProvider';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { DockerNetwork } from '../types'; 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.Driver)).resolves.toBeVisible();
await expect(findByText(network.Scope)).resolves.toBeVisible(); await expect(findByText(network.Scope)).resolves.toBeVisible();
await expect( await expect(
findByText(network.IPAM?.Config[0].Gateway || 'not found', { exact: false }) findByText(network.IPAM?.Config[0].Gateway || 'not found', {
exact: false,
})
).resolves.toBeVisible(); ).resolves.toBeVisible();
await expect( await expect(
findByText(network.IPAM?.Config[0].Subnet || 'not found', { exact: false }) 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 user = new UserViewModel({ Username: 'test', Role: isAdmin ? 1 : 2 });
const queries = renderWithQueryClient( const Wrapped = withTestQueryProvider(
<UserContext.Provider value={{ user }}> withUserProvider(NetworkDetailsTable, user)
<NetworkDetailsTable );
network={network}
onRemoveNetworkClicked={() => {}} const queries = render(
/> <Wrapped network={network} onRemoveNetworkClicked={() => {}} />
</UserContext.Provider>
); );
await expect(queries.findByText('Network details')).resolves.toBeVisible(); 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'; import { NetworkOptions } from '../types';

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

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

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

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

@ -1,20 +1,17 @@
import { http, HttpResponse } from 'msw'; import { http, HttpResponse } from 'msw';
import { render } from '@testing-library/react';
import { server } from '@/setup-tests/server'; import { server } from '@/setup-tests/server';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { isoDate } from '@/portainer/filters/filters'; import { isoDate } from '@/portainer/filters/filters';
import { withTestRouter } from '@/react/test-utils/withRouter';
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
import { BackupFailedPanel } from './BackupFailedPanel'; import { BackupFailedPanel } from './BackupFailedPanel';
test('when backup failed, should show message', async () => { test('when backup failed, should show message', async () => {
const timestamp = 1500; 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( await expect(
findByText( 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 () => { test("when user is using less nodes then allowed he shouldn't see message", async () => {
server.use( const { findByText } = renderComponent({ failed: false });
http.get('/api/backup/s3/status', () =>
HttpResponse.json({ Failed: false })
)
);
const { findByText } = renderWithQueryClient(<BackupFailedPanel />);
await expect( await expect(
findByText('The latest automated backup has failed at', { exact: false }) findByText('The latest automated backup has failed at', { exact: false })
).rejects.toBeTruthy(); ).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 { http, HttpResponse } from 'msw';
import { render } from '@testing-library/react';
import { import {
EnvironmentGroup, EnvironmentGroup,
EnvironmentGroupId, EnvironmentGroupId,
} from '@/react/portainer/environments/environment-groups/types'; } from '@/react/portainer/environments/environment-groups/types';
import { Environment } from '@/react/portainer/environments/types'; import { Environment } from '@/react/portainer/environments/types';
import { UserContext } from '@/react/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user'; import { UserViewModel } from '@/portainer/models/user';
import { Tag } from '@/portainer/tags/types'; import { Tag } from '@/portainer/tags/types';
import { createMockEnvironment } from '@/react-tools/test-mocks'; import { createMockEnvironment } from '@/react-tools/test-mocks';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { server } from '@/setup-tests/server'; 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'; import { EnvironmentItem } from './EnvironmentItem';
@ -43,15 +45,17 @@ function renderComponent(
server.use(http.get('/api/tags', () => HttpResponse.json(tags))); server.use(http.get('/api/tags', () => HttpResponse.json(tags)));
return renderWithQueryClient( const Wrapped = withTestQueryProvider(
<UserContext.Provider value={{ user }}> withTestRouter(withUserProvider(EnvironmentItem, user))
<EnvironmentItem );
isActive={false}
onClickBrowse={() => {}} return render(
onClickDisconnect={() => {}} <Wrapped
environment={env} isActive={false}
groupName={group.Name} onClickBrowse={() => {}}
/> onClickDisconnect={() => {}}
</UserContext.Provider> environment={env}
groupName={group.Name}
/>
); );
} }

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

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

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

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

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

@ -1,8 +1,10 @@
import userEvent from '@testing-library/user-event'; 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 { 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'; 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() { function renderComponent() {
const user = new UserViewModel({ Username: 'user' }); const user = new UserViewModel({ Username: 'user' });
return renderWithQueryClient( const Wrapped = withTestQueryProvider(
<UserContext.Provider value={{ user }}> withUserProvider(withTestRouter(CreateUserAccessToken), user)
<CreateUserAccessToken />
</UserContext.Provider>
); );
return render(<Wrapped />);
} }

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

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

@ -1,7 +1,6 @@
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { PropsWithChildren } from 'react'; import { PropsWithChildren } from 'react';
import { render } from '@testing-library/react';
import { render } from '@/react-tools/test-utils';
import { AppTemplatesListItem } from './AppTemplatesListItem'; import { AppTemplatesListItem } from './AppTemplatesListItem';
import { TemplateViewModel } from './view-model'; 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 { 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'; import { TeamAssociationSelector } from './TeamAssociationSelector';
@ -13,9 +15,9 @@ test('renders correctly', () => {
function renderComponent() { function renderComponent() {
const user = new UserViewModel({ Username: 'user' }); const user = new UserViewModel({ Username: 'user' });
return renderWithQueryClient( const Wrapped = withTestQueryProvider(
<UserContext.Provider value={{ user }}> withUserProvider(TeamAssociationSelector, user)
<TeamAssociationSelector users={[]} memberships={[]} teamId={3} />
</UserContext.Provider>
); );
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 { 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'; import { TeamMembersList } from './TeamMembersList';
@ -13,11 +15,11 @@ test('renders correctly', () => {
function renderComponent() { function renderComponent() {
const user = new UserViewModel({ Username: 'user' }); const user = new UserViewModel({ Username: 'user' });
return renderWithQueryClient( const Wrapped = withTestQueryProvider(
<UserContext.Provider value={{ user }}> withUserProvider(TeamMembersList, user)
<TeamMembersList users={[]} roles={{}} teamId={3} />
</UserContext.Provider>
); );
return render(<Wrapped users={[]} roles={{}} teamId={3} />);
} }
test.todo('when users list is empty, add all users button is disabled'); 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 { 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'; import { UsersList } from './UsersList';
@ -12,11 +14,10 @@ test('renders correctly', () => {
function renderComponent() { function renderComponent() {
const user = new UserViewModel({ Username: 'user' }); const user = new UserViewModel({ Username: 'user' });
return renderWithQueryClient(
<UserContext.Provider value={{ user }}> const Wrapped = withTestQueryProvider(withUserProvider(UsersList, user));
<UsersList users={[]} teamId={3} />
</UserContext.Provider> return render(<Wrapped users={[]} teamId={3} />);
);
} }
test.todo('when users list is empty, add all users button is disabled'); 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 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'; import { CreateTeamForm } from './CreateTeamForm';
test('filling the name should make the submit button clickable and emptying it should make it disabled', async () => { test('filling the name should make the submit button clickable and emptying it should make it disabled', async () => {
const { findByLabelText, findByText } = renderWithQueryClient( const Wrapped = withTestQueryProvider(CreateTeamForm);
<CreateTeamForm users={[]} teams={[]} />
const { findByLabelText, findByText } = render(
<Wrapped users={[]} teams={[]} />
); );
const button = await findByText('Create team'); 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 { 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'; import { TestSidebarProvider } from '../useSidebarState';
@ -30,11 +32,11 @@ test('dashboard items should render correctly', () => {
function renderComponent() { function renderComponent() {
const user = new UserViewModel({ Username: 'user' }); const user = new UserViewModel({ Username: 'user' });
const Wrapped = withUserProvider(withTestRouter(AzureSidebar), user);
return render( return render(
<UserContext.Provider value={{ user }}> <TestSidebarProvider>
<TestSidebarProvider> <Wrapped environmentId={1} />
<AzureSidebar environmentId={1} /> </TestSidebarProvider>
</TestSidebarProvider>
</UserContext.Provider>
); );
} }

@ -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 { createMockTeams } from '@/react-tools/test-mocks';
import { PublicSettingsResponse } from '@/react/portainer/settings/types'; import { PublicSettingsResponse } from '@/react/portainer/settings/types';
import { UserId } from '@/portainer/users/types'; import { UserId } from '@/portainer/users/types';
import { VersionResponse } from '@/react/portainer/system/useSystemVersion';
import { azureHandlers } from './setup-handlers/azure'; import { azureHandlers } from './setup-handlers/azure';
import { dockerHandlers } from './setup-handlers/docker'; import { dockerHandlers } from './setup-handlers/docker';
@ -85,6 +86,9 @@ export const handlers = [
http.get<never, never, Partial<StatusResponse>>('/api/status', () => http.get<never, never, Partial<StatusResponse>>('/api/status', () =>
HttpResponse.json({}) 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/teams/:id/memberships', () => HttpResponse.json([])),
http.get('/api/endpoints/agent_versions', () => 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: { test: {
globals: true, globals: true,
environment: 'jsdom', 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: { coverage: {
reporter: ['text', 'html'], reporter: ['text', 'html'],
exclude: ['node_modules/', 'app/setup-tests/global-setup.js'], exclude: ['node_modules/', 'app/setup-tests/global-setup.js'],

Loading…
Cancel
Save