1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Make composite not know about how children are transformed

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>

Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
Iku-turso 2022-10-24 11:28:32 +03:00
parent c6782954b1
commit 11d2023c70
2 changed files with 137 additions and 203 deletions

View File

@ -3,39 +3,43 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { sortBy } from "lodash/fp";
import type { Composite } from "./get-composite"; import type { Composite } from "./get-composite";
import getComposite from "./get-composite"; import getCompositeFor from "./get-composite";
import { getCompositePaths } from "../get-composite-paths/get-composite-paths"; import { getCompositePaths } from "../get-composite-paths/get-composite-paths";
import { sortBy } from "lodash/fp";
describe("get-composite", () => { describe("get-composite", () => {
it("given items and a specified root id, creates a composite", () => { it("given items and a specified root id, creates a composite", () => {
const getComposite = getCompositeFor<{
id: string;
parentId: string | undefined;
}>({
rootId: "some-root-id",
getId: (x) => x.id,
getParentId: (x) => x.parentId,
});
const someRootItem = { const someRootItem = {
someId: "some-root-id", id: "some-root-id",
someParentId: undefined, parentId: undefined,
someProperty: "some-root-content", someProperty: "some-root-content",
}; };
const someItem = { const someItem = {
someId: "some-id", id: "some-id",
someParentId: "some-root-id", parentId: "some-root-id",
someProperty: "some-content", someProperty: "some-content",
}; };
const someNestedItem = { const someNestedItem = {
someId: "some-nested-id", id: "some-nested-id",
someParentId: "some-id", parentId: "some-id",
someProperty: "some-nested-content", someProperty: "some-nested-content",
}; };
const items = [someRootItem, someItem, someNestedItem]; const items = [someRootItem, someItem, someNestedItem];
const composite = getComposite({ const composite = getComposite(items);
source: items,
rootId: "some-root-id",
getId: (x) => x.someId,
getParentId: (x) => x.someParentId,
});
expect(composite).toEqual({ expect(composite).toEqual({
id: "some-root-id", id: "some-root-id",
@ -62,34 +66,38 @@ describe("get-composite", () => {
it("given items and an unspecified root id and single item without parent as root, creates a composite", () => { it("given items and an unspecified root id and single item without parent as root, creates a composite", () => {
const someRootItem = { const someRootItem = {
someId: "some-root-id", id: "some-root-id",
someProperty: "some-root-content", someProperty: "some-root-content",
// Notice: no "someParentId" makes this the root. // Notice: no "parentId" makes this the root.
someParentId: undefined, parentId: undefined,
}; };
const someItem = { const someItem = {
someId: "some-id", id: "some-id",
someParentId: "some-root-id", parentId: "some-root-id",
someProperty: "some-content", someProperty: "some-content",
}; };
const someNestedItem = { const someNestedItem = {
someId: "some-nested-id", id: "some-nested-id",
someParentId: "some-id", parentId: "some-id",
someProperty: "some-nested-content", someProperty: "some-nested-content",
}; };
const items = [someRootItem, someItem, someNestedItem]; const items = [someRootItem, someItem, someNestedItem];
const composite = getComposite({ const getComposite = getCompositeFor<{
source: items, id: string;
parentId: string | undefined;
}>({
// Notice: no root id // Notice: no root id
// rootId: "some-root-id", // rootId: "some-root-id",
getId: (x) => x.someId, getId: (x) => x.id,
getParentId: (x) => x.someParentId, getParentId: (x) => x.parentId,
}); });
const composite = getComposite(items);
expect(composite).toEqual({ expect(composite).toEqual({
id: "some-root-id", id: "some-root-id",
value: someRootItem, value: someRootItem,
@ -115,27 +123,29 @@ describe("get-composite", () => {
it("given items and an unspecified root id and multiple items without parent as root, throws", () => { it("given items and an unspecified root id and multiple items without parent as root, throws", () => {
const someRootItem = { const someRootItem = {
someId: "some-root-id", id: "some-root-id",
// Notice: no "someParentId" makes this a root. // Notice: no "parentId" makes this a root.
someParentId: undefined, parentId: undefined,
}; };
const someOtherRootItem = { const someOtherRootItem = {
someId: "some-other-root-id", id: "some-other-root-id",
// Notice: no "someParentId" makes also this a root. // Notice: no "parentId" makes also this a root.
someParentId: undefined, parentId: undefined,
}; };
const items = [someRootItem, someOtherRootItem]; const items = [someRootItem, someOtherRootItem];
const getComposite = getCompositeFor<{
id: string;
parentId: string | undefined;
}>({
getId: (x) => x.id,
getParentId: (x) => x.parentId,
});
expect(() => { expect(() => {
getComposite({ getComposite(items);
source: items,
// Notice: no root id
// rootId: "some-root-id",
getId: (x) => x.someId,
getParentId: (x) => x.someParentId,
});
}).toThrow( }).toThrow(
'Tried to get a composite, but multiple roots where encountered: "some-root-id", "some-other-root-id"', 'Tried to get a composite, but multiple roots where encountered: "some-root-id", "some-other-root-id"',
); );
@ -143,24 +153,27 @@ describe("get-composite", () => {
it("given non-unique ids, throws", () => { it("given non-unique ids, throws", () => {
const someItem = { const someItem = {
someId: "some-id", id: "some-id",
someParentId: "irrelevant", parentId: "irrelevant",
}; };
const someOtherItem = { const someOtherItem = {
someId: "some-id", id: "some-id",
someParentId: "irrelevant", parentId: "irrelevant",
}; };
const items = [someItem, someOtherItem]; const items = [someItem, someOtherItem];
const getComposite = getCompositeFor<{
id: string;
parentId: string | undefined;
}>({
getId: (x) => x.id,
getParentId: (x) => x.parentId,
});
expect(() => { expect(() => {
getComposite({ getComposite(items);
source: items,
rootId: "irrelevant",
getId: (x) => x.someId,
getParentId: (x) => x.someParentId,
});
}).toThrow( }).toThrow(
'Tried to get a composite but encountered non-unique ids: "some-id"', 'Tried to get a composite but encountered non-unique ids: "some-id"',
); );
@ -168,24 +181,27 @@ describe("get-composite", () => {
it("given items with missing parent ids, when creating composite without handling for unknown parents, throws", () => { it("given items with missing parent ids, when creating composite without handling for unknown parents, throws", () => {
const someItem = { const someItem = {
someId: "some-id", id: "some-id",
someParentId: undefined, parentId: undefined,
}; };
const someItemWithMissingParentId = { const someItemWithMissingParentId = {
someId: "some-other-id", id: "some-other-id",
someParentId: "some-missing-id", parentId: "some-missing-id",
}; };
const items = [someItem, someItemWithMissingParentId]; const items = [someItem, someItemWithMissingParentId];
const getComposite = getCompositeFor<{
id: string;
parentId: string | undefined;
}>({
getId: (x) => x.id,
getParentId: (x) => x.parentId,
});
expect(() => { expect(() => {
getComposite({ getComposite(items);
source: items,
rootId: "irrelevant",
getId: (x) => x.someId,
getParentId: (x) => x.someParentId,
});
}).toThrow( }).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".
@ -202,7 +218,6 @@ Available parent ids are:
beforeEach(() => { beforeEach(() => {
const someItem = { const someItem = {
id: "some-root-id", id: "some-root-id",
parentId: undefined,
}; };
const someItemWithMissingParentId = { const someItemWithMissingParentId = {
@ -215,10 +230,16 @@ Available parent ids are:
handleMissingParentIdMock = jest.fn(); handleMissingParentIdMock = jest.fn();
composite = getComposite({ const getComposite = getCompositeFor<{
source: items, id: string;
parentId?: string;
}>({
getId: (x) => x.id,
getParentId: (x) => x.parentId,
handleMissingParentIds: handleMissingParentIdMock, handleMissingParentIds: handleMissingParentIdMock,
}); });
composite = getComposite(items);
}); });
it("creates composite without the orphan item, and without throwing", () => { it("creates composite without the orphan item, and without throwing", () => {
@ -243,151 +264,58 @@ Available parent ids are:
const someRoot = { const someRoot = {
id: "root", id: "root",
someParentId: undefined, parentId: undefined,
}; };
const items = [someItem, someRoot]; const items = [someItem, someRoot];
const getComposite = getCompositeFor<{
id: string;
parentId: string | undefined;
}>({
getId: (x) => x.id,
getParentId: (x) => x.parentId,
});
expect(() => { expect(() => {
getComposite({ getComposite(items);
source: items, }).toThrow(
}); 'Tried to get a composite, but found items with self as parent: "some-id"',
}).toThrow('Tried to get a composite, but found items with self as parent: "some-id"'); );
}); });
it("given undefined ids, throws", () => { it("given undefined ids, throws", () => {
const root = { const root = {
someParentId: undefined, parentId: undefined,
someId: "some-root", id: "some-root",
}; };
const someItem = { const someItem = {
someParentId: "some-root", parentId: "some-root",
someId: undefined, id: undefined,
}; };
const someOtherItem = { const someOtherItem = {
someParentId: "some-root", parentId: "some-root",
someId: undefined, id: undefined,
}; };
const items = [root, someItem, someOtherItem]; const items = [root, someItem, someOtherItem];
const getComposite = getCompositeFor<{
id: any;
parentId: string | undefined;
}>({
getId: (x) => x.id,
getParentId: (x) => x.parentId,
});
expect(() => { expect(() => {
getComposite({ getComposite(items);
source: items,
rootId: "some-root",
getId: (x) => x.someId as any,
getParentId: (x) => x.someParentId,
});
}).toThrow("Tried to get a composite but encountered 2 undefined ids"); }).toThrow("Tried to get a composite but encountered 2 undefined ids");
}); });
it("given items with default properties for id and parentId, creates a composite", () => { it("given transformed children, creates a composite with transformed children", () => {
const someRootItem = {
id: "some-root-id",
};
const someItem = {
id: "some-id",
parentId: "some-root-id",
};
const someNestedItem = {
id: "some-nested-id",
parentId: "some-id",
};
const items = [someRootItem, someItem, someNestedItem];
const composite = getComposite({
source: items,
// Notice: no need for functions
// getId: (x) => x.id,
// getParentId: (x) => x.parentId,
});
expect(composite).toEqual({
id: "some-root-id",
value: someRootItem,
children: [
{
id: "some-id",
parentId: "some-root-id",
value: someItem,
children: [
{
id: "some-nested-id",
parentId: "some-id",
value: someNestedItem,
children: [],
},
],
},
],
});
});
it("given explicitly ordered items, creates a composite with ordered children", () => {
const someRootItem = {
id: "some-root-id",
someOrderNumber: 1,
};
const someItem1 = {
id: "some-id-1",
parentId: "some-root-id",
someOrderNumber: 1,
};
const someItem2 = {
id: "some-id-2",
parentId: "some-root-id",
someOrderNumber: 2,
};
const someChildItem1 = {
id: "some-child-id-1",
parentId: "some-id-1",
someOrderNumber: 1,
};
const someChildItem2 = {
id: "some-child-id-2",
parentId: "some-id-1",
someOrderNumber: 2,
};
const items = [
someRootItem,
// Note: not in order yet.
someItem2,
someItem1,
someChildItem2,
someChildItem1,
];
const composite = getComposite({
source: items,
// Note: this is the explicit function to order a composite's children.
getOrderedChildren: (things) =>
sortBy((thing) => thing.someOrderNumber, things),
});
const orderedPaths = getCompositePaths(composite);
expect(orderedPaths).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"],
]);
});
it("given implicitly ordered items, creates a composite with ordered children", () => {
const someRootItem = { const someRootItem = {
id: "some-root-id", id: "some-root-id",
orderNumber: 1, orderNumber: 1,
@ -426,12 +354,19 @@ Available parent ids are:
someChildItem1, someChildItem1,
]; ];
const composite = getComposite({ const getComposite = getCompositeFor<{
source: items, id: string;
// Note: without explicit getOrderedChildren for ordering, implicit default value of "orderNumber" will be used, if it exists. parentId?: string;
// getOrderedChildren: things => sortBy(thing => thing.orderNumber, things), orderNumber?: number;
}>({
getId: (x) => x.id,
getParentId: (x) => x.parentId,
transformChildren: (things) =>
sortBy((thing) => thing.orderNumber, things),
}); });
const composite = getComposite(items);
const orderedPaths = getCompositePaths(composite); const orderedPaths = getCompositePaths(composite);
expect(orderedPaths).toEqual([ expect(orderedPaths).toEqual([

View File

@ -13,8 +13,7 @@ import {
uniq, uniq,
without, without,
compact, compact,
get, identity,
sortBy,
} from "lodash/fp"; } from "lodash/fp";
export interface Composite<T> { export interface Composite<T> {
@ -24,21 +23,21 @@ export interface Composite<T> {
children: Composite<T>[]; children: Composite<T>[];
} }
export default <T>({ interface Configuration<T> {
source,
rootId,
getId = get("id"),
getParentId = get("parentId"),
getOrderedChildren = (things: T[]) => sortBy("orderNumber", things),
handleMissingParentIds = throwMissingParentIds,
}: {
source: T[];
rootId?: string; rootId?: string;
getId?: (thing: T) => string; getId: (thing: T) => string;
getParentId?: (thing: T) => string | undefined; getParentId: (thing: T) => string | undefined;
getOrderedChildren?: (things: T[]) => T[]; transformChildren?: (things: T[]) => T[];
handleMissingParentIds?: (parentIdsForHandling: ParentIdsForHandling) => void; handleMissingParentIds?: (parentIdsForHandling: ParentIdsForHandling) => void;
}) => { }
export default <T>({
rootId = undefined,
getId,
getParentId,
transformChildren = identity,
handleMissingParentIds = throwMissingParentIds,
}: Configuration<T>) => (source: T[]) => {
const undefinedIds = pipeline( const undefinedIds = pipeline(
source, source,
filter((x) => getId(x) === undefined), filter((x) => getId(x) === undefined),
@ -107,7 +106,7 @@ export default <T>({
return parentId !== undefined && parentId === thingId; return parentId !== undefined && parentId === thingId;
}), }),
getOrderedChildren, transformChildren,
map(toComposite), map(toComposite),
), ),