From b71a38261965cf0b8ba15100001707806ba9bbdd Mon Sep 17 00:00:00 2001 From: Tomas Date: Thu, 17 Oct 2024 12:17:29 +0200 Subject: [PATCH 1/2] Refactor the SchemaModel class with a base class --- .../lib/{ => SchemaModel}/SchemaModel.test.ts | 211 +--------------- .../src/lib/{ => SchemaModel}/SchemaModel.ts | 127 +--------- .../lib/SchemaModel/SchemaModelBase.test.ts | 229 ++++++++++++++++++ .../src/lib/SchemaModel/SchemaModelBase.ts | 120 +++++++++ .../schema-model/src/lib/SchemaModel/index.ts | 1 + 5 files changed, 373 insertions(+), 315 deletions(-) rename frontend/packages/schema-model/src/lib/{ => SchemaModel}/SchemaModel.test.ts (83%) rename frontend/packages/schema-model/src/lib/{ => SchemaModel}/SchemaModel.ts (82%) create mode 100644 frontend/packages/schema-model/src/lib/SchemaModel/SchemaModelBase.test.ts create mode 100644 frontend/packages/schema-model/src/lib/SchemaModel/SchemaModelBase.ts create mode 100644 frontend/packages/schema-model/src/lib/SchemaModel/index.ts diff --git a/frontend/packages/schema-model/src/lib/SchemaModel.test.ts b/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.test.ts similarity index 83% rename from frontend/packages/schema-model/src/lib/SchemaModel.test.ts rename to frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.test.ts index d21c553844f..c4672af43ec 100644 --- a/frontend/packages/schema-model/src/lib/SchemaModel.test.ts +++ b/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.test.ts @@ -2,25 +2,18 @@ import { SchemaModel } from './SchemaModel'; import { allOfNodeChildMock, allOfNodeMock, - arrayNodeMock, combinationDefNodeChild1Mock, - combinationDefNodeMock, combinationNodeWithMultipleChildrenMock, defNodeMock, defNodeWithChildrenChildMock, defNodeWithChildrenGrandchildMock, defNodeWithChildrenMock, - enumNodeMock, nodeMockBase, nodeWithSameNameAsStringNodeMock, - numberNodeMock, - optionalNodeMock, parentNodeMock, - referenceDefinitionMock, referenceNodeMock, referenceToCombinationDefNodeMock, referenceToObjectNodeMock, - requiredNodeMock, rootNodeMock, simpleArrayMock, simpleChildNodeMock, @@ -30,18 +23,17 @@ import { subSubNodeMock, uiSchemaMock, unusedDefinitionMock, - unusedDefinitionWithSameNameAsExistingObjectMock, -} from '../../test/uiSchemaMock'; +} from '../../../test/uiSchemaMock'; import { expect } from '@jest/globals'; -import { validateTestUiSchema } from '../../test/validateTestUiSchema'; -import type { NodePosition, UiSchemaNodes } from '../types'; -import { CombinationKind, FieldType, ObjectKind } from '../types'; -import type { FieldNode } from '../types/FieldNode'; -import type { ReferenceNode } from '../types/ReferenceNode'; -import { extractNameFromPointer } from './pointerUtils'; -import { isArray, isDefinition } from './utils'; -import { ROOT_POINTER, UNIQUE_POINTER_PREFIX } from './constants'; -import type { CombinationNode } from '../types/CombinationNode'; +import { validateTestUiSchema } from '../../../test/validateTestUiSchema'; +import type { NodePosition, UiSchemaNodes } from '../../types'; +import { CombinationKind, FieldType, ObjectKind } from '../../types'; +import type { FieldNode } from '../../types/FieldNode'; +import type { ReferenceNode } from '../../types/ReferenceNode'; +import { extractNameFromPointer } from '../pointerUtils'; +import { isArray, isDefinition } from '../utils'; +import { ROOT_POINTER, UNIQUE_POINTER_PREFIX } from '../constants'; +import type { CombinationNode } from '../../types/CombinationNode'; import { ArrayUtils } from '@studio/pure-functions'; // Test data: @@ -88,33 +80,6 @@ describe('SchemaModel', () => { }); }); - describe('getRootNode', () => { - it('Returns the root node', () => { - expect(schemaModel.getRootNode()).toEqual(rootNodeMock); - }); - - it('Throws an error if the root node is not a field nor a combination node', () => { - const invalidRootNode = { ...referenceNodeMock, schemaPointer: ROOT_POINTER }; - const model = SchemaModel.fromArray([invalidRootNode]); - expect(() => model.getRootNode()).toThrowError(); - }); - }); - - describe('getNodeBySchemaPointer', () => { - it('Returns the node with the given pointer', () => { - expect(schemaModel.getNodeBySchemaPointer(parentNodeMock.schemaPointer)).toEqual( - parentNodeMock, - ); - expect(schemaModel.getNodeBySchemaPointer(defNodeMock.schemaPointer)).toEqual(defNodeMock); - expect(schemaModel.getNodeBySchemaPointer(allOfNodeMock.schemaPointer)).toEqual( - allOfNodeMock, - ); - expect(schemaModel.getNodeBySchemaPointer(stringNodeMock.schemaPointer)).toEqual( - stringNodeMock, - ); - }); - }); - describe('getNodeByUniquePointer', () => { it('Returns the node when unique pointer is the root', () => { const uniqueRootPointer = `${UNIQUE_POINTER_PREFIX}${rootNodeMock.schemaPointer}`; @@ -209,114 +174,6 @@ describe('SchemaModel', () => { }); }); - describe('hasNode', () => { - it('Returns true if the node with the given pointer exists', () => { - expect(schemaModel.hasNode(parentNodeMock.schemaPointer)).toBe(true); - expect(schemaModel.hasNode(defNodeMock.schemaPointer)).toBe(true); - expect(schemaModel.hasNode(allOfNodeMock.schemaPointer)).toBe(true); - expect(schemaModel.hasNode(stringNodeMock.schemaPointer)).toBe(true); - }); - - it('Returns false if the node with the given pointer does not exist', () => { - expect(schemaModel.hasNode('badPointer')).toBe(false); - }); - }); - - describe('hasDefinition', () => { - it('Returns true if the definition with the given name exists', () => { - expect(schemaModel.hasDefinition(extractNameFromPointer(defNodeMock.schemaPointer))).toBe( - true, - ); - expect( - schemaModel.hasDefinition(extractNameFromPointer(defNodeWithChildrenMock.schemaPointer)), - ).toBe(true); - }); - - it('Returns false if the definition with the given name does not exist', () => { - expect(schemaModel.hasDefinition('badName')).toBe(false); - }); - }); - - describe('getDefinitions', () => { - it('Returns all definition nodes', () => { - const result = schemaModel.getDefinitions(); - expect(result).toEqual([ - defNodeMock, - defNodeWithChildrenMock, - unusedDefinitionMock, - unusedDefinitionWithSameNameAsExistingObjectMock, - referenceDefinitionMock, - combinationDefNodeMock, - ]); - }); - }); - - describe('getRootProperties', () => { - it('Returns all root properties', () => { - const result = schemaModel.getRootProperties(); - expect(result).toEqual([ - parentNodeMock, - allOfNodeMock, - simpleParentNodeMock, - simpleArrayMock, - referenceToObjectNodeMock, - nodeWithSameNameAsStringNodeMock, - combinationNodeWithMultipleChildrenMock, - referenceToCombinationDefNodeMock, - ]); - }); - }); - - describe('getRootNodes', () => { - it('Returns all root nodes', () => { - const result = schemaModel.getRootChildren(); - expect(result).toEqual([ - parentNodeMock, - defNodeMock, - allOfNodeMock, - simpleParentNodeMock, - simpleArrayMock, - defNodeWithChildrenMock, - referenceToObjectNodeMock, - unusedDefinitionMock, - unusedDefinitionWithSameNameAsExistingObjectMock, - referenceDefinitionMock, - nodeWithSameNameAsStringNodeMock, - combinationNodeWithMultipleChildrenMock, - referenceToCombinationDefNodeMock, - combinationDefNodeMock, - ]); - }); - }); - - describe('getChildNodes', () => { - it('Returns all child nodes when the given node is an object', () => { - const result = schemaModel.getChildNodes(parentNodeMock.schemaPointer); - expect(result).toEqual([ - stringNodeMock, - numberNodeMock, - enumNodeMock, - arrayNodeMock, - optionalNodeMock, - requiredNodeMock, - referenceNodeMock, - subParentNodeMock, - ]); - }); - - it("Returns the referenced object's child nodes when the given node is a reference", () => { - const result = schemaModel.getChildNodes(referenceToObjectNodeMock.schemaPointer); - expect(result).toEqual([defNodeWithChildrenChildMock]); - }); - }); - - describe('getReferredNode', () => { - it('Returns the referred node', () => { - const result = schemaModel.getReferredNode(referenceNodeMock); - expect(result).toEqual(defNodeMock); - }); - }); - describe('areDefinitionParentsInUse', () => { it('Returns false if definition parent not in use', () => { const result = schemaModel.areDefinitionParentsInUse(unusedDefinitionMock.schemaPointer); @@ -331,54 +188,6 @@ describe('SchemaModel', () => { }); }); - describe('getFinalNode', () => { - it('Returns the node itself when it is not a reference', () => { - const result = schemaModel.getFinalNode(parentNodeMock.schemaPointer); - expect(result).toEqual(parentNodeMock); - }); - - it('Returns the referred node when the given node is a reference to a field node', () => { - const result = schemaModel.getFinalNode(referenceNodeMock.schemaPointer); - expect(result).toEqual(defNodeMock); - }); - - it('Returns the node referred by the referred node when the given node is a reference to a reference to a field node', () => { - const result = schemaModel.getFinalNode(referenceDefinitionMock.schemaPointer); - expect(result).toEqual(defNodeMock); - }); - }); - - describe('doesNodeHaveChildWithName', () => { - it('Returns true when the given node has a child with the given name', () => { - const result = schemaModel.doesNodeHaveChildWithName( - parentNodeMock.schemaPointer, - 'stringNode', - ); - expect(result).toBe(true); - }); - - it('Returns true when the node referred by the given node has a child with the given name', () => { - const result = schemaModel.doesNodeHaveChildWithName( - referenceToObjectNodeMock.schemaPointer, - 'child', - ); - expect(result).toBe(true); - }); - - it('Returns false when the given node does not have a child with the given name', () => { - const result = schemaModel.doesNodeHaveChildWithName(parentNodeMock.schemaPointer, 'badName'); - expect(result).toBe(false); - }); - - it('Returns false when the node referred by the given node does not have a child with the given name', () => { - const result = schemaModel.doesNodeHaveChildWithName( - referenceToObjectNodeMock.schemaPointer, - 'badName', - ); - expect(result).toBe(false); - }); - }); - describe('addCombination', () => { const parentPointer = parentNodeMock.schemaPointer; const index = 2; diff --git a/frontend/packages/schema-model/src/lib/SchemaModel.ts b/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.ts similarity index 82% rename from frontend/packages/schema-model/src/lib/SchemaModel.ts rename to frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.ts index 56a858d912a..1dce7abaa68 100644 --- a/frontend/packages/schema-model/src/lib/SchemaModel.ts +++ b/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.ts @@ -4,27 +4,26 @@ import { type NodePosition, type UiSchemaNode, type UiSchemaNodes, -} from '../types'; -import type { FieldNode } from '../types/FieldNode'; -import type { CombinationNode } from '../types/CombinationNode'; -import type { NodeMap } from '../types/NodeMap'; +} from '../../types'; +import type { FieldNode } from '../../types/FieldNode'; +import type { CombinationNode } from '../../types/CombinationNode'; +import type { NodeMap } from '../../types/NodeMap'; import { isCombination, isDefinition, isDefinitionPointer, isFieldOrCombination, isNodeValidParent, - isProperty, isReference, -} from './utils'; +} from '../utils'; import { generateUniqueStringWithNumber, insertArrayElementAtPos, moveArrayItem, replaceItemsByValue, } from 'app-shared/utils/arrayUtils'; -import { ROOT_POINTER, UNIQUE_POINTER_PREFIX } from './constants'; -import type { ReferenceNode } from '../types/ReferenceNode'; +import { ROOT_POINTER, UNIQUE_POINTER_PREFIX } from '../constants'; +import type { ReferenceNode } from '../../types/ReferenceNode'; import { ObjectUtils, ArrayUtils, StringUtils } from '@studio/pure-functions'; import { replaceStart } from 'app-shared/utils/stringUtils'; import { @@ -33,19 +32,18 @@ import { extractCategoryFromPointer, extractNameFromPointer, makePointerFromArray, -} from './pointerUtils'; +} from '../pointerUtils'; import { defaultCombinationNode, defaultFieldNode, defaultReferenceNode, -} from '../config/default-nodes'; -import { convertPropToType } from './mutations/convert-node'; - -export class SchemaModel { - private readonly nodeMap: NodeMap; +} from '../../config/default-nodes'; +import { convertPropToType } from '../mutations/convert-node'; +import { SchemaModelBase } from './SchemaModelBase'; +export class SchemaModel extends SchemaModelBase { constructor(nodes: NodeMap) { - this.nodeMap = nodes; + super(nodes); } public static fromArray(nodes: UiSchemaNodes): SchemaModel { @@ -53,10 +51,6 @@ export class SchemaModel { return new SchemaModel(map); } - public getNodeMap(): NodeMap { - return this.nodeMap; - } - public deepClone(): SchemaModel { const nodes = ObjectUtils.deepCopy(this.asArray()); return SchemaModel.fromArray(nodes); @@ -66,19 +60,6 @@ export class SchemaModel { return Array.from(this.nodeMap.values()); } - public getRootNode(): FieldNode | CombinationNode { - const rootNode = this.getNodeBySchemaPointer(ROOT_POINTER); - if (!isFieldOrCombination(rootNode)) - throw new Error('Root node is not a field nor a combination.'); - return rootNode; - } - - public getNodeBySchemaPointer(schemaPointer: string): UiSchemaNode { - if (!this.hasNode(schemaPointer)) - throw new Error(`Node with pointer ${schemaPointer} not found.`); - return this.nodeMap.get(schemaPointer); - } - public getNodeByUniquePointer(uniquePointer: string): UiSchemaNode { const schemaPointer = this.getSchemaPointerByUniquePointer(uniquePointer); return this.getNodeBySchemaPointer(schemaPointer); @@ -120,61 +101,6 @@ export class SchemaModel { return `${UNIQUE_POINTER_PREFIX}${parentPointer}/${category}/${extractNameFromPointer(schemaPointer)}`; } - public hasNode(schemaPointer: string): boolean { - return this.nodeMap.has(schemaPointer); - } - - public hasDefinition(name: string): boolean { - const schemaPointer = createDefinitionPointer(name); - return this.hasNode(schemaPointer); - } - - public getDefinition(name: string): UiSchemaNode { - const schemaPointer = createDefinitionPointer(name); - return this.getNodeBySchemaPointer(schemaPointer); - } - - public getDefinitions(): UiSchemaNodes { - return this.getRootChildren().filter(isDefinition); - } - - public getRootProperties(): UiSchemaNodes { - return this.getRootChildren().filter(isProperty); - } - - public getRootChildren(): UiSchemaNodes { - return this.getChildNodes(ROOT_POINTER); - } - - public getChildNodes(schemaPointer: string): UiSchemaNodes { - const node = this.getFinalNode(schemaPointer); - return this.getDirectChildNodes(node); - } - - private getDirectChildNodes(node: FieldNode | CombinationNode): UiSchemaNodes { - return node.children.map((childPointer) => this.getNodeBySchemaPointer(childPointer)); - } - - public getReferredNode(node: ReferenceNode): UiSchemaNode { - return this.getNodeBySchemaPointer(node.reference); - } - - /** Returns the node that the given node refers to, or the given node if it is not a reference. */ - public getFinalNode(schemaPointer: string): FieldNode | CombinationNode { - const node = this.getNodeBySchemaPointer(schemaPointer); - return isReference(node) ? this.getFinalNode(node.reference) : node; - } - - public getIndexOfChildNode(schemaPointer: string): number { - const parent = this.getParentNode(schemaPointer); - return parent.children.indexOf(schemaPointer); - } - - public doesNodeHaveChildWithName(nodePointer: string, name: string): boolean { - const children = this.getChildNodes(nodePointer); - return children.some((child) => extractNameFromPointer(child.schemaPointer) === name); - } - public addCombination = ( name?: string, target: NodePosition = defaultNodePosition, @@ -404,12 +330,6 @@ export class SchemaModel { parent.children = ArrayUtils.removeItemByValue(parent.children, schemaPointer); }; - public getParentNode(schemaPointer: string): FieldNode | CombinationNode | undefined { - const isParent = (node: UiSchemaNode): node is FieldNode | CombinationNode => - isFieldOrCombination(node) && node.children.includes(schemaPointer); - return this.find(isParent); - } - public updateNode(schemaPointer: string, newNode: UiSchemaNode): SchemaModel { this.updateNodeData(schemaPointer, newNode); if (schemaPointer !== newNode.schemaPointer) { @@ -444,16 +364,6 @@ export class SchemaModel { } } - private find( - predicate: (node: UiSchemaNode) => node is T, - ): T | undefined { - for (const node of this.nodeMap.values()) { - if (predicate(node)) { - return node; - } - } - } - private changePointerInReferences(oldPointer: string, newPointer: string): void { const referringNodes = this.getReferringNodes(oldPointer); referringNodes.forEach((node) => @@ -461,17 +371,6 @@ export class SchemaModel { ); } - private getReferringNodes(schemaPointer: string): ReferenceNode[] { - const referringNodes: ReferenceNode[] = []; - for (const node of this.nodeMap.values()) { - if (isReference(node) && node.reference === schemaPointer) { - referringNodes.push(node); - } - } - - return referringNodes; - } - private changePointerInChildren(oldPointer: string, newPointer: string): void { const node = this.getNodeBySchemaPointer(newPointer); // Expects the node map to be updated if (isFieldOrCombination(node) && node.children) { diff --git a/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModelBase.test.ts b/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModelBase.test.ts new file mode 100644 index 00000000000..0f82753d48f --- /dev/null +++ b/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModelBase.test.ts @@ -0,0 +1,229 @@ +import { + allOfNodeMock, + arrayNodeMock, + combinationDefNodeMock, + combinationNodeWithMultipleChildrenMock, + defNodeMock, + defNodeWithChildrenChildMock, + defNodeWithChildrenMock, + enumNodeMock, + nodeWithSameNameAsStringNodeMock, + numberNodeMock, + optionalNodeMock, + parentNodeMock, + referenceDefinitionMock, + referenceNodeMock, + referenceToCombinationDefNodeMock, + referenceToObjectNodeMock, + requiredNodeMock, + rootNodeMock, + simpleArrayMock, + simpleParentNodeMock, + stringNodeMock, + subParentNodeMock, + uiSchemaMock, + unusedDefinitionMock, + unusedDefinitionWithSameNameAsExistingObjectMock, +} from '../../../test/uiSchemaMock'; +import { expect } from '@jest/globals'; +import { extractNameFromPointer } from '../pointerUtils'; +import { ROOT_POINTER } from '../constants'; +import { SchemaModel } from './SchemaModel'; +import { SchemaModelBase } from './SchemaModelBase'; + +// Test data: +const schemaModel = SchemaModel.fromArray(uiSchemaMock); +const schemaModelBase = new SchemaModelBase(schemaModel.getNodeMap()); + +describe('SchemaModel', () => { + describe('getRootNode', () => { + it('Returns the root node', () => { + expect(schemaModelBase.getRootNode()).toEqual(rootNodeMock); + }); + + it('Throws an error if the root node is not a field nor a combination node', () => { + const invalidRootNode = { ...referenceNodeMock, schemaPointer: ROOT_POINTER }; + const model = SchemaModel.fromArray([invalidRootNode]); + const modelBase = new SchemaModelBase(model.getNodeMap()); + expect(() => modelBase.getRootNode()).toThrowError(); + }); + }); + + describe('getNodeBySchemaPointer', () => { + it('Returns the node with the given pointer', () => { + expect(schemaModelBase.getNodeBySchemaPointer(parentNodeMock.schemaPointer)).toEqual( + parentNodeMock, + ); + expect(schemaModelBase.getNodeBySchemaPointer(defNodeMock.schemaPointer)).toEqual( + defNodeMock, + ); + expect(schemaModelBase.getNodeBySchemaPointer(allOfNodeMock.schemaPointer)).toEqual( + allOfNodeMock, + ); + expect(schemaModelBase.getNodeBySchemaPointer(stringNodeMock.schemaPointer)).toEqual( + stringNodeMock, + ); + }); + }); + + describe('hasNode', () => { + it('Returns true if the node with the given pointer exists', () => { + expect(schemaModelBase.hasNode(parentNodeMock.schemaPointer)).toBe(true); + expect(schemaModelBase.hasNode(defNodeMock.schemaPointer)).toBe(true); + expect(schemaModelBase.hasNode(allOfNodeMock.schemaPointer)).toBe(true); + expect(schemaModelBase.hasNode(stringNodeMock.schemaPointer)).toBe(true); + }); + + it('Returns false if the node with the given pointer does not exist', () => { + expect(schemaModelBase.hasNode('badPointer')).toBe(false); + }); + }); + + describe('hasDefinition', () => { + it('Returns true if the definition with the given name exists', () => { + expect(schemaModelBase.hasDefinition(extractNameFromPointer(defNodeMock.schemaPointer))).toBe( + true, + ); + expect( + schemaModelBase.hasDefinition( + extractNameFromPointer(defNodeWithChildrenMock.schemaPointer), + ), + ).toBe(true); + }); + + it('Returns false if the definition with the given name does not exist', () => { + expect(schemaModelBase.hasDefinition('badName')).toBe(false); + }); + }); + + describe('getDefinitions', () => { + it('Returns all definition nodes', () => { + const result = schemaModelBase.getDefinitions(); + expect(result).toEqual([ + defNodeMock, + defNodeWithChildrenMock, + unusedDefinitionMock, + unusedDefinitionWithSameNameAsExistingObjectMock, + referenceDefinitionMock, + combinationDefNodeMock, + ]); + }); + }); + + describe('getRootProperties', () => { + it('Returns all root properties', () => { + const result = schemaModelBase.getRootProperties(); + expect(result).toEqual([ + parentNodeMock, + allOfNodeMock, + simpleParentNodeMock, + simpleArrayMock, + referenceToObjectNodeMock, + nodeWithSameNameAsStringNodeMock, + combinationNodeWithMultipleChildrenMock, + referenceToCombinationDefNodeMock, + ]); + }); + }); + + describe('getRootNodes', () => { + it('Returns all root nodes', () => { + const result = schemaModelBase.getRootChildren(); + expect(result).toEqual([ + parentNodeMock, + defNodeMock, + allOfNodeMock, + simpleParentNodeMock, + simpleArrayMock, + defNodeWithChildrenMock, + referenceToObjectNodeMock, + unusedDefinitionMock, + unusedDefinitionWithSameNameAsExistingObjectMock, + referenceDefinitionMock, + nodeWithSameNameAsStringNodeMock, + combinationNodeWithMultipleChildrenMock, + referenceToCombinationDefNodeMock, + combinationDefNodeMock, + ]); + }); + }); + + describe('getChildNodes', () => { + it('Returns all child nodes when the given node is an object', () => { + const result = schemaModelBase.getChildNodes(parentNodeMock.schemaPointer); + expect(result).toEqual([ + stringNodeMock, + numberNodeMock, + enumNodeMock, + arrayNodeMock, + optionalNodeMock, + requiredNodeMock, + referenceNodeMock, + subParentNodeMock, + ]); + }); + + it("Returns the referenced object's child nodes when the given node is a reference", () => { + const result = schemaModelBase.getChildNodes(referenceToObjectNodeMock.schemaPointer); + expect(result).toEqual([defNodeWithChildrenChildMock]); + }); + }); + + describe('getReferredNode', () => { + it('Returns the referred node', () => { + const result = schemaModelBase.getReferredNode(referenceNodeMock); + expect(result).toEqual(defNodeMock); + }); + }); + + describe('getFinalNode', () => { + it('Returns the node itself when it is not a reference', () => { + const result = schemaModelBase.getFinalNode(parentNodeMock.schemaPointer); + expect(result).toEqual(parentNodeMock); + }); + + it('Returns the referred node when the given node is a reference to a field node', () => { + const result = schemaModelBase.getFinalNode(referenceNodeMock.schemaPointer); + expect(result).toEqual(defNodeMock); + }); + + it('Returns the node referred by the referred node when the given node is a reference to a reference to a field node', () => { + const result = schemaModelBase.getFinalNode(referenceDefinitionMock.schemaPointer); + expect(result).toEqual(defNodeMock); + }); + }); + + describe('doesNodeHaveChildWithName', () => { + it('Returns true when the given node has a child with the given name', () => { + const result = schemaModelBase.doesNodeHaveChildWithName( + parentNodeMock.schemaPointer, + 'stringNode', + ); + expect(result).toBe(true); + }); + + it('Returns true when the node referred by the given node has a child with the given name', () => { + const result = schemaModelBase.doesNodeHaveChildWithName( + referenceToObjectNodeMock.schemaPointer, + 'child', + ); + expect(result).toBe(true); + }); + + it('Returns false when the given node does not have a child with the given name', () => { + const result = schemaModelBase.doesNodeHaveChildWithName( + parentNodeMock.schemaPointer, + 'badName', + ); + expect(result).toBe(false); + }); + + it('Returns false when the node referred by the given node does not have a child with the given name', () => { + const result = schemaModelBase.doesNodeHaveChildWithName( + referenceToObjectNodeMock.schemaPointer, + 'badName', + ); + expect(result).toBe(false); + }); + }); +}); diff --git a/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModelBase.ts b/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModelBase.ts new file mode 100644 index 00000000000..575e1f3ab98 --- /dev/null +++ b/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModelBase.ts @@ -0,0 +1,120 @@ +import { type UiSchemaNode, type UiSchemaNodes } from '../../types'; +import type { FieldNode } from '../../types/FieldNode'; +import type { CombinationNode } from '../../types/CombinationNode'; +import type { NodeMap } from '../../types/NodeMap'; +import { isDefinition, isFieldOrCombination, isProperty, isReference } from '../utils'; +import { ROOT_POINTER } from '../constants'; +import type { ReferenceNode } from '../../types/ReferenceNode'; +import { createDefinitionPointer, extractNameFromPointer } from '../pointerUtils'; + +export class SchemaModelBase { + protected readonly nodeMap: NodeMap; + + constructor(nodes: NodeMap) { + this.nodeMap = nodes; + } + + public getNodeMap(): NodeMap { + return this.nodeMap; + } + + public getRootNode(): FieldNode | CombinationNode { + const rootNode = this.getNodeBySchemaPointer(ROOT_POINTER); + if (!isFieldOrCombination(rootNode)) + throw new Error('Root node is not a field nor a combination.'); + return rootNode; + } + + public getNodeBySchemaPointer(schemaPointer: string): UiSchemaNode { + if (!this.hasNode(schemaPointer)) + throw new Error(`Node with pointer ${schemaPointer} not found.`); + return this.nodeMap.get(schemaPointer); + } + + public hasNode(schemaPointer: string): boolean { + return this.nodeMap.has(schemaPointer); + } + + public hasDefinition(name: string): boolean { + const schemaPointer = createDefinitionPointer(name); + return this.hasNode(schemaPointer); + } + + public getDefinition(name: string): UiSchemaNode { + const schemaPointer = createDefinitionPointer(name); + return this.getNodeBySchemaPointer(schemaPointer); + } + + public getDefinitions(): UiSchemaNodes { + return this.getRootChildren().filter(isDefinition); + } + + public getRootProperties(): UiSchemaNodes { + return this.getRootChildren().filter(isProperty); + } + + public getRootChildren(): UiSchemaNodes { + return this.getChildNodes(ROOT_POINTER); + } + + public getChildNodes(schemaPointer: string): UiSchemaNodes { + const node = this.getFinalNode(schemaPointer); + return this.getDirectChildNodes(node); + } + + private getDirectChildNodes(node: FieldNode | CombinationNode): UiSchemaNodes { + return node.children.map((childPointer) => this.getNodeBySchemaPointer(childPointer)); + } + + public getReferredNode(node: ReferenceNode): UiSchemaNode { + return this.getNodeBySchemaPointer(node.reference); + } + + /** Returns the node that the given node refers to, or the given node if it is not a reference. */ + public getFinalNode(schemaPointer: string): FieldNode | CombinationNode { + const node = this.getNodeBySchemaPointer(schemaPointer); + return isReference(node) ? this.getFinalNode(node.reference) : node; + } + + public getIndexOfChildNode(schemaPointer: string): number { + const parent = this.getParentNode(schemaPointer); + return parent.children.indexOf(schemaPointer); + } + + public doesNodeHaveChildWithName(nodePointer: string, name: string): boolean { + const children = this.getChildNodes(nodePointer); + return children.some((child) => extractNameFromPointer(child.schemaPointer) === name); + } + + public getParentNode(schemaPointer: string): FieldNode | CombinationNode | undefined { + const isParent = (node: UiSchemaNode): node is FieldNode | CombinationNode => + isFieldOrCombination(node) && node.children.includes(schemaPointer); + return this.find(isParent); + } + + private find( + predicate: (node: UiSchemaNode) => node is T, + ): T | undefined { + for (const node of this.nodeMap.values()) { + if (predicate(node)) { + return node; + } + } + } + + protected getReferringNodes(schemaPointer: string): ReferenceNode[] { + const referringNodes: ReferenceNode[] = []; + for (const node of this.nodeMap.values()) { + if (isReference(node) && node.reference === schemaPointer) { + referringNodes.push(node); + } + } + + return referringNodes; + } + + public hasReferringNodes(schemaPointer: string): boolean { + const referringNodes = this.getReferringNodes(schemaPointer); + return !!referringNodes.length; + } +} diff --git a/frontend/packages/schema-model/src/lib/SchemaModel/index.ts b/frontend/packages/schema-model/src/lib/SchemaModel/index.ts new file mode 100644 index 00000000000..a59a59b4e90 --- /dev/null +++ b/frontend/packages/schema-model/src/lib/SchemaModel/index.ts @@ -0,0 +1 @@ +export { SchemaModel } from './SchemaModel'; From 332e0a568e5d0e2fa9d249736d77290a3af27a51 Mon Sep 17 00:00:00 2001 From: Tomas Date: Thu, 17 Oct 2024 13:24:17 +0200 Subject: [PATCH 2/2] Remove duplicated method --- .../packages/schema-model/src/lib/SchemaModel/SchemaModel.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.ts b/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.ts index 1dce7abaa68..85263007fe8 100644 --- a/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.ts +++ b/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.ts @@ -405,11 +405,6 @@ export class SchemaModel extends SchemaModelBase { return this.hasReferringNodes(schemaPointer) || this.areDefinitionParentsInUse(schemaPointer); } - public hasReferringNodes(schemaPointer: string): boolean { - const referringNodes = this.getReferringNodes(schemaPointer); - return !!referringNodes.length; - } - public areDefinitionParentsInUse(schemaPointer: string): boolean { const parent = this.getParentNode(schemaPointer); return this.isDefinitionInUse(parent.schemaPointer);