UNPKG

@applicaster/zapp-react-native-utils

Version:

Applicaster Zapp React Native utilities package

412 lines (333 loc) • 11.2 kB
import * as R from "ramda"; const { debounce, noop, subscriber, parseJsonIfNeeded, isFunction, extractDataFromProperty, clearObject, } = require("../index"); const DATA = { id: 100, extensions: { free: true, playlistId: "playlist1000", stuff: "always", }, }; const VAL = { propToExtract: "property", path: "extensions.playlistId", nonExistent: "extensions.nope", }; const MAPPING = { showOne: { id: 1, property: VAL.path, }, showTwo: { id: 2, property: VAL.nonExistent, }, showThree: { id: 3, }, }; class Context { constructor() { this.isContextClass = true; } } // Here we need to use the standard function declaration // to be able to mock fn without automatically attaching // the mock to the jest global object const fn = jest.fn(function (...args) { const that = this || global; that.debouncedArgs = args; }); const wait = 10; const context = new Context(); const debouncedArgs = ["foo", 42]; function clearDebouncedArgsFromContext(that) { if (that.debouncedArgs) { delete that.debouncedArgs; } } function clearMocks() { fn.mockClear(); clearDebouncedArgsFromContext(global); clearDebouncedArgsFromContext(context); } function resolveAfterTimeout(func) { return new Promise((resolve) => { setTimeout(() => { func(); resolve(); }, wait + 1); }); } describe("functionUtils", () => { describe("debounce", () => { beforeEach(clearMocks); describe("when immediate is false", () => { beforeEach(clearMocks); it("calls the debounced function only once after the delay", () => { const debounced = debounce({ fn, wait, immediate: false }); debounced(...debouncedArgs); debounced(...debouncedArgs); expect(fn).not.toHaveBeenCalled(); return resolveAfterTimeout(() => { expect(fn).toHaveBeenCalledTimes(1); expect(fn).toHaveBeenNthCalledWith(1, ...debouncedArgs); expect(fn).not.toHaveBeenCalledTimes(2); expect.assertions(4); }); }); }); describe("when immediate is true", () => { beforeEach(clearMocks); it("calls the debounced function only once during the delay", () => { const debounced = debounce({ fn, wait }); debounced(...debouncedArgs); debounced(...debouncedArgs); expect(fn).toHaveBeenCalledTimes(1); expect(fn).toHaveBeenNthCalledWith(1, ...debouncedArgs); return resolveAfterTimeout(() => { expect(fn).not.toHaveBeenCalledTimes(2); expect.assertions(3); }); }); }); describe("when no context is provided", () => { beforeEach(clearMocks); it("invokes the debounce function on the current context", () => { const debounced = debounce({ fn, wait }); debounced(...debouncedArgs); return resolveAfterTimeout(() => { expect(global.debouncedArgs).toEqual(debouncedArgs); expect(context.debouncedArgs).toBeUndefined(); }); }); }); describe("when a context is provided", () => { beforeEach(clearMocks); it("invokes the debounce function on the provided context", (done) => { const debounced = debounce({ fn, wait, context }); debounced(...debouncedArgs); resolveAfterTimeout(() => { expect(global.debouncedArgs).toBeUndefined(); expect(context.debouncedArgs).toEqual(debouncedArgs); done(); }); }); }); }); describe("noop", () => { it("does nothing", () => { expect(noop()).toBeUndefined(); }); }); describe("clearObject", () => { it("should clear original object without changing the reference", () => { // arrange const originalObject = { handlers: { a: [1, 2, 3], b: { c: 1, d: 2 }, c: 3 }, }; const objCopy1 = originalObject.handlers; const objCopy2 = originalObject.handlers; // act clearObject(originalObject); // assert expect(objCopy1).not.toContain("a"); expect(objCopy2).not.toContain("b"); }); }); describe("subscriber", () => { const initialObj = { foo: "bar", method() { return this.foo; }, }; const handler = jest.fn(); const handler2 = jest.fn(); const event = "some_event"; const args = ["foo", { bar: 42 }]; beforeEach(() => { handler.mockClear(); handler2.mockClear(); }); it("returns the original object with additional methods", () => { const expectedInitialObj = Object.assign({}, initialObj); expect(subscriber(expectedInitialObj)).toMatchSnapshot(); expect(expectedInitialObj.handlers).not.toBeUndefined(); }); it("can be invoked without args", () => { expect(subscriber()).toMatchSnapshot(); }); describe("on", () => { it("returns the object so handlers can be chained", () => { const subscribable = subscriber(); expect(subscribable.on(event, handler)).toEqual(subscribable); }); }); describe("invokeHandler", () => { beforeEach(() => { handler.mockClear(); handler2.mockClear(); }); it("returns the object so handlers invokation can be chained", () => { const subscribable = subscriber(); expect(subscribable.invokeHandler(event, ...args)).toEqual( subscribable ); }); it("invokes the declared handlers", () => { subscriber() .on(event, handler) .on(event, handler2) .invokeHandler(event, ...args); expect(handler).toHaveBeenCalledTimes(1); expect(handler2).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledWith(...args, expect.any(Function)); expect(handler2).toHaveBeenCalledWith(...args, expect.any(Function)); }); }); describe("dispose", () => { const handlerWithDispose = jest.fn((...handlerArgs) => { const dispose = R.last(handlerArgs); dispose(); }); const handlerWithDisposeAll = jest.fn((...handlerArgs) => { const dispose = R.last(handlerArgs); dispose({ disposeAll: true }); }); beforeEach(() => { handler.mockClear(); handler2.mockClear(); handlerWithDispose.mockClear(); handlerWithDisposeAll.mockClear(); }); it("removes the specific handler", () => { const subscriberObj = subscriber() .on(event, handlerWithDispose) .on(event, handler); subscriberObj.invokeHandler(event, ...args); expect(handlerWithDispose).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledTimes(1); subscriberObj.invokeHandler(event, ...args); expect(handlerWithDispose).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledTimes(2); expect(subscriberObj.handlers[event]).toHaveLength(1); }); it("removes all the handlers if the specific option is provided", () => { const subscriberObj = subscriber() .on(event, handlerWithDisposeAll) .on(event, handler); expect(subscriberObj).toMatchObject({ invokeHandler: expect.any(Function), removeHandler: expect.any(Function), on: expect.any(Function), }); expect(subscriberObj?.handlers?.[event]).toHaveLength(2); subscriberObj.invokeHandler(event, ...args); expect(handlerWithDisposeAll).toHaveBeenCalledTimes(1); expect(handler).toHaveBeenCalledTimes(1); expect(subscriberObj.handlers?.[event]).not.toBeDefined(); subscriberObj.invokeHandler(event, ...args); expect(handler).toHaveBeenCalledTimes(1); expect(handlerWithDisposeAll).toHaveBeenCalledTimes(1); expect(subscriberObj.handlers?.[event]).not.toBeDefined(); }); it("should utilize existing subscriber object", () => { const mockExistingHandler = jest.fn(); const subscriberObjects = Object.create({ [event]: { handlers: { [event]: [mockExistingHandler], }, }, }); const subscriberObj = subscriber(subscriberObjects[event]).on( event, handler ); expect(subscriberObj).toMatchObject({ invokeHandler: expect.any(Function), removeHandler: expect.any(Function), on: expect.any(Function), }); expect(subscriberObj.handlers[event][0]).toBe(mockExistingHandler); expect(subscriberObj.handlers[event][1]).toBe(handler); expect(subscriberObj?.handlers?.[event]).toHaveLength(2); subscriberObj.invokeHandler(event, ...args); expect(handler).toHaveBeenCalledTimes(1); expect(mockExistingHandler).toHaveBeenCalledTimes(1); expect(subscriberObj.handlers?.[event]).toBeDefined(); expect(subscriberObj.handlers?.[event]).toHaveLength(2); }); }); }); describe("parseJsonIfNeeded", () => { const data = { foo: "bar" }; const json = JSON.stringify(data); describe("when parsing a json", () => { it("returns the object", () => { expect(parseJsonIfNeeded(json)).toEqual(data); }); }); describe("when parsing data that is not a json", () => { it("returns the initial value", () => { expect(parseJsonIfNeeded(data)).toEqual(data); }); }); }); describe("isFunction", () => { it("return true if named function is passed", () => { function foo() { return null; } const currentResult = isFunction(foo); expect(currentResult).toBe(true); }); it("return true if arrow function is passed", () => { const currentResult = isFunction(() => null); expect(currentResult).toBe(true); }); it("return true if anonymous function is passed", () => { const currentResult = isFunction(function () { return null; }); expect(currentResult).toBe(true); }); it("returns false otherwise", () => { expect(isFunction({})).toBe(false); expect(isFunction([])).toBe(false); expect(isFunction(false)).toBe(false); expect(isFunction(null)).toBe(false); expect(isFunction(undefined)).toBe(false); expect(isFunction(0)).toBe(false); expect(isFunction("foo")).toBe(false); expect(isFunction(123)).toBe(false); expect(isFunction(true)).toBe(false); }); }); describe("extractDataFromProperty", () => { it("returns the value of the path if exists", () => { expect( extractDataFromProperty(VAL.propToExtract, DATA)(MAPPING.showOne) ).toEqual(DATA.extensions.playlistId); }); it("returns null if the path or prop does not exist", () => { expect( extractDataFromProperty(VAL.propToExtract, DATA)(MAPPING.showTwo) ).toBe(null); }); it("returns null if the property does not exist on mapped object", () => { expect( extractDataFromProperty(VAL.propToExtract, DATA)(MAPPING.showThree) ).toBe(null); }); }); });