Skip to content

Commit 593d615

Browse files
committed
test: add UTs for utils
1 parent aaa5403 commit 593d615

17 files changed

Lines changed: 2434 additions & 0 deletions

tests/config.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
jest.mock("../src/utils/env.utils");
2+
3+
describe("Config", () => {
4+
beforeEach(() => {
5+
jest.resetModules();
6+
});
7+
8+
it("should use default logger config values", () => {
9+
jest.mock("../src/utils/env.utils", () => ({
10+
Env: {
11+
get: jest.fn((key: string, fallback: string) => fallback),
12+
getBoolean: jest.fn(() => false),
13+
},
14+
}));
15+
16+
const { Config } = require("../src/config");
17+
const { Env } = require("../src/utils/env.utils");
18+
19+
expect(Config.Logger.level).toBe("info");
20+
expect(Config.Logger.name).toBe("@catbee/utils");
21+
expect(Config.Logger.isoTimestamp).toBe(false);
22+
23+
expect(Env.get).toHaveBeenCalledWith("LOGGER_LEVEL", "info");
24+
expect(Env.get).toHaveBeenCalledWith("LOGGER_NAME", "@catbee/utils");
25+
expect(Env.get).toHaveBeenCalledWith("npm_package_name", "@catbee/utils");
26+
expect(Env.getBoolean).toHaveBeenCalledWith("LOGGER_ISO_TIMESTAMP", false);
27+
});
28+
29+
it("should load environment-provided logger values", () => {
30+
jest.mock("../src/utils/env.utils", () => ({
31+
Env: {
32+
get: jest.fn((key: string, fallback: string) => {
33+
const values: Record<string, string> = {
34+
LOGGER_LEVEL: "debug",
35+
LOGGER_NAME: "custom-logger",
36+
};
37+
return key in values ? values[key] : fallback;
38+
}),
39+
getBoolean: jest.fn((key: string, fallback: boolean) =>
40+
key === "LOGGER_ISO_TIMESTAMP" ? true : fallback,
41+
),
42+
},
43+
}));
44+
45+
const { Config } = require("../src/config");
46+
47+
expect(Config.Logger.level).toBe("debug");
48+
expect(Config.Logger.name).toBe("custom-logger");
49+
expect(Config.Logger.isoTimestamp).toBe(true);
50+
});
51+
52+
it("should fallback to npm_package_name when LOGGER_NAME is not set", () => {
53+
jest.mock("../src/utils/env.utils", () => ({
54+
Env: {
55+
get: jest.fn((key: string, fallback: string) => {
56+
const values: Record<string, string> = {
57+
LOGGER_LEVEL: "warn",
58+
npm_package_name: "my-pkg",
59+
};
60+
return key in values ? values[key] : fallback;
61+
}),
62+
getBoolean: jest.fn(() => false),
63+
},
64+
}));
65+
66+
const { Config } = require("../src/config");
67+
68+
expect(Config.Logger.level).toBe("warn");
69+
expect(Config.Logger.name).toBe("my-pkg");
70+
expect(Config.Logger.isoTimestamp).toBe(false);
71+
});
72+
73+
it("should still return string for level even if invalid", () => {
74+
jest.mock("../src/utils/env.utils", () => ({
75+
Env: {
76+
get: jest.fn(() => "nonsense"),
77+
getBoolean: jest.fn(() => false),
78+
},
79+
}));
80+
81+
const { Config } = require("../src/config");
82+
83+
expect(typeof Config.Logger.level).toBe("string");
84+
expect(Config.Logger.level).toBe("nonsense");
85+
});
86+
});

tests/utils/array.utils.test.ts

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import {
2+
chunk,
3+
unique,
4+
flattenDeep,
5+
random,
6+
groupBy,
7+
shuffle,
8+
pluck,
9+
difference,
10+
intersect,
11+
mergeSort,
12+
} from "../../src/utils/array.utils";
13+
14+
jest.mock("../../src/utils/obj.utils", () => ({
15+
getValueByPath: (obj: any, path: string) =>
16+
path.split(".").reduce((o, k) => (o ? o[k] : undefined), obj),
17+
}));
18+
19+
describe("ArrayUtils", () => {
20+
describe("chunk", () => {
21+
it("chunks array correctly", () => {
22+
expect(chunk([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]);
23+
});
24+
it("returns empty for empty array", () => {
25+
expect(chunk([], 3)).toEqual([]);
26+
});
27+
it("throws for non-array", () => {
28+
// @ts-expect-error
29+
expect(() => chunk({}, 2)).toThrow("Expected an array");
30+
});
31+
it("throws for non-positive size", () => {
32+
expect(() => chunk([1, 2], 0)).toThrow(
33+
"Chunk size must be a positive integer",
34+
);
35+
expect(() => chunk([1, 2], -1)).toThrow(
36+
"Chunk size must be a positive integer",
37+
);
38+
expect(() => chunk([1, 2], 1.2)).toThrow(
39+
"Chunk size must be a positive integer",
40+
);
41+
});
42+
});
43+
44+
describe("unique", () => {
45+
it("removes duplicates", () => {
46+
expect(unique([1, 2, 2, 3, 1, 4])).toEqual([1, 2, 3, 4]);
47+
});
48+
it("returns empty for empty input", () => {
49+
expect(unique([])).toEqual([]);
50+
});
51+
it("removes duplicates by keyFn", () => {
52+
const arr = [{ a: 1 }, { a: 2 }, { a: 1 }];
53+
expect(unique(arr, (x) => x.a)).toEqual([{ a: 1 }, { a: 2 }]);
54+
});
55+
});
56+
57+
describe("flattenDeep", () => {
58+
it("flattens deeply", () => {
59+
expect(flattenDeep([1, [2, [3, [4]], 5]])).toEqual([1, 2, 3, 4, 5]);
60+
expect(flattenDeep([])).toEqual([]);
61+
});
62+
it("returns [] for non-array", () => {
63+
// @ts-expect-error
64+
expect(flattenDeep(null)).toEqual([]);
65+
});
66+
});
67+
68+
describe("random", () => {
69+
it("returns a value from array or undefined", () => {
70+
jest.spyOn(global.Math, "random").mockReturnValue(0.49);
71+
expect(random([10, 20, 30])).toBe(20);
72+
expect(random([])).toBeUndefined();
73+
// @ts-expect-error
74+
expect(random(null)).toBeUndefined();
75+
jest.spyOn(global.Math, "random").mockRestore();
76+
});
77+
});
78+
79+
describe("groupBy", () => {
80+
it("groups by property key", () => {
81+
const arr = [
82+
{ type: "a", x: 1 },
83+
{ type: "b", x: 2 },
84+
{ type: "a", x: 3 },
85+
];
86+
expect(groupBy(arr, "type")).toEqual({
87+
a: [
88+
{ type: "a", x: 1 },
89+
{ type: "a", x: 3 },
90+
],
91+
b: [{ type: "b", x: 2 }],
92+
});
93+
});
94+
it("groups by keyFn", () => {
95+
const arr = ["a", "aa", "b"];
96+
expect(groupBy(arr, (x) => x.length)).toEqual({
97+
1: ["a", "b"],
98+
2: ["aa"],
99+
});
100+
});
101+
it("returns empty object for non-array/empty", () => {
102+
// @ts-expect-error
103+
expect(groupBy(null, "x")).toEqual({});
104+
expect(groupBy([], "x")).toEqual({});
105+
});
106+
});
107+
108+
describe("shuffle", () => {
109+
it("shuffles array (not mutating original)", () => {
110+
const arr = [1, 2, 3, 4, 5];
111+
const result = shuffle(arr);
112+
expect(result.sort()).toEqual(arr);
113+
expect(arr).toEqual([1, 2, 3, 4, 5]);
114+
// Note: randomness prevents strict equal, but sort should match input.
115+
});
116+
it("throws if input is not an array", () => {
117+
// @ts-expect-error
118+
expect(() => shuffle(null)).toThrow("Expected an array");
119+
});
120+
});
121+
122+
describe("pluck", () => {
123+
it("plucks values by key", () => {
124+
expect(pluck([{ a: 1 }, { a: 2 }, { a: 3 }], "a")).toEqual([1, 2, 3]);
125+
});
126+
it("returns empty for non-array", () => {
127+
// @ts-expect-error
128+
expect(pluck(null, "a")).toEqual([]);
129+
});
130+
});
131+
132+
describe("difference", () => {
133+
it("returns items in a not in b", () => {
134+
expect(difference([1, 2, 3], [2, 4])).toEqual([1, 3]);
135+
});
136+
it("returns [] for non-arrays", () => {
137+
// @ts-expect-error
138+
expect(difference(null, [1])).toEqual([]);
139+
// @ts-expect-error
140+
expect(difference([1], null)).toEqual([]);
141+
});
142+
});
143+
144+
describe("intersect", () => {
145+
it("returns common items", () => {
146+
expect(intersect([1, 2, 3], [2, 3, 4])).toEqual([2, 3]);
147+
});
148+
it("returns [] for non-arrays", () => {
149+
// @ts-expect-error
150+
expect(intersect(null, [1])).toEqual([]);
151+
// @ts-expect-error
152+
expect(intersect([1], null)).toEqual([]);
153+
});
154+
});
155+
156+
describe("mergeSort", () => {
157+
const arrObjs = [
158+
{ name: "Orange", info: { val: 2 } },
159+
{ name: "Apple", info: { val: 3 } },
160+
{ name: "Banana", info: { val: 1 } },
161+
{ name: "Kiwi", info: {} },
162+
{ name: "Dragonfruit", info: null },
163+
];
164+
it("sorts objects by nested key asc", () => {
165+
const sorted = mergeSort(arrObjs, "info.val", "asc");
166+
expect(sorted.map((x) => x.name)).toEqual([
167+
"Banana",
168+
"Orange",
169+
"Apple",
170+
"Kiwi",
171+
"Dragonfruit",
172+
]);
173+
});
174+
it("sorts by nested key desc", () => {
175+
const sorted = mergeSort(arrObjs, "info.val", "desc");
176+
expect(sorted.map((x) => x.name)).toEqual([
177+
"Kiwi",
178+
"Dragonfruit",
179+
"Apple",
180+
"Orange",
181+
"Banana",
182+
]);
183+
});
184+
it("uses key function", () => {
185+
const arr = [{ v: 3 }, { v: 1 }, { v: 2 }];
186+
expect(mergeSort(arr, (x) => x.v, "asc")).toEqual([
187+
{ v: 1 },
188+
{ v: 2 },
189+
{ v: 3 },
190+
]);
191+
});
192+
it("handles array of length 1", () => {
193+
expect(mergeSort([{ a: 2 }], "a")).toEqual([{ a: 2 }]);
194+
});
195+
it("throws for non-array", () => {
196+
// @ts-expect-error
197+
expect(() => mergeSort(null, "x")).toThrow("Expected array");
198+
});
199+
});
200+
});

0 commit comments

Comments
 (0)