diff --git a/packages/renderer-core/src/renderer/base.tsx b/packages/renderer-core/src/renderer/base.tsx index fb0354af3..830c92dbb 100644 --- a/packages/renderer-core/src/renderer/base.tsx +++ b/packages/renderer-core/src/renderer/base.tsx @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ /* eslint-disable react/prop-types */ import classnames from 'classnames'; import { create as createDataSourceEngine } from '@alilc/lowcode-datasource-engine/interpret'; @@ -154,8 +155,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { this.__showPlaceholder = false; return resolve({}); } - this.__dataHelper - .getInitData() + this.__dataHelper.getInitData() .then((res: any) => { this.__showPlaceholder = false; if (isEmpty(res)) { @@ -286,8 +286,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { // this.__showPlaceholder = false; return resolve({}); } - this.__dataHelper - .getInitData() + this.__dataHelper.getInitData() .then((res: any) => { // this.__showPlaceholder = false; if (isEmpty(res)) { diff --git a/packages/renderer-core/src/utils/common.ts b/packages/renderer-core/src/utils/common.ts index 5ed5bf043..437e6077f 100644 --- a/packages/renderer-core/src/utils/common.ts +++ b/packages/renderer-core/src/utils/common.ts @@ -118,7 +118,7 @@ export function isJSSlot(obj: any): obj is JSSlot { } // Compatible with the old protocol JSBlock - return ([EXPRESSION_TYPE.JSSLOT, EXPRESSION_TYPE.JSBLOCK].includes(obj.type)); + return [EXPRESSION_TYPE.JSSLOT, EXPRESSION_TYPE.JSBLOCK].includes(obj.type); } /** diff --git a/packages/renderer-core/src/utils/data-helper.ts b/packages/renderer-core/src/utils/data-helper.ts index 22dbf1b66..9eb152df9 100644 --- a/packages/renderer-core/src/utils/data-helper.ts +++ b/packages/renderer-core/src/utils/data-helper.ts @@ -1,9 +1,11 @@ +/* eslint-disable no-console */ +/* eslint-disable max-len */ /* eslint-disable object-curly-newline */ import { isJSFunction } from '@alilc/lowcode-types'; -import { transformArrayToMap, transformStringToFunction, clone } from './common'; +import { transformArrayToMap, transformStringToFunction } from './common'; import { jsonp, request, get, post } from './request'; -import { DataSource, DataSourceItem } from '../types'; import logger from './logger'; +import { DataSource, DataSourceItem, IRendererAppHelper } from '../types'; const DS_STATUS = { INIT: 'init', @@ -12,22 +14,77 @@ const DS_STATUS = { ERROR: 'error', }; +type DataSourceType = 'fetch' | 'jsonp'; + +/** + * do request for standard DataSourceType + * @param {DataSourceType} type type of DataSourceItem + * @param {any} options + */ +export function doRequest(type: DataSourceType, options: any) { + // eslint-disable-next-line prefer-const + let { uri, url, method = 'GET', headers, params, ...otherProps } = options; + otherProps = otherProps || {}; + if (type === 'jsonp') { + return jsonp(uri, params, otherProps); + } + + if (type === 'fetch') { + switch (method.toUpperCase()) { + case 'GET': + return get(uri, params, headers, otherProps); + case 'POST': + return post(uri, params, headers, otherProps); + default: + return request(uri, method, params, headers, otherProps); + } + } + + logger.log(`Engine default dataSource does not support type:[${type}] dataSource request!`, options); +} + +// TODO: according to protocol, we should implement errorHandler/shouldFetch/willFetch/requestHandler and isSync controll. export class DataHelper { + /** + * host object that will be "this" object when excuting dataHandler + * + * @type {*} + * @memberof DataHelper + */ host: any; + /** + * data source config + * + * @type {DataSource} + * @memberof DataHelper + */ config: DataSource; + /** + * a parser function which will be called to process config data + * which eventually will call common/utils.processData() to process data + * (originalConfig) => parsedConfig + * @type {*} + * @memberof DataHelper + */ parser: any; + /** + * config.list + * + * @type {any[]} + * @memberof DataHelper + */ ajaxList: any[]; ajaxMap: any; dataSourceMap: any; - appHelper: any; + appHelper: IRendererAppHelper; - constructor(comp: any, config: DataSource, appHelper: any, parser: any) { + constructor(comp: any, config: DataSource, appHelper: IRendererAppHelper, parser: any) { this.host = comp; this.config = config || {}; this.parser = parser; @@ -37,15 +94,6 @@ export class DataHelper { this.appHelper = appHelper; } - // 重置config,dataSourceMap状态会被重置; - resetConfig(config = {}) { - this.config = config as DataSource; - this.ajaxList = (config as DataSource)?.list || []; - this.ajaxMap = transformArrayToMap(this.ajaxList, 'id'); - this.dataSourceMap = this.generateDataSourceMap(); - return this.dataSourceMap; - } - // 更新config,只会更新配置,状态保存; updateConfig(config = {}) { this.config = config as DataSource; @@ -79,6 +127,7 @@ export class DataHelper { res[item.id] = { status: DS_STATUS.INIT, load: (...args: any) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return this.getDataSource(item.id, ...args); }, @@ -93,41 +142,54 @@ export class DataHelper { this.dataSourceMap[id].status = error ? DS_STATUS.ERROR : DS_STATUS.LOADED; } - getInitData() { - const initSyncData = this.parser(this.ajaxList).filter((item: DataSourceItem) => { - if (item.isInit) { + /** + * get all dataSourceItems which marked as isInit === true + * @private + * @returns + * @memberof DataHelper + */ + getInitDataSourseConfigs() { + const initConfigs = this.parser(this.ajaxList).filter((item: DataSourceItem) => { + // according to [spec](https://lowcode-engine.cn/lowcode), isInit should be boolean true to be working + if (item.isInit === true) { this.dataSourceMap[item.id].status = DS_STATUS.LOADING; return true; } return false; }); + return initConfigs; + } + + /** + * process all dataSourceItems which marked as isInit === true, and get dataSource request results. + * @public + * @returns + * @memberof DataHelper + */ + getInitData() { + const initSyncData = this.getInitDataSourseConfigs(); // 所有 datasource 的 datahandler return this.asyncDataHandler(initSyncData).then((res) => { - let { dataHandler } = this.config; - if (isJSFunction(dataHandler)) { - dataHandler = transformStringToFunction(dataHandler.value); - } - if (!dataHandler || typeof dataHandler !== 'function') return res; - try { - return (dataHandler as any).call(this.host, res); - } catch (e) { - console.error('请求数据处理函数运行出错', e); - } + const { dataHandler } = this.config; + return this.handleData(null, dataHandler, res, null); }); } getDataSource(id: string, params: any, otherOptions: any, callback: any) { const req = this.parser(this.ajaxMap[id]); const options = req.options || {}; + let callbackFn = callback; + let otherOptionsObj = otherOptions; if (typeof otherOptions === 'function') { - callback = otherOptions; - otherOptions = {}; + callbackFn = otherOptions; + otherOptionsObj = {}; } - const { headers, ...otherProps } = otherOptions || {}; + const { headers, ...otherProps } = otherOptionsObj || {}; if (!req) { console.warn(`getDataSource API named ${id} not exist`); return; } + return this.asyncDataHandler([ { ...req, @@ -149,170 +211,99 @@ export class DataHelper { }, }, ]) - .then((res: any) => { - try { - callback && callback(res && res[id]); - } catch (e) { - console.error('load请求回调函数报错', e); - } - - return res && res[id]; - }) - .catch((err) => { - try { - callback && callback(null, err); - } catch (e) { - console.error('load请求回调函数报错', e); - } - - return err; - }); + .then((res: any) => { + try { + callbackFn && callbackFn(res && res[id]); + } catch (e) { + console.error('load请求回调函数报错', e); + } + return res && res[id]; + }) + .catch((err) => { + try { + callbackFn && callbackFn(null, err); + } catch (e) { + console.error('load请求回调函数报错', e); + } + return err; + }); } asyncDataHandler(asyncDataList: any[]) { return new Promise((resolve, reject) => { - const allReq = []; - const doserReq: Array<{name: string; package: string; params: any }> = []; - const doserList: string[] = []; - const beforeRequest = this.appHelper && this.appHelper.utils && this.appHelper.utils.beforeRequest; - const afterRequest = this.appHelper && this.appHelper.utils && this.appHelper.utils.afterRequest; - const csrfInput = document.getElementById('_csrf_token'); - const _tb_token_ = (csrfInput as any)?.value; + const allReq: any[] = []; asyncDataList.forEach((req) => { - const { id, type, options } = req; - if (!id || !type || type === 'legao') return; - if (type === 'doServer') { - const { uri, params } = options || {}; - if (!uri) return; - doserList.push(id); - doserReq.push({ name: uri, package: 'cms', params }); - } else { - allReq.push(req); + const { id, type } = req; + // TODO: need refactoring to remove 'legao' related logic + if (!id || !type || type === 'legao') { + return; } + allReq.push(req); }); - if (doserReq.length > 0) { - allReq.push({ - type: 'doServer', - options: { - uri: '/nrsService.do', - cors: true, - method: 'POST', - params: { - data: JSON.stringify(doserReq), - _tb_token_, - }, - }, - }); + if (allReq.length === 0) { + resolve({}); } - if (allReq.length === 0) resolve({}); const res: any = {}; - // todo: Promise.all( allReq.map((item: any) => { - return new Promise((resolve) => { + return new Promise((innerResolve) => { const { type, id, dataHandler, options } = item; - const doFetch = (type: string, options: any) => { - this.fetchOne(type as any, options) - ?.then((data: any) => { - if (afterRequest) { - this.appHelper.utils.afterRequest(item, data, undefined, (data: any, error: any) => { - fetchHandler(data, error); - }); - } else { - fetchHandler(data, undefined); - } - }) - .catch((err: Error) => { - if (afterRequest) { - // 必须要这么调用,否则beforeRequest中的this会丢失 - this.appHelper.utils.afterRequest(item, undefined, err, (data: any, error: any) => { - fetchHandler(data, error); - }); - } else { - fetchHandler(undefined, err); - } - }); - }; + const fetchHandler = (data: any, error: any) => { - if (type === 'doServer') { - if (!Array.isArray(data)) { - data = [data]; - } - doserList.forEach((id, idx) => { - const req: any = this.ajaxMap[id]; - if (req) { - res[id] = this.dataHandler(id, req.dataHandler, data && data[idx], error); - this.updateDataSourceMap(id, res[id], error); - } - }); - } else { - res[id] = this.dataHandler(id, dataHandler, data, error); - this.updateDataSourceMap(id, res[id], error); - } - resolve({}); + res[id] = this.handleData(id, dataHandler, data, error); + this.updateDataSourceMap(id, res[id], error); + innerResolve({}); }; - if (type === 'doServer') { - doserList.forEach((item) => { - this.dataSourceMap[item].status = DS_STATUS.LOADING; - }); - } else { - this.dataSourceMap[id].status = DS_STATUS.LOADING; - } - // 请求切片 - if (beforeRequest) { - // 必须要这么调用,否则beforeRequest中的this会丢失 - this.appHelper.utils.beforeRequest(item, clone(options), (options: any) => doFetch(type, options)); - } else { - doFetch(type, options); - } + const doFetch = (innerType: string, innerOptions: any) => { + doRequest(innerType as any, innerOptions) + ?.then((data: any) => { + fetchHandler(data, undefined); + }) + .catch((err: Error) => { + fetchHandler(undefined, err); + }); + }; + + this.dataSourceMap[id].status = DS_STATUS.LOADING; + doFetch(type, options); }); }), - ) - .then(() => { - resolve(res); - }) - .catch((e) => { - reject(e); - }); + ).then(() => { + resolve(res); + }).catch((e) => { + reject(e); + }); }); } - // dataHandler todo: - dataHandler(id: string, dataHandler: any, data: any, error: any) { + /** + * process data using dataHandler + * + * @param {(string | null)} id request id, will be used in error message, can be null + * @param {*} dataHandler + * @param {*} data + * @param {*} error + * @returns + * @memberof DataHelper + */ + handleData(id: string | null, dataHandler: any, data: any, error: any) { + let dataHandlerFun = dataHandler; if (isJSFunction(dataHandler)) { - dataHandler = transformStringToFunction(dataHandler.value); + dataHandlerFun = transformStringToFunction(dataHandler.value); + } + if (!dataHandlerFun || typeof dataHandlerFun !== 'function') { + return data; } - if (!dataHandler || typeof dataHandler !== 'function') return data; try { - return dataHandler.call(this.host, data, error); + return dataHandlerFun.call(this.host, data, error); } catch (e) { - console.error(`[${ id }]单个请求数据处理函数运行出错`, e); - } - } - - fetchOne(type: DataSourceType, options: any) { - // eslint-disable-next-line prefer-const - let { uri, url, method = 'GET', headers, params, ...otherProps } = options; - otherProps = otherProps || {}; - if (type === 'jsonp') { - return jsonp(uri, params, otherProps); - } - - if (type === 'fetch') { - switch (method.toUpperCase()) { - case 'GET': - return get(uri, params, headers, otherProps); - case 'POST': - return post(uri, params, headers, otherProps); - default: - return request(uri, method, params, headers, otherProps); + if (id) { + console.error(`[${id}]单个请求数据处理函数运行出错`, e); + } else { + console.error('请求数据处理函数运行出错', e); } } - - logger.log(`Engine default dataSource not support type:[${type}] dataSource request!`, options); } } - -type DataSourceType = 'fetch' | 'jsonp'; diff --git a/packages/renderer-core/tests/utils/data-helper.test.ts b/packages/renderer-core/tests/utils/data-helper.test.ts new file mode 100644 index 000000000..cd4508ea8 --- /dev/null +++ b/packages/renderer-core/tests/utils/data-helper.test.ts @@ -0,0 +1,569 @@ +// @ts-nocheck +const mockJsonp = jest.fn(); +const mockRequest = jest.fn(); +const mockGet = jest.fn(); +const mockPost = jest.fn(); +jest.mock('../../src/utils/request', () => { + return { + jsonp: (uri, params, headers, otherProps) => { + return new Promise((resolve, reject) => { + resolve(mockJsonp(uri, params, headers, otherProps)); + }); + }, + request: (uri, params, headers, otherProps) => { + return new Promise((resolve, reject) => { + resolve(mockRequest(uri, params, headers, otherProps)); + }); + }, + get: (uri, params, headers, otherProps) => { + return new Promise((resolve, reject) => { + resolve(mockGet(uri, params, headers, otherProps)); + }); + }, + post: (uri, params, headers, otherProps) => { + return new Promise((resolve, reject) => { + resolve(mockPost(uri, params, headers, otherProps)); + }); + }, + }; + }); + +import { DataHelper, doRequest } from '../../src/utils/data-helper'; +import { parseData } from '../../src/utils/common'; + +describe('test DataHelper ', () => { + beforeEach(() => { + jest.resetModules(); + }) + it('can be inited', () => { + const mockHost = {}; + let mockDataSourceConfig = {}; + const mockAppHelper = {}; + const mockParser = (config: any) => parseData(config); + let dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser); + + expect(dataHelper).toBeTruthy(); + expect(dataHelper.host).toBe(mockHost); + expect(dataHelper.config).toBe(mockDataSourceConfig); + expect(dataHelper.appHelper).toBe(mockAppHelper); + expect(dataHelper.parser).toBe(mockParser); + + + dataHelper = new DataHelper(mockHost, undefined, mockAppHelper, mockParser); + expect(dataHelper.config).toStrictEqual({}); + expect(dataHelper.ajaxList).toStrictEqual([]); + + mockDataSourceConfig = { + list: [ + { + id: 'ds1', + }, { + id: 'ds2', + }, + ] + }; + dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser); + expect(dataHelper.config).toBe(mockDataSourceConfig); + expect(dataHelper.ajaxList.length).toBe(2); + expect(dataHelper.ajaxMap.ds1).toStrictEqual({ + id: 'ds1', + }); + }); + it('should handle generateDataSourceMap properly in constructor', () => { + const mockHost = {}; + let mockDataSourceConfig = {}; + const mockAppHelper = {}; + const mockParser = (config: any) => parseData(config); + let dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser); + + // test generateDataSourceMap logic + mockDataSourceConfig = { + list: [ + { + id: 'getInfo', + isInit: true, + type: 'fetch', // fetch/mtop/jsonp/custom + options: { + uri: 'mock/info.json', + method: 'GET', + params: { a: 1 }, + timeout: 5000, + }, + }, { + id: 'postInfo', + isInit: true, + type: 'fetch', + options: { + uri: 'mock/info.json', + method: 'POST', + params: { a: 1 }, + timeout: 5000, + }, + }, + ] + }; + dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser); + expect(Object.keys(dataHelper.dataSourceMap).length).toBe(2); + expect(dataHelper.dataSourceMap.getInfo.status).toBe('init'); + expect(typeof dataHelper.dataSourceMap.getInfo.load).toBe('function'); + }); + + it('getInitDataSourseConfigs should work', () => { + const mockHost = {}; + let mockDataSourceConfig = {}; + const mockAppHelper = {}; + const mockParser = (config: any) => parseData(config); + + // test generateDataSourceMap logic + mockDataSourceConfig = { + list: [ + { + id: 'getInfo', + isInit: true, + type: 'fetch', // fetch/mtop/jsonp/custom + options: { + uri: 'mock/info.json', + method: 'GET', + params: { a: 1 }, + timeout: 5000, + }, + }, + { + id: 'postInfo', + isInit: false, + type: 'fetch', + options: { + uri: 'mock/info.json', + method: 'POST', + params: { a: 1 }, + timeout: 5000, + }, + }, + { + id: 'getInfoLater', + isInit: false, + type: 'fetch', + options: { + uri: 'mock/info.json', + method: 'POST', + params: { a: 1 }, + timeout: 5000, + }, + }, + { + id: 'getInfoLater2', + isInit: 'not a valid boolean', + type: 'fetch', + options: { + uri: 'mock/info.json', + method: 'POST', + params: { a: 1 }, + timeout: 5000, + }, + }, + ], + }; + + const dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser); + expect(dataHelper.getInitDataSourseConfigs().length).toBe(1); + expect(dataHelper.getInitDataSourseConfigs()[0].id).toBe('getInfo'); + }); + it('util function doRequest should work', () => { + doRequest('jsonp', { + uri: 'https://www.baidu.com', + params: { a: 1 }, + otherStuff1: 'aaa', + }); + expect(mockJsonp).toBeCalled(); + + // test GET + doRequest('fetch', { + uri: 'https://www.baidu.com', + method: 'get', + params: { a: 1 }, + otherStuff1: 'aaa', + }); + expect(mockGet).toBeCalled(); + + mockGet.mockClear(); + doRequest('fetch', { + uri: 'https://www.baidu.com', + method: 'Get', + params: { a: 1 }, + otherStuff1: 'aaa', + }); + expect(mockGet).toBeCalled(); + + mockGet.mockClear(); + doRequest('fetch', { + uri: 'https://www.baidu.com', + method: 'GET', + params: { a: 1 }, + otherStuff1: 'aaa', + }); + expect(mockGet).toBeCalled(); + + mockGet.mockClear(); + + // test POST + doRequest('fetch', { + uri: 'https://www.baidu.com', + method: 'post', + params: { a: 1 }, + otherStuff1: 'aaa', + }); + expect(mockPost).toBeCalled(); + mockPost.mockClear(); + + doRequest('fetch', { + uri: 'https://www.baidu.com', + method: 'POST', + params: { a: 1 }, + otherStuff1: 'aaa', + }); + expect(mockPost).toBeCalled(); + mockPost.mockClear(); + doRequest('fetch', { + uri: 'https://www.baidu.com', + method: 'Post', + params: { a: 1 }, + otherStuff1: 'aaa', + }); + expect(mockPost).toBeCalled(); + mockPost.mockClear(); + + // test default + doRequest('fetch', { + uri: 'https://www.baidu.com', + method: 'whatever', + params: { a: 1 }, + otherStuff1: 'aaa', + }); + expect(mockRequest).toBeCalled(); + mockRequest.mockClear(); + mockGet.mockClear(); + + // method will be GET when not provided + doRequest('fetch', { + uri: 'https://www.baidu.com', + params: { a: 1 }, + otherStuff1: 'aaa', + }); + expect(mockRequest).toBeCalledTimes(0); + expect(mockGet).toBeCalledTimes(1); + + mockRequest.mockClear(); + mockGet.mockClear(); + mockPost.mockClear(); + mockJsonp.mockClear(); + + doRequest('someOtherType', { + uri: 'https://www.baidu.com', + params: { a: 1 }, + otherStuff1: 'aaa', + }); + expect(mockRequest).toBeCalledTimes(0); + expect(mockGet).toBeCalledTimes(0); + expect(mockPost).toBeCalledTimes(0); + expect(mockJsonp).toBeCalledTimes(0); + }); + it('updateDataSourceMap should work', () => { + const mockHost = {}; + const mockDataSourceConfig = { + list: [ + { + id: 'ds1', + }, { + id: 'ds2', + }, + ] + }; + const mockAppHelper = {}; + const mockParser = (config: any) => parseData(config); + const dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser); + dataHelper.updateDataSourceMap('ds1', { a: 1 }, null); + expect(dataHelper.dataSourceMap['ds1']).toBeTruthy(); + expect(dataHelper.dataSourceMap['ds1'].data).toStrictEqual({ a: 1 }); + expect(dataHelper.dataSourceMap['ds1'].error).toBeUndefined(); + expect(dataHelper.dataSourceMap['ds1'].status).toBe('loaded'); + dataHelper.updateDataSourceMap('ds2', { b: 2 }, new Error()); + expect(dataHelper.dataSourceMap['ds2']).toBeTruthy(); + expect(dataHelper.dataSourceMap['ds2'].data).toStrictEqual({ b: 2 }); + expect(dataHelper.dataSourceMap['ds2'].status).toBe('error'); + expect(dataHelper.dataSourceMap['ds2'].error).toBeTruthy(); + }); + + it('handleData should work', () => { + const mockHost = { stateA: 'aValue'}; + const mockDataSourceConfig = { + list: [ + { + id: 'fullConfigGet', + isInit: true, + options: { + params: {}, + method: 'GET', + isCors: true, + timeout: 5000, + headers: {}, + uri: 'mock/info.json', + }, + shouldFetch: { + type: 'JSFunction', + value: 'function() { return true; }', + }, + dataHandler: { + type: 'JSFunction', + value: 'function(res) { return res.data; }', + }, + errorHandler: { + type: 'JSFunction', + value: 'function(error) {}', + }, + willFetch: { + type: 'JSFunction', + value: 'function(options) { return options; }', + }, + }, + ] + }; + const mockAppHelper = {}; + const mockParser = (config: any) => parseData(config); + const dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser); + // test valid case + let mockDataHandler = { + type: 'JSFunction', + value: 'function(res) { return res.data + \'+\' + this.stateA; }', + }; + let result = dataHelper.handleData('fullConfigGet', mockDataHandler, { data: 'mockDataValue' }, null); + expect(result).toBe('mockDataValue+aValue'); + + // test invalid datahandler + mockDataHandler = { + type: 'not a JSFunction', + value: 'function(res) { return res.data + \'+\' + this.stateA; }', + }; + result = dataHelper.handleData('fullConfigGet', mockDataHandler, { data: 'mockDataValue' }, null); + expect(result).toStrictEqual({ data: 'mockDataValue' }); + + // test exception + const mockError = jest.fn(); + const orginalConsole = global.console; + global.console = { error: mockError }; + + // exception with id + mockDataHandler = { + type: 'JSFunction', + value: 'function(res) { return res.data + \'+\' + JSON.parse({a:1}); }', + }; + result = dataHelper.handleData('fullConfigGet', mockDataHandler, { data: 'mockDataValue' }, null); + expect(result).toBeUndefined(); + expect(mockError).toBeCalledWith('[fullConfigGet]单个请求数据处理函数运行出错', expect.anything()); + + // exception without id + mockDataHandler = { + type: 'JSFunction', + value: 'function(res) { return res.data + \'+\' + JSON.parse({a:1}); }', + }; + result = dataHelper.handleData(null, mockDataHandler, { data: 'mockDataValue' }, null); + expect(result).toBeUndefined(); + expect(mockError).toBeCalledWith('请求数据处理函数运行出错', expect.anything()); + + global.console = orginalConsole; + }); + + + it('updateConfig should work', () => { + const mockHost = { stateA: 'aValue'}; + const mockDataSourceConfig = { + list: [ + { + id: 'ds1', + }, { + id: 'ds2', + }, + { + id: 'fullConfigGet', + isInit: true, + options: { + params: {}, + method: 'GET', + isCors: true, + timeout: 5000, + headers: {}, + uri: 'mock/info.json', + }, + shouldFetch: { + type: 'JSFunction', + value: 'function() { return true; }', + }, + dataHandler: { + type: 'JSFunction', + value: 'function(res) { return res.data; }', + }, + errorHandler: { + type: 'JSFunction', + value: 'function(error) {}', + }, + willFetch: { + type: 'JSFunction', + value: 'function(options) { return options; }', + }, + }, + ] + }; + const mockAppHelper = {}; + const mockParser = (config: any) => parseData(config); + const dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser); + + expect(dataHelper.ajaxList.length).toBe(3); + + let updatedConfig = { + list: [ + { + id: 'ds2', + }, + { + id: 'fullConfigGet', + }, + ] + }; + dataHelper.updateConfig(updatedConfig); + + expect(dataHelper.ajaxList.length).toBe(2); + expect(dataHelper.dataSourceMap.ds1).toBeUndefined(); + + updatedConfig = { + list: [ + { + id: 'ds2', + }, + { + id: 'fullConfigGet', + }, + { + id: 'ds3', + }, + ] + }; + dataHelper.updateConfig(updatedConfig); + expect(dataHelper.ajaxList.length).toBe(3); + expect(dataHelper.dataSourceMap.ds3).toBeTruthy(); + }); + + it('getInitData should work', () => { + const mockHost = { stateA: 'aValue'}; + const mockDataSourceConfig = { + list: [ + { + id: 'ds1', + }, { + id: 'ds2', + }, + { + id: 'fullConfigGet', + isInit: true, + type: 'fetch', + options: { + params: {}, + method: 'GET', + isCors: true, + timeout: 5000, + headers: { + headerA: 1, + }, + uri: 'mock/info.json', + }, + shouldFetch: { + type: 'JSFunction', + value: 'function() { return true; }', + }, + dataHandler: { + type: 'JSFunction', + value: 'function(res) { return 123; }', + }, + errorHandler: { + type: 'JSFunction', + value: 'function(error) {}', + }, + willFetch: { + type: 'JSFunction', + value: 'function(options) { return options; }', + }, + }, + ] + }; + const mockAppHelper = {}; + const mockParser = (config: any) => parseData(config); + const dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser); + + expect(dataHelper.ajaxList.length).toBe(3); + expect(mockGet).toBeCalledTimes(0); + dataHelper.getInitData().then(res => { + expect(mockGet).toBeCalledTimes(1); + expect(mockGet).toBeCalledWith('mock/info.json', {}, { + headerA: 1, + }, expect.anything()); + mockGet.mockClear(); + }); + }); + + it('getDataSource should work', () => { + const mockHost = { stateA: 'aValue'}; + const mockDataSourceConfig = { + list: [ + { + id: 'ds1', + }, { + id: 'ds2', + }, + { + id: 'fullConfigGet', + isInit: true, + type: 'fetch', + options: { + params: {}, + method: 'GET', + isCors: true, + timeout: 5000, + headers: { + headerA: 1, + }, + uri: 'mock/info.json', + }, + shouldFetch: { + type: 'JSFunction', + value: 'function() { return true; }', + }, + dataHandler: { + type: 'JSFunction', + value: 'function(res) { return 123; }', + }, + errorHandler: { + type: 'JSFunction', + value: 'function(error) {}', + }, + willFetch: { + type: 'JSFunction', + value: 'function(options) { return options; }', + }, + }, + ] + }; + const mockAppHelper = {}; + const mockParser = (config: any) => parseData(config); + const dataHelper = new DataHelper(mockHost, mockDataSourceConfig, mockAppHelper, mockParser); + + expect(dataHelper.ajaxList.length).toBe(3); + expect(mockGet).toBeCalledTimes(0); + const callbackFn = jest.fn(); + dataHelper.getDataSource('fullConfigGet', { param1: 'value1' }, {}, callbackFn).then(res => { + expect(mockGet).toBeCalledTimes(1); + expect(mockGet).toBeCalledWith('mock/info.json', { param1: 'value1' }, { + headerA: 1, + }, expect.anything()); + mockGet.mockClear(); + expect(callbackFn).toBeCalledTimes(1); + }); + }); +});