diff --git a/src/common/utils/composite/find-composite/find-composite.test.ts b/src/common/utils/composite/find-composite/find-composite.test.ts index 510c075f53..10ba1c6111 100644 --- a/src/common/utils/composite/find-composite/find-composite.test.ts +++ b/src/common/utils/composite/find-composite/find-composite.test.ts @@ -7,15 +7,15 @@ import getComposite from "../get-composite/get-composite"; import { findComposite } from "./find-composite"; describe("find-composite", () => { - let composite: Composite<{ id: string; parentId?: string; someProperty: string }>; + let composite: Composite<{ id: string; parentId?: string }>; beforeEach(() => { composite = getComposite({ source: [ - { id: "some-root-id", someProperty: "some-value" }, - { id: "some-child-id", parentId: "some-root-id", someProperty: "some-value" }, - { id: "some-irrelevant-grandchild-id", parentId: "some-child-id", someProperty: "some-value" }, - { id: "some-grandchild-id", parentId: "some-child-id", someProperty: "some-value" }, + { id: "some-root-id" }, + { id: "some-child-id", parentId: "some-root-id" }, + { id: "some-grandchild-id", parentId: "some-child-id" }, + { id: "some-other-grandchild-id", parentId: "some-child-id" }, ], rootId: "some-root-id", @@ -25,28 +25,62 @@ describe("find-composite", () => { it("when finding root using path, does so", () => { const actual = findComposite("some-root-id")(composite); - expect(actual?.id).toBe("some-root-id"); + expect(actual.id).toBe("some-root-id"); }); it("when finding child using path, does so", () => { - const actual = findComposite("some-root-id.some-child-id")(composite); + const actual = findComposite("some-root-id", "some-child-id")(composite); - expect(actual?.id).toBe("some-child-id"); + expect(actual.id).toBe("some-child-id"); }); it("when finding grandchild using path, does so", () => { const actual = findComposite( - "some-root-id.some-child-id.some-grandchild-id", + "some-root-id", + "some-child-id", + "some-grandchild-id", )(composite); - expect(actual?.id).toBe("some-grandchild-id"); + expect(actual.id).toBe("some-grandchild-id"); }); - it("when finding with non existing path, returns undefined", () => { - const actual = findComposite("some-root-id.some-non-existing-path")( - composite, - ); + it("when finding with non existing leaf-level path, throws", () => { + expect(() => { + findComposite( + "some-root-id", + "some-child-id", + "some-non-existing-grandchild-id", + )(composite); + }).toThrow(`Tried to find 'some-root-id -> some-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing. - expect(actual).toBe(undefined); +Node 'some-root-id -> some-child-id' had only following children: +some-grandchild-id +some-other-grandchild-id`); + }); + + it("when finding with non-existing mid-level path, throws", () => { + expect(() => { + findComposite( + "some-root-id", + "some-non-existing-child-id", + "some-non-existing-grandchild-id", + )(composite); + }).toThrow(`Tried to find 'some-root-id -> some-non-existing-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing. + +Node 'some-root-id' had only following children: +some-child-id`); + }); + + it("when finding with non-existing root-level path, throws", () => { + expect(() => { + findComposite( + "some-non-existing-root-id", + "some-non-existing-child-id", + "some-non-existing-grandchild-id", + )(composite); + }).toThrow(`Tried to find 'some-non-existing-root-id -> some-non-existing-child-id -> some-non-existing-grandchild-id' from a composite, but found nothing. + +Node 'some-root-id' had only following children: +some-child-id`); }); }); diff --git a/src/common/utils/composite/find-composite/find-composite.ts b/src/common/utils/composite/find-composite/find-composite.ts index 81ec5c6e74..71dcf5baa3 100644 --- a/src/common/utils/composite/find-composite/find-composite.ts +++ b/src/common/utils/composite/find-composite/find-composite.ts @@ -3,9 +3,34 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import type { Composite } from "../get-composite/get-composite"; -import { getCompositeNormalization } from "../get-composite-normalization/get-composite-normalization"; + +const _findComposite = (currentLeftIds: string[], currentId: string, currentRightIds: string[], composite: Composite): Composite => { + const [nextId, ...nextRightIds] = currentRightIds; + const nextLeftIds = [...currentLeftIds, currentId]; + + if (currentRightIds.length === 0 && composite.id === currentId) { + return composite; + } + + const foundChildComposite = composite.children.find((child) => child.id === nextId); + + if (foundChildComposite) { + return _findComposite(nextLeftIds, nextId, nextRightIds, foundChildComposite); + } + + const fullPathString = [...currentLeftIds, currentId, ...currentRightIds].join(" -> "); + + throw new Error(`Tried to find '${fullPathString}' from a composite, but found nothing. + +Node '${[...currentLeftIds, composite.id].join(" -> ")}' had only following children: +${composite.children.map((child) => child.id).join("\n")}`); +}; export const findComposite = - (path: string) => - (composite: Composite): Composite | undefined => - new Map(getCompositeNormalization(composite)).get(path); + (...path: string[]) => + (composite: Composite): Composite => { + const [currentId, ...rightIds] = path; + const leftIds: string[] = []; + + return _findComposite(leftIds, currentId, rightIds, composite); + }; diff --git a/src/common/utils/composite/get-composite-normalization/get-composite-normalization.test.ts b/src/common/utils/composite/get-composite-normalization/get-composite-normalization.test.ts index 34a1eebad3..b4dbe8032e 100644 --- a/src/common/utils/composite/get-composite-normalization/get-composite-normalization.test.ts +++ b/src/common/utils/composite/get-composite-normalization/get-composite-normalization.test.ts @@ -6,7 +6,7 @@ import { getCompositeNormalization } from "./get-composite-normalization"; import getComposite from "../get-composite/get-composite"; describe("get-composite-normalization", () => { - it("given a composite, flattens it to path and composite", () => { + it("given a composite, flattens it to paths and composites", () => { const someRootItem = { id: "some-root-id", parentId: undefined, @@ -31,12 +31,12 @@ describe("get-composite-normalization", () => { const actual = getCompositeNormalization(composite); expect(actual).toEqual([ - ["some-root-id", expect.objectContaining({ value: someRootItem })], + [["some-root-id"], expect.objectContaining({ value: someRootItem })], - ["some-root-id.some-id", expect.objectContaining({ value: someItem })], + [["some-root-id", "some-id"], expect.objectContaining({ value: someItem })], [ - "some-root-id.some-id.some-child-id", + ["some-root-id", "some-id", "some-child-id"], expect.objectContaining({ value: someNestedItem }), ], ]); diff --git a/src/common/utils/composite/get-composite-normalization/get-composite-normalization.ts b/src/common/utils/composite/get-composite-normalization/get-composite-normalization.ts index a5d192c731..eb5628171a 100644 --- a/src/common/utils/composite/get-composite-normalization/get-composite-normalization.ts +++ b/src/common/utils/composite/get-composite-normalization/get-composite-normalization.ts @@ -8,15 +8,17 @@ export const getCompositeNormalization = (composite: Composite) => { const _normalizeComposite = ( composite: Composite, previousPath: string[] = [], - ): (readonly [path: string, composite: Composite])[] => { + ): (readonly [path: string[], composite: Composite])[] => { const currentPath = [...previousPath, composite.id]; - const pathAndCompositeTuple = [currentPath.join("."), composite] as const; + const pathAndCompositeTuple = [currentPath, composite] as const; return [ pathAndCompositeTuple, - ...composite.children.flatMap((x) => _normalizeComposite(x, currentPath)), + ...composite.children.flatMap((child) => + _normalizeComposite(child, currentPath), + ), ]; }; diff --git a/src/common/utils/composite/get-composite-paths/get-composite-paths.test.ts b/src/common/utils/composite/get-composite-paths/get-composite-paths.test.ts index d4e1b9e13c..ee2e48270d 100644 --- a/src/common/utils/composite/get-composite-paths/get-composite-paths.test.ts +++ b/src/common/utils/composite/get-composite-paths/get-composite-paths.test.ts @@ -11,37 +11,37 @@ describe("get-composite-paths", () => { id: "some-root-id", }; - const someItem1 = { - id: "some-id-1", - parentId: "some-root-id", - orderNumber: 1, - }; - - const someItem2 = { - id: "some-id-2", - parentId: "some-root-id", - orderNumber: 2, - }; - const someChildItem1 = { id: "some-child-id-1", - parentId: "some-id-1", + parentId: "some-root-id", orderNumber: 1, }; const someChildItem2 = { id: "some-child-id-2", - parentId: "some-id-1", + parentId: "some-root-id", + orderNumber: 2, + }; + + const someGrandchildItem1 = { + id: "some-grandchild-id-1", + parentId: "some-child-id-1", + orderNumber: 1, + }; + + const someGrandchildItem2 = { + id: "some-grandchild-id-2", + parentId: "some-child-id-1", orderNumber: 2, }; const items = [ someRootItem, // Note: not in order yet. - someItem2, - someItem1, someChildItem2, someChildItem1, + someGrandchildItem2, + someGrandchildItem1, ]; const composite = getComposite({ @@ -52,10 +52,10 @@ describe("get-composite-paths", () => { expect(actual).toEqual([ "some-root-id", - "some-root-id.some-id-1", - "some-root-id.some-id-1.some-child-id-1", - "some-root-id.some-id-1.some-child-id-2", - "some-root-id.some-id-2", + "some-root-id -> some-child-id-1", + "some-root-id -> some-child-id-1 -> some-grandchild-id-1", + "some-root-id -> some-child-id-1 -> some-grandchild-id-2", + "some-root-id -> some-child-id-2", ]); }); }); diff --git a/src/common/utils/composite/get-composite-paths/get-composite-paths.ts b/src/common/utils/composite/get-composite-paths/get-composite-paths.ts index 1de672d6e1..2739d912f2 100644 --- a/src/common/utils/composite/get-composite-paths/get-composite-paths.ts +++ b/src/common/utils/composite/get-composite-paths/get-composite-paths.ts @@ -9,4 +9,4 @@ import { getCompositeNormalization } from "../get-composite-normalization/get-co export const getCompositePaths = ( composite: Composite, -): string[] => pipeline(composite, getCompositeNormalization, map(([path]) => path)); +): string[] => pipeline(composite, getCompositeNormalization, map(([path]) => path.join(" -> ")));