Skip to content

Commit

Permalink
[H-0008] findByPrimaryKey is reworked
Browse files Browse the repository at this point in the history
  • Loading branch information
Kovalenkov Pavel committed Feb 5, 2023
1 parent 89930bf commit 44c911f
Show file tree
Hide file tree
Showing 14 changed files with 187 additions and 97 deletions.
2 changes: 1 addition & 1 deletion src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ export type { IRemoveDeepByKey } from "./removeDeepByKey";
export { removeByKey } from "./removeByKey";
export type { IRemoveByKey } from "./removeByKey";

export { initTreeUtils } from "./tree-utils";
export { initListUtils } from "./list-utils";
2 changes: 2 additions & 0 deletions src/common/list-utils/__mocks__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./nodeItem";
export * from "./nestedNodes";
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
interface ITreeItem {
interface INodeItem {
id: string;
value: string;
children?: ITreeItem[];
children?: INodeItem[];
}

const nestedItem = {
const nestedNodeItem: INodeItem = {
id: "1-1",
value: "v-1-1",
children: [],
};

const nestedItemDeep = {
const nestedNodeItemDeep: INodeItem = {
id: "1-2-1",
value: "v-1-2-1",
};

const nestedTreeItems: ITreeItem[] = [
const nestedNodeItems: INodeItem[] = [
{
id: "0",
value: "v-0",
Expand All @@ -24,11 +24,11 @@ const nestedTreeItems: ITreeItem[] = [
id: "1",
value: "v-1",
children: [
nestedItem,
nestedNodeItem,
{
id: "1-2",
value: "v-1-2",
children: [nestedItemDeep],
children: [nestedNodeItemDeep],
},
],
},
Expand All @@ -38,4 +38,6 @@ const nestedTreeItems: ITreeItem[] = [
},
];

export { nestedTreeItems, nestedItem, nestedItemDeep };
export type { INodeItem };

export { nestedNodeItems, nestedNodeItem, nestedNodeItemDeep };
12 changes: 12 additions & 0 deletions src/common/list-utils/__mocks__/nodeItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
interface INodeItem {
id: string;
value: string;
children?: INodeItem[];
}

const nodeItem: INodeItem = {
id: "0",
value: "0",
};

export { nodeItem };
42 changes: 42 additions & 0 deletions src/common/list-utils/__tests__/findByPrimaryKey.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { findByPrimaryKey } from "../findByPrimaryKey";
import {
INodeItem,
nestedNodeItem,
nestedNodeItemDeep,
nestedNodeItems,
nodeItem,
} from "../__mocks__";

describe("findByPrimaryKey", () => {
const primaryKey = "id";
const secondaryKey = "children";

test("finds object by a given primary key in a plain structure", () => {
expect(findByPrimaryKey(primaryKey, secondaryKey)([nodeItem], "0")).toBe(nodeItem);
});

test("finds object by a given primary key in a nested structure (nesting level 1)", () => {
expect(findByPrimaryKey(primaryKey, secondaryKey)(nestedNodeItems, "1-1")).toBe(nestedNodeItem);
});

test("finds object by a given primary key in a nested structure (nesting level 2)", () => {
expect(findByPrimaryKey(primaryKey, secondaryKey)(nestedNodeItems, "1-2-1")).toBe(
nestedNodeItemDeep,
);
});

test("returns 'null' if value by a given primary key doesn't exist", () => {
expect(findByPrimaryKey(primaryKey, secondaryKey)(nestedNodeItems, "1-2-3")).toBeNull();
});

test("calls a mutation callback (if provided) on a found node", () => {
const mutation = (node: INodeItem) => (node.value = `${node.value}-mutated`);
const nodeWithMutation = findByPrimaryKey(primaryKey, secondaryKey)(
nestedNodeItems,
"1-1",
mutation,
);

expect(nodeWithMutation?.value).toBe("v-1-1-mutated");
});
});
27 changes: 27 additions & 0 deletions src/common/list-utils/__tests__/initListUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { findByPrimaryKey } from "../findByPrimaryKey";
import { initListUtils } from "../initListUtils";

jest.mock("../findByPrimaryKey");

describe("initListUtils", () => {
const primaryKey = "id";
const childrenKey = "children";

afterEach(jest.clearAllMocks);

test("during initialization, calls 'findByPrimaryKey' utility with proper arguments", () => {
initListUtils({ primaryKey, childrenKey });

expect(findByPrimaryKey).toBeCalledWith(primaryKey, childrenKey);
});

test("provides a result of a proper signature", () => {
(findByPrimaryKey as jest.Mock).mockImplementationOnce(() => jest.fn());

const utils = initListUtils({ primaryKey, childrenKey });

expect(utils).toMatchObject({
findByPrimaryKey: expect.any(Function),
});
});
});
65 changes: 65 additions & 0 deletions src/common/list-utils/findByPrimaryKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
type PartialRecord<K extends keyof any, T> = {
[P in K]?: T;
};

interface IFindByPrimaryKeyMutation<T extends Record<string, unknown>> {
(node: T): void;
}

/**
* @param primaryKey primary key name which is a unique identifier for each object within the given list.
* @param childrenKey secondary key name which is used as an identifier for nested nodes list within a current one.
* @returns function that accepts:
* 1) list to traverse over;
* 2) value of a primary key to match;
* 3) callback to be called on a found node.
*
* Say, our list is something like that:
*
* const list = [{ id: 0, value: '0', children: [{ id: 1, value: '1' }] }];
*
* Then, we can find an object with id === 1 doing the following:
*
* const node = findByPrimaryKey('id', 'children')(list, 1);
* @returns if target node exists - the link to that node itself, otherwise - null
*/
const findByPrimaryKey =
<PK extends string, SK extends string>(primaryKey: PK, childrenKey: SK) =>
<T extends Record<PK, T[PK]> & PartialRecord<SK, T[]>>(
items: T[],
value: T[PK],
cb?: IFindByPrimaryKeyMutation<T>,
) => {
let foundItem: T | null = null;

const find = (children: T[]) => {
for (let i = 0; i < children.length; i++) {
const current = children[i];

if (current[primaryKey] === value) {
foundItem = current;
break;
}

const next = current[childrenKey];

if (next && Array.isArray(next)) {
find(next);
}
}

return foundItem;
};

const result = find(items);

if (cb && result) {
cb(result);
}

return result;
};

export type { IFindByPrimaryKeyMutation };

export { findByPrimaryKey };
2 changes: 2 additions & 0 deletions src/common/list-utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { initListUtils } from "./initListUtils";
export { findByPrimaryKey } from "./findByPrimaryKey";
26 changes: 26 additions & 0 deletions src/common/list-utils/initListUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { findByPrimaryKey } from "./findByPrimaryKey";

interface IInitListUtilsOptions<PK, SK> {
primaryKey: PK;
childrenKey: SK;
}

/**
* Creates a bag of useful traversing utilities for going over, finding and mutating nodes within
* some nested objects structures.
* For now, contains following methods:
* - findByPrimaryKey - finds a node by it's primary identifier and invokes provided callback to mutate the node.
*/
function initListUtils<PK extends string, SK extends string>(
options: IInitListUtilsOptions<PK, SK>,
) {
const { primaryKey, childrenKey } = options;

return {
findByPrimaryKey: findByPrimaryKey(primaryKey, childrenKey),
};
}

export type { IInitListUtilsOptions };

export { initListUtils };
2 changes: 0 additions & 2 deletions src/common/tree-utils/__mocks__/index.ts

This file was deleted.

12 changes: 0 additions & 12 deletions src/common/tree-utils/__mocks__/treeItem.ts

This file was deleted.

24 changes: 0 additions & 24 deletions src/common/tree-utils/__tests__/initTreeUtils.test.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/common/tree-utils/index.ts

This file was deleted.

49 changes: 0 additions & 49 deletions src/common/tree-utils/initTreeUtils.ts

This file was deleted.

0 comments on commit 44c911f

Please sign in to comment.