@applicaster/zapp-react-native-utils
Version:
Applicaster Zapp React Native utilities package
574 lines (482 loc) • 15.9 kB
JavaScript
import * as R from "ramda";
import { HooksManager } from "../index";
import { HOOKS_EVENTS } from "../constants";
import riversWithHooks from "./fixtures/riverWithHooks.json";
const simpleRivers = {
A1234: {
id: "A1234",
hooks: {
preload_plugins: [
{
identifier: "hook_plugin",
type: "general",
weight: 1,
},
],
},
},
};
const payload = { foo: "bar" };
const getHookPlugin = (identifier, mock_response = {}) => ({
identifier,
type: "general",
module: {
run: jest.fn((payload, callback) => {
callback(
R.merge(
{ success: true, payload: { ...payload, identifier } },
mock_response
)
);
}),
isFlowBlocker: () => true,
},
configuration: { name: identifier },
});
describe("HooksManager", () => {
describe("when there are no hooks", () => {
const riverWithNoHook = [
{
id: "A1234",
hooks: {
preload_plugins: [],
},
},
];
const plugins = [];
const targetScreen = { id: "A1234" };
const hooksManager = HooksManager({
rivers: riverWithNoHook,
targetScreen,
plugins,
});
const successHandler = jest.fn();
it("invokes the success handler directly", () => {
hooksManager
.on(HOOKS_EVENTS.COMPLETE, successHandler)
.handleHooks(payload);
expect(successHandler).toHaveBeenCalledTimes(1);
expect(successHandler).toHaveBeenCalledWith(
{ payload, hookPlugin: { lastHook: true } },
expect.any(Function)
);
});
});
describe("when target route is playable", () => {
const rivers = simpleRivers;
const targetScreen = { screenType: "playable", id: "1234" };
const headlessPlayerHook = {
identifier: "headless_player_hook",
module: {
hasPlayerHook: true,
run: jest.fn((payload, callback) => {
callback({ success: true, payload });
}),
},
configuration: { config_field: "bar" },
};
const plugins = [headlessPlayerHook];
const hooksManager = HooksManager({
rivers,
targetScreen,
plugins,
});
const successHandler = jest.fn();
const completeHandler = jest.fn();
const presentScreenHandler = jest.fn();
it("executes the player hook", () => {
hooksManager
.on(HOOKS_EVENTS.PRESENT_SCREEN_HOOK, presentScreenHandler)
.on(HOOKS_EVENTS.SUCCESS, successHandler)
.on(HOOKS_EVENTS.COMPLETE, completeHandler)
.handleHooks(payload);
expect(headlessPlayerHook.module.run).toHaveBeenCalledWith(
payload,
expect.any(Function),
headlessPlayerHook.configuration
);
expect(successHandler).toHaveBeenCalledWith(
{ hookPlugin: expect.objectContaining(headlessPlayerHook), payload },
expect.any(Function)
);
expect(completeHandler).toHaveBeenCalledWith(
{ hookPlugin: expect.objectContaining(headlessPlayerHook), payload },
expect.any(Function)
);
});
});
describe("when there are preload hooks", () => {
describe("hook with screen", () => {
const hookScreenId = "6e15fcbf-9414-4ea8-b7db-a287593ab6bd";
const pluginScreenId = "1bb8ad39-a42e-4096-a134-56aadbd35c83";
const rivers = R.zipObj(
R.map(R.prop("id"), riversWithHooks),
riversWithHooks
);
const targetScreen = rivers[hookScreenId];
const hookScreen = getHookPlugin("hook_screen");
const plugins = [hookScreen];
const hooksManager = HooksManager({
rivers,
targetScreen,
plugins,
});
const successHandler = jest.fn();
const completeHandler = jest.fn();
const presentScreenHandler = jest.fn(
({ payload: { payload, callback } }) => {
callback({ success: true, payload });
}
);
it("executes the hook", () => {
hooksManager
.on(HOOKS_EVENTS.PRESENT_SCREEN_HOOK, presentScreenHandler)
.on(HOOKS_EVENTS.SUCCESS, successHandler)
.on(HOOKS_EVENTS.COMPLETE, completeHandler)
.handleHooks(payload);
expect(presentScreenHandler).toHaveBeenCalledWith(
{
route: `/hooks/${pluginScreenId}`,
hookPlugin: expect.objectContaining(hookScreen),
payload: {
payload,
hookPlugin: expect.objectContaining(hookScreen),
callback: expect.any(Function),
},
},
expect.any(Function)
);
expect(successHandler).toHaveBeenCalledWith(
{ payload, hookPlugin: expect.objectContaining(hookScreen) },
expect.any(Function)
);
expect(completeHandler).toHaveBeenCalledWith(
{ payload, hookPlugin: expect.objectContaining(hookScreen) },
expect.any(Function)
);
});
});
});
describe("when there are multiple hooks", () => {
const hookScreenId = "6e15fcbf-multiple-hooks";
const pluginScreenId = "1bb8ad39-a42e-4096-a134-56aadbd35c83";
const pluginScreenId2 = "1bb8ad39-hook_screen_2";
const rivers = R.zipObj(
R.map(R.prop("id"), riversWithHooks),
riversWithHooks
);
const targetScreen = rivers[hookScreenId];
const hookScreen = getHookPlugin("hook_screen");
const hookScreen2 = getHookPlugin("hook_screen_2");
const plugins = [hookScreen, hookScreen2];
const hooksManager = HooksManager({
rivers,
targetScreen,
plugins,
});
const successHandler = jest.fn();
const completeHandler = jest.fn();
const presentScreenHandler = jest.fn(({ route, payload }) => {
const modified_by = R.path(["payload", "modified_by"], payload) || [];
const hookPayload = R.merge(payload.payload, {
modified_by: R.append(route, modified_by),
});
payload.callback({ success: true, payload: hookPayload });
});
it("executes the hook", () => {
hooksManager
.on(HOOKS_EVENTS.PRESENT_SCREEN_HOOK, presentScreenHandler)
.on(HOOKS_EVENTS.SUCCESS, successHandler)
.on(HOOKS_EVENTS.COMPLETE, completeHandler)
.handleHooks(payload);
expect(presentScreenHandler).toHaveBeenCalledTimes(2);
expect(presentScreenHandler).toHaveBeenNthCalledWith(
1,
{
route: `/hooks/${pluginScreenId}`,
hookPlugin: expect.objectContaining(hookScreen),
payload: {
callback: expect.any(Function),
payload,
hookPlugin: expect.objectContaining(hookScreen),
},
},
expect.any(Function)
);
expect(presentScreenHandler).toHaveBeenNthCalledWith(
2,
{
route: `/hooks/${pluginScreenId2}`,
hookPlugin: expect.objectContaining(hookScreen2),
payload: {
payload: expect.objectContaining({
...payload,
modified_by: [`/hooks/${pluginScreenId}`],
}),
hookPlugin: expect.objectContaining(hookScreen2),
callback: expect.any(Function),
},
},
expect.any(Function)
);
expect(successHandler).toHaveBeenCalledTimes(2);
expect(successHandler).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
hookPlugin: expect.objectContaining(hookScreen),
payload: expect.objectContaining({
...payload,
modified_by: [`/hooks/${pluginScreenId}`],
}),
}),
expect.any(Function)
);
expect(successHandler).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
hookPlugin: expect.objectContaining(hookScreen2),
payload: expect.objectContaining({
...payload,
modified_by: [
`/hooks/${pluginScreenId}`,
`/hooks/${pluginScreenId2}`,
],
}),
}),
expect.any(Function)
);
expect(completeHandler).toHaveBeenCalledTimes(1);
expect(completeHandler).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
payload: {
...payload,
modified_by: [
`/hooks/${pluginScreenId}`,
`/hooks/${pluginScreenId2}`,
],
},
}),
expect.any(Function)
);
});
});
describe("hook with an failure and flow_blocker", () => {
const hookScreenId = "6e15fcbf-9414-4ea8-b7db-a287593ab6bd";
const rivers = R.zipObj(
R.map(R.prop("id"), riversWithHooks),
riversWithHooks
);
const targetScreen = rivers[hookScreenId];
const hookScreen = getHookPlugin("hook_screen");
const plugins = [hookScreen];
const hooksManager = HooksManager({
rivers,
targetScreen,
plugins,
});
const successHandler = jest.fn();
const completeHandler = jest.fn();
const cancelHandler = jest.fn();
const presentScreenHandler = jest.fn(
({ payload: { payload, callback } }) => {
callback({ success: false, payload });
}
);
it("stops the flow", () => {
hooksManager
.on(HOOKS_EVENTS.PRESENT_SCREEN_HOOK, presentScreenHandler)
.on(HOOKS_EVENTS.CANCEL, cancelHandler)
.on(HOOKS_EVENTS.SUCCESS, successHandler)
.on(HOOKS_EVENTS.COMPLETE, completeHandler)
.handleHooks(payload);
expect(cancelHandler).toHaveBeenCalledWith(
{ hookPlugin: expect.any(Object), payload },
expect.any(Function)
);
expect(successHandler).not.toHaveBeenCalled();
expect(completeHandler).not.toHaveBeenCalled();
});
});
describe("headless hook", () => {
const rivers = simpleRivers;
const targetScreen = rivers.A1234;
const hookPlugin = getHookPlugin("hook_plugin");
const plugins = [hookPlugin];
const successHandler = jest.fn();
const completeHandler = jest.fn();
const hooksManager = HooksManager({ rivers, targetScreen, plugins });
const payload = { foo: "bar" };
it("executes the hook", () => {
hooksManager
.on(HOOKS_EVENTS.SUCCESS, successHandler)
.on(HOOKS_EVENTS.COMPLETE, completeHandler)
.handleHooks(payload);
expect(successHandler).toHaveBeenCalledWith(
{
payload: { ...payload, identifier: "hook_plugin" },
hookPlugin: expect.objectContaining(hookPlugin),
},
expect.any(Function)
);
expect(completeHandler).toHaveBeenCalledWith(
{
payload: { ...payload, identifier: "hook_plugin" },
hookPlugin: expect.objectContaining(hookPlugin),
},
expect.any(Function)
);
});
});
describe("headless hook without implementation", () => {
const rivers = simpleRivers;
const targetScreen = rivers.A1234;
const hookPlugin = {
module: {},
identifier: "hook_plugin",
type: "general",
};
const plugins = [hookPlugin];
const successHandler = jest.fn();
const completeHandler = jest.fn();
const errorHandler = jest.fn();
const hooksManager = HooksManager({ rivers, targetScreen, plugins });
it("invokes the error handler but not the success handler", () => {
hooksManager
.on(HOOKS_EVENTS.SUCCESS, successHandler)
.on(HOOKS_EVENTS.COMPLETE, completeHandler)
.on(HOOKS_EVENTS.ERROR, errorHandler)
.handleHooks(payload);
expect(successHandler).not.toHaveBeenCalled();
expect(completeHandler).not.toHaveBeenCalled();
expect(errorHandler).toHaveBeenCalledWith(
{
error: new TypeError("hookPlugin.module.run is not a function"),
hookPlugin: expect.any(Object),
payload,
},
expect.any(Function)
);
});
});
describe("hook with an error", () => {
const rivers = simpleRivers;
const targetScreen = rivers.A1234;
const hookPlugin = getHookPlugin("hook_plugin", {
error: new Error("error"),
});
const plugins = [hookPlugin];
const successHandler = jest.fn();
const completeHandler = jest.fn();
const errorHandler = jest.fn();
const cancelHandler = jest.fn();
const hooksManager = HooksManager({ rivers, targetScreen, plugins });
it("invokes the error handler, and stops the flow", () => {
hooksManager
.on(HOOKS_EVENTS.SUCCESS, successHandler)
.on(HOOKS_EVENTS.COMPLETE, completeHandler)
.on(HOOKS_EVENTS.ERROR, errorHandler)
.on(HOOKS_EVENTS.CANCEL, cancelHandler)
.handleHooks(payload);
expect(errorHandler).toHaveBeenCalledWith(
{
error: new Error("error"),
hookPlugin: expect.any(Object),
payload: { ...payload, identifier: "hook_plugin" },
},
expect.any(Function)
);
expect(cancelHandler).not.toHaveBeenCalled();
expect(completeHandler).not.toHaveBeenCalled();
expect(successHandler).not.toHaveBeenCalled();
});
});
describe("multiple hooks", () => {
const rivers = {
A1234: {
id: "A1234",
hooks: {
preload_plugins: [
{
identifier: "hook_plugin",
type: "general",
weight: 1,
},
{
identifier: "hook_plugin_2",
type: "general",
weight: 1,
},
{
identifier: "hook_plugin_3",
type: "general",
weight: 2,
},
],
},
},
};
const targetScreen = rivers.A1234;
const hookPlugin = getHookPlugin("hook_plugin");
const hookPlugin2 = getHookPlugin("hook_plugin_2");
const hookPlugin3 = getHookPlugin("hook_plugin_3");
const plugins = [hookPlugin, hookPlugin2, hookPlugin3];
const successHandler = jest.fn();
const completeHandler = jest.fn();
const hooksManager = HooksManager({ rivers, targetScreen, plugins });
it("executes the hook", () => {
hooksManager
.on(HOOKS_EVENTS.COMPLETE, completeHandler)
.on(HOOKS_EVENTS.SUCCESS, successHandler)
.handleHooks(payload);
expect(successHandler).toHaveBeenCalledTimes(3);
expect(successHandler).toHaveBeenNthCalledWith(
1,
{
payload: { ...payload, identifier: "hook_plugin" },
hookPlugin: expect.objectContaining(hookPlugin),
},
expect.any(Function)
);
expect(successHandler).toHaveBeenNthCalledWith(
2,
{
payload: { ...payload, identifier: "hook_plugin_2" },
hookPlugin: expect.objectContaining(hookPlugin2),
},
expect.any(Function)
);
expect(successHandler).toHaveBeenNthCalledWith(
3,
{
payload: { ...payload, identifier: "hook_plugin_3" },
hookPlugin: expect.objectContaining(hookPlugin3),
},
expect.any(Function)
);
expect(completeHandler).toHaveBeenCalledTimes(1);
expect(completeHandler).toHaveBeenCalledWith(
{
payload: { ...payload, identifier: "hook_plugin_3" },
hookPlugin: expect.objectContaining(hookPlugin3),
},
expect.any(Function)
);
expect(hookPlugin.module.run).toHaveBeenCalledWith(
payload,
expect.any(Function),
hookPlugin.configuration
);
expect(hookPlugin2.module.run).toHaveBeenCalledWith(
{ ...payload, identifier: "hook_plugin" },
expect.any(Function),
hookPlugin2.configuration
);
expect(hookPlugin3.module.run).toHaveBeenCalledWith(
{ ...payload, identifier: "hook_plugin_2" },
expect.any(Function),
hookPlugin3.configuration
);
});
});
});