fix: Fix the rendering error caused by incorrect key value when configuring the loop

This commit is contained in:
liujuping 2022-04-08 15:22:51 +08:00 committed by LeoYuan 袁力皓
parent d2502427ca
commit 1026763dc5
12 changed files with 720 additions and 17 deletions

View File

@ -0,0 +1,6 @@
{
"plugins": [
"build-plugin-component",
"@alilc/lowcode-test-mate/plugin/index.ts"
]
}

View File

@ -1,7 +1,3 @@
const esModules = [
'@alilc/lowcode-datasource-engine',
].join('|');
module.exports = { module.exports = {
transform: { transform: {
'^.+\\.(ts|tsx)$': 'ts-jest', '^.+\\.(ts|tsx)$': 'ts-jest',

View File

@ -10,6 +10,7 @@
"es" "es"
], ],
"scripts": { "scripts": {
"test": "build-scripts test --config build.test.json",
"build": "build-scripts build --skip-demo" "build": "build-scripts build --skip-demo"
}, },
"dependencies": { "dependencies": {

View File

@ -565,7 +565,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
engine?.props?.onCompGetCtx(schema, scope); engine?.props?.onCompGetCtx(schema, scope);
} }
props.key = props.key || `${schema.__ctx.lceKey}_${schema.__ctx.idx || 0}_${idx !== undefined ? idx : ''}`; props.key = props.key || `${schema.__ctx.lceKey}_${schema.__ctx.idx || 0}_${idx !== undefined ? idx : ''}`;
} else if (typeof idx === 'number' && !props.key) { } else if ((typeof idx === 'number' || typeof idx === 'string') && !props.key) {
// 仅当循环场景走这里 // 仅当循环场景走这里
props.key = idx; props.key = idx;
} }

View File

@ -0,0 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`leafWrapper base 1`] = `
<div
_leaf={
Node {
"emitter": EventEmitter {
"_events": Object {
"onChildrenChange": [Function],
"onPropChange": [Function],
"onVisibleChange": [Function],
},
"_eventsCount": 3,
"_maxListeners": undefined,
Symbol(kCapture): false,
},
"hasLoop": false,
"schema": Object {},
}
}
>
<div>
content
</div>
</div>
`;
exports[`leafWrapper change props 1`] = `
<div
_leaf={
Node {
"emitter": EventEmitter {
"_events": Object {
"onChildrenChange": [Function],
"onPropChange": [Function],
"onVisibleChange": [Function],
},
"_eventsCount": 3,
"_maxListeners": undefined,
Symbol(kCapture): false,
},
"hasLoop": false,
"schema": Object {},
}
}
>
<div>
new content
</div>
</div>
`;

View File

@ -0,0 +1,108 @@
import renderer from 'react-test-renderer';
import React from 'react';
import { createElement } from 'react';
import '../utils/react-env-init';
import { leafWrapper } from '../../src/hoc/leaf';
import components from '../utils/components';
import Node from '../utils/node';
const baseRenderer: any = {
__debug () {},
__getComponentProps (schema: any) {
return schema.props;
},
__getSchemaChildrenVirtualDom () {},
context: {
engine: {
createElement,
}
},
props: {
__host: {},
getNode: () => {},
__container: () => {},
}
}
describe('leafWrapper', () => {
const Div = leafWrapper(components.Div as any, {
schema: {
id: 'div',
},
baseRenderer,
componentInfo: {},
scope: {},
});
const DivNode = new Node({});
const TextNode = new Node({});
const Text = leafWrapper(components.Text as any, {
schema: {
id: 'div',
props: {
content: 'content'
}
},
baseRenderer,
componentInfo: {},
scope: {},
});
const component = renderer.create(
// @ts-ignore
<Div _leaf={DivNode}>
<Text _leaf={TextNode} content="content"></Text>
</Div>
);
it('base', () => {
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
it('change props', () => {
TextNode.emitPropChange({
key: 'content',
newValue: 'new content',
} as any);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
describe('loop', () => {
const Div = leafWrapper(components.Div as any, {
schema: {
id: 'div',
},
baseRenderer,
componentInfo: {},
scope: {},
});
const DivNode = new Node({});
const TextNode = new Node({});
const Text = leafWrapper(components.Text as any, {
schema: {
id: 'div',
props: {
content: 'content'
}
},
baseRenderer,
componentInfo: {},
scope: {},
});
const component = renderer.create(
// @ts-ignore
<Div _leaf={DivNode}>
<Text _leaf={TextNode} content="content"></Text>
</Div>
);
})

View File

@ -0,0 +1,221 @@
const schema = {
"componentName": "Page",
"id": "node_ocl1djd9o41",
"docId": "docl1djd9o4",
"props": {
"templateVersion": "1.0.0",
"containerStyle": {},
"pageStyle": {
"backgroundColor": "#f2f3f5"
},
"className": "_css_pseudo_node_ocl1djd9o41"
},
"dataSource": {
"offline": [],
"globalConfig": {},
"online": [
{
"gmtModified": 1639385418000,
"initialData": "",
"globalUid": "AY866BC1ERSVK0BE55NU364515LH3NM0RF4XK61",
"formUuid": "FORM-3KYJN7RV-J47BPFK63W2PHAGPO1VC3-B4H1WE5K-131",
"name": "locale",
"description": "当前语种(在 window.g_config 中设置)",
"id": "AY866BC1ERSVK0BE55NU364515LH3NM0RF4XK61",
"protocal": "VALUE",
"shareType": "APP"
},
{
"gmtModified": 1639385418000,
"initialData": "",
"globalUid": "AY866BC1ERSVK0BE55NU364515LH3SM0RF4XK71",
"formUuid": "FORM-RFYJTWKV-D47BWO6R0QHA74R062FN2-R5IPXK4K-0H",
"name": "appType",
"description": "应用的唯一 code",
"id": "AY866BC1ERSVK0BE55NU364515LH3SM0RF4XK71",
"protocal": "VALUE",
"shareType": "APP"
},
{
"gmtModified": 1639385418000,
"initialData": "",
"globalUid": "AY866BC1ERSVK0BE55NU364515LH3XM0RF4XK81",
"formUuid": "FORM-RFYJTWKV-D47BWO6R0QHA74R062FN2-R5IPXK4K-0H",
"name": "version",
"description": "应该版本,默认 0.1.0",
"id": "AY866BC1ERSVK0BE55NU364515LH3XM0RF4XK81",
"protocal": "VALUE",
"shareType": "APP"
},
{
"gmtModified": 1639385418000,
"initialData": "",
"globalUid": "AY866BC1ERSVK0BE55NU364515LH33N0RF4XK91",
"formUuid": "FORM-RFYJTWKV-D47BWO6R0QHA74R062FN2-R5IPXK4K-0H",
"name": "apiPrefix",
"description": "",
"id": "AY866BC1ERSVK0BE55NU364515LH33N0RF4XK91",
"protocal": "VALUE",
"shareType": "APP"
}
],
"sync": true,
"list": [
{
"gmtModified": 1639385418000,
"initialData": "",
"globalUid": "AY866BC1ERSVK0BE55NU364515LH3NM0RF4XK61",
"formUuid": "FORM-3KYJN7RV-J47BPFK63W2PHAGPO1VC3-B4H1WE5K-131",
"name": "locale",
"description": "当前语种(在 window.g_config 中设置)",
"id": "AY866BC1ERSVK0BE55NU364515LH3NM0RF4XK61",
"protocal": "VALUE",
"shareType": "APP"
},
{
"gmtModified": 1639385418000,
"initialData": "",
"globalUid": "AY866BC1ERSVK0BE55NU364515LH3SM0RF4XK71",
"formUuid": "FORM-RFYJTWKV-D47BWO6R0QHA74R062FN2-R5IPXK4K-0H",
"name": "appType",
"description": "应用的唯一 code",
"id": "AY866BC1ERSVK0BE55NU364515LH3SM0RF4XK71",
"protocal": "VALUE",
"shareType": "APP"
},
{
"gmtModified": 1639385418000,
"initialData": "",
"globalUid": "AY866BC1ERSVK0BE55NU364515LH3XM0RF4XK81",
"formUuid": "FORM-RFYJTWKV-D47BWO6R0QHA74R062FN2-R5IPXK4K-0H",
"name": "version",
"description": "应该版本,默认 0.1.0",
"id": "AY866BC1ERSVK0BE55NU364515LH3XM0RF4XK81",
"protocal": "VALUE",
"shareType": "APP"
},
{
"gmtModified": 1639385418000,
"initialData": "",
"globalUid": "AY866BC1ERSVK0BE55NU364515LH33N0RF4XK91",
"formUuid": "FORM-RFYJTWKV-D47BWO6R0QHA74R062FN2-R5IPXK4K-0H",
"name": "apiPrefix",
"description": "",
"id": "AY866BC1ERSVK0BE55NU364515LH33N0RF4XK91",
"protocal": "VALUE",
"shareType": "APP"
}
]
},
"methods": {},
"hidden": false,
"title": "",
"isLocked": false,
"condition": true,
"conditionGroup": "",
"children": [
{
"componentName": "RootHeader",
"id": "node_ocl1djd9o42",
"docId": "docl1djd9o4",
"props": {},
"hidden": false,
"title": "",
"isLocked": false,
"condition": true,
"conditionGroup": ""
},
{
"componentName": "RootContent",
"id": "node_ocl1djd9o43",
"docId": "docl1djd9o4",
"props": {
"contentMargin": "20",
"contentPadding": "20",
"contentBgColor": "white"
},
"hidden": false,
"title": "",
"isLocked": false,
"condition": true,
"conditionGroup": "",
"children": [
{
"componentName": "Div",
"id": "node_ocl1djd9o45",
"docId": "docl1djd9o4",
"props": {
"behavior": "NORMAL",
"__style__": {},
"fieldId": "div_l1djdj1n",
"events": {
"ignored": true
},
"useFieldIdAsDomId": false,
"customClassName": "",
"className": "_css_pseudo_node_ocl1djd9o45"
},
"hidden": false,
"title": "",
"isLocked": false,
"condition": true,
"conditionGroup": "",
"loop": [
1,
2,
3
],
"loopArgs": [
null,
null
],
"children": [
{
"componentName": "Div",
"id": "node_ocl1djd9o46",
"docId": "docl1djd9o4",
"props": {
"behavior": "NORMAL",
"__style__": {},
"fieldId": "div_l1djdj1o",
"events": {
"ignored": true
},
"useFieldIdAsDomId": false,
"customClassName": "",
"className": "_css_pseudo_node_ocl1djd9o46"
},
"hidden": false,
"title": "",
"isLocked": false,
"condition": true,
"conditionGroup": "",
"loop": [
1,
2,
3
],
"loopArgs": [
null,
null
]
}
]
}
]
},
{
"componentName": "RootFooter",
"id": "node_ocl1djd9o44",
"docId": "docl1djd9o4",
"props": {},
"hidden": false,
"title": "",
"isLocked": false,
"condition": true,
"conditionGroup": ""
}
]
};
export default schema;

View File

@ -1,5 +1,234 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`loop schema loop key 1`] = `
<div
className="lce-page _css_pseudo_node_ocl1djd9o41"
style={Object {}}
>
<div
__componentName="RootHeader"
__id="node_ocl1djd9o42"
>
Component Not Found
</div>
<div
__componentName="RootContent"
__id="node_ocl1djd9o43"
contentBgColor="white"
contentMargin="20"
contentPadding="20"
>
<div
__componentName="Div"
__id="node_ocl1djd9o45"
__style__={Object {}}
behavior="NORMAL"
className="_css_pseudo_node_ocl1djd9o45"
customClassName=""
events={
Object {
"ignored": true,
}
}
fieldId="div_l1djdj1n"
useFieldIdAsDomId={false}
>
<div
__componentName="Div"
__id="node_ocl1djd9o46"
__style__={Object {}}
behavior="NORMAL"
className="_css_pseudo_node_ocl1djd9o46"
customClassName=""
events={
Object {
"ignored": true,
}
}
fieldId="div_l1djdj1o"
useFieldIdAsDomId={false}
>
Component Not Found
</div>
<div
__componentName="Div"
__id="node_ocl1djd9o46"
__style__={Object {}}
behavior="NORMAL"
className="_css_pseudo_node_ocl1djd9o46"
customClassName=""
events={
Object {
"ignored": true,
}
}
fieldId="div_l1djdj1o"
useFieldIdAsDomId={false}
>
Component Not Found
</div>
<div
__componentName="Div"
__id="node_ocl1djd9o46"
__style__={Object {}}
behavior="NORMAL"
className="_css_pseudo_node_ocl1djd9o46"
customClassName=""
events={
Object {
"ignored": true,
}
}
fieldId="div_l1djdj1o"
useFieldIdAsDomId={false}
>
Component Not Found
</div>
</div>
<div
__componentName="Div"
__id="node_ocl1djd9o45"
__style__={Object {}}
behavior="NORMAL"
className="_css_pseudo_node_ocl1djd9o45"
customClassName=""
events={
Object {
"ignored": true,
}
}
fieldId="div_l1djdj1n"
useFieldIdAsDomId={false}
>
<div
__componentName="Div"
__id="node_ocl1djd9o46"
__style__={Object {}}
behavior="NORMAL"
className="_css_pseudo_node_ocl1djd9o46"
customClassName=""
events={
Object {
"ignored": true,
}
}
fieldId="div_l1djdj1o"
useFieldIdAsDomId={false}
>
Component Not Found
</div>
<div
__componentName="Div"
__id="node_ocl1djd9o46"
__style__={Object {}}
behavior="NORMAL"
className="_css_pseudo_node_ocl1djd9o46"
customClassName=""
events={
Object {
"ignored": true,
}
}
fieldId="div_l1djdj1o"
useFieldIdAsDomId={false}
>
Component Not Found
</div>
<div
__componentName="Div"
__id="node_ocl1djd9o46"
__style__={Object {}}
behavior="NORMAL"
className="_css_pseudo_node_ocl1djd9o46"
customClassName=""
events={
Object {
"ignored": true,
}
}
fieldId="div_l1djdj1o"
useFieldIdAsDomId={false}
>
Component Not Found
</div>
</div>
<div
__componentName="Div"
__id="node_ocl1djd9o45"
__style__={Object {}}
behavior="NORMAL"
className="_css_pseudo_node_ocl1djd9o45"
customClassName=""
events={
Object {
"ignored": true,
}
}
fieldId="div_l1djdj1n"
useFieldIdAsDomId={false}
>
<div
__componentName="Div"
__id="node_ocl1djd9o46"
__style__={Object {}}
behavior="NORMAL"
className="_css_pseudo_node_ocl1djd9o46"
customClassName=""
events={
Object {
"ignored": true,
}
}
fieldId="div_l1djdj1o"
useFieldIdAsDomId={false}
>
Component Not Found
</div>
<div
__componentName="Div"
__id="node_ocl1djd9o46"
__style__={Object {}}
behavior="NORMAL"
className="_css_pseudo_node_ocl1djd9o46"
customClassName=""
events={
Object {
"ignored": true,
}
}
fieldId="div_l1djdj1o"
useFieldIdAsDomId={false}
>
Component Not Found
</div>
<div
__componentName="Div"
__id="node_ocl1djd9o46"
__style__={Object {}}
behavior="NORMAL"
className="_css_pseudo_node_ocl1djd9o46"
customClassName=""
events={
Object {
"ignored": true,
}
}
fieldId="div_l1djdj1o"
useFieldIdAsDomId={false}
>
Component Not Found
</div>
</div>
</div>
<div
__componentName="RootFooter"
__id="node_ocl1djd9o44"
>
Component Not Found
</div>
</div>
`;
exports[`notFountComponent not found snapshot 1`] = ` exports[`notFountComponent not found snapshot 1`] = `
<div <div
className="lce-page _css_pseudo_node_ockyigdqxl1" className="lce-page _css_pseudo_node_ockyigdqxl1"

View File

@ -3,6 +3,7 @@ import React from 'react';
import '../utils/react-env-init'; import '../utils/react-env-init';
import pageRendererFactory from '../../src/renderer/renderer'; import pageRendererFactory from '../../src/renderer/renderer';
import { sampleSchema } from '../mock/sample'; import { sampleSchema } from '../mock/sample';
import loopSchema from '../mock/loop';
describe('notFountComponent', () => { describe('notFountComponent', () => {
const Render = pageRendererFactory(); const Render = pageRendererFactory();
@ -20,4 +21,22 @@ describe('notFountComponent', () => {
let tree = component.toJSON(); let tree = component.toJSON();
expect(tree).toMatchSnapshot(); expect(tree).toMatchSnapshot();
}); });
});
describe('loop schema', () => {
it('loop key', () => {
const Render = pageRendererFactory();
const component = renderer.create(
// @ts-ignore
<Render
schema={loopSchema as any}
components={{}}
appHelper={{}}
/>,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
})
}) })

View File

@ -1,12 +0,0 @@
jest.mock('zen-logger', () => {
class Logger {
log() {}
error() {}
warn() {}
debug() {}
}
return {
__esModule: true,
default: Logger,
};
});

View File

@ -0,0 +1,29 @@
import React from 'react';
import { Box, Breadcrumb, Form, Select, Input, Button, Table, Pagination, Dialog } from '@alifd/next';
const Div = (props: any) => (<div {...props}>{props.children}</div>);
const Text = (props: any) => (<div>{props.content}</div>);
const SlotComponent = (props: any) => props.mobileSlot;
const components = {
Box,
Breadcrumb,
'Breadcrumb.Item': Breadcrumb.Item,
Form,
'Form.Item': Form.Item,
Select,
Input,
Button,
'Button.Group': Button.Group,
Table,
Pagination,
Dialog,
ErrorComponent: Select,
Div,
SlotComponent,
Text,
};
export default components;

View File

@ -0,0 +1,55 @@
import { PropChangeOptions } from "@ali/lowcode-designer";
import EventEmitter from "events";
export default class Node {
private emitter: EventEmitter;
schema: any = {
props: {},
};
hasLoop = false;
constructor(schema: any) {
this.emitter = new EventEmitter();
this.schema = schema;
}
mockLoop() {
this.hasLoop = true;
}
onChildrenChange(fn: any) {
this.emitter.on('onChildrenChange', fn);
return () => {
this.emitter.off('onChildrenChange', fn);
}
}
onPropChange(fn: any) {
this.emitter.on('onPropChange', fn);
return () => {
this.emitter.off('onPropChange', fn);
}
}
emitPropChange(val: PropChangeOptions) {
this.schema.props = {
...this.schema.props,
[val.key + '']: val.newValue,
}
this.emitter?.emit('onPropChange', val);
}
onVisibleChange(fn: any) {
this.emitter.on('onVisibleChange', fn);
return () => {
this.emitter.off('onVisibleChange', fn);
}
}
emitVisibleChange(val: boolean) {
this.emitter?.emit('onVisibleChange', val);
}
export() {
return this.schema;
}
}