Merge branch 'develop' into release/1.0.80

This commit is contained in:
lihao.ylh 2022-03-29 10:01:39 +08:00
commit f6538b6704
21 changed files with 2224 additions and 21 deletions

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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}

View File

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

View 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/**',
],
};

View File

@ -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",

View File

@ -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);

View File

@ -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;
}

View 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

View 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();
});
})
})

View 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;

View 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();

View File

@ -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;
}

View File

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

View 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/**',
],
};

View File

@ -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": {

View File

@ -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;
});
}
});
}
}
}

View File

@ -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 {

View 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",
},
},
],
},
},
}
`;

View 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');
});
})