Skip to content

Commit 2e17708

Browse files
committed
test(utils): add comprehensive unit tests for utility functions
1 parent ae5ca3d commit 2e17708

3 files changed

Lines changed: 156 additions & 5 deletions

File tree

jest.config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export default {
2+
preset: 'ts-jest/presets/default-esm',
3+
testEnvironment: 'node',
4+
moduleNameMapper: {
5+
'^(\\.\\.?/.*)\\.js$': '$1',
6+
},
7+
transform: {
8+
'^.+\\.tsx?$': [
9+
'ts-jest',
10+
{
11+
useESM: true,
12+
tsconfig: 'tsconfig.json',
13+
},
14+
],
15+
},
16+
};

src/utils.test.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import {afterEach, beforeEach, describe, expect, jest, test} from "@jest/globals";
2+
import {callWithPromise, handleListener, safeListener, throwRuntimeError} from "./utils";
3+
4+
describe("utils", () => {
5+
let originalChrome: any;
6+
let originalBrowser: any;
7+
let originalConsoleError: any;
8+
9+
beforeEach(() => {
10+
originalChrome = globalThis.chrome;
11+
originalBrowser = globalThis.browser;
12+
originalConsoleError = console.error;
13+
console.error = jest.fn();
14+
15+
delete (globalThis as any).chrome;
16+
delete (globalThis as any).browser;
17+
});
18+
19+
afterEach(() => {
20+
globalThis.chrome = originalChrome;
21+
(globalThis as any).browser = originalBrowser;
22+
console.error = originalConsoleError;
23+
jest.resetAllMocks();
24+
});
25+
26+
describe("throwRuntimeError", () => {
27+
test("should not throw if lastError is undefined", () => {
28+
globalThis.chrome = {runtime: {lastError: undefined}} as any;
29+
expect(() => throwRuntimeError()).not.toThrow();
30+
});
31+
32+
test("should throw Error if lastError exists", () => {
33+
const errorMessage = "Some error";
34+
globalThis.chrome = {runtime: {lastError: {message: errorMessage}}} as any;
35+
expect(() => throwRuntimeError()).toThrow(errorMessage);
36+
});
37+
38+
test("should throw Error if WebExtension API is not available", () => {
39+
expect(() => throwRuntimeError()).toThrow("WebExtension API not available in this context");
40+
});
41+
});
42+
43+
describe("callWithPromise", () => {
44+
test("should resolve with result when successful", async () => {
45+
globalThis.chrome = {runtime: {lastError: undefined}} as any;
46+
const expectedResult = {foo: "bar"};
47+
const executor = (cb: any) => cb(expectedResult);
48+
49+
const result = await callWithPromise(executor);
50+
expect(result).toBe(expectedResult);
51+
});
52+
53+
test("should resolve with undefined when result is undefined", async () => {
54+
globalThis.chrome = {runtime: {lastError: undefined}} as any;
55+
const executor = (cb: any) => cb(undefined);
56+
57+
const result = await callWithPromise(executor);
58+
expect(result).toBeUndefined();
59+
});
60+
61+
test("should reject when lastError exists", async () => {
62+
const errorMessage = "Async error";
63+
globalThis.chrome = {runtime: {lastError: {message: errorMessage}}} as any;
64+
const executor = (cb: any) => cb(null);
65+
66+
await expect(callWithPromise(executor)).rejects.toThrow(errorMessage);
67+
});
68+
69+
test("should reject when lastError exists even if result is provided", async () => {
70+
const errorMessage = "Async error";
71+
globalThis.chrome = {runtime: {lastError: {message: errorMessage}}} as any;
72+
const executor = (cb: any) => cb({data: "some data"});
73+
74+
await expect(callWithPromise(executor)).rejects.toThrow(errorMessage);
75+
});
76+
77+
test("should reject when executor throws", async () => {
78+
const executor = () => {
79+
throw new Error("Executor error");
80+
};
81+
await expect(callWithPromise(executor)).rejects.toThrow("Executor error");
82+
});
83+
});
84+
85+
describe("safeListener", () => {
86+
test("should execute listener and return result", () => {
87+
const expectedResult = "success";
88+
const listener = jest.fn().mockReturnValue(expectedResult);
89+
const wrapped = safeListener(listener);
90+
91+
const result = wrapped("arg1");
92+
expect(listener).toHaveBeenCalledWith("arg1");
93+
expect(result).toBe(expectedResult);
94+
});
95+
96+
test("should catch sync error and log it", () => {
97+
const error = new Error("Sync fail");
98+
const listener = () => {
99+
throw error;
100+
};
101+
const wrapped = safeListener(listener);
102+
103+
const result = wrapped();
104+
expect(result).toBeUndefined();
105+
expect(console.error).toHaveBeenCalledWith("Listener error:", error);
106+
});
107+
108+
test("should catch promise rejection and log it", async () => {
109+
const error = new Error("Async fail");
110+
const listener = () => Promise.reject(error);
111+
const wrapped = safeListener(listener);
112+
113+
const result = wrapped();
114+
expect(result).toBeInstanceOf(Promise);
115+
116+
// Wait for promise rejection to be handled
117+
await new Promise(resolve => setTimeout(resolve, 0));
118+
expect(console.error).toHaveBeenCalledWith("Listener in promise error:", error);
119+
});
120+
});
121+
122+
describe("handleListener", () => {
123+
test("should add listener and return unsubscribe function", () => {
124+
const addListener = jest.fn();
125+
const removeListener = jest.fn();
126+
const target = {addListener, removeListener} as any;
127+
const callback = () => {};
128+
129+
const unsubscribe = handleListener(target, callback);
130+
131+
expect(addListener).toHaveBeenCalled();
132+
expect(typeof unsubscribe).toBe("function");
133+
134+
unsubscribe();
135+
expect(removeListener).toHaveBeenCalled();
136+
});
137+
});
138+
});

tsconfig.json

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
"strict": true,
44
"module": "ESNext",
55
"target": "ESNext",
6-
"moduleResolution": "Bundler",
6+
"moduleResolution": "node",
7+
"resolvePackageJsonExports": false,
78
"declaration": true,
8-
"emitDeclarationOnly": false,
9-
"resolvePackageJsonExports": true,
109
"esModuleInterop": true,
1110
"forceConsistentCasingInFileNames": true,
1211
"rootDir": ".",
@@ -31,8 +30,6 @@
3130
"node_modules/@types/node/*.d.ts"
3231
],
3332
"exclude": [
34-
"**/*.test.ts",
35-
"**/tests/**/*.ts",
3633
"dist"
3734
]
3835
}

0 commit comments

Comments
 (0)