abisan-custom-library 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -0
- package/jest.config.cjs +6 -0
- package/package.json +31 -0
- package/src/Extraction/pluck.ts +46 -0
- package/src/Foundation/foundation/every.ts +11 -0
- package/src/Foundation/foundation/filter.ts +16 -0
- package/src/Foundation/foundation/map.ts +14 -0
- package/src/Foundation/foundation/reduce.ts +26 -0
- package/src/Foundation/foundation/reduceright.ts +13 -0
- package/src/Foundation/foundation/some.ts +11 -0
- package/src/Lens/lens.ts +15 -0
- package/src/Lens/set.ts +5 -0
- package/src/Lens/utils.ts +16 -0
- package/src/Lens/view.ts +5 -0
- package/src/Search/findByKeyValue.ts +12 -0
- package/src/Search/findByPredicate.ts +11 -0
- package/src/index.ts +15 -0
- package/test/every.test.ts +40 -0
- package/test/filter.test.ts +45 -0
- package/test/findByKeyValue.test.ts +29 -0
- package/test/findByPredicate.test.ts +29 -0
- package/test/lens.test.ts +99 -0
- package/test/map.test.ts +38 -0
- package/test/pluck.test.ts +87 -0
- package/test/reduce.test.ts +25 -0
- package/test/reduceRight.test.ts +25 -0
- package/test/some.test.ts +24 -0
- package/tsconfig.json +14 -0
package/README.md
ADDED
|
File without changes
|
package/jest.config.cjs
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "abisan-custom-library",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest",
|
|
8
|
+
"coverage": "jest --coverage",
|
|
9
|
+
"watch": "jest --watch"
|
|
10
|
+
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/Abinashini31/Custom-Library.git"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"type": "commonjs",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/Abinashini31/Custom-Library/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/Abinashini31/Custom-Library#readme",
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/jest": "^30.0.0",
|
|
26
|
+
"jest": "^30.3.0",
|
|
27
|
+
"ts-jest": "^29.4.9",
|
|
28
|
+
"ts-node": "^10.9.2",
|
|
29
|
+
"typescript": "^6.0.2"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/Extraction/pluck.ts
|
|
2
|
+
|
|
3
|
+
type Path = string | string[];
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Polymorphic pluck:
|
|
7
|
+
* - "id" → simple property
|
|
8
|
+
* - "a.b.c" → deep path
|
|
9
|
+
* - ["id", "tags"] → multi-key pick
|
|
10
|
+
*/
|
|
11
|
+
export function pluck<T>(path: Path, data: T[]): any[] {
|
|
12
|
+
if (!Array.isArray(data) || data.length === 0) return [];
|
|
13
|
+
|
|
14
|
+
if (Array.isArray(path)) {
|
|
15
|
+
return multiPick(path, data);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (typeof path === 'string' && path.includes('.')) {
|
|
19
|
+
return deepPick(path, data);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return simplePick(path as string, data);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* ------------------ Helpers ------------------ */
|
|
26
|
+
|
|
27
|
+
function simplePick<T>(key: string, data: T[]) {
|
|
28
|
+
return data.map(item => (item as any)?.[key]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function deepPick<T>(path: string, data: T[]) {
|
|
32
|
+
const keys = path.split('.');
|
|
33
|
+
|
|
34
|
+
return data.map(obj =>
|
|
35
|
+
keys.reduce((acc: any, key) => acc?.[key], obj)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function multiPick<T>(keys: string[], data: T[]) {
|
|
40
|
+
return data.map(obj =>
|
|
41
|
+
keys.reduce((acc: any, key) => {
|
|
42
|
+
acc[key] = (obj as any)?.[key];
|
|
43
|
+
return acc;
|
|
44
|
+
}, {})
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function filter<T>(
|
|
2
|
+
fn: (value: T, index: number, arr: readonly T[]) => boolean,
|
|
3
|
+
arr: readonly T[] | null | undefined
|
|
4
|
+
): T[] {
|
|
5
|
+
if (!arr) return [];
|
|
6
|
+
|
|
7
|
+
const result: T[] = [];
|
|
8
|
+
|
|
9
|
+
for (let i = 0; i < arr.length; i++) {
|
|
10
|
+
if (fn(arr[i], i, arr)) {
|
|
11
|
+
result.push(arr[i]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function map<T, R>(
|
|
2
|
+
fn: (item: T, index: number) => R,
|
|
3
|
+
arr: T[]
|
|
4
|
+
): R[] {
|
|
5
|
+
if (!Array.isArray(arr)) return [];
|
|
6
|
+
|
|
7
|
+
const result: R[] = [];
|
|
8
|
+
|
|
9
|
+
for (let i = 0; i < arr.length; i++) {
|
|
10
|
+
result.push(fn(arr[i], i));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function reduce<T, U>(
|
|
2
|
+
fn: (acc: U, val: T, index: number, arr: T[]) => U,
|
|
3
|
+
initial: U,
|
|
4
|
+
arr: T[]
|
|
5
|
+
): U {
|
|
6
|
+
let acc = initial;
|
|
7
|
+
|
|
8
|
+
for (let i = 0; i < arr.length; i++) {
|
|
9
|
+
acc = fn(acc, arr[i], i, arr);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return acc;
|
|
13
|
+
}
|
|
14
|
+
export function reduceRight<T, U>(
|
|
15
|
+
fn: (acc: U, val: T, index: number, arr: T[]) => U,
|
|
16
|
+
initial: U,
|
|
17
|
+
arr: T[]
|
|
18
|
+
): U {
|
|
19
|
+
let acc = initial;
|
|
20
|
+
|
|
21
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
22
|
+
acc = fn(acc, arr[i], i, arr);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return acc;
|
|
26
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function reduceRight<T, U>(
|
|
2
|
+
fn: (acc: U, val: T, index: number, arr: readonly T[]) => U,
|
|
3
|
+
initial: U,
|
|
4
|
+
arr: readonly T[]
|
|
5
|
+
): U {
|
|
6
|
+
let acc = initial;
|
|
7
|
+
|
|
8
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
9
|
+
acc = fn(acc, arr[i], i, arr as T[]);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return acc;
|
|
13
|
+
}
|
package/src/Lens/lens.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getDeep, setDeep } from './utils';
|
|
2
|
+
|
|
3
|
+
export type Lens = {
|
|
4
|
+
get: (obj: any) => any;
|
|
5
|
+
set: (value: any, obj: any) => any;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function lens(path: string): Lens {
|
|
9
|
+
const keys = path ? path.split('.') : [];
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
get: (obj: any) => getDeep(obj, keys),
|
|
13
|
+
set: (value: any, obj: any) => setDeep(obj, keys, value)
|
|
14
|
+
};
|
|
15
|
+
}
|
package/src/Lens/set.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function getDeep(obj: any, keys: string[]) {
|
|
2
|
+
return keys.reduce((acc, key) => acc?.[key], obj);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function setDeep(obj: any, keys: string[], value: any): any {
|
|
6
|
+
if (keys.length === 0) return value;
|
|
7
|
+
|
|
8
|
+
const [first, ...rest] = keys;
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
...obj,
|
|
12
|
+
[first]: rest.length
|
|
13
|
+
? setDeep(obj?.[first] ?? {}, rest, value)
|
|
14
|
+
: value
|
|
15
|
+
};
|
|
16
|
+
}
|
package/src/Lens/view.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { map } from "./Foundation/foundation/map";
|
|
2
|
+
export { filter } from "./Foundation/foundation/filter";
|
|
3
|
+
export { reduce } from "./Foundation/foundation/reduce";
|
|
4
|
+
export { reduceRight } from "./Foundation/foundation/reduceright";
|
|
5
|
+
export { some } from "./Foundation/foundation/some";
|
|
6
|
+
export { every } from "./Foundation/foundation/every";
|
|
7
|
+
|
|
8
|
+
export { findByPredicate } from "./Search/findByPredicate";
|
|
9
|
+
export { findByKeyValue } from "./Search/findByKeyValue";
|
|
10
|
+
|
|
11
|
+
export { pluck } from "./Extraction/pluck";
|
|
12
|
+
|
|
13
|
+
export { lens } from "./Lens/lens";
|
|
14
|
+
export { view } from "./Lens/view";
|
|
15
|
+
export { set } from "./Lens/set";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { every } from "../src/Foundation/foundation/every";
|
|
2
|
+
|
|
3
|
+
describe("every", () => {
|
|
4
|
+
test("returns true if all elements satisfy condition", () => {
|
|
5
|
+
const result = every((x) => x > 0, [1, 2, 3, 4]);
|
|
6
|
+
expect(result).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("returns false if any element fails condition", () => {
|
|
10
|
+
const result = every((x) => x > 0, [1, -2, 3]);
|
|
11
|
+
expect(result).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("works with objects (all true)", () => {
|
|
15
|
+
const users = [
|
|
16
|
+
{ active: true },
|
|
17
|
+
{ active: true },
|
|
18
|
+
{ active: true }
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const result = every((u) => u.active === true, users);
|
|
22
|
+
expect(result).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("works with objects (one false)", () => {
|
|
26
|
+
const users = [
|
|
27
|
+
{ active: true },
|
|
28
|
+
{ active: false },
|
|
29
|
+
{ active: true }
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const result = every((u) => u.active === true, users);
|
|
33
|
+
expect(result).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("empty array returns true (JS behavior)", () => {
|
|
37
|
+
const result = every((x) => x > 0, []);
|
|
38
|
+
expect(result).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
import { filter } from "../src/Foundation/foundation/filter";
|
|
3
|
+
|
|
4
|
+
describe("filter()", () => {
|
|
5
|
+
|
|
6
|
+
test("filters values correctly", () => {
|
|
7
|
+
|
|
8
|
+
const arr = [1, 2, 3, 4];
|
|
9
|
+
|
|
10
|
+
const result = filter(x => x > 2, arr);
|
|
11
|
+
|
|
12
|
+
expect(result).toEqual([3, 4]);
|
|
13
|
+
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("returns empty array if no match", () => {
|
|
17
|
+
|
|
18
|
+
const arr = [1, 2];
|
|
19
|
+
|
|
20
|
+
const result = filter(x => x > 5, arr);
|
|
21
|
+
|
|
22
|
+
expect(result).toEqual([]);
|
|
23
|
+
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("handles empty array", () => {
|
|
27
|
+
|
|
28
|
+
expect(filter(x => x, [])).toEqual([]);
|
|
29
|
+
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("does not mutate original array", () => {
|
|
33
|
+
|
|
34
|
+
const arr = [1, 2, 3];
|
|
35
|
+
|
|
36
|
+
const copy = [...arr];
|
|
37
|
+
|
|
38
|
+
filter(x => x > 1, arr);
|
|
39
|
+
|
|
40
|
+
expect(arr).toEqual(copy);
|
|
41
|
+
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
});
|
|
45
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { findByKeyValue } from "../src/Search/findByKeyValue";
|
|
2
|
+
|
|
3
|
+
describe("findByKeyValue", () => {
|
|
4
|
+
const users = [
|
|
5
|
+
{ id: 1, username: "vishnu_dev", active: true },
|
|
6
|
+
{ id: 2, username: "john_doe", active: false },
|
|
7
|
+
{ id: 3, username: "alice", active: true }
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
test("finds user by username", () => {
|
|
11
|
+
const result = findByKeyValue(users, "username", "john_doe");
|
|
12
|
+
expect(result).toEqual({ id: 2, username: "john_doe", active: false });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("finds user by id", () => {
|
|
16
|
+
const result = findByKeyValue(users, "id", 3);
|
|
17
|
+
expect(result).toEqual({ id: 3, username: "alice", active: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("returns undefined if key-value not found", () => {
|
|
21
|
+
const result = findByKeyValue(users, "username", "not_exist");
|
|
22
|
+
expect(result).toBeUndefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("works with empty array", () => {
|
|
26
|
+
const result = findByKeyValue([], "id", 1);
|
|
27
|
+
expect(result).toBeUndefined();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { findByPredicate } from "../src/Search/findByPredicate";
|
|
2
|
+
|
|
3
|
+
describe("findByPredicate", () => {
|
|
4
|
+
const users = [
|
|
5
|
+
{ id: 1, username: "vishnu_dev", active: true },
|
|
6
|
+
{ id: 2, username: "john_doe", active: false },
|
|
7
|
+
{ id: 3, username: "alice", active: true }
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
test("finds user by predicate match", () => {
|
|
11
|
+
const result = findByPredicate(users, (u) => u.id === 1);
|
|
12
|
+
expect(result).toEqual({ id: 1, username: "vishnu_dev", active: true });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("returns undefined if no match", () => {
|
|
16
|
+
const result = findByPredicate(users, (u) => u.id === 999);
|
|
17
|
+
expect(result).toBeUndefined();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("finds first active user", () => {
|
|
21
|
+
const result = findByPredicate(users, (u) => u.active === true);
|
|
22
|
+
expect(result).toEqual({ id: 1, username: "vishnu_dev", active: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("works with empty array", () => {
|
|
26
|
+
const result = findByPredicate([], (u: any) => u.id === 1);
|
|
27
|
+
expect(result).toBeUndefined();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// test/lens.test.ts
|
|
2
|
+
|
|
3
|
+
import { lens } from '../src/Lens/lens';
|
|
4
|
+
import { view } from '../src/Lens/view';
|
|
5
|
+
import { set } from '../src/Lens/set';
|
|
6
|
+
|
|
7
|
+
describe('Lens (split modules)', () => {
|
|
8
|
+
const user = {
|
|
9
|
+
id: 1,
|
|
10
|
+
profile: {
|
|
11
|
+
settings: {
|
|
12
|
+
theme: 'light'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/* ------------------ VIEW ------------------ */
|
|
18
|
+
|
|
19
|
+
it('should view deep value', () => {
|
|
20
|
+
const l = lens('profile.settings.theme');
|
|
21
|
+
|
|
22
|
+
expect(view(l, user)).toBe('light');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should return undefined for missing path', () => {
|
|
26
|
+
const l = lens('profile.settings.lang');
|
|
27
|
+
|
|
28
|
+
expect(view(l, user)).toBeUndefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should handle null safely', () => {
|
|
32
|
+
const l = lens('a.b.c');
|
|
33
|
+
|
|
34
|
+
// @ts-ignore
|
|
35
|
+
expect(view(l, null)).toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/* ------------------ SET ------------------ */
|
|
39
|
+
|
|
40
|
+
it('should update value immutably', () => {
|
|
41
|
+
const l = lens('profile.settings.theme');
|
|
42
|
+
|
|
43
|
+
const updated = set(l, 'dark', user);
|
|
44
|
+
|
|
45
|
+
expect(updated.profile.settings.theme).toBe('dark');
|
|
46
|
+
expect(user.profile.settings.theme).toBe('light'); // unchanged
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should create missing path', () => {
|
|
50
|
+
const l = lens('profile.preferences.language');
|
|
51
|
+
|
|
52
|
+
const updated = set(l, 'en', user);
|
|
53
|
+
|
|
54
|
+
expect(updated.profile.preferences.language).toBe('en');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should not mutate original object', () => {
|
|
58
|
+
const copy = JSON.parse(JSON.stringify(user));
|
|
59
|
+
|
|
60
|
+
const l = lens('profile.settings.theme');
|
|
61
|
+
set(l, 'dark', user);
|
|
62
|
+
|
|
63
|
+
expect(user).toEqual(copy);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should preserve other fields', () => {
|
|
67
|
+
const l = lens('profile.settings.theme');
|
|
68
|
+
|
|
69
|
+
const updated = set(l, 'dark', user);
|
|
70
|
+
|
|
71
|
+
expect(updated.id).toBe(1);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should work with empty object', () => {
|
|
75
|
+
const l = lens('a.b.c');
|
|
76
|
+
|
|
77
|
+
const result = set(l, 10, {});
|
|
78
|
+
|
|
79
|
+
expect(result).toEqual({
|
|
80
|
+
a: { b: { c: 10 } }
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/* ------------------ EMPTY PATH ------------------ */
|
|
85
|
+
|
|
86
|
+
it('should replace entire object when path is empty', () => {
|
|
87
|
+
const l = lens('');
|
|
88
|
+
|
|
89
|
+
const result = set(l, { x: 1 }, { a: 1 });
|
|
90
|
+
|
|
91
|
+
expect(result).toEqual({ x: 1 });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should return entire object on view when path is empty', () => {
|
|
95
|
+
const l = lens('');
|
|
96
|
+
|
|
97
|
+
expect(view(l, user)).toEqual(user);
|
|
98
|
+
});
|
|
99
|
+
});
|
package/test/map.test.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { map } from "../src/Foundation/foundation/map";
|
|
2
|
+
|
|
3
|
+
describe("map()", () => {
|
|
4
|
+
|
|
5
|
+
test("transforms values correctly", () => {
|
|
6
|
+
const arr = [1, 2, 3, 4];
|
|
7
|
+
|
|
8
|
+
const result = map(x => x * 2, arr);
|
|
9
|
+
|
|
10
|
+
expect(result).toEqual([2, 4, 6, 8]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("returns empty array if input is empty", () => {
|
|
14
|
+
const arr: number[] = [];
|
|
15
|
+
|
|
16
|
+
const result = map(x => x * 2, arr);
|
|
17
|
+
|
|
18
|
+
expect(result).toEqual([]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("does not mutate original array", () => {
|
|
22
|
+
const arr = [1, 2, 3];
|
|
23
|
+
const copy = [...arr];
|
|
24
|
+
|
|
25
|
+
map(x => x * 2, arr);
|
|
26
|
+
|
|
27
|
+
expect(arr).toEqual(copy);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("passes index correctly", () => {
|
|
31
|
+
const arr = [10, 20, 30];
|
|
32
|
+
|
|
33
|
+
const result = map((x, i) => i, arr);
|
|
34
|
+
|
|
35
|
+
expect(result).toEqual([0, 1, 2]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// test/pluck.test.ts
|
|
2
|
+
|
|
3
|
+
import { pluck } from '../src/Extraction/pluck';
|
|
4
|
+
|
|
5
|
+
describe('pluck utility', () => {
|
|
6
|
+
const data = [
|
|
7
|
+
{ id: 1, info: { email: 'v@test.com' }, tags: ['dev'] },
|
|
8
|
+
{ id: 2, info: { email: 'a@test.com' }, tags: ['hr'] }
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
/* ------------------ Simple Property ------------------ */
|
|
12
|
+
|
|
13
|
+
it('should extract simple property', () => {
|
|
14
|
+
const result = pluck('id', data);
|
|
15
|
+
expect(result).toEqual([1, 2]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/* ------------------ Deep Path ------------------ */
|
|
19
|
+
|
|
20
|
+
it('should extract deep path using dot notation', () => {
|
|
21
|
+
const result = pluck('info.email', data);
|
|
22
|
+
expect(result).toEqual(['v@test.com', 'a@test.com']);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/* ------------------ Multi Key ------------------ */
|
|
26
|
+
|
|
27
|
+
it('should extract multiple keys', () => {
|
|
28
|
+
const result = pluck(['id', 'tags'], data);
|
|
29
|
+
|
|
30
|
+
expect(result).toEqual([
|
|
31
|
+
{ id: 1, tags: ['dev'] },
|
|
32
|
+
{ id: 2, tags: ['hr'] }
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/* ------------------ Edge Cases ------------------ */
|
|
37
|
+
|
|
38
|
+
it('should return empty array for empty input', () => {
|
|
39
|
+
expect(pluck('id', [])).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle null/undefined safely', () => {
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
expect(pluck('id', null)).toEqual([]);
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
expect(pluck('id', undefined)).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should handle missing keys gracefully', () => {
|
|
50
|
+
const result = pluck('nonexistent', data);
|
|
51
|
+
expect(result).toEqual([undefined, undefined]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should handle deep missing paths', () => {
|
|
55
|
+
const result = pluck('info.phone.number', data);
|
|
56
|
+
expect(result).toEqual([undefined, undefined]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should handle multi-pick with missing keys', () => {
|
|
60
|
+
const result = pluck(['id', 'missing'], data);
|
|
61
|
+
|
|
62
|
+
expect(result).toEqual([
|
|
63
|
+
{ id: 1, missing: undefined },
|
|
64
|
+
{ id: 2, missing: undefined }
|
|
65
|
+
]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/* ------------------ Immutability ------------------ */
|
|
69
|
+
|
|
70
|
+
it('should not mutate original data (simple)', () => {
|
|
71
|
+
const copy = JSON.parse(JSON.stringify(data));
|
|
72
|
+
pluck('id', data);
|
|
73
|
+
expect(data).toEqual(copy);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should not mutate original data (deep)', () => {
|
|
77
|
+
const copy = JSON.parse(JSON.stringify(data));
|
|
78
|
+
pluck('info.email', data);
|
|
79
|
+
expect(data).toEqual(copy);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should not mutate original data (multi)', () => {
|
|
83
|
+
const copy = JSON.parse(JSON.stringify(data));
|
|
84
|
+
pluck(['id', 'tags'], data);
|
|
85
|
+
expect(data).toEqual(copy);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { reduce } from "../src/Foundation/foundation/reduce";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
describe("reduce", () => {
|
|
5
|
+
test("sum of numbers", () => {
|
|
6
|
+
const result = reduce((acc, val) => acc + val, 0, [1, 2, 3, 4]);
|
|
7
|
+
expect(result).toBe(10);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("subtract left to right", () => {
|
|
11
|
+
const result = reduce((acc, val) => acc - val, 100, [10, 5]);
|
|
12
|
+
// (100 - 10) - 5 = 85
|
|
13
|
+
expect(result).toBe(85);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("empty array returns initial value", () => {
|
|
17
|
+
const result = reduce((acc, val) => acc + val, 10, []);
|
|
18
|
+
expect(result).toBe(10);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("works with strings", () => {
|
|
22
|
+
const result = reduce((acc, val) => acc + val, "", ["a", "b", "c"]);
|
|
23
|
+
expect(result).toBe("abc");
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { reduceRight } from "../src/Foundation/foundation/reduceright";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
describe("reduceRight", () => {
|
|
5
|
+
test("subtract right to left", () => {
|
|
6
|
+
const result = reduceRight((acc, val) => acc - val, 100, [10, 5]);
|
|
7
|
+
// (100 - 5) - 10 = 85
|
|
8
|
+
expect(result).toBe(85);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("concatenation right to left", () => {
|
|
12
|
+
const result = reduceRight((acc, val) => acc + val, "", ["a", "b", "c"]);
|
|
13
|
+
expect(result).toBe("cba");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("single element array", () => {
|
|
17
|
+
const result = reduceRight((acc, val) => acc + val, 10, [5]);
|
|
18
|
+
expect(result).toBe(15);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("empty array returns initial value", () => {
|
|
22
|
+
const result = reduceRight((acc, val) => acc + val, 0, []);
|
|
23
|
+
expect(result).toBe(0);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { some } from "../src/Foundation/foundation/some";
|
|
2
|
+
|
|
3
|
+
describe("some", () => {
|
|
4
|
+
test("returns true if any element matches condition", () => {
|
|
5
|
+
const users = [
|
|
6
|
+
{ role: "user" },
|
|
7
|
+
{ role: "admin" },
|
|
8
|
+
{ role: "guest" }
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const result = some((u) => u.role === "admin", users);
|
|
12
|
+
expect(result).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("returns false if no element matches", () => {
|
|
16
|
+
const result = some((x) => x > 10, [1, 2, 3]);
|
|
17
|
+
expect(result).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("empty array returns false", () => {
|
|
21
|
+
const result = some((x) => x > 0, []);
|
|
22
|
+
expect(result).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": "src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"types": ["jest"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|