mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-12 00:48:16 +00:00
Merge branch 'develop' into release/1.0.80
This commit is contained in:
commit
f6538b6704
@ -69,7 +69,7 @@ import { Project } from '../project';
|
||||
import { Scroller } from '../designer/scroller';
|
||||
import { isElementNode, isDOMNodeVisible } from '../utils/misc';
|
||||
|
||||
export interface LibraryItem extends Package{
|
||||
export interface LibraryItem extends Package {
|
||||
package: string;
|
||||
library: string;
|
||||
urls?: Asset;
|
||||
@ -342,9 +342,11 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
const _library = library || (this.get('library') as LibraryItem[]);
|
||||
const libraryAsset: AssetList = [];
|
||||
const libraryExportList: string[] = [];
|
||||
const functionCallLibraryExportList: string[] = [];
|
||||
|
||||
if (_library && _library.length) {
|
||||
_library.forEach((item) => {
|
||||
const { exportMode, exportSourceLibrary } = item;
|
||||
this.libraryMap[item.package] = item.library;
|
||||
if (item.async) {
|
||||
this.asyncLibraryMap[item.package] = item;
|
||||
@ -354,6 +356,11 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
`Object.defineProperty(window,'${item.exportName}',{get:()=>window.${item.library}});`,
|
||||
);
|
||||
}
|
||||
if (exportMode === 'functionCall' && exportSourceLibrary) {
|
||||
functionCallLibraryExportList.push(
|
||||
`window["${item.library}"] = window["${exportSourceLibrary}"]("${item.library}", "${item.package}");`,
|
||||
);
|
||||
}
|
||||
if (item.editUrls) {
|
||||
libraryAsset.push(item.editUrls);
|
||||
} else if (item.urls) {
|
||||
@ -362,7 +369,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
});
|
||||
}
|
||||
libraryAsset.unshift(assetItem(AssetType.JSText, libraryExportList.join('')));
|
||||
|
||||
libraryAsset.push(assetItem(AssetType.JSText, functionCallLibraryExportList.join('')));
|
||||
return libraryAsset;
|
||||
}
|
||||
|
||||
@ -386,7 +393,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
// required & use once
|
||||
assetBundle(
|
||||
this.get('environment') ||
|
||||
(this.renderEnv === 'rax' ? defaultRaxEnvironment : defaultEnvironment),
|
||||
(this.renderEnv === 'rax' ? defaultRaxEnvironment : defaultEnvironment),
|
||||
AssetLevel.Environment,
|
||||
),
|
||||
// required & use once
|
||||
@ -399,7 +406,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
// required & use once
|
||||
assetBundle(
|
||||
this.get('simulatorUrl') ||
|
||||
(this.renderEnv === 'rax' ? defaultRaxSimulatorUrl : defaultSimulatorUrl),
|
||||
(this.renderEnv === 'rax' ? defaultRaxSimulatorUrl : defaultSimulatorUrl),
|
||||
AssetLevel.Runtime,
|
||||
),
|
||||
];
|
||||
@ -418,6 +425,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
if (Object.keys(this.asyncLibraryMap).length > 0) {
|
||||
// 加载异步Library
|
||||
await renderer.loadAsyncLibrary(this.asyncLibraryMap);
|
||||
Object.keys(this.asyncLibraryMap).forEach(key => {
|
||||
delete this.asyncLibraryMap[key];
|
||||
});
|
||||
}
|
||||
|
||||
// step 5 ready & render
|
||||
@ -437,7 +447,14 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
||||
|
||||
async setupComponents(library) {
|
||||
const libraryAsset: AssetList = this.buildLibrary(library);
|
||||
await this.renderer.load(libraryAsset);
|
||||
await this.renderer?.load(libraryAsset);
|
||||
if (Object.keys(this.asyncLibraryMap).length > 0) {
|
||||
// 加载异步Library
|
||||
await this.renderer?.loadAsyncLibrary(this.asyncLibraryMap);
|
||||
Object.keys(this.asyncLibraryMap).forEach(key => {
|
||||
delete this.asyncLibraryMap[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setupEvents() {
|
||||
@ -1605,4 +1622,4 @@ function getMatched(elements: Array<Element | Text>, selector: string): Element
|
||||
}
|
||||
}
|
||||
return firstQueried;
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,7 @@ export interface BuiltinSimulatorRenderer {
|
||||
setNativeSelection(enableFlag: boolean): void;
|
||||
setDraggingState(state: boolean): void;
|
||||
setCopyState(state: boolean): void;
|
||||
loadAsyncLibrary(asyncMap: { [index: string]: any }): void;
|
||||
clearState(): void;
|
||||
run(): void;
|
||||
}
|
||||
|
||||
@ -199,6 +199,8 @@ exports[`React Renderer render basic case 1`] = `
|
||||
maxLength={null}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCompositionEnd={[Function]}
|
||||
onCompositionStart={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="请选择"
|
||||
@ -296,6 +298,8 @@ exports[`React Renderer render basic case 1`] = `
|
||||
maxLength={null}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCompositionEnd={[Function]}
|
||||
onCompositionStart={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
placeholder="请选择"
|
||||
@ -378,6 +382,8 @@ exports[`React Renderer render basic case 1`] = `
|
||||
maxLength={null}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCompositionEnd={[Function]}
|
||||
onCompositionStart={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
readOnly={false}
|
||||
@ -826,6 +832,8 @@ exports[`React Renderer render basic case 1`] = `
|
||||
maxLength={null}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCompositionEnd={[Function]}
|
||||
onCompositionStart={[Function]}
|
||||
onFocus={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
readOnly={false}
|
||||
|
||||
6
packages/renderer-core/build.test.json
Normal file
6
packages/renderer-core/build.test.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"plugins": [
|
||||
"build-plugin-component",
|
||||
"@ali/lowcode-test-mate/plugin/index.ts"
|
||||
]
|
||||
}
|
||||
20
packages/renderer-core/jest.config.js
Normal file
20
packages/renderer-core/jest.config.js
Normal file
@ -0,0 +1,20 @@
|
||||
const esModules = ['@recore/obx-react'].join('|');
|
||||
|
||||
module.exports = {
|
||||
// transform: {
|
||||
// '^.+\\.[jt]sx?$': 'babel-jest',
|
||||
// // '^.+\\.(ts|tsx)$': 'ts-jest',
|
||||
// // '^.+\\.(js|jsx)$': 'babel-jest',
|
||||
// },
|
||||
// testMatch: ['(/tests?/.*(test))\\.[jt]s$'],
|
||||
transformIgnorePatterns: [
|
||||
`/node_modules/(?!${esModules})/`,
|
||||
],
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.{ts,tsx}',
|
||||
'!**/node_modules/**',
|
||||
'!**/vendor/**',
|
||||
],
|
||||
};
|
||||
@ -10,8 +10,13 @@
|
||||
"es"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "build-scripts test --config build.test.json",
|
||||
"build": "build-scripts build --skip-demo"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ali/b3-one": "^0.0.17",
|
||||
"@ali/bzb-request": "^2.6.0-beta.13",
|
||||
|
||||
@ -32,7 +32,7 @@ import { compWrapper } from '../hoc';
|
||||
import { IComponentConstruct, IComponentHoc, leafWrapper } from '../hoc/leaf';
|
||||
import logger from '../utils/logger';
|
||||
|
||||
export default function baseRenererFactory() {
|
||||
export default function baseRendererFactory() {
|
||||
const { BaseRenderer: customBaseRenderer } = adapter.getRenderers();
|
||||
|
||||
if (customBaseRenderer) {
|
||||
@ -712,7 +712,7 @@ export default function baseRenererFactory() {
|
||||
) {
|
||||
return checkProps(props);
|
||||
}
|
||||
if (isJSExpression(props)) {
|
||||
if (isJSExpression(props) || isJSFunction(props)) {
|
||||
props = parseExpression(props, scope);
|
||||
// 只有当变量解析出来为模型结构的时候才会继续解析
|
||||
if (!isSchema(props) && !isJSSlot(props)) return checkProps(props);
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { BuiltinSimulatorHost } from '@ali/lowcode-designer';
|
||||
import { baseRendererFactory } from '../renderer';
|
||||
import baseRenererFactory from '../renderer/base';
|
||||
|
||||
export type IBaseRenderer = ReturnType<typeof baseRenererFactory>;
|
||||
export type IBaseRenderer = ReturnType<typeof baseRendererFactory>;
|
||||
export type IBaseRendererInstance = InstanceType<ReturnType<typeof baseRendererFactory>>;
|
||||
|
||||
export interface IProps {
|
||||
@ -28,7 +27,7 @@ export interface IProps {
|
||||
|
||||
export interface IState {
|
||||
engineRenderError?: boolean;
|
||||
error?: Error
|
||||
error?: Error;
|
||||
onCompGetRef: (schema: ISchema, ref: any) => void;
|
||||
onCompGetCtx: (schema: ISchema, ref: any) => void;
|
||||
customCreateElement: (...args: any) => any;
|
||||
@ -57,7 +56,7 @@ export interface ComponentModel {
|
||||
export interface ISchema {
|
||||
componentName: string;
|
||||
props: any;
|
||||
children: ComponentModel[]
|
||||
children: ComponentModel[];
|
||||
dataSource?: any;
|
||||
methods?: any;
|
||||
lifeCycles?: any;
|
||||
@ -68,7 +67,7 @@ export interface IInfo {
|
||||
schema: ISchema;
|
||||
Comp: any;
|
||||
componentInfo?: any;
|
||||
componentChildren?: any
|
||||
componentChildren?: any;
|
||||
}
|
||||
|
||||
export interface JSExpression {
|
||||
@ -113,9 +112,9 @@ export interface IRendererModules {
|
||||
BaseRenderer?: new(...args: any) => IRenderer;
|
||||
PageRenderer: any;
|
||||
ComponentRenderer: any;
|
||||
BlockRenderer?: any,
|
||||
AddonRenderer?: any,
|
||||
TempRenderer?: any,
|
||||
BlockRenderer?: any;
|
||||
AddonRenderer?: any;
|
||||
TempRenderer?: any;
|
||||
DivRenderer?: any;
|
||||
}
|
||||
|
||||
|
||||
567
packages/renderer-core/test/fixtures/schema/basic.ts
vendored
Normal file
567
packages/renderer-core/test/fixtures/schema/basic.ts
vendored
Normal file
@ -0,0 +1,567 @@
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
324
packages/renderer-core/test/renderer/renderer.test.tsx
Normal file
324
packages/renderer-core/test/renderer/renderer.test.tsx
Normal file
@ -0,0 +1,324 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import schema from '../fixtures/schema/basic';
|
||||
import '../utils/mock-react-render';
|
||||
import rendererFactory from '../../src/renderer/renderer';
|
||||
import components from '../utils/components';
|
||||
|
||||
const Renderer = rendererFactory();
|
||||
|
||||
function getComp(schema, comp = null): Promise<{
|
||||
component,
|
||||
inst,
|
||||
}> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const component = renderer.create(
|
||||
// @ts-ignore
|
||||
<Renderer
|
||||
schema={schema}
|
||||
components={components}
|
||||
/>);
|
||||
|
||||
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 = (
|
||||
// @ts-ignore
|
||||
<Renderer
|
||||
schema={schema}
|
||||
components={components}
|
||||
/>);
|
||||
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).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('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: 'JSExpression',
|
||||
value: '() => 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: "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();
|
||||
});
|
||||
})
|
||||
})
|
||||
28
packages/renderer-core/test/utils/components.tsx
Normal file
28
packages/renderer-core/test/utils/components.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { Box, Breadcrumb, Form, Select, Input, Button, Table, Pagination, Dialog } from '@alifd/next';
|
||||
|
||||
const Div = (props: any) => (<div {...props}>{props.children}</div>);
|
||||
|
||||
const Text = (props: any) => (<div>{props.content}</div>);
|
||||
|
||||
const SlotComponent = (props: any) => props.mobileSlot;
|
||||
|
||||
const components = {
|
||||
Box,
|
||||
Breadcrumb,
|
||||
'Breadcrumb.Item': Breadcrumb.Item,
|
||||
Form,
|
||||
'Form.Item': Form.Item,
|
||||
Select,
|
||||
Input,
|
||||
Button,
|
||||
'Button.Group': Button.Group,
|
||||
Table,
|
||||
Pagination,
|
||||
Dialog,
|
||||
ErrorComponent: Select,
|
||||
Div,
|
||||
SlotComponent,
|
||||
Text,
|
||||
};
|
||||
|
||||
export default components;
|
||||
66
packages/renderer-core/test/utils/mock-react-render.ts
Normal file
66
packages/renderer-core/test/utils/mock-react-render.ts
Normal file
@ -0,0 +1,66 @@
|
||||
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.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() {
|
||||
const Renderer = rendererFactory();
|
||||
return class ReactRenderer extends Renderer implements Component {
|
||||
readonly props: types.IProps;
|
||||
|
||||
context: ContextType<any>;
|
||||
|
||||
setState: (
|
||||
state: types.IState,
|
||||
callback?: () => void,
|
||||
) => void;
|
||||
|
||||
forceUpdate: (callback?: () => void) => void;
|
||||
|
||||
refs: {
|
||||
[key: string]: ReactInstance,
|
||||
};
|
||||
|
||||
constructor(props: types.IProps, context: ContextType<any>) {
|
||||
super(props, context);
|
||||
}
|
||||
|
||||
isValidComponent(obj: any) {
|
||||
return obj?.prototype?.isReactComponent || obj?.prototype instanceof Component;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default factory();
|
||||
@ -8,7 +8,9 @@ export interface Package { // 应该被编辑器默认加载,定义组件大
|
||||
urls?: string[] | any; // 组件渲染态视图打包后的 CDN url 列表,包含 js 和 css
|
||||
editUrls?: string[] | any; // 组件编辑态视图打包后的 CDN url 列表,包含 js 和 css
|
||||
library: string; // 作为全局变量引用时的名称,和webpack output.library字段含义一样,用来定义全局变量名
|
||||
async?: boolean,
|
||||
async?: boolean;
|
||||
exportMode?: string;
|
||||
exportSourceLibrary?: any;
|
||||
exportName?: string;
|
||||
}
|
||||
|
||||
|
||||
6
packages/utils/build.test.json
Normal file
6
packages/utils/build.test.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"plugins": [
|
||||
"build-plugin-component",
|
||||
"@ali/lowcode-test-mate/plugin/index.ts"
|
||||
]
|
||||
}
|
||||
14
packages/utils/jest.config.js
Normal file
14
packages/utils/jest.config.js
Normal file
@ -0,0 +1,14 @@
|
||||
const esModules = ['@recore/obx-react'].join('|');
|
||||
|
||||
module.exports = {
|
||||
transformIgnorePatterns: [
|
||||
`/node_modules/(?!${esModules})/`,
|
||||
],
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.{ts,tsx}',
|
||||
'!**/node_modules/**',
|
||||
'!**/vendor/**',
|
||||
],
|
||||
};
|
||||
@ -9,6 +9,7 @@
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.js",
|
||||
"scripts": {
|
||||
"test": "build-scripts test --config build.test.json",
|
||||
"build": "build-scripts build --skip-demo"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@ -320,21 +320,25 @@ export class AssetLoader {
|
||||
}
|
||||
|
||||
private async loadAsyncLibrary(asyncLibraryMap) {
|
||||
const promiseList = []; const libraryKeyList = [];
|
||||
const promiseList = [];
|
||||
const libraryKeyList = [];
|
||||
const pkgs = [];
|
||||
for (const key in asyncLibraryMap) {
|
||||
// 需要异步加载
|
||||
if (asyncLibraryMap[key].async) {
|
||||
promiseList.push(window[asyncLibraryMap[key].library]);
|
||||
libraryKeyList.push(asyncLibraryMap[key].library);
|
||||
pkgs.push(asyncLibraryMap[key]);
|
||||
}
|
||||
}
|
||||
await Promise.all(promiseList).then((mods) => {
|
||||
if (mods.length > 0) {
|
||||
mods.map((item, index) => {
|
||||
window[libraryKeyList[index]] = item;
|
||||
const { exportMode, exportSourceLibrary, library } = pkgs[index];
|
||||
window[libraryKeyList[index]] = exportMode === 'functionCall' && (exportSourceLibrary == null || exportSourceLibrary === library) ? item() : item;
|
||||
return item;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,7 +26,7 @@ export function compatibleLegaoSchema(props: any): any {
|
||||
type: 'JSSlot',
|
||||
title: (props.value.props as any)?.slotTitle,
|
||||
name: (props.value.props as any)?.slotName,
|
||||
value: props.value.children,
|
||||
value: compatibleLegaoSchema(props.value.children),
|
||||
params: (props.value.props as any)?.slotParams,
|
||||
};
|
||||
} else {
|
||||
|
||||
23
packages/utils/test/src/__snapshots__/schema.test.ts.snap
Normal file
23
packages/utils/test/src/__snapshots__/schema.test.ts.snap
Normal file
@ -0,0 +1,23 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Schema Ut props 1`] = `
|
||||
Object {
|
||||
"props": Object {
|
||||
"mobileSlot": Object {
|
||||
"name": undefined,
|
||||
"params": undefined,
|
||||
"title": undefined,
|
||||
"type": "JSSlot",
|
||||
"value": Array [
|
||||
Object {
|
||||
"loop": Object {
|
||||
"mock": undefined,
|
||||
"type": "JSExpression",
|
||||
"value": "props.content",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
27
packages/utils/test/src/schema.test.ts
Normal file
27
packages/utils/test/src/schema.test.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { compatibleLegaoSchema } from '../../src/schema';
|
||||
describe('Schema Ut', () => {
|
||||
it('props', () => {
|
||||
const schema = {
|
||||
props: {
|
||||
mobileSlot: {
|
||||
type: "JSBlock",
|
||||
value: {
|
||||
componentName: "Slot",
|
||||
children: [
|
||||
{
|
||||
loop: {
|
||||
variable: "props.content",
|
||||
type: "variable"
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = compatibleLegaoSchema(schema);
|
||||
expect(result).toMatchSnapshot();
|
||||
expect(result.props.mobileSlot.value[0].loop.type).toBe('JSExpression');
|
||||
});
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user