2022-08-11 12:03:51 +00:00
|
|
|
import { fireEvent } from "@testing-library/dom";
|
2023-02-07 08:30:46 +00:00
|
|
|
import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
|
2022-08-11 12:03:51 +00:00
|
|
|
|
2022-09-13 13:15:52 +00:00
|
|
|
import { LocaleCode } from "../locales";
|
|
|
|
// eslint-disable-next-line
|
2022-12-05 16:17:17 +00:00
|
|
|
import {
|
|
|
|
actions,
|
|
|
|
ActionType,
|
|
|
|
AppBridge,
|
|
|
|
DispatchResponseEvent,
|
|
|
|
HandshakeEvent,
|
|
|
|
ThemeEvent,
|
|
|
|
} from ".";
|
2022-09-13 13:15:52 +00:00
|
|
|
|
2022-08-11 12:03:51 +00:00
|
|
|
// mock document.referrer
|
|
|
|
const origin = "http://example.com";
|
2022-08-22 13:53:03 +00:00
|
|
|
const domain = "saleor.domain.host";
|
2023-02-07 08:30:46 +00:00
|
|
|
const validJwtToken =
|
|
|
|
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6Ijk4ZTEzNDk4YmM5NThjM2QyNzk2NjY5Zjk0NzYxMzZkIn0.eyJpYXQiOjE2NjkxOTE4NDUsIm93bmVyIjoic2FsZW9yIiwiaXNzIjoiZGVtby5ldS5zYWxlb3IuY2xvdWQiLCJleHAiOjE2NjkyNzgyNDUsInRva2VuIjoic2JsRmVrWnVCSUdXIiwiZW1haWwiOiJhZG1pbkBleGFtcGxlLmNvbSIsInR5cGUiOiJ0aGlyZHBhcnR5IiwidXNlcl9pZCI6IlZYTmxjam95TWc9PSIsImlzX3N0YWZmIjp0cnVlLCJhcHAiOiJRWEJ3T2pJM05RPT0iLCJwZXJtaXNzaW9ucyI6W10sInVzZXJfcGVybWlzc2lvbnMiOlsiTUFOQUdFX1BBR0VfVFlQRVNfQU5EX0FUVFJJQlVURVMiLCJNQU5BR0VfUFJPRFVDVF9UWVBFU19BTkRfQVRUUklCVVRFUyIsIk1BTkFHRV9ESVNDT1VOVFMiLCJNQU5BR0VfUExVR0lOUyIsIk1BTkFHRV9TVEFGRiIsIk1BTkFHRV9QUk9EVUNUUyIsIk1BTkFHRV9TSElQUElORyIsIk1BTkFHRV9UUkFOU0xBVElPTlMiLCJNQU5BR0VfT0JTRVJWQUJJTElUWSIsIk1BTkFHRV9VU0VSUyIsIk1BTkFHRV9BUFBTIiwiTUFOQUdFX0NIQU5ORUxTIiwiTUFOQUdFX0dJRlRfQ0FSRCIsIkhBTkRMRV9QQVlNRU5UUyIsIklNUEVSU09OQVRFX1VTRVIiLCJNQU5BR0VfU0VUVElOR1MiLCJNQU5BR0VfUEFHRVMiLCJNQU5BR0VfTUVOVVMiLCJNQU5BR0VfQ0hFQ0tPVVRTIiwiSEFORExFX0NIRUNLT1VUUyIsIk1BTkFHRV9PUkRFUlMiXX0.PUyvuUlDvUBXMGSaexusdlkY5wF83M8tsjefVXOknaKuVgLbafvLOgx78YGVB4kdAybC7O3Yjs7IIFOzz5U80Q";
|
2022-08-22 13:53:03 +00:00
|
|
|
|
2022-08-11 12:03:51 +00:00
|
|
|
Object.defineProperty(window.document, "referrer", {
|
|
|
|
value: origin,
|
|
|
|
writable: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(window, "location", {
|
|
|
|
value: {
|
2022-08-22 13:53:03 +00:00
|
|
|
href: `${origin}?domain=${domain}&id=appid`,
|
2022-08-11 12:03:51 +00:00
|
|
|
},
|
|
|
|
writable: true,
|
|
|
|
});
|
|
|
|
|
2022-08-11 21:14:41 +00:00
|
|
|
const handshakeEvent: HandshakeEvent = {
|
|
|
|
payload: {
|
2023-02-07 08:30:46 +00:00
|
|
|
token: validJwtToken,
|
2022-08-11 21:14:41 +00:00
|
|
|
version: 1,
|
|
|
|
},
|
|
|
|
type: "handshake",
|
|
|
|
};
|
2022-08-11 12:03:51 +00:00
|
|
|
|
2022-09-06 14:02:24 +00:00
|
|
|
const themeEvent: ThemeEvent = {
|
|
|
|
type: "theme",
|
|
|
|
payload: {
|
|
|
|
theme: "light",
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
const delay = (timeout: number) =>
|
|
|
|
new Promise((res) => {
|
|
|
|
setTimeout(res, timeout);
|
|
|
|
});
|
|
|
|
|
2022-12-05 16:17:17 +00:00
|
|
|
const mockDashboardActionResponse = (actionType: ActionType, actionID: string) => {
|
|
|
|
function onMessage(event: MessageEvent) {
|
|
|
|
if (event.data.type === actionType) {
|
|
|
|
fireEvent(
|
|
|
|
window,
|
|
|
|
new MessageEvent("message", {
|
|
|
|
data: {
|
|
|
|
type: "response",
|
|
|
|
payload: { ok: true, actionId: actionID },
|
|
|
|
} as DispatchResponseEvent,
|
|
|
|
origin,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
window.addEventListener("message", onMessage);
|
|
|
|
|
|
|
|
return function cleanup() {
|
|
|
|
window.removeEventListener("message", onMessage);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-08-22 13:47:40 +00:00
|
|
|
describe("AppBridge", () => {
|
|
|
|
let appBridge = new AppBridge();
|
2022-08-11 21:14:41 +00:00
|
|
|
|
|
|
|
beforeEach(() => {
|
2022-08-22 13:47:40 +00:00
|
|
|
appBridge = new AppBridge();
|
2023-02-07 08:30:46 +00:00
|
|
|
|
|
|
|
vi.spyOn(console, "warn").mockImplementation(() => {
|
|
|
|
// noop
|
|
|
|
});
|
|
|
|
|
2022-12-05 16:17:17 +00:00
|
|
|
vi.spyOn(console, "error").mockImplementation(() => {
|
|
|
|
// noop
|
|
|
|
});
|
2022-08-11 21:14:41 +00:00
|
|
|
});
|
2022-08-11 12:03:51 +00:00
|
|
|
|
2023-02-07 08:30:46 +00:00
|
|
|
afterAll(() => {
|
|
|
|
vi.clearAllMocks();
|
|
|
|
});
|
|
|
|
|
2022-08-22 13:47:40 +00:00
|
|
|
it("correctly sets the default domain, if not set in constructor", () => {
|
|
|
|
expect(appBridge.getState().domain).toEqual(domain);
|
2022-08-11 12:03:51 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it("authenticates", () => {
|
2022-08-22 13:47:40 +00:00
|
|
|
expect(appBridge.getState().ready).toBe(false);
|
2022-08-11 12:03:51 +00:00
|
|
|
|
2023-02-07 08:30:46 +00:00
|
|
|
const token = validJwtToken;
|
2022-08-11 12:03:51 +00:00
|
|
|
fireEvent(
|
|
|
|
window,
|
|
|
|
new MessageEvent("message", {
|
|
|
|
data: { type: "handshake", payload: { token } },
|
|
|
|
origin,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
2022-08-22 13:47:40 +00:00
|
|
|
expect(appBridge.getState().ready).toBe(true);
|
|
|
|
expect(appBridge.getState().token).toEqual(token);
|
2022-08-11 12:03:51 +00:00
|
|
|
});
|
|
|
|
|
2022-08-11 21:14:41 +00:00
|
|
|
it("subscribes to an event and returns unsubscribe function", () => {
|
2022-08-11 12:03:51 +00:00
|
|
|
const callback = vi.fn();
|
2022-08-22 13:47:40 +00:00
|
|
|
const unsubscribe = appBridge.subscribe("handshake", callback);
|
2022-08-11 12:03:51 +00:00
|
|
|
|
|
|
|
expect(callback).not.toHaveBeenCalled();
|
|
|
|
|
2023-02-07 08:30:46 +00:00
|
|
|
const token = validJwtToken;
|
2022-08-11 21:14:41 +00:00
|
|
|
|
|
|
|
// First call proper event
|
2022-08-11 12:03:51 +00:00
|
|
|
fireEvent(
|
|
|
|
window,
|
|
|
|
new MessageEvent("message", {
|
2022-08-11 21:14:41 +00:00
|
|
|
data: handshakeEvent,
|
2022-08-11 12:03:51 +00:00
|
|
|
origin,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
// incorrect event type
|
|
|
|
fireEvent(
|
|
|
|
window,
|
|
|
|
new MessageEvent("message", {
|
|
|
|
data: { type: "invalid", payload: { token: "invalid" } },
|
|
|
|
origin,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
// incorrect origin
|
|
|
|
fireEvent(
|
|
|
|
window,
|
|
|
|
new MessageEvent("message", {
|
|
|
|
data: { type: "handshake", payload: { token } },
|
|
|
|
origin: "http://wrong.origin.com",
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(callback).toHaveBeenCalledTimes(1);
|
2022-08-11 21:14:41 +00:00
|
|
|
expect(callback).toHaveBeenCalledWith(handshakeEvent.payload);
|
2022-08-22 13:47:40 +00:00
|
|
|
expect(appBridge.getState().token).toEqual(handshakeEvent.payload.token);
|
|
|
|
expect(appBridge.getState().id).toEqual("appid");
|
2022-08-11 12:03:51 +00:00
|
|
|
|
|
|
|
unsubscribe();
|
|
|
|
|
|
|
|
fireEvent(
|
|
|
|
window,
|
|
|
|
new MessageEvent("message", {
|
2023-02-07 08:30:46 +00:00
|
|
|
data: { type: "handshake", payload: { token: validJwtToken } },
|
2022-08-11 12:03:51 +00:00
|
|
|
origin,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(callback).toHaveBeenCalledTimes(1);
|
2023-02-07 08:30:46 +00:00
|
|
|
expect(appBridge.getState().token).toEqual(validJwtToken);
|
2022-08-11 12:03:51 +00:00
|
|
|
});
|
|
|
|
|
2022-09-06 14:02:24 +00:00
|
|
|
it("Subscribes to theme change event and runs callback with new value after delay", async () => {
|
|
|
|
expect.assertions(2);
|
|
|
|
const callback = vi.fn();
|
|
|
|
|
|
|
|
const unsubscribe = appBridge.subscribe("theme", callback);
|
|
|
|
|
|
|
|
await delay(200);
|
|
|
|
|
|
|
|
fireEvent(
|
|
|
|
window,
|
|
|
|
new MessageEvent("message", {
|
|
|
|
data: themeEvent,
|
|
|
|
origin,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(callback).toHaveBeenCalledOnce();
|
|
|
|
expect(callback).toHaveBeenCalledWith({ theme: "light" });
|
|
|
|
|
|
|
|
unsubscribe();
|
|
|
|
});
|
|
|
|
|
2022-08-11 12:03:51 +00:00
|
|
|
it("persists domain", () => {
|
2022-08-22 13:47:40 +00:00
|
|
|
expect(appBridge.getState().domain).toEqual(domain);
|
2022-08-11 12:03:51 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it("dispatches valid action", () => {
|
|
|
|
const target = "/test";
|
|
|
|
const action = actions.Redirect({ to: target });
|
|
|
|
|
2022-12-05 16:17:17 +00:00
|
|
|
mockDashboardActionResponse(action.type, action.payload.actionId);
|
2022-08-11 12:03:51 +00:00
|
|
|
|
2022-08-22 13:47:40 +00:00
|
|
|
return expect(appBridge.dispatch(action)).resolves.toBeUndefined();
|
2022-08-11 12:03:51 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
it("times out after action response has not been registered", () =>
|
2022-08-22 13:47:40 +00:00
|
|
|
expect(appBridge.dispatch(actions.Redirect({ to: "/test" }))).rejects.toBeInstanceOf(Error));
|
2022-08-11 21:14:41 +00:00
|
|
|
|
|
|
|
it("unsubscribes from all listeners", () => {
|
|
|
|
const cb1 = vi.fn();
|
|
|
|
const cb2 = vi.fn();
|
|
|
|
|
2022-08-22 13:47:40 +00:00
|
|
|
appBridge.subscribe("handshake", cb1);
|
|
|
|
appBridge.subscribe("handshake", cb2);
|
2022-08-11 21:14:41 +00:00
|
|
|
|
|
|
|
fireEvent(
|
|
|
|
window,
|
|
|
|
new MessageEvent("message", {
|
|
|
|
data: handshakeEvent,
|
|
|
|
origin,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(cb1).toHaveBeenCalledTimes(1);
|
|
|
|
expect(cb2).toHaveBeenCalledTimes(1);
|
|
|
|
|
2022-08-22 13:47:40 +00:00
|
|
|
appBridge.unsubscribeAll();
|
2022-08-11 21:14:41 +00:00
|
|
|
|
|
|
|
fireEvent(
|
|
|
|
window,
|
|
|
|
new MessageEvent("message", {
|
|
|
|
data: handshakeEvent,
|
|
|
|
origin,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(cb1).toHaveBeenCalledTimes(1);
|
|
|
|
expect(cb2).toHaveBeenCalledTimes(1);
|
|
|
|
});
|
2022-08-22 13:47:40 +00:00
|
|
|
|
|
|
|
it("attaches domain from options in constructor", () => {
|
|
|
|
appBridge = new AppBridge({
|
|
|
|
targetDomain: "https://foo.bar",
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(appBridge.getState().domain).toEqual("https://foo.bar");
|
|
|
|
});
|
2022-09-13 13:15:52 +00:00
|
|
|
|
|
|
|
it.each<LocaleCode>(["pl", "en", "it"])("sets initial locale \"%s\" from constructor", (locale) => {
|
|
|
|
const instance = new AppBridge({
|
|
|
|
initialLocale: locale,
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(instance.getState().locale).toBe(locale);
|
|
|
|
});
|
2022-09-14 12:21:53 +00:00
|
|
|
|
|
|
|
it("Detects locale from URL param and set it to be initial", () => {
|
|
|
|
const localeToOverwrite = "pl";
|
|
|
|
|
|
|
|
const currentLocationHref = window.location.href;
|
|
|
|
|
|
|
|
window.location.href = `${origin}?domain=${domain}&id=appid&locale=${localeToOverwrite}`;
|
|
|
|
|
|
|
|
expect(new AppBridge().getState().locale).toBe(localeToOverwrite);
|
|
|
|
|
|
|
|
window.location.href = currentLocationHref;
|
|
|
|
});
|
2022-12-05 16:17:17 +00:00
|
|
|
|
2023-01-24 18:21:05 +00:00
|
|
|
it("Detects theme from URL param and set it to be initial", () => {
|
|
|
|
const themeToOverwrite = "dark";
|
|
|
|
|
|
|
|
const currentLocationHref = window.location.href;
|
|
|
|
|
|
|
|
window.location.href = `${origin}?domain=${domain}&id=appid&theme=${themeToOverwrite}`;
|
|
|
|
|
|
|
|
expect(new AppBridge().getState().theme).toBe(themeToOverwrite);
|
|
|
|
|
|
|
|
window.location.href = currentLocationHref;
|
2022-12-05 16:17:17 +00:00
|
|
|
});
|
2023-01-24 18:21:05 +00:00
|
|
|
|
|
|
|
it("dispatches 'notifyReady' action when enabled in constructor", () =>
|
|
|
|
new Promise<void>((res) => {
|
|
|
|
window.addEventListener("message", (event) => {
|
|
|
|
if (event.data.type === ActionType.notifyReady) {
|
|
|
|
res();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
appBridge = new AppBridge({ autoNotifyReady: true });
|
|
|
|
}));
|
2022-08-11 12:03:51 +00:00
|
|
|
});
|