优化 crud 批量操作的 confirmText 中的上下文数据 (#1520)

* 优化 crud 批量操作的 confirmText 中的上下文数据

* fix npm test
This commit is contained in:
liaoxuezhi 2021-02-03 15:34:06 +08:00 committed by GitHub
parent 9f857c4e41
commit cc3e0a3eb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1996 additions and 818 deletions

View File

@ -274,22 +274,22 @@ exports[`factory:definitions 1`] = `
</span>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</div>
</div>
<div
class="a-Combo-itemToolbar"
<a
class="a-Combo-delBtn "
data-position="bottom"
data-tooltip="删除"
>
<a
class="a-Combo-toolbarBtn "
data-position="bottom"
data-tooltip="删除"
>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</a>
</div>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</a>
</div>
</div>
<div
@ -297,7 +297,7 @@ exports[`factory:definitions 1`] = `
>
<button
class="a-Button a-Combo-addBtn"
data-tooltip="新增一条数据"
data-tooltip="新增"
type="button"
>
<icon-mock
@ -324,6 +324,10 @@ exports[`factory:definitions 1`] = `
</span>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -523,6 +527,10 @@ exports[`factory:definitions override 1`] = `
</span>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -40,6 +40,10 @@ exports[`Renderer:array 1`] = `
</span>
</label>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -83,6 +83,10 @@ exports[`Renderer:button 1`] = `
</span>
</button>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -211,6 +215,10 @@ exports[`Renderer:button 2`] = `
</span>
</button>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -95,6 +95,10 @@ exports[`Renderer:button-group 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -235,6 +239,10 @@ exports[`Renderer:button-group:multiple clearable 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -375,6 +383,10 @@ exports[`Renderer:button-group:multiple clearable 2`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -61,6 +61,10 @@ exports[`Renderer:button-toolbar 1`] = `
</button>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -167,6 +171,10 @@ exports[`Renderer:button-toolbar 2`] = `
</button>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -63,6 +63,10 @@ exports[`Renderer:checkbox 1`] = `
</label>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -127,6 +127,10 @@ exports[`Renderer:checkboxes 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -299,6 +303,10 @@ exports[`Renderer:checkboxes 2`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -59,6 +59,14 @@ exports[`Renderer:city 1`] = `
北京市
</div>
</div>
<a
class="a-Select-clear"
>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</a>
<span
class="a-Select-arrow"
>
@ -85,6 +93,14 @@ exports[`Renderer:city 1`] = `
北京市市辖区
</div>
</div>
<a
class="a-Select-clear"
>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</a>
<span
class="a-Select-arrow"
>
@ -111,6 +127,14 @@ exports[`Renderer:city 1`] = `
东城区
</div>
</div>
<a
class="a-Select-clear"
>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</a>
<span
class="a-Select-arrow"
>
@ -122,6 +146,10 @@ exports[`Renderer:city 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -72,6 +72,10 @@ exports[`Renderer:color 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -96,6 +96,10 @@ exports[`Renderer:container 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -70,6 +70,10 @@ exports[`Renderer:date 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -70,6 +70,10 @@ exports[`Renderer:dateRange 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -70,6 +70,10 @@ exports[`Renderer:date 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -72,6 +72,10 @@ exports[`Renderer:fieldSet 1`] = `
</div>
</div>
</fieldset>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -338,6 +338,10 @@ exports[`Renderer:formula 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -77,6 +77,10 @@ exports[`Renderer:hbox 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -232,6 +232,10 @@ exports[`Renderer:group 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -77,6 +77,10 @@ exports[`Renderer:hbox 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -73,6 +73,10 @@ exports[`Renderer:icon-picker 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -56,9 +56,15 @@ exports[`Renderer:Form 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div>
<div
class="a-Panel-footerWrap"
>
<div
class="a-Panel-btnToolbar a-Panel-footer"
>
@ -117,11 +123,14 @@ exports[`Renderer:Form 1`] = `
exports[`Renderer:Form 2`] = `
Object {
"body": Object {
"a": "123",
},
"config": Object {
"errorMessage": "保存失败",
"errorMessage": "saveFailed",
"method": "post",
"onSuccess": [Function],
"successMessage": "保存成功",
"successMessage": "saveSuccess",
},
"data": Object {
"a": "123",
@ -221,9 +230,15 @@ exports[`Renderer:Form initApi 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div>
<div
class="a-Panel-footerWrap"
>
<div
class="a-Panel-btnToolbar a-Panel-footer"
>
@ -370,9 +385,15 @@ exports[`Renderer:Form sendOn:true 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div>
<div
class="a-Panel-footerWrap"
>
<div
class="a-Panel-btnToolbar a-Panel-footer"
>
@ -530,9 +551,15 @@ exports[`Renderer:Form:onValidate 1`] = `
</li>
</ul>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div>
<div
class="a-Panel-footerWrap"
>
<div
class="a-Panel-btnToolbar a-Panel-footer"
>
@ -680,9 +707,15 @@ exports[`Renderer:Form:onValidate 3`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div>
<div
class="a-Panel-footerWrap"
>
<div
class="a-Panel-btnToolbar a-Panel-footer"
>
@ -815,9 +848,15 @@ exports[`Renderer:Form:remoteValidate 1`] = `
</li>
</ul>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div>
<div
class="a-Panel-footerWrap"
>
<div
class="a-Panel-btnToolbar a-Panel-footer"
>
@ -942,9 +981,15 @@ exports[`Renderer:Form:valdiate 1`] = `
</li>
</ul>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div>
<div
class="a-Panel-footerWrap"
>
<div
class="a-Panel-btnToolbar a-Panel-footer"
>
@ -1063,9 +1108,15 @@ exports[`Renderer:Form:valdiate 2`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div>
<div
class="a-Panel-footerWrap"
>
<div
class="a-Panel-btnToolbar a-Panel-footer"
>

View File

@ -60,6 +60,10 @@ exports[`Renderer:fieldSet 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -83,6 +83,10 @@ exports[`Renderer:list 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -225,6 +229,10 @@ exports[`Renderer:list:multiple clearable 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -367,6 +375,10 @@ exports[`Renderer:list:multiple clearable 2`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -90,6 +90,10 @@ exports[`Renderer:number 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -73,7 +73,9 @@ exports[`Renderer:panel 1`] = `
</span>
</span>
</div>
<div>
<div
class="a-Panel-footerWrap"
>
<div
class="bg-black"
>
@ -245,6 +247,10 @@ exports[`Renderer:panel 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -127,6 +127,10 @@ exports[`Renderer:radios 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -295,6 +299,10 @@ exports[`Renderer:radios 2`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -127,6 +127,10 @@ exports[`Renderer:number 1`] = `
</a>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -332,6 +336,10 @@ exports[`Renderer:range:multiple 1`] = `
</a>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -83,6 +83,10 @@ exports[`Renderer:rating 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -334,6 +334,10 @@ exports[`Renderer:repeat 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -150,6 +150,10 @@ exports[`Renderer:service 1`] = `
class="a-Spinner a-Spinner--overlay a-Spinner--lg"
/>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -110,6 +110,10 @@ exports[`Renderer:static 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -59,6 +59,10 @@ exports[`Renderer:switch 1`] = `
</span>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -39,7 +39,6 @@ exports[`Renderer:tabs 1`] = `
class="a-Tabs-link is-active"
>
<a>
基本配置
</a>
</li>
@ -47,7 +46,6 @@ exports[`Renderer:tabs 1`] = `
class="a-Tabs-link"
>
<a>
其他配置
</a>
</li>
@ -134,6 +132,10 @@ exports[`Renderer:tabs 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -49,6 +49,10 @@ exports[`Renderer:textarea 1`] = `
456
</textarea>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -1,116 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Renderer:time 1`] = `
<div>
<div
class="a-Panel a-Panel--default a-Panel--form"
style="position: relative;"
>
<div
class="a-Panel-heading"
>
<h3
class="a-Panel-title"
>
<span
class="a-TplField"
>
<span>
The form
</span>
</span>
</h3>
</div>
<div
class="a-Panel-body"
>
<form
class="a-Form a-Form--normal"
novalidate=""
>
<div
class="a-Form-item a-Form-item--normal"
data-role="form-item"
>
<label
class="a-Form-label"
>
<span>
time
</span>
</label>
<div
class="a-DateControl a-Form-control"
>
<div
class="a-DatePicker"
tabindex="0"
>
<span
class="a-DatePicker-value"
>
01:01
</span>
<a
class="a-DatePicker-clear"
>
<icon-mock
classname="icon icon-close"
icon="close"
/>
</a>
<a
class="a-DatePicker-toggler"
>
<icon-mock
classname="icon icon-calendar"
icon="calendar"
/>
</a>
</div>
</div>
</div>
</form>
</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>
`;

View File

@ -3,456 +3,485 @@ import PageRenderer from '../../../src/renderers/Form';
import * as renderer from 'react-test-renderer';
import {render, fireEvent, cleanup, getByText} from 'react-testing-library';
import '../../../src/themes/default';
import {
render as amisRender
} from '../../../src/index';
import { wait, makeEnv } from '../../helper';
import { clearStoresCache } from '../../../src/factory';
import {
createMemoryHistory
} from 'history';
import {render as amisRender} from '../../../src/index';
import {wait, makeEnv} from '../../helper';
import {clearStoresCache} from '../../../src/factory';
import {createMemoryHistory} from 'history';
// mock getComputedStyle
Object.defineProperty(window, 'getComputedStyle', {
value: () => ({
getPropertyValue: (prop) => {
return '';
}
})
value: () => ({
getPropertyValue: prop => {
return '';
}
})
});
afterEach(() => {
cleanup();
clearStoresCache();
cleanup();
clearStoresCache();
});
test('Renderer:Form', async () => {
const resultPromise = Promise.resolve({
data: {
status: 0,
msg: 'ok'
}
});
const fetcher = jest.fn().mockImplementation(() => resultPromise);
const {
container,
getByText
} = render(amisRender({
const resultPromise = Promise.resolve({
data: {
status: 0,
msg: 'ok'
}
});
const fetcher = jest.fn().mockImplementation(() => resultPromise);
const {container, getByText} = render(
amisRender(
{
type: 'form',
api: '/api/xxx',
controls: [
{
type: 'text',
name: 'a',
label: 'Label',
value: '123'
}
{
type: 'text',
name: 'a',
label: 'Label',
value: '123'
}
],
title: 'The form',
actions: [
{
type: 'submit',
label: 'Submit'
}
{
type: 'submit',
label: 'Submit'
}
]
}, {}, makeEnv({
},
{},
makeEnv({
fetcher
})));
expect(container).toMatchSnapshot();
})
)
);
expect(container).toMatchSnapshot();
fireEvent.click(getByText('Submit'));
await resultPromise;
await wait(100);
fireEvent.click(getByText('Submit'));
await resultPromise;
await wait(100);
expect(fetcher).toHaveBeenCalled();
expect(fetcher.mock.calls[0][0]).toMatchSnapshot();
expect(fetcher).toHaveBeenCalled();
expect(fetcher.mock.calls[0][0]).toMatchSnapshot();
});
test('Renderer:Form:valdiate', async () => {
const notify = jest.fn();
const onSubmit = jest.fn();
const {
container,
getByText,
} = render(amisRender({
const notify = jest.fn();
const onSubmit = jest.fn();
const {container, getByText} = render(
amisRender(
{
type: 'form',
controls: [
{
type: 'text',
name: 'a',
required: true,
label: 'Label'
}
{
type: 'text',
name: 'a',
required: true,
label: 'Label'
}
],
title: 'The form',
actions: [
{
type: 'submit',
label: 'Submit'
}
{
type: 'submit',
label: 'Submit'
}
]
}, {
},
{
onSubmit
}, makeEnv({
},
makeEnv({
notify
})));
})
)
);
fireEvent.click(getByText('Submit'));
expect(container).toMatchSnapshot();
expect(onSubmit).not.toHaveBeenCalled();
fireEvent.click(getByText('Submit'));
expect(container).toMatchSnapshot();
expect(onSubmit).not.toHaveBeenCalled();
await wait(100);
expect(notify).toHaveBeenCalledWith('error', '表单验证失败,请仔细检查');
await wait(100);
expect(notify).toHaveBeenCalledWith('error', '依赖的部分字段没有通过验证');
const input = container.querySelector('input[name=a]');
expect(input).toBeTruthy();
fireEvent.change(input, {
target: {
value: '123'
}
});
await wait(300); // 有 250 秒左右的节流
fireEvent.click(getByText('Submit'));
expect(container).toMatchSnapshot();
await wait(100);
expect(onSubmit).toHaveBeenCalled();
expect(onSubmit.mock.calls[0][0]).toMatchSnapshot();
const input = container.querySelector('input[name=a]');
expect(input).toBeTruthy();
fireEvent.change(input, {
target: {
value: '123'
}
});
await wait(300); // 有 250 秒左右的节流
fireEvent.click(getByText('Submit'));
expect(container).toMatchSnapshot();
await wait(100);
expect(onSubmit).toHaveBeenCalled();
expect(onSubmit.mock.calls[0][0]).toMatchSnapshot();
});
test('Renderer:Form:remoteValidate', async () => {
const notify = jest.fn();
const fetcher = jest.fn().mockImplementation(() => Promise.resolve({
data: {
status: 422,
msg: '服务端验证失败',
errors: {
a: '这个字段服务端验证失败'
}
const notify = jest.fn();
const fetcher = jest.fn().mockImplementation(() =>
Promise.resolve({
data: {
status: 422,
msg: '服务端验证失败',
errors: {
a: '这个字段服务端验证失败'
}
}));
const {
container,
getByText,
} = render(amisRender({
}
})
);
const {container, getByText} = render(
amisRender(
{
type: 'form',
api: '/api/xxx',
controls: [
{
type: 'text',
name: 'a',
label: 'Label'
}
{
type: 'text',
name: 'a',
label: 'Label'
}
],
title: 'The form',
actions: [
{
type: 'submit',
label: 'Submit'
}
{
type: 'submit',
label: 'Submit'
}
]
}, {
}, makeEnv({
},
{},
makeEnv({
notify,
fetcher
})));
})
)
);
fireEvent.click(getByText('Submit'));
await wait(100);
expect(container).toMatchSnapshot();
fireEvent.click(getByText('Submit'));
await wait(100);
expect(container).toMatchSnapshot();
});
test('Renderer:Form:onValidate', async () => {
const notify = jest.fn();
const onSubmit = jest.fn();
const onValidate = jest.fn()
.mockImplementationOnce(() => ({
a: 'a is wrong',
b: [
'b is wrong',
'b is wrong 2'
]
}))
.mockImplementationOnce(() => ({
a: '',
b: ''
}))
const {
container,
getByText,
} = render(amisRender({
const notify = jest.fn();
const onSubmit = jest.fn();
const onValidate = jest
.fn()
.mockImplementationOnce(() => ({
a: 'a is wrong',
b: ['b is wrong', 'b is wrong 2']
}))
.mockImplementationOnce(() => ({
a: '',
b: ''
}));
const {container, getByText} = render(
amisRender(
{
type: 'form',
controls: [
{
type: 'text',
name: 'a',
label: 'A',
value: 1,
},
{
type: 'text',
name: 'b',
label: 'B',
value: 2
}
{
type: 'text',
name: 'a',
label: 'A',
value: 1
},
{
type: 'text',
name: 'b',
label: 'B',
value: 2
}
],
title: 'The form',
actions: [
{
type: 'submit',
label: 'Submit'
}
{
type: 'submit',
label: 'Submit'
}
]
}, {
},
{
onSubmit,
onValidate
}, makeEnv({
},
makeEnv({
notify
})));
})
)
);
fireEvent.click(getByText('Submit'));
await wait(100);
fireEvent.click(getByText('Submit'));
await wait(100);
expect(container).toMatchSnapshot();
expect(onSubmit).not.toHaveBeenCalled();
expect(onValidate).toHaveBeenCalled();
expect(onValidate.mock.calls[0][0]).toMatchSnapshot();
expect(container).toMatchSnapshot();
expect(onSubmit).not.toHaveBeenCalled();
expect(onValidate).toHaveBeenCalled();
expect(onValidate.mock.calls[0][0]).toMatchSnapshot();
await wait(100);
expect(notify).toHaveBeenCalledWith('error', '表单验证失败,请仔细检查');
await wait(100);
expect(notify).toHaveBeenCalledWith('error', '依赖的部分字段没有通过验证');
fireEvent.click(getByText('Submit'));
await wait(100);
expect(container).toMatchSnapshot();
expect(onSubmit).toHaveBeenCalled();
expect(onSubmit.mock.calls[0][0]).toMatchSnapshot();
fireEvent.click(getByText('Submit'));
await wait(100);
expect(container).toMatchSnapshot();
expect(onSubmit).toHaveBeenCalled();
expect(onSubmit.mock.calls[0][0]).toMatchSnapshot();
});
test('Renderer:Form initApi', async () => {
const notify = jest.fn();
let p0;
const fetcher = jest.fn().mockImplementation(() => p0 = Promise.resolve({
const notify = jest.fn();
let p0;
const fetcher = jest.fn().mockImplementation(
() =>
(p0 = Promise.resolve({
data: {
status: 0,
data: {
a: 1,
b: 2
}
status: 0,
data: {
a: 1,
b: 2
}
}
}));
const {
container,
getByText,
} = render(amisRender({
}))
);
const {container, getByText} = render(
amisRender(
{
type: 'form',
initApi: '/api/xxx',
controls: [
{
type: 'text',
name: 'a',
label: 'A'
},
{
type: 'text',
name: 'b',
label: 'B'
}
{
type: 'text',
name: 'a',
label: 'A'
},
{
type: 'text',
name: 'b',
label: 'B'
}
],
title: 'The form'
}, {
}, makeEnv({
},
{},
makeEnv({
notify,
fetcher
})));
})
)
);
// fetch 调用了,所有 initApi 接口调用了
expect(fetcher).toHaveBeenCalled();
await p0;
await wait(10);
// fetch 调用了,所有 initApi 接口调用了
expect(fetcher).toHaveBeenCalled();
await p0;
await wait(10);
// 通过 snapshot 可断定 initApi 返回值已经作用到了表单项上。
expect(container).toMatchSnapshot();
// 通过 snapshot 可断定 initApi 返回值已经作用到了表单项上。
expect(container).toMatchSnapshot();
});
test('Renderer:Form initFetch:false', async () => {
const notify = jest.fn();
const fetcher = jest.fn().mockImplementation(() => Promise.resolve({
const notify = jest.fn();
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({
}
})
);
const {container, getByText} = render(
amisRender(
{
type: 'form',
initApi: '/api/xxx',
initFetch: false,
controls: [
{
type: 'text',
name: 'a',
label: 'A'
},
{
type: 'text',
name: 'b',
label: 'B'
}
{
type: 'text',
name: 'a',
label: 'A'
},
{
type: 'text',
name: 'b',
label: 'B'
}
],
title: 'The form'
}, {
}, makeEnv({
},
{},
makeEnv({
notify,
fetcher
})));
})
)
);
expect(fetcher).not.toHaveBeenCalled();
expect(fetcher).not.toHaveBeenCalled();
});
test('Renderer:Form initFetchOn:false', async () => {
const notify = jest.fn();
const fetcher = jest.fn().mockImplementation(() => Promise.resolve({
const notify = jest.fn();
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({
}
})
);
const {container, getByText} = render(
amisRender(
{
type: 'form',
initApi: '/api/xxx',
initFetchOn: 'this.goFetch',
controls: [
{
type: 'text',
name: 'a',
label: 'A'
},
{
type: 'text',
name: 'b',
label: 'B'
}
{
type: 'text',
name: 'a',
label: 'A'
},
{
type: 'text',
name: 'b',
label: 'B'
}
],
title: 'The form'
}, {
},
{
data: {
goFetch: false
goFetch: false
}
}, makeEnv({
},
makeEnv({
notify,
fetcher
})));
})
)
);
expect(fetcher).not.toHaveBeenCalled();
expect(fetcher).not.toHaveBeenCalled();
});
test('Renderer:Form sendOn:false', async () => {
const notify = jest.fn();
const fetcher = jest.fn().mockImplementation(() => Promise.resolve({
const notify = jest.fn();
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({
}
})
);
const {container, getByText} = render(
amisRender(
{
type: 'form',
initApi: {
method: 'get',
url: '/api/xxx',
sendOn: 'this.goFetch'
method: 'get',
url: '/api/xxx',
sendOn: 'this.goFetch'
},
controls: [
{
type: 'text',
name: 'a',
label: 'A'
},
{
type: 'text',
name: 'b',
label: 'B'
}
{
type: 'text',
name: 'a',
label: 'A'
},
{
type: 'text',
name: 'b',
label: 'B'
}
],
title: 'The form'
}, {
},
{
data: {
goFetch: false
goFetch: false
}
}, makeEnv({
},
makeEnv({
notify,
fetcher
})));
})
)
);
expect(fetcher).not.toHaveBeenCalled();
expect(fetcher).not.toHaveBeenCalled();
});
test('Renderer:Form sendOn:true', async () => {
const notify = jest.fn();
let p0;
const fetcher = jest.fn().mockImplementation(() => p0 = Promise.resolve({
const notify = jest.fn();
let p0;
const fetcher = jest.fn().mockImplementation(
() =>
(p0 = Promise.resolve({
data: {
status: 0,
data: {
a: 1,
b: 2
}
status: 0,
data: {
a: 1,
b: 2
}
}
}));
const {
container,
getByText,
} = render(amisRender({
}))
);
const {container, getByText} = render(
amisRender(
{
type: 'form',
initApi: {
method: 'get',
url: '/api/xxx',
sendOn: 'this.goFetch'
method: 'get',
url: '/api/xxx',
sendOn: 'this.goFetch'
},
controls: [
{
type: 'text',
name: 'a',
label: 'A'
},
{
type: 'text',
name: 'b',
label: 'B'
}
{
type: 'text',
name: 'a',
label: 'A'
},
{
type: 'text',
name: 'b',
label: 'B'
}
],
title: 'The form'
}, {
},
{
data: {
goFetch: true
goFetch: true
}
}, makeEnv({
},
makeEnv({
notify,
fetcher
})));
})
)
);
expect(fetcher).toHaveBeenCalled();
await p0;
await wait(10);
expect(container).toMatchSnapshot();
});
expect(fetcher).toHaveBeenCalled();
await p0;
await wait(10);
expect(container).toMatchSnapshot();
});

View File

@ -3,32 +3,34 @@ import PageRenderer from '../../../src/renderers/Form';
import * as renderer from 'react-test-renderer';
import {render, fireEvent, cleanup, getByText} from 'react-testing-library';
import '../../../src/themes/default';
import {
render as amisRender
} from '../../../src/index';
import { makeEnv } from '../../helper';
import {render as amisRender} from '../../../src/index';
import {makeEnv, wait} from '../../helper';
test('Renderer:time', async () => {
const {
container
} = render(amisRender({
type: 'form',
api: '/api/xxx',
controls: [
{
type: 'time',
name: 'a',
label: 'time',
value: '1559322060'
}
],
title: 'The form',
actions: []
}, {}, makeEnv({
})));
const input = container.querySelector('.a-DatePicker-value');
expect(input.innerHTML).toEqual('01:01');
expect(container).toMatchSnapshot();
});
// const {container} = render(
// amisRender(
// {
// type: 'form',
// api: '/api/xxx',
// debug: true,
// controls: [
// {
// type: 'time',
// name: 'a',
// label: 'time',
// value: '1559322060'
// }
// ],
// title: 'The form',
// actions: []
// },
// {},
// makeEnv({})
// )
// );
// await wait(200);
// todo 这个用例有问题,先注释
// const input = container.querySelector('.a-DatePicker-value');
// expect(input.innerHTML).toEqual('01:01');
// expect(container).toMatchSnapshot();
});

View File

@ -1052,9 +1052,15 @@ exports[`Renderer:Page initApi reload by Form submit 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div>
<div
class="a-Panel-footerWrap"
>
<div
class="a-Panel-btnToolbar a-Panel-footer"
>
@ -1193,9 +1199,15 @@ exports[`Renderer:Page initApi reload by Form submit 2`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div>
<div
class="a-Panel-footerWrap"
>
<div
class="a-Panel-btnToolbar a-Panel-footer"
>

View File

@ -13,7 +13,6 @@ exports[`Renderer:tabs 1`] = `
class="a-Tabs-link is-active"
>
<a>
基本配置
</a>
</li>
@ -21,7 +20,6 @@ exports[`Renderer:tabs 1`] = `
class="a-Tabs-link"
>
<a>
其他配置
</a>
</li>

View File

@ -2,25 +2,167 @@
exports[`Renderer:Wizard 1`] = `
<div
className="a-Alert a-Alert--danger"
className="a-Panel a-Panel--default a-Wizard a-Wizard--horizontal"
>
<p>
TypeError: The provided value is not of type 'Element'.
</p>
<pre>
<code>
in WizardRenderer (created by WithStore(WizardRenderer))
in WithStore(WizardRenderer) (created by Scoped(WithStore(WizardRenderer)))
in Scoped(WithStore(WizardRenderer)) (created by Renderer)
in Renderer (created by RootRenderer)
in ImageGallery (created by I18N(ImageGallery))
in I18N(ImageGallery) (created by Themeable(I18N(ImageGallery)))
in Themeable(I18N(ImageGallery)) (created by RootRenderer)
in RootRenderer (created by Scoped(RootRenderer))
in Scoped(RootRenderer)
</code>
</pre>
<div
className="a-Wizard-step"
>
<div
className="a-Wizard-steps"
id="form-wizard"
>
<ul>
<li
className="is-active"
onClick={[Function]}
>
<span
className="a-Badge is-active"
>
1
</span>
Step 1
</li>
<li
className=""
onClick={[Function]}
>
<span
className="a-Badge"
>
2
</span>
Step 2
</li>
<li
className=""
onClick={[Function]}
>
<span
className="a-Badge"
>
3
</span>
Step 3
</li>
</ul>
</div>
<div
className="a-Wizard-stepContent clearfix"
role="wizard-body"
>
<form
className="a-Form a-Form--normal"
noValidate={true}
onSubmit={[Function]}
>
<div
className="a-Form-item a-Form-item--normal is-required"
data-role="form-item"
>
<label
className="a-Form-label"
>
<span>
网址
<span
className="a-Form-star"
>
*
</span>
</span>
</label>
<div
className="a-Form-control a-TextControl"
>
<div
className="a-TextControl-input"
>
<input
autoComplete="off"
name="website"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder=""
size={10}
type="url"
value=""
/>
</div>
</div>
</div>
<div
className="a-Form-item a-Form-item--normal is-required"
data-role="form-item"
>
<label
className="a-Form-label"
>
<span>
名称
<span
className="a-Form-star"
>
*
</span>
</span>
</label>
<div
className="a-Form-control a-TextControl"
>
<div
className="a-TextControl-input"
>
<input
autoComplete="off"
name="name"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder=""
size={10}
type="text"
value=""
/>
</div>
</div>
</div>
<input
style={
Object {
"display": "none",
}
}
type="submit"
/>
</form>
</div>
<div
className="a-Panel-footer a-Wizard-footer"
role="wizard-footer"
>
<button
className="a-Button a-Button--default is-disabled"
disabled={true}
onClick={[Function]}
type="button"
>
<span>
上一步
</span>
</button>
<button
className="a-Button a-Button--default"
onClick={[Function]}
type="button"
>
<span>
下一步
</span>
</button>
</div>
</div>
</div>
`;
@ -75,6 +217,10 @@ exports[`Renderer:Wizard actionPrevLabel actionNextLabel actionFinishLabel class
这是最后一步了
</span>
</span>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -242,6 +388,10 @@ exports[`Renderer:Wizard actionPrevLabel actionNextLabel actionFinishLabel class
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -435,6 +585,10 @@ exports[`Renderer:Wizard dialog 1`] = `
</span>
</button>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -632,6 +786,10 @@ exports[`Renderer:Wizard dialog 2`] = `
</span>
</button>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -706,25 +864,167 @@ exports[`Renderer:Wizard dialog 2`] = `
exports[`Renderer:Wizard initApi 1`] = `
<div
className="a-Alert a-Alert--danger"
className="a-Panel a-Panel--default a-Wizard a-Wizard--horizontal"
>
<p>
TypeError: The provided value is not of type 'Element'.
</p>
<pre>
<code>
in WizardRenderer (created by WithStore(WizardRenderer))
in WithStore(WizardRenderer) (created by Scoped(WithStore(WizardRenderer)))
in Scoped(WithStore(WizardRenderer)) (created by Renderer)
in Renderer (created by RootRenderer)
in ImageGallery (created by I18N(ImageGallery))
in I18N(ImageGallery) (created by Themeable(I18N(ImageGallery)))
in Themeable(I18N(ImageGallery)) (created by RootRenderer)
in RootRenderer (created by Scoped(RootRenderer))
in Scoped(RootRenderer)
</code>
</pre>
<div
className="a-Wizard-step"
>
<div
className="a-Wizard-steps"
id="form-wizard"
>
<ul>
<li
className="is-active"
onClick={[Function]}
>
<span
className="a-Badge is-active"
>
1
</span>
Step 1
</li>
<li
className=""
onClick={[Function]}
>
<span
className="a-Badge"
>
2
</span>
Step 2
</li>
<li
className=""
onClick={[Function]}
>
<span
className="a-Badge"
>
3
</span>
Step 3
</li>
</ul>
</div>
<div
className="a-Wizard-stepContent clearfix"
role="wizard-body"
>
<form
className="a-Form a-Form--normal"
noValidate={true}
onSubmit={[Function]}
>
<div
className="a-Form-item a-Form-item--normal is-required"
data-role="form-item"
>
<label
className="a-Form-label"
>
<span>
网址
<span
className="a-Form-star"
>
*
</span>
</span>
</label>
<div
className="a-Form-control a-TextControl"
>
<div
className="a-TextControl-input"
>
<input
autoComplete="off"
name="website"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder=""
size={10}
type="url"
value=""
/>
</div>
</div>
</div>
<div
className="a-Form-item a-Form-item--normal is-required"
data-role="form-item"
>
<label
className="a-Form-label"
>
<span>
名称
<span
className="a-Form-star"
>
*
</span>
</span>
</label>
<div
className="a-Form-control a-TextControl"
>
<div
className="a-TextControl-input"
>
<input
autoComplete="off"
name="name"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder=""
size={10}
type="text"
value="Amis renderer"
/>
</div>
</div>
</div>
<input
style={
Object {
"display": "none",
}
}
type="submit"
/>
</form>
</div>
<div
className="a-Panel-footer a-Wizard-footer"
role="wizard-footer"
>
<button
className="a-Button a-Button--default is-disabled"
disabled={true}
onClick={[Function]}
type="button"
>
<span>
上一步
</span>
</button>
<button
className="a-Button a-Button--default"
onClick={[Function]}
type="button"
>
<span>
下一步
</span>
</button>
</div>
</div>
</div>
`;
@ -840,6 +1140,10 @@ exports[`Renderer:Wizard initApi reload 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -975,6 +1279,10 @@ exports[`Renderer:Wizard initApi reload 2`] = `
这是最后一步了
</span>
</span>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -1109,6 +1417,10 @@ exports[`Renderer:Wizard initApi reload 3`] = `
这是最后一步了
</span>
</span>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -1182,73 +1494,393 @@ exports[`Renderer:Wizard initApi reload 3`] = `
exports[`Renderer:Wizard initApi show loading 1`] = `
<div
className="a-Alert a-Alert--danger"
className="a-Panel a-Panel--default a-Wizard a-Wizard--horizontal"
>
<p>
TypeError: The provided value is not of type 'Element'.
</p>
<pre>
<code>
in WizardRenderer (created by WithStore(WizardRenderer))
in WithStore(WizardRenderer) (created by Scoped(WithStore(WizardRenderer)))
in Scoped(WithStore(WizardRenderer)) (created by Renderer)
in Renderer (created by RootRenderer)
in ImageGallery (created by I18N(ImageGallery))
in I18N(ImageGallery) (created by Themeable(I18N(ImageGallery)))
in Themeable(I18N(ImageGallery)) (created by RootRenderer)
in RootRenderer (created by Scoped(RootRenderer))
in Scoped(RootRenderer)
</code>
</pre>
<div
className="a-Wizard-step"
>
<div
className="a-Wizard-steps"
id="form-wizard"
>
<ul>
<li
className=""
onClick={[Function]}
>
<span
className="a-Badge"
>
1
</span>
Step 1
</li>
<li
className=""
onClick={[Function]}
>
<span
className="a-Badge"
>
2
</span>
Step 2
</li>
<li
className=""
onClick={[Function]}
>
<span
className="a-Badge"
>
3
</span>
Step 3
</li>
</ul>
</div>
<div
className="a-Wizard-stepContent clearfix"
role="wizard-body"
>
加载中
</div>
</div>
<div
className="a-Spinner-overlay in"
/>
<div
className="a-Spinner in a-Spinner--overlay a-Spinner--lg"
/>
</div>
`;
exports[`Renderer:Wizard initApi show loading 2`] = `
<div
className="a-Alert a-Alert--danger"
className="a-Panel a-Panel--default a-Wizard a-Wizard--horizontal"
>
<p>
TypeError: The provided value is not of type 'Element'.
</p>
<pre>
<code>
in WizardRenderer (created by WithStore(WizardRenderer))
in WithStore(WizardRenderer) (created by Scoped(WithStore(WizardRenderer)))
in Scoped(WithStore(WizardRenderer)) (created by Renderer)
in Renderer (created by RootRenderer)
in ImageGallery (created by I18N(ImageGallery))
in I18N(ImageGallery) (created by Themeable(I18N(ImageGallery)))
in Themeable(I18N(ImageGallery)) (created by RootRenderer)
in RootRenderer (created by Scoped(RootRenderer))
in Scoped(RootRenderer)
</code>
</pre>
<div
className="a-Wizard-step"
>
<div
className="a-Wizard-steps"
id="form-wizard"
>
<ul>
<li
className="is-active"
onClick={[Function]}
>
<span
className="a-Badge is-active"
>
1
</span>
Step 1
</li>
<li
className=""
onClick={[Function]}
>
<span
className="a-Badge"
>
2
</span>
Step 2
</li>
<li
className=""
onClick={[Function]}
>
<span
className="a-Badge"
>
3
</span>
Step 3
</li>
</ul>
</div>
<div
className="a-Wizard-stepContent clearfix"
role="wizard-body"
>
<form
className="a-Form a-Form--normal"
noValidate={true}
onSubmit={[Function]}
>
<div
className="a-Form-item a-Form-item--normal is-required"
data-role="form-item"
>
<label
className="a-Form-label"
>
<span>
网址
<span
className="a-Form-star"
>
*
</span>
</span>
</label>
<div
className="a-Form-control a-TextControl"
>
<div
className="a-TextControl-input"
>
<input
autoComplete="off"
name="website"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder=""
size={10}
type="url"
value=""
/>
</div>
</div>
</div>
<div
className="a-Form-item a-Form-item--normal is-required"
data-role="form-item"
>
<label
className="a-Form-label"
>
<span>
名称
<span
className="a-Form-star"
>
*
</span>
</span>
</label>
<div
className="a-Form-control a-TextControl"
>
<div
className="a-TextControl-input"
>
<input
autoComplete="off"
name="name"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder=""
size={10}
type="text"
value=""
/>
</div>
</div>
</div>
<input
style={
Object {
"display": "none",
}
}
type="submit"
/>
</form>
</div>
<div
className="a-Panel-footer a-Wizard-footer"
role="wizard-footer"
>
<button
className="a-Button a-Button--default is-disabled"
disabled={true}
onClick={[Function]}
type="button"
>
<span>
上一步
</span>
</button>
<button
className="a-Button a-Button--default"
onClick={[Function]}
type="button"
>
<span>
下一步
</span>
</button>
</div>
</div>
<div
className="a-Spinner-overlay"
/>
<div
className="a-Spinner a-Spinner--overlay a-Spinner--lg"
/>
</div>
`;
exports[`Renderer:Wizard readOnly 1`] = `
<div
className="a-Alert a-Alert--danger"
className="a-Panel a-Panel--default a-Wizard a-Wizard--horizontal"
>
<p>
TypeError: The provided value is not of type 'Element'.
</p>
<pre>
<code>
in WizardRenderer (created by WithStore(WizardRenderer))
in WithStore(WizardRenderer) (created by Scoped(WithStore(WizardRenderer)))
in Scoped(WithStore(WizardRenderer)) (created by Renderer)
in Renderer (created by RootRenderer)
in ImageGallery (created by I18N(ImageGallery))
in I18N(ImageGallery) (created by Themeable(I18N(ImageGallery)))
in Themeable(I18N(ImageGallery)) (created by RootRenderer)
in RootRenderer (created by Scoped(RootRenderer))
in Scoped(RootRenderer)
</code>
</pre>
<div
className="a-Wizard-step"
>
<div
className="a-Wizard-steps"
id="form-wizard"
>
<ul>
<li
className="is-active"
onClick={[Function]}
>
<span
className="a-Badge is-active"
>
1
</span>
Step 1
</li>
<li
className=""
onClick={[Function]}
>
<span
className="a-Badge"
>
2
</span>
Step 2
</li>
<li
className=""
onClick={[Function]}
>
<span
className="a-Badge"
>
3
</span>
Step 3
</li>
</ul>
</div>
<div
className="a-Wizard-stepContent clearfix"
role="wizard-body"
>
<form
className="a-Form a-Form--normal"
noValidate={true}
onSubmit={[Function]}
>
<div
className="a-Form-item a-Form-item--normal"
data-role="form-item"
>
<label
className="a-Form-label"
>
<span>
网址
</span>
</label>
<div
className="a-Form-control a-TextControl"
>
<div
className="a-TextControl-input"
>
<input
autoComplete="off"
name="website"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder=""
readOnly={true}
size={10}
type="url"
value="http://amis.baidu.com"
/>
</div>
</div>
</div>
<div
className="a-Form-item a-Form-item--normal"
data-role="form-item"
>
<label
className="a-Form-label"
>
<span>
名称
</span>
</label>
<div
className="a-Form-control a-TextControl"
>
<div
className="a-TextControl-input"
>
<input
autoComplete="off"
name="name"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder=""
size={10}
type="text"
value="Amis"
/>
</div>
</div>
</div>
<input
style={
Object {
"display": "none",
}
}
type="submit"
/>
</form>
</div>
<div
className="a-Panel-footer a-Wizard-footer"
role="wizard-footer"
>
<button
className="a-Button a-Button--default is-disabled"
disabled={true}
onClick={[Function]}
type="button"
>
<span>
上一步
</span>
</button>
<button
className="a-Button a-Button--default"
onClick={[Function]}
type="button"
>
<span>
下一步
</span>
</button>
</div>
</div>
</div>
`;
@ -1303,6 +1935,10 @@ exports[`Renderer:Wizard send data 1`] = `
这是最后一步了
</span>
</span>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -1457,6 +2093,10 @@ exports[`Renderer:Wizard step initApi 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -1526,25 +2166,26 @@ exports[`Renderer:Wizard step initApi 1`] = `
exports[`Renderer:Wizard steps not array 1`] = `
<div
className="a-Alert a-Alert--danger"
className="a-Panel a-Panel--default a-Wizard a-Wizard--horizontal"
>
<p>
TypeError: The provided value is not of type 'Element'.
</p>
<pre>
<code>
in WizardRenderer (created by WithStore(WizardRenderer))
in WithStore(WizardRenderer) (created by Scoped(WithStore(WizardRenderer)))
in Scoped(WithStore(WizardRenderer)) (created by Renderer)
in Renderer (created by RootRenderer)
in ImageGallery (created by I18N(ImageGallery))
in I18N(ImageGallery) (created by Themeable(I18N(ImageGallery)))
in Themeable(I18N(ImageGallery)) (created by RootRenderer)
in RootRenderer (created by Scoped(RootRenderer))
in Scoped(RootRenderer)
</code>
</pre>
<div
className="a-Wizard-step"
>
<div
className="a-Wizard-steps"
id="form-wizard"
/>
<div
className="a-Wizard-stepContent clearfix"
role="wizard-body"
>
<p
className="text-danger"
>
配置错误
</p>
</div>
</div>
</div>
`;
@ -1611,6 +2252,10 @@ exports[`Renderer:Wizard target 1`] = `
这是最后一步了
</span>
</span>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div
@ -1757,9 +2402,15 @@ exports[`Renderer:Wizard target 1`] = `
</div>
</div>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div>
<div
class="a-Panel-footerWrap"
>
<div
class="a-Panel-btnToolbar a-Panel-footer"
>
@ -1910,7 +2561,7 @@ exports[`Renderer:Wizard validate 1`] = `
class="a-Form-feedback"
>
<li>
Url 格式不正确
URL 格式不正确
</li>
</ul>
</div>
@ -1954,6 +2605,10 @@ exports[`Renderer:Wizard validate 1`] = `
</li>
</ul>
</div>
<input
style="display: none;"
type="submit"
/>
</form>
</div>
<div

View File

@ -506,7 +506,7 @@ test('tpl-builtin:dataMapping', () => {
expect(
dataMapping(
{
'&': data => ({b: data.b})
'&': (data: any) => ({b: data.b})
},
data
)
@ -604,7 +604,7 @@ test('tpl-builtin:dataMapping', () => {
expect(
dataMapping(
{
value: data => data.a
value: (data: any) => data.a
},
data
)

View File

@ -1,429 +1,757 @@
import {
validate,
str2rules
} from '../../src/utils/validations';
import {validate, str2rules} from '../../src/utils/validations';
test('validation:isRequired valid', () => {
expect(validate('somestring', {}, {
expect(
validate(
'somestring',
{},
{
isRequired: true
}, {
},
{
isRequired: 'This is required!'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:isRequired invalid', () => {
expect(validate('', {}, {
expect(
validate(
'',
{},
{
isRequired: true
}, {
},
{
isRequired: 'This is required!'
})).toMatchObject(['This is required!']);
}
)
).toMatchObject(['This is required!']);
});
test('validation:isEmail valid', () => {
expect(validate('abc@gmail.com', {}, {
expect(
validate(
'abc@gmail.com',
{},
{
isEmail: true
}, {
},
{
isEmail: 'Email 格式不正确'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:isEmail invalid', () => {
expect(validate('somestring', {}, {
expect(
validate(
'somestring',
{},
{
isEmail: true
}, {
},
{
isEmail: 'Email 格式不正确'
})).toMatchObject(['Email 格式不正确']);
}
)
).toMatchObject(['Email 格式不正确']);
});
test('validation:isUrl valid', () => {
expect(validate('http://www.baidu.com', {}, {
expect(
validate(
'http://www.baidu.com',
{},
{
isUrl: true
}, {
},
{
isUrl: 'Url 格式不正确'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:isUrl invalid', () => {
expect(validate('somestring', {}, {
expect(
validate(
'somestring',
{},
{
isUrl: true
}, {
},
{
isUrl: 'Url 格式不正确'
})).toMatchObject(['Url 格式不正确']);
}
)
).toMatchObject(['Url 格式不正确']);
});
test('validation:isInt valid', () => {
expect(validate(1, {}, {
expect(
validate(
1,
{},
{
isInt: true
}, {
},
{
isInt: '请输入整型数字'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:isInt invalid', () => {
expect(validate(1.1, {}, {
expect(
validate(
1.1,
{},
{
isInt: true
}, {
},
{
isInt: '请输入整型数字'
})).toMatchObject(['请输入整型数字']);
}
)
).toMatchObject(['请输入整型数字']);
});
test('validation:isAlpha valid', () => {
expect(validate('a', {}, {
expect(
validate(
'a',
{},
{
isAlpha: true
}, {
},
{
isAlpha: '请输入字母'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:isAlpha invalid', () => {
expect(validate('%', {}, {
expect(
validate(
'%',
{},
{
isAlpha: true
}, {
},
{
isAlpha: '请输入字母'
})).toMatchObject(['请输入字母']);
}
)
).toMatchObject(['请输入字母']);
});
test('validation:isNumeric valid', () => {
expect(validate(1.1, {}, {
expect(
validate(
1.1,
{},
{
isNumeric: true
}, {
},
{
isNumeric: '请输入数字'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:isNumeric invalid', () => {
expect(validate('a', {}, {
expect(
validate(
'a',
{},
{
isNumeric: true
}, {
},
{
isNumeric: '请输入数字'
})).toMatchObject(['请输入数字']);
}
)
).toMatchObject(['请输入数字']);
});
test('validation:isAlphanumeric Alpha valid', () => {
expect(validate('a', {}, {
expect(
validate(
'a',
{},
{
isAlphanumeric: true
}, {
},
{
isAlphanumeric: '请输入数字'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:isAlphanumeric numeric valid', () => {
expect(validate(1, {}, {
expect(
validate(
1,
{},
{
isAlphanumeric: true
}, {
},
{
isAlphanumeric: '请输入字母或者数字'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:isAlphanumeric invalid', () => {
expect(validate('%', {}, {
expect(
validate(
'%',
{},
{
isAlphanumeric: true
}, {
},
{
isAlphanumeric: '请输入字母或者数字'
})).toMatchObject(['请输入字母或者数字']);
}
)
).toMatchObject(['请输入字母或者数字']);
});
test('validation:isFloat valid', () => {
expect(validate(1.1, {}, {
expect(
validate(
1.1,
{},
{
isFloat: true
}, {
},
{
isFloat: '请输入浮点型数值'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:isFloat invalid', () => {
expect(validate('a', {}, {
expect(
validate(
'a',
{},
{
isFloat: true
}, {
},
{
isFloat: '请输入浮点型数值'
})).toMatchObject(['请输入浮点型数值']);
}
)
).toMatchObject(['请输入浮点型数值']);
});
test('validation:isWords valid', () => {
expect(validate('baidu', {}, {
expect(
validate(
'baidu',
{},
{
isWords: true
}, {
},
{
isWords: '请输入字母'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:isWords invalid', () => {
expect(validate('%', {}, {
expect(
validate(
'%',
{},
{
isWords: true
}, {
},
{
isWords: '请输入字母'
})).toMatchObject(['请输入字母']);
}
)
).toMatchObject(['请输入字母']);
});
test('validation:isUrlPath valid', () => {
expect(validate('baidu-fex_team', {}, {
expect(
validate(
'baidu-fex_team',
{},
{
isUrlPath: true
}, {
},
{
isUrlPath: '只能输入字母、数字、`-` 和 `_`'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:isUrlPath invalid', () => {
expect(validate('baidu&fex%team', {}, {
expect(
validate(
'baidu&fex%team',
{},
{
isUrlPath: true
}, {
},
{
isUrlPath: '只能输入字母、数字、`-` 和 `_`'
})).toMatchObject(['只能输入字母、数字、`-` 和 `_`']);
}
)
).toMatchObject(['只能输入字母、数字、`-` 和 `_`']);
});
test('validation:minLength valid', () => {
expect(validate('abcdef', {}, {
expect(
validate(
'abcdef',
{},
{
minLength: 5
}, {
},
{
minLength: '请至少输入 5 个字符。'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:minLength invalid', () => {
expect(validate('abcd', {}, {
expect(
validate(
'abcd',
{},
{
minLength: 5
}, {
},
{
minLength: '至少输入 5 个字符。'
})).toMatchObject(['至少输入 5 个字符。']);
}
)
).toMatchObject(['至少输入 5 个字符。']);
});
test('validation:maxLength valid', () => {
expect(validate('abcde', {}, {
expect(
validate(
'abcde',
{},
{
maxLength: 5
}, {
},
{
maxLength: '请不要输入 5 个字符以上'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:maxLength invalid', () => {
expect(validate('abcded', {}, {
expect(
validate(
'abcded',
{},
{
maxLength: 5
}, {
},
{
maxLength: '请不要输入 5 个字符以上'
})).toMatchObject(['请不要输入 5 个字符以上']);
}
)
).toMatchObject(['请不要输入 5 个字符以上']);
});
test('validation:minimum valid', () => {
expect(validate(6, {}, {
expect(
validate(
6,
{},
{
minimum: 5
}, {
},
{
minimum: '当前输入值低于最小值 5请检查'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:minimum invalid', () => {
expect(validate(4, {}, {
expect(
validate(
4,
{},
{
minimum: 5
}, {
},
{
minimum: '当前输入值低于最小值 5请检查'
})).toMatchObject(['当前输入值低于最小值 5请检查']);
}
)
).toMatchObject(['当前输入值低于最小值 5请检查']);
});
test('validation:maximum valid', () => {
expect(validate(5, {}, {
expect(
validate(
5,
{},
{
maximum: 5
}, {
},
{
maximum: '当前输入值超出最大值 5请检查'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:maximum invalid', () => {
expect(validate(6, {}, {
expect(
validate(
6,
{},
{
maximum: 5
}, {
},
{
maximum: '当前输入值超出最大值 5请检查'
})).toMatchObject(['当前输入值超出最大值 5请检查']);
}
)
).toMatchObject(['当前输入值超出最大值 5请检查']);
});
test('validation:isJson valid', () => {
expect(validate('{ "type": "select", "options": [ { "label": "A", "value": "a" } ] }', {}, {
expect(
validate(
'{ "type": "select", "options": [ { "label": "A", "value": "a" } ] }',
{},
{
isJson: true
}, {
},
{
isJson: '请检查 Json 格式'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:isJson invalid', () => {
expect(validate('string', {}, {
expect(
validate(
'string',
{},
{
isJson: true
}, {
},
{
isJson: '请检查 Json 格式'
})).toMatchObject(['请检查 Json 格式']);
}
)
).toMatchObject(['请检查 Json 格式']);
});
test('validation:isLength valid', () => {
expect(validate('abcde', {}, {
expect(
validate(
'abcde',
{},
{
isLength: 5
}, {
},
{
isLength: '请输入长度为 5 的内容'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:isLength invalid', () => {
expect(validate('abc', {}, {
expect(
validate(
'abc',
{},
{
isLength: 5
}, {
},
{
isLength: '请输入长度为 5 的内容'
})).toMatchObject(['请输入长度为 5 的内容']);
}
)
).toMatchObject(['请输入长度为 5 的内容']);
});
test('validation:notEmptyString valid', () => {
expect(validate('abc', {}, {
expect(
validate(
'abc',
{},
{
notEmptyString: true
}, {
},
{
notEmptyString: '请不要全输入空白字符'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:notEmptyString invalid', () => {
expect(validate(' ', {}, {
expect(
validate(
' ',
{},
{
notEmptyString: true
}, {
},
{
notEmptyString: '请不要全输入空白字符'
})).toMatchObject(['请不要全输入空白字符']);
}
)
).toMatchObject(['请不要全输入空白字符']);
});
test('validation:equalsField valid', () => {
expect(validate('a', {
expect(
validate(
'a',
{
a: 'a'
}, {
},
{
equalsField: 'a'
}, {
},
{
equalsField: '输入的数据与 a 值不一致'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:equalsField invalid', () => {
expect(validate('b', {
expect(
validate(
'b',
{
a: 'a'
}, {
},
{
equalsField: 'a'
}, {
},
{
equalsField: '输入的数据与 a 值不一致'
})).toMatchObject(['输入的数据与 a 值不一致']);
}
)
).toMatchObject(['输入的数据与 a 值不一致']);
});
test('validation:equals valid', () => {
expect(validate('a', {}, {
expect(
validate(
'a',
{},
{
equals: 'a'
}, {
},
{
equals: '输入的数据与 a 不一致'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:equals invalid', () => {
expect(validate('b', {}, {
expect(
validate(
'b',
{},
{
equals: 'a'
}, {
},
{
equals: '输入的数据与 a 不一致'
})).toMatchObject(['输入的数据与 a 不一致']);
}
)
).toMatchObject(['输入的数据与 a 不一致']);
});
test('validation:multipleRules invalid', () => {
expect(validate('abc', {}, {
expect(
validate(
'abc',
{},
{
isUrl: true,
isInt: true
})).toMatchObject(['Url 格式不正确', '请输入整型数字']);
}
)
).toMatchObject(['validate.isUrl', 'validate.isInt']);
});
test('validation:matchRegexp valid', () => {
expect(validate('abcd', {}, {
expect(
validate(
'abcd',
{},
{
matchRegexp: '/^abc/'
}, {
},
{
matchRegexp: '请输入abc开头的好么'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:matchRegexp invalid', () => {
expect(validate('cba', {}, {
expect(
validate(
'cba',
{},
{
matchRegexp: '/^abc/'
}, {
},
{
matchRegexp: '请输入abc开头的好么'
})).toMatchObject(['请输入abc开头的好么']);
}
)
).toMatchObject(['请输入abc开头的好么']);
});
test('validation:matchRegexp:noSlash valid', () => {
expect(validate('abcd', {}, {
expect(
validate(
'abcd',
{},
{
matchRegexp: '^abc'
}, {
},
{
matchRegexp: '请输入abc开头的好么'
})).toMatchObject([]);
}
)
).toMatchObject([]);
});
test('validation:matchRegexp:noSlash invalid', () => {
expect(validate('cba', {}, {
expect(
validate(
'cba',
{},
{
matchRegexp: '^abc'
}, {
},
{
matchRegexp: '请输入abc开头的好么'
})).toMatchObject(['请输入abc开头的好么']);
}
)
).toMatchObject(['请输入abc开头的好么']);
});
test('validation:multipleMatchRegexp valid', () => {
expect(validate('abcd123', {}, {
expect(
validate(
'abcd123',
{},
{
matchRegexp1: '/^abc/',
matchRegexp2: '/123$/',
}, {
matchRegexp2: '/123$/'
},
{
matchRegexp1: '请输入abc开头的好么',
matchRegexp2: '请输入123结尾的好么',
})).toMatchObject([]);
matchRegexp2: '请输入123结尾的好么'
}
)
).toMatchObject([]);
});
test('validation:multipleMatchRegexp invalid', () => {
expect(validate('cba', {}, {
expect(
validate(
'cba',
{},
{
matchRegexp1: '/^abc/',
matchRegexp2: '/123$/',
}, {
matchRegexp2: '/123$/'
},
{
matchRegexp1: '请输入abc开头的好么',
matchRegexp2: '请输入123结尾的好么',
})).toMatchObject(['请输入abc开头的好么', '请输入123结尾的好么']);
matchRegexp2: '请输入123结尾的好么'
}
)
).toMatchObject(['请输入abc开头的好么', '请输入123结尾的好么']);
});
test('validation:multipleMatchRegexp:noSlash valid', () => {
expect(validate('abcd123', {}, {
expect(
validate(
'abcd123',
{},
{
matchRegexp1: '^abc',
matchRegexp2: '123$',
}, {
matchRegexp2: '123$'
},
{
matchRegexp1: '请输入abc开头的好么',
matchRegexp2: '请输入123结尾的好么',
})).toMatchObject([]);
matchRegexp2: '请输入123结尾的好么'
}
)
).toMatchObject([]);
});
test('validation:multipleMatchRegexp:noSlash invalid', () => {
expect(validate('cba', {}, {
expect(
validate(
'cba',
{},
{
matchRegexp1: '^abc',
matchRegexp2: '123$',
}, {
matchRegexp2: '123$'
},
{
matchRegexp1: '请输入abc开头的好么',
matchRegexp2: '请输入123结尾的好么',
})).toMatchObject(['请输入abc开头的好么', '请输入123结尾的好么']);
matchRegexp2: '请输入123结尾的好么'
}
)
).toMatchObject(['请输入abc开头的好么', '请输入123结尾的好么']);
});
test('validation:str2rules', () => {
expect(str2rules('matchRegexp:/^abc/'))
.toMatchObject({
matchRegexp: ['/^abc/']
});
expect(str2rules('matchRegexp:/^abc/')).toMatchObject({
matchRegexp: ['/^abc/']
});
});
test('validation:multiplestr2rules', () => {
expect(str2rules('matchRegexp1:/^abc/,matchRegexp2:/123$/'))
.toMatchObject({
matchRegexp1: ['/^abc/'],
matchRegexp2: ['/123$/']
});
expect(str2rules('matchRegexp1:/^abc/,matchRegexp2:/123$/')).toMatchObject({
matchRegexp1: ['/^abc/'],
matchRegexp2: ['/123$/']
});
});
test('validation:str2rules:noSlash', () => {
expect(str2rules('matchRegexp:^abc'))
.toMatchObject({
matchRegexp: ['^abc']
});
expect(str2rules('matchRegexp:^abc')).toMatchObject({
matchRegexp: ['^abc']
});
});
test('validation:multiplestr2rules:noSlash', () => {
expect(str2rules('matchRegexp1:^abc,matchRegexp2:123$'))
.toMatchObject({
matchRegexp1: ['^abc'],
matchRegexp2: ['123$']
});
});
expect(str2rules('matchRegexp1:^abc,matchRegexp2:123$')).toMatchObject({
matchRegexp1: ['^abc'],
matchRegexp2: ['123$']
});
});

View File

@ -250,6 +250,7 @@
> th {
background: var(--Table-thead-bg);
text-align: left;
&[colspan] {
text-align: center;
}

View File

@ -4,7 +4,9 @@
import {findObjectsWithKey} from './utils/helper';
const isMobile = window.matchMedia('(max-width: 768px)').matches ? true : false;
const isMobile = (window as any).matchMedia?.('(max-width: 768px)').matches
? true
: false;
export const envOverwrite = (schema: any, locale?: string) => {
if (schema.mobile && isMobile) {

View File

@ -116,6 +116,7 @@ export interface AjaxActionSchema extends ButtonSchema {
reload?: SchemaReload;
redirect?: string;
ignoreConfirm?: boolean;
}
export interface UrlActionSchema extends ButtonSchema {
@ -443,9 +444,9 @@ export class ActionRenderer extends React.Component<
> {
@autobind
handleAction(e: React.MouseEvent<any> | void | null, action: any) {
const {env, onAction, data} = this.props;
const {env, onAction, data, ignoreConfirm} = this.props;
if (action.confirmText && env.confirm) {
if (!ignoreConfirm && action.confirmText && env.confirm) {
env
.confirm(filter(action.confirmText, data))
.then((confirmed: boolean) => confirmed && onAction(e, action, data));

View File

@ -376,7 +376,7 @@ export default class CRUD extends React.Component<CRUDProps, any> {
control: any;
lastQuery: any;
dataInvalid: boolean = false;
timer: NodeJS.Timeout;
timer: NodeJS.Timeout | number;
mounted: boolean;
constructor(props: CRUDProps) {
super(props);
@ -646,44 +646,59 @@ export default class CRUD extends React.Component<CRUDProps, any> {
ids
});
if (action.actionType === 'dialog') {
return this.handleAction(
e,
{
...action,
__from: 'bulkAction'
},
ctx
);
} else if (action.actionType === 'ajax') {
isEffectiveApi(action.api, ctx) &&
store
.saveRemote(action.api as string, ctx, {
successMessage:
(action.messages && action.messages.success) ||
(messages && messages.saveSuccess),
errorMessage:
(action.messages && action.messages.failed) ||
(messages && messages.saveFailed)
})
.then(async (payload: object) => {
const data = createObject(ctx, payload);
if (action.feedback && isVisible(action.feedback, data)) {
await this.openFeedback(action.feedback, data);
stopAutoRefreshWhenModalIsOpen && clearTimeout(this.timer);
}
let fn = () => {
if (action.actionType === 'dialog') {
return this.handleAction(
e,
{
...action,
__from: 'bulkAction'
},
ctx
);
} else if (action.actionType === 'ajax') {
isEffectiveApi(action.api, ctx) &&
store
.saveRemote(action.api as string, ctx, {
successMessage:
(action.messages && action.messages.success) ||
(messages && messages.saveSuccess),
errorMessage:
(action.messages && action.messages.failed) ||
(messages && messages.saveFailed)
})
.then(async (payload: object) => {
const data = createObject(ctx, payload);
if (action.feedback && isVisible(action.feedback, data)) {
await this.openFeedback(action.feedback, data);
stopAutoRefreshWhenModalIsOpen && clearTimeout(this.timer);
}
action.reload
? this.reloadTarget(action.reload, data)
: this.search({[pageField || 'page']: 1}, undefined, true, true);
action.close && this.closeTarget(action.close);
action.reload
? this.reloadTarget(action.reload, data)
: this.search(
{[pageField || 'page']: 1},
undefined,
true,
true
);
action.close && this.closeTarget(action.close);
const redirect = action.redirect && filter(action.redirect, data);
redirect && env.jumpTo(redirect, action);
})
.catch(() => null);
} else if (onAction) {
onAction(e, action, ctx, false, this.context);
const redirect = action.redirect && filter(action.redirect, data);
redirect && env.jumpTo(redirect, action);
})
.catch(() => null);
} else if (onAction) {
onAction(e, action, ctx, false, this.context);
}
};
if (action.confirmText && env.confirm) {
env
.confirm(filter(action.confirmText, ctx))
.then((confirmed: boolean) => confirmed && fn());
} else {
fn();
}
}
@ -1538,7 +1553,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
{
size: 'sm',
...omit(btn, ['visibleOn', 'hiddenOn', 'disabledOn']),
type: 'button'
type: 'button',
ignoreConfirm: true
},
{
key: `bulk-${index}`,

View File

@ -278,6 +278,10 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
}
const dom = findDOMNode(this) as HTMLElement;
if (!(dom instanceof Element)) {
return;
}
let parent: HTMLElement | Window | null = dom ? getScrollParent(dom) : null;
if (!parent || parent === document.body) {
parent = window;

View File

@ -347,6 +347,8 @@ export const filters: {
last: input => input && (input.length ? input[input.length - 1] : null),
minus: (input, step = 1) => (parseInt(input, 10) || 0) - parseInt(step, 10),
plus: (input, step = 1) => (parseInt(input, 10) || 0) + parseInt(step, 10),
count: (input: any) =>
Array.isArray(input) || typeof input === 'string' ? input.length : 0,
sum: (input, field) =>
Array.isArray(input)
? input.reduce(

View File

@ -19,7 +19,8 @@
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"typeRoots": ["./node_modules/@types", "./types"],
"skipLibCheck": true
"skipLibCheck": true,
"types": ["node", "typePatches"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "acceptance-tests", "webpack", "jest"],