From 1026763dc5a77d4395a1e86e5a0084ab4fb4230c Mon Sep 17 00:00:00 2001 From: liujuping Date: Fri, 8 Apr 2022 15:22:51 +0800 Subject: [PATCH] fix: Fix the rendering error caused by incorrect key value when configuring the loop --- packages/renderer-core/build.test.json | 6 + packages/renderer-core/jest.config.js | 4 - packages/renderer-core/package.json | 1 + packages/renderer-core/src/renderer/base.tsx | 2 +- .../test/hoc/__snapshots__/leaf.test.tsx.snap | 51 ++++ packages/renderer-core/test/hoc/leaf.test.tsx | 108 +++++++++ packages/renderer-core/test/mock/loop.ts | 221 +++++++++++++++++ .../renderer/__snapshots__/base.test.tsx.snap | 229 ++++++++++++++++++ .../renderer-core/test/renderer/base.test.tsx | 19 ++ packages/renderer-core/test/setup.ts | 12 - .../renderer-core/test/utils/components.tsx | 29 +++ packages/renderer-core/test/utils/node.ts | 55 +++++ 12 files changed, 720 insertions(+), 17 deletions(-) create mode 100644 packages/renderer-core/build.test.json create mode 100644 packages/renderer-core/test/hoc/__snapshots__/leaf.test.tsx.snap create mode 100644 packages/renderer-core/test/hoc/leaf.test.tsx create mode 100644 packages/renderer-core/test/mock/loop.ts delete mode 100644 packages/renderer-core/test/setup.ts create mode 100644 packages/renderer-core/test/utils/components.tsx create mode 100644 packages/renderer-core/test/utils/node.ts diff --git a/packages/renderer-core/build.test.json b/packages/renderer-core/build.test.json new file mode 100644 index 000000000..dcdc891e9 --- /dev/null +++ b/packages/renderer-core/build.test.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "build-plugin-component", + "@alilc/lowcode-test-mate/plugin/index.ts" + ] +} diff --git a/packages/renderer-core/jest.config.js b/packages/renderer-core/jest.config.js index af6e06512..c719abc5f 100644 --- a/packages/renderer-core/jest.config.js +++ b/packages/renderer-core/jest.config.js @@ -1,7 +1,3 @@ -const esModules = [ - '@alilc/lowcode-datasource-engine', -].join('|'); - module.exports = { transform: { '^.+\\.(ts|tsx)$': 'ts-jest', diff --git a/packages/renderer-core/package.json b/packages/renderer-core/package.json index f0f8e8924..72e56fb14 100644 --- a/packages/renderer-core/package.json +++ b/packages/renderer-core/package.json @@ -10,6 +10,7 @@ "es" ], "scripts": { + "test": "build-scripts test --config build.test.json", "build": "build-scripts build --skip-demo" }, "dependencies": { diff --git a/packages/renderer-core/src/renderer/base.tsx b/packages/renderer-core/src/renderer/base.tsx index 82d2884e1..286e0da43 100644 --- a/packages/renderer-core/src/renderer/base.tsx +++ b/packages/renderer-core/src/renderer/base.tsx @@ -565,7 +565,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { engine?.props?.onCompGetCtx(schema, scope); } 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; } diff --git a/packages/renderer-core/test/hoc/__snapshots__/leaf.test.tsx.snap b/packages/renderer-core/test/hoc/__snapshots__/leaf.test.tsx.snap new file mode 100644 index 000000000..591f768e2 --- /dev/null +++ b/packages/renderer-core/test/hoc/__snapshots__/leaf.test.tsx.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`leafWrapper base 1`] = ` +
+
+ content +
+
+`; + +exports[`leafWrapper change props 1`] = ` +
+
+ new content +
+
+`; diff --git a/packages/renderer-core/test/hoc/leaf.test.tsx b/packages/renderer-core/test/hoc/leaf.test.tsx new file mode 100644 index 000000000..83aff1f16 --- /dev/null +++ b/packages/renderer-core/test/hoc/leaf.test.tsx @@ -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 +
+ +
+ ); + + 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 +
+ +
+ ); +}) \ No newline at end of file diff --git a/packages/renderer-core/test/mock/loop.ts b/packages/renderer-core/test/mock/loop.ts new file mode 100644 index 000000000..60e74378d --- /dev/null +++ b/packages/renderer-core/test/mock/loop.ts @@ -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; diff --git a/packages/renderer-core/test/renderer/__snapshots__/base.test.tsx.snap b/packages/renderer-core/test/renderer/__snapshots__/base.test.tsx.snap index cdd695efb..565c0ce4e 100644 --- a/packages/renderer-core/test/renderer/__snapshots__/base.test.tsx.snap +++ b/packages/renderer-core/test/renderer/__snapshots__/base.test.tsx.snap @@ -1,5 +1,234 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`loop schema loop key 1`] = ` +
+
+ Component Not Found +
+
+
+
+ Component Not Found +
+
+ Component Not Found +
+
+ Component Not Found +
+
+
+
+ Component Not Found +
+
+ Component Not Found +
+
+ Component Not Found +
+
+
+
+ Component Not Found +
+
+ Component Not Found +
+
+ Component Not Found +
+
+
+
+ Component Not Found +
+
+`; + exports[`notFountComponent not found snapshot 1`] = `
{ const Render = pageRendererFactory(); @@ -20,4 +21,22 @@ describe('notFountComponent', () => { let tree = component.toJSON(); expect(tree).toMatchSnapshot(); }); +}); + +describe('loop schema', () => { + it('loop key', () => { + const Render = pageRendererFactory(); + + const component = renderer.create( + // @ts-ignore + , + ); + + let tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + }) }) \ No newline at end of file diff --git a/packages/renderer-core/test/setup.ts b/packages/renderer-core/test/setup.ts deleted file mode 100644 index a1b5e7328..000000000 --- a/packages/renderer-core/test/setup.ts +++ /dev/null @@ -1,12 +0,0 @@ -jest.mock('zen-logger', () => { - class Logger { - log() {} - error() {} - warn() {} - debug() {} - } - return { - __esModule: true, - default: Logger, - }; -}); \ No newline at end of file diff --git a/packages/renderer-core/test/utils/components.tsx b/packages/renderer-core/test/utils/components.tsx new file mode 100644 index 000000000..5cb557d4b --- /dev/null +++ b/packages/renderer-core/test/utils/components.tsx @@ -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) => (
{props.children}
); + +const Text = (props: any) => (
{props.content}
); + +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; \ No newline at end of file diff --git a/packages/renderer-core/test/utils/node.ts b/packages/renderer-core/test/utils/node.ts new file mode 100644 index 000000000..2756191c6 --- /dev/null +++ b/packages/renderer-core/test/utils/node.ts @@ -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; + } +} \ No newline at end of file