chore: delete useless codes

This commit is contained in:
1ncounter 2024-03-21 16:41:19 +08:00
parent 03f7c76284
commit fedbbca845
87 changed files with 3 additions and 12116 deletions

View File

@ -3,7 +3,8 @@
"workspaces": { "workspaces": {
"packages": [ "packages": [
"packages/*", "packages/*",
"runtime/*" "runtime/*",
"examples/*"
], ],
"nohoist": [ "nohoist": [
"**/css-modules-typescript-loader", "**/css-modules-typescript-loader",

View File

@ -1 +0,0 @@
module.exports = require('../../babel.config');

View File

@ -1,45 +0,0 @@
{
"entry": {
"AliLowCodeEngine": "../engine/src/index.ts",
"ReactSimulatorRenderer": "../react-simulator-renderer/src/index.ts"
},
"vendor": false,
"devServer": {
"liveReload": false,
"hot": false
},
"library": "[name]",
"publicPath": "/",
"externals": {
"react": "var window.React",
"react-dom": "var window.ReactDOM",
"prop-types": "var window.PropTypes",
"@alifd/next": "var window.Next",
"rax": "var window.Rax",
"@alilc/lowcode-engine": "var window.AliLowCodeEngine",
"@alilc/lowcode-engine-ext": "var window.AliLowCodeEngineExt",
"moment": "var moment",
"lodash": "var _"
},
"plugins": [
[
"build-plugin-react-app"
],
[
"build-plugin-fusion",
{
"themePackage": "@alifd/theme-lowcode-light",
"externalNext": "umd"
}
],
[
"build-plugin-moment-locales",
{
"locales": [
"zh-cn"
]
}
],
"./build.plugin.js"
]
}

View File

@ -1,25 +0,0 @@
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const fse = require('fs-extra');
// read from lerna
const lernaConfig = JSON.parse(fse.readFileSync('../../lerna.json', 'utf8'));
const { version } = lernaConfig;
module.exports = ({ context, onGetWebpackConfig }) => {
onGetWebpackConfig((config) => {
config.resolve.plugin('tsconfigpaths').use(TsconfigPathsPlugin, [
{
configFile: './tsconfig.json',
},
]);
config
.plugin('define')
.use(context.webpack.DefinePlugin, [{
VERSION_PLACEHOLDER: JSON.stringify(version),
}]);
config.plugins.delete('hot');
config.devServer.hot(false);
if (context.command === 'start') {
config.devtool('inline-source-map');
}
});
};

View File

@ -1,47 +0,0 @@
const fs = require('fs');
const { join } = require('path');
const esModules = [].join('|');
const pkgNames = fs.readdirSync(join('..')).filter(pkgName => !pkgName.startsWith('.'));
const jestConfig = {
// transform: {
// '^.+\\.[jt]sx?$': 'babel-jest',
// // '^.+\\.(ts|tsx)$': 'ts-jest',
// // '^.+\\.(js|jsx)$': 'babel-jest',
// },
// testMatch: ['**/node-children.test.ts'],
// testMatch: ['**/plugin-manager.test.ts'],
// testMatch: ['**/history/history.test.ts'],
// testMatch: ['**/document-model.test.ts'],
// testMatch: ['**/prop.test.ts'],
// testMatch: ['(/tests?/.*(test))\\.[jt]s$'],
transformIgnorePatterns: [
`/node_modules/(?!${esModules})/`,
],
setupFiles: ['./tests/fixtures/unhandled-rejection.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
collectCoverage: false,
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/icons/**',
'!src/locale/**',
'!src/builtin-simulator/utils/**',
'!src/plugin/sequencify.ts',
'!src/document/node/exclusive-group.ts',
'!src/document/node/props/value-to-source.ts',
'!src/builtin-simulator/live-editing/live-editing.ts',
'!src/designer/offset-observer.ts',
'!src/designer/clipboard.ts',
'!src/designer/scroller.ts',
'!src/builtin-simulator/host.ts',
'!**/node_modules/**',
'!**/vendor/**',
],
};
// 只对本仓库内的 pkg 做 mapping
jestConfig.moduleNameMapper = {};
jestConfig.moduleNameMapper[`^@alilc/lowcode\\-(${pkgNames.join('|')})$`] = '<rootDir>/../$1/src';
module.exports = jestConfig;

View File

@ -1,26 +0,0 @@
{
"name": "@alilc/lowcode-ignitor",
"version": "1.3.2",
"description": "点火器bootstrap lce project",
"main": "lib/index.js",
"private": true,
"files": [
"dist",
"es",
"lib"
],
"scripts": {
"start": "build-scripts start --disable-open --port 5555"
},
"license": "MIT",
"devDependencies": {
"@alib/build-scripts": "^0.1.18",
"fs-extra": "^10.0.0"
},
"repository": {
"type": "http",
"url": "https://github.com/alibaba/lowcode-engine/tree/main/packages/ignitor"
},
"bugs": "https://github.com/alibaba/lowcode-engine/issues",
"homepage": "https://github.com/alibaba/lowcode-engine/#readme"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1" />
<meta name="viewport" content="width=device-width" />
<title>LowCodeEngine Editor DEMO</title>
</head>
<body>
<h1>
This project only provides engine resource files. For usage, go for
<a href="https://github.com/alibaba/lowcode-demo" target="_blank">Lowcode Demo</a>
</h1>
<h2>
For local debugging of lowcode engine, please visit
<a href="https://lowcode-engine.cn/site/docs/participate/prepare" target="_blank">proxy documentation</a>
to get more information.
</h2>
</body>
</html>

View File

@ -1,9 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "lib"
},
"include": [
"./src/"
]
}

View File

@ -1,9 +0,0 @@
{
"plugins": [
"@alilc/build-plugin-lce",
"build-plugin-fusion",
["build-plugin-moment-locales", {
"locales": ["zh-cn"]
}]
]
}

View File

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

View File

@ -1,28 +0,0 @@
{
"entry": {
"react-renderer": "src/index"
},
"sourceMap": true,
"library": "AliLowCodeReactRenderer",
"libraryTarget": "umd",
"externals": {
"react": "var window.React",
"react-dom": "var window.ReactDOM",
"prop-types": "var window.PropTypes",
"@alifd/next": "var Next",
"moment": "var window.moment"
},
"polyfill": false,
"outputDir": "dist",
"vendor": false,
"ignoreHtmlTemplate": true,
"plugins": [
"build-plugin-react-app",
["build-plugin-fusion", {
"externalNext": "umd"
}],
["build-plugin-moment-locales", {
"locales": ["zh-cn"]
}]
]
}

View File

@ -1,37 +0,0 @@
---
title: 复杂组件
order: 2
---
````jsx
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
import ReactRenderer from '@alilc/lowcode-react-renderer';
import schema from './schemas/compose';
import components from './config/components/index';
import utils from './config/utils';
import constants from './config/constants';
class Demo extends PureComponent {
static displayName = 'renderer-demo';
render() {
return (
<div className="demo">
<ReactRenderer
key={schema.fileName}
schema={schema}
components={components}
appHelper={{
utils,
constants
}}
/>
</div>
);
}
}
ReactDOM.render((
<Demo />
), mountNode);
````

View File

@ -1,11 +0,0 @@
import React, { PureComponent } from 'react';
export default class AView extends PureComponent {
static displayName = 'A';
static version = '0.0.0';
render() {
return <a {...this.props} />;
}
}

View File

@ -1,11 +0,0 @@
import React, { PureComponent } from 'react';
export default class DivView extends PureComponent {
static displayName = 'Div';
static version = '0.0.0';
render() {
return <div {...this.props} />;
}
}

View File

@ -1,15 +0,0 @@
import React, { PureComponent } from 'react';
export default class ImageView extends PureComponent {
static displayName = 'Image';
static version = '0.0.0';
static defaultProps = {
src: '//img.alicdn.com/tfs/TB1knm4bqNj0u4jSZFyXXXgMVXa-240-240.png_100x100.jpg',
};
render() {
return <img {...this.props} />;
}
}

View File

@ -1,27 +0,0 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
export default class TextView extends PureComponent {
static displayName = 'Text';
static version = '0.0.0';
static propTypes = {
text: PropTypes.string,
};
render() {
const { text, ...restProps } = this.props;
let textNode = text;
//
try {
textNode = text.toString();
} catch (e) {
textNode = '';
}
if (window.__ctx && window.__ctx.canvasAppHelper) {
textNode = textNode || 'Text';
}
return <span {...restProps}>{textNode}</span>;
}
}

View File

@ -1,175 +0,0 @@
import Div from './Div';
import Text from './Text';
import A from './A';
import Image from './Image';
import {
Balloon,
Button,
Checkbox,
Dropdown,
Grid,
Menu,
Select,
Tab,
Table,
Radio,
Pagination,
Input,
Icon,
Switch,
Tree,
NumberPicker,
Collapse,
Range,
Dialog,
Overlay,
Search,
Loading,
MenuButton,
Badge,
Message,
Slider,
SplitButton,
Paragraph,
Nav,
Breadcrumb,
Step,
DatePicker,
TimePicker,
Rating,
Upload,
Tag,
Card,
Calendar,
Progress,
Cascader,
ConfigProvider,
Animate,
CascaderSelect,
Transfer,
TreeSelect,
Timeline,
VirtualList,
} from '@alifd/next';
const { Row, Col } = Grid;
const {
Item: MenuItem,
Group: MenuGroup,
SubMenu,
PopupItem: MenuPopupItem,
CheckboxItem: MenuCheckboxItem,
RadioItem: MenuRadioItem,
Divider: MenuDivider,
} = Menu;
const { Item: TabItem } = Tab;
const { Column: TableColumn, ColumnGroup: TableColumnGroup } = Table;
const { Group: ButtonGroup } = Button;
const { Group: RadioGroup } = Radio;
const { Node: TreeNode } = Tree;
const { Panel: CollapsePanel } = Collapse;
const { Tooltip } = Balloon;
const { AutoComplete: SelectAutoComplete, OptionGroup: SelectOptionGroup, Option: SelectOption } = Select;
const { Item: MenuButtonItem } = MenuButton;
const { Item: StepItem } = Step;
const { Item: NavItem, SubNav, PopupItem: NavPopItem, Group: NavGroup } = Nav;
const { Item: BreadcrumbItem } = Breadcrumb;
const { MonthPicker, RangePicker, YearPicker } = DatePicker;
const { Card: UploadCard, Dragger: UploadDragger, Selecter: UploadSelecter } = Upload;
const { Closeable: TagCloseable, Selectable: TagSelectable } = Tag;
const { Popup } = Overlay;
const { Node: TreeSelectNode } = TreeSelect;
const { Item: TimelineItem } = Timeline;
export default {
Div,
A,
Text,
Image,
Balloon,
Tooltip,
Button,
ButtonGroup,
Checkbox,
Row,
Col,
Select,
SelectAutoComplete,
SelectOptionGroup,
SelectOption,
Dropdown,
Menu,
MenuItem,
MenuGroup,
MenuDivider,
SubMenu,
MenuPopupItem,
MenuCheckboxItem,
MenuRadioItem,
MenuButton,
MenuButtonItem,
Loading,
Tab,
TabItem,
Table,
TableColumn,
TableColumnGroup,
Radio,
RadioGroup,
Pagination,
Input,
Icon,
Switch,
Tree,
TreeNode,
NumberPicker,
Collapse,
Dialog,
Overlay,
Popup,
CollapsePanel,
Range,
Search,
Badge,
Message,
Slider,
SplitButton,
Paragraph,
Nav,
NavItem,
NavPopItem,
NavGroup,
SubNav,
Breadcrumb,
BreadcrumbItem,
Rating,
Step,
StepItem,
DatePicker,
MonthPicker,
RangePicker,
YearPicker,
TimePicker,
Upload,
UploadCard,
UploadDragger,
UploadSelecter,
Tag,
TagCloseable,
TagSelectable,
Card,
Calendar,
Progress,
Cascader,
ConfigProvider,
Animate,
CascaderSelect,
Transfer,
TreeSelect,
TreeSelectNode,
Timeline,
TimelineItem,
VirtualList,
};

View File

@ -1,3 +0,0 @@
export default {
name: 'renderer-demo',
};

View File

@ -1,10 +0,0 @@
import { Message } from '@alifd/next';
import moment from 'moment';
export default {
Message,
moment,
test(msg) {
this.Message.notice(msg);
},
};

View File

@ -1,37 +0,0 @@
---
title: 数据源使用
order: 4
---
````jsx
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
import ReactRenderer from '@alilc/lowcode-react-renderer';
import schema from './schemas/dataSource';
import components from './config/components/index';
import utils from './config/utils';
import constants from './config/constants';
class Demo extends PureComponent {
static displayName = 'renderer-demo';
render() {
return (
<div className="demo">
<ReactRenderer
key={schema.fileName}
schema={schema}
components={components}
appHelper={{
utils,
constants
}}
/>
</div>
);
}
}
ReactDOM.render((
<Demo />
), mountNode);
````

View File

@ -1,42 +0,0 @@
---
title: 国际化
order: 5
---
````jsx
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
import ReactRenderer from '@alilc/lowcode-react-renderer';
import schema from './schemas/i18n';
import components from './config/components/index';
import utils from './config/utils';
import constants from './config/constants';
class Demo extends PureComponent {
static displayName = 'renderer-demo';
render() {
return (
<div className="demo">
<ReactRenderer
key={schema.fileName}
schema={schema}
components={components}
appHelper={{
utils,
constants
}}
locale="zh-CN"
messages={{
"hello": "你好",
"china": "中国"
}}
/>
</div>
);
}
}
ReactDOM.render((
<Demo />
), mountNode);
````

View File

@ -1,37 +0,0 @@
---
title: 列表
order: 1
---
````jsx
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
import ReactRenderer from '@alilc/lowcode-react-renderer';
import schema from './schemas/list';
import components from './config/components/index';
import utils from './config/utils';
import constants from './config/constants';
class Demo extends PureComponent {
static displayName = 'renderer-demo';
render() {
return (
<div className="demo">
<ReactRenderer
key={schema.fileName}
schema={schema}
components={components}
appHelper={{
utils,
constants
}}
/>
</div>
);
}
}
ReactDOM.render((
<Demo />
), mountNode);
````

View File

@ -1,61 +0,0 @@
/* eslint-disable */
export default {
componentName: 'Page',
fileName: 'compose',
props: {
},
children: [
{
"componentName": "Dropdown",
"props": {
"trigger": [
{
"componentName": "Button",
"props": {
"type": "primary"
},
"children": "确定"
}
],
"triggerType": "click"
},
"children": [
{
"componentName": "Menu",
"props": {
"style": {
"width": 200
}
},
"children": [
{ "componentName": "MenuItem", "props": {}, "children": "Option 1" },
{ "componentName": "MenuItem", "props": { "disabled": false }, "children": "option 2" },
{ "componentName": "MenuItem", "props": { "disabled": false }, "children": "option 3" }
]
}
]
},
{
"componentName": "Menu",
"props": {
"style": {
"width": 200
}
},
"children": [
{
"componentName": "MenuItem",
"props": {
},
"children": "Option 1"
},
{
"componentName": "MenuItem",
"props": {
},
"children": "Option 2"
}
]
}
]
};

View File

@ -1,37 +0,0 @@
export default {
componentName: 'Page',
fileName: 'dataSource',
props: {},
children: [{
componentName: 'Div',
props: {},
children: [{
componentName: 'Text',
props: {
text: '{{this.item.title}}',
},
}, {
componentName: 'Switch',
props: {
checkedChildren: '开',
unCheckedChildren: '关',
checked: '{{this.item.done}}',
},
}],
loop: '{{this.dataSourceMap.todos.data}}',
}],
dataSource: {
list: [{
id: 'todos',
isInit: true,
type: 'jsonp',
options: {
method: 'GET',
uri: 'https://mocks.alibaba-inc.com/mock/D8iUX7zB/todo_getAll',
},
dataHandler: function dataHandler(data) {
return data.data;
},
}],
},
};

View File

@ -1,20 +0,0 @@
export default {
componentName: 'Page',
fileName: 'i18n',
props: {},
children: [{
componentName: 'Div',
props: {},
children: [{
componentName: 'Text',
props: {
text: "{{this.i18n('hello')}}",
},
}, {
componentName: 'Text',
props: {
text: "{{this.i18n('china')}}",
},
}],
}],
};

View File

@ -1,225 +0,0 @@
/* eslint-disable */
export default {
componentName: 'Page',
fileName: 'tab_article',
props: {
style: {
paddingTop: 20,
paddingRight: 20,
paddingLeft: 20
}
},
children: [
{
componentName: 'Div',
props: {
style: {
marginTop: 5,
marginBottom: 15,
borderBottom: '1px solid rgba(244,244,244)'
}
},
children: [
{
componentName: 'Div',
props: {
style: {
marginBottom: 15
}
},
children: [
{
componentName: 'Text',
props: {
text: '{{this.item.title}}',
style: {
color: 'rgba(51,51,51)'
}
}
},
{
componentName: 'Text',
props: {
text: '{{this.item.datetime}}',
style: {
fontSize: '12px',
color: '#666',
float: 'right'
}
}
}
]
},
{
componentName: 'Div',
props: {
style: {
paddingBottom: 15,
fontSize: '13px',
color: '#666'
}
},
children: '{{this.item.description}}'
},
{
componentName: 'Div',
props: {
style: {
marginBottom: 15
}
},
children: [
{
componentName: 'Button',
props: {
type: 'normal',
style: {
marginRight: 5,
marginLeft: 5
},
size: 'small'
},
children: '{{this.item}}',
loop: '{{this.item.tags}}'
},
{
componentName: 'Div',
props: {
style: {
marginBottom: 15,
float: 'right'
}
},
children: [
{
componentName: 'Div',
props: {
style: {
display: 'inline-block',
marginRight: 5,
marginBottom: 15,
marginLeft: 5,
fontSize: 12,
color: '#666',
float: 'none'
}
},
children: '{{"点赞:"+this.item.star}}'
},
{
componentName: 'Div',
props: {
style: {
display: 'inline-block',
marginRight: 5,
marginBottom: 15,
marginLeft: 5,
fontSize: 12,
color: '#666',
float: 'none'
}
},
children: '{{"喜爱:"+this.item.like}}'
},
{
componentName: 'Div',
props: {
style: {
display: 'inline-block',
marginRight: 5,
marginBottom: 15,
marginLeft: 5,
fontSize: 12,
color: '#66',
float: 'none'
}
},
children: '{{"评论:"+this.item.comment}}'
}
]
}
]
}
],
loop: '{{this.state.dataSource}}'
},
{
componentName: 'Pagination',
props: {
shape: 'normal',
type: 'normal',
size: 'medium',
style: {
marginTop: 10,
marginBottom: 30,
textAlign: 'right'
},
onChange: function onChange(current, e) {
//页码发生改变时的回调函数
//@param {Number} current 改变后的页码数
//@param {Object} e 点击事件对象
this.page.reloadDataSource();
}
}
}
],
dataSource: {
dataHandler: function dataHandler(dataMap) {
const dataSource = [
{
title: '越夏越嗨皮-7月官方营销活动-技能提升方向',
description:
'商家通过V任务选择主播并达成合作费用按照商品链接计算一个商品为一个价格建议主播在一场直播里最多接60个商品并提供不少于两个小时的直播服务每个商品讲解时间不少于5分钟。 ',
tags: ['直播', '大促', '简介'],
datetime: '2017年12月12日 18:00',
star: Math.floor(Math.random() * 100) + 100,
like: Math.floor(Math.random() * 100) + 200,
comment: Math.floor(Math.random() * 100) + 100
},
{
title: '越夏越嗨皮-7月官方营销活动-技能提升方向',
description:
'商家通过V任务选择主播并达成合作费用按照商品链接计算一个商品为一个价格建议主播在一场直播里最多接60个商品并提供不少于两个小时的直播服务每个商品讲解时间不少于5分钟。 ',
tags: ['直播', '大促', '简介'],
datetime: '2017年12月12日 18:00',
star: Math.floor(Math.random() * 100) + 100,
like: Math.floor(Math.random() * 100) + 200,
comment: Math.floor(Math.random() * 100) + 100
},
{
title: '越夏越嗨皮-7月官方营销活动-技能提升方向',
description:
'商家通过V任务选择主播并达成合作费用按照商品链接计算一个商品为一个价格建议主播在一场直播里最多接60个商品并提供不少于两个小时的直播服务每个商品讲解时间不少于5分钟。 ',
tags: ['直播', '大促', '简介'],
datetime: '2017年12月12日 18:00',
star: Math.floor(Math.random() * 100) + 100,
like: Math.floor(Math.random() * 100) + 200,
comment: Math.floor(Math.random() * 100) + 100
},
{
title: '越夏越嗨皮-7月官方营销活动-技能提升方向',
description:
'商家通过V任务选择主播并达成合作费用按照商品链接计算一个商品为一个价格建议主播在一场直播里最多接60个商品并提供不少于两个小时的直播服务每个商品讲解时间不少于5分钟。 ',
tags: ['直播', '大促', '简介'],
datetime: '2017年12月12日 18:00',
star: Math.floor(Math.random() * 100) + 100,
like: Math.floor(Math.random() * 100) + 200,
comment: Math.floor(Math.random() * 100) + 100
},
{
title: '越夏越嗨皮-7月官方营销活动-技能提升方向',
description:
'商家通过V任务选择主播并达成合作费用按照商品链接计算一个商品为一个价格建议主播在一场直播里最多接60个商品并提供不少于两个小时的直播服务每个商品讲解时间不少于5分钟。 ',
tags: ['直播', '大促', '简介'],
datetime: '2017年12月12日 18:00',
star: Math.floor(Math.random() * 100) + 100,
like: Math.floor(Math.random() * 100) + 200,
comment: Math.floor(Math.random() * 100) + 100
}
];
return {
dataSource
};
}
}
};

View File

@ -1,367 +0,0 @@
/* eslint-disable */
export default {
componentName: 'Page',
fileName: 'filterTable',
props: {
style: {
paddingRight: 20,
paddingLeft: 20
}
},
children: [
{
componentName: 'Table',
props: {
hasBorder: false,
hasHeader: true,
dataSource: [
{
id: 1,
title: '2017秋冬新款背带裙复古格子连衣裙清新背心裙a字裙短裙子',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 2,
title: '2017秋冬新款 高质感特定纱线欧美宽松马海毛羊毛毛衣',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 3,
title: '日式天然玉米皮草编碗垫锅垫隔热垫茶垫加厚餐垫GD-29',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 4,
title: '2017秋冬新款 绑带腰封设计感超顺滑质感落肩铜氨丝连衣裙',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 5,
title: '日式糖果色陶瓷柄不锈钢餐具西餐牛扒刀叉勺子咖啡勺',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 6,
title: '日式和风深蓝素色文艺餐巾餐垫围裙锅垫隔热手套厨房桌布',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 7,
title: '日式雪点樱花手绘陶瓷餐具米饭碗拉面碗寿司盘子汤碗',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 8,
title: '川岛屋 釉下彩复古日式陶瓷盘子菜盘圆盘调味碟 米饭碗日式餐具',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
}
]
},
children: [
{
componentName: 'TableColumn',
props: {
dataIndex: 'title',
title: '问题描述',
resizable: false
}
},
{
componentName: 'TableColumn',
props: {
title: '问题分类',
dataIndex: 'type'
}
},
{
componentName: 'TableColumn',
props: {
title: '发布时间',
dataIndex: 'publishTime'
}
},
{
componentName: 'TableColumn',
props: {
title: '状态',
dataIndex: 'publishStatus',
cell: [
{
componentName: 'Button',
props: {
type: 'normal',
size: 'small',
component: 'div',
text: true,
ghost: false,
style: {
width: '30px',
fontSize: '12px',
color: '#666',
cursor: 'auto',
background: '#f7f8fa'
}
},
children: '已发布',
condition: false
},
{
componentName: 'Text',
props: {
text: '已发布',
style: {
paddingTop: 2,
paddingRight: 5,
paddingBottom: 2,
paddingLeft: 5,
fontSize: '12px',
color: '#666',
borderRadius: 3,
cursor: 'auto',
background: '#f7f8fa'
}
}
}
]
}
},
{
componentName: 'TableColumn',
props: {
title: '操作',
cell: [
{
componentName: 'Button',
props: {
type: 'normal',
component: 'a',
size: 'medium',
loading: false,
text: true,
style: {
paddingRight: 10,
paddingLeft: 10,
color: '#2077ff'
}
},
children: '解决'
},
{
componentName: 'Button',
props: {
type: 'normal',
component: 'a',
text: true,
style: {
paddingRight: 10,
paddingLeft: 10,
color: '#2077ff'
}
},
children: '详情'
},
{
componentName: 'Button',
props: {
type: 'normal',
text: true,
component: 'a',
style: {
paddingRight: 10,
paddingLeft: 10,
color: '#2077ff'
}
},
children: '分类'
}
]
}
}
],
loopArgs: ['', '']
},
{
componentName: 'Div',
props: {
style: {
textAlign: 'right'
}
},
children: [
{
componentName: 'Pagination',
props: {
shape: 'normal',
type: 'normal',
size: 'medium',
style: {
marginTop: 20
}
}
}
]
}
],
dataSource: {
dataHandler: function dataHandler(dataMap) {
let dataSource = [
{
id: 1,
title: '2017秋冬新款背带裙复古格子连衣裙清新背心裙a字裙短裙子',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 2,
title: '2017秋冬新款 高质感特定纱线欧美宽松马海毛羊毛毛衣',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 3,
title: '日式天然玉米皮草编碗垫锅垫隔热垫茶垫加厚餐垫GD-29',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 4,
title: '2017秋冬新款 绑带腰封设计感超顺滑质感落肩铜氨丝连衣裙',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 5,
title: '日式糖果色陶瓷柄不锈钢餐具西餐牛扒刀叉勺子咖啡勺',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 6,
title: '日式和风深蓝素色文艺餐巾餐垫围裙锅垫隔热手套厨房桌布',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 7,
title: '日式雪点樱花手绘陶瓷餐具米饭碗拉面碗寿司盘子汤碗',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
},
{
id: 8,
title: '川岛屋 釉下彩复古日式陶瓷盘子菜盘圆盘调味碟 米饭碗日式餐具',
url: 'https://item.taobao.com/item.htm?id=558559528377',
type: '清单',
publishTime: '17-04-28 20:29:20',
publishStatus: '已发布',
pushStatus: '已推送至订阅号',
operation: {
edit: true
}
}
];
return {
...dataMap,
dataSource
};
}
}
};

View File

@ -1,37 +0,0 @@
---
title: 表格
order: 1
---
````jsx
import React, { PureComponent } from 'react';
import ReactDOM from 'react-dom';
import ReactRenderer from '@alilc/lowcode-react-renderer';
import schema from './schemas/table';
import components from './config/components/index';
import utils from './config/utils';
import constants from './config/constants';
class Demo extends PureComponent {
static displayName = 'renderer-demo';
render() {
return (
<div className="demo">
<ReactRenderer
key={schema.fileName}
schema={schema}
components={components}
appHelper={{
utils,
constants
}}
/>
</div>
);
}
}
ReactDOM.render((
<Demo />
), mountNode);
````

View File

@ -1,32 +0,0 @@
const fs = require('fs');
const { join } = require('path');
const esModules = [].join('|');
const pkgNames = fs.readdirSync(join('..')).filter(pkgName => !pkgName.startsWith('.'));
const jestConfig = {
// transform: {
// '^.+\\.[jt]sx?$': 'babel-jest',
// // '^.+\\.(ts|tsx)$': 'ts-jest',
// // '^.+\\.(js|jsx)$': 'babel-jest',
// },
// testMatch: ['**/document/node/node.test.ts'],
// testMatch: ['**/designer/builtin-hotkey.test.ts'],
// testMatch: ['**/plugin/plugin-manager.test.ts'],
// testMatch: ['(/tests?/.*(test))\\.[jt]s$'],
transformIgnorePatterns: [
`/node_modules/(?!${esModules})/`,
],
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!**/node_modules/**',
],
};
// 只对本仓库内的 pkg 做 mapping
jestConfig.moduleNameMapper = {};
jestConfig.moduleNameMapper[`^@alilc/lowcode\\-(${pkgNames.join('|')})$`] = '<rootDir>/../$1/src';
module.exports = jestConfig;

View File

@ -1,47 +0,0 @@
{
"name": "@alilc/lowcode-react-renderer",
"version": "1.3.2",
"description": "react renderer for ali lowcode engine",
"main": "lib/index.js",
"module": "es/index.js",
"files": [
"lib",
"es",
"dist"
],
"scripts": {
"test": "build-scripts test --config build.test.json",
"start": "build-scripts start",
"build": "build-scripts build",
"build:umd": "NODE_OPTIONS=--max_old_space_size=8192 build-scripts build --config build.umd.json"
},
"keywords": [
"lowcode",
"engine",
"react"
],
"dependencies": {
"@alifd/next": "^1.21.16",
"@alilc/lowcode-renderer-core": "1.3.2"
},
"devDependencies": {
"@alib/build-scripts": "^0.1.18",
"@alifd/next": "^1.19.17",
"build-plugin-fusion": "^0.1.0",
"build-plugin-moment-locales": "^0.1.0",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-test-renderer": "^16"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"repository": {
"type": "http",
"url": "https://github.com/alibaba/lowcode-engine/tree/main/packages/react-renderer"
},
"homepage": "https://github.com/alibaba/lowcode-engine/#readme",
"gitHead": "2669f179e6f899d395ce1942d0fe04f9c5ed48a6",
"bugs": "https://github.com/alibaba/lowcode-engine/issues"
}

View File

@ -1,66 +0,0 @@
import React, { Component, PureComponent, createElement, createContext, forwardRef, ReactInstance, ContextType } from 'react';
import ReactDOM from 'react-dom';
import {
adapter,
pageRendererFactory,
componentRendererFactory,
blockRendererFactory,
addonRendererFactory,
tempRendererFactory,
rendererFactory,
types,
} from '@alilc/lowcode-renderer-core';
import ConfigProvider from '@alifd/next/lib/config-provider';
window.React = React;
(window as any).ReactDom = ReactDOM;
adapter.setRuntime({
Component,
PureComponent,
createContext,
createElement,
forwardRef,
findDOMNode: ReactDOM.findDOMNode,
});
adapter.setRenderers({
PageRenderer: pageRendererFactory(),
ComponentRenderer: componentRendererFactory(),
BlockRenderer: blockRendererFactory(),
AddonRenderer: addonRendererFactory(),
TempRenderer: tempRendererFactory(),
DivRenderer: blockRendererFactory(),
});
adapter.setConfigProvider(ConfigProvider);
function factory(): types.IRenderComponent {
const Renderer = rendererFactory();
return class ReactRenderer extends Renderer implements Component {
readonly props: types.IRendererProps;
context: ContextType<any>;
setState: (
state: types.IRendererState,
callback?: () => void,
) => void;
forceUpdate: (callback?: () => void) => void;
refs: {
[key: string]: ReactInstance;
};
constructor(props: types.IRendererProps, context: ContextType<any>) {
super(props, context);
}
isValidComponent(obj: any) {
return obj?.prototype?.isReactComponent || obj?.prototype instanceof Component;
}
};
}
export default factory();

View File

@ -1,865 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`React Renderer render basic case 1`] = `
<div
className="lce-page lce-test"
style={
Object {
"padding": "0 5px 0 5px",
}
}
>
<div
__id="node_dockcy8n9xed"
className="next-box"
style={
Object {
"backgroundColor": "rgba(31,56,88,0.1)",
"flexDirection": "column",
"flexWrap": "nowrap",
"msFlexDirection": "column",
"msFlexWrap": "none",
"padding": "12px 12px 12px 12px",
}
}
>
<div
__id="node_dockcy8n9xee"
className="next-box"
style={
Object {
"backgroundColor": "#ffffff",
"flexDirection": "column",
"flexWrap": "nowrap",
"msFlexDirection": "column",
"msFlexWrap": "none",
"padding": "12px 12px 12px 12px",
}
}
>
<nav
__id="node_dockcy8n9xef"
aria-label="Breadcrumb"
style={
Object {
"position": "relative",
}
}
>
<ul
className="next-breadcrumb"
>
<li
className="next-breadcrumb-item"
dir={null}
>
<span
__id="node_dockcy8n9xeg"
className="next-breadcrumb-text"
>
首页
</span>
<span
className="next-breadcrumb-separator"
>
<i
className="next-icon next-icon-arrow-right next-medium next-breadcrumb-icon-sep"
style={Object {}}
/>
</span>
</li>
<li
className="next-breadcrumb-item"
dir={null}
>
<span
__id="node_dockcy8n9xei"
className="next-breadcrumb-text"
>
品质中台
</span>
<span
className="next-breadcrumb-separator"
>
<i
className="next-icon next-icon-arrow-right next-medium next-breadcrumb-icon-sep"
style={Object {}}
/>
</span>
</li>
<li
className="next-breadcrumb-item"
dir={null}
>
<span
__id="node_dockcy8n9xek"
className="next-breadcrumb-text"
>
商家品质页面管理
</span>
<span
className="next-breadcrumb-separator"
>
<i
className="next-icon next-icon-arrow-right next-medium next-breadcrumb-icon-sep"
style={Object {}}
/>
</span>
</li>
<li
className="next-breadcrumb-item"
dir={null}
>
<span
__id="node_dockcy8n9xem"
aria-current="page"
className="next-breadcrumb-text activated"
>
质检知识条配置
</span>
</li>
</ul>
</nav>
</div>
<div
__id="node_dockcy8n9xeo"
className="next-box"
style={
Object {
"backgroundColor": "#ffffff",
"flexDirection": "column",
"flexWrap": "nowrap",
"marginTop": "12px",
"msFlexDirection": "column",
"msFlexWrap": "none",
}
}
>
<form
__events={Array []}
__id="node_dockcy8n9xep"
className="next-form next-inline next-medium"
onSubmit={[Function]}
role="grid"
style={
Object {
"marginLeft": "12px",
"marginRight": "12px",
"marginTop": "12px",
}
}
>
<div
__id="node_dockcy8n9xeq"
className="next-form-item next-left next-medium"
style={
Object {
"marginBottom": "0",
}
}
>
<div
className="next-form-item-label"
>
<label>
类目名:
</label>
</div>
<div
className="next-form-item-control"
>
<span
aria-haspopup={true}
className="next-select next-select-trigger next-select-single next-medium next-inactive next-no-search"
onClick={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
style={
Object {
"width": "150px",
}
}
>
<span
className="next-input next-medium next-select-inner"
>
<span
className="next-select-values next-input-text-field"
>
<span
className="next-select-trigger-search"
>
<input
__id="node_dockcy8n9xer"
autoComplete="off"
disabled={false}
height="100%"
maxLength={null}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="请选择"
readOnly={true}
role="combobox"
size="1"
tabIndex={0}
value=""
/>
<span
aria-hidden={true}
>
<span>
请选择
</span>
<span>
 
</span>
</span>
</span>
</span>
<span
className="next-input-control"
>
<span
aria-hidden={true}
className="next-select-arrow"
onClick={[Function]}
>
<i
className="next-icon next-icon-arrow-down next-medium next-select-symbol-fold"
style={Object {}}
/>
</span>
</span>
</span>
<span
aria-live="polite"
className="next-sr-only"
>
</span>
</span>
</div>
</div>
<div
__id="node_dockcy8n9xes"
className="next-form-item next-left next-medium"
style={
Object {
"marginBottom": "0",
}
}
>
<div
className="next-form-item-label"
>
<label>
项目类型:
</label>
</div>
<div
className="next-form-item-control"
>
<span
aria-haspopup={true}
className="next-select next-select-trigger next-select-single next-medium next-inactive next-no-search"
onClick={[Function]}
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
style={
Object {
"width": "200px",
}
}
>
<span
className="next-input next-medium next-select-inner"
>
<span
className="next-select-values next-input-text-field"
>
<span
className="next-select-trigger-search"
>
<input
__id="node_dockcy8n9xet"
autoComplete="off"
disabled={false}
height="100%"
maxLength={null}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
placeholder="请选择"
readOnly={true}
role="combobox"
size="1"
tabIndex={0}
value=""
/>
<span
aria-hidden={true}
>
<span>
请选择
</span>
<span>
 
</span>
</span>
</span>
</span>
<span
className="next-input-control"
>
<span
aria-hidden={true}
className="next-select-arrow"
onClick={[Function]}
>
<i
className="next-icon next-icon-arrow-down next-medium next-select-symbol-fold"
style={Object {}}
/>
</span>
</span>
</span>
<span
aria-live="polite"
className="next-sr-only"
>
</span>
</span>
</div>
</div>
<div
__id="node_dockcy8n9xeu"
className="next-form-item next-left next-medium"
style={
Object {
"marginBottom": "0",
}
}
>
<div
className="next-form-item-label"
>
<label>
项目 ID
</label>
</div>
<div
className="next-form-item-control"
>
<span
className="next-input next-medium"
style={
Object {
"width": "200px",
}
}
>
<input
__id="node_dockcy8n9xev"
autoComplete="off"
disabled={false}
height="100%"
maxLength={null}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
readOnly={false}
value=""
/>
</span>
</div>
</div>
<div
__id="node_dockcy8n9xew"
className="next-btn-group"
>
<button
__id="node_dockcy8n9xex"
className="next-btn next-medium next-btn-primary"
disabled={false}
onClick={[Function]}
onMouseUp={[Function]}
style={
Object {
"margin": "0 5px 0 5px",
}
}
type="submit"
>
<span
className="next-btn-helper"
>
搜索
</span>
</button>
<button
__id="node_dockcy8n9xe10"
className="next-btn next-medium next-btn-normal"
disabled={false}
onClick={[Function]}
onMouseUp={[Function]}
style={
Object {
"margin": "0 5px 0 5px",
}
}
type="reset"
>
<span
className="next-btn-helper"
>
清空
</span>
</button>
</div>
</form>
</div>
<div
__id="node_dockcy8n9xe1f"
className="next-box"
style={
Object {
"backgroundColor": "#ffffff",
"display": "flex",
"flexDirection": "row",
"flexWrap": "nowrap",
"justifyContent": "flex-end",
"msFlexDirection": "column",
"msFlexWrap": "none",
"paddingBottom": "24px",
}
}
>
<button
__events={
Array [
Object {
"name": "onClick",
"relatedEventName": "onClick",
"type": "componentEvent",
},
]
}
__id="node_dockd5nrh9p4"
className="next-btn next-medium next-btn-primary"
disabled={false}
onClick={[Function]}
onMouseUp={[Function]}
style={Object {}}
type="button"
>
<span
className="next-btn-helper"
>
新建配置
</span>
</button>
</div>
<div
__id="node_dockd5nrh9p5"
className="next-box"
style={
Object {
"flexDirection": "column",
"flexWrap": "nowrap",
"msFlexDirection": "column",
"msFlexWrap": "none",
}
}
>
<div
__id="node_dockjielosj1"
actionBar={
Array [
Object {
"title": "新增",
"type": "primary",
},
Object {
"title": "编辑",
},
]
}
actionColumn={
Array [
Object {
"callback": [Function],
"device": Array [
"desktop",
],
"title": "编辑",
},
Object {
"callback": [Function],
"mode": "EDIT",
"title": "保存",
},
]
}
actionFixed="right"
actionHidden={false}
actionTitle="操作"
actionType="link"
actionWidth={180}
className="next-table next-table-medium"
data={
Array [
Object {
"age": 15000,
"email": "aaa@abc.com",
"id": "1",
"name": "王小",
},
Object {
"age": 25000,
"email": "bbb@abc.com",
"id": "2",
"name": "王中",
},
Object {
"age": 35000,
"email": "ccc@abc.com",
"id": "3",
"name": "王大",
},
]
}
maxWebShownActionCount={2}
showActionBar={true}
showMiniPager={true}
style={Object {}}
>
<table
role="table"
style={
Object {
"width": undefined,
}
}
>
<colgroup>
<col
style={
Object {
"width": 200,
}
}
/>
<col
style={
Object {
"width": 200,
}
}
/>
<col
style={
Object {
"width": 200,
}
}
/>
</colgroup>
<thead
className="next-table-header"
>
<tr>
<th
className="next-table-cell next-table-header-node"
dataKey="name"
editType="text"
role="gridcell"
rowSpan={1}
style={
Object {
"textAlign": "center",
}
}
>
<div
className="next-table-cell-wrapper"
data-next-table-col={0}
>
姓名
</div>
</th>
<th
className="next-table-cell next-table-header-node"
dataKey="age"
role="gridcell"
rowSpan={1}
style={
Object {
"textAlign": "center",
}
}
>
<div
className="next-table-cell-wrapper"
data-next-table-col={1}
>
年龄
</div>
</th>
<th
className="next-table-cell next-table-header-node"
dataKey="email"
role="gridcell"
rowSpan={1}
style={
Object {
"textAlign": "center",
}
}
>
<div
className="next-table-cell-wrapper"
data-next-table-col={2}
>
邮箱
</div>
</th>
</tr>
</thead>
<tbody
className="next-table-body"
>
<tr>
<td
colSpan={3}
>
<div
className="next-table-empty"
style={
Object {
"left": 0,
"overflow": "hidden",
"position": "sticky",
"width": -1,
}
}
>
没有数据
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div
__id="node_dockd5nrh9pg"
className="next-box"
style={
Object {
"display": "flex",
"flexDirection": "row",
"flexWrap": "nowrap",
"justifyContent": "flex-end",
"msFlexDirection": "column",
"msFlexWrap": "none",
}
}
>
<div
__id="node_dockd5nrh9pf"
className="next-pagination next-medium next-normal"
style={Object {}}
>
<div
className="next-pagination-pages"
>
<button
aria-label="上一页当前第1页"
className="next-btn next-medium next-btn-normal next-pagination-item next-prev"
disabled={true}
onClick={[Function]}
onMouseUp={[Function]}
type="button"
>
<i
className="next-icon next-icon-arrow-left next-xs next-btn-icon next-icon-first next-pagination-icon-prev"
style={Object {}}
/>
<span
className="next-btn-helper"
>
上一页
</span>
</button>
<div
className="next-pagination-list"
>
<button
aria-label="第1页共10页"
className="next-btn next-medium next-btn-normal next-pagination-item next-current"
disabled={false}
onClick={[Function]}
onMouseUp={[Function]}
type="button"
>
<span
className="next-btn-helper"
>
1
</span>
</button>
<button
aria-label="第2页共10页"
className="next-btn next-medium next-btn-normal next-pagination-item"
disabled={false}
onClick={[Function]}
onMouseUp={[Function]}
type="button"
>
<span
className="next-btn-helper"
>
2
</span>
</button>
<button
aria-label="第3页共10页"
className="next-btn next-medium next-btn-normal next-pagination-item"
disabled={false}
onClick={[Function]}
onMouseUp={[Function]}
type="button"
>
<span
className="next-btn-helper"
>
3
</span>
</button>
<button
aria-label="第4页共10页"
className="next-btn next-medium next-btn-normal next-pagination-item"
disabled={false}
onClick={[Function]}
onMouseUp={[Function]}
type="button"
>
<span
className="next-btn-helper"
>
4
</span>
</button>
<i
className="next-icon next-icon-ellipsis next-medium next-pagination-ellipsis next-pagination-icon-ellipsis"
style={Object {}}
/>
<button
aria-label="第10页共10页"
className="next-btn next-medium next-btn-normal next-pagination-item"
disabled={false}
onClick={[Function]}
onMouseUp={[Function]}
type="button"
>
<span
className="next-btn-helper"
>
10
</span>
</button>
</div>
<button
aria-label="下一页当前第1页"
className="next-btn next-medium next-btn-normal next-pagination-item next-next"
disabled={false}
onClick={[Function]}
onMouseUp={[Function]}
type="button"
>
<span
className="next-btn-helper"
>
下一页
</span>
<i
className="next-icon next-icon-arrow-right next-xs next-btn-icon next-icon-last next-pagination-icon-next"
style={Object {}}
/>
</button>
<span
className="next-pagination-display"
>
<em>
1
</em>
/
10
</span>
<span
className="next-pagination-jump-text"
>
到第
</span>
<span
className="next-input next-medium next-pagination-jump-input"
>
<input
aria-label="请输入跳转到第几页"
autoComplete="off"
disabled={false}
height="100%"
maxLength={null}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
readOnly={false}
value=""
/>
</span>
<span
className="next-pagination-jump-text"
>
</span>
<button
className="next-btn next-medium next-btn-normal next-pagination-jump-go"
disabled={false}
onClick={[Function]}
onMouseUp={[Function]}
type="button"
>
<span
className="next-btn-helper"
>
确定
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div
__id="node_dockd5nrh9pr"
name="error"
>
Component Not Found
</div>
</div>
`;

View File

@ -1,567 +0,0 @@
export default {
componentName: 'Page',
id: 'node_dockcviv8fo1',
props: {
ref: 'outterView',
autoLoading: true,
style: {
padding: '0 5px 0 5px',
},
},
fileName: 'test',
dataSource: {
list: [],
},
state: {
text: 'outter',
isShowDialog: false,
},
css: 'body {font-size: 12px;} .botton{width:100px;color:#ff00ff}',
lifeCycles: {
componentDidMount: {
type: 'JSFunction',
value: "function() {\n console.log('did mount');\n }",
},
componentWillUnmount: {
type: 'JSFunction',
value: "function() {\n console.log('will umount');\n }",
},
},
methods: {
testFunc: {
type: 'JSFunction',
value: "function() {\n console.log('test func');\n }",
},
onClick: {
type: 'JSFunction',
value: 'function() {\n this.setState({\n isShowDialog: true\n })\n }',
},
closeDialog: {
type: 'JSFunction',
value: 'function() {\n this.setState({\n isShowDialog: false\n })\n }',
},
},
children: [
{
componentName: 'Box',
id: 'node_dockcy8n9xed',
props: {
style: {
backgroundColor: 'rgba(31,56,88,0.1)',
padding: '12px 12px 12px 12px',
},
},
children: [
{
componentName: 'Box',
id: 'node_dockcy8n9xee',
props: {
style: {
padding: '12px 12px 12px 12px',
backgroundColor: '#ffffff',
},
},
children: [
{
componentName: 'Breadcrumb',
id: 'node_dockcy8n9xef',
props: {
prefix: 'next-',
maxNode: 100,
component: 'nav',
},
children: [
{
componentName: 'Breadcrumb.Item',
id: 'node_dockcy8n9xeg',
props: {
prefix: 'next-',
children: '首页',
},
},
{
componentName: 'Breadcrumb.Item',
id: 'node_dockcy8n9xei',
props: {
prefix: 'next-',
children: '品质中台',
},
},
{
componentName: 'Breadcrumb.Item',
id: 'node_dockcy8n9xek',
props: {
prefix: 'next-',
children: '商家品质页面管理',
},
},
{
componentName: 'Breadcrumb.Item',
id: 'node_dockcy8n9xem',
props: {
prefix: 'next-',
children: '质检知识条配置',
},
},
],
},
],
},
{
componentName: 'Box',
id: 'node_dockcy8n9xeo',
props: {
style: {
marginTop: '12px',
backgroundColor: '#ffffff',
},
},
children: [
{
componentName: 'Form',
id: 'node_dockcy8n9xep',
props: {
inline: true,
style: {
marginTop: '12px',
marginRight: '12px',
marginLeft: '12px',
},
__events: [],
},
children: [
{
componentName: 'Form.Item',
id: 'node_dockcy8n9xeq',
props: {
style: {
marginBottom: '0',
},
label: '类目名:',
},
children: [
{
componentName: 'Select',
id: 'node_dockcy8n9xer',
props: {
mode: 'single',
hasArrow: true,
cacheValue: true,
style: {
width: '150px',
},
},
},
],
},
{
componentName: 'Form.Item',
id: 'node_dockcy8n9xes',
props: {
style: {
marginBottom: '0',
},
label: '项目类型:',
},
children: [
{
componentName: 'Select',
id: 'node_dockcy8n9xet',
props: {
mode: 'single',
hasArrow: true,
cacheValue: true,
style: {
width: '200px',
},
},
},
],
},
{
componentName: 'Form.Item',
id: 'node_dockcy8n9xeu',
props: {
style: {
marginBottom: '0',
},
label: '项目 ID',
},
children: [
{
componentName: 'Input',
id: 'node_dockcy8n9xev',
props: {
hasBorder: true,
size: 'medium',
autoComplete: 'off',
style: {
width: '200px',
},
},
},
],
},
{
componentName: 'Button.Group',
id: 'node_dockcy8n9xew',
props: {},
children: [
{
componentName: 'Button',
id: 'node_dockcy8n9xex',
props: {
type: 'primary',
style: {
margin: '0 5px 0 5px',
},
htmlType: 'submit',
children: '搜索',
},
},
{
componentName: 'Button',
id: 'node_dockcy8n9xe10',
props: {
type: 'normal',
style: {
margin: '0 5px 0 5px',
},
htmlType: 'reset',
children: '清空',
},
},
],
},
],
},
],
},
{
componentName: 'Box',
id: 'node_dockcy8n9xe1f',
props: {
style: {
backgroundColor: '#ffffff',
paddingBottom: '24px',
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
},
},
children: [
{
componentName: 'Button',
id: 'node_dockd5nrh9p4',
props: {
type: 'primary',
size: 'medium',
htmlType: 'button',
component: 'button',
children: '新建配置',
style: {},
__events: [
{
type: 'componentEvent',
name: 'onClick',
relatedEventName: 'onClick',
},
],
onClick: {
type: 'JSFunction',
value: 'function(){ this.onClick() }',
},
},
},
],
},
{
componentName: 'Box',
id: 'node_dockd5nrh9p5',
props: {},
children: [
{
componentName: 'Table',
id: 'node_dockjielosj1',
props: {
showMiniPager: true,
showActionBar: true,
actionBar: [
{
title: '新增',
type: 'primary',
},
{
title: '编辑',
},
],
columns: [
{
dataKey: 'name',
width: 200,
align: 'center',
title: '姓名',
editType: 'text',
},
{
dataKey: 'age',
width: 200,
align: 'center',
title: '年龄',
},
{
dataKey: 'email',
width: 200,
align: 'center',
title: '邮箱',
},
],
data: [
{
name: '王小',
id: '1',
age: 15000,
email: 'aaa@abc.com',
},
{
name: '王中',
id: '2',
age: 25000,
email: 'bbb@abc.com',
},
{
name: '王大',
id: '3',
age: 35000,
email: 'ccc@abc.com',
},
],
actionTitle: '操作',
actionWidth: 180,
actionType: 'link',
actionFixed: 'right',
actionHidden: false,
maxWebShownActionCount: 2,
actionColumn: [
{
title: '编辑',
callback: {
type: 'JSFunction',
value: '(rowData, action, table) => {\n return table.editRow(rowData).then((row) => {\n console.log(row);\n });\n }',
},
device: [
'desktop',
],
},
{
title: '保存',
callback: {
type: 'JSFunction',
value: '(rowData, action, table) => { \nreturn table.saveRow(rowData).then((row) => { \nconsole.log(row); \n}); \n}',
},
mode: 'EDIT',
},
],
},
},
{
componentName: 'Box',
id: 'node_dockd5nrh9pg',
props: {
style: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
},
},
children: [
{
componentName: 'Pagination',
id: 'node_dockd5nrh9pf',
props: {
prefix: 'next-',
type: 'normal',
shape: 'normal',
size: 'medium',
defaultCurrent: 1,
total: 100,
pageShowCount: 5,
pageSize: 10,
pageSizePosition: 'start',
showJump: true,
style: {},
},
},
],
},
],
},
],
},
{
componentName: 'Dialog',
id: 'node_dockcy8n9xe1h',
props: {
prefix: 'next-',
footerAlign: 'right',
footerActions: [
'ok',
'cancel',
],
closeable: 'esc,close',
hasMask: true,
align: 'cc cc',
minMargin: 40,
visible: {
type: 'JSExpression',
value: 'this.state.isShowDialog',
},
title: '标题',
events: [],
__events: [
{
type: 'componentEvent',
name: 'onCancel',
relatedEventName: 'closeDialog',
},
{
type: 'componentEvent',
name: 'onClose',
relatedEventName: 'closeDialog',
},
{
type: 'componentEvent',
name: 'onOk',
relatedEventName: 'testFunc',
},
],
onCancel: {
type: 'JSFunction',
value: 'function(){ this.closeDialog() }',
},
onClose: {
type: 'JSFunction',
value: 'function(){ this.closeDialog() }',
},
onOk: {
type: 'JSFunction',
value: 'function(){ this.testFunc() }',
},
},
children: [
{
componentName: 'Form',
id: 'node_dockd5nrh9pi',
props: {
inline: false,
labelAlign: 'top',
labelTextAlign: 'right',
size: 'medium',
},
children: [
{
componentName: 'Form.Item',
id: 'node_dockd5nrh9pj',
props: {
style: {
marginBottom: '0',
minWidth: '200px',
minHeight: '28px',
},
label: '商品类目',
},
children: [
{
componentName: 'Select',
id: 'node_dockd5nrh9pk',
props: {
mode: 'single',
hasArrow: true,
cacheValue: true,
},
},
],
},
{
componentName: 'Form.Item',
id: 'node_dockd5nrh9pl',
props: {
style: {
marginBottom: '0',
minWidth: '200px',
minHeight: '28px',
},
label: '商品类目',
},
children: [
{
componentName: 'Select',
id: 'node_dockd5nrh9pm',
props: {
mode: 'single',
hasArrow: true,
cacheValue: true,
},
},
],
},
{
componentName: 'Form.Item',
id: 'node_dockd5nrh9pn',
props: {
style: {
marginBottom: '0',
minWidth: '200px',
minHeight: '28px',
},
label: '商品类目',
asterisk: true,
},
children: [
{
componentName: 'Select',
id: 'node_dockd5nrh9po',
props: {
mode: 'single',
hasArrow: true,
cacheValue: true,
},
},
],
},
{
componentName: 'Form.Item',
id: 'node_dockd5nrh9pp',
props: {
style: {
marginBottom: '0',
minWidth: '200px',
minHeight: '28px',
},
label: '商品类目',
},
children: [
{
componentName: 'Input',
id: 'node_dockd5nrh9pr',
props: {
hasBorder: true,
size: 'medium',
autoComplete: 'off',
},
},
],
},
],
},
],
},
{
componentName: 'ErrorComponent',
id: 'node_dockd5nrh9pr',
props: {
name: 'error',
},
},
],
};

View File

@ -1,31 +0,0 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { Box, Breadcrumb, Form, Select, Input, Button, Table, Pagination, Dialog } from '@alifd/next';
import ReactRenderer from '../src';
import schema from './fixtures/schema/basic';
describe('React Renderer', () => {
it('render basic case', () => {
const components = {
Box,
Breadcrumb,
'Breadcrumb.Item': Breadcrumb.Item,
Form,
'Form.Item': Form.Item,
Select,
Input,
Button,
'Button.Group': Button.Group,
Table,
Pagination,
Dialog,
};
const content = (
<ReactRenderer
schema={schema}
components={components}
/>);
const tree = renderer.create(content).toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@ -1,9 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "lib"
},
"include": [
"./src/"
]
}

View File

@ -1,6 +0,0 @@
{
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
}

View File

@ -1 +0,0 @@
module.exports = require('../../babel.config');

View File

@ -1,10 +0,0 @@
{
"plugins": [
[
"@alilc/build-plugin-lce",
{
"babelPlugins": ["@babel/plugin-transform-typescript"]
}
]
]
}

View File

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

View File

@ -1,38 +0,0 @@
const fs = require('fs');
const { join } = require('path');
const esModules = [].join('|');
const pkgNames = fs.readdirSync(join('..')).filter(pkgName => !pkgName.startsWith('.'));
const jestConfig = {
// transform: {
// // '^.+\\.[jt]sx?$': 'babel-jest',
// '^.+\\.(ts|tsx)$': 'ts-jest',
// // '^.+\\.(js|jsx)$': 'babel-jest',
// },
// testMatch: ['(/tests?/.*(test))\\.[jt]s$'],
// testMatch: ['**/*/base.test.tsx'],
// testMatch: ['**/utils/common.test.ts'],
// testMatch: ['**/*/leaf.test.tsx'],
// testMatch: ['**/*/is-use-loop.test.ts'],
transformIgnorePatterns: [
`/node_modules/(?!${esModules})/`,
],
setupFiles: [
'./tests/fixtures/unhandled-rejection.ts',
'./tests/setup.ts',
],
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
collectCoverage: true,
collectCoverageFrom: [
'src/**/*.ts',
'src/**/*.tsx',
'!src/utils/logger.ts',
'!src/types/index.ts',
],
};
// 只对本仓库内的 pkg 做 mapping
jestConfig.moduleNameMapper = {};
jestConfig.moduleNameMapper[`^@alilc/lowcode\\-(${pkgNames.join('|')})$`] = '<rootDir>/../$1/src';
module.exports = jestConfig;

View File

@ -1,61 +0,0 @@
{
"name": "@alilc/lowcode-renderer-core",
"version": "1.3.2",
"description": "renderer core",
"license": "MIT",
"main": "lib/index.js",
"module": "es/index.js",
"files": [
"lib",
"es"
],
"scripts": {
"build": "build-scripts build",
"test": "build-scripts test --config build.test.json",
"test:cov": "build-scripts test --config build.test.json --jest-coverage"
},
"dependencies": {
"@alilc/lowcode-datasource-engine": "^1.0.0",
"@alilc/lowcode-types": "1.3.2",
"@alilc/lowcode-utils": "1.3.2",
"classnames": "^2.2.6",
"debug": "^4.1.1",
"fetch-jsonp": "^1.1.3",
"intl-messageformat": "^9.3.1",
"jsonuri": "^2.1.2",
"lodash": "^4.17.11",
"prop-types": "^15.7.2",
"react-is": "^16.10.1",
"socket.io-client": "^2.2.0",
"whatwg-fetch": "^3.0.0"
},
"devDependencies": {
"@alib/build-scripts": "^0.1.18",
"@alifd/next": "^1.26.0",
"@alilc/lowcode-designer": "1.3.2",
"@babel/plugin-transform-typescript": "^7.16.8",
"@testing-library/react": "^11.2.2",
"@types/classnames": "^2.2.11",
"@types/debug": "^4.1.5",
"@types/jest": "^26.0.16",
"@types/lodash": "^4.14.167",
"@types/node": "^13.7.1",
"@types/prop-types": "^15.7.3",
"@types/react-is": "^17.0.3",
"@types/react-test-renderer": "^17.0.1",
"jest": "^26.6.3",
"react-test-renderer": "^17.0.2",
"ts-jest": "^26.5.0"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"repository": {
"type": "http",
"url": "https://github.com/alibaba/lowcode-engine/tree/main/packages/renderer-core"
},
"gitHead": "2669f179e6f899d395ce1942d0fe04f9c5ed48a6",
"bugs": "https://github.com/alibaba/lowcode-engine/issues",
"homepage": "https://github.com/alibaba/lowcode-engine/#readme"
}

View File

@ -1,104 +0,0 @@
import { IRuntime, IRendererModules, IGeneralConstructor } from '../types';
export enum Env {
React = 'react',
}
class Adapter {
runtime: IRuntime;
builtinModules = ['Component', 'PureComponent', 'createElement', 'createContext', 'forwardRef', 'findDOMNode'];
env: Env;
renderers: IRendererModules;
configProvider: any;
constructor() {
this.initRuntime();
}
initRuntime() {
const Component: IGeneralConstructor = class <T = any, S = any> {
state: Readonly<S>;
props: Readonly<T> & Readonly<{ children?: any | undefined }>;
refs: Record<string, unknown>;
context: Record<string, unknown>;
setState() {}
forceUpdate() {}
render() {}
};
const PureComponent = class <T = any, S = any> {
state: Readonly<S>;
props: Readonly<T> & Readonly<{ children?: any | undefined }>;
refs: Record<string, unknown>;
context: Record<string, unknown>;
setState() {}
forceUpdate() {}
render() {}
};
const createElement = () => {};
const createContext = () => {};
const forwardRef = () => {};
const findDOMNode = () => {};
this.runtime = {
Component,
PureComponent,
createElement,
createContext,
forwardRef,
findDOMNode,
};
}
setRuntime(runtime: IRuntime) {
if (this.isValidRuntime(runtime)) {
this.runtime = runtime;
}
}
isValidRuntime(runtime: IRuntime) {
if (typeof runtime !== 'object' || Array.isArray(runtime)) {
return false;
}
return this.builtinModules.every((m) => {
const flag = !!runtime[m];
if (!flag) {
throw new Error(`runtime is invalid, module '${m}' does not exist`);
}
return flag;
});
}
getRuntime() {
return this.runtime;
}
setEnv(env: Env) {
this.env = env;
}
isReact() {
return this.env === Env.React;
}
setRenderers(renderers: IRendererModules) {
this.renderers = renderers;
}
getRenderers() {
return this.renderers || {};
}
setConfigProvider(Comp: any) {
this.configProvider = Comp;
}
getConfigProvider() {
return this.configProvider;
}
}
export default new Adapter();

View File

@ -1,15 +0,0 @@
import adapter from '../adapter';
import { IGeneralConstructor } from '../types';
export default function divFactory(): IGeneralConstructor {
const { PureComponent, createElement } = adapter.getRuntime();
return class Div extends PureComponent {
static displayName = 'Div';
static version = '0.0.0';
render() {
return createElement('div', this.props);
}
};
}

View File

@ -1,19 +0,0 @@
.visual-dom .panel-container {
box-sizing: border-box;
border: 1px solid #e9e9e9;
}
.visual-dom .panel-container .title {
display: block;
font-size: 12px;
color: #333;
background-color: #ebecf0;
line-height: 28px;
padding: 0 12px;
border-bottom: 1px solid #e9e9e9;
}
.visual-dom .panel-container .content {
min-height: 20px;
padding: 5px;
}

View File

@ -1,33 +0,0 @@
import PropTypes from 'prop-types';
import adapter from '../../adapter';
import { IGeneralConstructor } from '../../types';
import './index.css';
export default function visualDomFactory(): IGeneralConstructor {
const { PureComponent, createElement } = adapter.getRuntime();
return class VisualDom extends PureComponent {
static displayName = 'VisualDom';
static propTypes = {
children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]),
};
static defaultProps = {
children: null,
};
render() {
const { children, cell, title, label, text, __componentName } = this.props;
let mainContent = children;
if (cell && typeof cell === 'function') {
mainContent = cell();
}
return createElement('div', { className: 'visual-dom' },
createElement('div', { className: 'panel-container' },
[
createElement('span', { className: 'title' }, title || label || text || __componentName),
createElement('div', { className: 'content' }, mainContent),
]));
}
};
}

View File

@ -1,13 +0,0 @@
import adapter from '../adapter';
export default function contextFactory() {
const { createContext } = adapter.getRuntime();
let context = (window as any).__appContext;
if (!context) {
context = createContext({});
(window as any).__appContext = context;
}
return context;
}

View File

@ -1,88 +0,0 @@
import { cloneEnumerableProperty } from '@alilc/lowcode-utils';
import adapter from '../adapter';
import { IBaseRendererInstance, IRendererProps } from '../types';
interface Options {
baseRenderer: IBaseRendererInstance;
schema: any;
}
function patchDidCatch(Comp: any, { baseRenderer }: Options) {
if (Comp.patchedCatch) {
return;
}
Comp.patchedCatch = true;
const { PureComponent } = adapter.getRuntime();
// Rax 的 getDerivedStateFromError 有 BUG这里先用 componentDidCatch 来替代
// @see https://github.com/alibaba/rax/issues/2211
const originalDidCatch = Comp.prototype.componentDidCatch;
Comp.prototype.componentDidCatch = function didCatch(this: any, error: Error, errorInfo: any) {
this.setState({ engineRenderError: true, error });
if (originalDidCatch && typeof originalDidCatch === 'function') {
originalDidCatch.call(this, error, errorInfo);
}
};
const { engine } = baseRenderer.context;
const originRender = Comp.prototype.render;
Comp.prototype.render = function () {
if (this.state && this.state.engineRenderError) {
this.state.engineRenderError = false;
return engine.createElement(engine.getFaultComponent(), {
...this.props,
error: this.state.error,
componentName: this.props._componentName,
});
}
return originRender.call(this);
};
if (!(Comp.prototype instanceof PureComponent)) {
const originShouldComponentUpdate = Comp.prototype.shouldComponentUpdate;
Comp.prototype.shouldComponentUpdate = function (nextProps: IRendererProps, nextState: any) {
if (nextState && nextState.engineRenderError) {
return true;
}
return originShouldComponentUpdate
? originShouldComponentUpdate.call(this, nextProps, nextState)
: true;
};
}
}
const cache = new Map<string, { Comp: any; WrapperComponent: any }>();
export function compWrapper(Comp: any, options: Options) {
const { createElement, Component, forwardRef } = adapter.getRuntime();
if (
Comp?.prototype?.isReactComponent || // react
Comp?.prototype?.setState || // rax
Comp?.prototype instanceof Component
) {
patchDidCatch(Comp, options);
return Comp;
}
if (cache.has(options.schema.id) && cache.get(options.schema.id)?.Comp === Comp) {
return cache.get(options.schema.id)?.WrapperComponent;
}
class Wrapper extends Component {
render() {
return createElement(Comp, { ...this.props, ref: this.props.forwardRef });
}
}
(Wrapper as any).displayName = Comp.displayName;
patchDidCatch(Wrapper, options);
const WrapperComponent = cloneEnumerableProperty(
forwardRef((props: any, ref: any) => {
return createElement(Wrapper, { ...props, forwardRef: ref });
}),
Comp,
);
cache.set(options.schema.id, { WrapperComponent, Comp });
return WrapperComponent;
}

View File

@ -1,600 +0,0 @@
import { INode, IPublicTypePropChangeOptions } from '@alilc/lowcode-designer';
import { GlobalEvent, IPublicEnumTransformStage, IPublicTypeNodeSchema, IPublicTypeEngineOptions } from '@alilc/lowcode-types';
import { isReactComponent, cloneEnumerableProperty } from '@alilc/lowcode-utils';
import { debounce } from '../utils/common';
import adapter from '../adapter';
import * as types from '../types/index';
import logger from '../utils/logger';
export interface IComponentHocInfo {
schema: any;
baseRenderer: types.IBaseRendererInstance;
componentInfo: any;
scope: any;
}
export interface IComponentHocProps {
__tag: any;
componentId: any;
_leaf: any;
forwardedRef?: any;
}
export interface IComponentHocState {
childrenInState: boolean;
nodeChildren: any;
nodeCacheProps: any;
/** 控制是否显示隐藏 */
visible: boolean;
/** 控制是否渲染 */
condition: boolean;
nodeProps: any;
}
type DesignMode = Pick<IPublicTypeEngineOptions, 'designMode'>['designMode'];
export interface IComponentHoc {
designMode: DesignMode | DesignMode[];
hoc: IComponentConstruct;
}
export type IComponentConstruct = (Comp: types.IBaseRenderComponent, info: IComponentHocInfo) => types.IGeneralConstructor;
interface IProps {
_leaf: INode | undefined;
visible: boolean;
componentId: number;
children?: INode[];
__tag: number;
forwardedRef?: any;
}
enum RerenderType {
All = 'All',
ChildChanged = 'ChildChanged',
PropsChanged = 'PropsChanged',
VisibleChanged = 'VisibleChanged',
MinimalRenderUnit = 'MinimalRenderUnit',
}
// 缓存 Leaf 层组件,防止重新渲染问题
class LeafCache {
/** 组件缓存 */
component = new Map();
/**
* state
*/
state = new Map();
/**
* rerender
*/
event = new Map();
ref = new Map();
constructor(public documentId: string, public device: string) {
}
}
let cache: LeafCache;
/** 部分没有渲染的 node 节点进行兜底处理 or 渲染方式没有渲染 LeafWrapper */
function initRerenderEvent({
schema,
__debug,
container,
getNode,
}: any) {
const leaf = getNode?.(schema.id);
if (!leaf
|| cache.event.get(schema.id)?.clear
|| leaf === cache.event.get(schema.id)
) {
return;
}
cache.event.get(schema.id)?.dispose.forEach((disposeFn: any) => disposeFn && disposeFn());
const debounceRerender = debounce(() => {
container.rerender();
}, 20);
cache.event.set(schema.id, {
clear: false,
leaf,
dispose: [
leaf?.onPropChange?.(() => {
if (!container.autoRepaintNode) {
return;
}
__debug(`${schema.componentName}[${schema.id}] leaf not render in SimulatorRendererView, leaf onPropsChange make rerender`);
debounceRerender();
}),
leaf?.onChildrenChange?.(() => {
if (!container.autoRepaintNode) {
return;
}
__debug(`${schema.componentName}[${schema.id}] leaf not render in SimulatorRendererView, leaf onChildrenChange make rerender`);
debounceRerender();
}) as Function,
leaf?.onVisibleChange?.(() => {
if (!container.autoRepaintNode) {
return;
}
__debug(`${schema.componentName}[${schema.id}] leaf not render in SimulatorRendererView, leaf onVisibleChange make rerender`);
debounceRerender();
}),
],
});
}
/** 渲染的 node 节点全局注册事件清除 */
function clearRerenderEvent(id: string): void {
if (cache.event.get(id)?.clear) {
return;
}
cache.event.get(id)?.dispose?.forEach((disposeFn: any) => disposeFn && disposeFn());
cache.event.set(id, {
clear: true,
dispose: [],
});
}
// 给每个组件包裹一个 HOC Leaf支持组件内部属性变化自响应渲染
export function leafWrapper(Comp: types.IBaseRenderComponent, {
schema,
baseRenderer,
componentInfo,
scope,
}: IComponentHocInfo) {
const {
__debug,
__getComponentProps: getProps,
__getSchemaChildrenVirtualDom: getChildren,
__parseData,
} = baseRenderer;
const { engine } = baseRenderer.context;
const host = baseRenderer.props?.__host;
const curDocumentId = baseRenderer.props?.documentId ?? '';
const curDevice = baseRenderer.props?.device ?? '';
const getNode = baseRenderer.props?.getNode;
const container = baseRenderer.props?.__container;
const setSchemaChangedSymbol = baseRenderer.props?.setSchemaChangedSymbol;
const editor = host?.designer?.editor;
const runtime = adapter.getRuntime();
const { forwardRef, createElement } = runtime;
const Component = runtime.Component as types.IGeneralConstructor<
IComponentHocProps, IComponentHocState
>;
const componentCacheId = schema.id;
if (!cache || (curDocumentId && curDocumentId !== cache.documentId) || (curDevice && curDevice !== cache.device)) {
cache?.event.forEach(event => {
event.dispose?.forEach((disposeFn: any) => disposeFn && disposeFn());
});
cache = new LeafCache(curDocumentId, curDevice);
}
if (!isReactComponent(Comp)) {
logger.error(`${schema.componentName} component may be has errors: `, Comp);
}
initRerenderEvent({
schema,
__debug,
container,
getNode,
});
if (curDocumentId && cache.component.has(componentCacheId) && (cache.component.get(componentCacheId).Comp === Comp)) {
return cache.component.get(componentCacheId).LeafWrapper;
}
class LeafHoc extends Component {
recordInfo: {
startTime?: number | null;
type?: string;
node?: INode;
} = {};
private curEventLeaf: INode | undefined;
static displayName = schema.componentName;
disposeFunctions: Array<((() => void) | Function)> = [];
__component_tag = 'leafWrapper';
renderUnitInfo: {
minimalUnitId?: string;
minimalUnitName?: string;
singleRender?: boolean;
};
// 最小渲染单元做防抖处理
makeUnitRenderDebounced = debounce(() => {
this.beforeRender(RerenderType.MinimalRenderUnit);
const schema = this.leaf?.export?.(IPublicEnumTransformStage.Render);
if (!schema) {
return;
}
const nextProps = getProps(schema, scope, Comp, componentInfo);
const children = getChildren(schema, scope, Comp);
const nextState = {
nodeProps: nextProps,
nodeChildren: children,
childrenInState: true,
};
if ('children' in nextProps) {
nextState.nodeChildren = nextProps.children;
}
__debug(`${this.leaf?.componentName}(${this.props.componentId}) MinimalRenderUnit Render!`);
this.setState(nextState);
}, 20);
constructor(props: IProps, context: any) {
super(props, context);
// 监听以下事件,当变化时更新自己
__debug(`${schema.componentName}[${this.props.componentId}] leaf render in SimulatorRendererView`);
clearRerenderEvent(componentCacheId);
this.curEventLeaf = this.leaf;
cache.ref.set(componentCacheId, {
makeUnitRender: this.makeUnitRender,
});
let cacheState = cache.state.get(componentCacheId);
if (!cacheState || cacheState.__tag !== props.__tag) {
cacheState = this.getDefaultState(props);
}
this.state = cacheState;
}
recordTime = () => {
if (!this.recordInfo.startTime) {
return;
}
const endTime = Date.now();
const nodeCount = host?.designer?.currentDocument?.getNodeCount?.();
const componentName = this.recordInfo.node?.componentName || this.leaf?.componentName || 'UnknownComponent';
editor?.eventBus.emit(GlobalEvent.Node.Rerender, {
componentName,
time: endTime - this.recordInfo.startTime,
type: this.recordInfo.type,
nodeCount,
});
this.recordInfo.startTime = null;
};
makeUnitRender = () => {
this.makeUnitRenderDebounced();
};
get autoRepaintNode() {
return container?.autoRepaintNode;
}
componentDidUpdate() {
this.recordTime();
}
componentDidMount() {
const _leaf = this.leaf;
this.initOnPropsChangeEvent(_leaf);
this.initOnChildrenChangeEvent(_leaf);
this.initOnVisibleChangeEvent(_leaf);
this.recordTime();
}
getDefaultState(nextProps: any) {
const {
hidden = false,
condition = true,
} = nextProps.__inner__ || this.leaf?.export?.(IPublicEnumTransformStage.Render) || {};
return {
nodeChildren: null,
childrenInState: false,
visible: !hidden,
condition: __parseData?.(condition, scope),
nodeCacheProps: {},
nodeProps: {},
};
}
setState(state: any) {
cache.state.set(componentCacheId, {
...this.state,
...state,
__tag: this.props.__tag,
});
super.setState(state);
}
/** 由于内部属性变化,在触发渲染前,会执行该函数 */
beforeRender(type: string, node?: INode): void {
this.recordInfo.startTime = Date.now();
this.recordInfo.type = type;
this.recordInfo.node = node;
setSchemaChangedSymbol?.(true);
}
judgeMiniUnitRender() {
if (!this.renderUnitInfo) {
this.getRenderUnitInfo();
}
const renderUnitInfo = this.renderUnitInfo || {
singleRender: true,
};
if (renderUnitInfo.singleRender) {
return;
}
const ref = cache.ref.get(renderUnitInfo.minimalUnitId);
if (!ref) {
__debug('Cant find minimalRenderUnit ref! This make rerender!');
container?.rerender();
return;
}
__debug(`${this.leaf?.componentName}(${this.props.componentId}) need render, make its minimalRenderUnit ${renderUnitInfo.minimalUnitName}(${renderUnitInfo.minimalUnitId})`);
ref.makeUnitRender();
}
getRenderUnitInfo(leaf = this.leaf) {
// leaf 在低代码组件中存在 mock 的情况,退出最小渲染单元判断
if (!leaf || typeof leaf.isRoot !== 'function') {
return;
}
if (leaf.isRootNode) {
this.renderUnitInfo = {
singleRender: true,
...(this.renderUnitInfo || {}),
};
}
if (leaf.componentMeta.isMinimalRenderUnit) {
this.renderUnitInfo = {
minimalUnitId: leaf.id,
minimalUnitName: leaf.componentName,
singleRender: false,
};
}
if (leaf.hasLoop()) {
// 含有循环配置的元素,父元素是最小渲染单元
this.renderUnitInfo = {
minimalUnitId: leaf?.parent?.id,
minimalUnitName: leaf?.parent?.componentName,
singleRender: false,
};
}
if (leaf.parent) {
this.getRenderUnitInfo(leaf.parent);
}
}
componentWillReceiveProps(nextProps: any) {
let { componentId } = nextProps;
if (nextProps.__tag === this.props.__tag) {
return null;
}
const _leaf = getNode?.(componentId);
if (_leaf && this.curEventLeaf && _leaf !== this.curEventLeaf) {
this.disposeFunctions.forEach((fn) => fn());
this.disposeFunctions = [];
this.initOnChildrenChangeEvent(_leaf);
this.initOnPropsChangeEvent(_leaf);
this.initOnVisibleChangeEvent(_leaf);
this.curEventLeaf = _leaf;
}
const {
visible,
...resetState
} = this.getDefaultState(nextProps);
this.setState(resetState);
}
/** 监听参数变化 */
initOnPropsChangeEvent(leaf = this.leaf): void {
const handlePropsChange = debounce((propChangeInfo: IPublicTypePropChangeOptions) => {
const {
key,
newValue = null,
} = propChangeInfo;
const node = leaf;
if (key === '___condition___') {
const { condition = true } = this.leaf?.export(IPublicEnumTransformStage.Render) || {};
const conditionValue = __parseData?.(condition, scope);
__debug(`key is ___condition___, change condition value to [${condition}]`);
// 条件表达式改变
this.setState({
condition: conditionValue,
});
return;
}
// 如果循坏条件变化,从根节点重新渲染
// 目前多层循坏无法判断需要从哪一层开始渲染,故先粗暴解决
if (key === '___loop___') {
__debug('key is ___loop___, render a page!');
container?.rerender();
// 由于 scope 变化,需要清空缓存,使用新的 scope
cache.component.delete(componentCacheId);
return;
}
this.beforeRender(RerenderType.PropsChanged);
const { state } = this;
const { nodeCacheProps } = state;
const nodeProps = getProps(node?.export?.(IPublicEnumTransformStage.Render) as IPublicTypeNodeSchema, scope, Comp, componentInfo);
if (key && !(key in nodeProps) && (key in this.props)) {
// 当 key 在 this.props 中时,且不存在在计算值中,需要用 newValue 覆盖掉 this.props 的取值
nodeCacheProps[key] = newValue;
}
__debug(`${leaf?.componentName}[${this.props.componentId}] component trigger onPropsChange!`, nodeProps, nodeCacheProps, key, newValue);
this.setState('children' in nodeProps ? {
nodeChildren: nodeProps.children,
nodeProps,
childrenInState: true,
nodeCacheProps,
} : {
nodeProps,
nodeCacheProps,
});
this.judgeMiniUnitRender();
});
const dispose = leaf?.onPropChange?.((propChangeInfo: IPublicTypePropChangeOptions) => {
if (!this.autoRepaintNode) {
return;
}
handlePropsChange(propChangeInfo);
});
dispose && this.disposeFunctions.push(dispose);
}
/**
*
*/
initOnVisibleChangeEvent(leaf = this.leaf) {
const dispose = leaf?.onVisibleChange?.((flag: boolean) => {
if (!this.autoRepaintNode) {
return;
}
if (this.state.visible === flag) {
return;
}
__debug(`${leaf?.componentName}[${this.props.componentId}] component trigger onVisibleChange(${flag}) event`);
this.beforeRender(RerenderType.VisibleChanged);
this.setState({
visible: flag,
});
this.judgeMiniUnitRender();
});
dispose && this.disposeFunctions.push(dispose);
}
/**
* ...
*/
initOnChildrenChangeEvent(leaf = this.leaf) {
const dispose = leaf?.onChildrenChange?.((param): void => {
if (!this.autoRepaintNode) {
return;
}
const {
type,
node,
} = param || {};
this.beforeRender(`${RerenderType.ChildChanged}-${type}`, node);
// TODO: 缓存同级其他元素的 children。
// 缓存二级 children Next 查询筛选组件有问题
// 缓存一级 children Next Tab 组件有问题
const nextChild = getChildren(leaf?.export?.(IPublicEnumTransformStage.Render) as types.ISchema, scope, Comp);
__debug(`${schema.componentName}[${this.props.componentId}] component trigger onChildrenChange event`, nextChild);
this.setState({
nodeChildren: nextChild,
childrenInState: true,
});
this.judgeMiniUnitRender();
});
dispose && this.disposeFunctions.push(dispose);
}
componentWillUnmount() {
this.disposeFunctions.forEach(fn => fn());
}
get hasChildren(): boolean {
if (!this.state.childrenInState) {
return 'children' in this.props;
}
return true;
}
get children(): any {
if (this.state.childrenInState) {
return this.state.nodeChildren;
}
if (this.props.children && !Array.isArray(this.props.children)) {
return [this.props.children];
}
if (this.props.children && this.props.children.length) {
return this.props.children;
}
return this.props.children;
}
get leaf(): INode | undefined {
if (this.props._leaf?.isMock) {
// 低代码组件作为一个整体更新,其内部的组件不需要监听相关事件
return undefined;
}
return getNode?.(componentCacheId);
}
render() {
if (!this.state.visible || !this.state.condition) {
return null;
}
const {
forwardedRef,
...rest
} = this.props;
const compProps = {
...rest,
...(this.state.nodeCacheProps || {}),
...(this.state.nodeProps || {}),
children: [],
__id: this.props.componentId,
ref: forwardedRef,
};
delete compProps.__inner__;
if (this.hasChildren) {
return engine.createElement(Comp, compProps, this.children);
}
return engine.createElement(Comp, compProps);
}
}
let LeafWrapper = forwardRef((props: any, ref: any) => {
return createElement(LeafHoc, {
...props,
forwardedRef: ref,
});
});
LeafWrapper = cloneEnumerableProperty(LeafWrapper, Comp);
LeafWrapper.displayName = (Comp as any).displayName;
cache.component.set(componentCacheId, {
LeafWrapper,
Comp,
});
return LeafWrapper;
}

View File

@ -1,9 +0,0 @@
import adapter from './adapter';
import contextFactory from './context';
export { adapter, contextFactory };
export * from './renderer';
export * as types from './types';
export * as utils from './utils';
export * from './hoc';

View File

@ -1,82 +0,0 @@
import PropTypes from 'prop-types';
import baseRendererFactory from './base';
import { isEmpty } from '../utils';
import { IRendererAppHelper, IBaseRendererProps, IBaseRenderComponent } from '../types';
import logger from '../utils/logger';
export default function addonRendererFactory(): IBaseRenderComponent {
const BaseRenderer = baseRendererFactory();
return class AddonRenderer extends BaseRenderer {
static displayName = 'AddonRenderer';
__namespace = 'addon';
static propTypes = {
config: PropTypes.object,
__schema: PropTypes.object,
};
static defaultProps = {
config: {},
__schema: {},
};
addonKey: any;
appHelper: IRendererAppHelper;
open: () => any;
close: () => any;
__afterInit(props: IBaseRendererProps) {
this.__generateCtx({
component: this,
});
const schema = props.__schema || {};
this.state = this.__parseData(schema.state || {});
if (isEmpty(props.config) || !props.config?.addonKey) {
logger.warn('lce addon has wrong config');
this.setState({
__hasError: true,
});
return;
}
// 注册插件
this.addonKey = props.config.addonKey;
this.appHelper.addons = this.appHelper.addons || {};
this.appHelper.addons[this.addonKey] = this;
this.__initDataSource(props);
this.open = this.open || (() => { });
this.close = this.close || (() => { });
this.__executeLifeCycleMethod('constructor', [...arguments]);
}
async componentWillUnmount() {
super.componentWillUnmount?.apply(this, [...arguments] as any);
// 注销插件
const config = this.props.config || {};
if (config && this.appHelper.addons) {
delete this.appHelper.addons[config.addonKey];
}
}
get utils() {
const { utils = {} } = this.context.config || {};
return { ...this.appHelper.utils, ...utils };
}
render() {
const { __schema } = this.props;
if (this.__checkSchema(__schema)) {
return '插件 schema 结构异常!';
}
this.__debug(`${AddonRenderer.displayName} render - ${__schema.fileName}`);
this.__generateCtx({
component: this,
});
this.__render();
return this.__renderContent(this.__renderContextProvider({ compContext: this }));
}
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +0,0 @@
import baseRendererFactory from './base';
import { IBaseRendererProps, IBaseRenderComponent } from '../types';
export default function blockRendererFactory(): IBaseRenderComponent {
const BaseRenderer = baseRendererFactory();
return class BlockRenderer extends BaseRenderer {
static displayName = 'BlockRenderer';
__namespace = 'block';
__afterInit(props: IBaseRendererProps) {
this.__generateCtx({});
const schema = props.__schema || {};
this.state = this.__parseData(schema.state || {});
this.__initDataSource(props);
this.__executeLifeCycleMethod('constructor', [...arguments]);
}
render() {
const { __schema, __components } = this.props;
if (this.__checkSchema(__schema, 'Div')) {
return '区块 schema 结构异常!';
}
this.__debug(`${BlockRenderer.displayName} render - ${__schema?.fileName}`);
this.__generateCtx({});
this.__render();
const { Block } = __components;
if (Block) {
return this.__renderComp(Block, {});
}
return this.__renderContent(this.__renderContextProvider());
}
};
}

View File

@ -1,50 +0,0 @@
import baseRendererFactory from './base';
import { IBaseRendererProps, IBaseRenderComponent } from '../types';
export default function componentRendererFactory(): IBaseRenderComponent {
const BaseRenderer = baseRendererFactory();
return class CompRenderer extends BaseRenderer {
static displayName = 'CompRenderer';
__namespace = 'component';
__afterInit(props: IBaseRendererProps) {
this.__generateCtx({
component: this,
});
const schema = props.__schema || {};
this.state = this.__parseData(schema.state || {});
this.__initDataSource(props);
this.__executeLifeCycleMethod('constructor', arguments as any);
}
render() {
const { __schema, __components } = this.props;
if (this.__checkSchema(__schema)) {
return '自定义组件 schema 结构异常!';
}
this.__debug(`${CompRenderer.displayName} render - ${__schema.fileName}`);
this.__generateCtx({
component: this,
});
this.__render();
const noContainer = this.__parseData(__schema.props?.noContainer);
this.__bindCustomMethods(this.props);
if (noContainer) {
return this.__renderContextProvider({ compContext: this });
}
const Component = __components?.[__schema?.componentName];
if (!Component) {
return this.__renderContent(this.__renderContextProvider({ compContext: this }));
}
return this.__renderComp(Component, this.__renderContextProvider({ compContext: this }));
}
};
}

View File

@ -1,17 +0,0 @@
import baseRendererFactory from './base';
import pageRendererFactory from './page';
import componentRendererFactory from './component';
import blockRendererFactory from './block';
import addonRendererFactory from './addon';
import tempRendererFactory from './temp';
import rendererFactory from './renderer';
export {
baseRendererFactory,
pageRendererFactory,
componentRendererFactory,
blockRendererFactory,
addonRendererFactory,
tempRendererFactory,
rendererFactory,
};

View File

@ -1,63 +0,0 @@
import { getLogger } from '@alilc/lowcode-utils';
import baseRendererFactory from './base';
import { IBaseRendererProps, IBaseRenderComponent } from '../types';
const logger = getLogger({ level: 'warn', bizName: 'renderer-core:page' });
export default function pageRendererFactory(): IBaseRenderComponent {
const BaseRenderer = baseRendererFactory();
return class PageRenderer extends BaseRenderer {
static displayName = 'PageRenderer';
__namespace = 'page';
__afterInit(props: IBaseRendererProps, ...rest: unknown[]) {
this.__generateCtx({
page: this,
});
const schema = props.__schema || {};
this.state = this.__parseData(schema.state || {});
this.__initDataSource(props);
this.__executeLifeCycleMethod('constructor', [props, ...rest]);
}
async componentDidUpdate(prevProps: IBaseRendererProps, _prevState: {}, snapshot: unknown) {
const { __ctx } = this.props;
// 当编排的时候修改 schema.state 值,需要将最新 schema.state 值 setState
if (JSON.stringify(prevProps.__schema.state) != JSON.stringify(this.props.__schema.state)) {
const newState = this.__parseData(this.props.__schema.state, __ctx);
this.setState(newState);
}
super.componentDidUpdate?.(prevProps, _prevState, snapshot);
}
setState(state: any, callback?: () => void) {
logger.info('page set state', state);
super.setState(state, callback);
}
render() {
const { __schema, __components } = this.props;
if (this.__checkSchema(__schema)) {
return '页面schema结构异常';
}
this.__debug(`${PageRenderer.displayName} render - ${__schema.fileName}`);
this.__bindCustomMethods(this.props);
this.__initDataSource(this.props);
this.__generateCtx({
page: this,
});
this.__render();
const { Page } = __components;
if (Page) {
return this.__renderComp(Page, { pageContext: this });
}
return this.__renderContent(this.__renderContextProvider({ pageContext: this }));
}
};
}

View File

@ -1,182 +0,0 @@
import Debug from 'debug';
import adapter from '../adapter';
import contextFactory from '../context';
import { isFileSchema, isEmpty } from '../utils';
import baseRendererFactory from './base';
import divFactory from '../components/Div';
import { IRenderComponent, IRendererProps, IRendererState } from '../types';
import { IPublicTypeNodeSchema, IPublicTypeRootSchema } from '@alilc/lowcode-types';
import logger from '../utils/logger';
export default function rendererFactory(): IRenderComponent {
const { PureComponent, Component, createElement, findDOMNode } = adapter.getRuntime();
const RENDERER_COMPS: any = adapter.getRenderers();
const BaseRenderer = baseRendererFactory();
const AppContext = contextFactory();
const Div = divFactory();
const ConfigProvider = adapter.getConfigProvider() || Div;
const debug = Debug('renderer:entry');
class FaultComponent extends PureComponent<IPublicTypeNodeSchema | any> {
render() {
logger.error(`%c${this.props.componentName || ''} 组件渲染异常, 异常原因: ${this.props.error?.message || this.props.error || '未知'}`, 'color: #ff0000;');
return createElement(Div, {
style: {
width: '100%',
height: '50px',
lineHeight: '50px',
textAlign: 'center',
fontSize: '15px',
color: '#ff0000',
border: '2px solid #ff0000',
},
}, `${this.props.componentName || ''} 组件渲染异常,请查看控制台日志`);
}
}
class NotFoundComponent extends PureComponent<{
componentName: string;
} & IRendererProps> {
render() {
if (this.props.enableStrictNotFoundMode) {
return `${this.props.componentName || ''} Component Not Found`;
}
return createElement(Div, this.props, this.props.children || `${this.props.componentName || ''} Component Not Found`);
}
}
return class Renderer extends Component<IRendererProps> {
static displayName = 'Renderer';
state: Partial<IRendererState> = {};
__ref: any;
static defaultProps: IRendererProps = {
appHelper: undefined,
components: {},
designMode: '',
suspended: false,
schema: {} as IPublicTypeRootSchema,
onCompGetRef: () => { },
onCompGetCtx: () => { },
thisRequiredInJSE: true,
};
static findDOMNode = findDOMNode;
constructor(props: IRendererProps, context: any) {
super(props, context);
this.state = {};
debug(`entry.constructor - ${props?.schema?.componentName}`);
}
async componentDidMount() {
debug(`entry.componentDidMount - ${this.props.schema && this.props.schema.componentName}`);
}
async componentDidUpdate() {
debug(`entry.componentDidUpdate - ${this.props?.schema?.componentName}`);
}
async componentWillUnmount() {
debug(`entry.componentWillUnmount - ${this.props?.schema?.componentName}`);
}
componentDidCatch(error: Error) {
this.state.engineRenderError = true;
this.state.error = error;
}
shouldComponentUpdate(nextProps: IRendererProps) {
return !nextProps.suspended;
}
__getRef = (ref: any) => {
this.__ref = ref;
if (ref) {
this.props.onCompGetRef?.(this.props.schema, ref);
}
};
isValidComponent(SetComponent: any) {
return SetComponent;
}
createElement(SetComponent: any, props: any, children?: any) {
return (this.props.customCreateElement || createElement)(SetComponent, props, children);
}
getNotFoundComponent() {
return this.props.notFoundComponent || NotFoundComponent;
}
getFaultComponent() {
const { faultComponent, faultComponentMap, schema } = this.props;
if (faultComponentMap) {
const { componentName } = schema;
return faultComponentMap[componentName] || faultComponent || FaultComponent;
}
return faultComponent || FaultComponent;
}
getComp() {
const { schema, components } = this.props;
const { componentName } = schema;
const allComponents = { ...RENDERER_COMPS, ...components };
let Comp = allComponents[componentName] || RENDERER_COMPS[`${componentName}Renderer`];
if (Comp && Comp.prototype) {
if (!(Comp.prototype instanceof BaseRenderer)) {
Comp = RENDERER_COMPS[`${componentName}Renderer`];
}
}
return Comp;
}
render() {
const { schema, designMode, appHelper, components } = this.props;
if (isEmpty(schema)) {
return null;
}
// 兼容乐高区块模板
if (schema.componentName !== 'Div' && !isFileSchema(schema)) {
logger.error('The root component name needs to be one of Page、Block、Component, please check the schema: ', schema);
return '模型结构异常';
}
debug('entry.render');
const allComponents = { ...RENDERER_COMPS, ...components };
let Comp = this.getComp();
if (this.state && this.state.engineRenderError) {
return createElement(this.getFaultComponent(), {
...this.props,
error: this.state.error,
});
}
if (Comp) {
return createElement(AppContext.Provider, {
value: {
appHelper,
components: allComponents,
engine: this,
},
}, createElement(ConfigProvider, {
device: this.props.device,
locale: this.props.locale,
}, createElement(Comp, {
key: schema.__ctx && `${schema.__ctx.lceKey}_${schema.__ctx.idx || '0'}`,
ref: this.__getRef,
__appHelper: appHelper,
__components: allComponents,
__schema: schema,
__designMode: designMode,
...this.props,
})));
}
return null;
}
};
}

View File

@ -1,60 +0,0 @@
import { IBaseRenderComponent } from '../types';
import logger from '../utils/logger';
import baseRendererFactory from './base';
export default function tempRendererFactory(): IBaseRenderComponent {
const BaseRenderer = baseRendererFactory();
return class TempRenderer extends BaseRenderer {
static displayName = 'TempRenderer';
__namespace = 'temp';
cacheSetState?: Record<string, any>;
__init() {
this.state = {};
this.cacheSetState = {};
}
async componentDidMount() {
const ctx = this.props.__ctx;
if (!ctx) return;
const { setState } = ctx;
this.cacheSetState = setState;
ctx.setState = (...args: any) => {
setState.call(ctx, ...args);
setTimeout(() => this.forceUpdate(), 0);
};
this.__debug(`componentDidMount - ${this.props.__schema.fileName}`);
}
async componentDidUpdate() {
this.__debug(`componentDidUpdate - ${this.props.__schema.fileName}`);
}
async componentWillUnmount() {
const ctx = this.props.__ctx;
if (!ctx || !this.cacheSetState) return;
ctx.setState = this.cacheSetState;
delete this.cacheSetState;
this.__debug(`componentWillUnmount - ${this.props.__schema.fileName}`);
}
async componentDidCatch(e: any) {
logger.warn(e);
this.__debug(`componentDidCatch - ${this.props.__schema.fileName}`);
}
render() {
const { __schema, __ctx } = this.props;
if (this.__checkSchema(__schema)) {
return '下钻编辑 schema 结构异常!';
}
this.__debug(`${TempRenderer.displayName} render - ${__schema?.fileName}`);
return this.__renderContent(this.__renderContextProvider({ __ctx }));
}
};
}

View File

@ -1,342 +0,0 @@
import type { ComponentLifecycle, CSSProperties } from 'react';
import { BuiltinSimulatorHost, BuiltinSimulatorRenderer } from '@alilc/lowcode-designer';
import { RequestHandler, IPublicTypeNodeSchema, IPublicTypeRootSchema, IPublicTypeJSONObject } from '@alilc/lowcode-types';
export type ISchema = IPublicTypeNodeSchema | IPublicTypeRootSchema;
/*
** Duck typed component type supporting both react and rax
*/
interface IGeneralComponent<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> {
readonly props: Readonly<P> & Readonly<{ children?: any | undefined }>;
state: Readonly<S>;
refs: Record<string, any>;
context: any;
setState<K extends keyof S>(
state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
callback?: () => void
): void;
forceUpdate(callback?: () => void): void;
render(): any;
}
export type IGeneralConstructor<
T = {
[key: string]: any;
}, S = {
[key: string]: any;
}, D = any
> = new <TT = T, SS = S, DD = D>(props: TT, context: any) => IGeneralComponent<TT, SS, DD>;
/**
* duck-typed History
*
* @see https://github.com/ReactTraining/history/tree/master/docs/api-reference.md
*/
interface IHistoryLike {
readonly action: any;
readonly location: ILocationLike;
createHref: (to: any) => string;
push: (to: any, state?: any) => void;
replace: (to: any, state?: any) => void;
go: (delta: any) => void;
back: () => void;
forward: () => void;
listen: (listener: any) => () => void;
block: (blocker: any) => () => void;
}
/**
* duck-typed History.Location
*
* @see https://github.com/remix-run/history/blob/dev/docs/api-reference.md#location
*/
export interface ILocationLike {
pathname: any;
search: any;
state: any;
hash: any;
key?: any;
}
export type IRendererAppHelper = Partial<{
/** 全局公共函数 */
utils: Record<string, any>;
/** 全局常量 */
constants: Record<string, any>;
/** react-router 的 location 实例 */
location: ILocationLike;
/** react-router 的 history 实例 */
history: IHistoryLike;
/** @deprecated 已无业务使用 */
match: any;
/** @experimental 内部使用 */
logParams: Record<string, any>;
/** @experimental 内部使用 */
addons: Record<string, any>;
/** @experimental 内部使用 */
requestHandlersMap: Record<string, RequestHandler<{
data: unknown;
}>>;
}>;
/**
*
*
* @see @todo @承虎
*/
export interface IRendererProps {
/** 符合低代码搭建协议的数据 */
schema: IPublicTypeRootSchema | IPublicTypeNodeSchema;
/** 组件依赖的实例 */
components: Record<string, IGeneralComponent>;
/** CSS 类名 */
className?: string;
/** style */
style?: CSSProperties;
/** id */
id?: string | number;
/** 语言 */
locale?: string;
/**
*
* https://lowcode-engine.cn/lowcode 中 2.6 国际化多语言支持
* */
messages?: Record<string, any>;
/** 主要用于设置渲染模块的全局上下文,里面定义的内容可以在低代码中通过 this 来访问,比如 this.utils */
appHelper?: IRendererAppHelper;
/**
* https://lowcode-engine.cn/lowcode
* 使
*
* >
*/
componentsMap?: { [key: string]: any };
/** 设计模式可选值live、design */
designMode?: string;
/** 渲染模块是否挂起,当设置为 true 时,渲染模块最外层容器的 shouldComponentUpdate 将始终返回false在下钻编辑或者多引擎渲染的场景会用到该参数。 */
suspended?: boolean;
/** 组件获取 ref 时触发的钩子 */
onCompGetRef?: (schema: IPublicTypeNodeSchema, ref: any) => void;
/** 组件 ctx 更新回调 */
onCompGetCtx?: (schema: IPublicTypeNodeSchema, ref: any) => void;
/** 传入的 schema 是否有变更 */
getSchemaChangedSymbol?: () => boolean;
/** 设置 schema 是否有变更 */
setSchemaChangedSymbol?: (symbol: boolean) => void;
/** 自定义创建 element 的钩子 */
customCreateElement?: (Component: any, props: any, children: any) => any;
/** 渲染类型,标识当前模块是以什么类型进行渲染的 */
rendererName?: 'LowCodeRenderer' | 'PageRenderer' | string;
/** 当找不到组件时,显示的组件 */
notFoundComponent?: IGeneralComponent;
/** 当组件渲染异常时,显示的组件 */
faultComponent?: IGeneralComponent;
/** */
faultComponentMap?: {
[prop: string]: IGeneralComponent;
};
/** 设备信息 */
device?: string;
/**
* @default true
* JSExpression 使 this 访
*/
thisRequiredInJSE?: boolean;
/**
* @default false
*
*/
enableStrictNotFoundMode?: boolean;
}
export interface IRendererState {
engineRenderError?: boolean;
error?: Error;
}
/**
*
*/
export interface IBaseRendererProps {
locale?: string;
messages: Record<string, any>;
__appHelper: IRendererAppHelper;
__components: Record<string, any>;
__ctx: Record<string, any>;
__schema: IPublicTypeRootSchema;
__host?: BuiltinSimulatorHost;
__container?: BuiltinSimulatorRenderer;
config?: Record<string, any>;
designMode?: 'design';
className?: string;
style?: CSSProperties;
id?: string | number;
getSchemaChangedSymbol?: () => boolean;
setSchemaChangedSymbol?: (symbol: boolean) => void;
thisRequiredInJSE?: boolean;
documentId?: string;
getNode?: any;
/**
* 'default'
*/
device?: 'default' | 'mobile' | string;
componentName?: string;
}
export interface INodeInfo {
schema?: IPublicTypeNodeSchema;
Comp: any;
componentInfo?: any;
componentChildren?: any;
}
export interface JSExpression {
type: string;
value: string;
}
export interface DataSourceItem {
id: string;
isInit?: boolean | JSExpression;
type?: string;
options?: {
uri: string | JSExpression;
params?: IPublicTypeJSONObject | JSExpression;
method?: string | JSExpression;
shouldFetch?: string;
willFetch?: string;
fit?: string;
didFetch?: string;
};
dataHandler?: JSExpression;
}
export interface DataSource {
list?: DataSourceItem[];
dataHandler?: JSExpression;
}
export interface IRuntime {
[key: string]: any;
Component: IGeneralConstructor;
PureComponent: IGeneralConstructor;
createElement: (...args: any) => any;
createContext: (...args: any) => any;
forwardRef: (...args: any) => any;
findDOMNode: (...args: any) => any;
}
export interface IRendererModules {
BaseRenderer?: IBaseRenderComponent;
PageRenderer: IBaseRenderComponent;
ComponentRenderer: IBaseRenderComponent;
BlockRenderer?: IBaseRenderComponent;
AddonRenderer?: IBaseRenderComponent;
TempRenderer?: IBaseRenderComponent;
DivRenderer?: IBaseRenderComponent;
}
export interface IBaseRendererContext {
appHelper: IRendererAppHelper;
components: Record<string, IGeneralComponent>;
engine: IRuntime;
pageContext?: IBaseRenderComponent;
compContext?: IBaseRenderComponent;
}
export type IBaseRendererInstance = IGeneralComponent<
IBaseRendererProps,
Record<string, any>,
any
>
& {
reloadDataSource(): Promise<any>;
__beforeInit(props: IBaseRendererProps): void;
__init(props: IBaseRendererProps): void;
__afterInit(props: IBaseRendererProps): void;
__executeLifeCycleMethod(method: string, args?: any[]): void;
__bindCustomMethods(props: IBaseRendererProps): void;
__generateCtx(ctx: Record<string, any>): void;
__parseData(data: any, ctx?: any): any;
__initDataSource(props: IBaseRendererProps): void;
__render(): void;
__getRef(ref: any): void;
__getSchemaChildrenVirtualDom(
schema: IPublicTypeNodeSchema | undefined,
Comp: any,
nodeChildrenMap?: any
): any;
__getComponentProps(schema: IPublicTypeNodeSchema | undefined, scope: any, Comp: any, componentInfo?: any): any;
__createDom(): any;
__createVirtualDom(schema: any, self: any, parentInfo: INodeInfo, idx: string | number): any;
__createLoopVirtualDom(schema: any, self: any, parentInfo: INodeInfo, idx: number | string): any;
__parseProps(props: any, self: any, path: string, info: INodeInfo): any;
__initDebug?(): void;
__debug(...args: any[]): void;
__renderContextProvider(customProps?: object, children?: any): any;
__renderContextConsumer(children: any): any;
__renderContent(children: any): any;
__checkSchema(schema: IPublicTypeNodeSchema | undefined, extraComponents?: string | string[]): any;
__renderComp(Comp: any, ctxProps: object): any;
$(filedId: string, instance?: any): any;
};
export interface IBaseRenderComponent {
new(
props: IBaseRendererProps,
context: any
): IBaseRendererInstance;
}
export interface IRenderComponent {
displayName: string;
defaultProps: IRendererProps;
findDOMNode: (...args: any) => any;
new(props: IRendererProps, context: any): IGeneralComponent<IRendererProps, IRendererState> & {
[x: string]: any;
__getRef: (ref: any) => void;
componentDidMount(): Promise<void> | void;
componentDidUpdate(): Promise<void> | void;
componentWillUnmount(): Promise<void> | void;
componentDidCatch(e: any): Promise<void> | void;
shouldComponentUpdate(nextProps: IRendererProps): boolean;
isValidComponent(SetComponent: any): any;
createElement(SetComponent: any, props: any, children?: any): any;
getNotFoundComponent(): any;
getFaultComponent(): any;
};
}

View File

@ -1,370 +0,0 @@
/* eslint-disable no-console */
/* eslint-disable no-new-func */
import logger from './logger';
import { IPublicTypeRootSchema, IPublicTypeNodeSchema, IPublicTypeJSSlot } from '@alilc/lowcode-types';
import { isI18nData, isJSExpression } from '@alilc/lowcode-utils';
import { isEmpty } from 'lodash';
import IntlMessageFormat from 'intl-messageformat';
import pkg from '../../package.json';
(window as any).sdkVersion = pkg.version;
export { pick, isEqualWith as deepEqual, cloneDeep as clone, isEmpty, throttle, debounce } from 'lodash';
const EXPRESSION_TYPE = {
JSEXPRESSION: 'JSExpression',
JSFUNCTION: 'JSFunction',
JSSLOT: 'JSSlot',
JSBLOCK: 'JSBlock',
I18N: 'i18n',
};
/**
* check if schema passed in is a valid schema
* @name isSchema
* @returns boolean
*/
export function isSchema(schema: any): schema is IPublicTypeNodeSchema {
if (isEmpty(schema)) {
return false;
}
// Leaf and Slot should be valid
if (schema.componentName === 'Leaf' || schema.componentName === 'Slot') {
return true;
}
if (Array.isArray(schema)) {
return schema.every((item) => isSchema(item));
}
// check if props is valid
const isValidProps = (props: any) => {
if (!props) {
return false;
}
if (isJSExpression(props)) {
return true;
}
return (typeof schema.props === 'object' && !Array.isArray(props));
};
return !!(schema.componentName && isValidProps(schema.props));
}
/**
* check if schema passed in is a container type, including : Component Block Page
* @param schema
* @returns boolean
*/
export function isFileSchema(schema: IPublicTypeNodeSchema): schema is IPublicTypeRootSchema {
if (!isSchema(schema)) {
return false;
}
return ['Page', 'Block', 'Component'].includes(schema.componentName);
}
/**
* check if current page is nested within another page with same host
* @returns boolean
*/
export function inSameDomain() {
try {
return window.parent !== window && window.parent.location.host === window.location.host;
} catch (e) {
return false;
}
}
/**
* get css styled name from schema`s fileName
* FileName -> lce-file-name
* @returns string
*/
export function getFileCssName(fileName: string) {
if (!fileName) {
return;
}
const name = fileName.replace(/([A-Z])/g, '-$1').toLowerCase();
return (`lce-${name}`)
.split('-')
.filter((p) => !!p)
.join('-');
}
/**
* check if a object is type of JSSlot
* @returns string
*/
export function isJSSlot(obj: any): obj is IPublicTypeJSSlot {
if (!obj) {
return false;
}
if (typeof obj !== 'object' || Array.isArray(obj)) {
return false;
}
// Compatible with the old protocol JSBlock
return [EXPRESSION_TYPE.JSSLOT, EXPRESSION_TYPE.JSBLOCK].includes(obj.type);
}
/**
* get value from an object
* @returns string
*/
export function getValue(obj: any, path: string, defaultValue = {}) {
// array is not valid type, return default value
if (Array.isArray(obj)) {
return defaultValue;
}
if (isEmpty(obj) || typeof obj !== 'object') {
return defaultValue;
}
const res = path.split('.').reduce((pre, cur) => {
return pre && pre[cur];
}, obj);
if (res === undefined) {
return defaultValue;
}
return res;
}
/**
*
* @param {*} key
* @param {*} values
* @param {*} locale zh-CNen-US
* @param {*} messages
*/
export function getI18n(key: string, values = {}, locale = 'zh-CN', messages: Record<string, any> = {}) {
if (!messages || !messages[locale] || !messages[locale][key]) {
return '';
}
const formater = new IntlMessageFormat(messages[locale][key], locale);
return formater.format(values);
}
/**
* ref
* @param {*} Comp
*/
export function canAcceptsRef(Comp: any) {
const hasSymbol = typeof Symbol === 'function' && Symbol.for;
const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0;
// eslint-disable-next-line max-len
return Comp?.$$typeof === REACT_FORWARD_REF_TYPE || Comp?.prototype?.isReactComponent || Comp?.prototype?.setState || Comp._forwardRef;
}
/**
* transform array to a object
* @param arr array to be transformed
* @param key key of array item, which`s value will be used as key in result map
* @param overwrite overwrite existing item in result or not
* @returns object result map
*/
export function transformArrayToMap(arr: any[], key: string, overwrite = true) {
if (isEmpty(arr) || !Array.isArray(arr)) {
return {};
}
const res: any = {};
arr.forEach((item) => {
const curKey = item[key];
if (item[key] === undefined) {
return;
}
if (res[curKey] && !overwrite) {
return;
}
res[curKey] = item;
});
return res;
}
/**
* transform string to a function
* @param str function in string form
* @returns funtion
*/
export function transformStringToFunction(str: string) {
if (typeof str !== 'string') {
return str;
}
if (inSameDomain() && (window.parent as any).__newFunc) {
return (window.parent as any).__newFunc(`"use strict"; return ${str}`)();
} else {
return new Function(`"use strict"; return ${str}`)();
}
}
/**
* JSExpressionthis
* @param str expression in string form
* @param self scope object
* @returns funtion
*/
function parseExpression(options: {
str: any; self: any; thisRequired?: boolean; logScope?: string;
}): any;
function parseExpression(str: any, self: any, thisRequired?: boolean): any;
function parseExpression(a: any, b?: any, c = false) {
let str;
let self;
let thisRequired;
let logScope;
if (typeof a === 'object' && b === undefined) {
str = a.str;
self = a.self;
thisRequired = a.thisRequired;
logScope = a.logScope;
} else {
str = a;
self = b;
thisRequired = c;
}
try {
const contextArr = ['"use strict";', 'var __self = arguments[0];'];
contextArr.push('return ');
let tarStr: string;
tarStr = (str.value || '').trim();
// NOTE: use __self replace 'this' in the original function str
// may be wrong in extreme case which contains '__self' already
tarStr = tarStr.replace(/this(\W|$)/g, (_a: any, b: any) => `__self${b}`);
tarStr = contextArr.join('\n') + tarStr;
// 默认调用顶层窗口的parseObj, 保障new Function的window对象是顶层的window对象
if (inSameDomain() && (window.parent as any).__newFunc) {
return (window.parent as any).__newFunc(tarStr)(self);
}
const code = `with(${thisRequired ? '{}' : '$scope || {}'}) { ${tarStr} }`;
return new Function('$scope', code)(self);
} catch (err) {
logger.error(`${logScope || ''} parseExpression.error`, err, str, self?.__self ?? self);
return undefined;
}
}
export {
parseExpression,
};
export function parseThisRequiredExpression(str: any, self: any) {
return parseExpression(str, self, true);
}
/**
* capitalize first letter
* @param word string to be proccessed
* @returns string capitalized string
*/
export function capitalizeFirstLetter(word: string) {
if (!word || !isString(word) || word.length === 0) {
return word;
}
return word[0].toUpperCase() + word.slice(1);
}
/**
* check str passed in is a string type of not
* @param str obj to be checked
* @returns boolean
*/
export function isString(str: any): boolean {
return {}.toString.call(str) === '[object String]';
}
/**
* check if obj is type of variable structure
* @param obj object to be checked
* @returns boolean
*/
export function isVariable(obj: any) {
if (!obj || Array.isArray(obj)) {
return false;
}
return typeof obj === 'object' && obj?.type === 'variable';
}
/**
* i18n i18n
* @param i18nInfo object
* @param self context
*/
export function parseI18n(i18nInfo: any, self: any) {
return parseExpression({
type: EXPRESSION_TYPE.JSEXPRESSION,
value: `this.i18n('${i18nInfo.key}')`,
}, self);
}
/**
* for each key in targetObj, run fn with the value of the value, and the context paased in.
* @param targetObj object that keys will be for each
* @param fn function that process each item
* @param context
*/
export function forEach(targetObj: any, fn: any, context?: any) {
if (!targetObj || Array.isArray(targetObj) || isString(targetObj) || typeof targetObj !== 'object') {
return;
}
Object.keys(targetObj).forEach((key) => fn.call(context, targetObj[key], key));
}
interface IParseOptions {
thisRequiredInJSE?: boolean;
logScope?: string;
}
export function parseData(schema: unknown, self: any, options: IParseOptions = {}): any {
if (isJSExpression(schema)) {
return parseExpression({
str: schema,
self,
thisRequired: options.thisRequiredInJSE,
logScope: options.logScope,
});
} else if (isI18nData(schema)) {
return parseI18n(schema, self);
} else if (typeof schema === 'string') {
return schema.trim();
} else if (Array.isArray(schema)) {
return schema.map((item) => parseData(item, self, options));
} else if (typeof schema === 'function') {
return schema.bind(self);
} else if (typeof schema === 'object') {
// 对于undefined及null直接返回
if (!schema) {
return schema;
}
const res: any = {};
forEach(schema, (val: any, key: string) => {
if (key.startsWith('__')) {
return;
}
res[key] = parseData(val, self, options);
});
return res;
}
return schema;
}
/**
* process params for using in a url query
* @param obj params to be processed
* @returns string
*/
export function serializeParams(obj: any) {
let result: any = [];
forEach(obj, (val: any, key: any) => {
if (val === null || val === undefined || val === '') {
return;
}
if (typeof val === 'object') {
result.push(`${key}=${encodeURIComponent(JSON.stringify(val))}`);
} else {
result.push(`${key}=${encodeURIComponent(val)}`);
}
});
return result.join('&');
}

View File

@ -1,309 +0,0 @@
/* eslint-disable no-console */
/* eslint-disable max-len */
/* eslint-disable object-curly-newline */
import { isJSFunction } from '@alilc/lowcode-utils';
import { transformArrayToMap, transformStringToFunction } from './common';
import { jsonp, request, get, post } from './request';
import logger from './logger';
import { DataSource, DataSourceItem, IRendererAppHelper } from '../types';
const DS_STATUS = {
INIT: 'init',
LOADING: 'loading',
LOADED: 'loaded',
ERROR: 'error',
};
type DataSourceType = 'fetch' | 'jsonp';
/**
* do request for standard DataSourceType
* @param {DataSourceType} type type of DataSourceItem
* @param {any} options
*/
export function doRequest(type: DataSourceType, options: any) {
// eslint-disable-next-line prefer-const
let { uri, url, method = 'GET', headers, params, ...otherProps } = options;
otherProps = otherProps || {};
if (type === 'jsonp') {
return jsonp(uri, params, otherProps);
}
if (type === 'fetch') {
switch (method.toUpperCase()) {
case 'GET':
return get(uri, params, headers, otherProps);
case 'POST':
return post(uri, params, headers, otherProps);
default:
return request(uri, method, params, headers, otherProps);
}
}
logger.log(`Engine default dataSource does not support type:[${type}] dataSource request!`, options);
}
// TODO: according to protocol, we should implement errorHandler/shouldFetch/willFetch/requestHandler and isSync controll.
export class DataHelper {
/**
* host object that will be "this" object when excuting dataHandler
*
* @type {*}
* @memberof DataHelper
*/
host: any;
/**
* data source config
*
* @type {DataSource}
* @memberof DataHelper
*/
config: DataSource;
/**
* a parser function which will be called to process config data
* which eventually will call common/utils.processData() to process data
* (originalConfig) => parsedConfig
* @type {*}
* @memberof DataHelper
*/
parser: any;
/**
* config.list
*
* @type {any[]}
* @memberof DataHelper
*/
ajaxList: any[];
ajaxMap: any;
dataSourceMap: any;
appHelper: IRendererAppHelper;
constructor(comp: any, config: DataSource, appHelper: IRendererAppHelper, parser: any) {
this.host = comp;
this.config = config || {};
this.parser = parser;
this.ajaxList = config?.list || [];
this.ajaxMap = transformArrayToMap(this.ajaxList, 'id');
this.dataSourceMap = this.generateDataSourceMap();
this.appHelper = appHelper;
}
// 更新config只会更新配置状态保存
updateConfig(config = {}) {
this.config = config as DataSource;
this.ajaxList = (config as DataSource)?.list || [];
const ajaxMap: any = transformArrayToMap(this.ajaxList, 'id');
// 删除已经移除的接口
Object.keys(this.ajaxMap).forEach((key) => {
if (!ajaxMap[key]) {
delete this.dataSourceMap[key];
}
});
this.ajaxMap = ajaxMap;
// 添加未加入到dataSourceMap中的接口
this.ajaxList.forEach((item) => {
if (!this.dataSourceMap[item.id]) {
this.dataSourceMap[item.id] = {
status: DS_STATUS.INIT,
load: (...args: any) => {
// @ts-ignore
return this.getDataSource(item.id, ...args);
},
};
}
});
return this.dataSourceMap;
}
generateDataSourceMap() {
const res: any = {};
this.ajaxList.forEach((item) => {
res[item.id] = {
status: DS_STATUS.INIT,
load: (...args: any) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return this.getDataSource(item.id, ...args);
},
};
});
return res;
}
updateDataSourceMap(id: string, data: any, error: any) {
this.dataSourceMap[id].error = error || undefined;
this.dataSourceMap[id].data = data;
this.dataSourceMap[id].status = error ? DS_STATUS.ERROR : DS_STATUS.LOADED;
}
/**
* get all dataSourceItems which marked as isInit === true
* @private
* @returns
* @memberof DataHelper
*/
getInitDataSourseConfigs() {
const initConfigs = this.parser(this.ajaxList).filter((item: DataSourceItem) => {
// according to [spec](https://lowcode-engine.cn/lowcode), isInit should be boolean true to be working
if (item.isInit === true) {
this.dataSourceMap[item.id].status = DS_STATUS.LOADING;
return true;
}
return false;
});
return initConfigs;
}
/**
* process all dataSourceItems which marked as isInit === true, and get dataSource request results.
* @public
* @returns
* @memberof DataHelper
*/
getInitData() {
const initSyncData = this.getInitDataSourseConfigs();
// 所有 datasource 的 datahandler
return this.asyncDataHandler(initSyncData).then((res) => {
const { dataHandler } = this.config;
return this.handleData(null, dataHandler, res, null);
});
}
getDataSource(id: string, params: any, otherOptions: any, callback: any) {
const req = this.parser(this.ajaxMap[id]);
const options = req.options || {};
let callbackFn = callback;
let otherOptionsObj = otherOptions;
if (typeof otherOptions === 'function') {
callbackFn = otherOptions;
otherOptionsObj = {};
}
const { headers, ...otherProps } = otherOptionsObj || {};
if (!req) {
logger.warn(`getDataSource API named ${id} not exist`);
return;
}
return this.asyncDataHandler([
{
...req,
options: {
...options,
// 支持参数为array的情况当参数为array时不做参数合并
params:
Array.isArray(options.params) || Array.isArray(params)
? params || options.params
: {
...options.params,
...params,
},
headers: {
...options.headers,
...headers,
},
...otherProps,
},
},
])
.then((res: any) => {
try {
callbackFn && callbackFn(res && res[id]);
} catch (e) {
logger.error('load请求回调函数报错', e);
}
return res && res[id];
})
.catch((err) => {
try {
callbackFn && callbackFn(null, err);
} catch (e) {
logger.error('load请求回调函数报错', e);
}
return err;
});
}
asyncDataHandler(asyncDataList: any[]) {
return new Promise((resolve, reject) => {
const allReq: any[] = [];
asyncDataList.forEach((req) => {
const { id, type } = req;
// TODO: need refactoring to remove 'legao' related logic
if (!id || !type || type === 'legao') {
return;
}
allReq.push(req);
});
if (allReq.length === 0) {
resolve({});
}
const res: any = {};
Promise.all(
allReq.map((item: any) => {
return new Promise((innerResolve) => {
const { type, id, dataHandler, options } = item;
const fetchHandler = (data: any, error: any) => {
res[id] = this.handleData(id, dataHandler, data, error);
this.updateDataSourceMap(id, res[id], error);
innerResolve({});
};
const doFetch = (innerType: string, innerOptions: any) => {
doRequest(innerType as any, innerOptions)
?.then((data: any) => {
fetchHandler(data, undefined);
})
.catch((err: Error) => {
fetchHandler(undefined, err);
});
};
this.dataSourceMap[id].status = DS_STATUS.LOADING;
doFetch(type, options);
});
}),
).then(() => {
resolve(res);
}).catch((e) => {
reject(e);
});
});
}
/**
* process data using dataHandler
*
* @param {(string | null)} id request id, will be used in error message, can be null
* @param {*} dataHandler
* @param {*} data
* @param {*} error
* @returns
* @memberof DataHelper
*/
handleData(id: string | null, dataHandler: any, data: any, error: any) {
let dataHandlerFun = dataHandler;
if (isJSFunction(dataHandler)) {
dataHandlerFun = transformStringToFunction(dataHandler.value);
}
if (!dataHandlerFun || typeof dataHandlerFun !== 'function') {
return data;
}
try {
return dataHandlerFun.call(this.host, data, error);
} catch (e) {
if (id) {
logger.error(`[${id}]单个请求数据处理函数运行出错`, e);
} else {
logger.error('请求数据处理函数运行出错', e);
}
}
}
}

View File

@ -1,3 +0,0 @@
export * from './common';
export * from './data-helper';
export * from './request';

View File

@ -1,20 +0,0 @@
import { IPublicTypeJSExpression } from '@alilc/lowcode-types';
import { isJSExpression } from '@alilc/lowcode-utils';
// 1.渲染模式下loop 是数组,则按照数组长度渲染组件
// 2.设计模式下loop 需要长度大于 0按照循环模式渲染防止无法设计的情况
export default function isUseLoop(loop: null | any[] | IPublicTypeJSExpression, isDesignMode: boolean): boolean {
if (isJSExpression(loop)) {
return true;
}
if (!isDesignMode) {
return true;
}
if (!Array.isArray(loop)) {
return false;
}
return loop.length > 0;
}

View File

@ -1,3 +0,0 @@
import { Logger } from '@alilc/lowcode-utils';
export default new Logger({ level: 'warn', bizName: 'renderer' });

View File

@ -1,198 +0,0 @@
import 'whatwg-fetch';
import fetchJsonp from 'fetch-jsonp';
import { serializeParams } from '.';
/**
* this is a private method, export for testing purposes only.
*
* @export
* @param {*} dataAPI
* @param {*} params
* @returns
*/
export function buildUrl(dataAPI: any, params: any) {
const paramStr = serializeParams(params);
if (paramStr) {
return dataAPI.indexOf('?') > 0 ? `${dataAPI}&${paramStr}` : `${dataAPI}?${paramStr}`;
}
return dataAPI;
}
/**
* do Get request
*
* @export
* @param {*} dataAPI
* @param {*} [params={}]
* @param {*} [headers={}]
* @param {*} [otherProps={}]
* @returns
*/
export function get(dataAPI: any, params = {}, headers = {}, otherProps = {}) {
const processedHeaders = {
Accept: 'application/json',
...headers,
};
const url = buildUrl(dataAPI, params);
return request(url, 'GET', null, processedHeaders, otherProps);
}
/**
* do Post request
*
* @export
* @param {*} dataAPI
* @param {*} [params={}]
* @param {*} [headers={}]
* @param {*} [otherProps={}]
* @returns
*/
export function post(dataAPI: any, params = {}, headers: any = {}, otherProps = {}) {
const processedHeaders = {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
...headers,
};
const body = processedHeaders['Content-Type'].indexOf('application/json') > -1 || Array.isArray(params)
? JSON.stringify(params)
: serializeParams(params);
return request(
dataAPI,
'POST',
body,
processedHeaders,
otherProps,
);
}
/**
* do request
*
* @export
* @param {*} dataAPI
* @param {string} [method='GET']
* @param {*} data
* @param {*} [headers={}]
* @param {*} [otherProps={}]
* @returns
*/
export function request(dataAPI: any, method = 'GET', data: any, headers = {}, otherProps: any = {}) {
let processedHeaders = headers || {};
let payload = data;
if (method === 'PUT' || method === 'DELETE') {
processedHeaders = {
Accept: 'application/json',
'Content-Type': 'application/json',
...processedHeaders,
};
payload = JSON.stringify(payload || {});
}
return new Promise((resolve, reject) => {
if (otherProps.timeout) {
setTimeout(() => {
reject(new Error('timeout'));
}, otherProps.timeout);
}
fetch(dataAPI, {
method,
credentials: 'include',
headers: processedHeaders,
body: payload,
...otherProps,
})
.then((response) => {
switch (response.status) {
case 200:
case 201:
case 202:
return response.json();
case 204:
if (method === 'DELETE') {
return {
success: true,
};
} else {
return {
__success: false,
code: response.status,
};
}
case 400:
case 401:
case 403:
case 404:
case 406:
case 410:
case 422:
case 500:
return response
.json()
.then((res) => {
return {
__success: false,
code: response.status,
data: res,
};
})
.catch(() => {
return {
__success: false,
code: response.status,
};
});
default:
}
return null;
})
.then((json) => {
if (!json) {
reject(json);
return;
}
if (json.__success !== false) {
resolve(json);
} else {
// eslint-disable-next-line no-param-reassign
delete json.__success;
reject(json);
}
})
.catch((err) => {
reject(err);
});
});
}
/**
* do jsonp request
*
* @export
* @param {*} dataAPI
* @param {*} [params={}]
* @param {*} [otherProps={}]
* @returns
*/
export function jsonp(dataAPI: any, params = {}, otherProps = {}) {
return new Promise((resolve, reject) => {
const processedOtherProps = {
timeout: 5000,
...otherProps,
};
const url = buildUrl(dataAPI, params);
fetchJsonp(url, processedOtherProps)
.then((response) => {
response.json();
})
.then((json) => {
if (json) {
resolve(json);
} else {
reject();
}
})
.catch((err) => {
reject(err);
});
});
}

View File

@ -1,101 +0,0 @@
// @ts-nocheck
import adapter, { Env } from '../../src/adapter';
describe('test src/adapter ', () => {
it('adapter basic use works', () => {
expect(adapter).toBeTruthy();
});
it('isValidRuntime works', () => {
expect(adapter.isValidRuntime([] as any)).toBeFalsy();
expect(adapter.isValidRuntime('' as any)).toBeFalsy();
let invalidRuntime = {};
expect(() => adapter.isValidRuntime(invalidRuntime as any)).toThrowError(/Component/);
invalidRuntime = {
Component: {},
};
expect(() => adapter.isValidRuntime(invalidRuntime as any)).toThrowError(/PureComponent/);
invalidRuntime = {
Component: {},
PureComponent: {},
};
expect(() => adapter.isValidRuntime(invalidRuntime as any)).toThrowError(/createElement/);
invalidRuntime = {
Component: {},
PureComponent: {},
createElement: {},
};
expect(() => adapter.isValidRuntime(invalidRuntime as any)).toThrowError(/createContext/);
invalidRuntime = {
Component: {},
PureComponent: {},
createElement: {},
createContext: {},
};
expect(() => adapter.isValidRuntime(invalidRuntime as any)).toThrowError(/forwardRef/);
invalidRuntime = {
Component: {},
PureComponent: {},
createElement: {},
createContext: {},
forwardRef: {},
};
expect(() => adapter.isValidRuntime(invalidRuntime as any)).toThrowError(/findDOMNode/);
const validRuntime = {
Component: {},
PureComponent: {},
createElement: {},
createContext: {},
forwardRef: {},
findDOMNode: {},
};
expect(adapter.isValidRuntime(validRuntime as any)).toBeTruthy();
});
it('setRuntime/getRuntime works', () => {
const validRuntime = {
Component: {},
PureComponent: {},
createElement: {},
createContext: {},
forwardRef: {},
findDOMNode: {},
};
adapter.setRuntime(validRuntime as any);
expect(adapter.getRuntime()).toBe(validRuntime);
// won`t work when invalid runtime paased in.
adapter.setRuntime([] as any);
expect(adapter.getRuntime()).toBe(validRuntime);
});
it('setEnv/.env/isReact works', () => {
adapter.setEnv(Env.React);
expect(adapter.env).toBe(Env.React);
expect(adapter.isReact()).toBeTruthy();
});
it('setRenderers/getRenderers works', () => {
const mockRenderers = { BaseRenderer: {} as IBaseRenderComponent};
adapter.setRenderers(mockRenderers);
expect(adapter.getRenderers()).toBe(mockRenderers);
adapter.setRenderers(undefined);
expect(adapter.getRenderers()).toStrictEqual({});
});
it('setConfigProvider/getConfigProvider works', () => {
const mockConfigProvider = { a: 111 };
adapter.setConfigProvider(mockConfigProvider);
expect(adapter.getConfigProvider()).toBe(mockConfigProvider);
});
});

View File

@ -1,567 +0,0 @@
export default {
componentName: 'Page',
id: 'node_dockcviv8fo1',
props: {
ref: 'outterView',
autoLoading: true,
style: {
padding: '0 5px 0 5px',
},
},
fileName: 'test',
dataSource: {
list: [],
},
state: {
text: 'outter',
isShowDialog: false,
},
css: 'body {font-size: 12px;} .botton{width:100px;color:#ff00ff}',
lifeCycles: {
componentDidMount: {
type: 'JSFunction',
value: "function() {\n console.log('did mount');\n }",
},
componentWillUnmount: {
type: 'JSFunction',
value: "function() {\n console.log('will umount');\n }",
},
},
methods: {
testFunc: {
type: 'JSFunction',
value: "function() {\n console.log('test func');\n }",
},
onClick: {
type: 'JSFunction',
value: 'function() {\n this.setState({\n isShowDialog: true\n })\n }',
},
closeDialog: {
type: 'JSFunction',
value: 'function() {\n this.setState({\n isShowDialog: false\n })\n }',
},
},
children: [
{
componentName: 'Box',
id: 'node_dockcy8n9xed',
props: {
style: {
backgroundColor: 'rgba(31,56,88,0.1)',
padding: '12px 12px 12px 12px',
},
},
children: [
{
componentName: 'Box',
id: 'node_dockcy8n9xee',
props: {
style: {
padding: '12px 12px 12px 12px',
backgroundColor: '#ffffff',
},
},
children: [
{
componentName: 'Breadcrumb',
id: 'node_dockcy8n9xef',
props: {
prefix: 'next-',
maxNode: 100,
component: 'nav',
},
children: [
{
componentName: 'Breadcrumb.Item',
id: 'node_dockcy8n9xeg',
props: {
prefix: 'next-',
children: '首页',
},
},
{
componentName: 'Breadcrumb.Item',
id: 'node_dockcy8n9xei',
props: {
prefix: 'next-',
children: '品质中台',
},
},
{
componentName: 'Breadcrumb.Item',
id: 'node_dockcy8n9xek',
props: {
prefix: 'next-',
children: '商家品质页面管理',
},
},
{
componentName: 'Breadcrumb.Item',
id: 'node_dockcy8n9xem',
props: {
prefix: 'next-',
children: '质检知识条配置',
},
},
],
},
],
},
{
componentName: 'Box',
id: 'node_dockcy8n9xeo',
props: {
style: {
marginTop: '12px',
backgroundColor: '#ffffff',
},
},
children: [
{
componentName: 'Form',
id: 'node_dockcy8n9xep',
props: {
inline: true,
style: {
marginTop: '12px',
marginRight: '12px',
marginLeft: '12px',
},
__events: [],
},
children: [
{
componentName: 'Form.Item',
id: 'node_dockcy8n9xeq',
props: {
style: {
marginBottom: '0',
},
label: '类目名:',
},
children: [
{
componentName: 'Select',
id: 'node_dockcy8n9xer',
props: {
mode: 'single',
hasArrow: true,
cacheValue: true,
style: {
width: '150px',
},
},
},
],
},
{
componentName: 'Form.Item',
id: 'node_dockcy8n9xes',
props: {
style: {
marginBottom: '0',
},
label: '项目类型:',
},
children: [
{
componentName: 'Select',
id: 'node_dockcy8n9xet',
props: {
mode: 'single',
hasArrow: true,
cacheValue: true,
style: {
width: '200px',
},
},
},
],
},
{
componentName: 'Form.Item',
id: 'node_dockcy8n9xeu',
props: {
style: {
marginBottom: '0',
},
label: '项目 ID',
},
children: [
{
componentName: 'Input',
id: 'node_dockcy8n9xev',
props: {
hasBorder: true,
size: 'medium',
autoComplete: 'off',
style: {
width: '200px',
},
},
},
],
},
{
componentName: 'Button.Group',
id: 'node_dockcy8n9xew',
props: {},
children: [
{
componentName: 'Button',
id: 'node_dockcy8n9xex',
props: {
type: 'primary',
style: {
margin: '0 5px 0 5px',
},
htmlType: 'submit',
children: '搜索',
},
},
{
componentName: 'Button',
id: 'node_dockcy8n9xe10',
props: {
type: 'normal',
style: {
margin: '0 5px 0 5px',
},
htmlType: 'reset',
children: '清空',
},
},
],
},
],
},
],
},
{
componentName: 'Box',
id: 'node_dockcy8n9xe1f',
props: {
style: {
backgroundColor: '#ffffff',
paddingBottom: '24px',
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
},
},
children: [
{
componentName: 'Button',
id: 'node_dockd5nrh9p4',
props: {
type: 'primary',
size: 'medium',
htmlType: 'button',
component: 'button',
children: '新建配置',
style: {},
__events: [
{
type: 'componentEvent',
name: 'onClick',
relatedEventName: 'onClick',
},
],
onClick: {
type: 'JSFunction',
value: 'function(){ this.onClick() }',
},
},
},
],
},
{
componentName: 'Box',
id: 'node_dockd5nrh9p5',
props: {},
children: [
{
componentName: 'Table',
id: 'node_dockjielosj1',
props: {
showMiniPager: true,
showActionBar: true,
actionBar: [
{
title: '新增',
type: 'primary',
},
{
title: '编辑',
},
],
columns: [
{
dataKey: 'name',
width: 200,
align: 'center',
title: '姓名',
editType: 'text',
},
{
dataKey: 'age',
width: 200,
align: 'center',
title: '年龄',
},
{
dataKey: 'email',
width: 200,
align: 'center',
title: '邮箱',
},
],
data: [
{
name: '王小',
id: '1',
age: 15000,
email: 'aaa@abc.com',
},
{
name: '王中',
id: '2',
age: 25000,
email: 'bbb@abc.com',
},
{
name: '王大',
id: '3',
age: 35000,
email: 'ccc@abc.com',
},
],
actionTitle: '操作',
actionWidth: 180,
actionType: 'link',
actionFixed: 'right',
actionHidden: false,
maxWebShownActionCount: 2,
actionColumn: [
{
title: '编辑',
callback: {
type: 'JSFunction',
value: '(rowData, action, table) => {\n return table.editRow(rowData).then((row) => {\n console.log(row);\n });\n }',
},
device: [
'desktop',
],
},
{
title: '保存',
callback: {
type: 'JSFunction',
value: '(rowData, action, table) => { \nreturn table.saveRow(rowData).then((row) => { \nconsole.log(row); \n}); \n}',
},
mode: 'EDIT',
},
],
},
},
{
componentName: 'Box',
id: 'node_dockd5nrh9pg',
props: {
style: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
},
},
children: [
{
componentName: 'Pagination',
id: 'node_dockd5nrh9pf',
props: {
prefix: 'next-',
type: 'normal',
shape: 'normal',
size: 'medium',
defaultCurrent: 1,
total: 100,
pageShowCount: 5,
pageSize: 10,
pageSizePosition: 'start',
showJump: true,
style: {},
},
},
],
},
],
},
],
},
{
componentName: 'Dialog',
id: 'node_dockcy8n9xe1h',
props: {
prefix: 'next-',
footerAlign: 'right',
footerActions: [
'ok',
'cancel',
],
closeable: 'esc,close',
hasMask: true,
align: 'cc cc',
minMargin: 40,
visible: {
type: 'JSExpression',
value: 'this.state.isShowDialog',
},
title: '标题',
events: [],
__events: [
{
type: 'componentEvent',
name: 'onCancel',
relatedEventName: 'closeDialog',
},
{
type: 'componentEvent',
name: 'onClose',
relatedEventName: 'closeDialog',
},
{
type: 'componentEvent',
name: 'onOk',
relatedEventName: 'testFunc',
},
],
onCancel: {
type: 'JSFunction',
value: 'function(){ this.closeDialog() }',
},
onClose: {
type: 'JSFunction',
value: 'function(){ this.closeDialog() }',
},
onOk: {
type: 'JSFunction',
value: 'function(){ this.testFunc() }',
},
},
children: [
{
componentName: 'Form',
id: 'node_dockd5nrh9pi',
props: {
inline: false,
labelAlign: 'top',
labelTextAlign: 'right',
size: 'medium',
},
children: [
{
componentName: 'Form.Item',
id: 'node_dockd5nrh9pj',
props: {
style: {
marginBottom: '0',
minWidth: '200px',
minHeight: '28px',
},
label: '商品类目',
},
children: [
{
componentName: 'Select',
id: 'node_dockd5nrh9pk',
props: {
mode: 'single',
hasArrow: true,
cacheValue: true,
},
},
],
},
{
componentName: 'Form.Item',
id: 'node_dockd5nrh9pl',
props: {
style: {
marginBottom: '0',
minWidth: '200px',
minHeight: '28px',
},
label: '商品类目',
},
children: [
{
componentName: 'Select',
id: 'node_dockd5nrh9pm',
props: {
mode: 'single',
hasArrow: true,
cacheValue: true,
},
},
],
},
{
componentName: 'Form.Item',
id: 'node_dockd5nrh9pn',
props: {
style: {
marginBottom: '0',
minWidth: '200px',
minHeight: '28px',
},
label: '商品类目',
asterisk: true,
},
children: [
{
componentName: 'Select',
id: 'node_dockd5nrh9po',
props: {
mode: 'single',
hasArrow: true,
cacheValue: true,
},
},
],
},
{
componentName: 'Form.Item',
id: 'node_dockd5nrh9pp',
props: {
style: {
marginBottom: '0',
minWidth: '200px',
minHeight: '28px',
},
label: '商品类目',
},
children: [
{
componentName: 'Input',
id: 'node_dockd5nrh9pr',
props: {
hasBorder: true,
size: 'medium',
autoComplete: 'off',
},
},
],
},
],
},
],
},
{
componentName: 'ErrorComponent',
id: 'node_dockd5nrh9pr',
props: {
name: 'error',
},
},
],
};

View File

@ -1,7 +0,0 @@
if (!process.env.LISTENING_TO_UNHANDLED_REJECTION) {
process.on('unhandledRejection', reason => {
throw reason;
});
// Avoid memory leak by adding too many listeners
process.env.LISTENING_TO_UNHANDLED_REJECTION = 'true';
}

View File

@ -1,148 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`children this.props.children is array 1`] = `
<div>
<div
content="content"
>
content
</div>
<div
content="content"
>
content
</div>
</div>
`;
exports[`lifecycle leaf change and make componentWillReceiveProps 1`] = `
<div>
<div
__id="text6"
__tag="222"
componentId="text6"
content="content new leaf"
>
content new leaf
</div>
</div>
`;
exports[`lifecycle props change and make componentWillReceiveProps 1`] = `
<div>
<div
content="content"
>
content
</div>
</div>
`;
exports[`lifecycle props change and make componentWillReceiveProps 2`] = `
<div>
<div
content="content 123"
>
content 123
</div>
</div>
`;
exports[`lifecycle props change and make componentWillReceiveProps 3`] = `
<div>
<div
__tag="111"
content="content 123"
>
content 123
</div>
</div>
`;
exports[`mini unit render leaf has a loop, render from parent 1`] = `
<div>
this is a new children
</div>
`;
exports[`mini unit render make text props change 1`] = `
<div>
<div
content="content"
>
content
</div>
</div>
`;
exports[`mini unit render make text props change 2`] = `
<div
newPropKey="newPropValue"
/>
`;
exports[`mini unit render parent is a mock leaf 1`] = `
<div>
<div
content="new content to mock"
>
new content to mock
</div>
</div>
`;
exports[`mini unit render props has new children 1`] = `
<div>
children 01
children 02
</div>
`;
exports[`onChildrenChange children is array string 1`] = `
<div>
onChildrenChange content 01
onChildrenChange content 02
</div>
`;
exports[`onPropChange change textNode [key:___condition___] props, but not hidden component 1`] = `
<div>
<div
content="content"
>
content
</div>
</div>
`;
exports[`onPropChange change textNode [key:___condition___] props, hide textNode component 1`] = `<div />`;
exports[`onPropChange change textNode [key:content], content in this.props but not in leaf.export result 1`] = `
<div>
<div
content="content"
>
content
</div>
</div>
`;
exports[`onPropChange change textNode [key:content], content in this.props but not in leaf.export result 2`] = `
<div>
<div
content={null}
/>
</div>
`;
exports[`onVisibleChange visible is false 1`] = `<div />`;
exports[`onVisibleChange visible is true 1`] = `
<div>
<div
content="content"
>
content
</div>
</div>
`;

View File

@ -1,604 +0,0 @@
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';
import { parseData } from '../../src/utils';
let rerenderCount = 0;
const nodeMap = new Map();
const makeSnapshot = (component) => {
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
}
const baseRenderer: any = {
__debug () {},
__getComponentProps (schema: any) {
return schema.props;
},
__getSchemaChildrenVirtualDom (schema: any) {
return schema.children;
},
context: {
engine: {
createElement,
}
},
props: {
__host: {},
getNode: (id) => nodeMap.get(id),
__container: {
rerender: () => {
rerenderCount = 1 + rerenderCount;
},
autoRepaintNode: true,
},
documentId: '01'
},
__parseData (data, scope) {
return parseData(data, scope, {});
}
}
let Div, DivNode, Text, TextNode, component, textSchema, divSchema;
let id = 0;
beforeEach(() => {
textSchema = {
id: 'text' + id,
props: {
content: 'content'
},
};
divSchema = {
id: 'div' + id,
};
id++;
Div = leafWrapper(components.Div as any, {
schema: divSchema,
baseRenderer,
componentInfo: {},
scope: {},
});
DivNode = new Node(divSchema);
TextNode = new Node(textSchema);
nodeMap.set(divSchema.id, DivNode);
nodeMap.set(textSchema.id, TextNode);
Text = leafWrapper(components.Text as any, {
schema: textSchema,
baseRenderer,
componentInfo: {},
scope: {},
});
component = renderer.create(
<Div _leaf={DivNode}>
<Text _leaf={TextNode} content="content"></Text>
</Div>
);
});
afterEach(() => {
component.unmount(component);
});
describe('onPropChange', () => {
it('change textNode [key:content] props', () => {
TextNode.emitPropChange({
key: 'content',
newValue: 'new content',
} as any);
const root = component.root;
expect(root.findByType(components.Text).props.content).toEqual('new content')
});
it('change textNode [key:___condition___] props, hide textNode component', () => {
// mock leaf?.export result
TextNode.schema.condition = false;
TextNode.emitPropChange({
key: '___condition___',
newValue: false,
} as any);
makeSnapshot(component);
});
it('change textNode [key:___condition___] props, but not hidden component', () => {
TextNode.schema.condition = true;
TextNode.emitPropChange({
key: '___condition___',
newValue: false,
} as any);
makeSnapshot(component);
});
it('change textNode [key:content], content in this.props but not in leaf.export result', () => {
makeSnapshot(component);
delete TextNode.schema.props.content;
TextNode.emitPropChange({
key: 'content',
newValue: null,
} as any, true);
makeSnapshot(component);
const root = component.root;
const TextInst = root.findByType(components.Text);
expect(TextInst.props.content).toBeNull();
});
it('change textNode [key:___loop___], make rerender', () => {
expect(leafWrapper(components.Text as any, {
schema: textSchema,
baseRenderer,
componentInfo: {},
scope: {},
})).toEqual(Text);
const nextRerenderCount = rerenderCount + 1;
TextNode.emitPropChange({
key: '___loop___',
newValue: 'new content',
} as any);
expect(rerenderCount).toBe(nextRerenderCount);
expect(leafWrapper(components.Text as any, {
schema: textSchema,
baseRenderer,
componentInfo: {},
scope: {},
})).not.toEqual(Text);
});
});
describe('lifecycle', () => {
it('props change and make componentWillReceiveProps', () => {
makeSnapshot(component);
// 没有 __tag 标识
component.update((
<Div _leaf={DivNode}>
<Text _leaf={TextNode} content="content 123"></Text>
</Div>
));
makeSnapshot(component);
// 有 __tag 标识
component.update((
<Div _leaf={DivNode}>
<Text _leaf={TextNode} __tag="111" content="content 123"></Text>
</Div>
));
makeSnapshot(component);
});
it('leaf change and make componentWillReceiveProps', () => {
const newTextNodeLeaf = new Node(textSchema);
nodeMap.set(textSchema.id, newTextNodeLeaf);
component.update((
<Div _leaf={DivNode}>
<Text componentId={textSchema.id} __tag="222" content="content 123"></Text>
</Div>
));
newTextNodeLeaf.emitPropChange({
key: 'content',
newValue: 'content new leaf',
});
makeSnapshot(component);
});
});
describe('mini unit render', () => {
let miniRenderSchema, MiniRenderDiv, MiniRenderDivNode;
beforeEach(() => {
miniRenderSchema = {
id: 'miniDiv' + id,
};
MiniRenderDiv = leafWrapper(components.MiniRenderDiv as any, {
schema: miniRenderSchema,
baseRenderer,
componentInfo: {},
scope: {},
});
MiniRenderDivNode = new Node(miniRenderSchema, {
componentMeta: {
isMinimalRenderUnit: true,
},
});
TextNode = new Node(textSchema, {
parent: MiniRenderDivNode,
});
nodeMap.set(miniRenderSchema.id, MiniRenderDivNode);
nodeMap.set(textSchema.id, TextNode);
component = renderer.create(
<MiniRenderDiv _leaf={MiniRenderDivNode}>
<Text _leaf={TextNode} content="content"></Text>
</MiniRenderDiv>
);
})
it('make text props change', () => {
if (!MiniRenderDivNode.schema.props) {
MiniRenderDivNode.schema.props = {};
}
MiniRenderDivNode.schema.props['newPropKey'] = 'newPropValue';
makeSnapshot(component);
const inst = component.root;
const TextInst = inst.findByType(Text).children[0];
TextNode.emitPropChange({
key: 'content',
newValue: 'new content',
} as any);
expect((TextInst as any)?._fiber.stateNode.renderUnitInfo).toEqual({
singleRender: false,
minimalUnitId: 'miniDiv' + id,
minimalUnitName: undefined,
});
makeSnapshot(component);
});
it('dont render mini render component', () => {
const TextNode = new Node(textSchema, {
parent: new Node({
id: 'random',
}, {
componentMeta: {
isMinimalRenderUnit: true,
},
}),
});
nodeMap.set(textSchema.id, TextNode);
renderer.create(
<div>
<Text _leaf={TextNode} content="content"></Text>
</div>
);
const nextCount = rerenderCount + 1;
TextNode.emitPropChange({
key: 'content',
newValue: 'new content',
} as any);
expect(rerenderCount).toBe(nextCount);
});
it('leaf is a mock function', () => {
const TextNode = new Node(textSchema, {
parent: {
isEmpty: () => false,
}
});
renderer.create(
<div>
<Text _leaf={TextNode} content="content"></Text>
</div>
);
TextNode.emitPropChange({
key: 'content',
newValue: 'new content',
} as any);
});
it('change component leaf isRoot is true', () => {
const TextNode = new Node(textSchema, {
isRoot: true,
isRootNode: true,
});
nodeMap.set(textSchema.id, TextNode);
const component = renderer.create(
<Text _leaf={TextNode} content="content"></Text>
);
const inst = component.root;
TextNode.emitPropChange({
key: 'content',
newValue: 'new content',
} as any);
expect((inst.children[0] as any)?._fiber.stateNode.renderUnitInfo).toEqual({
singleRender: true,
});
});
it('change component leaf parent isRoot is true', () => {
const TextNode = new Node(textSchema, {
parent: new Node({
id: 'first-parent',
}, {
componentMeta: {
isMinimalRenderUnit: true,
},
parent: new Node({
id: 'rootId',
}, {
isRoot: true,
isRootNode: true
}),
})
});
nodeMap.set(textSchema.id, TextNode);
const component = renderer.create(
<Text _leaf={TextNode} content="content"></Text>
);
const inst = component.root;
TextNode.emitPropChange({
key: 'content',
newValue: 'new content',
} as any);
expect((inst.children[0] as any)?._fiber.stateNode.renderUnitInfo).toEqual({
singleRender: false,
minimalUnitId: 'first-parent',
minimalUnitName: undefined,
});
});
it('parent is a mock leaf', () => {
const MiniRenderDivNode = {
isMock: true,
};
const component = renderer.create(
<MiniRenderDiv _leaf={MiniRenderDivNode}>
<Text _leaf={TextNode} content="content"></Text>
</MiniRenderDiv>
);
TextNode.emitPropChange({
key: 'content',
newValue: 'new content to mock',
} as any);
makeSnapshot(component);
});
it('props has new children', () => {
MiniRenderDivNode.schema.props.children = [
'children 01',
'children 02',
];
TextNode.emitPropChange({
key: 'content',
newValue: 'props'
});
makeSnapshot(component);
});
it('leaf has a loop, render from parent', () => {
MiniRenderDivNode = new Node(miniRenderSchema, {});
TextNode = new Node(textSchema, {
parent: MiniRenderDivNode,
hasLoop: true,
});
nodeMap.set(textSchema.id, TextNode);
nodeMap.set(miniRenderSchema.id, MiniRenderDivNode);
component = renderer.create(
<MiniRenderDiv _leaf={MiniRenderDivNode}>
<Text _leaf={TextNode} content="content"></Text>
</MiniRenderDiv>
);
MiniRenderDivNode.schema.children = ['this is a new children'];
TextNode.emitPropChange({
key: 'content',
newValue: '1',
});
makeSnapshot(component);
});
});
describe('component cache', () => {
it('get different component with same is and different doc id', () => {
const baseRenderer02 = {
...baseRenderer,
props: {
...baseRenderer.props,
documentId: '02',
}
}
const Div3 = leafWrapper(components.Div as any, {
schema: divSchema,
baseRenderer: baseRenderer02,
componentInfo: {},
scope: {},
});
expect(Div).not.toEqual(Div3);
});
it('get component again and get ths cache component', () => {
const Div2 = leafWrapper(components.Div as any, {
schema: divSchema,
baseRenderer,
componentInfo: {},
scope: {},
});
expect(Div).toEqual(Div2);
});
});
describe('onVisibleChange', () => {
it('visible is false', () => {
TextNode.emitVisibleChange(false);
makeSnapshot(component);
});
it('visible is true', () => {
TextNode.emitVisibleChange(true);
makeSnapshot(component);
});
});
describe('children', () => {
it('this.props.children is array', () => {
const component = renderer.create(
<Div _leaf={DivNode}>
<Text _leaf={TextNode} content="content"></Text>
<Text _leaf={TextNode} content="content"></Text>
</Div>
);
makeSnapshot(component);
});
});
describe('onChildrenChange', () => {
it('children is array string', () => {
DivNode.schema.children = [
'onChildrenChange content 01',
'onChildrenChange content 02'
]
DivNode.emitChildrenChange();
makeSnapshot(component);
});
it('children is 0', () => {
DivNode.schema.children = 0
DivNode.emitChildrenChange();
const componentInstance = component.root;
expect(componentInstance.findByType(components.Div).props.children).toEqual(0);
});
it('children is false', () => {
DivNode.schema.children = false
DivNode.emitChildrenChange();
const componentInstance = component.root;
expect(componentInstance.findByType(components.Div).props.children).toEqual(false);
});
it('children is []', () => {
DivNode.schema.children = []
DivNode.emitChildrenChange();
const componentInstance = component.root;
expect(componentInstance.findByType(components.Div).props.children).toEqual([]);
});
it('children is null', () => {
DivNode.schema.children = null
DivNode.emitChildrenChange();
const componentInstance = component.root;
expect(componentInstance.findByType(components.Div).props.children).toEqual(null);
});
it('children is undefined', () => {
DivNode.schema.children = undefined;
DivNode.emitChildrenChange();
const componentInstance = component.root;
expect(componentInstance.findByType(components.Div).props.children).toEqual(undefined);
});
});
describe('not render leaf', () => {
let miniRenderSchema, MiniRenderDiv, MiniRenderDivNode;
beforeEach(() => {
miniRenderSchema = {
id: 'miniDiv' + id,
};
MiniRenderDivNode = new Node(miniRenderSchema, {
componentMeta: {
isMinimalRenderUnit: true,
},
});
nodeMap.set(miniRenderSchema.id, MiniRenderDivNode);
MiniRenderDiv = leafWrapper(components.MiniRenderDiv as any, {
schema: miniRenderSchema,
baseRenderer,
componentInfo: {},
scope: {},
});
TextNode = new Node(textSchema, {
parent: MiniRenderDivNode,
});
component = renderer.create(
<Text _leaf={TextNode} content="content"></Text>
);
});
it('onPropsChange', () => {
const nextCount = rerenderCount + 1;
MiniRenderDivNode.emitPropChange({
key: 'any',
newValue: 'any',
});
expect(rerenderCount).toBe(nextCount);
});
it('onChildrenChange', () => {
const nextCount = rerenderCount + 1;
MiniRenderDivNode.emitChildrenChange({
key: 'any',
newValue: 'any',
});
expect(rerenderCount).toBe(nextCount);
});
it('onVisibleChange', () => {
const nextCount = rerenderCount + 1;
MiniRenderDivNode.emitVisibleChange(true);
expect(rerenderCount).toBe(nextCount);
});
});

View File

@ -1,221 +0,0 @@
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,184 +0,0 @@
export const sampleSchema = {
"componentName": "Page",
"id": "node_ockyigdqxl1",
"docId": "dockyigdqxl",
"props": {
"templateVersion": "1.0.0",
"containerStyle": {},
"pageStyle": {
"backgroundColor": "#f2f3f5"
},
"className": "_css_pseudo_node_ockyigdqxl1"
},
"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": {},
"lifeCycles": {},
"hidden": false,
"title": "",
"isLocked": false,
"condition": true,
"conditionGroup": "",
"children": [
{
"componentName": "RootHeader",
"id": "node_ockyigdqxl2",
"docId": "dockyigdqxl",
"props": {},
"hidden": false,
"title": "",
"isLocked": false,
"condition": true,
"conditionGroup": ""
},
{
"componentName": "RootContent",
"id": "node_ockyigdqxl3",
"docId": "dockyigdqxl",
"props": {
"contentMargin": "20",
"contentPadding": "20",
"contentBgColor": "white"
},
"hidden": false,
"title": "",
"isLocked": false,
"condition": true,
"conditionGroup": "",
"children": [
{
"componentName": "Button",
"id": "node_ockyigdqxl5",
"docId": "dockyigdqxl",
"props": {
"content": "按 钮",
"type": "primary",
"size": "medium",
"behavior": "NORMAL",
"__style__": {},
"fieldId": "button_kyige3yf",
"events": {
"ignored": true
},
"baseIcon": "",
"otherIcon": "",
"loading": false,
"triggerEventsWhenLoading": false,
"className": "_css_pseudo_node_ockyigdqxl5"
},
"hidden": false,
"title": "",
"isLocked": false,
"condition": true,
"conditionGroup": ""
}
]
},
{
"componentName": "RootFooter",
"id": "node_ockyigdqxl4",
"docId": "dockyigdqxl",
"props": {},
"hidden": false,
"title": "",
"isLocked": false,
"condition": true,
"conditionGroup": ""
}
]
};

View File

@ -1,5 +0,0 @@
module.exports = {
process() {
return '';
},
};

View File

@ -1,222 +0,0 @@
import React, { Component, createElement, forwardRef, PureComponent, createContext } from 'react';
const mockGetRenderers = jest.fn();
const mockGetRuntime = jest.fn();
const mockParseExpression = jest.fn();
jest.mock('../../src/adapter', () => {
return {
getRenderers: () => { return mockGetRenderers();},
getRuntime: () => { return mockGetRuntime();},
};
});
jest.mock('../../src/utils', () => {
const originalUtils = jest.requireActual('../../src/utils');
return {
...originalUtils,
parseExpression: (...args) => { mockParseExpression(args);},
};
});
import baseRendererFactory from '../../src/renderer/base';
import { IBaseRendererProps } from '../../src/types';
import TestRenderer from 'react-test-renderer';
import components from '../utils/components';
import schema from '../fixtures/schema/basic';
describe('Base Render factory', () => {
it('customBaseRenderer logic works', () => {
mockGetRenderers.mockReturnValue({BaseRenderer: {}});
const baseRenderer = baseRendererFactory();
expect(mockGetRenderers).toBeCalledTimes(1);
expect(baseRenderer).toStrictEqual({});
mockGetRenderers.mockClear();
});
});
describe('Base Render methods', () => {
let RendererClass;
const mockRendererFactory = () => {
return class extends Component {
constructor(props: IBaseRendererProps, context: any) {
super(props, context);
}
}
}
beforeEach(() => {
const mockRnederers = {
PageRenderer: mockRendererFactory(),
ComponentRenderer: mockRendererFactory(),
BlockRenderer: mockRendererFactory(),
AddonRenderer: mockRendererFactory(),
TempRenderer: mockRendererFactory(),
DivRenderer: mockRendererFactory(),
};
mockGetRenderers.mockReturnValue(mockRnederers);
mockGetRuntime.mockReturnValue({
Component,
createElement,
PureComponent,
createContext,
forwardRef,
});
RendererClass = baseRendererFactory();
})
afterEach(() => {
mockGetRenderers.mockClear();
})
it('should excute lifecycle.getDerivedStateFromProps when defined', () => {
const mockGetDerivedStateFromProps = {
type: 'JSFunction',
value: 'function() {\n console.log(\'did mount\');\n }',
};
const mockSchema = schema;
(mockSchema.lifeCycles as any).getDerivedStateFromProps = mockGetDerivedStateFromProps;
// const originalUtils = jest.requireActual('../../src/utils');
// mockParseExpression.mockImplementation(originalUtils.parseExpression);
const component = TestRenderer.create(
<RendererClass
__schema={mockSchema}
components={components as any}
thisRequiredInJSE={false}
a='1'
/>);
// console.log(component.root.props.a);
// component.update(<RendererClasssnippets
// schema={mockSchema}
// components={components as any}
// thisRequiredInJSE={false}
// a='2'
// />);
// console.log(component.root.props.a);
// expect(mockParseExpression).toHaveBeenCalledWith(mockGetDerivedStateFromProps, expect.anything())
// test lifecycle.getDerivedStateFromProps is null
// test lifecycle.getDerivedStateFromProps is JSExpression
// test lifecycle.getDerivedStateFromProps is JSFunction
// test lifecycle.getDerivedStateFromProps is function
});
// it('should excute lifecycle.getSnapshotBeforeUpdate when defined', () => {
// });
// it('should excute lifecycle.componentDidMount when defined', () => {
// });
// it('should excute lifecycle.componentDidUpdate when defined', () => {
// });
// it('should excute lifecycle.componentWillUnmount when defined', () => {
// });
// it('should excute lifecycle.componentDidCatch when defined', () => {
// });
// it('__executeLifeCycleMethod should work', () => {
// });
// it('reloadDataSource should work', () => {
// });
// it('shouldComponentUpdate should work', () => {
// });
// it('_getComponentView should work', () => {
// });
// it('__bindCustomMethods should work', () => {
// });
// it('__generateCtx should work', () => {
// });
// it('__parseData should work', () => {
// });
// it('__initDataSource should work', () => {
// });
// it('__initI18nAPIs should work', () => {
// });
// it('__writeCss should work', () => {
// });
// it('__render should work', () => {
// });
// it('getSchemaChildren should work', () => {
// });
// it('__createDom should work', () => {
// });
// it('__createVirtualDom should work', () => {
// });
// it('__componentHOCs should work', () => {
// });
// it('__getSchemaChildrenVirtualDom should work', () => {
// });
// it('__getComponentProps should work', () => {
// });
// it('__createLoopVirtualDom should work', () => {
// });
// it('__designModeIsDesign should work', () => {
// });
// it('__parseProps should work', () => {
// });
// it('$ should work', () => {
// });
// it('__renderContextProvider should work', () => {
// });
// it('__renderContextConsumer should work', () => {
// });
// it('__getHOCWrappedComponent should work', () => {
// });
// it('__renderComp should work', () => {
// });
// it('__renderContent should work', () => {
// });
// it('__checkSchema should work', () => {
// });
// it('requestHandlersMap should work', () => {
// });
// it('utils should work', () => {
// });
// it('constants should work', () => {
// });
// it('history should work', () => {
// });
// it('location should work', () => {
// });
// it('match should work', () => {
// });
});

View File

@ -1,447 +0,0 @@
import React from 'react';
import renderer from 'react-test-renderer';
import schema from '../fixtures/schema/basic';
import '../utils/react-env-init';
import rendererFactory from '../../src/renderer/renderer';
import components from '../utils/components';
const Renderer = rendererFactory();
function getComp(schema, comp = null, others = {}): Promise<{
component,
inst,
}> {
return new Promise((resolve, reject) => {
const component = renderer.create(
<Renderer
schema={schema}
components={components as any}
{...others}
/>);
const componentInstance = component.root;
setTimeout(() => {
resolve({
inst: comp ? componentInstance.findAllByType(comp) : null,
component,
});
}, 20);
})
}
beforeEach(() => {
});
let componentSnapshot;
afterEach(() => {
if (componentSnapshot) {
let tree = componentSnapshot.toJSON();
expect(tree).toMatchSnapshot();
componentSnapshot = null;
}
});
describe('Base Render', () => {
it('renderComp', () => {
const content = (
<Renderer
schema={schema as any}
components={components as any}
/>);
const tree = renderer.create(content).toJSON();
expect(tree).toMatchSnapshot();
});
});
describe('JSExpression', () => {
it('base props', (done) => {
const schema = {
componentName: 'Page',
props: {},
children: [
{
componentName: "Div",
props: {
className: 'div-ut',
text: "123",
visible: true,
}
}
]
};
getComp(schema, components.Div).then(({ component, inst }) => {
expect(inst[0].props.text).toBe('123');
expect(inst[0].props.visible).toBeTruthy();
componentSnapshot = component;
done();
});
});
it('JSExpression props', (done) => {
const schema = {
componentName: 'Page',
props: {},
state: {
isShowDialog: true,
},
children: [
{
componentName: "Div",
props: {
className: "div-ut",
visible: {
type: 'JSExpression',
value: 'this.state.isShowDialog',
},
}
}
]
};
getComp(schema, components.Div).then(({ component, inst }) => {
expect(inst[0].props.visible).toBeTruthy();
componentSnapshot = component;
done();
});
});
it('JSExpression props with loop', (done) => {
const schema = {
componentName: 'Page',
props: {},
state: {
isShowDialog: true,
},
children: [
{
componentName: "Div",
loop: [
{
name: '1',
},
{
name: '2'
}
],
props: {
className: "div-ut",
name1: {
type: 'JSExpression',
value: 'this.item.name',
},
name2: {
type: 'JSExpression',
value: 'item.name',
},
}
}
]
};
getComp(schema, components.Div, {
thisRequiredInJSE: false,
}).then(({ component, inst }) => {
// expect(inst[0].props.visible).toBeTruthy();
expect(inst.length).toEqual(2);
[1, 2].forEach((i) => {
expect(inst[0].props[`name${i}`]).toBe('1');
expect(inst[1].props[`name${i}`]).toBe('2');
})
componentSnapshot = component;
done();
});
});
it('JSExpression props with loop, and thisRequiredInJSE is true', (done) => {
const schema = {
componentName: 'Page',
props: {},
state: {
isShowDialog: true,
},
children: [
{
componentName: "Div",
loop: [
{
name: '1',
},
{
name: '2'
}
],
props: {
className: "div-ut",
name1: {
type: 'JSExpression',
value: 'this.item.name',
},
name2: {
type: 'JSExpression',
value: 'item.name',
},
}
}
]
};
getComp(schema, components.Div).then(({ component, inst }) => {
expect(inst.length).toEqual(2);
[0, 1].forEach((i) => {
expect(inst[i].props[`name1`]).toBe(i + 1 + '');
expect(inst[i].props[`name2`]).toBe(undefined);
})
componentSnapshot = component;
done();
});
});
// it('JSFunction props with loop', (done) => {
// const schema = {
// componentName: 'Page',
// props: {},
// state: {
// isShowDialog: true,
// },
// children: [
// {
// componentName: "Div",
// loop: [
// {
// name: '1',
// },
// {
// name: '2'
// }
// ],
// props: {
// className: "div-ut",
// onClick1: {
// type: 'JSFunction',
// value: '() => this.item.name',
// },
// onClick2: {
// type: 'JSFunction',
// value: 'function(){ return this.item.name }',
// },
// onClick3: {
// type: 'JSFunction',
// value: 'function(){ return item.name }',
// },
// onClick4: {
// type: 'JSFunction',
// value: '() => item.name',
// }
// }
// }
// ]
// };
// getComp(schema, components.Div).then(({ component, inst }) => {
// // expect(inst[0].props.visible).toBeTruthy();
// expect(inst.length).toEqual(2);
// [1, 2, 3, 4].forEach((i) => {
// expect(inst[0].props[`onClick${i}`]()).toBe('1');
// expect(inst[1].props[`onClick${i}`]()).toBe('2');
// })
// componentSnapshot = component;
// done();
// });
// });
it('JSFunction props', (done) => {
const schema = {
componentName: 'Page',
props: {},
state: {
isShowDialog: true,
},
children: [
{
componentName: "Div",
props: {
className: "div-ut",
onClick: {
type: 'JSFunction',
value: 'function() {return this.state.isShowDialog}',
},
}
}
]
};
getComp(schema, components.Div).then(({ component, inst }) => {
expect(!!inst[0].props.onClick).toBeTruthy();
expect(inst[0].props.onClick()).toBeTruthy();
componentSnapshot = component;
done();
});
});
it('JSSlot has loop', (done) => {
const schema = {
componentName: "Page",
props: {},
children: [
{
componentName: "SlotComponent",
id: "node_k8bnubvz",
props: {
mobileSlot: {
type: "JSSlot",
title: "mobile容器",
name: "mobileSlot",
value: [
{
condition: true,
hidden: false,
children: [
{
condition: true,
hidden: false,
loopArgs: [
"item",
"index"
],
isLocked: false,
conditionGroup: "",
componentName: "Text",
id: "node_ocl1ao1o7w4",
title: "",
props: {
maxLine: 0,
showTitle: false,
className: "text_l1ao7pfb",
behavior: "NORMAL",
content: "这是一个低代码业务组件~",
__style__: ":root {\n font-size: 14px;\n color: #666;\n}",
fieldId: "text_l1ao7lvp"
}
}
],
loop: {
type: "JSExpression",
value: "this.state.content"
},
loopArgs: [
"item",
"index"
],
isLocked: false,
conditionGroup: "",
componentName: "Div",
id: "node_ocl1ao1o7w3",
title: "",
props: {
useFieldIdAsDomId: false,
customClassName: "",
className: "div_l1ao7pfc",
behavior: "NORMAL",
__style__: ":root {\n padding: 12px;\n background: #f2f2f2;\n border: 1px solid #ddd;\n}",
fieldId: "div_l1ao7lvq"
}
}
]
},
},
}
],
state: {
content: {
type: "JSExpression",
value: "[{}, {}, {}]",
},
},
};
getComp(schema, components.Div).then(({ component, inst }) => {
expect(inst.length).toBe(3);
componentSnapshot = component;
done();
});
})
});
describe("designMode", () => {
it('designMode:default', (done) => {
const schema = {
componentName: 'Page',
props: {},
children: [
{
componentName: "Div",
props: {
className: 'div-ut',
children: [
{
componentName: "Div",
visible: true,
props: {
className: 'div-ut-children',
}
}
]
}
}
]
};
getComp(schema, components.Div).then(({ component, inst }) => {
expect(inst.length).toBe(2);
expect(inst[0].props.className).toBe('div-ut');
expect(inst[1].props.className).toBe('div-ut-children');
componentSnapshot = component;
done();
});
});
it('designMode:design', (done) => {
const schema = {
componentName: 'Page',
props: {},
children: [
{
componentName: "Div",
id: '0',
props: {
className: 'div-ut',
children: [
{
componentName: "Div",
id: 'hiddenId',
hidden: true,
props: {
className: 'div-ut-children',
}
}
]
}
}
]
};
getComp(schema, components.Div, {
designMode: 'design',
getNode: (id) => {
if (id === 'hiddenId') {
return {
export() {
return {
hidden: true,
};
}
}
}
}
}).then(({ component, inst }) => {
expect(inst.length).toBe(1);
expect(inst[0].props.className).toBe('div-ut');
done();
});
});
})

View File

@ -1,14 +0,0 @@
jest.mock('lodash', () => {
const original = jest.requireActual('lodash');
return {
...original,
debounce: (fn) => (...args: any[]) => fn.apply(this, args),
throttle: (fn) => (...args: any[]) => fn.apply(this, args),
}
})
export const mockConsoleWarn = jest.fn();
console.warn = mockConsoleWarn;
process.env.NODE_ENV = 'production';

View File

@ -1,463 +0,0 @@
import {
isSchema,
isFileSchema,
inSameDomain,
getFileCssName,
isJSSlot,
getValue,
getI18n,
transformArrayToMap,
transformStringToFunction,
isVariable,
capitalizeFirstLetter,
forEach,
isString,
serializeParams,
parseExpression,
parseThisRequiredExpression,
parseI18n,
parseData,
} from '../../src/utils/common';
import logger from '../../src/utils/logger';
describe('test isSchema', () => {
it('should be false when empty value is passed', () => {
expect(isSchema(null)).toBeFalsy();
expect(isSchema(undefined)).toBeFalsy();
expect(isSchema('')).toBeFalsy();
expect(isSchema({})).toBeFalsy();
});
it('should be true when componentName is Leaf or Slot ', () => {
expect(isSchema({ componentName: 'Leaf' })).toBeTruthy();
expect(isSchema({ componentName: 'Slot' })).toBeTruthy();
});
it('should check each item of an array', () => {
const validArraySchema = [
{ componentName: 'Button', props: {}},
{ componentName: 'Button', props: { type: 'JSExpression' }},
{ componentName: 'Leaf' },
{ componentName: 'Slot'},
];
const invalidArraySchema = [
...validArraySchema,
{ componentName: 'ComponentWithoutProps'},
];
expect(isSchema(validArraySchema)).toBeTruthy();
expect(isSchema(invalidArraySchema)).toBeFalsy();
});
it('normal valid schema should contains componentName, and props of type object or JSExpression', () => {
expect(isSchema({ componentName: 'Button', props: {}})).toBeTruthy();
expect(isSchema({ componentName: 'Button', props: { type: 'JSExpression' }})).toBeTruthy();
expect(isSchema({ xxxName: 'Button'})).toBeFalsy();
expect(isSchema({ componentName: 'Button', props: null})).toBeFalsy();
expect(isSchema({ componentName: 'Button', props: []})).toBeFalsy();
expect(isSchema({ componentName: 'Button', props: 'props string'})).toBeFalsy();
});
});
describe('test isFileSchema ', () => {
it('should be false when invalid schema is passed', () => {
expect(isFileSchema({ xxxName: 'Button'})).toBeFalsy();
expect(isFileSchema({ componentName: 'Button', props: null})).toBeFalsy();
expect(isFileSchema({ componentName: 'Button', props: []})).toBeFalsy();
expect(isFileSchema({ componentName: 'Button', props: 'props string'})).toBeFalsy();
});
it('should be true only when schema with root named Page || Block || Component is passed', () => {
expect(isFileSchema({ componentName: 'Page', props: {}})).toBeTruthy();
expect(isFileSchema({ componentName: 'Block', props: {}})).toBeTruthy();
expect(isFileSchema({ componentName: 'Component', props: {}})).toBeTruthy();
expect(isFileSchema({ componentName: 'Button', props: {}})).toBeFalsy();
});
});
describe('test inSameDomain ', () => {
let windowSpy;
beforeEach(() => {
windowSpy = jest.spyOn(window, "window", "get");
});
afterEach(() => {
windowSpy.mockRestore();
});
it('should work', () => {
windowSpy.mockImplementation(() => ({
parent: {
location: {
host: "example.com"
},
},
location: {
host: "example.com"
}
}));
expect(inSameDomain()).toBeTruthy();
windowSpy.mockImplementation(() => ({
parent: {
location: {
host: "example.com"
},
},
location: {
host: "another.com"
}
}));
expect(inSameDomain()).toBeFalsy();
windowSpy.mockImplementation(() => ({
parent: null,
location: {
host: "example.com"
}
}));
expect(inSameDomain()).toBeFalsy();
});
});
describe('test getFileCssName ', () => {
it('should work', () => {
expect(getFileCssName(null)).toBe(undefined);
expect(getFileCssName(undefined)).toBe(undefined);
expect(getFileCssName('')).toBe(undefined);
expect(getFileCssName('FileName')).toBe('lce-file-name');
expect(getFileCssName('Page1_abc')).toBe('lce-page1_abc');
});
});
describe('test isJSSlot ', () => {
it('should work', () => {
expect(isJSSlot(null)).toBeFalsy();
expect(isJSSlot(undefined)).toBeFalsy();
expect(isJSSlot('stringValue')).toBeFalsy();
expect(isJSSlot([1, 2, 3])).toBeFalsy();
expect(isJSSlot({ type: 'JSSlot' })).toBeTruthy();
expect(isJSSlot({ type: 'JSBlock' })).toBeTruthy();
expect(isJSSlot({ type: 'anyOtherType' })).toBeFalsy();
});
});
describe('test getValue ', () => {
it('should check params', () => {
expect(getValue(null, 'somePath')).toStrictEqual({});
expect(getValue(undefined, 'somePath')).toStrictEqual({});
// array is not valid input, return default
expect(getValue([], 'somePath')).toStrictEqual({});
expect(getValue([], 'somePath', 'aaa')).toStrictEqual('aaa');
expect(getValue([1, 2, 3], 'somePath', 'aaa')).toStrictEqual('aaa');
expect(getValue({}, 'somePath')).toStrictEqual({});
expect(getValue({}, 'somePath', 'default')).toStrictEqual('default');
});
it('should work normally', () => {
// single segment path
expect(getValue({ a: 'aValue' }, 'a')).toStrictEqual('aValue');
expect(getValue({ a: 'aValue', f:null }, 'f')).toBeNull();
expect(getValue({ a: { b: 'bValue' } }, 'a.b')).toStrictEqual('bValue');
expect(getValue({ a: { b: 'bValue', c: { d: 'dValue' } } }, 'a.c.d')).toStrictEqual('dValue');
expect(getValue({ a: { b: 'bValue', c: { d: 'dValue' } } }, 'e')).toStrictEqual({});
});
});
describe('test getI18n ', () => {
it('should work', () => {
const messages = {
'zh-CN': {
'key1': '啊啊啊',
'key2': '哈哈哈',
},
};
expect(getI18n('keyString', {}, 'zh-CN')).toStrictEqual('');
expect(getI18n('keyString', {}, 'zh-CN', null)).toStrictEqual('');
expect(getI18n('keyString', {}, 'en-US', messages)).toStrictEqual('');
expect(getI18n('key3', {}, 'zh-CN', messages)).toStrictEqual('');
});
});
describe('test transformArrayToMap ', () => {
it('should work', () => {
expect(transformArrayToMap([])).toStrictEqual({});
expect(transformArrayToMap('not a array')).toStrictEqual({});
expect(transformArrayToMap({'not Array': 1})).toStrictEqual({});
let mockArray = [
{
name: 'jack',
age: 2,
},
{
name: 'jack',
age: 20,
}
];
// test override
expect(transformArrayToMap(mockArray, 'name', true).jack.age).toBe(20);
expect(transformArrayToMap(mockArray, 'name').jack.age).toBe(20);
expect(transformArrayToMap(mockArray, 'name', false).jack.age).toBe(2);
mockArray = [
{
name: 'jack',
age: 2,
},
{
name: 'rose',
age: 20,
}
];
// normal case
expect(transformArrayToMap(mockArray, 'name').jack.age).toBe(2);
expect(transformArrayToMap(mockArray, 'name').jack.name).toBe('jack');
expect(transformArrayToMap(mockArray, 'name').rose.age).toBe(20);
// key not exists
expect(transformArrayToMap(mockArray, 'nameEn')).toStrictEqual({});
});
});
describe('test transformStringToFunction ', () => {
it('should work', () => {
const mockFun = jest.fn();
expect(transformStringToFunction(mockFun)).toBe(mockFun);
expect(transformStringToFunction(111)).toBe(111);
let mockFnStr = 'function(){return 111;}';
let fn = transformStringToFunction(mockFnStr);
expect(fn()).toBe(111);
mockFnStr = '() => { return 222; }';
fn = transformStringToFunction(mockFnStr);
expect(fn()).toBe(222);
mockFnStr = 'function getValue() { return 333; }';
fn = transformStringToFunction(mockFnStr);
expect(fn()).toBe(333);
mockFnStr = 'function getValue(aaa) {\
return aaa; \
}';
fn = transformStringToFunction(mockFnStr);
expect(fn(123)).toBe(123);
});
});
describe('test isVariable ', () => {
it('should work', () => {
expect(isVariable(null)).toBeFalsy();
expect(isVariable(undefined)).toBeFalsy();
expect(isVariable([1, 2, 3])).toBeFalsy();
expect(isVariable({})).toBeFalsy();
expect(isVariable({ type: 'any other type' })).toBeFalsy();
expect(isVariable({ type: 'variable' })).toBeTruthy();
});
});
describe('test capitalizeFirstLetter ', () => {
it('should work', () => {
expect(capitalizeFirstLetter(null)).toBeNull();
expect(capitalizeFirstLetter()).toBeUndefined();
expect(capitalizeFirstLetter([1, 2, 3])).toStrictEqual([1, 2, 3]);
expect(capitalizeFirstLetter({ a: 1 })).toStrictEqual({ a: 1 });
expect(capitalizeFirstLetter('')).toStrictEqual('');
expect(capitalizeFirstLetter('a')).toStrictEqual('A');
expect(capitalizeFirstLetter('abcd')).toStrictEqual('Abcd');
});
});
describe('test forEach ', () => {
it('should work', () => {
const mockFn = jest.fn();
forEach(null, mockFn);
expect(mockFn).toBeCalledTimes(0);
forEach(undefined, mockFn);
expect(mockFn).toBeCalledTimes(0);
forEach([1, 2, 3], mockFn);
expect(mockFn).toBeCalledTimes(0);
forEach('stringValue', mockFn);
expect(mockFn).toBeCalledTimes(0);
forEach({ a: 1, b: 2, c: 3 }, mockFn);
expect(mockFn).toBeCalledTimes(3);
const mockFn2 = jest.fn();
forEach({ a: 1 }, mockFn2, { b: 'bbb' });
expect(mockFn2).toHaveBeenCalledWith(1, 'a');
let sum = 0;
const mockFn3 = function(value, key) { sum = value + this.b; };
forEach({ a: 1 }, mockFn3, { b: 10 });
expect(sum).toEqual(11);
});
});
describe('test isString ', () => {
it('should work', () => {
expect(isString(123)).toBeFalsy();
expect(isString([])).toBeFalsy();
expect(isString({})).toBeFalsy();
expect(isString(null)).toBeFalsy();
expect(isString(undefined)).toBeFalsy();
expect(isString(true)).toBeFalsy();
expect(isString('111')).toBeTruthy();
expect(isString(new String('111'))).toBeTruthy();
});
});
describe('test serializeParams ', () => {
it('should work', () => {
const mockParams = { a: 1, b: 2, c: 'cvalue', d:[1, 'a', {}], e: {e1: 'value1', e2: 'value2'}};
const result = serializeParams(mockParams);
const decodedParams = decodeURIComponent(result);
expect(result).toBe('a=1&b=2&c=cvalue&d=%5B1%2C%22a%22%2C%7B%7D%5D&e=%7B%22e1%22%3A%22value1%22%2C%22e2%22%3A%22value2%22%7D');
expect(decodedParams).toBe('a=1&b=2&c=cvalue&d=[1,"a",{}]&e={"e1":"value1","e2":"value2"}');
});
});
describe('test parseExpression ', () => {
it('can handle JSExpression', () => {
const mockExpression = {
"type": "JSExpression",
"value": "function (params) { return this.scopeValue + params.param1 + 5;}"
};
const result = parseExpression(mockExpression, { scopeValue: 1 });
expect(result({ param1: 2 })).toBe((1 + 2 + 5));
});
it('[success] JSExpression handle without this use scopeValue', () => {
const mockExpression = {
"type": "JSExpression",
"value": "state"
};
const result = parseExpression(mockExpression, { state: 1 });
expect(result).toBe((1));
});
it('[success] JSExpression handle without this use scopeValue', () => {
const mockExpression = {
"type": "JSExpression",
"value": "this.state"
};
const result = parseExpression(mockExpression, { state: 1 });
expect(result).toBe((1));
});
});
describe('test parseThisRequiredExpression', () => {
it('can handle JSExpression', () => {
const mockExpression = {
"type": "JSExpression",
"value": "function (params) { return this.scopeValue + params.param1 + 5;}"
};
const result = parseThisRequiredExpression(mockExpression, { scopeValue: 1 });
expect(result({ param1: 2 })).toBe((1 + 2 + 5));
});
it('[error] JSExpression handle without this use scopeValue', () => {
const mockExpression = {
"type": "JSExpression",
"value": "state.text"
};
const fn = logger.error = jest.fn();
parseThisRequiredExpression(mockExpression, { state: { text: 'text' } });
expect(fn).toBeCalledWith(' parseExpression.error', new ReferenceError('state is not defined'), {"type": "JSExpression", "value": "state.text"}, {"state": {"text": "text"}});
});
it('[success] JSExpression handle without this use scopeValue', () => {
const mockExpression = {
"type": "JSExpression",
"value": "this.state"
};
const result = parseThisRequiredExpression(mockExpression, { state: 1 });
expect(result).toBe((1));
});
})
describe('test parseI18n ', () => {
it('can handle normal parseI18n', () => {
const mockI18n = {
"type": "i18n",
"key": "keyA"
};
const mockI18nFun = (key) => { return 'hahaha' + key;};
const result = parseI18n(mockI18n, { i18n: mockI18nFun });
expect(result).toBe('hahahakeyA');
});
});
describe('test parseData ', () => {
it('should work when isJSExpression === true', () => {
const mockExpression = {
"type": "JSExpression",
"value": "function (params) { return this.scopeValue + params.param1 + 5;}"
};
const result = parseData(mockExpression, { scopeValue: 1 });
expect(result({ param1: 2 })).toBe((1 + 2 + 5));
});
it('should work when isI18nData === true', () => {
const mockI18n = {
"type": "i18n",
"key": "keyA"
};
const mockI18nFun = (key) => { return 'hahaha' + key;};
const result = parseData(mockI18n, { i18n: mockI18nFun });
expect(result).toBe('hahahakeyA');
});
it('should work when schema is string', () => {
expect(parseData(' this is a normal string, will be trimmed only ')).toStrictEqual('this is a normal string, will be trimmed only');
});
it('should work when schema is array', () => {
const mockData = [
{
"type": "i18n",
"key": "keyA"
},
' this is a normal string, will be trimmed only ',
];
const mockI18nFun = (key) => { return 'hahaha' + key;};
const result = parseData(mockData, { i18n: mockI18nFun });
expect(result[0]).toStrictEqual('hahahakeyA');
expect(result[1]).toStrictEqual('this is a normal string, will be trimmed only');
});
it('should work when schema is function', () => {
const mockFn = function() { return this.a; };
const result = parseData(mockFn, { a: 111 });
expect(result()).toBe(111);
});
it('should work when schema is null or undefined', () => {
expect(parseData(null)).toBe(null);
expect(parseData(undefined)).toBe(undefined);
});
it('should work when schema is normal object', () => {
expect(parseData({})).toStrictEqual({});
const mockI18nFun = (key) => { return 'hahaha' + key;};
const result = parseData({
key1: {
"type": "i18n",
"key": "keyA"
},
key2: ' this is a normal string, will be trimmed only ',
__privateKey: 'any value',
}, { i18n: mockI18nFun });
expect(result.key1).toStrictEqual('hahahakeyA');
expect(result.key2).toStrictEqual('this is a normal string, will be trimmed only');
expect(result.__privateKey).toBeUndefined();
});
});

View File

@ -1,38 +0,0 @@
import React from 'react';
import { Box, Breadcrumb, Form, Select, Input, Button, Table, Pagination, Dialog } from '@alifd/next';
const Div = ({_leaf, ...rest}: any) => (<div {...rest}>{rest.children}</div>);
const MiniRenderDiv = ({_leaf, ...rest}: any) => {
return (
<div {...rest}>
{rest.children}
</div>
);
};
const Text = ({_leaf, ...rest}: any) => (<div {...rest}>{rest.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,
MiniRenderDiv,
};
export default components;

View File

@ -1,559 +0,0 @@
// @ts-nocheck
const mockJsonp = jest.fn();
const mockRequest = jest.fn();
const mockGet = jest.fn();
const mockPost = jest.fn();
jest.mock('../../src/utils/request', () => {
return {
jsonp: (uri, params, headers, otherProps) => {
return new Promise((resolve, reject) => {
resolve(mockJsonp(uri, params, headers, otherProps));
});
},
request: (uri, params, headers, otherProps) => {
return new Promise((resolve, reject) => {
resolve(mockRequest(uri, params, headers, otherProps));
});
},
get: (uri, params, headers, otherProps) => {
return new Promise((resolve, reject) => {
resolve(mockGet(uri, params, headers, otherProps));
});
},
post: (uri, params, headers, otherProps) => {
return new Promise((resolve, reject) => {
resolve(mockPost(uri, params, headers, otherProps));
});
},
};
});
import { DataHelper, doRequest } from '../../src/utils/data-helper';
import { parseData } from '../../src/utils/common';
describe('test DataHelper ', () => {
beforeEach(() => {
jest.resetModules();
})
it('can be inited', () => {
const mockHost = {};
let mockDataSourceConfig = {};
const mockAppHelper = {};
const mockParser = (config: any) => parseData(config);
let dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser);
expect(dataHelper).toBeTruthy();
expect(dataHelper.host).toBe(mockHost);
expect(dataHelper.config).toBe(mockDataSourceConfig);
expect(dataHelper.appHelper).toBe(mockAppHelper);
expect(dataHelper.parser).toBe(mockParser);
dataHelper = new DataHelper(mockHost, undefined, mockAppHelper, mockParser);
expect(dataHelper.config).toStrictEqual({});
expect(dataHelper.ajaxList).toStrictEqual([]);
mockDataSourceConfig = {
list: [
{
id: 'ds1',
}, {
id: 'ds2',
},
]
};
dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser);
expect(dataHelper.config).toBe(mockDataSourceConfig);
expect(dataHelper.ajaxList.length).toBe(2);
expect(dataHelper.ajaxMap.ds1).toStrictEqual({
id: 'ds1',
});
});
it('should handle generateDataSourceMap properly in constructor', () => {
const mockHost = {};
let mockDataSourceConfig = {};
const mockAppHelper = {};
const mockParser = (config: any) => parseData(config);
let dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser);
// test generateDataSourceMap logic
mockDataSourceConfig = {
list: [
{
id: 'getInfo',
isInit: true,
type: 'fetch', // fetch/mtop/jsonp/custom
options: {
uri: 'mock/info.json',
method: 'GET',
params: { a: 1 },
timeout: 5000,
},
}, {
id: 'postInfo',
isInit: true,
type: 'fetch',
options: {
uri: 'mock/info.json',
method: 'POST',
params: { a: 1 },
timeout: 5000,
},
},
]
};
dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser);
expect(Object.keys(dataHelper.dataSourceMap).length).toBe(2);
expect(dataHelper.dataSourceMap.getInfo.status).toBe('init');
expect(typeof dataHelper.dataSourceMap.getInfo.load).toBe('function');
});
it('getInitDataSourseConfigs should work', () => {
const mockHost = {};
let mockDataSourceConfig = {};
const mockAppHelper = {};
const mockParser = (config: any) => parseData(config);
// test generateDataSourceMap logic
mockDataSourceConfig = {
list: [
{
id: 'getInfo',
isInit: true,
type: 'fetch', // fetch/mtop/jsonp/custom
options: {
uri: 'mock/info.json',
method: 'GET',
params: { a: 1 },
timeout: 5000,
},
},
{
id: 'postInfo',
isInit: false,
type: 'fetch',
options: {
uri: 'mock/info.json',
method: 'POST',
params: { a: 1 },
timeout: 5000,
},
},
{
id: 'getInfoLater',
isInit: false,
type: 'fetch',
options: {
uri: 'mock/info.json',
method: 'POST',
params: { a: 1 },
timeout: 5000,
},
},
{
id: 'getInfoLater2',
isInit: 'not a valid boolean',
type: 'fetch',
options: {
uri: 'mock/info.json',
method: 'POST',
params: { a: 1 },
timeout: 5000,
},
},
],
};
const dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser);
expect(dataHelper.getInitDataSourseConfigs().length).toBe(1);
expect(dataHelper.getInitDataSourseConfigs()[0].id).toBe('getInfo');
});
it('util function doRequest should work', () => {
doRequest('jsonp', {
uri: 'https://www.baidu.com',
params: { a: 1 },
otherStuff1: 'aaa',
});
expect(mockJsonp).toBeCalled();
// test GET
doRequest('fetch', {
uri: 'https://www.baidu.com',
method: 'get',
params: { a: 1 },
otherStuff1: 'aaa',
});
expect(mockGet).toBeCalled();
mockGet.mockClear();
doRequest('fetch', {
uri: 'https://www.baidu.com',
method: 'Get',
params: { a: 1 },
otherStuff1: 'aaa',
});
expect(mockGet).toBeCalled();
mockGet.mockClear();
doRequest('fetch', {
uri: 'https://www.baidu.com',
method: 'GET',
params: { a: 1 },
otherStuff1: 'aaa',
});
expect(mockGet).toBeCalled();
mockGet.mockClear();
// test POST
doRequest('fetch', {
uri: 'https://www.baidu.com',
method: 'post',
params: { a: 1 },
otherStuff1: 'aaa',
});
expect(mockPost).toBeCalled();
mockPost.mockClear();
doRequest('fetch', {
uri: 'https://www.baidu.com',
method: 'POST',
params: { a: 1 },
otherStuff1: 'aaa',
});
expect(mockPost).toBeCalled();
mockPost.mockClear();
doRequest('fetch', {
uri: 'https://www.baidu.com',
method: 'Post',
params: { a: 1 },
otherStuff1: 'aaa',
});
expect(mockPost).toBeCalled();
mockPost.mockClear();
// test default
doRequest('fetch', {
uri: 'https://www.baidu.com',
method: 'whatever',
params: { a: 1 },
otherStuff1: 'aaa',
});
expect(mockRequest).toBeCalled();
mockRequest.mockClear();
mockGet.mockClear();
// method will be GET when not provided
doRequest('fetch', {
uri: 'https://www.baidu.com',
params: { a: 1 },
otherStuff1: 'aaa',
});
expect(mockRequest).toBeCalledTimes(0);
expect(mockGet).toBeCalledTimes(1);
mockRequest.mockClear();
mockGet.mockClear();
mockPost.mockClear();
mockJsonp.mockClear();
doRequest('someOtherType', {
uri: 'https://www.baidu.com',
params: { a: 1 },
otherStuff1: 'aaa',
});
expect(mockRequest).toBeCalledTimes(0);
expect(mockGet).toBeCalledTimes(0);
expect(mockPost).toBeCalledTimes(0);
expect(mockJsonp).toBeCalledTimes(0);
});
it('updateDataSourceMap should work', () => {
const mockHost = {};
const mockDataSourceConfig = {
list: [
{
id: 'ds1',
}, {
id: 'ds2',
},
]
};
const mockAppHelper = {};
const mockParser = (config: any) => parseData(config);
const dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser);
dataHelper.updateDataSourceMap('ds1', { a: 1 }, null);
expect(dataHelper.dataSourceMap['ds1']).toBeTruthy();
expect(dataHelper.dataSourceMap['ds1'].data).toStrictEqual({ a: 1 });
expect(dataHelper.dataSourceMap['ds1'].error).toBeUndefined();
expect(dataHelper.dataSourceMap['ds1'].status).toBe('loaded');
dataHelper.updateDataSourceMap('ds2', { b: 2 }, new Error());
expect(dataHelper.dataSourceMap['ds2']).toBeTruthy();
expect(dataHelper.dataSourceMap['ds2'].data).toStrictEqual({ b: 2 });
expect(dataHelper.dataSourceMap['ds2'].status).toBe('error');
expect(dataHelper.dataSourceMap['ds2'].error).toBeTruthy();
});
it('handleData should work', () => {
const mockHost = { stateA: 'aValue'};
const mockDataSourceConfig = {
list: [
{
id: 'fullConfigGet',
isInit: true,
options: {
params: {},
method: 'GET',
isCors: true,
timeout: 5000,
headers: {},
uri: 'mock/info.json',
},
shouldFetch: {
type: 'JSFunction',
value: 'function() { return true; }',
},
dataHandler: {
type: 'JSFunction',
value: 'function(res) { return res.data; }',
},
errorHandler: {
type: 'JSFunction',
value: 'function(error) {}',
},
willFetch: {
type: 'JSFunction',
value: 'function(options) { return options; }',
},
},
]
};
const mockAppHelper = {};
const mockParser = (config: any) => parseData(config);
const dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser);
// test valid case
let mockDataHandler = {
type: 'JSFunction',
value: 'function(res) { return res.data + \'+\' + this.stateA; }',
};
let result = dataHelper.handleData('fullConfigGet', mockDataHandler, { data: 'mockDataValue' }, null);
expect(result).toBe('mockDataValue+aValue');
// test invalid datahandler
mockDataHandler = {
type: 'not a JSFunction',
value: 'function(res) { return res.data + \'+\' + this.stateA; }',
};
result = dataHelper.handleData('fullConfigGet', mockDataHandler, { data: 'mockDataValue' }, null);
expect(result).toStrictEqual({ data: 'mockDataValue' });
// exception with id
mockDataHandler = {
type: 'JSFunction',
value: 'function(res) { return res.data + \'+\' + JSON.parse({a:1}); }',
};
result = dataHelper.handleData('fullConfigGet', mockDataHandler, { data: 'mockDataValue' }, null);
expect(result).toBeUndefined();
// exception without id
mockDataHandler = {
type: 'JSFunction',
value: 'function(res) { return res.data + \'+\' + JSON.parse({a:1}); }',
};
result = dataHelper.handleData(null, mockDataHandler, { data: 'mockDataValue' }, null);
expect(result).toBeUndefined();
});
it('updateConfig should work', () => {
const mockHost = { stateA: 'aValue'};
const mockDataSourceConfig = {
list: [
{
id: 'ds1',
}, {
id: 'ds2',
},
{
id: 'fullConfigGet',
isInit: true,
options: {
params: {},
method: 'GET',
isCors: true,
timeout: 5000,
headers: {},
uri: 'mock/info.json',
},
shouldFetch: {
type: 'JSFunction',
value: 'function() { return true; }',
},
dataHandler: {
type: 'JSFunction',
value: 'function(res) { return res.data; }',
},
errorHandler: {
type: 'JSFunction',
value: 'function(error) {}',
},
willFetch: {
type: 'JSFunction',
value: 'function(options) { return options; }',
},
},
]
};
const mockAppHelper = {};
const mockParser = (config: any) => parseData(config);
const dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser);
expect(dataHelper.ajaxList.length).toBe(3);
let updatedConfig = {
list: [
{
id: 'ds2',
},
{
id: 'fullConfigGet',
},
]
};
dataHelper.updateConfig(updatedConfig);
expect(dataHelper.ajaxList.length).toBe(2);
expect(dataHelper.dataSourceMap.ds1).toBeUndefined();
updatedConfig = {
list: [
{
id: 'ds2',
},
{
id: 'fullConfigGet',
},
{
id: 'ds3',
},
]
};
dataHelper.updateConfig(updatedConfig);
expect(dataHelper.ajaxList.length).toBe(3);
expect(dataHelper.dataSourceMap.ds3).toBeTruthy();
});
it('getInitData should work', () => {
const mockHost = { stateA: 'aValue'};
const mockDataSourceConfig = {
list: [
{
id: 'ds1',
}, {
id: 'ds2',
},
{
id: 'fullConfigGet',
isInit: true,
type: 'fetch',
options: {
params: {},
method: 'GET',
isCors: true,
timeout: 5000,
headers: {
headerA: 1,
},
uri: 'mock/info.json',
},
shouldFetch: {
type: 'JSFunction',
value: 'function() { return true; }',
},
dataHandler: {
type: 'JSFunction',
value: 'function(res) { return 123; }',
},
errorHandler: {
type: 'JSFunction',
value: 'function(error) {}',
},
willFetch: {
type: 'JSFunction',
value: 'function(options) { return options; }',
},
},
]
};
const mockAppHelper = {};
const mockParser = (config: any) => parseData(config);
const dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser);
expect(dataHelper.ajaxList.length).toBe(3);
expect(mockGet).toBeCalledTimes(0);
dataHelper.getInitData().then(res => {
expect(mockGet).toBeCalledTimes(1);
expect(mockGet).toBeCalledWith('mock/info.json', {}, {
headerA: 1,
}, expect.anything());
mockGet.mockClear();
});
});
it('getDataSource should work', () => {
const mockHost = { stateA: 'aValue'};
const mockDataSourceConfig = {
list: [
{
id: 'ds1',
}, {
id: 'ds2',
},
{
id: 'fullConfigGet',
isInit: true,
type: 'fetch',
options: {
params: {},
method: 'GET',
isCors: true,
timeout: 5000,
headers: {
headerA: 1,
},
uri: 'mock/info.json',
},
shouldFetch: {
type: 'JSFunction',
value: 'function() { return true; }',
},
dataHandler: {
type: 'JSFunction',
value: 'function(res) { return 123; }',
},
errorHandler: {
type: 'JSFunction',
value: 'function(error) {}',
},
willFetch: {
type: 'JSFunction',
value: 'function(options) { return options; }',
},
},
]
};
const mockAppHelper = {};
const mockParser = (config: any) => parseData(config);
const dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser);
expect(dataHelper.ajaxList.length).toBe(3);
expect(mockGet).toBeCalledTimes(0);
const callbackFn = jest.fn();
dataHelper.getDataSource('fullConfigGet', { param1: 'value1' }, {}, callbackFn).then(res => {
expect(mockGet).toBeCalledTimes(1);
expect(mockGet).toBeCalledWith('mock/info.json', { param1: 'value1' }, {
headerA: 1,
}, expect.anything());
mockGet.mockClear();
expect(callbackFn).toBeCalledTimes(1);
});
});
});

View File

@ -1,31 +0,0 @@
// @ts-nocheck
import isUseLoop from '../../src/utils/is-use-loop';
describe('base test', () => {
it('designMode is true', () => {
expect(isUseLoop([], true)).toBeFalsy();
expect(isUseLoop([{}], true)).toBeTruthy();
expect(isUseLoop(null, true)).toBeFalsy();
expect(isUseLoop(undefined, true)).toBeFalsy();
expect(isUseLoop(0, true)).toBeFalsy();
});
it('loop is expression', () => {
expect(isUseLoop({
"type": "JSExpression",
"value": "function() { console.log('componentDidMount'); }"
}, true)).toBeTruthy();
expect(isUseLoop({
"type": "JSExpression",
"value": "function() { console.log('componentDidMount'); }"
}, false)).toBeTruthy();
});
it('designMode is false', () => {
expect(isUseLoop([], false)).toBeTruthy();
expect(isUseLoop([{}], false)).toBeTruthy();
expect(isUseLoop(null, false)).toBeTruthy();
expect(isUseLoop(undefined, false)).toBeTruthy();
expect(isUseLoop(0, false)).toBeTruthy();
});
});

View File

@ -1,97 +0,0 @@
import { IPublicTypePropChangeOptions } from "@ali/lowcode-designer";
import EventEmitter from "events";
export default class Node {
private emitter: EventEmitter;
schema: any = {
props: {},
};
componentMeta = {};
parent;
hasLoop = () => this._hasLoop;
id;
_isRoot: false;
_hasLoop: false;
constructor(schema: any, info: any = {}) {
this.emitter = new EventEmitter();
const {
componentMeta,
parent,
isRoot,
hasLoop,
} = info;
this.schema = {
props: {},
...schema,
};
this.componentMeta = componentMeta || {};
this.parent = parent;
this.id = schema.id;
this._isRoot = isRoot;
this._hasLoop = hasLoop;
}
isRoot = () => this._isRoot;
get isRootNode () {
return this._isRoot;
};
// componentMeta() {
// return this.componentMeta;
// }
// mockLoop() {
// // this.hasLoop = true;
// }
onChildrenChange(fn: any) {
this.emitter.on('onChildrenChange', fn);
return () => {
this.emitter.off('onChildrenChange', fn);
}
}
emitChildrenChange() {
this.emitter?.emit('onChildrenChange', {});
}
onPropChange(fn: any) {
this.emitter.on('onPropChange', fn);
return () => {
this.emitter.off('onPropChange', fn);
}
}
emitPropChange(val: IPublicTypePropChangeOptions, skip?: boolean) {
if (!skip) {
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;
}
}

View File

@ -1,66 +0,0 @@
import React, { Component, PureComponent, createElement, createContext, forwardRef, ReactInstance, ContextType } from 'react';
import ReactDOM from 'react-dom';
import {
adapter,
pageRendererFactory,
componentRendererFactory,
blockRendererFactory,
addonRendererFactory,
tempRendererFactory,
// rendererFactory,
// types,
} from '../../src';
import ConfigProvider from '@alifd/next/lib/config-provider';
(window as any).React = React;
(window as any).ReactDom = ReactDOM;
adapter.setRuntime({
Component,
PureComponent,
createContext,
createElement,
forwardRef,
findDOMNode: ReactDOM.findDOMNode,
});
adapter.setRenderers({
PageRenderer: pageRendererFactory(),
ComponentRenderer: componentRendererFactory(),
BlockRenderer: blockRendererFactory(),
AddonRenderer: addonRendererFactory(),
TempRenderer: tempRendererFactory(),
DivRenderer: blockRendererFactory(),
});
adapter.setConfigProvider(ConfigProvider);
// function factory(): types.IRenderComponent {
// const Renderer = rendererFactory();
// return class ReactRenderer extends Renderer implements Component {
// readonly props: types.IRendererProps;
// context: ContextType<any>;
// setState: (
// state: types.IRendererState,
// callback?: () => void,
// ) => void;
// forceUpdate: (callback?: () => void) => void;
// refs: {
// [key: string]: ReactInstance;
// };
// constructor(props: types.IRendererProps, context: ContextType<any>) {
// super(props, context);
// }
// isValidComponent(obj: any) {
// return obj?.prototype?.isReactComponent || obj?.prototype instanceof Component;
// }
// };
// }
// export default factory();

View File

@ -1,159 +0,0 @@
// @ts-nocheck
const mockSerializeParams = jest.fn();
jest.mock('../../src/utils/common', () => {
return {
serializeParams: (params) => {
return mockSerializeParams(params);
},
};
});
const mockFetchJsonp = jest.fn();
jest.mock('fetch-jsonp', () => {
return (uri, otherProps) => {
mockFetchJsonp(uri, otherProps);
return Promise.resolve({
json: () => {
return Promise.resolve({ data: [1, 2, 3]});
} ,
ok: true,
});
}
});
import { get, post, buildUrl, request, jsonp } from '../../src/utils/request';
describe('test utils/request.ts ', () => {
it('buildUrl should be working properly', () => {
mockSerializeParams.mockImplementation((params) => {
return 'serializedParams=serializedParams';
});
expect(buildUrl('mockDataApi', { a: 1, b: 'a', c: []})).toBe('mockDataApi?serializedParams=serializedParams');
expect(buildUrl('mockDataApi?existingParamA=valueA', { a: 1, b: 'a', c: []})).toBe('mockDataApi?existingParamA=valueA&serializedParams=serializedParams');
mockSerializeParams.mockClear();
mockSerializeParams.mockImplementation((params) => {
return undefined;
});
expect(buildUrl('mockDataApi', { a: 1, b: 'a', c: []})).toBe('mockDataApi');
mockSerializeParams.mockClear();
});
it('request should be working properly', () => {
const fetchMock = jest
.spyOn(global, 'fetch')
.mockImplementation(() =>
Promise.resolve({
json: () => Promise.resolve([]) ,
status: 200,
})
);
request('https://someradomurl/api/list', 'GET', {}, {}, {}).then((response) => {
expect(fetchMock).toBeCalledWith('https://someradomurl/api/list', { body: {}, credentials: 'include', headers: {}, method: 'GET'});
}).catch((error) => {
console.error(error);
});
});
it('get should be working properly', () => {
const fetchMock = jest
.spyOn(global, 'fetch')
.mockImplementation(() =>
Promise.resolve({
json: () => Promise.resolve([]) ,
status: 200,
})
);
get('https://someradomurl/api/list', {}, {}, {}).then((response) => {
expect(fetchMock).toBeCalledWith(
'https://someradomurl/api/list',
{
body: null,
headers: { Accept: 'application/json' },
method: 'GET',
credentials: 'include',
});
}).catch((error) => {
console.error(error);
});
});
it('post should be working properly', () => {
const fetchMock = jest
.spyOn(global, 'fetch')
.mockImplementation(() =>
Promise.resolve({
json: () => Promise.resolve([]) ,
status: 200,
})
);
post('https://someradomurl/api/list', { a: 1, b: 'a', c: [] }, { 'Content-Type': 'application/json' }, {}).then((response) => {
expect(fetchMock).toBeCalledWith(
'https://someradomurl/api/list',
{
body: '{"a":1,"b":"a","c":[]}',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
method: 'POST',
credentials: 'include',
});
}).catch((error) => {
console.error(error);
});
post('https://someradomurl/api/list', [ 1, 2, 3, 4 ], {}, {}).then((response) => {
expect(fetchMock).toBeCalledWith(
'https://someradomurl/api/list',
{
body: '[1,2,3,4]',
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
},
method: 'POST',
credentials: 'include',
});
}).catch((error) => {
console.error(error);
});
mockSerializeParams.mockImplementation((params) => {
return 'serializedParams=serializedParams';
});
post('https://someradomurl/api/list', { a: 1, b: 'a', c: [] }, {}, {}).then((response) => {
expect(fetchMock).toBeCalledWith(
'https://someradomurl/api/list',
{
body: 'serializedParams=serializedParams',
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
},
method: 'POST',
credentials: 'include',
});
mockSerializeParams.mockClear();
}).catch((error) => {
console.error(error);
});
});
it('jsonp should be working properly', () => {
mockSerializeParams.mockImplementation((params) => {
return 'params';
});
jsonp('https://someradomurl/api/list', {}, { otherParam1: '123'}).catch(() => {
expect(mockFetchJsonp).toBeCalledWith('https://someradomurl/api/list?params', { timeout: 5000, otherParam1: '123' });
mockSerializeParams.mockClear();
});
});
});

View File

@ -1,8 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "lib",
"jsx": "react"
},
"include": ["./src/"],
}

View File

@ -1,6 +1,6 @@
import { useEvent } from '@alilc/renderer-core'; import { useEvent } from '@alilc/renderer-core';
export type HistoryState = Record<string | number, any>; export type HistoryState = History['state'];
export type HistoryLocation = string; export type HistoryLocation = string;
export enum NavigationType { export enum NavigationType {