@applicaster/zapp-react-native-utils
Version:
Applicaster Zapp React Native utilities package
412 lines (333 loc) • 11.2 kB
JavaScript
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);
});
});
});