UNPKG

@beezwax/fmbond

Version:

A bridge for sending/receiving data between FileMaker and the web viewer

894 lines (832 loc) 27.3 kB
const rewire = require("rewire"); const jsdom = require("jsdom"); /* Setup */ const PROMISE_UUID = "4q5bt4y8-339c-44d3-96aj-50ee623oc6az"; const WEB_VIEWER = "wv"; const RELAY_SCRIPT = "FMBondRelay"; const createFMResponse = function ( promiseUUID = "", scriptResult = "", webViewerName = "wv", code = "0", message = "OK" ) { return { response: { promiseUUID, scriptResult, webViewerName, }, messages: [ { code, message, }, ], }; }; // Because of the quirk with FMBond hoisting itself to the window level, // we need to define the window scope before importing FMBond. let FMBondForWindow, FMBond; beforeAll(() => { const { JSDOM } = jsdom; const { window } = new JSDOM("<!DOCTYPE html>"); global.window = window; global.document = window.document; // First 'require' FMBond FMBondForWindow = require("../FMBond"); // Second 'rewire' FMBond FMBond = rewire("../FMBond"); fmbondWindow = FMBond.__set__("window", jest.fn()); }); afterAll(() => { fmbondWindow(); }); /* * ~~~~~~~~~~~~~~~~~~ TESTS ~~~~~~~~~~~~~~~~~~ */ describe("FMBond call parameters", () => { test("new keyword throws error", () => { expect.assertions(1); const formattedFMError = FMBond.__get__("formattedFMError"); const { NO_NEW_KEYWORD } = FMBond.__get__("FMBOND_ERROR_CODE"); const expectedError = formattedFMError( NO_NEW_KEYWORD, "Use of the new keyword is prohibited with FMBond" ); expect(() => new FMBond()).toThrow(expectedError); }); test("no parameters should return undefined", () => { expect.assertions(1); expect(FMBond()).not.toBeDefined(); }); test("non-string should return undefined", () => { expect.assertions(1); expect(FMBond({ test: "test" })).not.toBeDefined(); }); test("no-length string should return undefined", () => { expect.assertions(1); expect(FMBond("")).not.toBeDefined(); }); /* This test simulates a situation where the response from FileMaker is given to a web viewer that does contain FMBond, however was not the original web viewer the call to FileMaker came from. We purposefully throw here so that our FileMaker script knows that it failed. */ test("FMBond called with object with non-existent response.promiseUUID throws error", () => { expect.assertions(1); const formattedFMError = FMBond.__get__("formattedFMError"); const { INVALID_PARAMETER } = FMBond.__get__("FMBOND_ERROR_CODE"); const param = JSON.stringify(createFMResponse("non-existent-uuid-123")); const expectedError = formattedFMError( INVALID_PARAMETER, "FMBond was called with an invalid a promiseUUID" ); expect(() => FMBond(param)).toThrow(expectedError); }); test("valid FMResponse should perform handleCallFromRelayScript", () => { // setup const spy = jest.fn(); const resetHandleCallFromRelayScript = FMBond.__set__( "handleCallFromRelayScript", spy ); const resetCreateUUID = FMBond.__set__("createUUID", () => PROMISE_UUID); const resetPerformScriptSafely = FMBond.__set__("performScriptSafely", () => Promise.resolve() ); const responseObj = createFMResponse(PROMISE_UUID); const responseStr = JSON.stringify(responseObj); // test expect.assertions(2); FMBond.PerformScript("a"); FMBond(responseStr); expect(spy).toHaveBeenCalledWith(responseObj); expect(spy).toHaveBeenCalledTimes(1); // cleanup resetHandleCallFromRelayScript(); resetCreateUUID(); resetPerformScriptSafely(); }); test("valid FM Request should perform handleJSCallFromFM", () => { // setup const spy = jest.fn(); const resetHandleJSCallFromFM = FMBond.__set__("handleJSCallFromFM", spy); const validJSRequestParams = [ ["a"], ["a", 1, 2, 3], ["() => 'hey'"], ["(a,b) => a*b", 5, 10], ]; // test expect.assertions(validJSRequestParams.length + 1); validJSRequestParams.forEach((req) => { const [jsFunc, ...rest] = req; FMBond(jsFunc, ...rest); expect(spy).toHaveBeenLastCalledWith(jsFunc, rest); }); expect(spy).toHaveBeenCalledTimes(validJSRequestParams.length); // cleanup resetHandleJSCallFromFM(); }); }); describe("PerformScript", () => { let resetCreateUUID, resetPerformScriptSafely; beforeAll(() => { resetCreateUUID = FMBond.__set__("createUUID", () => PROMISE_UUID); resetPerformScriptSafely = FMBond.__set__("performScriptSafely", () => Promise.resolve() ); }); afterAll(() => { resetCreateUUID(); resetPerformScriptSafely(); }); beforeEach(() => { FMBond.__get__("promiseMap").clear(); FMBond.SetWebViewerName(""); }); test("should return js-formatted script results as resolved promises", () => { // setup const promiseMap = FMBond.__get__("promiseMap"); const scriptResultTests = [ { fromFM: "", expectedPromiseResult: "", toBe: "toBe", }, { fromFM: "1", expectedPromiseResult: 1, toBe: "toBe", }, { fromFM: "1.2", expectedPromiseResult: 1.2, toBe: "toBe", }, { fromFM: "test", expectedPromiseResult: "test", toBe: "toMatch", }, { fromFM: '[100,"200","abc"]', expectedPromiseResult: [100, "200", "abc"], toBe: "toMatchObject", }, { fromFM: '{"a": "b", "c":[1,2,3], "d": { "e": 1000, "f": ["g", "h"] }}', expectedPromiseResult: { a: "b", c: [1, 2, 3], d: { e: 1000, f: ["g", "h"] }, }, toBe: "toMatchObject", }, ]; // test expect.assertions(scriptResultTests.length * 4); scriptResultTests.forEach(async (test, idx) => { const { fromFM, expectedPromiseResult, toBe } = test; const responseStr = JSON.stringify( createFMResponse(PROMISE_UUID, fromFM, WEB_VIEWER) ); expect(FMBond.GetWebViewerName()).toMatch(idx === 0 ? "" : WEB_VIEWER); expect(promiseMap.get(PROMISE_UUID)).not.toBeDefined(); const deferredPromise = FMBond.PerformScript("a"); expect(promiseMap.get(PROMISE_UUID)).toBeDefined(); FMBond(responseStr); await deferredPromise; expect(deferredPromise).resolves[toBe](expectedPromiseResult); }); }); test("invalid script name should throw", async () => { // setup const { INVALID_PARAMETER } = FMBond.__get__("FMBOND_ERROR_CODE"); const formattedFMError = FMBond.__get__("formattedFMError"); const expectedResult = formattedFMError( INVALID_PARAMETER, "The script name parameter must be a valid string" ); const invalidScriptNames = ["", { test: "hey" }, undefined, null]; // test expect.assertions(invalidScriptNames.length); invalidScriptNames.forEach((scriptName) => { expect(() => FMBond.PerformScript(scriptName)).toThrow(expectedResult); }); }); test("missing relay script name should throw", async () => { // setup const { INVALID_PARAMETER } = FMBond.__get__("FMBOND_ERROR_CODE"); const formattedFMError = FMBond.__get__("formattedFMError"); const expectedResult = formattedFMError( INVALID_PARAMETER, "The script name parameter must be a valid string" ); const invalidScriptNames = ["", { test: "hey" }, undefined, null]; // test expect.assertions(invalidScriptNames.length); invalidScriptNames.forEach((scriptName) => { expect(() => FMBond.PerformScript(scriptName)).toThrow(expectedResult); }); }); describe("Options are handled correctly", () => { test("timeouts throw", async () => { // setup const { TIMEOUT_HIT } = FMBond.__get__("FMBOND_ERROR_CODE"); const formattedFMError = FMBond.__get__("formattedFMError"); const expectedResult = formattedFMError( TIMEOUT_HIT, "Failed to resolve promise within 10ms." ); // test expect.assertions(1); return expect( FMBond.PerformScript("a", "", { timeout: 10 }) ).rejects.toThrow(expectedResult); }); test("ignore result option should resolve, even with timeout", () => { expect.assertions(1); return expect( FMBond.PerformScript("a", "", { ignoreResult: true, timeout: 1 }) ).resolves.not.toBeDefined(); }); test("valid callTypes", async () => { // setup validCallTypes = [ null, "null", undefined, "undefined", "", 0, "0", "continue", 1, "1", "halt", 2, "2", "exit", 3, "3", "resume", 4, "4", "pause", 5, "5", "interrupt", ]; // test - since there are two methods of sending callType, we test for all valid types * 2 expect.assertions(validCallTypes.length * 2); const tests = validCallTypes.map(async (callType, idx) => { const uuid1 = `uuid1-${idx}`; const uuid2 = `uuid2-${idx}`; let secondCall = false; FMBond.__set__("createUUID", () => { secondCall = !secondCall; return secondCall ? uuid2 : uuid1; }); // two methods of sending callType const promise1 = FMBond.PerformScript("a", "", callType); const promise2 = FMBond.PerformScript("a", "", { callType }); FMBond(JSON.stringify(createFMResponse(uuid1))); FMBond(JSON.stringify(createFMResponse(uuid2))); return Promise.all([ expect(promise1).resolves.toMatch(""), expect(promise2).resolves.toMatch(""), ]); }); const allRun = await Promise.all(tests); // cleanup FMBond.__set__("createUUID", () => PROMISE_UUID); return allRun; }); test("invalid callTypes", async () => { // setup const invalidCallTypes = [ "invalidString", "999", 999, { callType: "invalid" }, { callType: "999" }, { callType: 999 }, { callType: { callType: "invalid" } }, ]; const { INVALID_PARAMETER } = FMBond.__get__("FMBOND_ERROR_CODE"); const formattedFMError = FMBond.__get__("formattedFMError"); const expectedResult = (myString) => formattedFMError( INVALID_PARAMETER, `${myString} is not a valid call type. Please use options 0 - 5` ); // tests expect.assertions(invalidCallTypes.length); invalidCallTypes.forEach((callType) => { let expectedCallType = typeof callType === "object" ? callType.callType : callType; const expected = expectedResult(`${expectedCallType}`.toLowerCase()); expect(() => FMBond.PerformScript("a", "", callType)).toThrow(expected); }); }); }); }); describe("RegisterScript", () => { test("invalid parameters", () => { // setup const { INVALID_PARAMETER } = FMBond.__get__("FMBOND_ERROR_CODE"); const formattedFMError = FMBond.__get__("formattedFMError"); const expectedError = formattedFMError( INVALID_PARAMETER, "Invalid plugin parameters: { exec, scriptName } are required plugin options." ); const invalidParams = [ "", "test", {}, { exec: "", scriptName: "test" }, { exec: "test", scriptName: "" }, undefined, null, ]; // test expect.assertions(invalidParams.length); invalidParams.forEach((param) => { expect(() => FMBond.RegisterScript(param)).toThrow(expectedError); }); }); test("registering a method that already exists should throw", () => { // setup const { INVALID_PARAMETER } = FMBond.__get__("FMBOND_ERROR_CODE"); const formattedFMError = FMBond.__get__("formattedFMError"); const expectedError = formattedFMError( INVALID_PARAMETER, "Unable to register PerformScript as this method already exists. Please use a different name." ); // test expect.assertions(1); expect(() => FMBond.RegisterScript({ exec: "PerformScript", scriptName: "test" }) ).toThrow(expectedError); }); test("registering works normally", () => { // setup FMBond.SetWebViewerName("wv"); const spy = jest.fn(() => Promise.resolve()); const fileMakerParams = { promiseUUID: PROMISE_UUID, mode: "PERFORM_SCRIPT", options: { callType: "interrupt", ignoreResult: false, webViewerName: "wv", }, script: { name: "My FileMaker Script", parameter: "my parameter", }, }; const resetPerformScriptSafely = FMBond.__set__("performScriptSafely", spy); const resetCreateUUID = FMBond.__set__("createUUID", () => PROMISE_UUID); // test expect.assertions(7); expect(FMBond.Run).not.toBeDefined(); expect( FMBond.RegisterScript({ exec: "Run", scriptName: "My FileMaker Script", throwIf: (res) => res === "NoCanDo!", }) ).toBe(FMBond); expect(FMBond.Run).toBeDefined(); // test good result const goodResult = FMBond.Run("my parameter", "interrupt"); expect(spy).toHaveBeenCalledWith(RELAY_SCRIPT, fileMakerParams, "5"); FMBond(JSON.stringify(createFMResponse(PROMISE_UUID, "YesWeCan!"))); expect(goodResult).resolves.toMatch("YesWeCan!"); // test result that throws const thrownResult = FMBond.Run("my parameter", "interrupt"); expect(spy).toHaveBeenCalledWith(RELAY_SCRIPT, fileMakerParams, "5"); FMBond(JSON.stringify(createFMResponse(PROMISE_UUID, "NoCanDo!"))); expect(thrownResult).rejects.toMatch("NoCanDo!"); // cleanup resetCreateUUID(); resetPerformScriptSafely(); FMBond.SetWebViewerName(""); }); }); describe("handleJSCallFromFM", () => { test("valid parameters", async () => { // setup FMBond.SetRelayScriptName("r"); const performScriptSafelySpy = jest.fn(); const resetPerformScriptSafely = FMBond.__set__( "performScriptSafely", performScriptSafelySpy ); // a valid parameter is a correctly-defined js function const validJSRequestParams = [ { request: ["Math.max", 1, 10], expectedResult: 10, }, { request: ["(a) => a", '{ "object": "test" }'], expectedResult: { object: "test" }, }, { request: ["Math.min", 1, 10], expectedResult: 1, }, { request: ["() => 'hey'"], expectedResult: "hey", }, { request: ["(a,b) => { return `${a}_${b}`; }", "my", "milkshake"], expectedResult: "my_milkshake", }, { request: [ "function(callback, param) { return callback(param); }", "ƒ(param) => { return param.toUpperCase() }", "milkshake", ], expectedResult: "MILKSHAKE", }, ]; // test expect.assertions(validJSRequestParams.length * 2); validJSRequestParams.forEach((req) => { const { request, expectedResult } = req; const [jsFunc, ...rest] = request; const result = FMBond(jsFunc, ...rest); const param = { mode: "RETURN_JAVASCRIPT_RESULT", result: { messages: [ { code: "0", message: "OK", }, ], response: { result: expectedResult, }, }, }; expect(performScriptSafelySpy).toHaveBeenLastCalledWith("r", param, "5"); expect(result).not.toBeDefined(); }); // cleanup FMBond.SetRelayScriptName(""); resetPerformScriptSafely(); }); test("invalid parameters", async () => { // setup FMBond.SetRelayScriptName("r"); const performScriptSafelySpy = jest.fn(); const resetPerformScriptSafely = FMBond.__set__( "performScriptSafely", performScriptSafelySpy ); // an invalid parameter is any incorrectly-defined js function const invalidJSRequestParams = [ ["a"], ["a(", 1, 2, 3], ["( => 'hey'"], ["function(a,b) => a*b", 5, 10], ["function(a,b)", 5, 10], ["function(a,b) { return `${a}_${b}`", 5, 10], ["fuction(a,b) { return `${a}_${b}` }", 5, 10], ["Math.idonotexist", 5, 10], ]; // test expect.assertions(invalidJSRequestParams.length); invalidJSRequestParams.forEach((req) => { const [jsFunc, ...rest] = req; FMBond(jsFunc, ...rest); const param = { mode: "RETURN_JAVASCRIPT_RESULT", result: { messages: [ { code: "-3", message: `The function '${jsFunc}' either (a) is missing from the global scope, or (b) is not a valid function definition`, }, ], response: {}, }, }; expect(performScriptSafelySpy).toHaveBeenLastCalledWith("r", param, "5"); }); // cleanup FMBond.SetRelayScriptName(""); resetPerformScriptSafely(); }); test("function throws are handled", async () => { // setup FMBond.SetRelayScriptName("r"); const performScriptSafelySpy = jest.fn(); const resetPerformScriptSafely = FMBond.__set__( "performScriptSafely", performScriptSafelySpy ); let expectedError; try { JSON.parse("{"); } catch (e) { expectedError = e.toString(); } const param = { mode: "RETURN_JAVASCRIPT_RESULT", result: { messages: [ { code: "-5", message: expectedError, }, ], response: {}, }, }; // test expect.assertions(1); FMBond("JSON.parse", "{"); expect(performScriptSafelySpy).toHaveBeenLastCalledWith("r", param, "5"); // cleanup FMBond.SetRelayScriptName(""); resetPerformScriptSafely(); }); test("Node.js server returns result", async () => { // setup const performScriptSafelySpy = jest.fn(); const resetPerformScriptSafely = FMBond.__set__( "performScriptSafely", performScriptSafelySpy ); // undefine the window const oldWindow = FMBond.__get__("window"); FMBond.__set__("window", undefined); // test expect.assertions(3); const expectedResult1 = { messages: [{ code: "0", message: "OK" }], response: { result: 10 }, }; const expectedResult2 = { messages: [ { code: "-5", message: "SyntaxError: Unexpected end of JSON input", }, ], response: {}, }; const result1 = FMBond("(a) => a + 9", "1"); expect(result1).toEqual(expectedResult1); const result2 = FMBond("JSON.parse", "{"); expect(result2).toEqual(expectedResult2); expect(performScriptSafelySpy).not.toHaveBeenCalled(); // cleanup FMBond.__set__("window", oldWindow); resetPerformScriptSafely(); }); }); describe("Getters & Setters", () => { beforeEach(() => { FMBond.SetRelayScriptName(""); FMBond.SetWebViewerName(""); FMBond.SetWebViewerConfig({}); }); afterAll(() => { FMBond.SetRelayScriptName(RELAY_SCRIPT); FMBond.SetWebViewerName(WEB_VIEWER); FMBond.SetWebViewerConfig({}); }); test("web viewer name", () => { expect.assertions(3); expect(FMBond.GetWebViewerName()).toMatch(""); expect(FMBond.SetWebViewerName("test")).toEqual(FMBond); expect(FMBond.GetWebViewerName()).toMatch("test"); }); test("relay script name", () => { expect.assertions(3); // Defaults to RELAY_SCRIPT when not set expect(FMBond.GetRelayScriptName()).toMatch(RELAY_SCRIPT); expect(FMBond.SetRelayScriptName("test")).toEqual(FMBond); expect(FMBond.GetRelayScriptName()).toMatch("test"); }); test("web viewer config", () => { expect.assertions(3); const expectedConfig = { my: "config", }; expect(FMBond.GetWebViewerConfig()).toMatchObject({}); expect(FMBond.SetWebViewerConfig(expectedConfig)).toEqual(FMBond); expect(FMBond.GetWebViewerConfig()).toMatchObject({ my: "config", }); }); test("SetWebViewerNameFromFM when name is already set", () => { // setup FMBond.SetWebViewerName("wvNameWhenAlreadySet"); // test - when name is already set expect.assertions(2); return FMBond.SetWebViewerNameFromFM().then((res) => { expect(res).toMatch(""); expect(FMBond.GetWebViewerName()).toMatch("wvNameWhenAlreadySet"); }); }); test("SetWebViewerNameFromFM when name not set", () => { // setup const performScriptSafelySpy = jest.fn(() => Promise.resolve()); const resetPerformScriptSafely = FMBond.__set__( "performScriptSafely", performScriptSafelySpy ); const resetCreateUUID = FMBond.__set__("createUUID", () => PROMISE_UUID); const fmReponse = JSON.stringify( createFMResponse(PROMISE_UUID, "", "testWVname") ); FMBond.SetRelayScriptName("testRelayScriptName"); // test expect.assertions(5); expect(FMBond.GetWebViewerName()).toMatch(""); const promise = FMBond.SetWebViewerNameFromFM(); FMBond(fmReponse); return promise.then((res) => { expect(res).toMatch(""); expect(FMBond.GetWebViewerName()).toMatch("testWVname"); expect(performScriptSafelySpy).toHaveBeenCalledTimes(1); expect(performScriptSafelySpy).toHaveBeenCalledWith( "testRelayScriptName", { mode: "PERFORM_SCRIPT", options: { ignoreResult: false, webViewerName: "" }, promiseUUID: PROMISE_UUID, script: { name: "testRelayScriptName", parameter: { mode: "GET_WEB_VIEWER_NAME" }, }, }, "0" ); // cleanup resetPerformScriptSafely(); resetCreateUUID(); }); }); test("Sync Config", () => { // setup const config = { my: "config", }; const fmReponse = Promise.resolve(config); // PS = PerformScript const PSspy = jest.fn(() => Promise.resolve(fmReponse)); const resetPS = FMBond.__set__("FMBond.PerformScript", PSspy); FMBond.SetWebViewerName("wvName"); FMBond.SetRelayScriptName("rName"); // test expect.assertions(5); expect(FMBond.GetWebViewerName()).toMatch("wvName"); expect(FMBond.GetWebViewerConfig()).toMatchObject({}); return FMBond.SyncConfig().then((res) => { const expectedConfig = { my: "config", }; expect(res).toEqual(expectedConfig); expect(FMBond.GetWebViewerConfig()).toMatchObject(expectedConfig); expect(PSspy).toHaveBeenCalledWith("rName", { mode: "GET_CONFIG", options: { webViewerName: "wvName" }, }); // cleanup resetPS(); }); }); }); describe("Private utility methods work as intended", () => { describe("performScriptSafely", () => { test("works regularly", () => { // setup const FileMaker = function () {}; FileMaker.PerformScript = jest.fn(); FileMaker.PerformScriptWithOption = jest.fn(); const resetFileMaker = FMBond.__set__("FileMaker", FileMaker); const performScriptSafely = FMBond.__get__("performScriptSafely"); // test expect.assertions(3); expect(performScriptSafely("a", "b")).resolves.not.toBeDefined(); expect(FileMaker.PerformScript).not.toHaveBeenCalled(); expect(FileMaker.PerformScriptWithOption).toHaveBeenCalledWith( "a", "b", "0" ); // cleanup resetFileMaker(); }); test("Throws when FileMaker object is not available within 1000ms", async () => { // setup jest.useFakeTimers(); const performScriptSafely = FMBond.__get__("performScriptSafely"); const formattedFMError = FMBond.__get__("formattedFMError"); const { FM_NOT_FOUND } = FMBond.__get__("FMBOND_ERROR_CODE"); const expectedError = formattedFMError( FM_NOT_FOUND, "FileMaker object not found" ); // test expect.assertions(1); const promise = performScriptSafely("a", "b"); jest.advanceTimersByTime(1110); jest.useRealTimers(); return expect(promise).rejects.toThrow(expectedError); }); test("Safely waits for FileMaker to become available within 1000ms", async () => { // setup let runSetInterval; const setIntervalSpy = jest.fn((callback) => { runSetInterval = jest.fn(() => callback()); }); const resetSetInterval = FMBond.__set__("setInterval", setIntervalSpy); const FileMaker = function () {}; FileMaker.PerformScriptWithOption = jest.fn(); const performScriptSafely = FMBond.__get__("performScriptSafely"); // test expect.assertions(6); expect(FMBond.FileMaker).not.toBeDefined(); const pendingPromise = performScriptSafely("a", "b"); // simulate the passing of 50 milliseconds for (let i = 0; i < 50; i++) { runSetInterval(); } expect(runSetInterval).toHaveBeenCalledTimes(50); expect(FileMaker.PerformScriptWithOption).not.toHaveBeenCalled(); // simulate FileMaker being available in the global scope const resetFileMaker = FMBond.__set__("FileMaker", FileMaker); // simulate the passing of 1 more millisecond runSetInterval(); expect(FileMaker.PerformScriptWithOption).toHaveBeenCalledTimes(1); expect(FileMaker.PerformScriptWithOption).toHaveBeenCalledWith( "a", "b", "0" ); await pendingPromise; expect(pendingPromise).resolves.not.toBeDefined(); resetSetInterval(); resetFileMaker(); }); }); test("formattedFMError", () => { // setup const expected = new Error( JSON.stringify({ messages: [ { code: "1", message: "test message", }, ], response: { test: "TEST" }, }) ); const formattedFMError = FMBond.__get__("formattedFMError"); const received = formattedFMError("1", "test message", { test: "TEST" }); // test expect.assertions(1); expect(received.message).toMatch(expected.message); }); test("createUUID", () => { expect.assertions(1); const createUUID = FMBond.__get__("createUUID"); expect(createUUID()).toMatch( /[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}/ ); }); test("tryParse / tryStringify", () => { expect.assertions(6); const tryStringify = FMBond.__get__("tryStringify"); const tryParse = FMBond.__get__("tryParse"); const testStr = '{"abc":123}'; const testObj = { abc: 123 }; expect(tryParse(testStr)).toMatchObject(testObj); expect(tryParse(testObj)).toMatchObject(testObj); expect(tryStringify(testObj)).toBe(testStr); expect(tryStringify(testStr)).toBe(testStr); expect(tryStringify(1)).toBe("1"); expect(tryStringify([1, 2, 3])).toBe("[1,2,3]"); }); }); describe("Scopes", () => { test("Window & Global scopes have FMBond defined", () => { expect.assertions(4); expect(global.window).toHaveProperty("FMBond"); expect(global.window.FMBond).toBe(FMBondForWindow); expect(global).toHaveProperty("FMBond"); expect(global.FMBond).toBe(FMBondForWindow); }); });