diff --git a/src/features/application-menu/main/menu-items/get-composite/get-composite.test.ts b/src/features/application-menu/main/menu-items/get-composite/get-composite.test.ts index ac11602a60..b35a9ac4d2 100644 --- a/src/features/application-menu/main/menu-items/get-composite/get-composite.test.ts +++ b/src/features/application-menu/main/menu-items/get-composite/get-composite.test.ts @@ -4,6 +4,7 @@ */ import { sortBy } from "lodash/fp"; +import type { Composite } from "./get-composite"; import getComposite from "./get-composite"; import { getCompositePaths } from "./get-composite-paths/get-composite-paths"; @@ -165,7 +166,7 @@ describe("get-composite", () => { ); }); - it("given missing parent ids, throws", () => { + it("given items with missing parent ids, when creating composite without handling for unknown parents, throws", () => { const someItem = { someId: "some-id", someParentId: undefined, @@ -186,10 +187,54 @@ describe("get-composite", () => { getParentId: (x) => x.someParentId, }); }).toThrow( - 'Tried to get a composite but encountered missing parent ids: "some-missing-id"', + `Tried to get a composite but encountered missing parent ids: "some-missing-id". + +Available parent ids are: +"some-id", +"some-other-id"`, ); }); + describe("given items with missing parent ids, when creating composite with handling for missing parents", () => { + let composite: Composite; + let handleMissingParentIdMock: jest.Mock; + + beforeEach(() => { + const someItem = { + id: "some-root-id", + parentId: undefined, + }; + + const someItemWithMissingParentId = { + id: "some-orphan-id", + // Note: missing parent id makes the item orphan. + parentId: "some-missing-id", + }; + + const items = [someItem, someItemWithMissingParentId]; + + handleMissingParentIdMock = jest.fn(); + + composite = getComposite({ + source: items, + handleMissingParentIds: handleMissingParentIdMock, + }); + }); + + it("creates composite without the orphan item, and without throwing", () => { + const paths = getCompositePaths(composite); + + expect(paths).toEqual(["some-root-id"]); + }); + + it("handles the missing parent ids", () => { + expect(handleMissingParentIdMock).toHaveBeenCalledWith({ + missingParentIds: ["some-missing-id"], + availableParentIds: ["some-root-id", "some-orphan-id"], + }); + }); + }); + it("given undefined ids, throws", () => { const root = { someParentId: undefined, diff --git a/src/features/application-menu/main/menu-items/get-composite/get-composite.ts b/src/features/application-menu/main/menu-items/get-composite/get-composite.ts index 1c188e7951..e24e7656ec 100644 --- a/src/features/application-menu/main/menu-items/get-composite/get-composite.ts +++ b/src/features/application-menu/main/menu-items/get-composite/get-composite.ts @@ -30,12 +30,14 @@ export default ({ getId = get("id"), getParentId = get("parentId"), getOrderedChildren = (things: T[]) => sortBy("orderNumber", things), + handleMissingParentIds = throwMissingParentIds, }: { source: T[]; rootId?: string; getId?: (thing: T) => string; getParentId?: (thing: T) => string | undefined; getOrderedChildren?: (things: T[]) => T[]; + handleMissingParentIds?: (parentIdsForHandling: ParentIdsForHandling) => void; }) => { const undefinedIds = pipeline( source, @@ -68,14 +70,10 @@ export default ({ const allParentIds = pipeline(source, map(getParentId), uniq, compact); - const unknownParentIds = without(allIds, allParentIds); + const missingParentIds = without(allIds, allParentIds); - if (unknownParentIds.length) { - throw new Error( - `Tried to get a composite but encountered missing parent ids: "${unknownParentIds - .map((x) => String(x)) - .join('", "')}"`, - ); + if (missingParentIds.length) { + handleMissingParentIds({ missingParentIds, availableParentIds: allIds }); } const toComposite = (thing: T): Composite => { @@ -118,3 +116,19 @@ export default ({ return toComposite(roots[0]); }; + +interface ParentIdsForHandling { + missingParentIds: string[]; + availableParentIds: string[]; +} + +const throwMissingParentIds = ({ + missingParentIds, + availableParentIds, +}: ParentIdsForHandling) => { + throw new Error( + `Tried to get a composite but encountered missing parent ids: "${missingParentIds.join( + '", "', + )}".\n\nAvailable parent ids are:\n"${availableParentIds.join('",\n"')}"`, + ); +};