Merge remote-tracking branch 'upstream/master' into feat-password-reveal

This commit is contained in:
wuduoyi 2022-04-12 14:23:01 +08:00
commit 502f1a6bc0
41 changed files with 2161 additions and 1793 deletions

8
.github/labeler.yml vendored
View File

@ -16,10 +16,10 @@ labels:
'help wanted':
- '\bhelp( wanted)?\b'
'feat':
- '\bfeat\b'
- '\bfeat'
'fix':
- '\bfix\b'
- '\bfix'
'chore':
- '\bchore\b'
- '\bchore'
'doc':
- '\bdoc[s]?\b'
- '\bdoc[s]?'

View File

@ -197,9 +197,141 @@ exports[`factory:definitions 1`] = `
class="cxd-Combo-items"
>
<div
class="cxd-Combo-placeholder"
class="cxd-Combo-item"
>
&lt;空&gt;
<div
class="cxd-Combo-itemInner"
>
<div
class="cxd-Form cxd-Form--normal cxd-Combo-form"
novalidate=""
>
<input
style="display: none;"
type="submit"
/>
<div
class="cxd-Form-item cxd-Form-item--normal"
data-role="form-item"
>
<label
class="cxd-Form-label"
>
<span>
<span
class="cxd-TplField"
>
<span>
combo 1
</span>
</span>
</span>
</label>
<div
class="cxd-Form-control cxd-TextControl"
>
<div
class="cxd-TextControl-input"
>
<input
autocomplete="off"
name="key"
placeholder=""
size="10"
type="text"
value=""
/>
</div>
</div>
</div>
<div
class="cxd-Form-item cxd-Form-item--normal"
data-role="form-item"
>
<label
class="cxd-Form-label"
>
<span>
<span
class="cxd-TplField"
>
<span>
combo 2
</span>
</span>
</span>
</label>
<div
class="cxd-Form-control cxd-TextControl"
>
<div
class="cxd-TextControl-input"
>
<input
autocomplete="off"
name="value"
placeholder=""
size="10"
type="text"
value="ref value"
/>
</div>
</div>
<div
class="cxd-Remark cxd-Form-remark"
>
<span
class="cxd-Remark-icon"
>
<icon-mock
classname=" icon-warning-mark"
icon="warning-mark"
/>
</span>
</div>
</div>
<div
class="cxd-Form-item cxd-Form-item--normal"
data-role="form-item"
>
<label
class="cxd-Form-label"
>
<span>
<span
class="cxd-TplField"
>
<span>
children
</span>
</span>
</span>
</label>
<div
class="cxd-Remark cxd-Form-remark"
>
<span
class="cxd-Remark-icon"
>
<icon-mock
classname=" icon-warning-mark"
icon="warning-mark"
/>
</span>
</div>
</div>
</div>
</div>
<a
class="cxd-Combo-delBtn "
data-position="bottom"
data-tooltip="删除"
>
<icon-mock
classname="icon icon-status-close"
icon="status-close"
/>
</a>
</div>
</div>
<div

View File

@ -6,22 +6,24 @@ import {
import '../src/themes/default';
import {render as amisRender} from '../src/index';
import React = require('react');
import {render, fireEvent, cleanup} from '@testing-library/react';
import {render, fireEvent, cleanup, waitFor} from '@testing-library/react';
import {wait, makeEnv} from './helper';
test('factory unregistered Renderer', async () => {
const {container} = render(
const {container, getByText} = render(
amisRender({
type: 'my-renderer',
a: 23
})
);
await wait(300);
expect(container).toMatchSnapshot(); // not found
await waitFor(() => {
expect(getByText('Error: 找不到对应的渲染器')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
test('factory custom not found!', async () => {
const {container} = render(
const {container, getByText} = render(
amisRender(
{
type: 'my-renderer',
@ -33,12 +35,14 @@ test('factory custom not found!', async () => {
})
)
);
await wait(300);
await waitFor(() => {
expect(getByText('Not Found')).toBeInTheDocument();
});
expect(container).toMatchSnapshot(); // not found
});
test('factory custom not found 2!', async () => {
const {container} = render(
const {container, getByText} = render(
amisRender(
{
type: 'my-renderer',
@ -50,12 +54,14 @@ test('factory custom not found 2!', async () => {
})
)
);
await wait(300);
await waitFor(() => {
expect(getByText('Not Found')).toBeInTheDocument();
});
expect(container).toMatchSnapshot(); // not found
});
test('factory custom not found 3!', async () => {
const {container} = render(
const {container, getByText} = render(
amisRender(
{
type: 'my-renderer',
@ -67,12 +73,14 @@ test('factory custom not found 3!', async () => {
})
)
);
await wait(300);
await waitFor(() => {
expect(getByText('Not Found')).toBeInTheDocument();
});
expect(container).toMatchSnapshot(); // not found
});
test('factory load Renderer on need', async () => {
const {container} = render(
const {container, getByText} = render(
amisRender(
{
type: 'my-renderer2',
@ -100,7 +108,9 @@ test('factory load Renderer on need', async () => {
})
)
);
await wait(300);
await waitFor(() => {
expect(getByText('This is Custom Renderer2, a is 23')).toBeInTheDocument();
});
expect(container).toMatchSnapshot(); // not found
});
@ -193,13 +203,18 @@ test('factory:definitions', async () => {
)
);
await wait(300);
await waitFor(() => {
expect(getByText('新增')).toBeInTheDocument();
});
fireEvent.click(getByText('新增'));
await waitFor(() => {
expect(getByText('combo 1')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
test('factory:definitions override', () => {
const {container} = render(
test('factory:definitions override', async () => {
const {container, getByText} = render(
amisRender(
{
definitions: {
@ -271,5 +286,8 @@ test('factory:definitions override', () => {
)
);
await waitFor(() => {
expect(getByText('combo')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});

View File

@ -1,14 +1,33 @@
import {waitFor} from '@testing-library/react';
import {RenderOptions} from '../src/factory';
// jest.useFakeTimers 会修改 global 的 setTimeout 所以需要把原始的记录下来。
const timerFn = setTimeout;
export function wait(duration: number, fn?: Function) {
return new Promise<void>(resolve => {
timerFn(() => {
fn && fn();
resolve();
}, duration);
});
const timerFn = (global as any).originSetTimeout || setTimeout;
export function wait(duration: number, fnOrUseWaitFor?: Function | boolean) {
const useWaitFor = fnOrUseWaitFor !== false;
const fn = (
typeof fnOrUseWaitFor === 'function' ? fnOrUseWaitFor : undefined
) as Function;
return useWaitFor
? waitFor(
() =>
new Promise<void>(resolve => {
timerFn(() => {
fn && fn();
resolve();
}, duration);
}),
{
timeout: duration + 100
}
)
: new Promise<void>(resolve => {
timerFn(() => {
fn && fn();
resolve();
}, duration);
});
}
export function makeEnv(env?: Partial<RenderOptions>): RenderOptions {

View File

@ -1,5 +1,5 @@
const originalWarn = console.warn.bind(console.warn);
require('@testing-library/jest-dom');
require('moment-timezone');
const moment = require('moment');
moment.tz.setDefault('Asia/Shanghai');

View File

@ -1,7 +1,13 @@
import React = require('react');
import Action from '../../src/renderers/Action';
import * as renderer from 'react-test-renderer';
import {render, fireEvent, cleanup, screen} from '@testing-library/react';
import {
render,
fireEvent,
cleanup,
screen,
waitFor
} from '@testing-library/react';
import {render as amisRender} from '../../src/index';
import {makeEnv, wait} from '../helper';
import '../../src/themes/default';
@ -158,10 +164,9 @@ test('Renderers:Action countDown', async () => {
button = container.querySelector('button');
expect(button).toBeNull();
await wait(2000);
button = container.querySelector('button');
expect(button).not.toBeNull();
await waitFor(() => {
expect(container.querySelector('button')).not.toBeInTheDocument();
});
});
test('Renderers:Action tooltip', async () => {

View File

@ -1,12 +1,12 @@
import React = require('react');
import {render, fireEvent} from '@testing-library/react';
import {render, fireEvent, waitFor} from '@testing-library/react';
import '../../src/themes/default';
import {render as amisRender} from '../../src/index';
import {makeEnv, wait} from '../helper';
import 'jest-canvas-mock';
test('Renderer:bar-code', async () => {
const {container} = render(
const {container, getByTestId} = render(
amisRender(
{
type: 'page',
@ -19,7 +19,7 @@ test('Renderer:bar-code', async () => {
makeEnv({})
)
);
await wait(500);
await waitFor(() => expect(getByTestId('barcode')).toBeInTheDocument());
expect(container).toMatchSnapshot();
});

View File

@ -1,12 +1,12 @@
import React = require('react');
import {render} from '@testing-library/react';
import {render, waitFor} from '@testing-library/react';
import '../../src/themes/default';
import {render as amisRender} from '../../src/index';
import {makeEnv, wait} from '../helper';
import rows from '../mockData/rows';
test('Renderer:crud', async () => {
const {container, findByText} = render(
const {container, getByText} = render(
amisRender(
{
type: 'page',
@ -62,9 +62,12 @@ test('Renderer:crud', async () => {
)
);
await findByText('Internet Explorer 4.0');
await wait(1000);
await waitFor(() => {
expect(getByText('Internet Explorer 4.0')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});

View File

@ -1,5 +1,5 @@
import React = require('react');
import {render, fireEvent} from '@testing-library/react';
import {render, fireEvent, waitFor} from '@testing-library/react';
import '../../src/themes/default';
import {render as amisRender} from '../../src/index';
import {makeEnv, wait} from '../helper';
@ -43,6 +43,12 @@ test('Renderer:carousel', async () => {
const rightArrow = container.querySelector('div.cxd-Carousel-rightArrow');
fireEvent.click(rightArrow as HTMLDivElement);
await wait(500);
// 等到第二个点变成激活状态
await waitFor(() => {
expect(
container.querySelector('span:nth-child(2).is-active')
).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});

View File

@ -124,7 +124,7 @@ exports[`Renderer:city 1`] = `
aria-expanded="false"
aria-haspopup="listbox"
aria-labelledby="downshift-2-label"
class="cxd-Select"
class="cxd-Select is-focused"
role="combobox"
tabindex="0"
>

View File

@ -184,6 +184,7 @@ exports[`Renderer:Form initApi 1`] = `
/>
<div
class="cxd-Spinner cxd-Spinner--overlay"
data-testid="spinner"
>
<div
class="cxd-Spinner-icon cxd-Spinner-icon--default"
@ -1138,7 +1139,7 @@ exports[`Renderer:Form:valdiate 2`] = `
type="submit"
/>
<div
class="cxd-Form-item cxd-Form-item--normal is-required"
class="cxd-Form-item cxd-Form-item--normal is-error is-required has-error--isRequired"
data-role="form-item"
>
<label
@ -1160,7 +1161,7 @@ exports[`Renderer:Form:valdiate 2`] = `
</span>
</label>
<div
class="cxd-Form-control cxd-TextControl"
class="cxd-Form-control is-error has-error--isRequired cxd-TextControl"
>
<div
class="cxd-TextControl-input"
@ -1175,6 +1176,13 @@ exports[`Renderer:Form:valdiate 2`] = `
/>
</div>
</div>
<ul
class="cxd-Form-feedback"
>
<li>
这是必填项
</li>
</ul>
</div>
</form>
</div>

View File

@ -72,160 +72,6 @@ Object {
`;
exports[`Form:initData:remote 1`] = `
<div>
<div
class="cxd-Panel cxd-Panel--default cxd-Panel--form"
style="position: relative;"
>
<div
class="cxd-Panel-heading"
>
<h3
class="cxd-Panel-title"
>
<span
class="cxd-TplField"
>
<span>
The form
</span>
</span>
</h3>
</div>
<div
class="cxd-Panel-body"
>
<form
class="cxd-Form cxd-Form--normal"
novalidate=""
>
<input
style="display: none;"
type="submit"
/>
<div
class="cxd-Spinner-overlay"
/>
<div
class="cxd-Spinner cxd-Spinner--overlay"
>
<div
class="cxd-Spinner-icon cxd-Spinner-icon--default"
/>
</div>
<div
class="cxd-Form-item cxd-Form-item--normal"
data-role="form-item"
>
<label
class="cxd-Form-label"
>
<span>
<span
class="cxd-TplField"
>
<span>
Label
</span>
</span>
</span>
</label>
<div
class="cxd-Form-control cxd-TextControl is-disabled"
>
<div
class="cxd-TextControl-input"
>
<input
autocomplete="off"
disabled=""
name="a"
placeholder=""
size="10"
type="text"
value="123"
/>
</div>
</div>
</div>
</form>
</div>
<div
class="cxd-Panel-footerWrap"
>
<div
class="cxd-Panel-btnToolbar cxd-Panel-footer"
>
<div
class="cxd-Button cxd-Button--default is-disabled"
disabled=""
>
<span>
Submit
</span>
</div>
</div>
</div>
<div
class="resize-sensor"
style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px; overflow: scroll; z-index: -1; visibility: hidden;"
>
<div
class="resize-sensor-expand"
style="position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;"
>
<div
style="position: absolute; left: 0px; top: 0px; width: 10px; height: 10px;"
/>
</div>
<div
class="resize-sensor-shrink"
style="position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;"
>
<div
style="position: absolute; left: 0; top: 0; width: 200%; height: 200%"
/>
</div>
<div
class="resize-sensor-appear"
style="position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;animation-name: apearSensor; animation-duration: 0.2s;"
/>
</div>
</div>
</div>
`;
exports[`Form:initData:remote 2`] = `
Object {
"config": Object {
"cancelExecutor": [Function],
"errorMessage": "fetchFailed",
"onSuccess": [Function],
"successMessage": undefined,
},
"method": "get",
"query": Object {
"a": "123",
},
"url": "/api/xxx?a=123",
}
`;
exports[`Form:initData:remote 3`] = `
<div>
<div
class="cxd-Panel cxd-Panel--default cxd-Panel--form"
@ -352,7 +198,23 @@ exports[`Form:initData:remote 3`] = `
</div>
`;
exports[`Form:initData:remote 4`] = `
exports[`Form:initData:remote 2`] = `
Object {
"config": Object {
"cancelExecutor": [Function],
"errorMessage": "fetchFailed",
"onSuccess": [Function],
"successMessage": undefined,
},
"method": "get",
"query": Object {
"a": "123",
},
"url": "/api/xxx?a=123",
}
`;
exports[`Form:initData:remote 3`] = `
Object {
"a": 1,
"b": 2,

View File

@ -52,14 +52,18 @@ exports[`Renderer:tabs 1`] = `
<li
class="cxd-Tabs-link is-active"
>
<a>
<a
title="基本配置"
>
基本配置
</a>
</li>
<li
class="cxd-Tabs-link"
>
<a>
<a
title="其他配置"
>
其他配置
</a>
</li>

View File

@ -90,14 +90,18 @@ exports[`Renderer:tabs-transfer 1`] = `
<li
class="cxd-Tabs-link is-active"
>
<a>
<a
title="成员"
>
成员
</a>
</li>
<li
class="cxd-Tabs-link"
>
<a>
<a
title="用户"
>
用户
</a>
</li>

View File

@ -1248,7 +1248,7 @@ exports[`Renderer:text with counter 2`] = `
<span
class="cxd-TextControl-counter"
>
0
4
</span>
</div>
</div>
@ -1512,7 +1512,7 @@ exports[`Renderer:text with counter and maxLength 2`] = `
<span
class="cxd-TextControl-counter"
>
0/10
4/10
</span>
</div>
</div>

View File

@ -2,7 +2,7 @@ import React = require('react');
import {render, waitForElementToBeRemoved} from '@testing-library/react';
import '../../../src/themes/default';
import {render as amisRender} from '../../../src/index';
import {makeEnv} from '../../helper';
import {makeEnv, wait} from '../../helper';
test('Renderer:chained-select', async () => {
const {container, findByText} = render(
@ -44,11 +44,6 @@ test('Renderer:chained-select', async () => {
)
);
await findByText('B 0');
await waitForElementToBeRemoved(() =>
container.querySelector('.cxd-Spinner')
);
await wait(500);
expect(container).toMatchSnapshot();
});

View File

@ -2,7 +2,7 @@ import React = require('react');
import {render, fireEvent} from '@testing-library/react';
import '../../../src/themes/default';
import {render as amisRender} from '../../../src/index';
import {makeEnv} from '../../helper';
import {makeEnv, wait} from '../../helper';
test('Renderer:city', async () => {
const {container, getByText, findByText} = render(
@ -27,7 +27,6 @@ test('Renderer:city', async () => {
)
);
// await wait(200);
// 第一个用 findByText 来等待 suspense 组件加载出来后面的一般来说就不需要await的除非是耗时操作
fireEvent.click(await findByText('请选择'));
fireEvent.click(getByText('北京市'));
@ -36,5 +35,6 @@ test('Renderer:city', async () => {
fireEvent.click(getByText('请选择'));
fireEvent.click(getByText('东城区'));
await wait(500);
expect(container).toMatchSnapshot();
});

View File

@ -1,5 +1,11 @@
import React = require('react');
import {render, fireEvent, findByText} from '@testing-library/react';
import {
render,
fireEvent,
findByText,
waitFor,
act
} from '@testing-library/react';
import '../../../src/themes/default';
import {render as amisRender} from '../../../src/index';
import {makeEnv, wait} from '../../helper';
@ -97,28 +103,36 @@ test('Renderer:combo multiple', async () => {
)
);
await waitFor(() => {
expect(getByText('新增')).toBeInTheDocument();
});
const add = await findByText(container, '新增');
// 点击新增
add.click();
await wait(500);
await waitFor(() => {
expect(container.querySelector('input[name="text"]')).toBeInTheDocument();
});
// 输入
const input = container.querySelector(
'.cxd-TextControl-input input'
'input[name="text"]'
) as HTMLInputElement;
fireEvent.change(input, {target: {value: 'amis'}});
await wait(300);
// 下拉框点击
(container.querySelector('.cxd-Select') as HTMLDivElement).click();
fireEvent.click(container.querySelector('.cxd-Select')!);
await findByText(container, 'aOptions');
await waitFor(() => {
expect(getByText('aOptions')).toBeInTheDocument();
});
fireEvent.click(getByText('aOptions'));
await wait(500);
await wait(300);
const formDebug = JSON.parse(document.querySelector('pre code')!.innerHTML);
const formDebug = JSON.parse(container.querySelector('pre code')!.innerHTML);
expect(formDebug).toEqual({
combo: [

View File

@ -1,5 +1,5 @@
import React = require('react');
import {render, cleanup, fireEvent} from '@testing-library/react';
import {render, cleanup, fireEvent, waitFor} from '@testing-library/react';
import '../../../src/themes/default';
import {render as amisRender} from '../../../src/index';
import {makeEnv, wait} from '../../helper';
@ -125,13 +125,15 @@ test('Control:formItem:reload', async () => {
)
);
await wait(300);
expect(fetcher).toHaveBeenCalled();
await waitFor(() => {
expect(fetcher).toHaveBeenCalled();
});
fireEvent.click(getByText('Reload'));
await wait(500);
expect(fetcher).toBeCalledTimes(2);
await waitFor(() => {
expect(fetcher).toBeCalledTimes(2);
});
});
test('options:clearValueOnHidden ', async () => {

View File

@ -2,7 +2,7 @@ import React = require('react');
import {fireEvent, render} from '@testing-library/react';
import '../../../src/themes/default';
import {render as amisRender, setIconVendor} from '../../../src/index';
import {makeEnv} from '../../helper';
import {makeEnv, wait} from '../../helper';
test('Renderer:icon-picker', async () => {
const vendors = [
@ -46,7 +46,7 @@ test('Renderer:icon-picker', async () => {
);
const input = container.querySelector('input[name="a"]') as any;
input?.focus();
fireEvent.focus(input!);
fireEvent.click(await findByText(/Glyphicons/));
fireEvent.change(input!, {
@ -57,5 +57,6 @@ test('Renderer:icon-picker', async () => {
fireEvent.click(getByTitle('glyphicon glyphicon-plus'));
await wait(500);
expect(container).toMatchSnapshot();
});

View File

@ -1,7 +1,13 @@
import React = require('react');
import PageRenderer from '../../../src/renderers/Form';
import * as renderer from 'react-test-renderer';
import {render, fireEvent, cleanup, getByText} from '@testing-library/react';
import {
render,
fireEvent,
cleanup,
getByText,
waitFor
} from '@testing-library/react';
import '../../../src/themes/default';
import {render as amisRender} from '../../../src/index';
import {wait, makeEnv} from '../../helper';
@ -23,13 +29,14 @@ afterEach(() => {
});
test('Renderer:Form', async () => {
const resultPromise = Promise.resolve({
data: {
status: 0,
msg: 'ok'
}
});
const fetcher = jest.fn().mockImplementation(() => resultPromise);
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
status: 0,
msg: 'ok'
}
})
);
const {container, getByText} = render(
amisRender(
{
@ -60,11 +67,10 @@ test('Renderer:Form', async () => {
expect(container).toMatchSnapshot();
fireEvent.click(getByText('Submit'));
await resultPromise;
await wait(300);
expect(fetcher).toHaveBeenCalled();
expect(fetcher.mock.calls[0][0]).toMatchSnapshot();
await waitFor(() => {
expect(fetcher).toHaveBeenCalled();
expect(fetcher.mock.calls[0][0]).toMatchSnapshot();
});
});
test('Renderer:Form:valdiate', async () => {
@ -100,12 +106,13 @@ test('Renderer:Form:valdiate', async () => {
);
fireEvent.click(getByText('Submit'));
await wait(300);
await waitFor(() => {
expect(container.querySelector('.cxd-Form-feedback')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
expect(onSubmit).not.toHaveBeenCalled();
await wait(300);
expect(notify).toHaveBeenCalledWith('error', '依赖的部分字段没有通过验证');
const input = container.querySelector('input[name=a]');
@ -115,13 +122,20 @@ test('Renderer:Form:valdiate', async () => {
value: '123'
}
});
await wait(500); // 有 250 秒左右的节流
await waitFor(() => {
expect(
container.querySelector('input[name=a][value="123"]')
).toBeInTheDocument();
});
fireEvent.click(getByText('Submit'));
expect(container).toMatchSnapshot();
await wait(300);
expect(onSubmit).toHaveBeenCalled();
expect(onSubmit.mock.calls[0][0]).toMatchSnapshot();
await waitFor(() => {
expect(onSubmit).toHaveBeenCalled();
expect(onSubmit.mock.calls[0][0]).toMatchSnapshot();
});
});
test('Renderer:Form:remoteValidate', async () => {
@ -166,7 +180,9 @@ test('Renderer:Form:remoteValidate', async () => {
);
fireEvent.click(getByText('Submit'));
await wait(300);
await waitFor(() => {
expect(container.querySelector('.cxd-Form-feedback')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
@ -219,19 +235,29 @@ test('Renderer:Form:onValidate', async () => {
)
);
await waitFor(() => {
expect(getByText('Submit')).toBeInTheDocument();
});
fireEvent.click(getByText('Submit'));
await wait(300);
await waitFor(() => {
expect(container.querySelector('.cxd-Form-feedback')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
expect(onSubmit).not.toHaveBeenCalled();
expect(onValidate).toHaveBeenCalled();
expect(onValidate.mock.calls[0][0]).toMatchSnapshot();
await wait(300);
expect(notify).toHaveBeenCalledWith('error', '依赖的部分字段没有通过验证');
fireEvent.click(getByText('Submit'));
await wait(300);
await waitFor(() => {
expect(
container.querySelector('.cxd-Form-feedback')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
expect(onSubmit).toHaveBeenCalled();
@ -240,18 +266,16 @@ test('Renderer:Form:onValidate', async () => {
test('Renderer:Form initApi', async () => {
const notify = jest.fn();
let p0;
const fetcher = jest.fn().mockImplementation(
() =>
(p0 = Promise.resolve({
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
status: 0,
data: {
status: 0,
data: {
a: 1,
b: 2
}
a: 1,
b: 2
}
}))
}
})
);
const {container, getByText} = render(
amisRender(
@ -280,10 +304,14 @@ test('Renderer:Form initApi', async () => {
)
);
await waitFor(() => {
expect(
container.querySelector('[name="a"][value="1"]')
).toBeInTheDocument();
});
// fetch 调用了,所有 initApi 接口调用了
expect(fetcher).toHaveBeenCalled();
await p0;
await wait(10);
// 通过 snapshot 可断定 initApi 返回值已经作用到了表单项上。
expect(container).toMatchSnapshot();
@ -434,18 +462,16 @@ test('Renderer:Form sendOn:false', async () => {
test('Renderer:Form sendOn:true', async () => {
const notify = jest.fn();
let p0;
const fetcher = jest.fn().mockImplementation(
() =>
(p0 = Promise.resolve({
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
status: 0,
data: {
status: 0,
data: {
a: 1,
b: 2
}
a: 1,
b: 2
}
}))
}
})
);
const {container, getByText} = render(
amisRender(
@ -482,8 +508,14 @@ test('Renderer:Form sendOn:true', async () => {
)
);
await waitFor(() => {
expect(
container.querySelector('[name="a"][value="1"]')
).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(fetcher).toHaveBeenCalled();
await p0;
await wait(10);
expect(container).toMatchSnapshot();
});

View File

@ -42,13 +42,19 @@ test('Form:initData', async () => {
)
);
await waitFor(() => {
expect(
container.querySelector('[name="a"][value="1"]')
).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/Submit/));
await wait(500);
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchSnapshot();
await waitFor(() => {
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchSnapshot();
});
});
test('Form:initData:super', async () => {
@ -90,17 +96,18 @@ test('Form:initData:super', async () => {
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/Submit/));
await wait(500);
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchInlineSnapshot(
`
await waitFor(() => {
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchInlineSnapshot(
`
Object {
"a": 1,
"b": 2,
}
`
);
);
});
});
test('Form:initData:without-super', async () => {
@ -144,10 +151,11 @@ test('Form:initData:without-super', async () => {
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/Submit/));
await wait(500);
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchSnapshot();
await waitFor(() => {
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchSnapshot();
});
});
test('Form:initData:remote', async () => {
@ -191,57 +199,64 @@ test('Form:initData:remote', async () => {
})
)
);
expect(container).toMatchSnapshot();
await wait(300);
await resultPromise;
await wait(300);
await waitFor(() => {
expect(
container.querySelector('[name="a"][value="1"]')
).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
expect(fetcher).toHaveBeenCalled();
expect(fetcher.mock.calls[0][0]).toMatchSnapshot();
expect(container).toMatchSnapshot();
fireEvent.click(getByText('Submit'));
await wait(500);
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchSnapshot();
await waitFor(() => {
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchSnapshot();
});
});
// 主要用来测试 form 的 source 接口是否在 initApi 后调用
// 并且发送的参数是否可以携带 initApi 返回的数据
// 并且 source 接口如果返回了 value 是否可以应用上。
test('Form:initData:remote:options:source', async () => {
const initApiPromise = Promise.resolve({
data: {
status: 0,
msg: 'ok',
const fetcherinitApi = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
id: 1
status: 0,
msg: 'ok',
data: {
id: 1
}
}
}
});
const fetcherinitApi = jest.fn().mockImplementation(() => initApiPromise);
const sourceApiPromise = Promise.resolve({
data: {
status: 0,
msg: 'ok',
})
);
const fetcherSourceApi = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
value: 'b',
options: [
{
label: 'OptionA',
value: 'a'
},
{
label: 'OptionB',
value: 'b'
}
]
status: 0,
msg: 'ok',
data: {
value: 'b',
options: [
{
label: 'OptionA',
value: 'a'
},
{
label: 'OptionB',
value: 'b'
}
]
}
}
}
});
const fetcherSourceApi = jest.fn().mockImplementation(() => sourceApiPromise);
})
);
const onSubmit = jest.fn();
const fetcher = (arg1: any, ...rest: Array<any>) => {
const api = /\/api\/(\w+)/.test(arg1.url) ? RegExp.$1 : '';
@ -286,20 +301,22 @@ test('Form:initData:remote:options:source', async () => {
)
);
await initApiPromise;
await waitFor(() => expect(fetcherinitApi).toHaveBeenCalled());
expect(fetcherinitApi.mock.calls[0][0].url).toEqual('/api/initApi?op=init');
await waitFor(async () => {
expect(getByText('OptionB')).toBeInTheDocument();
await sourceApiPromise;
await waitFor(() => expect(fetcherSourceApi).toHaveBeenCalled());
// 这里要取 mock.call[1] 而不是 [0]因为这里其实会调用两次第一次被cancel了
expect(fetcherSourceApi.mock.calls[1][0].url).toEqual('/api/source?id=1');
expect(fetcherinitApi).toHaveBeenCalled();
expect(fetcherinitApi.mock.calls[0][0].url).toEqual('/api/initApi?op=init');
await wait(200); // 只有出现在 waitFor 里面的才有用。
});
fireEvent.click(getByText('Submit'));
await waitFor(() => expect(onSubmit).toBeCalled());
expect(onSubmit.mock.calls[0][0]).toMatchObject({
id: 1,
op: 'init',
a: 'b'
await waitFor(() => {
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchObject({
id: 1,
op: 'init',
a: 'b'
});
});
});

View File

@ -57,7 +57,7 @@ test('Renderer:radios', async () => {
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/Option A/));
await waitFor(async () => {
await waitFor(() => {
expect(
(container.querySelector('.cxd-PlainField') as Element).innerHTML
).toBe('a');

View File

@ -2,7 +2,7 @@ import React = require('react');
import {render, fireEvent} from '@testing-library/react';
import '../../../src/themes/default';
import {render as amisRender} from '../../../src/index';
import {makeEnv} from '../../helper';
import {makeEnv, wait} from '../../helper';
test('Renderer:range', async () => {
const {container} = render(
@ -40,6 +40,7 @@ test('Renderer:range', async () => {
value: '7'
}
});
await wait(200);
expect(container).toMatchSnapshot();
});
@ -83,6 +84,8 @@ test('Renderer:range:multiple', async () => {
const close = container.querySelector('a.cxd-InputRange-clear');
fireEvent.click(close!);
await wait(200);
expect(container).toMatchSnapshot();
});
@ -109,6 +112,7 @@ test('Renderer:range:showSteps', async () => {
)
);
await wait(200);
expect(container).toMatchSnapshot();
});
@ -141,6 +145,7 @@ test('Renderer:range:marks', async () => {
)
);
await wait(200);
expect(container).toMatchSnapshot();
});
@ -166,5 +171,6 @@ test('Renderer:range:tooltipVisible', async () => {
)
);
await wait(200);
expect(container).toMatchSnapshot();
});

View File

@ -1,5 +1,5 @@
import React = require('react');
import {render, cleanup, fireEvent} from '@testing-library/react';
import {render, cleanup, fireEvent, waitFor} from '@testing-library/react';
import '../../../src/themes/default';
import {render as amisRender} from '../../../src/index';
import {makeEnv, wait} from '../../helper';
@ -71,7 +71,12 @@ test('Renderer:service', async () => {
)
);
await wait(300);
await waitFor(() => {
expect(container.querySelector('[name="dy_1"]')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(fetcher).toHaveBeenCalled();
expect(container).toMatchSnapshot();
});
@ -120,14 +125,22 @@ test('form:service:remoteData', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(
container.querySelector('[name="child-a"][value="123"]')
).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText('Submit'));
await wait(500);
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchObject({
'child-a': '123'
await waitFor(() => {
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchObject({
'child-a': '123'
});
});
});
@ -183,15 +196,23 @@ test('form:service:remoteSchmea+data', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(
container.querySelector('[name="child-a"][value="123"]')
).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText('Submit'));
await wait(500);
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchObject({
'child-a': '123',
'child-b': '344'
await waitFor(() => {
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchObject({
'child-a': '123',
'child-b': '344'
});
});
});
@ -234,12 +255,19 @@ test('form:service:onChange', async () => {
value: '123'
}
});
await waitFor(() => {
expect(
container.querySelector('[name="child-a"][value="123"]')
).toBeInTheDocument();
});
fireEvent.click(getByText('Submit'));
await wait(500);
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchObject({
'child-a': '123'
await waitFor(() => {
expect(onSubmit).toBeCalled();
expect(onSubmit.mock.calls[0][0]).toMatchObject({
'child-a': '123'
});
});
});
@ -288,7 +316,14 @@ test('form:service:super-remoteData', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(
container.querySelector('[name="child-a"][value="123"]')
).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});

View File

@ -1,5 +1,5 @@
import React = require('react');
import {render, fireEvent} from '@testing-library/react';
import {render, fireEvent, waitFor} from '@testing-library/react';
import '../../../src/themes/default';
import {render as amisRender} from '../../../src/index';
import {makeEnv} from '../../helper';
@ -29,17 +29,21 @@ test('Renderer:textarea', async () => {
)
);
await waitFor(() => {
expect(container.querySelector('[name="a"]')).toBeInTheDocument();
});
const textarea = container.querySelector('textarea');
expect(textarea?.innerHTML).toEqual('123');
fireEvent.focus(textarea!);
fireEvent.change(textarea!, {
target: {
value: '456'
}
});
fireEvent.blur(textarea!);
const textareaChanged = container.querySelector('textarea');
expect(textareaChanged?.innerHTML).toEqual('456');
await waitFor(() => {
const textareaChanged = container.querySelector('textarea');
expect(textareaChanged?.innerHTML).toEqual('456');
});
expect(container).toMatchSnapshot();
});

View File

@ -1,5 +1,5 @@
import React = require('react');
import {render, cleanup, fireEvent} from '@testing-library/react';
import {render, cleanup, fireEvent, waitFor} from '@testing-library/react';
import '../../../src/themes/default';
import {render as amisRender} from '../../../src/index';
import {makeEnv, wait} from '../../helper';
@ -10,7 +10,7 @@ afterEach(() => {
clearStoresCache();
});
const setup = (inputOptions: any = {}, formOptions: any = {}) => {
const setup = async (inputOptions: any = {}, formOptions: any = {}) => {
const utils = render(
amisRender(
{
@ -32,6 +32,16 @@ const setup = (inputOptions: any = {}, formOptions: any = {}) => {
)
);
await waitFor(() => {
expect(
utils.container.querySelector('input[name="text"]')
).toBeInTheDocument();
expect(
utils.container.querySelector('button[type="submit"]')
).toBeInTheDocument();
});
const input = utils.container.querySelector(
'input[name="text"]'
) as HTMLInputElement;
@ -51,7 +61,7 @@ const setup = (inputOptions: any = {}, formOptions: any = {}) => {
* 使
*/
test('Renderer:text', async () => {
const {container, input} = setup();
const {container, input} = await setup();
expect(container).toMatchSnapshot();
// 输入是否正常
@ -65,7 +75,7 @@ test('Renderer:text', async () => {
* type url
*/
test('Renderer:text type is url', async () => {
const {container, input, submitBtn} = setup({
const {container, input, submitBtn} = await setup({
type: 'input-url'
});
@ -75,7 +85,7 @@ test('Renderer:text type is url', async () => {
expect(container).toMatchSnapshot('validate fail');
fireEvent.change(input, {target: {value: 'https://www.baidu.com'}});
await wait(200);
await wait(300);
expect(container).toMatchSnapshot('validate success');
});
@ -83,7 +93,7 @@ test('Renderer:text type is url', async () => {
* type email
*/
test('Renderer:text type is email', async () => {
const {container, input, submitBtn} = setup({
const {container, input, submitBtn} = await setup({
type: 'input-email'
});
@ -93,27 +103,28 @@ test('Renderer:text type is email', async () => {
expect(container).toMatchSnapshot('validate fail');
fireEvent.change(input, {target: {value: 'test@baidu.com'}});
await wait(200);
await wait(300);
expect(container).toMatchSnapshot('validate success');
});
/**
* type password
*/
test('Renderer:text type is password', () => {
const {container, input} = setup({
test('Renderer:text type is password', async () => {
const {container, input} = await setup({
type: 'input-password'
});
fireEvent.change(input, {target: {value: 'abcd'}});
await wait(300);
expect(container).toMatchSnapshot();
});
/**
* addOn
*/
test('Renderer:text with addOn', () => {
const {container, input} = setup({
test('Renderer:text with addOn', async () => {
const {container, input} = await setup({
addOn: {
type: 'button',
label: '搜索'
@ -127,7 +138,7 @@ test('Renderer:text with addOn', () => {
* clearable
*/
test('Renderer:text with clearable', async () => {
const {container, input} = setup({
const {container, input} = await setup({
clearable: true
});
fireEvent.change(input, {target: {value: 'abcd'}}); // 有值之后才会显示clear的icon
@ -145,7 +156,7 @@ test('Renderer:text with clearable', async () => {
*
*/
test('Renderer:text with options', async () => {
const {container, input} = setup(
const {container, input} = await setup(
{
options: [
{
@ -184,7 +195,7 @@ test('Renderer:text with options', async () => {
* ,
*/
test('Renderer:text with options and multiple', async () => {
const {container, input} = setup(
const {container, input} = await setup(
{
multiple: true,
options: [
@ -249,8 +260,8 @@ test('Renderer:text with options and multiple', async () => {
/**
*
*/
test('Renderer:text with prefix and suffix', () => {
const {container} = setup({
test('Renderer:text with prefix and suffix', async () => {
const {container} = await setup({
prefix: '¥',
suffix: 'RMB'
});
@ -261,27 +272,34 @@ test('Renderer:text with prefix and suffix', () => {
/**
*
*/
test('Renderer:text with counter', () => {
const {container, input} = setup({
test('Renderer:text with counter', async () => {
const {container, input} = await setup({
showCounter: true
});
expect(container).toMatchSnapshot();
fireEvent.change(input, {target: {value: 'abcd'}});
await wait(300);
expect(container).toMatchSnapshot();
});
/**
*
*/
test('Renderer:text with counter and maxLength', () => {
const {container, input} = setup({
test('Renderer:text with counter and maxLength', async () => {
const {container, input} = await setup({
showCounter: true,
maxLength: 10
});
expect(container).toMatchSnapshot();
fireEvent.change(input, {target: {value: 'abcd'}});
await waitFor(() => {
expect(
container.querySelector('[name="text"][value="abcd"]')
).toBeInTheDocument();
});
await wait(300);
expect(container).toMatchSnapshot();
});
@ -289,7 +307,7 @@ test('Renderer:text with counter and maxLength', () => {
*
*/
test('Renderer:text with transform lowerCase', async () => {
const {input} = setup({transform: {lowerCase: true}});
const {input} = await setup({transform: {lowerCase: true}});
fireEvent.change(input, {target: {value: 'AbCd'}});
await wait(300);
@ -300,7 +318,7 @@ test('Renderer:text with transform lowerCase', async () => {
*
*/
test('Renderer:text with transform upperCase', async () => {
const {input} = setup({transform: {upperCase: true}});
const {input} = await setup({transform: {upperCase: true}});
fireEvent.change(input, {target: {value: 'AbCd'}});
await wait(300);

View File

@ -1,11 +1,12 @@
import React = require('react');
import {render} from '@testing-library/react';
import {render, waitFor} from '@testing-library/react';
import '../../src/themes/default';
import {render as amisRender} from '../../src/index';
import {makeEnv} from '../helper';
test('Renderer:markdown', () => {
const {container} = render(
test('Renderer:markdown', async () => {
const {container, getByTestId} = render(
amisRender(
{
type: 'markdown',
@ -16,5 +17,8 @@ test('Renderer:markdown', () => {
)
);
await waitFor(() => {
expect(getByTestId('markdown-body')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});

View File

@ -1,7 +1,7 @@
import React = require('react');
import PageRenderer from '../../src/renderers/Page';
import * as renderer from 'react-test-renderer';
import {render, fireEvent, cleanup} from '@testing-library/react';
import {render, fireEvent, cleanup, waitFor} from '@testing-library/react';
import '../../src/themes/default';
import {render as amisRender} from '../../src/index';
import {wait, makeEnv} from '../helper';
@ -46,14 +46,8 @@ test('Renderer:Page initData', () => {
});
test('Renderer:Page initApi', async () => {
let done: Function;
let wating = new Promise(resolve => {
done = resolve;
});
const fetcher = jest.fn().mockImplementationOnce(() => {
setTimeout(done, 100);
return Promise.resolve({
const fetcher = jest.fn().mockImplementationOnce(() =>
Promise.resolve({
data: {
status: 0,
msg: 'ok',
@ -61,9 +55,9 @@ test('Renderer:Page initApi', async () => {
a: 2
}
}
});
});
const component = renderer.create(
})
);
const {container, getByText, rerender}: any = render(
amisRender(
{
type: 'page',
@ -77,28 +71,25 @@ test('Renderer:Page initApi', async () => {
)
);
await wating;
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
await waitFor(() => {
expect(getByText('The variable value is 2')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
test('Renderer:Page initApi error show Message', async () => {
let done: Function;
let wating = new Promise(resolve => {
done = resolve;
});
const fetcher = jest.fn().mockImplementationOnce(() => {
setTimeout(done, 100);
return Promise.resolve({
const fetcher = jest.fn().mockImplementationOnce(() =>
Promise.resolve({
data: {
status: 500,
msg: 'Internal Error'
}
});
});
const component = renderer.create(
})
);
const {container, getByText, rerender}: any = render(
amisRender(
{
type: 'page',
@ -111,38 +102,30 @@ test('Renderer:Page initApi error show Message', async () => {
)
);
await wating;
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
await waitFor(() => {
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
test('Renderer:Page initApi show loading', async () => {
let done: Function;
let wating = new Promise(resolve => {
done = resolve;
});
const fetcher = jest.fn().mockImplementationOnce(() => {
return new Promise(async resolve => {
await wait(100, () => expect(component.toJSON()).toMatchSnapshot());
await wait(100, () =>
expect(
resolve({
data: {
status: 0,
msg: 'ok',
data: {
a: 3
}
}
})
)
);
await wait(100, done);
await wait(200, false);
resolve({
data: {
status: 0,
msg: 'ok',
data: {
a: 3
}
}
});
});
});
const component = renderer.create(
const {container, getByText, getByTestId, unmount} = render(
amisRender(
{
type: 'page',
@ -156,10 +139,20 @@ test('Renderer:Page initApi show loading', async () => {
)
);
await wating;
let tree = component.toJSON();
await waitFor(() => {
expect(
container.querySelector('[data-testid="spinner"]')
).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
expect(tree).toMatchSnapshot();
await waitFor(() => {
expect(getByText('The variable value is 3')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
test('Renderer:Page initApi initFetch:false', async () => {
@ -174,7 +167,7 @@ test('Renderer:Page initApi initFetch:false', async () => {
}
})
);
renderer.create(
const {container, getByText, getByTestId} = render(
amisRender(
{
type: 'page',
@ -189,7 +182,9 @@ test('Renderer:Page initApi initFetch:false', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(getByText('The variable value is')).toBeInTheDocument();
});
expect(fetcher).not.toHaveBeenCalled();
});
@ -205,7 +200,7 @@ test('Renderer:Page initApi initFetch:true', async () => {
}
})
);
renderer.create(
const {container, getByText, getByTestId} = render(
amisRender(
{
type: 'page',
@ -220,7 +215,12 @@ test('Renderer:Page initApi initFetch:true', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(getByText('The variable value is 2')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(fetcher).toHaveBeenCalled();
});
@ -236,7 +236,7 @@ test('Renderer:Page initApi initFetchOn -> true', async () => {
}
})
);
renderer.create(
const {container, getByText, getByTestId} = render(
amisRender(
{
type: 'page',
@ -255,7 +255,12 @@ test('Renderer:Page initApi initFetchOn -> true', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(getByText('The variable value is 2')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(fetcher).toHaveBeenCalled();
});
@ -271,7 +276,7 @@ test('Renderer:Page initApi initFetchOn -> false', async () => {
}
})
);
renderer.create(
const {container, getByText, getByTestId} = render(
amisRender(
{
type: 'page',
@ -290,12 +295,14 @@ test('Renderer:Page initApi initFetchOn -> false', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(getByText('The variable value is')).toBeInTheDocument();
});
expect(fetcher).not.toHaveBeenCalled();
});
test('Renderer:Page classNames', () => {
const component = renderer.create(
test('Renderer:Page classNames', async () => {
const {container, getByText, getByTestId} = render(
amisRender({
type: 'page',
title: 'This is Title',
@ -311,9 +318,11 @@ test('Renderer:Page classNames', () => {
toolbarClassName: 'toolbar-class-name'
})
);
let tree = component.toJSON();
await waitFor(() => {
expect(getByText('This is body')).toBeInTheDocument();
});
expect(tree).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('Renderer:Page initApi interval 轮询调用', async () => {
@ -344,7 +353,7 @@ test('Renderer:Page initApi interval 轮询调用', async () => {
)
);
await wait(10);
await wait(10, false);
jest.advanceTimersByTime(3000);
const times = fetcher.mock.calls.length;
@ -398,52 +407,36 @@ test('Renderer:Page initApi interval 轮询调用自动停止', async () => {
});
test('Renderer:Page initApi silentPolling', async () => {
let done: Function;
let wating = new Promise(resolve => {
done = resolve;
});
jest.useFakeTimers();
const fetcher = jest
.fn()
.mockImplementationOnce(() => {
return new Promise(async resolve => {
await wait(100, () => expect(component.toJSON()).toMatchSnapshot());
await wait(100, () =>
expect(
resolve({
data: {
status: 0,
msg: 'ok',
data: {
a: 3
}
}
})
)
);
await wait(100, () => expect(component.toJSON()).toMatchSnapshot());
});
return new Promise(resolve =>
resolve({
data: {
status: 0,
msg: 'ok',
data: {
a: 3
}
}
})
);
})
.mockImplementationOnce(() => {
return new Promise(async resolve => {
await wait(100, () => expect(component.toJSON()).toMatchSnapshot());
await wait(100, () =>
expect(
resolve({
data: {
status: 0,
msg: 'ok',
data: {
a: 4
}
}
})
)
);
done();
resolve({
data: {
status: 0,
msg: 'ok',
data: {
a: 4
}
}
});
});
});
const component = renderer.create(
const {container, getByText, getByTestId} = render(
amisRender(
{
type: 'page',
@ -459,10 +452,19 @@ test('Renderer:Page initApi silentPolling', async () => {
)
);
await wating;
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
component.unmount();
await waitFor(() => {
expect(getByText('The variable value is 3')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
jest.advanceTimersByTime(3000);
await waitFor(() => {
expect(getByText('The variable value is 4')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
test('Renderer:Page initApi sendOn -> true', async () => {
@ -477,7 +479,7 @@ test('Renderer:Page initApi sendOn -> true', async () => {
}
})
);
renderer.create(
const {container, getByText, getByTestId} = render(
amisRender(
{
type: 'page',
@ -499,7 +501,12 @@ test('Renderer:Page initApi sendOn -> true', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(getByText('The variable value is 2')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(fetcher).toHaveBeenCalled();
});
@ -515,7 +522,7 @@ test('Renderer:Page initApi sendOn -> false', async () => {
}
})
);
renderer.create(
const {container, getByText, getByTestId} = render(
amisRender(
{
type: 'page',
@ -537,7 +544,9 @@ test('Renderer:Page initApi sendOn -> false', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(getByText('The variable value is')).toBeInTheDocument();
});
expect(fetcher).not.toHaveBeenCalled();
});
@ -666,13 +675,16 @@ test('Renderer:Page handleAction actionType=url|link', async () => {
)
);
await waitFor(() => {
expect(getByText('JumpTo')).toBeInTheDocument();
});
fireEvent.click(getByText(/JumpTo/));
await wait(300);
expect(jumpTo).toHaveBeenCalled();
expect(jumpTo.mock.calls[0][0]).toEqual('/goToPath?a=1');
});
test('Renderer:Page handleAction actionType=dialog', async () => {
test('Renderer:Page handleAction actionType=dialog default', async () => {
const {getByText, container}: any = render(
amisRender(
{
@ -695,12 +707,21 @@ test('Renderer:Page handleAction actionType=dialog', async () => {
)
);
fireEvent.click(getByText(/OpenDialog/));
await wait(300);
await waitFor(() => {
expect(getByText('OpenDialog')).toBeInTheDocument();
});
fireEvent.click(getByText('OpenDialog'));
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/取消/));
await wait(1000);
await waitFor(() => {
expect(getByText('取消')).toBeInTheDocument();
});
fireEvent.click(getByText('取消'));
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
@ -745,16 +766,24 @@ test('Renderer:Page handleAction actionType=dialog mergeData', async () => {
)
);
await waitFor(() => {
expect(getByText('OpenDialog')).toBeInTheDocument();
});
fireEvent.click(getByText(/OpenDialog/));
await wait(300);
await waitFor(() => {
expect(getByText('确认')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/确认/));
await wait(500);
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
test('Renderer:Page handleAction actionType=drawer', async () => {
test('Renderer:Page handleAction actionType=drawer default', async () => {
const {getByText, container}: any = render(
amisRender(
{
@ -777,12 +806,20 @@ test('Renderer:Page handleAction actionType=drawer', async () => {
)
);
await waitFor(() => {
expect(getByText('OpenDrawer')).toBeInTheDocument();
});
fireEvent.click(getByText(/OpenDrawer/));
await wait(300);
await waitFor(() => {
expect(getByText('取消')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/取消/));
await wait(1000);
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
@ -827,12 +864,21 @@ test('Renderer:Page handleAction actionType=drawer mergeData', async () => {
)
);
fireEvent.click(getByText(/OpenDrawer/));
await wait(600);
await waitFor(() => {
expect(getByText('OpenDrawer')).toBeInTheDocument();
});
fireEvent.click(getByText('OpenDrawer'));
await waitFor(() => {
expect(
container.querySelector('[name="a"][value="3"]')
).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/确认/));
await wait(600);
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
@ -870,8 +916,13 @@ test('Renderer:Page handleAction actionType=ajax', async () => {
)
);
await waitFor(() => {
expect(getByText('RequestAjax')).toBeInTheDocument();
});
fireEvent.click(getByText(/RequestAjax/));
await wait(300);
await waitFor(() => {
expect(getByText('The variable a is 6')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
@ -945,11 +996,15 @@ test('Renderer:Page handleAction actionType=ajax & feedback', async () => {
);
fireEvent.click(getByText(/RequestAjax/));
await wait(300);
await waitFor(() => {
expect(getByText('确认')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/确认/));
await wait(600);
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
@ -1053,12 +1108,19 @@ test('Renderer:Page initApi reload by action', async () => {
)
);
await wait(300);
await waitFor(() => {
expect(getByText('The variable value is 1')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/Reload/));
await wait(300);
await waitFor(() => {
expect(getByText('The variable value is 2')).toBeInTheDocument();
});
expect(fetcher).toHaveBeenCalledTimes(2);
expect(container).toMatchSnapshot();
});
@ -1138,18 +1200,29 @@ test('Renderer:Page initApi reload by Dialog action', async () => {
)
);
await wait(300);
await waitFor(() => {
expect(getByText('The variable value is 1')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/OpenDialog/));
await wait(400);
await waitFor(() => {
expect(getByText(/确认/)).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/确认/));
await wait(1000);
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
expect(getByText('The variable value is 2')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
await wait(1000);
expect(fetcher).toHaveBeenCalledTimes(2);
});
@ -1203,15 +1276,28 @@ test('Renderer:Page initApi reload by Drawer action', async () => {
)
);
await wait(300);
await waitFor(() => {
expect(getByText('The variable value is 1')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/OpenDialog/));
await wait(500);
await waitFor(() => {
expect(getByText(/确认/)).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/确认/));
await wait(1000);
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
expect(getByText('The variable value is 2')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
expect(fetcher).toHaveBeenCalledTimes(2);
@ -1266,11 +1352,21 @@ test('Renderer:Page initApi reload by Form submit', async () => {
)
);
await wait(300);
await waitFor(() => {
expect(getByText(/Submit/)).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/Submit/));
await wait(300);
await waitFor(() => {
expect(getByText('The variable value is 2')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
expect(fetcher).toHaveBeenCalledTimes(2);

View File

@ -1,6 +1,12 @@
import React = require('react');
import * as renderer from 'react-test-renderer';
import {render, fireEvent, cleanup} from '@testing-library/react';
import {
render,
fireEvent,
cleanup,
waitFor,
waitForElementToBeRemoved
} from '@testing-library/react';
import '../../src/themes/default';
import {render as amisRender} from '../../src/index';
import {wait, makeEnv} from '../helper';
@ -11,8 +17,8 @@ afterEach(() => {
clearStoresCache();
});
test('Renderer:Wizard', () => {
const component = renderer.create(
test('Renderer:Wizard default', () => {
const {container, getByTestId} = render(
amisRender(
{
type: 'wizard',
@ -53,16 +59,15 @@ test('Renderer:Wizard', () => {
]
},
{},
makeEnv()
makeEnv({})
)
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('Renderer:Wizard readOnly', () => {
const component = renderer.create(
const {container, getByTestId} = render(
amisRender(
{
type: 'wizard',
@ -107,12 +112,11 @@ test('Renderer:Wizard readOnly', () => {
makeEnv()
)
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('Renderer:Wizard initApi', async () => {
test('Renderer:Wizard initApi default', async () => {
const fetcher = jest.fn().mockImplementationOnce(() =>
Promise.resolve({
data: {
@ -128,7 +132,7 @@ test('Renderer:Wizard initApi', async () => {
})
);
const component = renderer.create(
const {container, getByText, getByTestId} = render(
amisRender(
{
type: 'wizard',
@ -176,35 +180,31 @@ test('Renderer:Wizard initApi', async () => {
)
);
await wait(500);
expect(component.toJSON()).toMatchSnapshot();
await waitFor(() => {
expect(getByText('名称')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
expect(fetcher).toHaveBeenCalled();
});
test('Renderer:Wizard initApi show loading', async () => {
let done: Function;
let wating = new Promise(resolve => {
done = resolve;
});
const fetcher = jest.fn().mockImplementationOnce(() => {
return new Promise(async resolve => {
await wait(100, () => expect(component.toJSON()).toMatchSnapshot());
await wait(100, () =>
resolve({
await wait(200, false); // 等一小会,否则会报没有被 act 包裹的错误
resolve({
data: {
status: 0,
msg: 'ok',
data: {
status: 0,
msg: 'ok',
data: {
a: 3
}
a: 3
}
})
);
await wait(100, done);
}
});
});
});
const component = renderer.create(
const {container, getByTestId} = render(
amisRender(
{
type: 'wizard',
@ -252,10 +252,13 @@ test('Renderer:Wizard initApi show loading', async () => {
)
);
await wating;
let tree = component.toJSON();
await waitFor(() => {
expect(getByTestId('spinner')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
expect(tree).toMatchSnapshot();
await waitForElementToBeRemoved(() => getByTestId('spinner'));
expect(container).toMatchSnapshot();
});
test('Renderer:Wizard initApi initFetch:false', async () => {
@ -274,7 +277,7 @@ test('Renderer:Wizard initApi initFetch:false', async () => {
})
);
renderer.create(
const {container, getByText, getByTestId} = render(
amisRender(
{
type: 'wizard',
@ -323,7 +326,9 @@ test('Renderer:Wizard initApi initFetch:false', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(getByText('名称')).toBeInTheDocument();
});
expect(fetcher).not.toHaveBeenCalled();
});
@ -343,7 +348,7 @@ test('Renderer:Wizard initApi initFetch:true', async () => {
})
);
renderer.create(
const {container, getByText, getByTestId} = render(
amisRender(
{
type: 'wizard',
@ -392,7 +397,9 @@ test('Renderer:Wizard initApi initFetch:true', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(getByText('名称')).toBeInTheDocument();
});
expect(fetcher).toHaveBeenCalled();
});
@ -412,7 +419,7 @@ test('Renderer:Wizard initApi initFetchOn:false', async () => {
})
);
renderer.create(
const {container, getByText, getByTestId} = render(
amisRender(
{
type: 'wizard',
@ -465,7 +472,9 @@ test('Renderer:Wizard initApi initFetchOn:false', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(getByText('名称')).toBeInTheDocument();
});
expect(fetcher).not.toHaveBeenCalled();
});
@ -485,7 +494,7 @@ test('Renderer:Wizard initApi initFetchOn:true', async () => {
})
);
renderer.create(
const {container, getByText, getByTestId} = render(
amisRender(
{
type: 'wizard',
@ -538,7 +547,9 @@ test('Renderer:Wizard initApi initFetchOn:true', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(getByText('名称')).toBeInTheDocument();
});
expect(fetcher).toHaveBeenCalled();
});
@ -592,16 +603,25 @@ test('Renderer:Wizard actionPrevLabel actionNextLabel actionFinishLabel classNam
)
);
await wait(1000);
await waitFor(() => {
expect(getByText('NextStep')).toBeInTheDocument();
});
fireEvent.click(getByText(/NextStep/));
await wait(1000);
await waitFor(() => {
expect(getByText('Submit')).toBeInTheDocument();
});
fireEvent.click(getByText(/Submit/));
await wait(1000);
await waitFor(() => {
expect(fetcher).toHaveBeenCalled();
});
expect(container).toMatchSnapshot();
expect(fetcher).toHaveBeenCalled();
fireEvent.click(getByText(/PrevStep/));
await wait(300);
await waitFor(() => {
expect(getByText('网址')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
@ -652,10 +672,13 @@ test('Renderer:Wizard actionNextSaveLabel', async () => {
)
);
await wait(1000);
await waitFor(() => {
expect(getByText('saveAndNext')).toBeInTheDocument();
});
fireEvent.click(getByText(/saveAndNext/));
await wait(1000);
expect(fetcher).toHaveBeenCalled();
await waitFor(() => {
expect(fetcher).toHaveBeenCalled();
});
});
test('Renderer:Wizard send data', async () => {
@ -705,19 +728,23 @@ test('Renderer:Wizard send data', async () => {
)
);
await wait(1000);
await waitFor(() => {
expect(getByText('下一步')).toBeInTheDocument();
});
fireEvent.click(getByText(/下一步/));
await wait(1000);
await waitFor(() => {
expect(getByText('完成')).toBeInTheDocument();
});
fireEvent.click(getByText(/完成/));
await wait(1000);
expect(fetcher).toHaveBeenCalled();
await waitFor(() => {
expect(fetcher).toHaveBeenCalled();
});
expect(fetcher.mock.calls[0][0]).toMatchObject({
data: {
name: 'Amis',
website: 'http://amis.baidu.com'
}
});
await wait(1000);
expect(container).toMatchSnapshot();
});
@ -779,10 +806,13 @@ test('Renderer:Wizard step api', async () => {
)
);
expect(fetcher).not.toHaveBeenCalled();
fireEvent.click(getByText(/下一步/));
await wait(1000);
expect(fetcher).toHaveBeenCalled();
await waitFor(() => {
expect(getByText('保存并下一步')).toBeInTheDocument();
});
fireEvent.click(getByText('保存并下一步'));
await waitFor(() => {
expect(fetcher).toHaveBeenCalled();
});
});
test('Renderer:Wizard step initApi', async () => {
@ -800,7 +830,7 @@ test('Renderer:Wizard step initApi', async () => {
})
);
const {getByText, container} = render(
const {getByText, getByTestId, container} = render(
amisRender(
{
type: 'wizard',
@ -848,9 +878,18 @@ test('Renderer:Wizard step initApi', async () => {
)
);
expect(fetcher).not.toHaveBeenCalled();
await waitFor(() => {
expect(getByText('下一步')).toBeInTheDocument();
});
fireEvent.click(getByText(/下一步/));
await wait(1000);
await waitFor(() => {
expect(
container.querySelector('[value="xxx@xxx.com"]')
).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(fetcher).toHaveBeenCalled();
expect(container).toMatchSnapshot();
});
@ -915,9 +954,17 @@ test('Renderer:Wizard step initFetch:false', async () => {
)
);
expect(fetcher).not.toHaveBeenCalled();
fireEvent.click(getByText(/下一步/));
await wait(1000);
await waitFor(() => {
expect(getByText('下一步')).toBeInTheDocument();
});
fireEvent.click(getByText('下一步'));
await waitFor(() => {
expect(getByText('邮箱')).toBeInTheDocument();
});
fireEvent.click(getByText('下一步'));
await waitFor(() => {
expect(getByText('这是必填项')).toBeInTheDocument();
});
expect(fetcher).not.toHaveBeenCalled();
});
@ -927,12 +974,14 @@ test('Renderer:Wizard step initFetch:true', async () => {
data: {
status: 0,
msg: '保存成功',
data: {}
data: {
email2: 'xxx@xxx.com'
}
}
})
);
const {getByText} = render(
const {container, getByText} = render(
amisRender(
{
type: 'wizard',
@ -981,9 +1030,15 @@ test('Renderer:Wizard step initFetch:true', async () => {
)
);
expect(fetcher).not.toHaveBeenCalled();
fireEvent.click(getByText(/下一步/));
await wait(1000);
await waitFor(() => {
expect(getByText('下一步')).toBeInTheDocument();
});
fireEvent.click(getByText('下一步'));
await waitFor(() => {
expect(
container.querySelector('[value="xxx@xxx.com"]')
).toBeInTheDocument();
});
expect(fetcher).toHaveBeenCalled();
});
@ -998,7 +1053,7 @@ test('Renderer:Wizard step initFetchOn:false', async () => {
})
);
const {getByText} = render(
const {container, getByText} = render(
amisRender(
{
type: 'wizard',
@ -1051,9 +1106,17 @@ test('Renderer:Wizard step initFetchOn:false', async () => {
)
);
expect(fetcher).not.toHaveBeenCalled();
fireEvent.click(getByText(/下一步/));
await wait(1000);
await waitFor(() => {
expect(getByText('下一步')).toBeInTheDocument();
});
fireEvent.click(getByText('下一步'));
await waitFor(() => {
expect(getByText('邮箱')).toBeInTheDocument();
});
fireEvent.click(getByText('下一步'));
await waitFor(() => {
expect(getByText('这是必填项')).toBeInTheDocument();
});
expect(fetcher).not.toHaveBeenCalled();
});
@ -1063,12 +1126,14 @@ test('Renderer:Wizard step initFetchOn:true', async () => {
data: {
status: 0,
msg: '保存成功',
data: {}
data: {
email2: 'xxx@xxx.com'
}
}
})
);
const {getByText} = render(
const {getByText, container} = render(
amisRender(
{
type: 'wizard',
@ -1121,9 +1186,16 @@ test('Renderer:Wizard step initFetchOn:true', async () => {
)
);
expect(fetcher).not.toHaveBeenCalled();
fireEvent.click(getByText(/下一步/));
await wait(1000);
await waitFor(() => {
expect(getByText('下一步')).toBeInTheDocument();
});
fireEvent.click(getByText('下一步'));
await waitFor(() => {
expect(
container.querySelector('[value="xxx@xxx.com"]')
).toBeInTheDocument();
});
expect(fetcher).toHaveBeenCalled();
});
@ -1174,8 +1246,14 @@ test('Renderer:Wizard validate', async () => {
)
);
fireEvent.click(getByText(/下一步/));
await wait(1000);
await waitFor(() => {
expect(getByText('下一步')).toBeInTheDocument();
});
fireEvent.click(getByText('下一步'));
await waitFor(() => {
expect(getByText('这是必填项')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
@ -1232,28 +1310,39 @@ test('Renderer:Wizard initApi reload', async () => {
)
);
await wait(500);
await waitFor(() => {
expect(getByText('下一步')).toBeInTheDocument();
expect(
container.querySelector('[data-testid="spinner"]')
).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/下一步/));
await wait(500);
await waitFor(() => {
expect(getByText('完成')).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/完成/));
await wait(1000);
expect(fetcher).toHaveBeenCalledTimes(3);
await waitFor(() => {
expect(fetcher).toHaveBeenCalledTimes(3);
});
expect(container).toMatchSnapshot();
});
test('Renderer:Wizard steps not array', () => {
const component = renderer.create(
test('Renderer:Wizard steps not array', async () => {
const {container, getByText} = render(
amisRender({
type: 'wizard',
api: '/api/mock2/form/saveForm?waitSeconds=2',
steps: ''
})
);
let tree = component.toJSON();
await waitFor(() => {
expect(getByText('配置错误')).toBeInTheDocument();
});
expect(tree).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('Renderer:Wizard target', async () => {
@ -1311,11 +1400,21 @@ test('Renderer:Wizard target', async () => {
)
);
await wait(1000);
fireEvent.click(getByText(/下一步/));
await wait(1000);
fireEvent.click(getByText(/完成/));
await wait(1000);
await waitFor(() => {
expect(getByText('下一步')).toBeInTheDocument();
});
fireEvent.click(getByText('下一步'));
await waitFor(() => {
expect(getByText('完成')).toBeInTheDocument();
});
fireEvent.click(getByText('完成'));
await waitFor(() => {
expect(
container.querySelector('[name="name"][value="Amis"]')
).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
@ -1378,11 +1477,21 @@ test('Renderer:Wizard dialog', async () => {
)
);
await waitFor(() => {
expect(getByText(/OpenDialog/)).toBeInTheDocument();
});
fireEvent.click(getByText(/OpenDialog/));
await wait(300);
await waitFor(() => {
expect(getByText(/添加/)).toBeInTheDocument();
});
expect(container).toMatchSnapshot();
fireEvent.click(getByText(/取消/));
await wait(1000);
await waitFor(() => {
expect(container.querySelector('[role="dialog"]')).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});

View File

@ -16,6 +16,7 @@ exports[`Renderer:bar-code 1`] = `
>
<div
class="cxd-BarCode"
data-testid="barcode"
>
<img
src="data:image/png;base64,00"

View File

@ -5,14 +5,23 @@ exports[`Renderer:markdown 1`] = `
<div
class="cxd-Markdown"
>
<div>
<div
class="cxd-Spinner in"
>
<div
class="cxd-Spinner-icon cxd-Spinner-icon--default"
/>
</div>
<div
class="markdown-body"
data-testid="markdown-body"
>
<h1>
title
</h1>
<p>
markdown
<strong>
text
</strong>
</p>
</div>
</div>
</div>

View File

@ -85,98 +85,96 @@ exports[`Renderer:Page 1`] = `
`;
exports[`Renderer:Page classNames 1`] = `
<div
className="cxd-Page cxd-Page--withSidebar"
onClick={[Function]}
>
<div>
<div
className="cxd-Page-aside cxd-Page-aside--withWidth aside-class-name"
class="cxd-Page cxd-Page--withSidebar"
>
<div
className="cxd-Page-asideInner"
class="cxd-Page-aside cxd-Page-aside--withWidth aside-class-name"
>
<div
className="cxd-TplField cxd-Page-asideTplWrapper"
>
<span>
This is aside
</span>
</div>
</div>
</div>
<div
className="cxd-Page-content"
>
<div
className="cxd-Page-main"
>
<div
className="cxd-Page-headerRow"
class="cxd-Page-asideInner"
style="position: sticky; top: 0px;"
>
<div
className="cxd-Page-header header-class-name"
>
<h2
className="cxd-Page-title"
>
<span
className="cxd-TplField"
>
<span>
This is Title
</span>
</span>
<div
className="cxd-Remark cxd-Remark--warning"
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
>
<span
className="cxd-Remark-icon icon"
>
<icon-mock
className=" icon-question-mark"
icon="question-mark"
/>
</span>
</div>
</h2>
<small
className="cxd-Page-subTitle"
>
<span
className="cxd-TplField"
>
<span>
This is SubTitle
</span>
</span>
</small>
</div>
<div
className="cxd-Page-toolbar toolbar-class-name"
>
<span
className="cxd-TplField"
>
<span>
This is toolbar
</span>
</span>
</div>
</div>
<div
className="cxd-Page-body body-class-name"
>
<span
className="cxd-TplField"
class="cxd-TplField cxd-Page-asideTplWrapper"
>
<span>
This is body
This is aside
</span>
</span>
</div>
</div>
</div>
<div
class="cxd-Page-content"
>
<div
class="cxd-Page-main"
>
<div
class="cxd-Page-headerRow"
>
<div
class="cxd-Page-header header-class-name"
>
<h2
class="cxd-Page-title"
>
<span
class="cxd-TplField"
>
<span>
This is Title
</span>
</span>
<div
class="cxd-Remark cxd-Remark--warning"
>
<span
class="cxd-Remark-icon icon"
>
<icon-mock
classname=" icon-question-mark"
icon="question-mark"
/>
</span>
</div>
</h2>
<small
class="cxd-Page-subTitle"
>
<span
class="cxd-TplField"
>
<span>
This is SubTitle
</span>
</span>
</small>
</div>
<div
class="cxd-Page-toolbar toolbar-class-name"
>
<span
class="cxd-TplField"
>
<span>
This is toolbar
</span>
</span>
</div>
</div>
<div
class="cxd-Page-body body-class-name"
>
<span
class="cxd-TplField"
>
<span>
This is body
</span>
</span>
</div>
</div>
</div>
</div>
@ -365,7 +363,7 @@ exports[`Renderer:Page handleAction actionType=ajax 1`] = `
</div>
`;
exports[`Renderer:Page handleAction actionType=dialog 1`] = `
exports[`Renderer:Page handleAction actionType=dialog default 1`] = `
<div>
<div
class="cxd-Page"
@ -394,72 +392,10 @@ exports[`Renderer:Page handleAction actionType=dialog 1`] = `
</div>
</div>
</div>
<div
class="amis-dialog-widget cxd-Modal cxd-Modal--1th"
role="dialog"
>
<div
class="cxd-Modal-overlay in"
/>
<div
class="cxd-Modal-content in"
>
<div
class="cxd-Modal-header"
>
<a
class="cxd-Modal-close"
data-position="left"
data-tooltip="关闭"
>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</a>
<div
class="cxd-Modal-title"
>
弹框
</div>
</div>
<div
class="cxd-Modal-body"
>
<span
class="cxd-TplField"
>
<span>
this is dialog
</span>
</span>
</div>
<div
class="cxd-Modal-footer"
>
<button
class="cxd-Button cxd-Button--default"
type="button"
>
<span>
取消
</span>
</button>
<button
class="cxd-Button cxd-Button--primary"
type="button"
>
<span>
确认
</span>
</button>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:Page handleAction actionType=dialog 2`] = `
exports[`Renderer:Page handleAction actionType=dialog default 2`] = `
<div>
<div
class="cxd-Page"
@ -662,104 +598,10 @@ exports[`Renderer:Page handleAction actionType=dialog mergeData 2`] = `
</div>
</div>
</div>
<div
class="amis-dialog-widget cxd-Modal cxd-Modal--1th"
role="dialog"
>
<div
class="cxd-Modal-overlay out"
/>
<div
class="cxd-Modal-content out"
>
<div
class="cxd-Modal-header"
>
<a
class="cxd-Modal-close"
data-position="left"
data-tooltip="关闭"
>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</a>
<div
class="cxd-Modal-title"
>
弹框
</div>
</div>
<div
class="cxd-Modal-body"
>
<form
class="cxd-Form cxd-Form--horizontal"
novalidate=""
>
<input
style="display: none;"
type="submit"
/>
<div
class="cxd-Form-item cxd-Form-item--horizontal"
data-role="form-item"
>
<label
class="cxd-Form-label cxd-Form-itemColumn--normal"
>
<span>
<span
class="cxd-TplField"
>
<span>
A
</span>
</span>
</span>
</label>
<div
class="cxd-Form-value"
>
<div
class="cxd-Form-control cxd-TextControl"
>
<div
class="cxd-TextControl-input"
>
<input
autocomplete="off"
name="a"
placeholder=""
size="10"
type="text"
value="3"
/>
</div>
</div>
</div>
</div>
</form>
</div>
<div
class="cxd-Modal-footer"
>
<button
class="cxd-Button cxd-Button--default"
type="submit"
>
<span>
确认
</span>
</button>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:Page handleAction actionType=drawer 1`] = `
exports[`Renderer:Page handleAction actionType=drawer default 1`] = `
<div>
<div
class="cxd-Page"
@ -817,6 +659,7 @@ exports[`Renderer:Page handleAction actionType=drawer 1`] = `
/>
<div
class="cxd-Spinner cxd-Spinner--overlay in"
data-testid="spinner"
>
<div
class="cxd-Spinner-icon cxd-Spinner-icon--lg cxd-Spinner-icon--default"
@ -848,7 +691,7 @@ exports[`Renderer:Page handleAction actionType=drawer 1`] = `
</div>
`;
exports[`Renderer:Page handleAction actionType=drawer 2`] = `
exports[`Renderer:Page handleAction actionType=drawer default 2`] = `
<div>
<div
class="cxd-Page"
@ -1047,26 +890,27 @@ exports[`Renderer:Page handleAction actionType=drawer mergeData 2`] = `
`;
exports[`Renderer:Page initApi 1`] = `
<div
className="cxd-Page"
onClick={[Function]}
>
<div>
<div
className="cxd-Page-content"
class="cxd-Page"
>
<div
className="cxd-Page-main"
class="cxd-Page-content"
>
<div
className="cxd-Page-body"
class="cxd-Page-main"
>
<span
className="cxd-TplField"
<div
class="cxd-Page-body"
>
<span>
The variable value is 2
<span
class="cxd-TplField"
>
<span>
The variable value is 2
</span>
</span>
</span>
</div>
</div>
</div>
</div>
@ -1074,41 +918,41 @@ exports[`Renderer:Page initApi 1`] = `
`;
exports[`Renderer:Page initApi error show Message 1`] = `
<div
className="cxd-Page"
onClick={[Function]}
>
<div>
<div
className="cxd-Page-content"
class="cxd-Page"
>
<div
className="cxd-Page-main"
class="cxd-Page-content"
>
<div
className="cxd-Page-body"
class="cxd-Page-main"
>
<div
className="cxd-Alert cxd-Alert--danger"
class="cxd-Page-body"
>
<div
className="cxd-Alert-content"
class="cxd-Alert cxd-Alert--danger"
>
<div
className="cxd-Alert-desc"
class="cxd-Alert-content"
>
Internal Error
<div
class="cxd-Alert-desc"
>
Internal Error
</div>
</div>
<button
class="cxd-Alert-close"
type="button"
>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</button>
</div>
<button
className="cxd-Alert-close"
onClick={[Function]}
type="button"
>
<icon-mock
className="icon icon-close"
icon="close"
/>
</button>
</div>
</div>
</div>
@ -1953,36 +1797,38 @@ exports[`Renderer:Page initApi reload by action 2`] = `
`;
exports[`Renderer:Page initApi show loading 1`] = `
<div
className="cxd-Page"
onClick={[Function]}
>
<div>
<div
className="cxd-Page-content"
class="cxd-Page"
>
<div
className="cxd-Page-main"
class="cxd-Page-content"
>
<div
className="cxd-Page-body"
class="cxd-Page-main"
>
<div
className="cxd-Spinner-overlay in"
/>
<div
className="cxd-Spinner cxd-Spinner--overlay in"
class="cxd-Page-body"
>
<div
className="cxd-Spinner-icon cxd-Spinner-icon--lg cxd-Spinner-icon--default"
class="cxd-Spinner-overlay"
/>
</div>
<span
className="cxd-TplField"
>
<span>
The variable value is
<div
class="cxd-Spinner cxd-Spinner--overlay"
data-testid="spinner"
>
<div
class="cxd-Spinner-icon cxd-Spinner-icon--lg cxd-Spinner-icon--default"
/>
</div>
<span
class="cxd-TplField"
>
<span>
The variable value is
</span>
</span>
</span>
</div>
</div>
</div>
</div>
@ -1990,26 +1836,27 @@ exports[`Renderer:Page initApi show loading 1`] = `
`;
exports[`Renderer:Page initApi show loading 2`] = `
<div
className="cxd-Page"
onClick={[Function]}
>
<div>
<div
className="cxd-Page-content"
class="cxd-Page"
>
<div
className="cxd-Page-main"
class="cxd-Page-content"
>
<div
className="cxd-Page-body"
class="cxd-Page-main"
>
<span
className="cxd-TplField"
<div
class="cxd-Page-body"
>
<span>
The variable value is 3
<span
class="cxd-TplField"
>
<span>
The variable value is 3
</span>
</span>
</span>
</div>
</div>
</div>
</div>
@ -2017,36 +1864,27 @@ exports[`Renderer:Page initApi show loading 2`] = `
`;
exports[`Renderer:Page initApi silentPolling 1`] = `
<div
className="cxd-Page"
onClick={[Function]}
>
<div>
<div
className="cxd-Page-content"
class="cxd-Page"
>
<div
className="cxd-Page-main"
class="cxd-Page-content"
>
<div
className="cxd-Page-body"
class="cxd-Page-main"
>
<div
className="cxd-Spinner-overlay in"
/>
<div
className="cxd-Spinner cxd-Spinner--overlay in"
class="cxd-Page-body"
>
<div
className="cxd-Spinner-icon cxd-Spinner-icon--lg cxd-Spinner-icon--default"
/>
</div>
<span
className="cxd-TplField"
>
<span>
The variable value is
<span
class="cxd-TplField"
>
<span>
The variable value is 3
</span>
</span>
</span>
</div>
</div>
</div>
</div>
@ -2054,80 +1892,27 @@ exports[`Renderer:Page initApi silentPolling 1`] = `
`;
exports[`Renderer:Page initApi silentPolling 2`] = `
<div
className="cxd-Page"
onClick={[Function]}
>
<div>
<div
className="cxd-Page-content"
class="cxd-Page"
>
<div
className="cxd-Page-main"
class="cxd-Page-content"
>
<div
className="cxd-Page-body"
class="cxd-Page-main"
>
<span
className="cxd-TplField"
<div
class="cxd-Page-body"
>
<span>
The variable value is 3
<span
class="cxd-TplField"
>
<span>
The variable value is 4
</span>
</span>
</span>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:Page initApi silentPolling 3`] = `
<div
className="cxd-Page"
onClick={[Function]}
>
<div
className="cxd-Page-content"
>
<div
className="cxd-Page-main"
>
<div
className="cxd-Page-body"
>
<span
className="cxd-TplField"
>
<span>
The variable value is 3
</span>
</span>
</div>
</div>
</div>
</div>
`;
exports[`Renderer:Page initApi silentPolling 4`] = `
<div
className="cxd-Page"
onClick={[Function]}
>
<div
className="cxd-Page-content"
>
<div
className="cxd-Page-main"
>
<div
className="cxd-Page-body"
>
<span
className="cxd-TplField"
>
<span>
The variable value is 4
</span>
</span>
</div>
</div>
</div>
</div>

View File

@ -25,14 +25,18 @@ exports[`Renderer:portlet 1`] = `
<li
class="cxd-Tabs-link is-active"
>
<a>
<a
title="Tab 1"
>
Tab 1
</a>
</li>
<li
class="cxd-Tabs-link"
>
<a>
<a
title="Tab 2"
>
Tab 2
</a>
</li>

View File

@ -22,14 +22,18 @@ exports[`Renderer:tabs 1`] = `
<li
class="cxd-Tabs-link is-active"
>
<a>
<a
title="基本配置"
>
基本配置
</a>
</li>
<li
class="cxd-Tabs-link"
>
<a>
<a
title="其他配置"
>
其他配置
</a>
</li>

File diff suppressed because it is too large Load Diff

View File

@ -97,6 +97,7 @@
},
"devDependencies": {
"@fortawesome/fontawesome-free": "^6.1.1",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.0.0",
"@types/async": "^2.0.45",
"@types/codemirror": "^5.60.3",

View File

@ -42,6 +42,12 @@ export default class Markdown extends React.Component<MarkdownProps> {
}
render() {
return <div className="markdown-body" ref={this.htmlRef}></div>;
return (
<div
data-testid="markdown-body"
className="markdown-body"
ref={this.htmlRef}
></div>
);
}
}

View File

@ -75,6 +75,7 @@ export class Spinner extends React.Component<SpinnerProps> {
{/* spinner图标和文案 */}
<div
data-testid="spinner"
className={cx(
`Spinner`,
tip && {

View File

@ -44,7 +44,7 @@ export class BarCodeField extends React.Component<BarCodeProps, object> {
return (
<Suspense fallback={<div>...</div>}>
<div className={cx('BarCode', className)}>
<div data-testid="barcode" className={cx('BarCode', className)}>
<BarCode value={value} options={options}></BarCode>
</div>
</Suspense>

View File

@ -22,6 +22,12 @@
"skipLibCheck": true
},
"include": ["src/**/*", "examples/**/*"],
"exclude": ["node_modules", "acceptance-tests", "webpack", "jest", "jest_cache"],
"types": ["node", "typePatches"]
"exclude": [
"node_modules",
"acceptance-tests",
"webpack",
"jest",
"jest_cache"
],
"types": ["node", "typePatches", "@testing-library/jest-dom"]
}