2022-03-03 10:59:52 +00:00
import { DependencyMap } from "utils/DynamicBindingUtils" ;
2022-01-21 12:08:40 +00:00
import { RenderModes } from "constants/WidgetConstants" ;
import { ValidationTypes } from "constants/WidgetValidation" ;
import {
DataTreeWidget ,
ENTITY_TYPE ,
EvaluationSubstitutionType ,
PrivateWidgets ,
} from "entities/DataTree/dataTreeFactory" ;
import {
2022-04-09 13:55:41 +00:00
DataTreeDiff ,
DataTreeDiffEvent ,
2022-01-21 12:08:40 +00:00
getAllPaths ,
getAllPrivateWidgetsInDataTree ,
getDataTreeWithoutPrivateWidgets ,
isPrivateEntityPath ,
2022-03-03 10:59:52 +00:00
makeParentsDependOnChildren ,
2022-04-09 13:55:41 +00:00
translateDiffEventToDataTreeDiffEvent ,
2022-01-21 12:08:40 +00:00
} from "./evaluationUtils" ;
2022-03-03 10:59:52 +00:00
import { warn as logWarn } from "loglevel" ;
2022-04-09 13:55:41 +00:00
import { Diff } from "deep-diff" ;
import { flatten } from "lodash" ;
2022-05-25 09:46:14 +00:00
import { overrideWidgetProperties } from "./evaluationUtils" ;
import { DataTree } from "entities/DataTree/dataTreeFactory" ;
import { EvalMetaUpdates } from "./DataTreeEvaluator/types" ;
import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget" ;
import TableWidget , { CONFIG as TableWidgetConfig } from "widgets/TableWidget" ;
import InputWidget , {
CONFIG as InputWidgetV2Config ,
} from "widgets/InputWidgetV2" ;
import { registerWidget } from "utils/WidgetRegisterHelpers" ;
2022-03-03 10:59:52 +00:00
// to check if logWarn was called.
// use jest.unmock, if the mock needs to be removed.
jest . mock ( "loglevel" ) ;
2022-01-21 12:08:40 +00:00
const BASE_WIDGET : DataTreeWidget = {
logBlackList : { } ,
widgetId : "randomID" ,
widgetName : "randomWidgetName" ,
bottomRow : 0 ,
isLoading : false ,
leftColumn : 0 ,
parentColumnSpace : 0 ,
parentRowSpace : 0 ,
renderMode : RenderModes.CANVAS ,
rightColumn : 0 ,
topRow : 0 ,
type : "SKELETON_WIDGET" ,
parentId : "0" ,
version : 1 ,
bindingPaths : { } ,
2022-04-12 13:09:26 +00:00
reactivePaths : { } ,
2022-01-21 12:08:40 +00:00
triggerPaths : { } ,
validationPaths : { } ,
ENTITY_TYPE : ENTITY_TYPE.WIDGET ,
privateWidgets : { } ,
2022-03-03 10:59:52 +00:00
propertyOverrideDependency : { } ,
overridingPropertyPaths : { } ,
2022-05-25 09:46:14 +00:00
meta : { } ,
2022-01-21 12:08:40 +00:00
} ;
const testDataTree : Record < string , DataTreeWidget > = {
Text1 : {
. . . BASE_WIDGET ,
widgetName : "Text1" ,
text : "Label" ,
type : "TEXT_WIDGET" ,
2022-04-12 13:09:26 +00:00
reactivePaths : {
2022-01-21 12:08:40 +00:00
text : EvaluationSubstitutionType.TEMPLATE ,
} ,
validationPaths : {
text : { type : ValidationTypes . TEXT } ,
} ,
} ,
Text2 : {
. . . BASE_WIDGET ,
widgetName : "Text2" ,
text : "{{Text1.text}}" ,
dynamicBindingPathList : [ { key : "text" } ] ,
type : "TEXT_WIDGET" ,
2022-04-12 13:09:26 +00:00
reactivePaths : {
2022-01-21 12:08:40 +00:00
text : EvaluationSubstitutionType.TEMPLATE ,
} ,
validationPaths : {
text : { type : ValidationTypes . TEXT } ,
} ,
} ,
Text3 : {
. . . BASE_WIDGET ,
widgetName : "Text3" ,
text : "{{Text1.text}}" ,
dynamicBindingPathList : [ { key : "text" } ] ,
type : "TEXT_WIDGET" ,
2022-04-12 13:09:26 +00:00
reactivePaths : {
2022-01-21 12:08:40 +00:00
text : EvaluationSubstitutionType.TEMPLATE ,
} ,
validationPaths : {
text : { type : ValidationTypes . TEXT } ,
} ,
} ,
Text4 : {
. . . BASE_WIDGET ,
widgetName : "Text4" ,
text : "{{Text1.text}}" ,
dynamicBindingPathList : [ { key : "text" } ] ,
type : "TEXT_WIDGET" ,
2022-04-12 13:09:26 +00:00
reactivePaths : {
2022-01-21 12:08:40 +00:00
text : EvaluationSubstitutionType.TEMPLATE ,
} ,
validationPaths : {
text : { type : ValidationTypes . TEXT } ,
} ,
} ,
List1 : {
. . . BASE_WIDGET ,
privateWidgets : {
Text2 : true ,
} ,
} ,
List2 : {
. . . BASE_WIDGET ,
privateWidgets : {
Text3 : true ,
} ,
} ,
} ;
2021-01-29 17:59:23 +00:00
2022-01-20 10:59:03 +00:00
describe ( "Correctly handle paths" , ( ) = > {
2021-01-29 17:59:23 +00:00
it ( "getsAllPaths" , ( ) = > {
const myTree = {
WidgetName : {
1 : "yo" ,
name : "WidgetName" ,
objectProperty : {
childObjectProperty : [
"1" ,
1 ,
{
key : "value" ,
2 : 1 ,
} ,
[ "1" , "2" ] ,
] ,
} ,
} ,
} ;
const result = {
WidgetName : true ,
"WidgetName.1" : true ,
"WidgetName.name" : true ,
"WidgetName.objectProperty" : true ,
"WidgetName.objectProperty.childObjectProperty" : true ,
"WidgetName.objectProperty.childObjectProperty[0]" : true ,
"WidgetName.objectProperty.childObjectProperty[1]" : true ,
"WidgetName.objectProperty.childObjectProperty[2]" : true ,
"WidgetName.objectProperty.childObjectProperty[2].key" : true ,
"WidgetName.objectProperty.childObjectProperty[2].2" : true ,
"WidgetName.objectProperty.childObjectProperty[3]" : true ,
"WidgetName.objectProperty.childObjectProperty[3][0]" : true ,
"WidgetName.objectProperty.childObjectProperty[3][1]" : true ,
} ;
const actual = getAllPaths ( myTree ) ;
expect ( actual ) . toStrictEqual ( result ) ;
} ) ;
2022-01-21 12:08:40 +00:00
} ) ;
2022-01-20 10:59:03 +00:00
2022-01-21 12:08:40 +00:00
describe ( "privateWidgets" , ( ) = > {
2022-01-20 10:59:03 +00:00
it ( "correctly checks if path is a PrivateEntityPath" , ( ) = > {
const privateWidgets : PrivateWidgets = {
Button1 : true ,
Image1 : true ,
Button2 : true ,
Image2 : true ,
} ;
expect (
isPrivateEntityPath ( privateWidgets , "List1.template.Button1.text" ) ,
) . toBeFalsy ( ) ;
expect ( isPrivateEntityPath ( privateWidgets , "Button1.text" ) ) . toBeTruthy ( ) ;
expect (
isPrivateEntityPath ( privateWidgets , "List2.template.Image2.data" ) ,
) . toBeFalsy ( ) ;
expect ( isPrivateEntityPath ( privateWidgets , "Image2.data" ) ) . toBeTruthy ( ) ;
} ) ;
2022-01-21 12:08:40 +00:00
it ( "Returns list of all privateWidgets" , ( ) = > {
const expectedPrivateWidgetsList = {
Text2 : true ,
Text3 : true ,
} ;
const actualPrivateWidgetsList = getAllPrivateWidgetsInDataTree (
testDataTree ,
) ;
expect ( expectedPrivateWidgetsList ) . toStrictEqual ( actualPrivateWidgetsList ) ;
} ) ;
it ( "Returns data tree without privateWidgets" , ( ) = > {
const expectedDataTreeWithoutPrivateWidgets : Record <
string ,
DataTreeWidget
> = {
Text1 : {
. . . BASE_WIDGET ,
widgetName : "Text1" ,
text : "Label" ,
type : "TEXT_WIDGET" ,
2022-04-12 13:09:26 +00:00
reactivePaths : {
2022-01-21 12:08:40 +00:00
text : EvaluationSubstitutionType.TEMPLATE ,
} ,
validationPaths : {
text : { type : ValidationTypes . TEXT } ,
} ,
} ,
Text4 : {
. . . BASE_WIDGET ,
widgetName : "Text4" ,
text : "{{Text1.text}}" ,
dynamicBindingPathList : [ { key : "text" } ] ,
type : "TEXT_WIDGET" ,
2022-04-12 13:09:26 +00:00
reactivePaths : {
2022-01-21 12:08:40 +00:00
text : EvaluationSubstitutionType.TEMPLATE ,
} ,
validationPaths : {
text : { type : ValidationTypes . TEXT } ,
} ,
} ,
List1 : {
. . . BASE_WIDGET ,
privateWidgets : {
Text2 : true ,
} ,
} ,
List2 : {
. . . BASE_WIDGET ,
privateWidgets : {
Text3 : true ,
} ,
} ,
} ;
const actualDataTreeWithoutPrivateWidgets = getDataTreeWithoutPrivateWidgets (
testDataTree ,
) ;
expect ( expectedDataTreeWithoutPrivateWidgets ) . toStrictEqual (
actualDataTreeWithoutPrivateWidgets ,
) ;
} ) ;
2021-01-29 17:59:23 +00:00
} ) ;
2022-03-03 10:59:52 +00:00
describe ( "makeParentsDependOnChildren" , ( ) = > {
it ( "makes parent properties depend on child properties" , ( ) = > {
let depMap : DependencyMap = {
Widget1 : [ ] ,
"Widget1.defaultText" : [ ] ,
"Widget1.defaultText.abc" : [ ] ,
} ;
const allkeys : Record < string , true > = {
Widget1 : true ,
"Widget1.defaultText" : true ,
"Widget1.defaultText.abc" : true ,
} ;
depMap = makeParentsDependOnChildren ( depMap , allkeys ) ;
expect ( depMap ) . toStrictEqual ( {
Widget1 : [ "Widget1.defaultText" ] ,
"Widget1.defaultText" : [ "Widget1.defaultText.abc" ] ,
"Widget1.defaultText.abc" : [ ] ,
} ) ;
} ) ;
it ( "logs warning for child properties not listed in allKeys" , ( ) = > {
const depMap : DependencyMap = {
Widget1 : [ ] ,
"Widget1.defaultText" : [ ] ,
} ;
const allkeys : Record < string , true > = {
Widget1 : true ,
} ;
makeParentsDependOnChildren ( depMap , allkeys ) ;
expect ( logWarn ) . toBeCalledWith (
"makeParentsDependOnChild - Widget1.defaultText is not present in dataTree." ,
"This might result in a cyclic dependency." ,
) ;
} ) ;
} ) ;
2022-04-09 13:55:41 +00:00
describe ( "translateDiffEvent" , ( ) = > {
it ( "noop when diff path does not exist" , ( ) = > {
const noDiffPath : Diff < any , any > = {
kind : "E" ,
lhs : undefined ,
rhs : undefined ,
} ;
const result = translateDiffEventToDataTreeDiffEvent ( noDiffPath , { } ) ;
expect ( result ) . toStrictEqual ( {
payload : {
propertyPath : "" ,
value : "" ,
} ,
event : DataTreeDiffEvent.NOOP ,
} ) ;
} ) ;
it ( "translates new and delete events" , ( ) = > {
const diffs : Diff < any , any > [ ] = [
{
kind : "N" ,
path : [ "Widget1" ] ,
rhs : { } ,
} ,
{
kind : "N" ,
path : [ "Widget1" , "name" ] ,
rhs : "Widget1" ,
} ,
{
kind : "D" ,
path : [ "Widget1" ] ,
lhs : { } ,
} ,
{
kind : "D" ,
path : [ "Widget1" , "name" ] ,
lhs : "Widget1" ,
} ,
{
kind : "E" ,
path : [ "Widget2" , "name" ] ,
rhs : "test" ,
lhs : "test2" ,
} ,
] ;
const expectedTranslations : DataTreeDiff [ ] = [
{
payload : {
propertyPath : "Widget1" ,
} ,
event : DataTreeDiffEvent.NEW ,
} ,
{
payload : {
propertyPath : "Widget1.name" ,
} ,
event : DataTreeDiffEvent.NEW ,
} ,
{
payload : {
propertyPath : "Widget1" ,
} ,
event : DataTreeDiffEvent.DELETE ,
} ,
{
payload : {
propertyPath : "Widget1.name" ,
} ,
event : DataTreeDiffEvent.DELETE ,
} ,
{
payload : {
propertyPath : "" ,
value : "" ,
} ,
event : DataTreeDiffEvent.NOOP ,
} ,
] ;
const actualTranslations = flatten (
diffs . map ( ( diff ) = > translateDiffEventToDataTreeDiffEvent ( diff , { } ) ) ,
) ;
expect ( expectedTranslations ) . toStrictEqual ( actualTranslations ) ;
} ) ;
it ( "properly categorises the edit events" , ( ) = > {
const diffs : Diff < any , any > [ ] = [
{
kind : "E" ,
path : [ "Widget2" , "name" ] ,
rhs : "test" ,
lhs : "test2" ,
} ,
] ;
const expectedTranslations : DataTreeDiff [ ] = [
{
payload : {
propertyPath : "" ,
value : "" ,
} ,
event : DataTreeDiffEvent.NOOP ,
} ,
] ;
const actualTranslations = flatten (
diffs . map ( ( diff ) = > translateDiffEventToDataTreeDiffEvent ( diff , { } ) ) ,
) ;
expect ( expectedTranslations ) . toStrictEqual ( actualTranslations ) ;
} ) ;
} ) ;
2022-05-25 09:46:14 +00:00
describe ( "overrideWidgetProperties" , ( ) = > {
beforeAll ( ( ) = > {
registerWidget ( TableWidget , TableWidgetConfig ) ;
registerWidget ( InputWidget , InputWidgetV2Config ) ;
} ) ;
describe ( "1. Input widget " , ( ) = > {
const currentTree : DataTree = { } ;
beforeAll ( ( ) = > {
const inputWidgetDataTree = generateDataTreeWidget (
{
type : InputWidget . getWidgetType ( ) ,
widgetId : "egwwwfgab" ,
widgetName : "Input1" ,
children : [ ] ,
} ,
{ } ,
) ;
currentTree [ "Input1" ] = inputWidgetDataTree ;
} ) ;
// When default text is re-evaluated it will override values of meta.text and text in InputWidget
it ( "1. defaultText updating meta.text and text" , ( ) = > {
const evalMetaUpdates : EvalMetaUpdates = [ ] ;
const overwriteObj = overrideWidgetProperties ( {
currentTree ,
entity : currentTree.Input1 as DataTreeWidget ,
propertyPath : "defaultText" ,
value : "abcde" ,
evalMetaUpdates ,
} ) ;
expect ( overwriteObj ) . toStrictEqual ( undefined ) ;
expect ( evalMetaUpdates ) . toStrictEqual ( [
{
widgetId : currentTree.Input1.widgetId ,
metaPropertyPath : [ "inputText" ] ,
value : "abcde" ,
} ,
{
widgetId : currentTree.Input1.widgetId ,
metaPropertyPath : [ "text" ] ,
value : "abcde" ,
} ,
] ) ;
expect ( currentTree . Input1 . meta ) . toStrictEqual ( {
text : "abcde" ,
inputText : "abcde" ,
} ) ;
} ) ;
// When meta.text is re-evaluated it will override values text in InputWidget
it ( "2. meta.text updating text" , ( ) = > {
const evalMetaUpdates : EvalMetaUpdates = [ ] ;
const overwriteObj = overrideWidgetProperties ( {
currentTree ,
entity : currentTree.Input1 as DataTreeWidget ,
propertyPath : "meta.text" ,
value : "abcdefg" ,
evalMetaUpdates ,
} ) ;
expect ( overwriteObj ) . toStrictEqual ( undefined ) ;
expect ( evalMetaUpdates ) . toStrictEqual ( [ ] ) ;
expect ( currentTree . Input1 . text ) . toStrictEqual ( "abcdefg" ) ;
} ) ;
} ) ;
describe ( "2. Table widget " , ( ) = > {
const currentTree : DataTree = { } ;
beforeAll ( ( ) = > {
const tableWidgetDataTree = generateDataTreeWidget (
{
type : TableWidget . getWidgetType ( ) ,
widgetId : "random" ,
widgetName : "Table1" ,
children : [ ] ,
} ,
{ } ,
) ;
currentTree [ "Table1" ] = tableWidgetDataTree ;
} ) ;
// When default defaultSelectedRow is re-evaluated it will override values of meta.selectedRowIndices, selectedRowIndices, meta.selectedRowIndex & selectedRowIndex.
it ( "1. On change of defaultSelectedRow " , ( ) = > {
const evalMetaUpdates : EvalMetaUpdates = [ ] ;
const overwriteObj = overrideWidgetProperties ( {
currentTree ,
entity : currentTree.Table1 as DataTreeWidget ,
propertyPath : "defaultSelectedRow" ,
value : [ 0 , 1 ] ,
evalMetaUpdates ,
} ) ;
expect ( overwriteObj ) . toStrictEqual ( undefined ) ;
expect ( evalMetaUpdates ) . toStrictEqual ( [
{
widgetId : currentTree.Table1.widgetId ,
metaPropertyPath : [ "selectedRowIndex" ] ,
value : [ 0 , 1 ] ,
} ,
{
widgetId : currentTree.Table1.widgetId ,
metaPropertyPath : [ "selectedRowIndices" ] ,
value : [ 0 , 1 ] ,
} ,
] ) ;
expect ( currentTree . Table1 . meta . selectedRowIndex ) . toStrictEqual ( [ 0 , 1 ] ) ;
expect ( currentTree . Table1 . meta . selectedRowIndices ) . toStrictEqual ( [ 0 , 1 ] ) ;
} ) ;
// When meta.selectedRowIndex is re-evaluated it will override values selectedRowIndex
it ( "2. meta.selectedRowIndex updating selectedRowIndex" , ( ) = > {
const evalMetaUpdates : EvalMetaUpdates = [ ] ;
const overwriteObj = overrideWidgetProperties ( {
currentTree ,
entity : currentTree.Table1 as DataTreeWidget ,
propertyPath : "meta.selectedRowIndex" ,
value : 0 ,
evalMetaUpdates ,
} ) ;
expect ( overwriteObj ) . toStrictEqual ( undefined ) ;
expect ( evalMetaUpdates ) . toStrictEqual ( [ ] ) ;
expect ( currentTree . Table1 . selectedRowIndex ) . toStrictEqual ( 0 ) ;
} ) ;
} ) ;
} ) ;