Skip to content

Commit 7475ebf

Browse files
author
João Dias
committed
feat(callIfExists): improved type safety
1 parent 5fc769d commit 7475ebf

File tree

3 files changed

+108
-17
lines changed

3 files changed

+108
-17
lines changed

cypress/test/functions/utlities/call-if-exists.cy.ts

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,102 @@
33
*
44
* (c) 2024 Feedzai
55
*/
6-
import { callIfExists } from "src/functions";
6+
import { callIfExists } from "src/functions/utilities/call-if-exists";
77

8-
describe("callIfExist", () => {
8+
describe("callIfExists", () => {
99
it("should not throw an exception when no callback is passed", () => {
1010
expect(callIfExists.bind(this, undefined)).not.throw();
1111
});
1212

13+
it("should not throw an exception when null callback is passed", () => {
14+
expect(callIfExists.bind(this, null)).not.throw();
15+
});
16+
17+
it("should not throw an exception when non-function is passed", () => {
18+
// @ts-expect-error - Testing invalid input
19+
expect(callIfExists.bind(this, "not a function")).not.throw();
20+
// @ts-expect-error - Testing invalid input
21+
expect(callIfExists.bind(this, 42)).not.throw();
22+
// @ts-expect-error - Testing invalid input
23+
expect(callIfExists.bind(this, {})).not.throw();
24+
});
25+
1326
it("should call the function passed", () => {
1427
const func = cy.stub();
15-
1628
callIfExists(func);
17-
1829
expect(func).to.have.been.called;
1930
});
31+
32+
it("should pass arguments to the callback function", () => {
33+
const func = cy.stub();
34+
callIfExists(func, "arg1", 42, { key: "value" });
35+
expect(func).to.have.been.calledWith("arg1", 42, { key: "value" });
36+
});
37+
38+
it("should return the result of the callback function", () => {
39+
const result = callIfExists(() => "test");
40+
expect(result).to.equal("test");
41+
});
42+
43+
it("should return undefined when callback is not a function", () => {
44+
expect(callIfExists(undefined)).to.be.undefined;
45+
expect(callIfExists(null)).to.be.undefined;
46+
// @ts-expect-error - Testing invalid input
47+
expect(callIfExists("not a function")).to.be.undefined;
48+
// @ts-expect-error - Testing invalid input
49+
expect(callIfExists(42)).to.be.undefined;
50+
// @ts-expect-error - Testing invalid input
51+
expect(callIfExists({})).to.be.undefined;
52+
});
53+
54+
it("should handle functions with different return types", () => {
55+
expect(callIfExists(() => 42)).to.equal(42);
56+
expect(callIfExists(() => "string")).to.equal("string");
57+
expect(callIfExists(() => true)).to.be.true;
58+
expect(callIfExists(() => ({ key: "value" }))).to.deep.equal({ key: "value" });
59+
expect(callIfExists(() => [1, 2, 3])).to.deep.equal([1, 2, 3]);
60+
});
61+
62+
it("should handle functions with different parameter types", () => {
63+
const stringFunc = (str: string) => str.toUpperCase();
64+
const numberFunc = (num: number) => num * 2;
65+
const objectFunc = (obj: { key: string }) => obj.key;
66+
const arrayFunc = (arr: number[]) => arr.reduce((a, b) => a + b, 0);
67+
68+
// @ts-expect-error - Testing type compatibility
69+
expect(callIfExists(stringFunc, "test")).to.equal("TEST");
70+
// @ts-expect-error - Testing type compatibility
71+
expect(callIfExists(numberFunc, 21)).to.equal(42);
72+
// @ts-expect-error - Testing type compatibility
73+
expect(callIfExists(objectFunc, { key: "value" })).to.equal("value");
74+
// @ts-expect-error - Testing type compatibility
75+
expect(callIfExists(arrayFunc, [1, 2, 3])).to.equal(6);
76+
});
77+
78+
it("should handle async functions", async () => {
79+
const asyncFunc = async () => "async result";
80+
const result = await callIfExists(asyncFunc);
81+
expect(result).to.equal("async result");
82+
});
83+
84+
it("should handle functions that throw errors", () => {
85+
const errorFunc = () => {
86+
throw new Error("test error");
87+
};
88+
expect(() => callIfExists(errorFunc)).to.throw("test error");
89+
});
90+
91+
it("should handle functions with rest parameters", () => {
92+
const sum = (...numbers: number[]) => numbers.reduce((a, b) => a + b, 0);
93+
// @ts-expect-error - Testing type compatibility
94+
expect(callIfExists(sum, 1, 2, 3, 4)).to.equal(10);
95+
});
96+
97+
it("should handle functions with optional parameters", () => {
98+
const greet = (name: string, greeting = "Hello") => `${greeting}, ${name}!`;
99+
// @ts-expect-error - Testing type compatibility
100+
expect(callIfExists(greet, "John")).to.equal("Hello, John!");
101+
// @ts-expect-error - Testing type compatibility
102+
expect(callIfExists(greet, "John", "Hi")).to.equal("Hi, John!");
103+
});
20104
});

docs/docs/functions/utils/call-if-exists.mdx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ Executes the given callback function, if present.
66
## API
77

88
```typescript
9-
function callIfExists<GenericFunctionType>(callback: GenericFunctionType, ...args: any[]): void;
9+
function callIfExists<T extends (...args: unknown[]) => ReturnType<T>>(
10+
callback: T | undefined | null,
11+
...args: Parameters<T>
12+
): ReturnType<T> | undefined
1013
```
1114

1215
### Usage

src/functions/utilities/call-if-exists.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,34 @@
44
* (c) 2024 Feedzai
55
*/
66

7+
import { isFunction } from "../typed";
8+
79
/**
8-
* Executes the given callback function, if it exists.
10+
* Safely executes a callback function if it exists.
11+
*
12+
* @template T - The type of the callback function
913
*
1014
* @param callback - The function to be executed if it exists
1115
* @param args - Arguments to be passed to the callback
1216
*
17+
* @returns The result of the callback function, or undefined if callback is not a function
18+
*
1319
* @example
14-
* // Calling a function with several arguments
20+
* // Basic usage
1521
* callIfExists((a: number, b: string) => console.log(a, b), 1, "test");
1622
*
17-
* // Calling a function without any argument
23+
* // No arguments
1824
* callIfExists(() => console.log("Hello"));
1925
*
20-
* // Nothing happens if the callback is not a function
21-
* callIfExists(undefined);
22-
* callIfExists(null);
23-
* callIfExists("not a function");
26+
* // Non-function callback
27+
* callIfExists(undefined); // => undefined
28+
* callIfExists(null); // => undefined
2429
*/
25-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
26-
export function callIfExists<T extends (...args: any[]) => any>(
30+
export function callIfExists<T extends (...args: unknown[]) => ReturnType<T>>(
2731
callback: T | undefined | null,
2832
...args: Parameters<T>
29-
): void {
30-
if (typeof callback === "function") {
31-
callback(...args);
33+
): ReturnType<T> | undefined {
34+
if (isFunction(callback)) {
35+
return callback(...args);
3236
}
3337
}

0 commit comments

Comments
 (0)