mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-12 19:52:51 +00:00
test: add ut for render-core/utils/common
This commit is contained in:
parent
e08084e357
commit
d5daba8aaf
@ -1,24 +1,41 @@
|
||||
module.exports = {
|
||||
transform: {
|
||||
'^.+\\.(ts|tsx)$': 'ts-jest',
|
||||
'^.+\\.(js|ts|tsx|jsx)$': 'babel-jest',
|
||||
'^.+\\.(css|less|scss)$': './test/mock/styleMock.js',
|
||||
},
|
||||
// testMatch: ['**/bugs/*.test.ts'],
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
const esModules = ['zen-logger'].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$'],
|
||||
// transformIgnorePatterns: [
|
||||
// `/node_modules/(?!${esModules})/`,
|
||||
// ],
|
||||
testEnvironment: 'jsdom',
|
||||
// testMatch: ['**/*/common.test.ts'],
|
||||
transformIgnorePatterns: [
|
||||
`/node_modules/(?!${esModules})/`,
|
||||
],
|
||||
setupFiles: ['./tests/fixtures/unhandled-rejection.ts'],
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||
collectCoverage: false,
|
||||
collectCoverageFrom: [
|
||||
'src/**/*.{ts,tsx}',
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^.+.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
|
||||
},
|
||||
setupFilesAfterEnv: [
|
||||
'./test/setup.ts',
|
||||
'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',
|
||||
'!**/node_modules/**',
|
||||
'!**/vendor/**',
|
||||
],
|
||||
};
|
||||
|
||||
// 只对本仓库内的 pkg 做 mapping
|
||||
jestConfig.moduleNameMapper = {};
|
||||
jestConfig.moduleNameMapper[`^@alilc/lowcode\\-(${pkgNames.join('|')})$`] = '<rootDir>/../$1/src';
|
||||
|
||||
module.exports = jestConfig;
|
||||
@ -10,8 +10,9 @@
|
||||
"es"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "build-scripts build --skip-demo",
|
||||
"test": "build-scripts test --config build.test.json",
|
||||
"build": "build-scripts build --skip-demo"
|
||||
"test:cov": "build-scripts test --config build.test.json --jest-coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alilc/lowcode-datasource-engine": "^1.0.0",
|
||||
@ -32,20 +33,24 @@
|
||||
"zen-logger": "^1.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@alilc/lowcode-designer": "^1.0.5",
|
||||
"@alib/build-scripts": "^0.1.18",
|
||||
"@alilc/lowcode-test-mate": "^1.0.1",
|
||||
"@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-test-renderer": "^17.0.1",
|
||||
"@types/serialize-javascript": "^5.0.0",
|
||||
"babel-jest": "^27.4.6",
|
||||
"babel-jest": "^26.5.2",
|
||||
"build-plugin-component": "^0.2.11",
|
||||
"jest": "^27.4.7",
|
||||
"jest": "^26.6.3",
|
||||
"react-test-renderer": "^17.0.2",
|
||||
"ts-jest": "^27.1.3"
|
||||
"ts-jest": "^26.5.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import baseRendererFactory from './base';
|
||||
import { isEmpty, goldlog } from '../utils';
|
||||
import { isEmpty } from '../utils';
|
||||
import { IRendererAppHelper, IBaseRendererProps, IBaseRenderComponent } from '../types';
|
||||
|
||||
export default function addonRendererFactory(): IBaseRenderComponent {
|
||||
@ -57,21 +57,6 @@ export default function addonRendererFactory(): IBaseRenderComponent {
|
||||
}
|
||||
}
|
||||
|
||||
goldlog = (goKey: string, params: any) => {
|
||||
const { addonKey, addonConfig = {} } = this.props.config || {};
|
||||
goldlog(
|
||||
goKey,
|
||||
{
|
||||
addonKey,
|
||||
package: addonConfig.package,
|
||||
version: addonConfig.version,
|
||||
...this.appHelper.logParams,
|
||||
...params,
|
||||
},
|
||||
'addon',
|
||||
);
|
||||
};
|
||||
|
||||
get utils() {
|
||||
const { utils = {} } = this.context.config || {};
|
||||
return { ...this.appHelper.utils, ...utils };
|
||||
|
||||
@ -930,7 +930,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
||||
|
||||
const buitin = capitalizeFirstLetter(this.__namespace);
|
||||
const componentNames = [buitin, ...extraComponents];
|
||||
return !isSchema(schema, true) || !componentNames.includes(schema?.componentName ?? '');
|
||||
return !isSchema(schema) || !componentNames.includes(schema?.componentName ?? '');
|
||||
};
|
||||
|
||||
get requestHandlersMap() {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import Debug from 'debug';
|
||||
import adapter from '../adapter';
|
||||
import contextFactory from '../context';
|
||||
import { isFileSchema, goldlog, isEmpty } from '../utils';
|
||||
import { isFileSchema, isEmpty } from '../utils';
|
||||
import baseRendererFactory from './base';
|
||||
import divFactory from '../components/Div';
|
||||
import { IGeneralConstructor, IRenderComponent, IRendererProps, IRendererState } from '../types';
|
||||
@ -71,14 +71,6 @@ export default function rendererFactory(): IRenderComponent {
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
goldlog(
|
||||
'EXP',
|
||||
{
|
||||
action: 'appear',
|
||||
value: !!this.props.designMode,
|
||||
},
|
||||
'renderer',
|
||||
);
|
||||
debug(`entry.componentDidMount - ${this.props.schema && this.props.schema.componentName}`);
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@ import type { ComponentLifecycle, CSSProperties } from 'react';
|
||||
import { BuiltinSimulatorHost } from '@alilc/lowcode-designer';
|
||||
import { RequestHandler, NodeSchema, NodeData, RootSchema, JSONObject } from '@alilc/lowcode-types';
|
||||
|
||||
export type ISchema = NodeSchema | RootSchema;
|
||||
|
||||
/*
|
||||
** Duck typed component type supporting both react and rax
|
||||
*/
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable no-new-func */
|
||||
import Debug from 'debug';
|
||||
import { isI18nData, RootSchema, NodeSchema, isJSExpression, JSSlot } from '@alilc/lowcode-types';
|
||||
@ -35,35 +36,53 @@ const EXPRESSION_TYPE = {
|
||||
I18N: 'i18n',
|
||||
};
|
||||
|
||||
const hasSymbol = typeof Symbol === 'function' && Symbol.for;
|
||||
const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0;
|
||||
const debug = Debug('utils:index');
|
||||
|
||||
const ENV = {
|
||||
TBE: 'TBE',
|
||||
WEBIDE: 'WEB-IDE',
|
||||
VSCODE: 'VSCODE',
|
||||
WEB: 'WEB',
|
||||
/**
|
||||
* check if schema passed in is a valid schema
|
||||
* @name isSchema
|
||||
* @returns boolean
|
||||
*/
|
||||
export function isSchema(schema: any): schema is NodeSchema {
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @name isSchema
|
||||
* @description 判断是否是模型结构
|
||||
* check if schema passed in is a container type, including : Component Block Page
|
||||
* @param schema
|
||||
* @returns boolean
|
||||
*/
|
||||
export function isSchema(schema: any, ignoreArr = false): schema is NodeSchema {
|
||||
if (isEmpty(schema)) return false;
|
||||
// Leaf 组件也返回 true
|
||||
if (schema.componentName === 'Leaf' || schema.componentName === 'Slot') return true;
|
||||
if (!ignoreArr && Array.isArray(schema)) return schema.every((item) => isSchema(item));
|
||||
return !!(schema.componentName && schema.props && (typeof schema.props === 'object' || isJSExpression(schema.props)));
|
||||
}
|
||||
|
||||
export function isFileSchema(schema: NodeSchema): schema is RootSchema {
|
||||
if (isEmpty(schema)) return false;
|
||||
return ['Page', 'Block', 'Component', 'Addon', 'Temp'].includes(schema.componentName);
|
||||
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;
|
||||
@ -72,8 +91,15 @@ export function inSameDomain() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get css styled name from schema`s fileName
|
||||
* FileName -> lce-file-name
|
||||
* @returns string
|
||||
*/
|
||||
export function getFileCssName(fileName: string) {
|
||||
if (!fileName) return;
|
||||
if (!fileName) {
|
||||
return;
|
||||
}
|
||||
const name = fileName.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
return (`lce-${name}`)
|
||||
.split('-')
|
||||
@ -81,79 +107,45 @@ export function getFileCssName(fileName: string) {
|
||||
.join('-');
|
||||
}
|
||||
|
||||
// 兼容乐高设计态 JSBlock 的老协议
|
||||
/**
|
||||
* check if a object is type of JSSlot
|
||||
* @returns string
|
||||
*/
|
||||
export function isJSSlot(obj: any): obj is JSSlot {
|
||||
return obj && typeof obj === 'object' && ([EXPRESSION_TYPE.JSSLOT, EXPRESSION_TYPE.JSBLOCK].includes(obj.type));
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @name wait
|
||||
* @description 等待函数
|
||||
* get value from an object
|
||||
* @returns string
|
||||
*/
|
||||
export function wait(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(() => resolve(true), ms));
|
||||
}
|
||||
|
||||
export function curry(Comp: any, hocs = []) {
|
||||
return hocs.reverse().reduce((pre, cur: (pre: any) => any) => {
|
||||
return cur(pre);
|
||||
}, Comp);
|
||||
}
|
||||
|
||||
export function getValue(obj: any, path: string, defaultValue = {}) {
|
||||
if (isEmpty(obj) || typeof obj !== 'object') return 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;
|
||||
if (res === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 更新obj的内容但不改变obj的指针
|
||||
export function fillObj(receiver: any = {}, ...suppliers: any) {
|
||||
Object.keys(receiver).forEach((item) => {
|
||||
delete receiver[item];
|
||||
});
|
||||
Object.assign(receiver, ...suppliers);
|
||||
return receiver;
|
||||
}
|
||||
|
||||
// 中划线转驼峰
|
||||
export function toHump(name: string) {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
return name.replace(/\-(\w)/g, (_: any, letter: string) => {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
// 驼峰转中划线
|
||||
export function toLine(name: string) {
|
||||
return name.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
}
|
||||
|
||||
// 获取当前环境
|
||||
export function getEnv() {
|
||||
const { userAgent } = navigator;
|
||||
const isVscode = /Electron\//.test(userAgent);
|
||||
if (isVscode) return ENV.VSCODE;
|
||||
const isTheia = (window as any).is_theia === true;
|
||||
if (isTheia) return ENV.WEBIDE;
|
||||
return ENV.WEB;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于构造国际化字符串处理函数
|
||||
* @param {*} locale 国际化标识,例如 zh-CN、en-US
|
||||
* @param {*} messages 国际化语言包
|
||||
*/
|
||||
export function generateI18n(locale = 'zh-CN', messages: any = {}) {
|
||||
return (key: string, values = {}) => {
|
||||
if (!messages || !messages[key]) return '';
|
||||
const formater = new IntlMessageFormat(messages[key], locale);
|
||||
return formater.format(values);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于处理国际化字符串
|
||||
* @param {*} key 语料标识
|
||||
@ -162,7 +154,9 @@ export function generateI18n(locale = 'zh-CN', messages: any = {}) {
|
||||
* @param {*} messages 国际化语言包
|
||||
*/
|
||||
export function getI18n(key: string, values = {}, locale = 'zh-CN', messages: Record<string, any> = {}) {
|
||||
if (!messages || !messages[locale] || !messages[locale][key]) return '';
|
||||
if (!messages || !messages[locale] || !messages[locale][key]) {
|
||||
return '';
|
||||
}
|
||||
const formater = new IntlMessageFormat(messages[locale][key], locale);
|
||||
return formater.format(values);
|
||||
}
|
||||
@ -172,107 +166,46 @@ export function getI18n(key: string, values = {}, locale = 'zh-CN', messages: Re
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 黄金令箭埋点
|
||||
* @param {String} gmKey 为黄金令箭业务类型
|
||||
* @param {Object} params 参数
|
||||
* @param {String} logKey 属性串
|
||||
* 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 goldlog(gmKey: string, params = {}, logKey = 'other') {
|
||||
// vscode 黄金令箭API
|
||||
const sendIDEMessage = (window as any).sendIDEMessage || (inSameDomain() && (window.parent as any).sendIDEMessage);
|
||||
const goKey = serializeParams({
|
||||
sdkVersion: pkg.version,
|
||||
env: getEnv(),
|
||||
...params,
|
||||
});
|
||||
if (sendIDEMessage) {
|
||||
sendIDEMessage({
|
||||
action: 'goldlog',
|
||||
data: {
|
||||
logKey: `/lce.core.${logKey}`,
|
||||
gmKey,
|
||||
goKey,
|
||||
},
|
||||
});
|
||||
}
|
||||
(window as any)?.goldlog?.record(`/lce.core.${logKey}`, gmKey, goKey, 'POST');
|
||||
}
|
||||
|
||||
// utils为编辑器打包生成的utils文件内容,utilsConfig为数据库存放的utils配置
|
||||
export function generateUtils(utils: any, utilsConfig: Array<{ name: string; type: string; content: any }>) {
|
||||
if (!Array.isArray(utilsConfig)) return { ...utils };
|
||||
const res: any = {};
|
||||
utilsConfig.forEach((item) => {
|
||||
if (!item.name || !item.type || !item.content) return;
|
||||
if (item.type === 'function' && typeof item.content === 'function') {
|
||||
res[item.name] = item.content;
|
||||
} else if (item.type === 'npm' && utils[item.name]) {
|
||||
res[item.name] = utils[item.name];
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
// 将函数返回结果转成promise形式,如果函数有返回值则根据返回值的bool类型判断是reject还是resolve,若函数无返回值默认执行resolve
|
||||
export function transformToPromise(input: any) {
|
||||
if (input instanceof Promise) return input;
|
||||
return new Promise((resolve, reject) => {
|
||||
if (input || input === undefined) {
|
||||
resolve({});
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function moveArrayItem(arr: any[], sourceIdx: number, distIdx: number, direction: 'after' | 'before') {
|
||||
if (
|
||||
!Array.isArray(arr) ||
|
||||
sourceIdx === distIdx ||
|
||||
sourceIdx < 0 ||
|
||||
sourceIdx >= arr.length ||
|
||||
distIdx < 0 ||
|
||||
distIdx >= arr.length
|
||||
) return arr;
|
||||
const item = arr[sourceIdx];
|
||||
if (direction === 'after') {
|
||||
arr.splice(distIdx + 1, 0, item);
|
||||
} else {
|
||||
arr.splice(distIdx, 0, item);
|
||||
}
|
||||
if (sourceIdx < distIdx) {
|
||||
arr.splice(sourceIdx, 1);
|
||||
} else {
|
||||
arr.splice(sourceIdx + 1, 1);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
export function transformArrayToMap(arr: any[], key: string, overwrite = true) {
|
||||
if (isEmpty(arr) || !Array.isArray(arr)) return {};
|
||||
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;
|
||||
if (item[key] === undefined) {
|
||||
return;
|
||||
}
|
||||
if (res[curKey] && !overwrite) {
|
||||
return;
|
||||
}
|
||||
res[curKey] = item;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
export function checkPropTypes(value: any, name: string, rule: any, componentName: string) {
|
||||
let ruleFunction = rule;
|
||||
if (typeof rule === 'string') {
|
||||
rule = new Function(`"use strict"; const PropTypes = arguments[0]; return ${rule}`)(PropTypes2);
|
||||
ruleFunction = new Function(`"use strict"; const PropTypes = arguments[0]; return ${rule}`)(PropTypes2);
|
||||
}
|
||||
if (!rule || typeof rule !== 'function') {
|
||||
if (!ruleFunction || typeof ruleFunction !== 'function') {
|
||||
console.warn('checkPropTypes should have a function type rule argument');
|
||||
return true;
|
||||
}
|
||||
const err = rule(
|
||||
const err = ruleFunction(
|
||||
{
|
||||
[name]: value,
|
||||
},
|
||||
@ -288,62 +221,16 @@ export function checkPropTypes(value: any, name: string, rule: any, componentNam
|
||||
return !err;
|
||||
}
|
||||
|
||||
export function transformSchemaToPure(obj: any) {
|
||||
const pureObj = (obj: any): any => {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => pureObj(item));
|
||||
} else if (typeof obj === 'object') {
|
||||
// 对于undefined及null直接返回
|
||||
if (!obj) return obj;
|
||||
const res: any = {};
|
||||
forEach(obj, (val: any, key: string) => {
|
||||
if (key.startsWith('__') && key !== '__ignoreParse') return;
|
||||
res[key] = pureObj(val);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
return pureObj(obj);
|
||||
}
|
||||
|
||||
export function transformSchemaToStandard(obj: any) {
|
||||
const standardObj = (obj: any): any => {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => standardObj(item));
|
||||
} else if (typeof obj === 'object') {
|
||||
// 对于undefined及null直接返回
|
||||
if (!obj) return obj;
|
||||
const res: any = {};
|
||||
forEach(obj, (val: any, key: string) => {
|
||||
if (key.startsWith('__') && key !== '__ignoreParse') return;
|
||||
if (isSchema(val) && key !== 'children' && obj.type !== 'JSSlot') {
|
||||
res[key] = {
|
||||
type: 'JSSlot',
|
||||
value: standardObj(val),
|
||||
};
|
||||
// table特殊处理
|
||||
if (key === 'cell') {
|
||||
res[key].params = ['value', 'index', 'record'];
|
||||
}
|
||||
} else {
|
||||
res[key] = standardObj(val);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
} else if (typeof obj === 'function') {
|
||||
return {
|
||||
type: 'JSFunction',
|
||||
value: obj.toString(),
|
||||
};
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
return standardObj(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (typeof str !== 'string') {
|
||||
return str;
|
||||
}
|
||||
if (inSameDomain() && (window.parent as any).__newFunc) {
|
||||
return (window.parent as any).__newFunc(`"use strict"; return ${str}`)();
|
||||
} else {
|
||||
@ -351,6 +238,96 @@ export function transformStringToFunction(str: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象类型JSExpression,支持省略this
|
||||
* @param str expression in string form
|
||||
* @param self scope object
|
||||
* @returns funtion
|
||||
*/
|
||||
export function parseExpression(str: any, self: any) {
|
||||
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($scope || {}) { ${tarStr} }`;
|
||||
return new Function('$scope', code)(self);
|
||||
} catch (err) {
|
||||
debug('parseExpression.error', err, str, self);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
||||
export function parseData(schema: unknown, self: any): any {
|
||||
if (isJSExpression(schema)) {
|
||||
return parseExpression(schema, self);
|
||||
@ -364,10 +341,14 @@ export function parseData(schema: unknown, self: any): any {
|
||||
return schema.bind(self);
|
||||
} else if (typeof schema === 'object') {
|
||||
// 对于undefined及null直接返回
|
||||
if (!schema) return schema;
|
||||
if (!schema) {
|
||||
return schema;
|
||||
}
|
||||
const res: any = {};
|
||||
forEach(schema, (val: any, key: string) => {
|
||||
if (key.startsWith('__')) return;
|
||||
if (key.startsWith('__')) {
|
||||
return;
|
||||
}
|
||||
res[key] = parseData(val, self);
|
||||
});
|
||||
return res;
|
||||
@ -375,79 +356,22 @@ export function parseData(schema: unknown, self: any): any {
|
||||
return schema;
|
||||
}
|
||||
|
||||
/* 全匹配{{开头,}}结尾的变量表达式,或者对象类型JSExpression,支持省略this */
|
||||
export function parseExpression(str: any, self: any) {
|
||||
try {
|
||||
const contextArr = ['"use strict";', 'var __self = arguments[0];'];
|
||||
contextArr.push('return ');
|
||||
let tarStr;
|
||||
|
||||
tarStr = (str.value || '').trim();
|
||||
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($scope || {}) { ${tarStr} }`;
|
||||
return new Function('$scope', code)(self);
|
||||
} catch (err) {
|
||||
debug('parseExpression.error', err, str, self);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// 首字母大写
|
||||
export function capitalizeFirstLetter(word: string) {
|
||||
return word[0].toUpperCase() + word.slice(1);
|
||||
}
|
||||
|
||||
export function isVariable(obj: any) {
|
||||
return obj && typeof obj === 'object' && obj?.type === 'variable';
|
||||
}
|
||||
|
||||
/* 将 i18n 结构,降级解释为对 i18n 接口的调用 */
|
||||
export function parseI18n(i18nInfo: any, self: any) {
|
||||
return parseExpression({
|
||||
type: EXPRESSION_TYPE.JSEXPRESSION,
|
||||
value: `this.i18n('${i18nInfo.key}')`,
|
||||
}, self);
|
||||
}
|
||||
|
||||
export function forEach(obj: any, fn: any, context?: any) {
|
||||
obj = obj || {};
|
||||
Object.keys(obj).forEach(key => fn.call(context, obj[key], key));
|
||||
}
|
||||
|
||||
export function shallowEqual(objA: any, objB: any) {
|
||||
if (objA === objB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const keysA = Object.keys(objA);
|
||||
if (keysA.length !== Object.keys(objB).length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0, key; i < keysA.length; i++) {
|
||||
key = keysA[i];
|
||||
if (!objB.hasOwnProperty(key) || objA[key] !== objB[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* process params for using in a url query
|
||||
* @param obj params to be processed
|
||||
* @returns string
|
||||
*/
|
||||
export function serializeParams(obj: any) {
|
||||
let rst: any = [];
|
||||
let result: any = [];
|
||||
forEach(obj, (val: any, key: any) => {
|
||||
if (val === null || val === undefined || val === '') return;
|
||||
if (typeof val === 'object') rst.push(`${key}=${encodeURIComponent(JSON.stringify(val))}`);
|
||||
else rst.push(`${key}=${encodeURIComponent(val)}`);
|
||||
});
|
||||
return rst.join('&');
|
||||
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('&');
|
||||
}
|
||||
@ -1,280 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`loop schema loop key 1`] = `
|
||||
<div
|
||||
className="lce-page _css_pseudo_node_ocl1djd9o41"
|
||||
style={Object {}}
|
||||
>
|
||||
<div
|
||||
__componentName="RootHeader"
|
||||
__id="node_ocl1djd9o42"
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
<div
|
||||
__componentName="RootContent"
|
||||
__id="node_ocl1djd9o43"
|
||||
contentBgColor="white"
|
||||
contentMargin="20"
|
||||
contentPadding="20"
|
||||
>
|
||||
<div
|
||||
__componentName="Div"
|
||||
__id="node_ocl1djd9o45"
|
||||
__style__={Object {}}
|
||||
behavior="NORMAL"
|
||||
className="_css_pseudo_node_ocl1djd9o45"
|
||||
customClassName=""
|
||||
events={
|
||||
Object {
|
||||
"ignored": true,
|
||||
}
|
||||
}
|
||||
fieldId="div_l1djdj1n"
|
||||
useFieldIdAsDomId={false}
|
||||
>
|
||||
<div
|
||||
__componentName="Div"
|
||||
__id="node_ocl1djd9o46"
|
||||
__style__={Object {}}
|
||||
behavior="NORMAL"
|
||||
className="_css_pseudo_node_ocl1djd9o46"
|
||||
customClassName=""
|
||||
events={
|
||||
Object {
|
||||
"ignored": true,
|
||||
}
|
||||
}
|
||||
fieldId="div_l1djdj1o"
|
||||
useFieldIdAsDomId={false}
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
<div
|
||||
__componentName="Div"
|
||||
__id="node_ocl1djd9o46"
|
||||
__style__={Object {}}
|
||||
behavior="NORMAL"
|
||||
className="_css_pseudo_node_ocl1djd9o46"
|
||||
customClassName=""
|
||||
events={
|
||||
Object {
|
||||
"ignored": true,
|
||||
}
|
||||
}
|
||||
fieldId="div_l1djdj1o"
|
||||
useFieldIdAsDomId={false}
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
<div
|
||||
__componentName="Div"
|
||||
__id="node_ocl1djd9o46"
|
||||
__style__={Object {}}
|
||||
behavior="NORMAL"
|
||||
className="_css_pseudo_node_ocl1djd9o46"
|
||||
customClassName=""
|
||||
events={
|
||||
Object {
|
||||
"ignored": true,
|
||||
}
|
||||
}
|
||||
fieldId="div_l1djdj1o"
|
||||
useFieldIdAsDomId={false}
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
__componentName="Div"
|
||||
__id="node_ocl1djd9o45"
|
||||
__style__={Object {}}
|
||||
behavior="NORMAL"
|
||||
className="_css_pseudo_node_ocl1djd9o45"
|
||||
customClassName=""
|
||||
events={
|
||||
Object {
|
||||
"ignored": true,
|
||||
}
|
||||
}
|
||||
fieldId="div_l1djdj1n"
|
||||
useFieldIdAsDomId={false}
|
||||
>
|
||||
<div
|
||||
__componentName="Div"
|
||||
__id="node_ocl1djd9o46"
|
||||
__style__={Object {}}
|
||||
behavior="NORMAL"
|
||||
className="_css_pseudo_node_ocl1djd9o46"
|
||||
customClassName=""
|
||||
events={
|
||||
Object {
|
||||
"ignored": true,
|
||||
}
|
||||
}
|
||||
fieldId="div_l1djdj1o"
|
||||
useFieldIdAsDomId={false}
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
<div
|
||||
__componentName="Div"
|
||||
__id="node_ocl1djd9o46"
|
||||
__style__={Object {}}
|
||||
behavior="NORMAL"
|
||||
className="_css_pseudo_node_ocl1djd9o46"
|
||||
customClassName=""
|
||||
events={
|
||||
Object {
|
||||
"ignored": true,
|
||||
}
|
||||
}
|
||||
fieldId="div_l1djdj1o"
|
||||
useFieldIdAsDomId={false}
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
<div
|
||||
__componentName="Div"
|
||||
__id="node_ocl1djd9o46"
|
||||
__style__={Object {}}
|
||||
behavior="NORMAL"
|
||||
className="_css_pseudo_node_ocl1djd9o46"
|
||||
customClassName=""
|
||||
events={
|
||||
Object {
|
||||
"ignored": true,
|
||||
}
|
||||
}
|
||||
fieldId="div_l1djdj1o"
|
||||
useFieldIdAsDomId={false}
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
__componentName="Div"
|
||||
__id="node_ocl1djd9o45"
|
||||
__style__={Object {}}
|
||||
behavior="NORMAL"
|
||||
className="_css_pseudo_node_ocl1djd9o45"
|
||||
customClassName=""
|
||||
events={
|
||||
Object {
|
||||
"ignored": true,
|
||||
}
|
||||
}
|
||||
fieldId="div_l1djdj1n"
|
||||
useFieldIdAsDomId={false}
|
||||
>
|
||||
<div
|
||||
__componentName="Div"
|
||||
__id="node_ocl1djd9o46"
|
||||
__style__={Object {}}
|
||||
behavior="NORMAL"
|
||||
className="_css_pseudo_node_ocl1djd9o46"
|
||||
customClassName=""
|
||||
events={
|
||||
Object {
|
||||
"ignored": true,
|
||||
}
|
||||
}
|
||||
fieldId="div_l1djdj1o"
|
||||
useFieldIdAsDomId={false}
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
<div
|
||||
__componentName="Div"
|
||||
__id="node_ocl1djd9o46"
|
||||
__style__={Object {}}
|
||||
behavior="NORMAL"
|
||||
className="_css_pseudo_node_ocl1djd9o46"
|
||||
customClassName=""
|
||||
events={
|
||||
Object {
|
||||
"ignored": true,
|
||||
}
|
||||
}
|
||||
fieldId="div_l1djdj1o"
|
||||
useFieldIdAsDomId={false}
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
<div
|
||||
__componentName="Div"
|
||||
__id="node_ocl1djd9o46"
|
||||
__style__={Object {}}
|
||||
behavior="NORMAL"
|
||||
className="_css_pseudo_node_ocl1djd9o46"
|
||||
customClassName=""
|
||||
events={
|
||||
Object {
|
||||
"ignored": true,
|
||||
}
|
||||
}
|
||||
fieldId="div_l1djdj1o"
|
||||
useFieldIdAsDomId={false}
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
__componentName="RootFooter"
|
||||
__id="node_ocl1djd9o44"
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`notFountComponent not found snapshot 1`] = `
|
||||
<div
|
||||
className="lce-page _css_pseudo_node_ockyigdqxl1"
|
||||
style={Object {}}
|
||||
>
|
||||
<div
|
||||
__componentName="RootHeader"
|
||||
__id="node_ockyigdqxl2"
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
<div
|
||||
__componentName="RootContent"
|
||||
__id="node_ockyigdqxl3"
|
||||
contentBgColor="white"
|
||||
contentMargin="20"
|
||||
contentPadding="20"
|
||||
>
|
||||
<div
|
||||
__componentName="Button"
|
||||
__id="node_ockyigdqxl5"
|
||||
__style__={Object {}}
|
||||
baseIcon=""
|
||||
behavior="NORMAL"
|
||||
className="_css_pseudo_node_ockyigdqxl5"
|
||||
content="按 钮"
|
||||
events={
|
||||
Object {
|
||||
"ignored": true,
|
||||
}
|
||||
}
|
||||
fieldId="button_kyige3yf"
|
||||
loading={false}
|
||||
otherIcon=""
|
||||
size="medium"
|
||||
triggerEventsWhenLoading={false}
|
||||
type="primary"
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
__componentName="RootFooter"
|
||||
__id="node_ockyigdqxl4"
|
||||
>
|
||||
Component Not Found
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -1,42 +0,0 @@
|
||||
import renderer from 'react-test-renderer';
|
||||
import React from 'react';
|
||||
import '../utils/react-env-init';
|
||||
import pageRendererFactory from '../../src/renderer/renderer';
|
||||
import { sampleSchema } from '../mock/sample';
|
||||
import loopSchema from '../mock/loop';
|
||||
|
||||
describe('notFountComponent', () => {
|
||||
const Render = pageRendererFactory();
|
||||
|
||||
const component = renderer.create(
|
||||
// @ts-ignore
|
||||
<Render
|
||||
schema={sampleSchema as any}
|
||||
components={{}}
|
||||
appHelper={{}}
|
||||
/>,
|
||||
);
|
||||
|
||||
it('not found snapshot', () => {
|
||||
let tree = component.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('loop schema', () => {
|
||||
it('loop key', () => {
|
||||
const Render = pageRendererFactory();
|
||||
|
||||
const component = renderer.create(
|
||||
// @ts-ignore
|
||||
<Render
|
||||
schema={loopSchema as any}
|
||||
components={{}}
|
||||
appHelper={{}}
|
||||
/>,
|
||||
);
|
||||
|
||||
let tree = component.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
})
|
||||
})
|
||||
567
packages/renderer-core/tests/fixtures/schema/basic.ts
vendored
Normal file
567
packages/renderer-core/tests/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',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
7
packages/renderer-core/tests/fixtures/unhandled-rejection.ts
vendored
Normal file
7
packages/renderer-core/tests/fixtures/unhandled-rejection.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
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';
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
324
packages/renderer-core/tests/renderer/renderer.test.tsx
Normal file
324
packages/renderer-core/tests/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/react-env-init';
|
||||
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 as any}
|
||||
/>);
|
||||
|
||||
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 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).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: '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: "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();
|
||||
});
|
||||
})
|
||||
})
|
||||
12
packages/renderer-core/tests/setup.ts
Normal file
12
packages/renderer-core/tests/setup.ts
Normal file
@ -0,0 +1,12 @@
|
||||
jest.mock('zen-logger', () => {
|
||||
class Logger {
|
||||
log() {}
|
||||
error() {}
|
||||
warn() {}
|
||||
debug() {}
|
||||
}
|
||||
return {
|
||||
__esModule: true,
|
||||
default: Logger,
|
||||
};
|
||||
});
|
||||
426
packages/renderer-core/tests/utils/common.test.ts
Normal file
426
packages/renderer-core/tests/utils/common.test.ts
Normal file
@ -0,0 +1,426 @@
|
||||
// @ts-nocheck
|
||||
import {
|
||||
isSchema,
|
||||
isFileSchema,
|
||||
inSameDomain,
|
||||
getFileCssName,
|
||||
isJSSlot,
|
||||
getValue,
|
||||
getI18n,
|
||||
transformArrayToMap,
|
||||
transformStringToFunction,
|
||||
isVariable,
|
||||
capitalizeFirstLetter,
|
||||
forEach,
|
||||
isString,
|
||||
serializeParams,
|
||||
parseExpression,
|
||||
parseI18n,
|
||||
parseData,
|
||||
} from '../../src/utils/common';
|
||||
|
||||
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));
|
||||
});
|
||||
});
|
||||
|
||||
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));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
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();
|
||||
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user