mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:48:55 +08:00
Merge branch 'baidu:master' into feat-unit-test-721
This commit is contained in:
commit
950dfc012d
@ -73,6 +73,9 @@ npm run update-snapshot
|
||||
# 先通过一下命令设置版本号
|
||||
npm run version
|
||||
|
||||
# 如果是 beta 版本使用如下命令
|
||||
# npm run version -- 2.0.1-beta.0 --no-git-tag-version
|
||||
|
||||
# 发布内部 registry
|
||||
npm run publish-to-internal
|
||||
|
||||
|
@ -26,6 +26,7 @@ order: 31
|
||||
"type": "button",
|
||||
"label": "编辑",
|
||||
"actionType": "dialog",
|
||||
"icon": "fa fa-pencil",
|
||||
"dialog": {
|
||||
"title": "编辑",
|
||||
"body": "你正在编辑该卡片"
|
||||
@ -34,6 +35,7 @@ order: 31
|
||||
{
|
||||
"type": "button",
|
||||
"label": "删除",
|
||||
"icon": "fa fa-trash",
|
||||
"actionType": "dialog",
|
||||
"dialog": {
|
||||
"title": "提示",
|
||||
|
@ -18,6 +18,7 @@ order: 55
|
||||
"type": "input-tag",
|
||||
"name": "tag",
|
||||
"label": "标签",
|
||||
"placeholder": "请选择标签",
|
||||
"options": [
|
||||
"Aaron Rodgers",
|
||||
"Tom Brady",
|
||||
|
@ -165,6 +165,7 @@ order: 60
|
||||
{
|
||||
"type": "page",
|
||||
"data": {
|
||||
"title": "title1",
|
||||
"items": [
|
||||
{
|
||||
"label": "cpu",
|
||||
@ -196,6 +197,7 @@ order: 60
|
||||
},
|
||||
"body": {
|
||||
"type": "property",
|
||||
"title": "${title}",
|
||||
"source": "${items}"
|
||||
}
|
||||
}
|
||||
@ -372,6 +374,8 @@ items 里的属性还支持 `visibleOn` 和 `hiddenOn` 表达式,能隐藏部
|
||||
| column | `number` | 3 | 每行几列 |
|
||||
| mode | `string` | 'table' | 显示模式,目前只有 'table' 和 'simple' |
|
||||
| separator | `string` | ',' | 'simple' 模式下属性名和值之间的分隔符 |
|
||||
| title | `string` | | 标题 |
|
||||
| source | `string` | | 数据源 |
|
||||
| items[].label | `SchemaTpl` | | 属性名 |
|
||||
| items[].content | `SchemaTpl` | | 属性值 |
|
||||
| items[].span | `SchemaTpl` | | 属性值跨几列 |
|
||||
|
@ -95,7 +95,12 @@ order: 9
|
||||
|
||||
### 发送 http 请求
|
||||
|
||||
通过配置`actionType: 'ajax'`和`api`实现 http 请求发送,该动作需实现 `env.fetcher` 请求器。请求结果的状态、数据、消息分别默认缓存在 `event.data.responseStatus`、`event.data.responseData`或`event.data.{{outputVar}}`、`event.data.responseMsg`。< 2.0.3 以以下版本,请求返回数据默认缓存在 `event.data`。`outputVar` 配置用于解决串行或者并行发送多个 http 请求的场景。
|
||||
通过配置`actionType: 'ajax'`和`api`实现 http 请求发送,该动作需实现 `env.fetcher` 请求器。
|
||||
|
||||
- 请求结果缓存在`event.data.responseResult`或`event.data.{{outputVar}}`。
|
||||
- 请求结果的状态、数据、消息分别默认缓存在:`event.data.{{outputVar}}.responseStatus`、`event.data.{{outputVar}}.responseData`、`event.data.{{outputVar}}.responseMsg`。
|
||||
|
||||
< 2.0.3 及以下版本,请求返回数据默认缓存在 `event.data`。`outputVar` 配置用于解决串行或者并行发送多个 http 请求的场景。
|
||||
|
||||
```schema
|
||||
{
|
||||
@ -129,7 +134,7 @@ order: 9
|
||||
},
|
||||
{
|
||||
actionType: 'toast',
|
||||
expression: '${event.data.responseStatus === 0}',
|
||||
expression: '${event.data.responseResult.responseStatus === 0}',
|
||||
args: {
|
||||
msg: '${event.data|json}'
|
||||
}
|
||||
@ -167,7 +172,7 @@ order: 9
|
||||
},
|
||||
{
|
||||
actionType: 'toast',
|
||||
expression: '${event.data.responseStatus === 0}',
|
||||
expression: '${event.data.responseResult.responseStatus === 0}',
|
||||
args: {
|
||||
msg: '${event.data|json}'
|
||||
}
|
||||
@ -2300,7 +2305,7 @@ registerAction('my-action', new MyAction());
|
||||
|
||||
**引用 http 请求动作返回的数据**
|
||||
|
||||
http 请求动作执行结束后,后面的动作可以通过 `event.data.responseStatus`、`event.data.responseData`或`event.data.{{outputVar}}`、`event.data.responseMsg`来获取请求结果的状态、数据、消息。
|
||||
http 请求动作执行结束后,后面的动作可以通过 `event.data.responseResult.responseStatus`或`event.data.{{outputVar}}.responseStatus`、`event.data.responseResult.responseData`或`event.data.{{outputVar}}.responseData`、`event.data.responseResult.responseMsg`或`event.data.{{outputVar}}.responseMsg`来获取请求结果的状态、数据、消息。
|
||||
|
||||
```schema
|
||||
{
|
||||
@ -2322,7 +2327,7 @@ http 请求动作执行结束后,后面的动作可以通过 `event.data.respo
|
||||
{
|
||||
actionType: 'dialog',
|
||||
args: {
|
||||
id: '${event.data.responseData.id}'
|
||||
id: '${event.data.responseResult.responseData.id}'
|
||||
},
|
||||
dialog: {
|
||||
type: 'dialog',
|
||||
|
@ -58,7 +58,6 @@ export class AjaxAction implements RendererAction {
|
||||
omit(action.args ?? {}, ['api', 'options', 'messages']),
|
||||
action.args?.options ?? {}
|
||||
);
|
||||
|
||||
const responseData =
|
||||
!isEmpty(result.data) || result.ok
|
||||
? normalizeApiResponseData(result.data)
|
||||
@ -68,9 +67,13 @@ export class AjaxAction implements RendererAction {
|
||||
event.setData(
|
||||
createObject(event.data, {
|
||||
...responseData, // 兼容历史配置
|
||||
[action.outputVar || 'responseData']: responseData,
|
||||
responseStatus: result.status,
|
||||
responseMsg: result.msg
|
||||
responseData: responseData,
|
||||
[action.outputVar || 'responseResult']: {
|
||||
...responseData,
|
||||
responseData,
|
||||
responseStatus: result.status,
|
||||
responseMsg: result.msg
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -166,6 +166,10 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
& > .#{$ns}Button-icon:first-child:not(:last-child) {
|
||||
margin-right: var(--Button-icon-rightMargin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,8 +263,11 @@ export class ResultBox extends React.Component<ResultBoxProps> {
|
||||
value={value || ''}
|
||||
onChange={this.handleChange}
|
||||
placeholder={__(
|
||||
Array.isArray(result) && result.length
|
||||
? inputPlaceholder
|
||||
/** 数组模式下输入内容后将不再展示placeholder */
|
||||
Array.isArray(result)
|
||||
? result.length > 0
|
||||
? inputPlaceholder
|
||||
: placeholder
|
||||
: result
|
||||
? ''
|
||||
: placeholder
|
||||
|
@ -683,7 +683,11 @@ export class Select extends React.Component<SelectProps, SelectState> {
|
||||
|
||||
@autobind
|
||||
handleKeyPress(e: React.KeyboardEvent) {
|
||||
if (this.props.multiple && e.key === ' ') {
|
||||
/**
|
||||
* 考虑到label/value中有空格的case
|
||||
* 这里使用组合键关闭 win:shift + space,mac:shift + space
|
||||
*/
|
||||
if (e.key === ' ' && e.shiftKey) {
|
||||
this.toggle();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
@ -628,46 +628,54 @@ export class Transfer<
|
||||
const placeholder =
|
||||
resultSearchPlaceholder || __('Transfer.selectFromLeft');
|
||||
|
||||
return resultSelectMode === 'table' ? (
|
||||
<ResultTableList
|
||||
classnames={cx}
|
||||
columns={columns!}
|
||||
options={options || []}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
option2value={option2value}
|
||||
cellRender={cellRender}
|
||||
onChange={onChange}
|
||||
multiple={false}
|
||||
searchable={searchable}
|
||||
placeholder={placeholder}
|
||||
onSearch={onResultSearch}
|
||||
/>
|
||||
) : resultSelectMode === 'tree' ? (
|
||||
<ResultTreeList
|
||||
classnames={cx}
|
||||
options={options}
|
||||
valueField={'value'}
|
||||
value={value || []}
|
||||
onChange={onChange!}
|
||||
itemRender={resultItemRender}
|
||||
searchable={searchable}
|
||||
placeholder={placeholder}
|
||||
onSearch={onResultSearch}
|
||||
/>
|
||||
) : (
|
||||
<ResultList
|
||||
className={cx('Transfer-value')}
|
||||
sortable={sortable}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
itemRender={resultItemRender}
|
||||
searchable={searchable}
|
||||
onSearch={onResultSearch}
|
||||
/>
|
||||
);
|
||||
switch (resultSelectMode) {
|
||||
case 'table':
|
||||
return (
|
||||
<ResultTableList
|
||||
classnames={cx}
|
||||
columns={columns!}
|
||||
options={options || []}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
option2value={option2value}
|
||||
cellRender={cellRender}
|
||||
onChange={onChange}
|
||||
multiple={false}
|
||||
searchable={searchable}
|
||||
placeholder={placeholder}
|
||||
onSearch={onResultSearch}
|
||||
/>
|
||||
);
|
||||
case 'tree':
|
||||
return (
|
||||
<ResultTreeList
|
||||
classnames={cx}
|
||||
className={cx('Transfer-value')}
|
||||
options={options}
|
||||
valueField={'value'}
|
||||
value={value || []}
|
||||
onChange={onChange!}
|
||||
itemRender={resultItemRender}
|
||||
searchable={searchable}
|
||||
placeholder={placeholder}
|
||||
onSearch={onResultSearch}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<ResultList
|
||||
className={cx('Transfer-value')}
|
||||
sortable={sortable}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
itemRender={resultItemRender}
|
||||
searchable={searchable}
|
||||
onSearch={onResultSearch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -172,12 +172,10 @@ export class FormulaPlugin {
|
||||
|
||||
eachTree(variables, item => {
|
||||
if (item.value) {
|
||||
const key = `\${${item.value}}`;
|
||||
varMap[key] = item.label;
|
||||
varMap[item.value] = item.label;
|
||||
}
|
||||
});
|
||||
const vars = Object.keys(varMap).sort((a, b) => b.length - a.length);
|
||||
|
||||
const editor = this.editor;
|
||||
const lines = editor.lineCount();
|
||||
for (let line = 0; line < lines; line++) {
|
||||
|
@ -231,7 +231,7 @@ register('zh-CN', {
|
||||
'Table.index': '序号',
|
||||
'Table.toggleColumn': '显示列',
|
||||
'Table.searchFields': '设置查询字段',
|
||||
'Tag.placeholder': '暂无标签',
|
||||
'Tag.placeholder': '请输入/选择标签',
|
||||
'Tag.tip': '最近使用的标签',
|
||||
'Text.add': '新增:{{label}}',
|
||||
'Time.placeholder': '请选择时间',
|
||||
|
@ -49,7 +49,7 @@ test('EventAction:ajax', async () => {
|
||||
actionType: 'setValue',
|
||||
componentId: 'page_001',
|
||||
args: {
|
||||
value: '${event.data.result}'
|
||||
value: '${event.data.result.responseData}'
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -93,7 +93,7 @@ test('EventAction:ajax', async () => {
|
||||
},
|
||||
{
|
||||
type: 'tpl',
|
||||
tpl: '${responseData.age}岁的天空,status:${responseStatus},msg:${responseMsg}'
|
||||
tpl: '${responseResult.responseData.age}岁的天空,status:${responseResult.responseStatus},msg:${responseResult.responseMsg}'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -40,7 +40,7 @@ test('EventAction:custom', async () => {
|
||||
actionType: 'setValue',
|
||||
componentId: 'page_001',
|
||||
args: {
|
||||
value: '${event.data.result}'
|
||||
value: '${event.data.result.responseData}'
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -63,7 +63,7 @@ test('EventAction:custom', async () => {
|
||||
actionType: 'setValue',
|
||||
componentId: 'page_001',
|
||||
args: {
|
||||
value: '${event.data.result}'
|
||||
value: '${event.data.result.responseData}'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -1,11 +1,17 @@
|
||||
import React = require('react');
|
||||
import {render, waitFor} from '@testing-library/react';
|
||||
import {cleanup, fireEvent, render, waitFor} from '@testing-library/react';
|
||||
import '../../src';
|
||||
import {render as amisRender} from '../../src';
|
||||
import {clearStoresCache, render as amisRender} from '../../src';
|
||||
import {makeEnv, wait} from '../helper';
|
||||
import rows from '../mockData/rows';
|
||||
|
||||
const fetcher = async (config: any) => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
clearStoresCache();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
async function fetcher(config: any) {
|
||||
return {
|
||||
status: 200,
|
||||
headers: {},
|
||||
@ -18,10 +24,11 @@ const fetcher = async (config: any) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
test('Renderer:crud', async () => {
|
||||
const {container, getByText} = render(
|
||||
test('Renderer:crud basic interval headerToolbar footerToolbar', async () => {
|
||||
const mockFetcher = jest.fn(fetcher);
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
@ -29,6 +36,10 @@ test('Renderer:crud', async () => {
|
||||
type: 'crud',
|
||||
api: '/api/mock2/sample',
|
||||
syncLocation: false,
|
||||
interval: 1000,
|
||||
perPage: 2,
|
||||
headerToolbar: ['export-excel', 'statistics'],
|
||||
footerToolbar: ['pagination', 'export-excel'],
|
||||
columns: [
|
||||
{
|
||||
name: '__id',
|
||||
@ -41,34 +52,52 @@ test('Renderer:crud', async () => {
|
||||
{
|
||||
name: 'browser',
|
||||
label: 'Browser'
|
||||
},
|
||||
{
|
||||
name: 'platform',
|
||||
label: 'Platform(s)'
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
label: 'Engine version'
|
||||
},
|
||||
{
|
||||
name: 'grade',
|
||||
label: 'CSS grade'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{},
|
||||
makeEnv({fetcher})
|
||||
makeEnv({fetcher: mockFetcher})
|
||||
)
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Internet Explorer 4.0')).toBeInTheDocument();
|
||||
expect(
|
||||
container.querySelector('[data-testid="spinner"]')
|
||||
).not.toBeInTheDocument();
|
||||
expect(container.querySelectorAll('tbody>tr').length > 5).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
await wait(1001);
|
||||
expect(mockFetcher).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('Renderer:crud stopAutoRefreshWhen', async () => {
|
||||
const mockFetcher = jest.fn(fetcher);
|
||||
render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
body: {
|
||||
type: 'crud',
|
||||
api: '/api/mock2/sample',
|
||||
syncLocation: false,
|
||||
interval: 1000,
|
||||
stopAutoRefreshWhen: 'true',
|
||||
columns: [
|
||||
{
|
||||
name: '__id',
|
||||
label: 'ID'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{},
|
||||
makeEnv({fetcher: mockFetcher})
|
||||
)
|
||||
);
|
||||
|
||||
await wait(1001);
|
||||
expect(mockFetcher).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('Renderer:crud loadDataOnce', async () => {
|
||||
@ -114,7 +143,9 @@ test('Renderer:crud loadDataOnce', async () => {
|
||||
)
|
||||
);
|
||||
|
||||
await wait(300);
|
||||
await waitFor(() => {
|
||||
expect(container.querySelectorAll('tbody>tr').length > 5).toBeTruthy();
|
||||
});
|
||||
expect(container.querySelector('.cxd-Crud-pager')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@ -141,7 +172,9 @@ test('Renderer:crud list', async () => {
|
||||
)
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
await wait(300);
|
||||
await waitFor(() => {
|
||||
expect(container.querySelectorAll('.cxd-ListItem').length > 5).toBeTruthy();
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@ -182,12 +215,14 @@ test('Renderer:crud cards', async () => {
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
await wait(300);
|
||||
await waitFor(() =>
|
||||
expect(container.querySelector('.cxd-Card-title')).toBeInTheDocument()
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Renderer:crud [source]', async () => {
|
||||
const {container, getByText} = render(
|
||||
test('Renderer:crud source & alwaysShowPagination', async () => {
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
@ -197,6 +232,7 @@ test('Renderer:crud [source]', async () => {
|
||||
body: {
|
||||
type: 'crud',
|
||||
source: 'fields',
|
||||
alwaysShowPagination: true,
|
||||
columns: [
|
||||
{
|
||||
name: '__id',
|
||||
@ -221,6 +257,7 @@ test('Renderer:crud [source]', async () => {
|
||||
});
|
||||
|
||||
test('Renderer:crud filter', async () => {
|
||||
const mockFetcher = jest.fn(fetcher);
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
@ -228,6 +265,9 @@ test('Renderer:crud filter', async () => {
|
||||
body: {
|
||||
type: 'crud',
|
||||
api: '/api/mock2/sample',
|
||||
defaultParams: {defaultValue: 'defaultValue'},
|
||||
pageField: 'customPageField',
|
||||
perPageField: 'customPerPageField',
|
||||
filter: {
|
||||
title: '条件搜索',
|
||||
body: [
|
||||
@ -269,15 +309,392 @@ test('Renderer:crud filter', async () => {
|
||||
}
|
||||
},
|
||||
{},
|
||||
makeEnv({fetcher: mockFetcher})
|
||||
)
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelectorAll('tbody>tr').length > 5).toBeTruthy();
|
||||
});
|
||||
|
||||
const {query} = mockFetcher.mock.calls[0][0];
|
||||
expect(query.defaultValue).toBe('defaultValue');
|
||||
expect(query.keywords).toBe('123');
|
||||
expect(query.customPageField).toBe(1);
|
||||
expect(query.customPerPageField).toBe(10);
|
||||
});
|
||||
|
||||
test('Renderer:crud draggable & itemDraggableOn', async () => {
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
body: {
|
||||
type: 'crud',
|
||||
api: '/api/mock2/sample',
|
||||
syncLocation: false,
|
||||
draggable: true,
|
||||
itemDraggableOn: '${__id !== 1}',
|
||||
columns: [
|
||||
{
|
||||
name: '__id',
|
||||
label: 'ID'
|
||||
},
|
||||
{
|
||||
name: 'engine',
|
||||
label: 'Rendering engine'
|
||||
},
|
||||
{
|
||||
name: 'browser',
|
||||
label: 'Browser'
|
||||
},
|
||||
{
|
||||
name: 'platform',
|
||||
label: 'Platform(s)'
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
label: 'Engine version'
|
||||
},
|
||||
{
|
||||
name: 'grade',
|
||||
label: 'CSS grade'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{},
|
||||
makeEnv({fetcher})
|
||||
)
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('[icon="exchange"]')).toBeInTheDocument();
|
||||
});
|
||||
fireEvent.click(container.querySelector('[icon="exchange"]')!);
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('[icon=drag]')).toBeInTheDocument();
|
||||
});
|
||||
expect(container.querySelectorAll('[icon=drag]').length).toBe(9);
|
||||
});
|
||||
|
||||
test('Renderer:crud quickEdit quickSaveApi', async () => {
|
||||
const mockFetcher = jest.fn(fetcher);
|
||||
const {container, getAllByText} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
body: {
|
||||
type: 'crud',
|
||||
api: '/api/mock2/sample',
|
||||
syncLocation: false,
|
||||
stopAutoRefreshWhen: 'true',
|
||||
quickSaveApi: '/api/mock2/sample/bulkUpdate',
|
||||
columns: [
|
||||
{
|
||||
name: '__id',
|
||||
label: 'ID'
|
||||
},
|
||||
{
|
||||
name: 'engine',
|
||||
label: 'Rendering engine',
|
||||
quickEdit: true
|
||||
},
|
||||
{
|
||||
name: 'browser',
|
||||
label: 'Browser',
|
||||
quickEdit: {
|
||||
saveImmediately: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{},
|
||||
makeEnv({fetcher: mockFetcher})
|
||||
)
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelectorAll('tbody>tr').length > 5).toBeTruthy();
|
||||
});
|
||||
fireEvent.click(container.querySelector('.cxd-Field-quickEditBtn')!);
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('input[name="engine"]')).toBeInTheDocument();
|
||||
});
|
||||
fireEvent.change(container.querySelector('input[name="engine"]')!, {
|
||||
target: {value: 'xxx'}
|
||||
});
|
||||
fireEvent.click(container.querySelector('button[type="submit"]')!);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
container.querySelector('input[name="engine"]')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
fireEvent.click(getAllByText('提交')[0]!);
|
||||
await wait(10);
|
||||
// * 提交后会调用一次 quickSaveApi 和 api
|
||||
expect(mockFetcher).toBeCalledTimes(3);
|
||||
});
|
||||
|
||||
test('Renderer:crud quickSaveItemApi saveImmediately', async () => {
|
||||
const mockFetcher = jest.fn(fetcher);
|
||||
const {container, getAllByText} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
body: {
|
||||
type: 'crud',
|
||||
api: '/api/mock2/sample',
|
||||
syncLocation: false,
|
||||
stopAutoRefreshWhen: 'true',
|
||||
quickSaveItemApi: '/api/mock2/sample/$id',
|
||||
hideQuickSaveBtn: true,
|
||||
columns: [
|
||||
{
|
||||
name: '__id',
|
||||
label: 'ID'
|
||||
},
|
||||
{
|
||||
name: 'browser',
|
||||
label: 'Browser',
|
||||
quickEdit: {
|
||||
saveImmediately: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{},
|
||||
makeEnv({fetcher: mockFetcher})
|
||||
)
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelectorAll('tbody>tr').length > 5).toBeTruthy();
|
||||
});
|
||||
fireEvent.click(container.querySelector('.cxd-Field-quickEditBtn')!);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
container.querySelector('input[name="browser"]')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
fireEvent.change(container.querySelector('input[name="browser"]')!, {
|
||||
target: {value: 'xxx'}
|
||||
});
|
||||
fireEvent.click(container.querySelector('button[type="submit"]')!);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
container.querySelector('input[name="browser"]')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
// * 提交后会调用一次 quickSaveItemApi 和 api
|
||||
expect(mockFetcher.mock.calls[1][0].url).toBe('/api/mock2/sample/');
|
||||
expect(mockFetcher).toBeCalledTimes(3);
|
||||
});
|
||||
|
||||
test('Renderer:crud bulkActions', async () => {
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
body: {
|
||||
type: 'crud',
|
||||
api: '/api/mock2/sample',
|
||||
syncLocation: false,
|
||||
bulkActions: [
|
||||
{
|
||||
label: '批量删除',
|
||||
actionType: 'ajax',
|
||||
api: 'delete:/amis/api/mock2/sample/${ids|raw}',
|
||||
confirmText: '确定要批量删除?'
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
name: '__id',
|
||||
label: 'ID'
|
||||
},
|
||||
{
|
||||
name: 'engine',
|
||||
label: 'Rendering engine',
|
||||
quickEdit: true
|
||||
},
|
||||
{
|
||||
name: 'browser',
|
||||
label: 'Browser',
|
||||
quickEdit: {
|
||||
saveImmediately: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{},
|
||||
makeEnv({fetcher})
|
||||
)
|
||||
);
|
||||
|
||||
// await wait(300);
|
||||
await waitFor(() => {
|
||||
expect(container.querySelectorAll('tbody>tr').length > 5).toBeTruthy();
|
||||
});
|
||||
expect(
|
||||
container.querySelector('.cxd-Button.is-disabled')
|
||||
).toBeInTheDocument();
|
||||
fireEvent.click(
|
||||
container.querySelector('.cxd-Table-checkCell input[type="checkbox"]')!
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
container.querySelector('[data-testid="spinner"]')
|
||||
container.querySelector('.cxd-Button.is-disabled')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('Renderer: crud sortable & orderBy & orderDir & orderField', async () => {
|
||||
const mockFetcher = jest.fn(fetcher);
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
body: {
|
||||
type: 'crud',
|
||||
api: '/api/mock2/sample',
|
||||
orderBy: 'id',
|
||||
orderDir: 'desc',
|
||||
syncLocation: false,
|
||||
orderField: 'xxx',
|
||||
columns: [
|
||||
{
|
||||
name: '__id',
|
||||
label: 'ID',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: 'engine',
|
||||
label: 'Rendering engine',
|
||||
quickEdit: true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{},
|
||||
makeEnv({fetcher: mockFetcher})
|
||||
)
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelectorAll('tbody>tr').length > 5).toBeTruthy();
|
||||
});
|
||||
expect(mockFetcher.mock.calls[0][0].query).toEqual({
|
||||
orderBy: 'id',
|
||||
orderDir: 'desc',
|
||||
page: 1,
|
||||
perPage: 10
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Renderer: crud keepItemSelectionOnPageChange & maxKeepItemSelectionLength & labelTpl', async () => {
|
||||
const mockFetcher = jest.fn(fetcher);
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
body: {
|
||||
type: 'crud',
|
||||
api: '/api/mock2/sample',
|
||||
syncLocation: false,
|
||||
keepItemSelectionOnPageChange: true,
|
||||
maxKeepItemSelectionLength: 4,
|
||||
labelTpl: '${id}${engine}',
|
||||
bulkActions: [
|
||||
{
|
||||
label: '批量删除',
|
||||
actionType: 'ajax',
|
||||
api: ''
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
name: '__id',
|
||||
label: 'ID',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
name: 'engine',
|
||||
label: 'Rendering engine',
|
||||
quickEdit: true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{},
|
||||
makeEnv({fetcher: mockFetcher})
|
||||
)
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelectorAll('tbody>tr').length > 5).toBeTruthy();
|
||||
});
|
||||
// 点击全部
|
||||
fireEvent.click(container.querySelector('th input[type="checkbox"]')!);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
container.querySelectorAll('.cxd-Crud-selection>.cxd-Crud-value').length
|
||||
).toBe(4);
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Renderer: crud autoGenerateFilter', async () => {
|
||||
const mockFetcher = jest.fn(fetcher);
|
||||
const {container} = render(
|
||||
amisRender(
|
||||
{
|
||||
type: 'page',
|
||||
body: {
|
||||
type: 'crud',
|
||||
api: '/api/mock2/sample',
|
||||
syncLocation: false,
|
||||
autoGenerateFilter: true,
|
||||
bulkActions: [
|
||||
{
|
||||
label: '批量删除',
|
||||
actionType: 'ajax',
|
||||
api: ''
|
||||
}
|
||||
],
|
||||
columns: [
|
||||
{
|
||||
name: '__id',
|
||||
label: 'ID',
|
||||
sortable: true,
|
||||
searchable: {
|
||||
type: 'input-text',
|
||||
name: 'id',
|
||||
label: '主键',
|
||||
placeholder: '输入id'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'engine',
|
||||
label: 'Rendering engine',
|
||||
quickEdit: true
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{},
|
||||
makeEnv({fetcher: mockFetcher})
|
||||
)
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelectorAll('tbody>tr').length > 5).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(
|
||||
container.querySelector('input[name="id"][placeholder="输入id"]')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
@ -1,123 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Renderer:InputTag InputTag input with batch tag 1`] = `
|
||||
<div>
|
||||
<form
|
||||
class="cxd-Form cxd-Form--normal"
|
||||
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>
|
||||
标签
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-expanded="true"
|
||||
aria-haspopup="listbox"
|
||||
aria-labelledby="downshift-2-label"
|
||||
aria-owns="downshift-2-menu"
|
||||
class="cxd-Form-control cxd-TagControl"
|
||||
role="combobox"
|
||||
>
|
||||
<div
|
||||
class="cxd-ResultBox cxd-TagControl-input is-focused is-group"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="cxd-ResultBox-value-wrap"
|
||||
>
|
||||
<div
|
||||
class="cxd-ResultBox-value"
|
||||
>
|
||||
<span
|
||||
class="cxd-ResultBox-valueLabel"
|
||||
>
|
||||
Apple
|
||||
</span>
|
||||
<a
|
||||
data-index="0"
|
||||
>
|
||||
<icon-mock
|
||||
classname="icon icon-close"
|
||||
icon="close"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="cxd-ResultBox-value"
|
||||
>
|
||||
<span
|
||||
class="cxd-ResultBox-valueLabel"
|
||||
>
|
||||
Orange
|
||||
</span>
|
||||
<a
|
||||
data-index="1"
|
||||
>
|
||||
<icon-mock
|
||||
classname="icon icon-close"
|
||||
icon="close"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="cxd-ResultBox-value"
|
||||
>
|
||||
<span
|
||||
class="cxd-ResultBox-valueLabel"
|
||||
>
|
||||
Banana
|
||||
</span>
|
||||
<a
|
||||
data-index="2"
|
||||
>
|
||||
<icon-mock
|
||||
classname="icon icon-close"
|
||||
icon="close"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="downshift-2-menu"
|
||||
aria-labelledby="downshift-2-label"
|
||||
autocomplete="off"
|
||||
class="cxd-ResultBox-value-input"
|
||||
id="downshift-2-input"
|
||||
name="tag"
|
||||
placeholder="请输入"
|
||||
theme="cxd"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="cxd-ResultBox-actions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Renderer:InputTag InputTag input with batch tag and separator "|" 1`] = `
|
||||
<div>
|
||||
<form
|
||||
class="cxd-Form cxd-Form--normal"
|
||||
@ -218,7 +101,7 @@ exports[`Renderer:InputTag InputTag input with batch tag and separator "|" 1`] =
|
||||
class="cxd-ResultBox-value-input"
|
||||
id="downshift-3-input"
|
||||
name="tag"
|
||||
placeholder="请输入"
|
||||
placeholder=""
|
||||
theme="cxd"
|
||||
type="text"
|
||||
value=""
|
||||
@ -234,7 +117,7 @@ exports[`Renderer:InputTag InputTag input with batch tag and separator "|" 1`] =
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Renderer:InputTag InputTag input with max quantity 2 1`] = `
|
||||
exports[`Renderer:InputTag InputTag input with batch tag and separator "|" 1`] = `
|
||||
<div>
|
||||
<form
|
||||
class="cxd-Form cxd-Form--normal"
|
||||
@ -262,9 +145,10 @@ exports[`Renderer:InputTag InputTag input with max quantity 2 1`] = `
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-expanded="true"
|
||||
aria-haspopup="listbox"
|
||||
aria-labelledby="downshift-4-label"
|
||||
aria-owns="downshift-4-menu"
|
||||
class="cxd-Form-control cxd-TagControl"
|
||||
role="combobox"
|
||||
>
|
||||
@ -309,17 +193,35 @@ exports[`Renderer:InputTag InputTag input with max quantity 2 1`] = `
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="cxd-ResultBox-value"
|
||||
>
|
||||
<span
|
||||
class="cxd-ResultBox-valueLabel"
|
||||
>
|
||||
Banana
|
||||
</span>
|
||||
<a
|
||||
data-index="2"
|
||||
>
|
||||
<icon-mock
|
||||
classname="icon icon-close"
|
||||
icon="close"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="downshift-4-menu"
|
||||
aria-labelledby="downshift-4-label"
|
||||
autocomplete="off"
|
||||
class="cxd-ResultBox-value-input"
|
||||
id="downshift-4-input"
|
||||
name="tag"
|
||||
placeholder="请输入"
|
||||
placeholder=""
|
||||
theme="cxd"
|
||||
type="text"
|
||||
value="Banana"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@ -332,7 +234,7 @@ exports[`Renderer:InputTag InputTag input with max quantity 2 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Renderer:InputTag InputTag input with maxTagLength 5 1`] = `
|
||||
exports[`Renderer:InputTag InputTag input with max quantity 2 1`] = `
|
||||
<div>
|
||||
<form
|
||||
class="cxd-Form cxd-Form--normal"
|
||||
@ -390,6 +292,23 @@ exports[`Renderer:InputTag InputTag input with maxTagLength 5 1`] = `
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="cxd-ResultBox-value"
|
||||
>
|
||||
<span
|
||||
class="cxd-ResultBox-valueLabel"
|
||||
>
|
||||
Orange
|
||||
</span>
|
||||
<a
|
||||
data-index="1"
|
||||
>
|
||||
<icon-mock
|
||||
classname="icon icon-close"
|
||||
icon="close"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-labelledby="downshift-5-label"
|
||||
@ -397,7 +316,88 @@ exports[`Renderer:InputTag InputTag input with maxTagLength 5 1`] = `
|
||||
class="cxd-ResultBox-value-input"
|
||||
id="downshift-5-input"
|
||||
name="tag"
|
||||
placeholder="请输入"
|
||||
placeholder=""
|
||||
theme="cxd"
|
||||
type="text"
|
||||
value="Banana"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="cxd-ResultBox-actions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Renderer:InputTag InputTag input with maxTagLength 5 1`] = `
|
||||
<div>
|
||||
<form
|
||||
class="cxd-Form cxd-Form--normal"
|
||||
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>
|
||||
标签
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-labelledby="downshift-6-label"
|
||||
class="cxd-Form-control cxd-TagControl"
|
||||
role="combobox"
|
||||
>
|
||||
<div
|
||||
class="cxd-ResultBox cxd-TagControl-input is-focused is-group"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="cxd-ResultBox-value-wrap"
|
||||
>
|
||||
<div
|
||||
class="cxd-ResultBox-value"
|
||||
>
|
||||
<span
|
||||
class="cxd-ResultBox-valueLabel"
|
||||
>
|
||||
Apple
|
||||
</span>
|
||||
<a
|
||||
data-index="0"
|
||||
>
|
||||
<icon-mock
|
||||
classname="icon icon-close"
|
||||
icon="close"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-labelledby="downshift-6-label"
|
||||
autocomplete="off"
|
||||
class="cxd-ResultBox-value-input"
|
||||
id="downshift-6-input"
|
||||
name="tag"
|
||||
placeholder=""
|
||||
theme="cxd"
|
||||
type="text"
|
||||
value="Banana"
|
||||
@ -443,8 +443,8 @@ exports[`Renderer:InputTag InputTag input with single tag 1`] = `
|
||||
<div
|
||||
aria-expanded="true"
|
||||
aria-haspopup="listbox"
|
||||
aria-labelledby="downshift-1-label"
|
||||
aria-owns="downshift-1-menu"
|
||||
aria-labelledby="downshift-2-label"
|
||||
aria-owns="downshift-2-menu"
|
||||
class="cxd-Form-control cxd-TagControl"
|
||||
role="combobox"
|
||||
>
|
||||
@ -474,13 +474,13 @@ exports[`Renderer:InputTag InputTag input with single tag 1`] = `
|
||||
</div>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="downshift-1-menu"
|
||||
aria-labelledby="downshift-1-label"
|
||||
aria-controls="downshift-2-menu"
|
||||
aria-labelledby="downshift-2-label"
|
||||
autocomplete="off"
|
||||
class="cxd-ResultBox-value-input"
|
||||
id="downshift-1-input"
|
||||
id="downshift-2-input"
|
||||
name="tag"
|
||||
placeholder="请输入"
|
||||
placeholder=""
|
||||
theme="cxd"
|
||||
type="text"
|
||||
value=""
|
||||
@ -526,8 +526,8 @@ exports[`Renderer:InputTag InputTag with options 1`] = `
|
||||
<div
|
||||
aria-expanded="true"
|
||||
aria-haspopup="listbox"
|
||||
aria-labelledby="downshift-0-label"
|
||||
aria-owns="downshift-0-menu"
|
||||
aria-labelledby="downshift-1-label"
|
||||
aria-owns="downshift-1-menu"
|
||||
class="cxd-Form-control cxd-TagControl has-popover"
|
||||
role="combobox"
|
||||
>
|
||||
@ -541,13 +541,13 @@ exports[`Renderer:InputTag InputTag with options 1`] = `
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-controls="downshift-0-menu"
|
||||
aria-labelledby="downshift-0-label"
|
||||
aria-controls="downshift-1-menu"
|
||||
aria-labelledby="downshift-1-label"
|
||||
autocomplete="off"
|
||||
class="cxd-ResultBox-value-input"
|
||||
id="downshift-0-input"
|
||||
id="downshift-1-input"
|
||||
name="tag"
|
||||
placeholder=""
|
||||
placeholder="请输入/选择标签"
|
||||
theme="cxd"
|
||||
type="text"
|
||||
value=""
|
||||
@ -610,7 +610,7 @@ exports[`Renderer:InputTag InputTag with options 1`] = `
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="cxd-ListMenu-item"
|
||||
id="downshift-0-item-0"
|
||||
id="downshift-1-item-0"
|
||||
role="option"
|
||||
>
|
||||
<div
|
||||
@ -622,7 +622,7 @@ exports[`Renderer:InputTag InputTag with options 1`] = `
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="cxd-ListMenu-item"
|
||||
id="downshift-0-item-1"
|
||||
id="downshift-1-item-1"
|
||||
role="option"
|
||||
>
|
||||
<div
|
||||
@ -634,7 +634,7 @@ exports[`Renderer:InputTag InputTag with options 1`] = `
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="cxd-ListMenu-item"
|
||||
id="downshift-0-item-2"
|
||||
id="downshift-1-item-2"
|
||||
role="option"
|
||||
>
|
||||
<div
|
||||
@ -689,3 +689,67 @@ exports[`Renderer:InputTag InputTag with options 1`] = `
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Renderer:InputTag InputTag with placeholder 1`] = `
|
||||
<div>
|
||||
<form
|
||||
class="cxd-Form cxd-Form--normal"
|
||||
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>
|
||||
标签
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-haspopup="listbox"
|
||||
aria-labelledby="downshift-0-label"
|
||||
class="cxd-Form-control cxd-TagControl"
|
||||
role="combobox"
|
||||
>
|
||||
<div
|
||||
class="cxd-ResultBox cxd-TagControl-input is-group"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="cxd-ResultBox-value-wrap"
|
||||
>
|
||||
<input
|
||||
aria-autocomplete="list"
|
||||
aria-labelledby="downshift-0-label"
|
||||
autocomplete="off"
|
||||
class="cxd-ResultBox-value-input"
|
||||
id="downshift-0-input"
|
||||
name="tag"
|
||||
placeholder="please input the tag"
|
||||
theme="cxd"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="cxd-ResultBox-actions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
@ -52,6 +52,14 @@ const setupInputTag = async (inputTagOptions: any = {}) => {
|
||||
};
|
||||
|
||||
describe('Renderer:InputTag', () => {
|
||||
test('InputTag with placeholder', async () => {
|
||||
const placeholder = 'please input the tag';
|
||||
const {container, input} = await setupInputTag({placeholder});
|
||||
|
||||
expect(input.placeholder).toBe(placeholder);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('InputTag with options', async () => {
|
||||
const {container, input} = await setupInputTag({
|
||||
options: ['Apple', 'Orange', 'Banana']
|
||||
|
@ -927,11 +927,12 @@ test('Renderer:table list', () => {
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Renderer:table selectable', async () => {
|
||||
describe('Renderer:table selectable & itemCheckableOn', () => {
|
||||
const schema: any = {
|
||||
type: 'table',
|
||||
title: '表格1',
|
||||
selectable: true,
|
||||
itemCheckableOn: '${__id != 1}',
|
||||
data: {
|
||||
items: rows
|
||||
},
|
||||
@ -947,19 +948,26 @@ test('Renderer:table selectable', async () => {
|
||||
]
|
||||
};
|
||||
|
||||
const {container, rerender} = render(amisRender(schema, {}, makeEnv({})));
|
||||
test('radio style', async () => {
|
||||
const {container, debug} = render(amisRender(schema, {}, makeEnv({})));
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('[type=radio]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('[type=radio]')).toBeInTheDocument();
|
||||
expect(
|
||||
container.querySelector('[data-id="1"] [type=radio][disabled=""]')!
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
schema.multiple = true;
|
||||
const {container: multipleContainer} = render(
|
||||
amisRender(schema, {}, makeEnv({}))
|
||||
);
|
||||
await waitFor(() => {
|
||||
test('checkbox style', async () => {
|
||||
schema.multiple = true;
|
||||
const {container} = render(amisRender(schema, {}, makeEnv({})));
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('[type=checkbox]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(
|
||||
multipleContainer.querySelector('[type=checkbox]')
|
||||
container.querySelector('[data-id="1"] [type=checkbox][disabled=""]')!
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -486,14 +486,13 @@ export default class FormTable extends React.Component<TableProps, TableState> {
|
||||
typeof column.name === 'string'
|
||||
) {
|
||||
if (
|
||||
[
|
||||
'input-date',
|
||||
'input-datetime',
|
||||
'input-time',
|
||||
'input-month',
|
||||
'input-quarter',
|
||||
'input-year'
|
||||
].includes(column.type)
|
||||
'type' in column &&
|
||||
(column.type === 'input-date' ||
|
||||
column.type === 'input-datetime' ||
|
||||
column.type === 'input-time' ||
|
||||
column.type === 'input-month' ||
|
||||
column.type === 'input-quarter' ||
|
||||
column.type === 'input-year')
|
||||
) {
|
||||
const date = filterDate(column.value, data, column.format || 'X');
|
||||
setVariable(
|
||||
|
@ -254,6 +254,10 @@ export default class TagControl extends React.PureComponent<
|
||||
}
|
||||
|
||||
async addItem(option: Option) {
|
||||
if (this.isReachMax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {selectedOptions, onChange} = this.props;
|
||||
const newValue = selectedOptions.concat();
|
||||
|
||||
@ -393,7 +397,7 @@ export default class TagControl extends React.PureComponent<
|
||||
|
||||
@autobind
|
||||
handleOptionChange(option: Option) {
|
||||
if (this.state.inputValue || !option) {
|
||||
if (this.isReachMax() || this.state.inputValue || !option) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -415,6 +419,12 @@ export default class TagControl extends React.PureComponent<
|
||||
reload?.();
|
||||
}
|
||||
|
||||
@autobind
|
||||
isReachMax() {
|
||||
const {max, selectedOptions} = this.props;
|
||||
return max != null && isInteger(max) && selectedOptions.length >= max;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
@ -445,6 +455,8 @@ export default class TagControl extends React.PureComponent<
|
||||
)
|
||||
: [];
|
||||
|
||||
const reachMax = this.isReachMax();
|
||||
|
||||
return (
|
||||
<Downshift
|
||||
selectedItem={selectedOptions}
|
||||
@ -461,13 +473,14 @@ export default class TagControl extends React.PureComponent<
|
||||
{...getInputProps({
|
||||
name,
|
||||
ref: this.input,
|
||||
placeholder: __(placeholder || 'Tag.placeholder'),
|
||||
placeholder: __(placeholder ?? 'Tag.placeholder'),
|
||||
value: this.state.inputValue,
|
||||
onKeyDown: this.handleKeyDown,
|
||||
onFocus: this.handleFocus,
|
||||
onBlur: this.handleBlur,
|
||||
disabled
|
||||
})}
|
||||
inputPlaceholder={''}
|
||||
onChange={this.handleInputChange}
|
||||
className={cx('TagControl-input')}
|
||||
result={selectedOptions}
|
||||
@ -507,7 +520,10 @@ export default class TagControl extends React.PureComponent<
|
||||
...getItemProps({
|
||||
index,
|
||||
item,
|
||||
disabled: item.disabled
|
||||
disabled: reachMax || item.disabled,
|
||||
className: cx('ListMenu-item', {
|
||||
'is-disabled': reachMax
|
||||
}),
|
||||
})
|
||||
})}
|
||||
/>
|
||||
@ -524,7 +540,7 @@ export default class TagControl extends React.PureComponent<
|
||||
{options.map((item, index) => (
|
||||
<div
|
||||
className={cx('TagControl-sugItem', {
|
||||
'is-disabled': item.disabled || disabled
|
||||
'is-disabled': item.disabled || disabled || reachMax
|
||||
})}
|
||||
key={index}
|
||||
onClick={this.addItem.bind(this, item)}
|
||||
|
@ -889,6 +889,7 @@ export default class NestedSelectControl extends React.Component<
|
||||
disabled={disabled}
|
||||
ref={this.domRef}
|
||||
placeholder={__(placeholder ?? 'placeholder.empty')}
|
||||
inputPlaceholder={''}
|
||||
className={cx(`NestedSelect`, {
|
||||
'NestedSelect--inline': inline,
|
||||
'NestedSelect--single': !multiple,
|
||||
@ -918,7 +919,6 @@ export default class NestedSelectControl extends React.Component<
|
||||
clearable={clearable}
|
||||
hasDropDownArrow={true}
|
||||
allowInput={searchable}
|
||||
inputPlaceholder={''}
|
||||
>
|
||||
{loading ? <Spinner size="sm" /> : undefined}
|
||||
</ResultBox>
|
||||
|
@ -629,7 +629,8 @@ export default class TreeSelectControl extends React.Component<
|
||||
<ResultBox
|
||||
disabled={disabled}
|
||||
ref={this.targetRef}
|
||||
placeholder={__(placeholder || 'placeholder.empty')}
|
||||
placeholder={__(placeholder ?? 'placeholder.empty')}
|
||||
inputPlaceholder={''}
|
||||
className={cx(`TreeSelect`, {
|
||||
'TreeSelect--inline': inline,
|
||||
'TreeSelect--single': !multiple,
|
||||
@ -657,7 +658,6 @@ export default class TreeSelectControl extends React.Component<
|
||||
onKeyDown={this.handleInputKeyDown}
|
||||
clearable={clearable}
|
||||
allowInput={searchable || isEffectiveApi(autoComplete)}
|
||||
inputPlaceholder={''}
|
||||
>
|
||||
{loading ? <Spinner size="sm" /> : undefined}
|
||||
</ResultBox>
|
||||
|
@ -119,14 +119,7 @@ export default class Property extends React.Component<PropertyProps, object> {
|
||||
*/
|
||||
prepareRows() {
|
||||
const {column = 3, items, source, data} = this.props;
|
||||
const propertyItems =
|
||||
(items
|
||||
? items
|
||||
: (resolveVariableAndFilter(
|
||||
source,
|
||||
data,
|
||||
'| raw'
|
||||
) as Array<PropertyItem>)) || [];
|
||||
const propertyItems = items ? items : source || [];
|
||||
|
||||
const rows: PropertyContent[][] = [];
|
||||
|
||||
@ -246,6 +239,7 @@ export default class Property extends React.Component<PropertyProps, object> {
|
||||
}
|
||||
|
||||
@Renderer({
|
||||
type: 'property'
|
||||
type: 'property',
|
||||
autoVar: true
|
||||
})
|
||||
export class PropertyRenderer extends Property {}
|
||||
|
@ -659,6 +659,7 @@ export class ServiceRenderer extends Service {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
const scoped = this.context as IScopedContext;
|
||||
scoped.unRegisterComponent(this as ScopedComponentType);
|
||||
}
|
||||
|
@ -228,7 +228,7 @@ export default class TableView extends React.Component<TableViewProps, object> {
|
||||
renderTrs(trs: TrObject[]) {
|
||||
const {data} = this.props;
|
||||
const tr = trs.map((tr, rowIndex) =>
|
||||
this.renderTr(resolveMappingObject(tr, data), rowIndex)
|
||||
this.renderTr(resolveMappingObject(tr, data) as TrObject, rowIndex)
|
||||
);
|
||||
return tr;
|
||||
}
|
||||
|
@ -6,6 +6,10 @@ npm run build --workspaces
|
||||
rm -rf npm
|
||||
mkdir npm
|
||||
|
||||
# 如果有问题可以注释掉这两行,不知道为啥会导致 cp -rf 挂掉
|
||||
# rm -rf packages/amis/node_modules/.bin
|
||||
# rm -rf packages/amis-ui/node_modules/.bin
|
||||
|
||||
cp -rf packages npm
|
||||
cp package.json npm
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user