diff --git a/src/common/utils/with-error-suppression/with-error-suppression.test.ts b/src/common/utils/with-error-suppression/with-error-suppression.test.ts new file mode 100644 index 0000000000..dc15762721 --- /dev/null +++ b/src/common/utils/with-error-suppression/with-error-suppression.test.ts @@ -0,0 +1,104 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { AsyncFnMock } from "@async-fn/jest"; +import asyncFn from "@async-fn/jest"; +import { getPromiseStatus } from "../../test-utils/get-promise-status"; +import { withErrorSuppression } from "./with-error-suppression"; + +describe("with-error-suppression", () => { + describe("given decorated sync function", () => { + let toBeDecorated: jest.Mock; + let decorated: (a: string, b: string) => void; + + beforeEach(() => { + toBeDecorated = jest.fn(); + + decorated = withErrorSuppression(toBeDecorated); + }); + + describe("when function does not throw", () => { + let returnValue: void; + + beforeEach(() => { + returnValue = decorated("some-parameter", "some-other-parameter"); + }); + + it("passes arguments to decorated function", () => { + expect(toBeDecorated).toHaveBeenCalledWith("some-parameter", "some-other-parameter"); + }); + + it("returns nothing", () => { + expect(returnValue).toBeUndefined(); + }); + }); + + describe("when function throws", () => { + let returnValue: void; + + beforeEach(() => { + // eslint-disable-next-line unused-imports/no-unused-vars-ts + toBeDecorated.mockImplementation((_, __) => { + throw new Error("some-error"); + }); + + returnValue = decorated("some-parameter", "some-other-parameter"); + }); + + it("passes arguments to decorated function", () => { + expect(toBeDecorated).toHaveBeenCalledWith("some-parameter", "some-other-parameter"); + }); + + it("returns nothing", () => { + expect(returnValue).toBeUndefined(); + }); + }); + }); + + describe("given decorated async function", () => { + let decorated: (a: string, b: string) => Promise; + let toBeDecorated: AsyncFnMock; + + beforeEach(() => { + toBeDecorated = asyncFn(); + + decorated = withErrorSuppression(toBeDecorated); + }); + + describe("when called", () => { + let returnValuePromise: Promise; + + beforeEach(() => { + returnValuePromise = decorated("some-parameter", "some-other-parameter"); + }); + + it("passes arguments to decorated function", () => { + expect(toBeDecorated).toHaveBeenCalledWith("some-parameter", "some-other-parameter"); + }); + + it("does not resolve yet", async () => { + const promiseStatus = await getPromiseStatus(returnValuePromise); + + expect(promiseStatus.fulfilled).toBe(false); + }); + + it("when call rejects, resolves with nothing", async () => { + await toBeDecorated.reject(new Error("some-error")); + + const returnValue = await returnValuePromise; + + expect(returnValue).toBeUndefined(); + }); + + it("when call resolves, resolves with nothing", async () => { + await toBeDecorated.resolve(undefined); + + const returnValue = await returnValuePromise; + + expect(returnValue).toBeUndefined(); + }); + }); + }); +}); diff --git a/src/common/utils/with-error-suppression/with-error-suppression.ts b/src/common/utils/with-error-suppression/with-error-suppression.ts new file mode 100644 index 0000000000..73d7e8b64c --- /dev/null +++ b/src/common/utils/with-error-suppression/with-error-suppression.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { noop } from "lodash/fp"; + +export function withErrorSuppression Promise>(toBeDecorated: TDecorated): (...args: Parameters) => Promise; +export function withErrorSuppression void>(toBeDecorated: TDecorated): (...args: Parameters) => void; + +export function withErrorSuppression(toBeDecorated: any) { + return (...args: any[]) => { + try { + const returnValue = toBeDecorated(...args); + + if (isPromise(returnValue)) { + return returnValue.catch(noop); + } + + return returnValue; + } catch (e) { + return undefined; + } + }; +} + +function isPromise(reference: any): reference is Promise { + return !!reference?.then; +}