From 73d54d5acf1595818ad775169f387d3211a4df78 Mon Sep 17 00:00:00 2001 From: jorgenherje Date: Thu, 5 Oct 2023 20:24:55 +0200 Subject: [PATCH] Add unit tests --- .../parameterListFilter.tsx | 5 +- .../private-utils/smartNodeSelectorUtils.ts | 50 ++++-- .../ParameterListFilterUtils.test.ts | 163 ++++++++++++++++++ 3 files changed, 202 insertions(+), 16 deletions(-) create mode 100644 frontend/tests/unit-tests/ParameterListFilterUtils.test.ts diff --git a/frontend/src/framework/components/ParameterListFilter/parameterListFilter.tsx b/frontend/src/framework/components/ParameterListFilter/parameterListFilter.tsx index 1bf483cab..e46bfddc5 100644 --- a/frontend/src/framework/components/ParameterListFilter/parameterListFilter.tsx +++ b/frontend/src/framework/components/ParameterListFilter/parameterListFilter.tsx @@ -7,6 +7,9 @@ import { resolveClassNames } from "@lib/utils/resolveClassNames"; import { isEqual } from "lodash"; +// Icons placed here due to limitation of jest for testing utils (cannot import svg) +import checkIcon from "./private-assets/check.svg"; +import segmentIcon from "./private-assets/segment.svg"; import { ParameterParentNodeNames, createTreeDataNodeListFromParameters, @@ -29,7 +32,7 @@ export const ParameterListFilter: React.FC = (props: P let candidateTreeDataNodeList = treeDataNodeList; if (parameters === null || !isEqual(props.parameters, parameters)) { - candidateTreeDataNodeList = createTreeDataNodeListFromParameters([...props.parameters]); + candidateTreeDataNodeList = createTreeDataNodeListFromParameters([...props.parameters], checkIcon, segmentIcon); setParameters(props.parameters); setTreeDataNodeList(candidateTreeDataNodeList); } diff --git a/frontend/src/framework/components/ParameterListFilter/private-utils/smartNodeSelectorUtils.ts b/frontend/src/framework/components/ParameterListFilter/private-utils/smartNodeSelectorUtils.ts index 8a6ad9c61..162ee8656 100644 --- a/frontend/src/framework/components/ParameterListFilter/private-utils/smartNodeSelectorUtils.ts +++ b/frontend/src/framework/components/ParameterListFilter/private-utils/smartNodeSelectorUtils.ts @@ -1,9 +1,6 @@ import { Parameter, ParameterIdent, ParameterType } from "@framework/EnsembleParameters"; import { TreeDataNode } from "@lib/components/SmartNodeSelector"; -import checkIcon from "../private-assets/check.svg"; -import segmentIcon from "../private-assets/segment.svg"; - export const ParameterParentNodeNames = { NAME: "Name", GROUP: "Group", @@ -25,13 +22,13 @@ export function fromParameterTypeToNodeName(type: ParameterType): string { throw new Error(`Parameter type ${type} not supported`); } -function createAndAddNode(treeNodeDataList: TreeDataNode[], nodeName: string, icon?: string): TreeDataNode { +export function createAndAddNode(treeNodeDataList: TreeDataNode[], nodeName: string, icon?: string): TreeDataNode { const newNode: TreeDataNode = { name: nodeName, description: "", icon: icon }; treeNodeDataList.push(newNode); return newNode; } -function findOrCreateNode(treeNodeDataList: TreeDataNode[], nodeName: string, icon?: string): TreeDataNode { +export function findOrCreateNode(treeNodeDataList: TreeDataNode[], nodeName: string, icon?: string): TreeDataNode { const existingNode = treeNodeDataList.find((node) => node.name === nodeName); if (existingNode) { return existingNode; @@ -40,9 +37,13 @@ function findOrCreateNode(treeNodeDataList: TreeDataNode[], nodeName: string, ic return createAndAddNode(treeNodeDataList, nodeName, icon); } -function addParameterNameAndGroupToTreeDataNodeList(treeNodeDataList: TreeDataNode[], parameter: Parameter): void { +export function addParameterNameAndGroupToTreeDataNodeList( + treeNodeDataList: TreeDataNode[], + parameter: Parameter, + icon?: string +): void { // Parameter Name - const nameParentNode = findOrCreateNode(treeNodeDataList, ParameterParentNodeNames.NAME, segmentIcon); + const nameParentNode = findOrCreateNode(treeNodeDataList, ParameterParentNodeNames.NAME, icon); if (!nameParentNode.children) { nameParentNode.children = []; } @@ -50,7 +51,7 @@ function addParameterNameAndGroupToTreeDataNodeList(treeNodeDataList: TreeDataNo // Parameter Group if (parameter.groupName) { - const groupParentNode = findOrCreateNode(treeNodeDataList, ParameterParentNodeNames.GROUP, segmentIcon); + const groupParentNode = findOrCreateNode(treeNodeDataList, ParameterParentNodeNames.GROUP, icon); if (!groupParentNode.children) { groupParentNode.children = []; } @@ -58,7 +59,11 @@ function addParameterNameAndGroupToTreeDataNodeList(treeNodeDataList: TreeDataNo } } -export function createTreeDataNodeListFromParameters(parameters: Parameter[]): TreeDataNode[] { +export function createTreeDataNodeListFromParameters( + parameters: Parameter[], + checkIcon?: string, + parentIcon?: string +): TreeDataNode[] { if (parameters.length === 0) { return []; } @@ -78,7 +83,7 @@ export function createTreeDataNodeListFromParameters(parameters: Parameter[]): T // Add name and group for parameters for (const parameter of parameters) { - addParameterNameAndGroupToTreeDataNodeList(treeDataNodeList, parameter); + addParameterNameAndGroupToTreeDataNodeList(treeDataNodeList, parameter, parentIcon); } return treeDataNodeList; @@ -97,9 +102,6 @@ export function getParametersMatchingSelectedNodes(parameters: Parameter[], sele .map((node) => node.split(delimiter, 2)[1]); }; - // Get parameter property filters - const selectedParameterNames = findSelectedParameterPropertiesFromName(ParameterParentNodeNames.NAME); - const selectedParameterGroups = findSelectedParameterPropertiesFromName(ParameterParentNodeNames.GROUP); const isContinuousSelected = selectedNodes.includes(ParameterParentNodeNames.CONTINUOUS); const isDiscreteSelected = selectedNodes.includes(ParameterParentNodeNames.DISCRETE); const isConstantSelected = selectedNodes.includes(ParameterParentNodeNames.IS_CONSTANT); @@ -107,14 +109,32 @@ export function getParametersMatchingSelectedNodes(parameters: Parameter[], sele const isLogarithmicSelected = selectedNodes.includes(ParameterParentNodeNames.IS_LOGARITHMIC); const isLinearSelected = selectedNodes.includes(ParameterParentNodeNames.IS_LINEAR); + // Intersection filtering, i.e. parameter cannot be both continuous and discrete, constant and non-constant, logarithmic and linear if (isContinuousSelected && isDiscreteSelected) return []; if (isConstantSelected && isNonConstantSelected) return []; if (isLogarithmicSelected && isLinearSelected) return []; + const selectedParameterNames = findSelectedParameterPropertiesFromName(ParameterParentNodeNames.NAME); + const selectedParameterGroups = findSelectedParameterPropertiesFromName(ParameterParentNodeNames.GROUP); + + // Prevent invalid nodes + if ( + !isContinuousSelected && + !isDiscreteSelected && + !isConstantSelected && + !isNonConstantSelected && + !isLogarithmicSelected && + !isLinearSelected && + selectedParameterNames.length === 0 && + selectedParameterGroups.length === 0 + ) { + return []; + } + const selectedEnsembleParameters: Parameter[] = []; for (const parameter of parameters) { // Filter by parameter name - if (selectedParameterNames.length > 0 && !selectedParameterNames.includes(parameter.name)) { + if (selectedParameterNames.length !== 0 && !selectedParameterNames.includes(parameter.name)) { continue; } @@ -138,7 +158,7 @@ export function getParametersMatchingSelectedNodes(parameters: Parameter[], sele if (isConstantSelected && !parameter.isConstant) continue; if (isNonConstantSelected && parameter.isConstant) continue; - // Filter by parameter is logarithmic/linear + // Filter by parameter is logarithmic/linear (only for continuous parameters) if (isLogarithmicSelected && parameter.type === ParameterType.CONTINUOUS && !parameter.isLogarithmic) continue; if (isLinearSelected && parameter.type === ParameterType.CONTINUOUS && parameter.isLogarithmic) continue; diff --git a/frontend/tests/unit-tests/ParameterListFilterUtils.test.ts b/frontend/tests/unit-tests/ParameterListFilterUtils.test.ts new file mode 100644 index 000000000..81082c798 --- /dev/null +++ b/frontend/tests/unit-tests/ParameterListFilterUtils.test.ts @@ -0,0 +1,163 @@ +import { ContinuousParameter, DiscreteParameter, Parameter, ParameterType } from "@framework/EnsembleParameters"; +import { + addParameterNameAndGroupToTreeDataNodeList, + createAndAddNode, + createTreeDataNodeListFromParameters, + findOrCreateNode, + fromParameterTypeToNodeName, + getParametersMatchingSelectedNodes, +} from "@framework/components/ParameterListFilter/private-utils/smartNodeSelectorUtils"; +import { TreeDataNode } from "@lib/components/SmartNodeSelector"; + +const CONTINUOUS_PARAMETER: ContinuousParameter = { + type: ParameterType.CONTINUOUS, + name: "continuous parameter", + groupName: "group1", + description: "continuous parameter description", + isConstant: false, + isLogarithmic: false, + realizations: [1, 2, 3], + values: [10, 11, 12], +}; + +const SECOND_CONTINUOUS_PARAMETER: ContinuousParameter = { + type: ParameterType.CONTINUOUS, + name: "second continuous parameter", + groupName: "group1", + description: "continuous parameter description 2", + isConstant: true, + isLogarithmic: false, + realizations: [1, 2, 3], + values: [10, 11, 12], +}; + +const DISCRETE_PARAMETER: DiscreteParameter = { + type: ParameterType.DISCRETE, + name: "discrete parameter", + groupName: "group2", + description: "discrete parameter description", + isConstant: true, + realizations: [1, 2, 3], + values: [10, 11, 12], +}; + +describe("Test of utility functions for ParameterListFilter", () => { + test("Check from parameter type to node name conversion", () => { + expect(fromParameterTypeToNodeName(ParameterType.CONTINUOUS)).toBe("Continuous"); + expect(fromParameterTypeToNodeName(ParameterType.DISCRETE)).toBe("Discrete"); + }); + + test("Test create and add node", () => { + const myTestList: TreeDataNode[] = []; + const newNode = createAndAddNode(myTestList, "my node"); + expect(newNode.name).toBe("my node"); + expect(newNode.name).toBe(myTestList[0].name); + }); + + test("Test find node", () => { + const testNode = { name: "my node", description: "", icon: undefined }; + const testNodes: TreeDataNode[] = [testNode]; + const foundNode = findOrCreateNode(testNodes, "my node"); + expect(foundNode.name).toBe("my node"); + expect(testNodes.length).toBe(1); + }); + + test("Test create node", () => { + const testNodes: TreeDataNode[] = []; + const createdNode = findOrCreateNode(testNodes, "my node"); + expect(createdNode.name).toBe("my node"); + expect(testNodes.length).toBe(1); + }); + + test("Add parameter name and group to tree data node list", () => { + const testNodes: TreeDataNode[] = []; + addParameterNameAndGroupToTreeDataNodeList(testNodes, CONTINUOUS_PARAMETER); + expect(testNodes.length).toBe(2); + expect(testNodes[0].name).toBe("Name"); + expect(testNodes[0].children?.length).toBe(1); + expect(testNodes[0].children?.[0].name).toBe("continuous parameter"); + expect(testNodes[1].name).toBe("Group"); + expect(testNodes[1].children?.length).toBe(1); + expect(testNodes[1].children?.[0].name).toBe("group1"); + + addParameterNameAndGroupToTreeDataNodeList(testNodes, DISCRETE_PARAMETER); + expect(testNodes.length).toBe(2); + expect(testNodes[0].name).toBe("Name"); + expect(testNodes[0].children?.length).toBe(2); + expect(testNodes[0].children?.[0].name).toBe("continuous parameter"); + expect(testNodes[0].children?.[1].name).toBe("discrete parameter"); + expect(testNodes[1].name).toBe("Group"); + expect(testNodes[1].children?.length).toBe(2); + expect(testNodes[1].children?.[0].name).toBe("group1"); + expect(testNodes[1].children?.[1].name).toBe("group2"); + }); + + test("Create tree data node list from parameters", () => { + const parameterList = [CONTINUOUS_PARAMETER, DISCRETE_PARAMETER]; + const testNodes = createTreeDataNodeListFromParameters(parameterList); + + expect(testNodes.length).toBe(8); + expect(testNodes[0].name).toBe("Continuous"); + expect(testNodes[0].children).toBe(undefined); + expect(testNodes[1].name).toBe("Discrete"); + expect(testNodes[1].children).toBe(undefined); + expect(testNodes[2].name).toBe("Constant"); + expect(testNodes[2].children).toBe(undefined); + expect(testNodes[3].name).toBe("Nonconstant"); + expect(testNodes[3].children).toBe(undefined); + expect(testNodes[4].name).toBe("Logarithmic"); + expect(testNodes[4].children).toBe(undefined); + expect(testNodes[5].name).toBe("Linear"); + expect(testNodes[5].children).toBe(undefined); + expect(testNodes[6].name).toBe("Name"); + expect(testNodes[6].children?.length).toBe(2); + expect(testNodes[6].children?.[0].name).toBe("continuous parameter"); + expect(testNodes[6].children?.[1].name).toBe("discrete parameter"); + expect(testNodes[7].name).toBe("Group"); + expect(testNodes[7].children?.length).toBe(2); + expect(testNodes[7].children?.[0].name).toBe("group1"); + expect(testNodes[7].children?.[1].name).toBe("group2"); + }); + + test("Get parameters matching selected nodes - invalid/conflicting", () => { + const parameterList = [CONTINUOUS_PARAMETER, DISCRETE_PARAMETER]; + + const invalidNodes = ["Invalid node"]; + expect(getParametersMatchingSelectedNodes(parameterList, invalidNodes)).toEqual([]); + const conflictingNodes = ["Continuous", "Discrete"]; + expect(getParametersMatchingSelectedNodes(parameterList, conflictingNodes)).toEqual([]); + }); + + test("Get parameters matching selected nodes - valid", () => { + const parameterList = [CONTINUOUS_PARAMETER, DISCRETE_PARAMETER]; + + expect(getParametersMatchingSelectedNodes(parameterList, ["Group:group1"])).toEqual([CONTINUOUS_PARAMETER]); + expect(getParametersMatchingSelectedNodes(parameterList, ["Name:discrete parameter"])).toEqual([ + DISCRETE_PARAMETER, + ]); + }); + + test("Get parameters matching selected nodes - multiple matches", () => { + const parameterList = [CONTINUOUS_PARAMETER, SECOND_CONTINUOUS_PARAMETER, DISCRETE_PARAMETER]; + + expect(getParametersMatchingSelectedNodes(parameterList, ["Group:group1"])).toEqual([ + CONTINUOUS_PARAMETER, + SECOND_CONTINUOUS_PARAMETER, + ]); + expect(getParametersMatchingSelectedNodes(parameterList, ["Constant"])).toEqual([ + SECOND_CONTINUOUS_PARAMETER, + DISCRETE_PARAMETER, + ]); + }); + + test("Get parameters matching selected nodes - multiple selected nodes", () => { + const parameterList = [CONTINUOUS_PARAMETER, SECOND_CONTINUOUS_PARAMETER, DISCRETE_PARAMETER]; + + expect(getParametersMatchingSelectedNodes(parameterList, ["Group:group1", "Constant"])).toEqual([ + SECOND_CONTINUOUS_PARAMETER, + ]); + expect(getParametersMatchingSelectedNodes(parameterList, ["Invalid Node", "Discrete"])).toEqual([ + DISCRETE_PARAMETER, + ]); + }); +});