mirror of
https://github.com/qlik-oss/nebula.js.git
synced 2025-12-19 09:48:18 -05:00
feat(engima-mock): mock qlik engine (#705)
This commit is contained in:
97
apis/enigma-mocker/README.md
Normal file
97
apis/enigma-mocker/README.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# @nebula.js/enigma-mocker
|
||||
|
||||
The purpose of the Enigma mocker is to be able to render visualizations without a connected Qlik engine. This could be useful for example when running rendering tests or to make runnable code examples.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install @nebula.js/enigma-mocker
|
||||
```
|
||||
|
||||
## Example usage
|
||||
|
||||
```js
|
||||
import { embed } from '@nebula.js/stardust';
|
||||
import EnigmaMocker from '@nebula.js/enigma-mocker';
|
||||
import mekko from '@nebula.js/sn-mekko-chart';
|
||||
|
||||
const genericObject = {
|
||||
getLayout() {
|
||||
return {
|
||||
qInfo: {
|
||||
qId: 'qqj4zx',
|
||||
qType: 'sn-grid-chart'
|
||||
},
|
||||
...
|
||||
}
|
||||
},
|
||||
getHyperCubeData(path, page) {
|
||||
return [ ... ];
|
||||
}
|
||||
};
|
||||
const app = await EnigmaMocker.fromGenericObjects([genericObject]);
|
||||
|
||||
const orion = embed(app, {
|
||||
types: [{
|
||||
name: 'mekko',
|
||||
load: () => Promise.resolve(mekko);
|
||||
}]
|
||||
});
|
||||
|
||||
orion.render({
|
||||
element,
|
||||
type: 'mekko',
|
||||
fields: ['Product', 'Region', 'Sales']
|
||||
});
|
||||
```
|
||||
|
||||
## Generic objects
|
||||
|
||||
The mocked enigma app can be created from one or more generic objects using `EnigmaMocker.fromGenericObjects(genericObjects)`. Each generic object represents one visualization and specifies how it behaves. For example, what layout to use the data to present.
|
||||
|
||||
The generic object is represented by a Javascript object with a number of properties. The name of the property correlates to the name in the Enigma model for `app.getObject(id)`. For example, the property `getLayout` in the generic object is used to define `app.getObject(id).getLayout()`. Any property can be added to the fixture (just make sure it exists and behaves as in the Enigma model!).
|
||||
|
||||
Structure of mocked enigma app:
|
||||
|
||||
```js
|
||||
{
|
||||
id: ...,
|
||||
getObject(id) {
|
||||
// Properties of generic object is added here
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
The value for each property is either fixed (string / boolean / number / object) or a function. Arguments are forwarded to the function to allow for greater flexibility. For example, this can be used to return different hypercube data when scrolling in the chart.
|
||||
|
||||
Example with fixed values:
|
||||
|
||||
```js
|
||||
{
|
||||
getLayout: {
|
||||
qInfo: {
|
||||
qId: 'qqj4zx',
|
||||
qType: 'sn-grid-chart'
|
||||
}
|
||||
},
|
||||
getHyperCubeData: [ ... ]
|
||||
}
|
||||
```
|
||||
|
||||
Example with functions:
|
||||
|
||||
```js
|
||||
{
|
||||
getLayout() {
|
||||
return {
|
||||
qInfo: {
|
||||
qId: 'qqj4zx',
|
||||
qType: 'sn-grid-chart'
|
||||
}
|
||||
}
|
||||
},
|
||||
getHyperCubeData(path, page) {
|
||||
return [ ... ];
|
||||
}
|
||||
}
|
||||
```
|
||||
1680
apis/enigma-mocker/examples/scrollable-fixture.js
Normal file
1680
apis/enigma-mocker/examples/scrollable-fixture.js
Normal file
File diff suppressed because it is too large
Load Diff
546
apis/enigma-mocker/examples/static-fixture.js
Normal file
546
apis/enigma-mocker/examples/static-fixture.js
Normal file
@@ -0,0 +1,546 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import EnigmaMocker from '../src';
|
||||
|
||||
const genericObject = {
|
||||
getLayout: {
|
||||
qInfo: {
|
||||
qId: '454d4be2-603e-487e-98ae-2ada9f851694',
|
||||
qType: 'sn-grid-chart',
|
||||
},
|
||||
qMeta: {
|
||||
privileges: ['read', 'update', 'delete', 'exportdata'],
|
||||
},
|
||||
qSelectionInfo: {},
|
||||
qHyperCube: {
|
||||
qSize: {
|
||||
qcx: 1,
|
||||
qcy: 8,
|
||||
},
|
||||
qDimensionInfo: [
|
||||
{
|
||||
qFallbackTitle: 'Region Name',
|
||||
qApprMaxGlyphCount: 9,
|
||||
qCardinal: 4,
|
||||
qSortIndicator: 'A',
|
||||
qGroupFallbackTitles: ['Region Name'],
|
||||
qGroupPos: 0,
|
||||
qStateCounts: {
|
||||
qLocked: 0,
|
||||
qSelected: 0,
|
||||
qOption: 4,
|
||||
qDeselected: 0,
|
||||
qAlternative: 0,
|
||||
qExcluded: 0,
|
||||
qSelectedExcluded: 0,
|
||||
qLockedExcluded: 0,
|
||||
},
|
||||
qTags: ['$ascii', '$text'],
|
||||
qDimensionType: 'D',
|
||||
qGrouping: 'N',
|
||||
qNumFormat: {
|
||||
qType: 'U',
|
||||
qnDec: 0,
|
||||
qUseThou: 0,
|
||||
},
|
||||
qIsAutoFormat: true,
|
||||
qGroupFieldDefs: ['Region Name'],
|
||||
qMin: 'NaN',
|
||||
qMax: 'NaN',
|
||||
qAttrExprInfo: [],
|
||||
qAttrDimInfo: [],
|
||||
qCardinalities: {
|
||||
qCardinal: 4,
|
||||
qHypercubeCardinal: 4,
|
||||
qAllValuesCardinal: 4,
|
||||
},
|
||||
autoSort: true,
|
||||
cId: 'TuJeyHR',
|
||||
othersLabel: 'Others',
|
||||
},
|
||||
{
|
||||
qFallbackTitle: 'Year',
|
||||
qApprMaxGlyphCount: 4,
|
||||
qCardinal: 2,
|
||||
qSortIndicator: 'A',
|
||||
qGroupFallbackTitles: ['Year'],
|
||||
qGroupPos: 0,
|
||||
qStateCounts: {
|
||||
qLocked: 0,
|
||||
qSelected: 0,
|
||||
qOption: 2,
|
||||
qDeselected: 0,
|
||||
qAlternative: 0,
|
||||
qExcluded: 0,
|
||||
qSelectedExcluded: 0,
|
||||
qLockedExcluded: 0,
|
||||
},
|
||||
qTags: ['$numeric', '$integer'],
|
||||
qDimensionType: 'N',
|
||||
qGrouping: 'N',
|
||||
qNumFormat: {
|
||||
qType: 'I',
|
||||
qnDec: 0,
|
||||
qUseThou: 0,
|
||||
qFmt: '###0',
|
||||
qDec: '.',
|
||||
qThou: ',',
|
||||
},
|
||||
qIsAutoFormat: true,
|
||||
qGroupFieldDefs: ['Year'],
|
||||
qMin: 2006,
|
||||
qMax: 2007,
|
||||
qContinuousAxes: true,
|
||||
qAttrExprInfo: [
|
||||
{
|
||||
qMin: 3264199.3800000213,
|
||||
qMax: 25219232.68999985,
|
||||
qFallbackTitle: 'Sum([Sales Amount])',
|
||||
qMinText: '3264199.38',
|
||||
qMaxText: '25219232.69',
|
||||
qNumFormat: {
|
||||
qType: 'U',
|
||||
qnDec: 0,
|
||||
qUseThou: 0,
|
||||
},
|
||||
qIsAutoFormat: true,
|
||||
id: 'colorByAlternative',
|
||||
label: 'Sum([Sales Amount])',
|
||||
},
|
||||
],
|
||||
qAttrDimInfo: [],
|
||||
qCardinalities: {
|
||||
qCardinal: 2,
|
||||
qHypercubeCardinal: 2,
|
||||
qAllValuesCardinal: 2,
|
||||
},
|
||||
autoSort: true,
|
||||
cId: 'rLUq',
|
||||
othersLabel: 'Others',
|
||||
},
|
||||
],
|
||||
qMeasureInfo: [
|
||||
{
|
||||
qFallbackTitle: 'Sum([Sales Amount])',
|
||||
qApprMaxGlyphCount: 11,
|
||||
qCardinal: 0,
|
||||
qSortIndicator: 'D',
|
||||
qNumFormat: {
|
||||
qType: 'U',
|
||||
qnDec: 0,
|
||||
qUseThou: 0,
|
||||
},
|
||||
qMin: 3264199.3800000213,
|
||||
qMax: 25219232.68999985,
|
||||
qIsAutoFormat: true,
|
||||
qAttrExprInfo: [],
|
||||
qAttrDimInfo: [],
|
||||
qTrendLines: [],
|
||||
autoSort: true,
|
||||
cId: 'ZPyHqLs',
|
||||
numFormatFromTemplate: true,
|
||||
},
|
||||
],
|
||||
qEffectiveInterColumnSortOrder: [0, 1],
|
||||
qGrandTotalRow: [],
|
||||
qTreeDataPages: [],
|
||||
qDataPages: [],
|
||||
qPivotDataPages: [],
|
||||
qStackedDataPages: [],
|
||||
qMode: 'T',
|
||||
qNoOfLeftDims: 2,
|
||||
qTreeNodesOnDim: [4, 8],
|
||||
qColumnOrder: [],
|
||||
},
|
||||
showTitles: true,
|
||||
title: '',
|
||||
subtitle: '',
|
||||
footnote: '',
|
||||
disableNavMenu: false,
|
||||
showDetails: false,
|
||||
dataPoint: {
|
||||
rangeBubbleSizes: [0.15, 1],
|
||||
},
|
||||
color: {
|
||||
auto: false,
|
||||
mode: 'byMeasure',
|
||||
formatting: {
|
||||
numFormatFromTemplate: true,
|
||||
},
|
||||
useBaseColors: 'off',
|
||||
paletteColor: {
|
||||
index: 6,
|
||||
},
|
||||
useDimColVal: true,
|
||||
useMeasureGradient: true,
|
||||
expressionIsColor: true,
|
||||
expressionLabel: '',
|
||||
measureScheme: 'sc',
|
||||
reverseScheme: true,
|
||||
dimensionScheme: '12',
|
||||
autoMinMax: true,
|
||||
measureMin: 0,
|
||||
measureMax: 10,
|
||||
altLabel: 'Sum([Sales Amount])',
|
||||
byMeasureDef: {
|
||||
label: 'Sum([Sales Amount])',
|
||||
key: 'Sum([Sales Amount])',
|
||||
type: 'expression',
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
dock: 'top',
|
||||
showTitle: true,
|
||||
},
|
||||
xAxis: {
|
||||
show: 'all',
|
||||
label: 'auto',
|
||||
dock: 'near',
|
||||
axisDisplayMode: 'auto',
|
||||
maxVisibleItems: 10,
|
||||
gridLines: false,
|
||||
},
|
||||
yAxis: {
|
||||
show: 'all',
|
||||
dock: 'near',
|
||||
axisDisplayMode: 'auto',
|
||||
maxVisibleItems: 10,
|
||||
gridLines: false,
|
||||
},
|
||||
visualization: 'sn-grid-chart',
|
||||
version: '0.1.0',
|
||||
extensionMeta: {
|
||||
translationKey: '',
|
||||
icon: 'GridChart',
|
||||
iconChar: 'puzzle',
|
||||
isLibraryItem: true,
|
||||
visible: true,
|
||||
name: 'Grid chart',
|
||||
description: 'A grid from two dimensions with markers of varying size based on a measure.',
|
||||
template: 'sn-grid-chart',
|
||||
iconPath:
|
||||
'M14.5,9 L13,9 L13,3.3 C13,3.1 12.9,3 12.7,3 L8,3 L8,1.5 C8,0.7 7.3,0 6.5,0 C5.7,0 5,0.7 5,1.5 L5,3 L0.3,3 C0.1,3 0,3.1 0,3.3 L0,9 L1.5,9 C2.3,9 3,9.7 3,10.5 C3,11.3 2.3,12 1.5,12 L0,12 L0,15.7 C0,15.9 0.1,16 0.3,16 L5,16 L5,14.5 C5,13.7 5.7,13 6.5,13 C7.3,13 8,13.7 8,14.5 L8,16 L12.7,16 C12.9,16 13,15.9 13,15.7 L13,12 L14.5,12 C15.3,12 16,11.3 16,10.5 C16,9.7 15.3,9 14.5,9 Z',
|
||||
isThirdParty: true,
|
||||
author: 'QlikTech International AB',
|
||||
preview: 'assets/grid-preview.png',
|
||||
supernova: true,
|
||||
type: 'visualization',
|
||||
version: '0.1.0',
|
||||
previewIconURL: 'https://rd-pnr-mast5881.rdlund.qliktech.com/extensions/sn-grid-chart/assets/grid-preview.png',
|
||||
},
|
||||
},
|
||||
getHyperCubeTreeData: [
|
||||
{
|
||||
qValue: 0,
|
||||
qElemNo: -1,
|
||||
qGroupPos: 0,
|
||||
qGroupSize: 4,
|
||||
qRow: 0,
|
||||
qType: 'R',
|
||||
qValues: [],
|
||||
qNodes: [
|
||||
{
|
||||
qText: 'Central',
|
||||
qValue: 'NaN',
|
||||
qElemNo: 3,
|
||||
qGroupPos: 0,
|
||||
qGroupSize: 2,
|
||||
qRow: 0,
|
||||
qType: 'N',
|
||||
qValues: [],
|
||||
qNodes: [
|
||||
{
|
||||
qText: '2006',
|
||||
qValue: 2006,
|
||||
qElemNo: 1,
|
||||
qGroupPos: 0,
|
||||
qGroupSize: 0,
|
||||
qRow: 0,
|
||||
qType: 'N',
|
||||
qValues: [
|
||||
{
|
||||
qText: '25219232.69',
|
||||
qValue: 25219232.68999985,
|
||||
},
|
||||
],
|
||||
qNodes: [],
|
||||
qAttrExps: {
|
||||
qValues: [
|
||||
{
|
||||
qText: '25219232.69',
|
||||
qNum: 25219232.68999985,
|
||||
},
|
||||
],
|
||||
},
|
||||
qMaxPos: [25219232.68999985],
|
||||
qMinNeg: [0],
|
||||
qState: 'O',
|
||||
qTreePath: [0],
|
||||
},
|
||||
{
|
||||
qText: '2007',
|
||||
qValue: 2007,
|
||||
qElemNo: 0,
|
||||
qGroupPos: 1,
|
||||
qGroupSize: 0,
|
||||
qRow: 1,
|
||||
qType: 'N',
|
||||
qValues: [
|
||||
{
|
||||
qText: '21592023.92',
|
||||
qValue: 21592023.91999996,
|
||||
},
|
||||
],
|
||||
qNodes: [],
|
||||
qAttrExps: {
|
||||
qValues: [
|
||||
{
|
||||
qText: '21592023.92',
|
||||
qNum: 21592023.91999996,
|
||||
},
|
||||
],
|
||||
},
|
||||
qMaxPos: [21592023.91999996],
|
||||
qMinNeg: [0],
|
||||
qState: 'O',
|
||||
qTreePath: [0],
|
||||
},
|
||||
],
|
||||
qMaxPos: [46811256.609999806],
|
||||
qMinNeg: [0],
|
||||
qCanExpand: true,
|
||||
qState: 'O',
|
||||
qTreePath: [],
|
||||
},
|
||||
{
|
||||
qText: 'Northeast',
|
||||
qValue: 'NaN',
|
||||
qElemNo: 2,
|
||||
qGroupPos: 1,
|
||||
qGroupSize: 2,
|
||||
qRow: 2,
|
||||
qType: 'N',
|
||||
qValues: [],
|
||||
qNodes: [
|
||||
{
|
||||
qText: '2006',
|
||||
qValue: 2006,
|
||||
qElemNo: 1,
|
||||
qGroupPos: 0,
|
||||
qGroupSize: 0,
|
||||
qRow: 2,
|
||||
qType: 'N',
|
||||
qValues: [
|
||||
{
|
||||
qText: '3264199.38',
|
||||
qValue: 3264199.3800000213,
|
||||
},
|
||||
],
|
||||
qNodes: [],
|
||||
qAttrExps: {
|
||||
qValues: [
|
||||
{
|
||||
qText: '3264199.38',
|
||||
qNum: 3264199.3800000213,
|
||||
},
|
||||
],
|
||||
},
|
||||
qMaxPos: [3264199.3800000213],
|
||||
qMinNeg: [0],
|
||||
qState: 'O',
|
||||
qTreePath: [1],
|
||||
},
|
||||
{
|
||||
qText: '2007',
|
||||
qValue: 2007,
|
||||
qElemNo: 0,
|
||||
qGroupPos: 1,
|
||||
qGroupSize: 0,
|
||||
qRow: 3,
|
||||
qType: 'N',
|
||||
qValues: [
|
||||
{
|
||||
qText: '3961020.43',
|
||||
qValue: 3961020.429999986,
|
||||
},
|
||||
],
|
||||
qNodes: [],
|
||||
qAttrExps: {
|
||||
qValues: [
|
||||
{
|
||||
qText: '3961020.43',
|
||||
qNum: 3961020.429999986,
|
||||
},
|
||||
],
|
||||
},
|
||||
qMaxPos: [3961020.429999986],
|
||||
qMinNeg: [0],
|
||||
qState: 'O',
|
||||
qTreePath: [1],
|
||||
},
|
||||
],
|
||||
qMaxPos: [7225219.810000008],
|
||||
qMinNeg: [0],
|
||||
qCanExpand: true,
|
||||
qState: 'O',
|
||||
qTreePath: [],
|
||||
},
|
||||
{
|
||||
qText: 'Southern',
|
||||
qValue: 'NaN',
|
||||
qElemNo: 1,
|
||||
qGroupPos: 2,
|
||||
qGroupSize: 2,
|
||||
qRow: 4,
|
||||
qType: 'N',
|
||||
qValues: [],
|
||||
qNodes: [
|
||||
{
|
||||
qText: '2006',
|
||||
qValue: 2006,
|
||||
qElemNo: 1,
|
||||
qGroupPos: 0,
|
||||
qGroupSize: 0,
|
||||
qRow: 4,
|
||||
qType: 'N',
|
||||
qValues: [
|
||||
{
|
||||
qText: '11512061.4',
|
||||
qValue: 11512061.400000045,
|
||||
},
|
||||
],
|
||||
qNodes: [],
|
||||
qAttrExps: {
|
||||
qValues: [
|
||||
{
|
||||
qText: '11512061.4',
|
||||
qNum: 11512061.400000045,
|
||||
},
|
||||
],
|
||||
},
|
||||
qMaxPos: [11512061.400000045],
|
||||
qMinNeg: [0],
|
||||
qState: 'O',
|
||||
qTreePath: [2],
|
||||
},
|
||||
{
|
||||
qText: '2007',
|
||||
qValue: 2007,
|
||||
qElemNo: 0,
|
||||
qGroupPos: 1,
|
||||
qGroupSize: 0,
|
||||
qRow: 5,
|
||||
qType: 'N',
|
||||
qValues: [
|
||||
{
|
||||
qText: '9234765.18',
|
||||
qValue: 9234765.179999989,
|
||||
},
|
||||
],
|
||||
qNodes: [],
|
||||
qAttrExps: {
|
||||
qValues: [
|
||||
{
|
||||
qText: '9234765.18',
|
||||
qNum: 9234765.179999989,
|
||||
},
|
||||
],
|
||||
},
|
||||
qMaxPos: [9234765.179999989],
|
||||
qMinNeg: [0],
|
||||
qState: 'O',
|
||||
qTreePath: [2],
|
||||
},
|
||||
],
|
||||
qMaxPos: [20746826.580000035],
|
||||
qMinNeg: [0],
|
||||
qCanExpand: true,
|
||||
qState: 'O',
|
||||
qTreePath: [],
|
||||
},
|
||||
{
|
||||
qText: 'Western',
|
||||
qValue: 'NaN',
|
||||
qElemNo: 0,
|
||||
qGroupPos: 3,
|
||||
qGroupSize: 2,
|
||||
qRow: 6,
|
||||
qType: 'N',
|
||||
qValues: [],
|
||||
qNodes: [
|
||||
{
|
||||
qText: '2006',
|
||||
qValue: 2006,
|
||||
qElemNo: 1,
|
||||
qGroupPos: 0,
|
||||
qGroupSize: 0,
|
||||
qRow: 6,
|
||||
qType: 'N',
|
||||
qValues: [
|
||||
{
|
||||
qText: '12552547.3',
|
||||
qValue: 12552547.30000001,
|
||||
},
|
||||
],
|
||||
qNodes: [],
|
||||
qAttrExps: {
|
||||
qValues: [
|
||||
{
|
||||
qText: '12552547.3',
|
||||
qNum: 12552547.30000001,
|
||||
},
|
||||
],
|
||||
},
|
||||
qMaxPos: [12552547.30000001],
|
||||
qMinNeg: [0],
|
||||
qState: 'O',
|
||||
qTreePath: [3],
|
||||
},
|
||||
{
|
||||
qText: '2007',
|
||||
qValue: 2007,
|
||||
qElemNo: 0,
|
||||
qGroupPos: 1,
|
||||
qGroupSize: 0,
|
||||
qRow: 7,
|
||||
qType: 'N',
|
||||
qValues: [
|
||||
{
|
||||
qText: '11336751.17',
|
||||
qValue: 11336751.170000054,
|
||||
},
|
||||
],
|
||||
qNodes: [],
|
||||
qAttrExps: {
|
||||
qValues: [
|
||||
{
|
||||
qText: '11336751.17',
|
||||
qNum: 11336751.170000054,
|
||||
},
|
||||
],
|
||||
},
|
||||
qMaxPos: [11336751.170000054],
|
||||
qMinNeg: [0],
|
||||
qState: 'O',
|
||||
qTreePath: [3],
|
||||
},
|
||||
],
|
||||
qMaxPos: [23889298.470000066],
|
||||
qMinNeg: [0],
|
||||
qCanExpand: true,
|
||||
qState: 'O',
|
||||
qTreePath: [],
|
||||
},
|
||||
],
|
||||
qMaxPos: [],
|
||||
qMinNeg: [],
|
||||
qState: 'L',
|
||||
qTreePath: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const app = await EnigmaMocker.fromGenericObjects([genericObject]);
|
||||
// const nebbie = embed(app, { ... config });
|
||||
})();
|
||||
1
apis/enigma-mocker/index.js
Normal file
1
apis/enigma-mocker/index.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('./dist/enigma-mocker');
|
||||
27
apis/enigma-mocker/package.json
Normal file
27
apis/enigma-mocker/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@nebula.js/enigma-mocker",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"license": "MIT",
|
||||
"author": "QlikTech International AB",
|
||||
"keywords": [],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/qlik-oss/nebula.js.git",
|
||||
"directory": "apis/enigma-mocker"
|
||||
},
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production rollup --config ../../rollup.config.js",
|
||||
"build:dev": "rollup --config ../../rollup.config.js",
|
||||
"build:watch": "rollup --config ../../rollup.config.js -w",
|
||||
"prepublishOnly": "rm -rf dist && yarn run build"
|
||||
}
|
||||
}
|
||||
313
apis/enigma-mocker/src/__tests__/from-generic-objects.spec.js
Normal file
313
apis/enigma-mocker/src/__tests__/from-generic-objects.spec.js
Normal file
@@ -0,0 +1,313 @@
|
||||
import createEnigmaMocker from '../from-generic-objects';
|
||||
|
||||
const genericObject = {
|
||||
getLayout: {
|
||||
qInfo: { qId: 'b488pz' },
|
||||
qHyperCube: {},
|
||||
},
|
||||
};
|
||||
const genericObjects = [genericObject];
|
||||
|
||||
describe('enigma-mocker', () => {
|
||||
it('supports engima functionlity', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
|
||||
expect(app).to.include.keys('id', 'session', 'createSessionObject', 'getObject', 'getAppLayout');
|
||||
});
|
||||
|
||||
it('id should be a string', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
expect(app.id).to.be.a.string;
|
||||
});
|
||||
|
||||
it('throws if no generic object is specified', async () => {
|
||||
expect(() => createEnigmaMocker()).to.throw();
|
||||
});
|
||||
|
||||
it('throws if generic objects argument is not array', async () => {
|
||||
expect(() => createEnigmaMocker({})).to.throw();
|
||||
});
|
||||
|
||||
it('throws if generic objects argument is empty array', async () => {
|
||||
expect(() => createEnigmaMocker([])).to.throw();
|
||||
});
|
||||
|
||||
describe('getObject', () => {
|
||||
it('returns undefined when id does not exist', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
const object = await app.getObject('p55ez');
|
||||
|
||||
expect(object).to.be.undefined;
|
||||
});
|
||||
|
||||
it('supports getObject functionality', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
const object = await app.getObject('b488pz');
|
||||
expect(object).to.include.keys('id', 'getLayout', 'on', 'once');
|
||||
});
|
||||
|
||||
it('id should be a string', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
const object = await app.getObject('b488pz');
|
||||
expect(object.id).to.be.a.string;
|
||||
});
|
||||
|
||||
it('supports multiple generic objects', async () => {
|
||||
const genericObjectA = {
|
||||
getLayout: { qInfo: { qId: '112233' } },
|
||||
};
|
||||
const genericObjectB = {
|
||||
getLayout: { qInfo: { qId: 'aabbcc' } },
|
||||
};
|
||||
const app = await createEnigmaMocker([genericObjectA, genericObjectB]);
|
||||
|
||||
expect(await app.getObject('112233')).to.not.be.undefined;
|
||||
expect(await app.getObject('aabbcc')).to.not.be.undefined;
|
||||
});
|
||||
|
||||
describe('getLayout', () => {
|
||||
it('shoud support fixed value', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
const object = await app.getObject('b488pz');
|
||||
const layout = await object.getLayout();
|
||||
expect(layout).to.equal(genericObject.getLayout);
|
||||
});
|
||||
|
||||
it('should support dynamic value', async () => {
|
||||
const getLayout = sinon.stub();
|
||||
getLayout.returns({ qInfo: { qId: '2pz14' } });
|
||||
|
||||
const app = await createEnigmaMocker([{ getLayout }]);
|
||||
const object = await app.getObject('2pz14');
|
||||
const layout = await object.getLayout();
|
||||
|
||||
expect(layout.qInfo.qId).to.equal('2pz14');
|
||||
expect(getLayout).to.have.been.called;
|
||||
});
|
||||
|
||||
it('is asynchronous', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
const object = await app.getObject('b488pz');
|
||||
const getLayoutPromise = object.getLayout();
|
||||
|
||||
expect(getLayoutPromise).to.be.a('promise');
|
||||
});
|
||||
|
||||
it('throws if no getLayout is specified', async () => {
|
||||
expect(() => createEnigmaMocker({})).to.throw();
|
||||
});
|
||||
|
||||
it('throws if no qId is specified', async () => {
|
||||
expect(() => createEnigmaMocker({ getLayout: { qInfo: {} } })).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHyperCubeData', () => {
|
||||
it('should support fixed value', async () => {
|
||||
const app = await createEnigmaMocker([{ ...genericObject, getHyperCubeData: [{ foo: 'bar' }] }]);
|
||||
const object = await app.getObject('b488pz');
|
||||
const hypercubeData = await object.getHyperCubeData();
|
||||
|
||||
expect(hypercubeData).to.be.an('array');
|
||||
expect(hypercubeData).to.be.of.length(1);
|
||||
expect(hypercubeData[0].foo).to.equal('bar');
|
||||
});
|
||||
|
||||
it('should support dynamic value', async () => {
|
||||
const getHyperCubeData = sinon.stub();
|
||||
getHyperCubeData.returns([{ foo: 'baz' }]);
|
||||
|
||||
const app = await createEnigmaMocker([{ ...genericObject, getHyperCubeData }]);
|
||||
const object = await app.getObject('b488pz');
|
||||
const hypercubeData = await object.getHyperCubeData('/qHyperCubeDef', {
|
||||
width: 10,
|
||||
height: 8,
|
||||
top: 0,
|
||||
left: 4,
|
||||
});
|
||||
|
||||
expect(hypercubeData).to.be.an('array');
|
||||
expect(hypercubeData).to.be.of.length(1);
|
||||
expect(hypercubeData[0]).to.eql({ foo: 'baz' });
|
||||
expect(getHyperCubeData).to.have.been.calledWith('/qHyperCubeDef', {
|
||||
width: 10,
|
||||
height: 8,
|
||||
top: 0,
|
||||
left: 4,
|
||||
});
|
||||
});
|
||||
|
||||
it('is asynchronous', async () => {
|
||||
const app = await createEnigmaMocker([{ ...genericObject, getHyperCubeData: [] }]);
|
||||
const object = await app.getObject('b488pz');
|
||||
const hypercubeDataPromise = object.getHyperCubeData();
|
||||
|
||||
expect(hypercubeDataPromise).to.be.a('promise');
|
||||
});
|
||||
});
|
||||
|
||||
describe('on', () => {
|
||||
it('is synchronous', async () => {
|
||||
const app = await createEnigmaMocker([{ ...genericObject, on: () => true }]);
|
||||
const object = await app.getObject('b488pz');
|
||||
const result = object.on();
|
||||
expect(result).to.not.be.a('promise');
|
||||
});
|
||||
|
||||
it('is extensible', async () => {
|
||||
const onStub = sinon.stub();
|
||||
onStub.returns(false);
|
||||
const app = await createEnigmaMocker([{ ...genericObject, on: onStub }]);
|
||||
const object = await app.getObject('b488pz');
|
||||
const result = await object.on();
|
||||
expect(result).to.equal(false);
|
||||
expect(onStub).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('once', () => {
|
||||
it('is synchronous', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
const object = await app.getObject('b488pz');
|
||||
const result = object.once();
|
||||
expect(result).to.not.be.a('promise');
|
||||
});
|
||||
|
||||
it('is extensible', async () => {
|
||||
const onceStub = sinon.stub();
|
||||
onceStub.returns(false);
|
||||
const app = await createEnigmaMocker([{ ...genericObject, once: onceStub }]);
|
||||
const object = await app.getObject('b488pz');
|
||||
const result = await object.once();
|
||||
expect(result).to.equal(false);
|
||||
expect(onceStub).to.have.been.called;
|
||||
});
|
||||
});
|
||||
|
||||
describe('addListener', () => {
|
||||
it('is synchronous', async () => {
|
||||
const app = await createEnigmaMocker([{ ...genericObject, addListener: () => true }]);
|
||||
const object = await app.getObject('b488pz');
|
||||
const result = object.addListener();
|
||||
expect(result).to.not.be.a('promise');
|
||||
});
|
||||
});
|
||||
|
||||
describe('emit', () => {
|
||||
it('is synchronous', async () => {
|
||||
const app = await createEnigmaMocker([{ ...genericObject, emit: () => true }]);
|
||||
const object = await app.getObject('b488pz');
|
||||
const result = object.emit();
|
||||
expect(result).to.not.be.a('promise');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeAllListeners', () => {
|
||||
it('is synchronous', async () => {
|
||||
const app = await createEnigmaMocker([{ ...genericObject, removeAllListeners: () => true }]);
|
||||
const object = await app.getObject('b488pz');
|
||||
const result = object.removeAllListeners();
|
||||
expect(result).to.not.be.a('promise');
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeListener', () => {
|
||||
it('is synchronous', async () => {
|
||||
const app = await createEnigmaMocker([{ ...genericObject, removeListener: () => true }]);
|
||||
const object = await app.getObject('b488pz');
|
||||
const result = object.removeListener();
|
||||
expect(result).to.not.be.a('promise');
|
||||
});
|
||||
});
|
||||
|
||||
describe('setMaxListerners', () => {
|
||||
it('is synchronous', async () => {
|
||||
const app = await createEnigmaMocker([{ ...genericObject, setMaxListerners: () => true }]);
|
||||
const object = await app.getObject('b488pz');
|
||||
const result = object.setMaxListerners();
|
||||
expect(result).to.not.be.a('promise');
|
||||
});
|
||||
});
|
||||
|
||||
describe('custom property (extensibility)', () => {
|
||||
it('should return value', async () => {
|
||||
const getCustomThing = sinon.stub();
|
||||
getCustomThing.returns({ foo: 'bar' });
|
||||
|
||||
const app = await createEnigmaMocker([{ ...genericObject, getCustomThing }]);
|
||||
const object = await app.getObject('b488pz');
|
||||
const customThing = await object.getCustomThing();
|
||||
|
||||
expect(customThing).to.eql({ foo: 'bar' });
|
||||
expect(getCustomThing).to.have.been.called;
|
||||
});
|
||||
|
||||
it('is asynchronous', async () => {
|
||||
const getCustomThing = sinon.stub();
|
||||
const app = await createEnigmaMocker([{ ...genericObject, getCustomThing }]);
|
||||
const object = await app.getObject('b488pz');
|
||||
const customThingPromise = object.getCustomThing();
|
||||
|
||||
expect(customThingPromise).to.be.a('promise');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('session', () => {
|
||||
describe('getObjectApi', () => {
|
||||
it('id should be a string', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
const objectApi = await app.session.getObjectApi();
|
||||
expect(objectApi.id).to.be.a.string;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSessionObject', () => {
|
||||
it('supports create session functionality', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
const sessionObject = await app.createSessionObject();
|
||||
expect(sessionObject).to.include.keys('on', 'once', 'getLayout', 'id');
|
||||
});
|
||||
|
||||
it('is asynchronous', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
const sessionObjectPromise = app.createSessionObject();
|
||||
expect(sessionObjectPromise).to.be.a('promise');
|
||||
});
|
||||
|
||||
it('uses "qInfo.qId" as id', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
const sessionObject = await app.createSessionObject({ qInfo: { qId: 'foo' } });
|
||||
expect(sessionObject.id).to.equal('foo');
|
||||
});
|
||||
|
||||
describe('extensibility', () => {
|
||||
it('supports custom props', async () => {
|
||||
const getCustomThing = sinon.stub();
|
||||
getCustomThing.returns({ foo: 'bar' });
|
||||
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
const sessionObject = await app.createSessionObject({ getCustomThing });
|
||||
const customThing = sessionObject.getCustomThing();
|
||||
|
||||
expect(customThing).to.eql({ foo: 'bar' });
|
||||
expect(getCustomThing).to.have.been.called;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAppLayout', () => {
|
||||
it('is asynchronous', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
const appLayoutPromise = app.getAppLayout();
|
||||
expect(appLayoutPromise).to.be.a('promise');
|
||||
});
|
||||
|
||||
it('id should be a string', async () => {
|
||||
const app = await createEnigmaMocker(genericObjects);
|
||||
const appLayout = await app.getAppLayout();
|
||||
expect(appLayout.id).to.be.a.string;
|
||||
});
|
||||
});
|
||||
});
|
||||
90
apis/enigma-mocker/src/__tests__/prop.spec.js
Normal file
90
apis/enigma-mocker/src/__tests__/prop.spec.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import { getPropValue, getPropFn } from '../prop';
|
||||
|
||||
describe('getPropValue', () => {
|
||||
describe('value is fixed', () => {
|
||||
it('supports value of type string', () => {
|
||||
const value = getPropValue('foo');
|
||||
expect(value).to.equal('foo');
|
||||
});
|
||||
|
||||
it('supports value of type object', () => {
|
||||
const value = getPropValue({ foo: 'bar' });
|
||||
expect(value).to.eql({ foo: 'bar' });
|
||||
});
|
||||
|
||||
it('supports value of type null', () => {
|
||||
const value = getPropValue(null);
|
||||
expect(value).to.equal(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('value is a function', () => {
|
||||
it('invokes function', () => {
|
||||
const prop = sinon.stub();
|
||||
prop.returns(true);
|
||||
const value = getPropValue(prop);
|
||||
expect(prop).to.have.been.calledOnce;
|
||||
expect(value).is.true;
|
||||
});
|
||||
|
||||
it('forwards arguments', () => {
|
||||
const prop = sinon.stub();
|
||||
prop.returns(true);
|
||||
const value = getPropValue(prop, { args: [true, 'search'] });
|
||||
expect(prop).to.have.been.calledWith(true, 'search');
|
||||
expect(value).is.true;
|
||||
});
|
||||
|
||||
it('returns dynamic value', () => {
|
||||
const prop = sinon.stub();
|
||||
prop.returns({ result: [] });
|
||||
const value = getPropValue(prop);
|
||||
expect(value).to.eql({ result: [] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('no value defined', () => {
|
||||
it('returns default value', () => {
|
||||
const value = getPropValue(undefined, { defaultValue: true });
|
||||
expect(value).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPropFn', () => {
|
||||
it('returns a function', () => {
|
||||
const fn = getPropFn('result');
|
||||
expect(fn).to.be.a('function');
|
||||
});
|
||||
|
||||
it('forwards arguments', async () => {
|
||||
const prop = sinon.stub();
|
||||
prop.returns(10);
|
||||
const fn = getPropFn(prop);
|
||||
const value = await fn(false, 'status');
|
||||
expect(prop).to.have.been.calledWith(false, 'status');
|
||||
expect(value).to.equal(10);
|
||||
});
|
||||
|
||||
describe('async is enabled', () => {
|
||||
it('returns promise', async () => {
|
||||
const prop = sinon.stub();
|
||||
prop.returns(500);
|
||||
const fn = getPropFn(prop, { async: true });
|
||||
const valuePromise = fn();
|
||||
expect(valuePromise).to.be.a('promise');
|
||||
const value = await valuePromise;
|
||||
expect(value).to.equal(500);
|
||||
});
|
||||
});
|
||||
|
||||
describe('async is disabled', () => {
|
||||
it('returns the value', () => {
|
||||
const prop = sinon.stub();
|
||||
prop.returns(500);
|
||||
const fn = getPropFn(prop, { async: false });
|
||||
const value = fn();
|
||||
expect(value).to.equal(500);
|
||||
});
|
||||
});
|
||||
});
|
||||
51
apis/enigma-mocker/src/from-generic-objects.js
Normal file
51
apis/enigma-mocker/src/from-generic-objects.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import SessionMock from './mocks/session-mock';
|
||||
import CreateSessionObjectMock from './mocks/create-session-object-mock';
|
||||
import GetObjectMock from './mocks/get-object-mock';
|
||||
import GetAppLayoutMock from './mocks/get-app-layout-mock';
|
||||
|
||||
/**
|
||||
* Mocks Engima app functionality. It accepts one / many generic objects as input argument and returns the mocked Enigma app. Each generic object represents one visulization and specifies how it behaves. For example, what layout to use the data to present.
|
||||
*
|
||||
* The generic object is represented with a Javascript object with a number of properties. The name of the property correlates to the name in the Enigma model for `app.getObject(id)`. For example, the property `getLayout` in the generic object is used to define `app.getObject(id).getLayout()`. Any property can be added to the fixture (just make sure it exists and behaves as in the Enigma model!).
|
||||
*
|
||||
* The value for each property is either fixed (string / boolean / number / object) or a function. Arguments are forwarded to the function to allow for greater flexibility. For example, this can be used to return different hypercube data when scrolling in the chart.
|
||||
*
|
||||
* @param {Array<object>} genericObjects Generic objects controling behaviour of visualizations.
|
||||
* @returns {Promise<enigma.Doc>}
|
||||
* @example
|
||||
* const genericObject = {
|
||||
* getLayout() {
|
||||
* return {
|
||||
* qInfo: {
|
||||
* qId: 'qqj4zx',
|
||||
* qType: 'sn-grid-chart'
|
||||
* },
|
||||
* ...
|
||||
* }
|
||||
* },
|
||||
* getHyperCubeData(path, page) {
|
||||
* return [ ... ];
|
||||
* }
|
||||
* };
|
||||
* const app = await EnigmaMocker.fromGenericObjects([genericObject]);
|
||||
*/
|
||||
export default (genericObjects) => {
|
||||
if (!Array.isArray(genericObjects) || genericObjects.length === 0) {
|
||||
throw new Error('No "genericObjects" specified');
|
||||
}
|
||||
|
||||
const session = new SessionMock();
|
||||
const createSessionObject = new CreateSessionObjectMock();
|
||||
const getObject = new GetObjectMock(genericObjects);
|
||||
const getAppLayout = new GetAppLayoutMock();
|
||||
|
||||
const app = {
|
||||
id: `app - ${+Date.now()}`,
|
||||
session,
|
||||
createSessionObject,
|
||||
getObject,
|
||||
getAppLayout,
|
||||
};
|
||||
|
||||
return Promise.resolve(app);
|
||||
};
|
||||
5
apis/enigma-mocker/src/index.js
Normal file
5
apis/enigma-mocker/src/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import fromGenericObjects from './from-generic-objects';
|
||||
|
||||
export default {
|
||||
fromGenericObjects,
|
||||
};
|
||||
12
apis/enigma-mocker/src/mocks/create-session-object-mock.js
Normal file
12
apis/enigma-mocker/src/mocks/create-session-object-mock.js
Normal file
@@ -0,0 +1,12 @@
|
||||
function CreateSessionObjectMock() {
|
||||
return (props) =>
|
||||
Promise.resolve({
|
||||
on: () => {},
|
||||
once: () => {},
|
||||
getLayout: () => Promise.resolve({}),
|
||||
id: props && props.qInfo && props.qInfo.qId ? props.qInfo.qId : `sel - ${+Date.now()}`,
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
export default CreateSessionObjectMock;
|
||||
5
apis/enigma-mocker/src/mocks/get-app-layout-mock.js
Normal file
5
apis/enigma-mocker/src/mocks/get-app-layout-mock.js
Normal file
@@ -0,0 +1,5 @@
|
||||
function GetAppLayoutMock() {
|
||||
return () => Promise.resolve({ id: 'app-layout' });
|
||||
}
|
||||
|
||||
export default GetAppLayoutMock;
|
||||
83
apis/enigma-mocker/src/mocks/get-object-mock.js
Normal file
83
apis/enigma-mocker/src/mocks/get-object-mock.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { getPropValue, getPropFn } from '../prop';
|
||||
import { findAsync } from '../util';
|
||||
|
||||
/**
|
||||
* Properties on `getObject()` operating synchronously.
|
||||
*/
|
||||
const PROPS_SYNC = [
|
||||
'addListener',
|
||||
'emit',
|
||||
'listeners',
|
||||
'on',
|
||||
'once',
|
||||
'removeAllListeners',
|
||||
'removeListener',
|
||||
'setMaxListerners',
|
||||
];
|
||||
|
||||
/**
|
||||
* Is property operating asynchrously.
|
||||
* @param {string} name Property name.
|
||||
* @returns `true` if property is operating asynchrously, otherwise `false`.
|
||||
*/
|
||||
function isPropAsync(name) {
|
||||
return !PROPS_SYNC.includes(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock of a generic object. Mandatory properties are added, functions returns async values where applicable etc.
|
||||
* @param {object} genericObject Generic object describing behaviour of mock
|
||||
* @returns The mocked object
|
||||
*/
|
||||
function createMock(genericObject) {
|
||||
const { id, session, ...props } = genericObject;
|
||||
return {
|
||||
id: getPropValue(id, { defaultValue: `object - ${+Date.now()}` }),
|
||||
session: getPropValue(session, { defaultValue: true }),
|
||||
on: () => {},
|
||||
once: () => {},
|
||||
...Object.entries(props).reduce(
|
||||
(fns, [name, value]) => ({
|
||||
...fns,
|
||||
[name]: getPropFn(value, { async: isPropAsync(name) }),
|
||||
}),
|
||||
{}
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if mandatory information is available.
|
||||
* @param {object} genericObject Generic object to validate
|
||||
* @throws {}
|
||||
* <ul>
|
||||
* <li>{Error} If getLayout is missing</li>
|
||||
* <li>{Error} If getLayout.qInfo.qId is missing</li>
|
||||
* </ul>
|
||||
*/
|
||||
function validate(genericObject) {
|
||||
if (!genericObject.getLayout) {
|
||||
throw new Error('Generic object is missing "getLayout"');
|
||||
}
|
||||
const layout = getPropValue(genericObject.getLayout);
|
||||
if (!(layout.qInfo && layout.qInfo.qId)) {
|
||||
throw new Error('Generic object is missing "qId" for path "getLayout().qInfo.qId"');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates mock of `getObject(id)` based on an array of generic objects.
|
||||
* @param {Array<object>} genericObjects Generic objects.
|
||||
* @returns Function to retrieve the mocked generic object with the corresponding id.
|
||||
*/
|
||||
function GetObjectMock(genericObjects = []) {
|
||||
genericObjects.forEach(validate);
|
||||
const genericObjectMocks = genericObjects.map(createMock);
|
||||
|
||||
return async (id) => {
|
||||
const mock = findAsync(genericObjectMocks, async (m) => (await m.getLayout()).qInfo.qId === id);
|
||||
return Promise.resolve(mock);
|
||||
};
|
||||
}
|
||||
|
||||
export default GetObjectMock;
|
||||
11
apis/enigma-mocker/src/mocks/session-mock.js
Normal file
11
apis/enigma-mocker/src/mocks/session-mock.js
Normal file
@@ -0,0 +1,11 @@
|
||||
function SessionMock() {
|
||||
return {
|
||||
getObjectApi() {
|
||||
return Promise.resolve({
|
||||
id: `sessapi - ${+Date.now()}`,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default SessionMock;
|
||||
62
apis/enigma-mocker/src/prop.js
Normal file
62
apis/enigma-mocker/src/prop.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Get value for a fixture property.
|
||||
*
|
||||
* The value is either static (e.g. pass a string / object / similar) or dynamic when passing a function.
|
||||
*
|
||||
* It falls back to the default value in case the fixture has no value specified.
|
||||
*
|
||||
* Example
|
||||
* ```js
|
||||
* const fixture = {
|
||||
* id: 'grid-chart-1',
|
||||
* };
|
||||
* const app = {
|
||||
* id: getValue(fixture.id, { defaultValue: 'object-id-${+Date.now()}'}),
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param {any} prop Fixture property. Either a fixed value (string / object / boolean / ...) or a function invoked when the value is needed.
|
||||
* @param {object} options Options.
|
||||
* @param {Array<any>} options.args Arguments used to evaluate the property value.
|
||||
* @param {any} options.defaultValue Default value in case not value is defined in fixture.
|
||||
* @returns The property value.
|
||||
*/
|
||||
export const getPropValue = (prop, { args = [], defaultValue } = {}) => {
|
||||
if (typeof prop === 'function') {
|
||||
return prop(...args);
|
||||
}
|
||||
if (prop !== undefined) {
|
||||
return prop;
|
||||
}
|
||||
return defaultValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get function for a fixture property.
|
||||
*
|
||||
* When the returned function is invoked it resolves the value - using `defaultValue` as fallback - and returns it. The value is returned as a promise if `option.usePromise` is `true`.
|
||||
*
|
||||
* Example:
|
||||
* ```js
|
||||
* const fixture = {
|
||||
* getHyperCubeData(path, page) {
|
||||
* return [ ... ];
|
||||
* }
|
||||
* }
|
||||
* const app = {
|
||||
* getHyperCubeData: getPropFn(fixture.getHyperCubeData, { defaultValue: [], usePromise: true })
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @param {any} prop Fixture property. Either a fixed value (string / object / boolean / ...) or a function invoked when the value is needed.
|
||||
* @param {object} options Options.
|
||||
* @param {any} options.defaultValue Default value in case not value is defined in fixture.
|
||||
* @param {boolean} options.async When `true` the returns value is wrapped in a promise, otherwise the value is directly returned.
|
||||
* @returns A fixture property function
|
||||
*/
|
||||
export const getPropFn =
|
||||
(prop, { defaultValue, async = true } = {}) =>
|
||||
(...args) => {
|
||||
const value = getPropValue(prop, { defaultValue, args });
|
||||
return async ? Promise.resolve(value) : value;
|
||||
};
|
||||
7
apis/enigma-mocker/src/util.js
Normal file
7
apis/enigma-mocker/src/util.js
Normal file
@@ -0,0 +1,7 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
export async function findAsync(arr, asyncCallback) {
|
||||
const promises = arr.map(asyncCallback);
|
||||
const results = await Promise.all(promises);
|
||||
const index = results.findIndex((result) => result);
|
||||
return arr[index];
|
||||
}
|
||||
@@ -14,6 +14,7 @@ module.exports = {
|
||||
'**/apis/theme/index.js',
|
||||
'**/apis/conversion/index.js',
|
||||
'**/apis/test-utils/index.js',
|
||||
'**/apis/enigma-mocker/index.js',
|
||||
'**/packages/ui/icons/**/*.js', // Exclude the defined icons but test the `<SvgIcon />`
|
||||
],
|
||||
},
|
||||
|
||||
@@ -130,6 +130,7 @@ const config = ({ format = 'umd', debug = false, file, targetPkg }) => {
|
||||
babelrc: false,
|
||||
include: [
|
||||
'/**/apis/conversion/**',
|
||||
'/**/apis/enigma-mocker/**',
|
||||
'/**/apis/locale/**',
|
||||
'/**/apis/nucleus/**',
|
||||
'/**/apis/snapshooter/**',
|
||||
|
||||
Reference in New Issue
Block a user