refactor: make testing more stable

This commit is contained in:
Rain 2023-07-29 18:07:00 +08:00
parent 767b81c65d
commit 3c7b3f3caf
14 changed files with 172 additions and 75 deletions

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { render, screen, userEvent, waitFor } from 'testUtils'; import { render, screen, sleep, userEvent, waitFor } from 'testUtils';
import App1 from '../demos/demo1'; import App1 from '../demos/demo1';
import App2 from '../demos/demo2'; import App2 from '../demos/demo2';
import App3 from '../demos/demo3'; import App3 from '../demos/demo3';
@ -13,6 +13,9 @@ import App9 from '../demos/demo9';
describe('DatePicker', () => { describe('DatePicker', () => {
it('basic', async () => { it('basic', async () => {
const { container, getByText } = render(<App1 />); const { container, getByText } = render(<App1 />);
await sleep();
const picker = container.querySelector('.ant-picker') as HTMLElement; const picker = container.querySelector('.ant-picker') as HTMLElement;
const input = container.querySelector('input') as HTMLElement; const input = container.querySelector('input') as HTMLElement;
@ -35,6 +38,9 @@ describe('DatePicker', () => {
it('GMT', async () => { it('GMT', async () => {
const { container, getByText } = render(<App2 />); const { container, getByText } = render(<App2 />);
await sleep();
const picker = container.querySelector('.ant-picker') as HTMLElement; const picker = container.querySelector('.ant-picker') as HTMLElement;
const input = container.querySelector('input') as HTMLElement; const input = container.querySelector('input') as HTMLElement;
@ -53,6 +59,9 @@ describe('DatePicker', () => {
it('non-UTC', async () => { it('non-UTC', async () => {
const { container } = render(<App3 />); const { container } = render(<App3 />);
await sleep();
const picker = container.querySelector('.ant-picker') as HTMLElement; const picker = container.querySelector('.ant-picker') as HTMLElement;
const input = container.querySelector('input') as HTMLElement; const input = container.querySelector('input') as HTMLElement;
@ -74,6 +83,9 @@ describe('DatePicker', () => {
describe('RangePicker', () => { describe('RangePicker', () => {
it('GMT', async () => { it('GMT', async () => {
const { container, getByPlaceholderText } = render(<App4 />); const { container, getByPlaceholderText } = render(<App4 />);
await sleep();
const picker = container.querySelector('.ant-picker') as HTMLElement; const picker = container.querySelector('.ant-picker') as HTMLElement;
const startInput = getByPlaceholderText('Start date'); const startInput = getByPlaceholderText('Start date');
const endInput = getByPlaceholderText('End date'); const endInput = getByPlaceholderText('End date');
@ -92,6 +104,9 @@ describe('RangePicker', () => {
it('non-GMT', async () => { it('non-GMT', async () => {
const { container, getByPlaceholderText } = render(<App5 />); const { container, getByPlaceholderText } = render(<App5 />);
await sleep();
const picker = container.querySelector('.ant-picker') as HTMLElement; const picker = container.querySelector('.ant-picker') as HTMLElement;
const startInput = getByPlaceholderText('Start date'); const startInput = getByPlaceholderText('Start date');
const endInput = getByPlaceholderText('End date'); const endInput = getByPlaceholderText('End date');
@ -115,6 +130,9 @@ describe('RangePicker', () => {
it('non-UTC', async () => { it('non-UTC', async () => {
const { container, getByPlaceholderText } = render(<App6 />); const { container, getByPlaceholderText } = render(<App6 />);
await sleep();
const picker = container.querySelector('.ant-picker') as HTMLElement; const picker = container.querySelector('.ant-picker') as HTMLElement;
const startInput = getByPlaceholderText('Start date'); const startInput = getByPlaceholderText('Start date');
const endInput = getByPlaceholderText('End date'); const endInput = getByPlaceholderText('End date');
@ -133,6 +151,9 @@ describe('RangePicker', () => {
it('showTime=false,gmt=true,utc=true', async () => { it('showTime=false,gmt=true,utc=true', async () => {
const { container } = render(<App7 />); const { container } = render(<App7 />);
await sleep();
const picker = container.querySelector('.ant-picker') as HTMLElement; const picker = container.querySelector('.ant-picker') as HTMLElement;
const input = container.querySelector('input') as HTMLElement; const input = container.querySelector('input') as HTMLElement;
@ -152,6 +173,9 @@ describe('RangePicker', () => {
it('showTime=false,gmt=false,utc=true', async () => { it('showTime=false,gmt=false,utc=true', async () => {
const { container } = render(<App8 />); const { container } = render(<App8 />);
await sleep();
const picker = container.querySelector('.ant-picker') as HTMLElement; const picker = container.querySelector('.ant-picker') as HTMLElement;
const input = container.querySelector('input') as HTMLElement; const input = container.querySelector('input') as HTMLElement;
@ -175,6 +199,9 @@ describe('RangePicker', () => {
it('showTime=false,gmt=true,utc=true & not input', async () => { it('showTime=false,gmt=true,utc=true & not input', async () => {
const currentDateString = new Date().toISOString().split('T')[0]; const currentDateString = new Date().toISOString().split('T')[0];
const { container } = render(<App9 />); const { container } = render(<App9 />);
await sleep();
const picker = container.querySelector('.ant-picker') as HTMLElement; const picker = container.querySelector('.ant-picker') as HTMLElement;
await userEvent.click(picker); await userEvent.click(picker);

View File

@ -1,11 +1,13 @@
import React from 'react'; import React from 'react';
import { render, screen } from 'testUtils'; import { render, screen, waitFor } from 'testUtils';
import App1 from '../demos/demo1'; import App1 from '../demos/demo1';
describe('FormItem', () => { describe('FormItem', () => {
it('should render correctly', () => { it('should render correctly', async () => {
render(<App1 />); render(<App1 />);
await waitFor(() => {
expect(screen.getByText('title')).toBeInTheDocument(); expect(screen.getByText('title')).toBeInTheDocument();
}); });
}); });
});

View File

@ -1,5 +1,18 @@
import { FormItem, FormProvider, Input, SchemaComponent } from '@nocobase/client'; import {
APIClientProvider,
CurrentUserProvider,
FormItem,
FormProvider,
Input,
SchemaComponent,
} from '@nocobase/client';
import React from 'react'; import React from 'react';
import { mockAPIClient } from '../../../../test';
const { apiClient, mockRequest } = mockAPIClient();
mockRequest.onGet('/auth:check').reply(() => {
return [200, { data: {} }];
});
const schema = { const schema = {
type: 'object', type: 'object',
@ -15,8 +28,12 @@ const schema = {
export default () => { export default () => {
return ( return (
<APIClientProvider apiClient={apiClient}>
<CurrentUserProvider>
<FormProvider> <FormProvider>
<SchemaComponent components={{ FormItem, Input }} schema={schema} /> <SchemaComponent components={{ FormItem, Input }} schema={schema} />
</FormProvider> </FormProvider>
</CurrentUserProvider>
</APIClientProvider>
); );
}; };

View File

@ -8,11 +8,13 @@ describe('FormV2', () => {
it('basic', async () => { it('basic', async () => {
render(<App1 />); render(<App1 />);
const input = document.querySelector('.ant-input') as HTMLInputElement; let input, submit;
const submit = screen.getByText('Submit'); await waitFor(() => {
input = document.querySelector('.ant-input') as HTMLInputElement;
submit = screen.getByText('Submit');
expect(input).toBeInTheDocument(); expect(input).toBeInTheDocument();
expect(screen.getByText('Nickname')).toBeInTheDocument(); expect(screen.queryByText('Nickname')).toBeInTheDocument();
});
await userEvent.type(input, '李四'); await userEvent.type(input, '李四');
await userEvent.click(submit); await userEvent.click(submit);
@ -41,8 +43,7 @@ describe('FormV2', () => {
}); });
}); });
// TODO: 等 @Testing-Library 升级到 14.x it('read pretty', async () => {
it.skip('read pretty', async () => {
render(<App3 />); render(<App3 />);
await waitFor(() => { await waitFor(() => {

View File

@ -4,6 +4,7 @@ import {
Action, Action,
CollectionField, CollectionField,
CollectionManagerProvider, CollectionManagerProvider,
CurrentUserProvider,
FormBlockProvider, FormBlockProvider,
FormItem, FormItem,
FormV2, FormV2,
@ -26,6 +27,9 @@ mockRequest.onPost('/users:update').reply((params) => {
}); });
return [200, JSON.parse(params.data)]; return [200, JSON.parse(params.data)];
}); });
mockRequest.onGet('/auth:check').reply(() => {
return [200, { data: {} }];
});
function useAction() { function useAction() {
const ctx = useFormBlockContext(); const ctx = useFormBlockContext();
@ -82,11 +86,13 @@ const schema: ISchema = {
export default () => { export default () => {
return ( return (
<APIClientProvider apiClient={apiClient}> <APIClientProvider apiClient={apiClient}>
<CurrentUserProvider>
<CollectionManagerProvider collections={collections}> <CollectionManagerProvider collections={collections}>
<SchemaComponentProvider components={{ FormBlockProvider, FormV2, FormItem, CollectionField, Action, Input }}> <SchemaComponentProvider components={{ FormBlockProvider, FormV2, FormItem, CollectionField, Action, Input }}>
<SchemaComponent schema={schema} /> <SchemaComponent schema={schema} />
</SchemaComponentProvider> </SchemaComponentProvider>
</CollectionManagerProvider> </CollectionManagerProvider>
</CurrentUserProvider>
</APIClientProvider> </APIClientProvider>
); );
}; };

View File

@ -5,6 +5,7 @@ import {
BlockSchemaComponentProvider, BlockSchemaComponentProvider,
CollectionField, CollectionField,
CollectionManagerProvider, CollectionManagerProvider,
CurrentUserProvider,
FormBlockProvider, FormBlockProvider,
FormItem, FormItem,
FormV2, FormV2,
@ -36,6 +37,9 @@ mockRequest.onPost('/users:update').reply((params) => {
}); });
return [200, JSON.parse(params.data)]; return [200, JSON.parse(params.data)];
}); });
mockRequest.onGet('/auth:check').reply(() => {
return [200, { data: {} }];
});
const useAction = () => { const useAction = () => {
const ctx = useFormBlockContext(); const ctx = useFormBlockContext();
@ -107,6 +111,7 @@ const schema: ISchema = {
export default () => { export default () => {
return ( return (
<APIClientProvider apiClient={apiClient}> <APIClientProvider apiClient={apiClient}>
<CurrentUserProvider>
<CollectionManagerProvider collections={collections}> <CollectionManagerProvider collections={collections}>
<SchemaComponentProvider <SchemaComponentProvider
components={{ FormBlockProvider, FormItem, CollectionField, Input, Action, FormV2, Password }} components={{ FormBlockProvider, FormItem, CollectionField, Input, Action, FormV2, Password }}
@ -116,6 +121,7 @@ export default () => {
</BlockSchemaComponentProvider> </BlockSchemaComponentProvider>
</SchemaComponentProvider> </SchemaComponentProvider>
</CollectionManagerProvider> </CollectionManagerProvider>
</CurrentUserProvider>
</APIClientProvider> </APIClientProvider>
); );
}; };

View File

@ -5,9 +5,11 @@ import {
BlockSchemaComponentProvider, BlockSchemaComponentProvider,
CollectionField, CollectionField,
CollectionManagerProvider, CollectionManagerProvider,
CurrentUserProvider,
FormBlockProvider, FormBlockProvider,
FormItem, FormItem,
FormV2, FormV2,
Grid,
Input, Input,
Password, Password,
SchemaComponent, SchemaComponent,
@ -26,6 +28,9 @@ mockRequest.onGet('/users:get').reply(200, {
password: '123456', password: '123456',
}, },
}); });
mockRequest.onGet('/auth:check').reply(() => {
return [200, { data: {} }];
});
const schema: ISchema = { const schema: ISchema = {
type: 'object', type: 'object',
@ -93,15 +98,17 @@ const schema: ISchema = {
export default () => { export default () => {
return ( return (
<APIClientProvider apiClient={apiClient}> <APIClientProvider apiClient={apiClient}>
<CurrentUserProvider>
<CollectionManagerProvider collections={collections}> <CollectionManagerProvider collections={collections}>
<SchemaComponentProvider <SchemaComponentProvider
components={{ FormBlockProvider, FormItem, CollectionField, Input, Action, FormV2, Password }} components={{ FormBlockProvider, FormItem, CollectionField, Input, Action, FormV2, Password, Grid }}
> >
<BlockSchemaComponentProvider> <BlockSchemaComponentProvider>
<SchemaComponent schema={schema} /> <SchemaComponent schema={schema} />
</BlockSchemaComponentProvider> </BlockSchemaComponentProvider>
</SchemaComponentProvider> </SchemaComponentProvider>
</CollectionManagerProvider> </CollectionManagerProvider>
</CurrentUserProvider>
</APIClientProvider> </APIClientProvider>
); );
}; };

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { render, screen } from 'testUtils'; import { render, screen, waitFor } from 'testUtils';
import App1 from '../demos/demo1'; import App1 from '../demos/demo1';
import App2 from '../demos/demo2'; import App2 from '../demos/demo2';
import App3 from '../demos/demo3'; import App3 from '../demos/demo3';
@ -14,13 +14,14 @@ describe('Grid', () => {
expect(screen.getByText('Block 1')).toBeInTheDocument(); expect(screen.getByText('Block 1')).toBeInTheDocument();
}); });
it('input', () => { it('input', async () => {
render(<App2 />); render(<App2 />);
await waitFor(() => {
const inputs = document.querySelectorAll('.ant-input'); const inputs = document.querySelectorAll('.ant-input');
expect(inputs.length).toBe(3); expect(inputs.length).toBe(3);
}); });
});
it('initializer', () => { it('initializer', () => {
render(<App3 />); render(<App3 />);

View File

@ -1,8 +1,24 @@
import { ISchema } from '@formily/react'; import { ISchema } from '@formily/react';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
import { Form, FormItem, Grid, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; import {
APIClientProvider,
CurrentUserProvider,
Form,
FormItem,
Grid,
Input,
SchemaComponent,
SchemaComponentProvider,
} from '@nocobase/client';
import React from 'react'; import React from 'react';
import { mockAPIClient } from '../../../../test';
const { apiClient, mockRequest } = mockAPIClient();
mockRequest.onGet('/auth:check').reply(() => {
return [200, { data: {} }];
});
const schema: ISchema = { const schema: ISchema = {
type: 'void', type: 'void',
name: 'grid1', name: 'grid1',
@ -54,8 +70,12 @@ const schema: ISchema = {
export default function App() { export default function App() {
return ( return (
<APIClientProvider apiClient={apiClient}>
<CurrentUserProvider>
<SchemaComponentProvider components={{ Form, Grid, Input, FormItem }}> <SchemaComponentProvider components={{ Form, Grid, Input, FormItem }}>
<SchemaComponent schema={schema} /> <SchemaComponent schema={schema} />
</SchemaComponentProvider> </SchemaComponentProvider>
</CurrentUserProvider>
</APIClientProvider>
); );
} }

View File

@ -4,6 +4,7 @@ import {
APIClientProvider, APIClientProvider,
BlockSchemaComponentProvider, BlockSchemaComponentProvider,
CollectionManagerProvider, CollectionManagerProvider,
CurrentUserProvider,
SchemaComponent, SchemaComponent,
SchemaComponentProvider, SchemaComponentProvider,
} from '@nocobase/client'; } from '@nocobase/client';
@ -19,6 +20,9 @@ mockRequest.onGet('/t_j6omof6tza8:list').reply(async (config) => {
await sleep(200); await sleep(200);
return [200, data]; return [200, data];
}); });
mockRequest.onGet('/auth:check').reply(() => {
return [200, { data: {} }];
});
const schema: ISchema = { const schema: ISchema = {
type: 'object', type: 'object',
@ -69,6 +73,7 @@ const schema: ISchema = {
export default () => { export default () => {
return ( return (
<APIClientProvider apiClient={apiClient}> <APIClientProvider apiClient={apiClient}>
<CurrentUserProvider>
<SchemaComponentProvider> <SchemaComponentProvider>
<CollectionManagerProvider collections={collections}> <CollectionManagerProvider collections={collections}>
<AntdSchemaComponentProvider> <AntdSchemaComponentProvider>
@ -78,6 +83,7 @@ export default () => {
</AntdSchemaComponentProvider> </AntdSchemaComponentProvider>
</CollectionManagerProvider> </CollectionManagerProvider>
</SchemaComponentProvider> </SchemaComponentProvider>
</CurrentUserProvider>
</APIClientProvider> </APIClientProvider>
); );
}; };

View File

@ -6,15 +6,18 @@ describe('RecordPicker', () => {
it('should show selected options', async () => { it('should show selected options', async () => {
render(<App1 />); render(<App1 />);
const selector = document.querySelector('.ant-select-selector') as HTMLElement; let selector;
await waitFor(() => {
selector = document.querySelector('.ant-select-selector') as HTMLElement;
expect(selector).toBeInTheDocument(); expect(selector).toBeInTheDocument();
});
await userEvent.click(selector); await userEvent.click(selector);
await waitFor(() => { await waitFor(() => {
// 弹窗标题 // 弹窗标题
expect(screen.getByText(/select record/i)).toBeInTheDocument(); expect(screen.queryByText(/select record/i)).toBeInTheDocument();
}); });
const checkboxes = document.querySelectorAll('.ant-checkbox'); const checkboxes = document.querySelectorAll('.ant-checkbox');
// 第 3 个选项的内容是: “软件开发” // 第 3 个选项的内容是: “软件开发”
@ -22,8 +25,8 @@ describe('RecordPicker', () => {
await userEvent.click(screen.getByText(/submit/i)); await userEvent.click(screen.getByText(/submit/i));
await waitFor(() => { await waitFor(() => {
expect(within(selector).getByText(/软件开发/i)).toBeInTheDocument(); expect(within(selector).queryByText(/软件开发/i)).toBeInTheDocument();
expect(screen.getByText(/软件开发/i, { selector: '.test-record-picker-read-pretty-item' })).toBeInTheDocument(); expect(screen.queryByText(/软件开发/i, { selector: '.test-record-picker-read-pretty-item' })).toBeInTheDocument();
}); });
}); });
}); });

View File

@ -9,6 +9,7 @@ import {
BlockItem, BlockItem,
CollectionField, CollectionField,
CollectionManagerProvider, CollectionManagerProvider,
CurrentUserProvider,
FormItem, FormItem,
Input, Input,
RecordPicker, RecordPicker,
@ -23,6 +24,9 @@ import data from './mockData';
const { apiClient, mockRequest } = mockAPIClient(); const { apiClient, mockRequest } = mockAPIClient();
mockRequest.onGet('/auth:check').reply(() => {
return [200, { data: {} }];
});
mockRequest.onGet('/tt_bd_range:list').reply(({ params }) => { mockRequest.onGet('/tt_bd_range:list').reply(({ params }) => {
// 已选中的 id // 已选中的 id
const ids = JSON.parse(params.filter).$and?.[0]?.['id.$ne'] || []; const ids = JSON.parse(params.filter).$and?.[0]?.['id.$ne'] || [];
@ -172,11 +176,13 @@ export default () => {
return ( return (
<APIClientProvider apiClient={apiClient}> <APIClientProvider apiClient={apiClient}>
<CurrentUserProvider>
<CollectionManagerProvider collections={mainCollections}> <CollectionManagerProvider collections={mainCollections}>
<SchemaComponentProvider components={components}> <SchemaComponentProvider components={components}>
<SchemaComponent schema={schema} /> <SchemaComponent schema={schema} />
</SchemaComponentProvider> </SchemaComponentProvider>
</CollectionManagerProvider> </CollectionManagerProvider>
</CurrentUserProvider>
</APIClientProvider> </APIClientProvider>
); );
}; };

View File

@ -14,18 +14,18 @@ type VariablesCtx = {
}; };
export const useVariablesCtx = (): VariablesCtx => { export const useVariablesCtx = (): VariablesCtx => {
const { data } = useCurrentUserContext() || {}; const currentUser = useCurrentUserContext();
const { field, service, rowKey } = useTableBlockContext(); const { field, service, rowKey } = useTableBlockContext();
const contextData = service?.data?.data?.filter((v) => (field?.data?.selectedRowKeys || [])?.includes(v[rowKey])); const contextData = service?.data?.data?.filter((v) => (field?.data?.selectedRowKeys || [])?.includes(v[rowKey]));
return useMemo(() => { return useMemo(() => {
return { return {
$user: data?.data || {}, $user: currentUser?.data?.data || {},
$date: { $date: {
now: () => dayjs().toISOString(), now: () => dayjs().toISOString(),
}, },
$context: contextData, $context: contextData,
}; };
}, [data]); }, [contextData, currentUser?.data?.data]);
}; };
export const isVariable = (str: unknown) => { export const isVariable = (str: unknown) => {

View File

@ -68,11 +68,6 @@ function Label() {
return null; return null;
} }
if (!currentUser) {
error('Please check if provide `CurrentUserProvider` in your app.');
throw new Error('Please check if provide `CurrentUserProvider` in your app.');
}
if (!systemSettings) { if (!systemSettings) {
error('Please check if provide `SystemSettingsProvider` in your app.'); error('Please check if provide `SystemSettingsProvider` in your app.');
throw new Error('Please check if provide `SystemSettingsProvider` in your app.'); throw new Error('Please check if provide `SystemSettingsProvider` in your app.');