From 3797fde39948579d1ad32469e31d2c2d104ecc38 Mon Sep 17 00:00:00 2001 From: Charlie Dibble Date: Tue, 23 Jan 2024 14:28:40 -0800 Subject: [PATCH] chore: migrate to vitest (#181) * started test driving data collector util * add tests and todos, break fns down a bit * add fraudnet util * update correct fn url * cleanup * outline of fraudnet work * ohhhh boy, its messy vitest/flow stuff * more test crazyiness * moving server tests and client api to vitest WIP * lots of tests, lots of inprogress * graphql tests updated * lots more tests and such, evaluating the meta things cause ugh * in prgoress but coming along a ton...merge script and scriptutils tests * so close. a couple challenges but otherwise so much progress converting to vitest * so many files done now to fix up some lingering problem tests * all skipped tests addressed other than logger * remove client test index and remove msw usage for mocking request * remove console logs * remove unused modules and comment out logger test for now * remove some deps * remove local env pathin domain * remove logger from vitest run while evaluate deletion * remove logger from vitest run while evaluate deletion * got coverage going for vitest on src files * use single coverageupload in main flow * remove mocha * broken for now thats ok * start testing fraudnet * add tests for fraudnet * spy on logger, handle test for suppressing of all errors * clear all gql mocks * removed unneeded files, scriptUtils now all in script.test.js * Update src/fraudnet.js * remove unused vars/imports * remove iteration from single case test * remove test index, iterate over globals for vitest setup * make envs a fn for clarity * clean up vite config file * so much flow ignore bruh * nock out some of the lint issues, but not all * resotre logger domain local block * fix api test * forgot to import host and protocol fns * colocate test files and impl, remove test globals file as not needed * put test file back since it's used in other repos * fix globals ref in webpack config * remove sticky session id use because flow * remove test ref from lint step * fix lint errors * fix flow again * put make mock element function in helper and adjust to always pass mock url * fix test lint imports * add input type for makemockscript * add test back as an export * update old coverage ignore lines to work with vitest v8 * move globals vars to own file for webpack and vitest usage * fix webpack build and ignore hermes flow types * add back in stickysession once belter updated * test stickysessionid fix * remove old codeblock comment * sort input --------- Co-authored-by: Shraddha Shah --- .flowconfig | 1 + .github/workflows/main.yml | 11 +- babel.config.js | 3 +- karma.conf.js | 25 - package.json | 21 +- server/babel.config.js | 3 +- .../meta.integration.test.js | 3 +- {test/server => server}/meta.test.js | 3 +- src/api.test.js | 150 +++++ src/config.test.js | 39 ++ src/domains.js | 4 +- src/domains.test.js | 99 +++ src/fraudnet.js | 6 +- src/fraudnet.test.js | 167 +++++ src/global.test.js | 202 ++++++ src/graphql.test.js | 88 +++ src/meta.js | 1 + src/meta.test.js | 82 +++ src/script.js | 28 +- src/script.test.js | 566 +++++++++++++++++ src/session.js | 2 +- src/session.test.js | 78 +++ src/tracking.js | 1 - src/tracking.test.js | 36 ++ test/client/api.js | 193 ------ test/client/common.js | 24 - test/client/config.js | 57 -- test/client/domains.js | 160 ----- test/client/global.js | 359 ----------- test/client/graphql.js | 100 --- test/client/index.js | 14 - test/client/logger.js | 226 ------- test/client/meta.js | 111 ---- test/client/script.js | 586 ------------------ test/client/scriptUtils.js | 233 ------- test/client/session.js | 49 -- test/client/tracking.js | 41 -- test/globals.js | 12 +- test/helpers.js | 10 + test/index.js | 3 - vite.config.js | 58 ++ vitestSetup.js | 10 + webpack.config.js | 6 +- 43 files changed, 1628 insertions(+), 2243 deletions(-) delete mode 100644 karma.conf.js rename {test/server => server}/meta.integration.test.js (98%) rename {test/server => server}/meta.test.js (99%) create mode 100644 src/api.test.js create mode 100644 src/config.test.js create mode 100644 src/domains.test.js create mode 100644 src/fraudnet.test.js create mode 100644 src/global.test.js create mode 100644 src/graphql.test.js create mode 100644 src/meta.test.js create mode 100644 src/script.test.js create mode 100644 src/session.test.js create mode 100644 src/tracking.test.js delete mode 100644 test/client/api.js delete mode 100644 test/client/common.js delete mode 100644 test/client/config.js delete mode 100644 test/client/domains.js delete mode 100644 test/client/global.js delete mode 100644 test/client/graphql.js delete mode 100644 test/client/index.js delete mode 100644 test/client/logger.js delete mode 100644 test/client/meta.js delete mode 100644 test/client/script.js delete mode 100644 test/client/scriptUtils.js delete mode 100644 test/client/session.js delete mode 100644 test/client/tracking.js create mode 100644 test/helpers.js delete mode 100644 test/index.js create mode 100644 vite.config.js create mode 100644 vitestSetup.js diff --git a/.flowconfig b/.flowconfig index 8ba69425..d361db80 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,4 +1,5 @@ [ignore] +.*/node_modules/hermes-estree .*/node_modules/babel-plugin-flow-runtime .*/node_modules/flow-runtime .*/node_modules/cross-domain-safe-weakmap/dist diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e6d419c6..f7700e50 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,14 +29,7 @@ jobs: - name: ▶️ Run build script run: npm run build - - name: ⬆️ Upload client coverage report + - name: ⬆️ Upload coverage report uses: codecov/codecov-action@v3 with: - directory: ./coverage/karma - flags: client - - - name: ⬆️ Upload server coverage report - uses: codecov/codecov-action@v3 - with: - directory: ./coverage/jest - flags: server + directory: ./coverage diff --git a/babel.config.js b/babel.config.js index 66bc35cb..86eb1e46 100644 --- a/babel.config.js +++ b/babel.config.js @@ -2,5 +2,6 @@ /* eslint import/no-commonjs: off */ module.exports = { - extends: "@krakenjs/babel-config-grumbler/babelrc-node", + extends: "@krakenjs/grumbler-scripts/config/.babelrc-node", + presets: ["@krakenjs/babel-config-grumbler/flow-ts-babel-preset"], }; diff --git a/karma.conf.js b/karma.conf.js deleted file mode 100644 index 38088c66..00000000 --- a/karma.conf.js +++ /dev/null @@ -1,25 +0,0 @@ -/* @flow */ -/* eslint import/no-default-export: off */ - -import { getKarmaConfig } from "@krakenjs/karma-config-grumbler"; - -import { WEBPACK_CONFIG_TEST } from "./webpack.config"; - -export default function configKarma(karma: Object) { - const karmaConfig = getKarmaConfig(karma, { - basePath: __dirname, - webpack: WEBPACK_CONFIG_TEST, - }); - - karma.set({ - ...karmaConfig, - coverageReporter: { - reporters: [ - { - type: "lcov", - dir: "coverage/karma", - }, - ], - }, - }); -} diff --git a/package.json b/package.json index 59a525a4..01f53e0a 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,7 @@ "doc": "esdoc", "flow": "flow", "flow-typed": "rm -rf ./flow-typed && flow-typed install", - "karma": "cross-env NODE_ENV=test babel-node --plugins=transform-es2015-modules-commonjs ./node_modules/.bin/karma start", - "lint": "eslint src/ server/ test/ *.js", + "lint": "eslint src/ server/ *.js", "reinstall": "rimraf flow-typed && rimraf node_modules && npm install && flow-typed install", "release": "./publish.sh", "release:major": "./publish.sh major", @@ -21,9 +20,10 @@ "setup": "npm install && npm run flow-typed", "format": "prettier --write --ignore-unknown .", "format:check": "prettier --check .", - "test": "npm run format:check && npm run lint && npm run flow-typed && npm run flow && npm run jest && npm run karma", + "test": "npm run format:check && npm run lint && npm run flow-typed && npm run flow && npm run test:unit", "webpack": "babel-node --plugins=transform-es2015-modules-commonjs ./node_modules/.bin/webpack --progress", - "jest": "jest test/server --env=node --no-cache --collectCoverageFrom='server/' --coverageDirectory='coverage/jest' --coverage --verbose --runInBand --silent=false", + "test:unit:watch": "vitest --coverage", + "test:unit": "vitest run --coverage", "prepublishOnly": "npm run babel", "postpublish": "rm -rf ./server && git checkout ./server", "validate-codecov": "curl --data-binary @.github/codecov.yml https://codecov.io/validate", @@ -62,8 +62,10 @@ "bowser": "^2.0.0" }, "devDependencies": { + "@bunchtogether/vite-plugin-flow": "^1.0.2", + "@krakenjs/babel-config-grumbler": "^8.1.1", + "@krakenjs/eslint-config-grumbler": "^8.1.1", "@krakenjs/grumbler-scripts": "^8.0.4", - "@krakenjs/sync-browser-mocks": "^3.0.0", "babel-core": "7.0.0-bridge.0", "cheerio": "1.0.0-rc.9", "cross-env": "^7.0.3", @@ -73,10 +75,13 @@ "flow-bin": "0.155.0", "flow-typed": "^3.8.0", "husky": "^8.0.1", - "jest": "^29.3.1", + "jsdom": "^20.0.3", "lint-staged": "^13.0.3", - "mocha": "^10.0.0", - "prettier": "2.8.8" + "prettier": "2.8.8", + "@vitest/coverage-v8": "^1.0.0", + "@vitest/ui": "^1.0.0", + "vite": "^4.0.1", + "vitest": "^1.0.0" }, "lint-staged": { "**/*": "prettier --write --ignore-unknown" diff --git a/server/babel.config.js b/server/babel.config.js index 66bc35cb..86eb1e46 100644 --- a/server/babel.config.js +++ b/server/babel.config.js @@ -2,5 +2,6 @@ /* eslint import/no-commonjs: off */ module.exports = { - extends: "@krakenjs/babel-config-grumbler/babelrc-node", + extends: "@krakenjs/grumbler-scripts/config/.babelrc-node", + presets: ["@krakenjs/babel-config-grumbler/flow-ts-babel-preset"], }; diff --git a/test/server/meta.integration.test.js b/server/meta.integration.test.js similarity index 98% rename from test/server/meta.integration.test.js rename to server/meta.integration.test.js index ab8b9b12..736712ba 100644 --- a/test/server/meta.integration.test.js +++ b/server/meta.integration.test.js @@ -1,8 +1,9 @@ /* @flow */ import cheerio from "cheerio"; +import { test } from "vitest"; -import { unpackSDKMeta } from "../../server"; +import { unpackSDKMeta } from "."; /** * List with real URL query parameters. diff --git a/test/server/meta.test.js b/server/meta.test.js similarity index 99% rename from test/server/meta.test.js rename to server/meta.test.js index 6473fbbe..6f672c1f 100644 --- a/test/server/meta.test.js +++ b/server/meta.test.js @@ -2,8 +2,9 @@ /* eslint max-lines: off */ import cheerio from "cheerio"; +import { test, afterEach } from "vitest"; -import { unpackSDKMeta } from "../../server"; +import { unpackSDKMeta } from "."; afterEach(() => { // eslint-disable-next-line no-process-env diff --git a/src/api.test.js b/src/api.test.js new file mode 100644 index 00000000..eba983d8 --- /dev/null +++ b/src/api.test.js @@ -0,0 +1,150 @@ +/* @flow */ +import { describe, beforeEach, it, expect, vi } from "vitest"; +import { getCurrentScript, request, memoize } from "@krakenjs/belter/src"; + +import { createAccessToken, createOrder } from "./api"; + +vi.mock("@krakenjs/belter/src", async () => { + const actual = await vi.importActual("@krakenjs/belter/src"); + return { + ...actual, + getCurrentScript: vi.fn(), + request: vi.fn().mockResolvedValue(), + }; +}); + +describe("api cases", () => { + let order; + const invalidClientId = "invalid-client-id"; + const emptyResponseClientId = "empty-response-client-id"; + const createOrderValidId = "create-order-valid-order-id"; + const expectedToken = + "A21AAKNZBaqilFBC4dVVz-tr-ySIT78NREeBidy3lkGdr-EA8wbhGrByPayhgnJRPE5xg4QW46moDbCFjZ13i1GH-Ax4SjtjA"; + const defaultAuthResponse = { + scope: "https://uri.paypal.com/services/invoicing", + access_token: expectedToken, + token_type: "Bearer", + app_id: "APP-80W284485P519543T", + expires_in: 31838, + nonce: "2022-03-07T22:41:38ZqHkiC0_odfzFwo27_X0wVuF67STYq39KRplBeeyY2bk", + error: null, + }; + + beforeEach(() => { + memoize.clear(); + window.__PAYPAL_DOMAIN__ = "testurl"; + // $FlowIgnore + getCurrentScript.mockReturnValue({ + src: `https://sdkplz.com/sdk/js?intent=capture`, + attributes: [], + }); + vi.clearAllMocks(); + + order = { + intent: "CAPTURE", + purchase_units: [ + { + amount: { + value: "10.00", + currency_code: "USD", + }, + }, + ], + }; + }); + + describe("createAccessToken()", () => { + it("createAccessToken should return a valid token", async () => { + // $FlowIgnore + request.mockResolvedValueOnce({ body: defaultAuthResponse }); + + const result = await createAccessToken("testClient"); + + expect(result).toEqual(expectedToken); + }); + + it("createAccessToken should throw invalid client argument error", async () => { + // $FlowIgnore + request.mockResolvedValueOnce({ body: { error: "invalid_client" } }); + + await expect(() => + createAccessToken(invalidClientId) + ).rejects.toThrowError(/Auth Api invalid client id:/); + }); + + it("createAccessToken should return an error message when response is an empty object", async () => { + // $FlowIgnore + request.mockResolvedValueOnce({ body: {} }); + + await expect(() => + createAccessToken(emptyResponseClientId) + ).rejects.toThrow(/Auth Api response error:/); + }); + }); + + describe("createOrder()", () => { + it("createOrder should throw an error when clientId is null", () => { + // $FlowIgnore + expect(() => createOrder(null)).toThrowError(/Client ID not passed/); + }); + + it("createOrder should throw an error when order is null", () => { + // $FlowIgnore + expect(() => createOrder("testClient", null)).toThrow( + /Expected order details to be passed/ + ); + }); + + it("createOrder should throw an error when order intent does not match with query parameters intent", () => { + const expectedErrorMessage = + "Unexpected intent: AUTHORIZE passed to order.create. Please ensure you are passing /sdk/js?intent=authorize in the paypal script tag."; + + order.intent = "AUTHORIZE"; + + expect(() => createOrder("testClient", order)).toThrowError( + expectedErrorMessage + ); + }); + + it("createOrder should throw an error when order currency does not match with query parameters currency", () => { + const expectedErrorMessage = + "Unexpected currency: AUD passed to order.create. Please ensure you are passing /sdk/js?currency=AUD in the paypal script tag."; + order.purchase_units[0].amount.currency_code = "AUD"; + + expect(() => createOrder("testClient", order)).toThrow( + expectedErrorMessage + ); + }); + + it("createOrder should throw an error when order identifier is not in the server response", async () => { + const expectedErrorMessage = "Order Api response error:"; + const failuredPayload = {}; + + request + // $FlowIgnore + .mockResolvedValueOnce({ body: defaultAuthResponse }) + .mockResolvedValueOnce({ body: failuredPayload }); + + await expect(() => createOrder("testClient", order)).rejects.toThrow( + expectedErrorMessage + ); + }); + + it("createOrder should return a valid orderId", async () => { + const expectedOrderId = "9BL31648CM342010L"; + const mockOrderResponse = { + id: expectedOrderId, + status: "CREATED", + links: [], + }; + + request + // $FlowIgnore + .mockResolvedValueOnce({ body: defaultAuthResponse }) + .mockResolvedValueOnce({ body: mockOrderResponse }); + + const result = await createOrder(createOrderValidId, order); + expect(result).toEqual(expectedOrderId); + }); + }); +}); diff --git a/src/config.test.js b/src/config.test.js new file mode 100644 index 00000000..b2a51cfd --- /dev/null +++ b/src/config.test.js @@ -0,0 +1,39 @@ +/* @flow */ +import { describe, it, expect } from "vitest"; + +import { + getPayPalLoggerDomain, + buildPayPalUrl, + buildPayPalAPIUrl, + getPayPalLoggerUrl, +} from "./domains"; + +describe(`config cases`, () => { + it("should successfully get the global paypal logger domain", () => { + const expectedDomain = "mock://www.paypal.com"; + window.__PAYPAL_DOMAIN__ = expectedDomain; + const domain = getPayPalLoggerDomain(); + expect(domain).toEqual(expectedDomain); + }); + + it("should successfully build a paypal url", () => { + const expectedPayPalUrl = `${window.location.protocol}//${window.location.host}/foo/bar`; + const result = buildPayPalUrl("/foo/bar"); + + expect(result).toEqual(expectedPayPalUrl); + }); + + it("should successfully build a paypal api url", () => { + const expectedPayPalUrl = `${window.location.protocol}//${window.location.host}/bar/baz`; + const result = buildPayPalAPIUrl("/bar/baz"); + + expect(result).toEqual(expectedPayPalUrl); + }); + + it("should successfully build a paypal logger url", () => { + const expectedPayPalUrl = `${window.location.protocol}//${window.location.host}/xoplatform/logger/api/logger`; + const result = getPayPalLoggerUrl(); + + expect(result).toEqual(expectedPayPalUrl); + }); +}); diff --git a/src/domains.js b/src/domains.js index 6bdf2d50..f176ca38 100644 --- a/src/domains.js +++ b/src/domains.js @@ -8,10 +8,10 @@ import { } from "@krakenjs/cross-domain-utils/src"; import { + getPayPalAPIDomain, + getPayPalDomain, getProtocol, getStageHost, - getPayPalDomain, - getPayPalAPIDomain, } from "./global"; import { URI } from "./config"; diff --git a/src/domains.test.js b/src/domains.test.js new file mode 100644 index 00000000..25d64002 --- /dev/null +++ b/src/domains.test.js @@ -0,0 +1,99 @@ +/* @flow */ +import { ENV } from "@paypal/sdk-constants/src"; +import { describe, it, expect } from "vitest"; + +import { + getAuthAPIUrl, + getOrderAPIUrl, + getPayPalDomainRegex, + getVenmoDomainRegex, + isPayPalTrustedDomain, +} from "./domains"; + +describe(`domains test`, () => { + it("should successfully match valid paypal domain", () => { + const validDomains = [ + "master.qa.paypal.com", + "test-env.qa.paypal.com:3000", + "geo.qa.paypal.com", + "www.paypal.com:3080", + "www.paypal.cn", + "www.paypal.cn:3000", + "www.mschina.qa.paypal.cn", + "www.paypal.com", + ]; + + for (const domain of validDomains) { + expect(domain).toMatch(getPayPalDomainRegex()); + } + }); + + it("should not match invalid paypal domains", () => { + const invalidDomains = [ + "www.paypal.com.example.com", + "www.paypal.cn.example.com", + ]; + + for (const domain of invalidDomains) { + expect(domain).not.toMatch(getPayPalDomainRegex()); + } + }); + + it("should successfully match valid venmo domain", () => { + const validDomains = [ + "https://venmo.com", + "http://www.venmo.com", + "https://id.venmo.com", + "http://www.venmo.com:8000", + "https://account.qa.venmo.com", + "http://www.account.qa.venmo.com", + "https://account.qa.venmo.com", + "https://account.venmo.com", + ]; + + for (const domain of validDomains) { + expect(domain).toMatch(getVenmoDomainRegex()); + } + }); + + it("should successfully match valid venmo testing domain", () => { + window.__ENV__ = "local"; + const domain = "https://localhost.venmo.com"; + + expect(domain).toMatch(getVenmoDomainRegex()); + }); + + it("should not match invalid venmo domains", () => { + const invalidDomains = [ + "www.venmo.com.example.com", + "www.venmo.cn.example.com", + "www.venmo.com", + ]; + + for (const domain of invalidDomains) { + expect(domain).not.toMatch(getVenmoDomainRegex()); + } + }); + + it("isPayPalTrustedDomain should return true", () => { + window.__ENV__ = ENV.LOCAL; + const result = isPayPalTrustedDomain(); + + expect(result).toBe(true); + }); + + it("getAuthAPIUrl should return a valid authentication string URL", () => { + const url = new URL(getAuthAPIUrl()); // eslint-disable-line compat/compat + const baseUrl = `${url.protocol}//${url.hostname}`; + expect(baseUrl).toEqual("http://localhost"); + expect(url.pathname).toEqual("/v1/oauth2/token"); + }); + + it("getOrderAPIUrl should return a valid order string URL", () => { + const url = new URL(getOrderAPIUrl()); // eslint-disable-line compat/compat + + const baseUrl = `${url.protocol}//${url.hostname}`; + expect(baseUrl).toEqual("http://localhost"); + expect(url.pathname).toEqual("/v2/checkout/orders"); + }); +}); diff --git a/src/fraudnet.js b/src/fraudnet.js index 1bc74485..2bed82bb 100644 --- a/src/fraudnet.js +++ b/src/fraudnet.js @@ -45,10 +45,6 @@ export const createConfigScript = ({ appName, }: CreateConfigOptions): ZalgoPromise => { return new ZalgoPromise((resolve) => { - if (__TEST__) { - return resolve(); - } - const config: FraudnetConfig = { f: clientMetadataID, s: appName, @@ -69,6 +65,7 @@ export const createConfigScript = ({ configScript.text = JSON.stringify(config); // eslint-disable-next-line compat/compat document.body?.appendChild(configScript); + resolve(); }); }; @@ -91,7 +88,6 @@ export const createFraudnetScript = ({ fraudnetScript.setAttribute("nonce", cspNonce || ""); fraudnetScript.setAttribute("src", fraudnetUrl); - fraudnetScript.addEventListener("error", () => resolve()); window.fnCallback = resolve; // eslint-disable-next-line compat/compat diff --git a/src/fraudnet.test.js b/src/fraudnet.test.js new file mode 100644 index 00000000..b0dfcb51 --- /dev/null +++ b/src/fraudnet.test.js @@ -0,0 +1,167 @@ +/* @flow */ +import { beforeEach, describe, it, expect, vi } from "vitest"; +import { memoize } from "@krakenjs/belter/src"; + +import { + loadFraudnet, + createConfigScript, + createFraudnetScript, +} from "./fraudnet"; +import { FRAUDNET_FNCLS, FRAUDNET_URL } from "./constants"; +// eslint-disable-next-line import/no-namespace +import * as logger from "./logger"; + +vi.spyOn(logger, "getLogger"); + +describe("fraudnet.js", () => { + const actual = document.createElement("script"); + const createElementSpy = vi + .spyOn(document, "createElement") + .mockImplementation(() => actual); + // eslint-disable-next-line compat/compat + const appendChildSpy = vi.spyOn(document.body, "appendChild"); + + beforeEach(() => { + vi.clearAllMocks(); + memoize.clear(); + }); + + const fraudnetInputs = { + env: "test", + clientMetadataID: "test-cmid", + cspNonce: "test-csp-nonce", + appName: "sdk-test", + // queryStringParams: {}, + }; + describe("loadFraudnet()", () => { + window.PAYPAL = { + asyncData: { + collect: vi.fn().mockResolvedValue(), + }, + }; + beforeEach(() => { + // $FlowIgnore + actual.addEventListener = vi.fn((event, cb) => { + if (event === "load") { + cb(); + } + }); + }); + + it("creates both scripts", () => { + loadFraudnet(fraudnetInputs); + // $FlowIgnore + expect(document.createElement).toBeCalledTimes(2); + }); + + it("should be memoized and thus cache subsequent calls", () => { + loadFraudnet(fraudnetInputs); + loadFraudnet(fraudnetInputs); + + // once for createConfigScript, another for createFraudnetScript + expect(createElementSpy).toBeCalledTimes(2); + }); + + it("returns collect function", () => { + const result = loadFraudnet(fraudnetInputs); + expect(result).toEqual({ collect: expect.any(Function) }); + }); + + it("collect function calls fraudnet collect", async () => { + const mockCollect = vi.fn().mockResolvedValue(); + window.PAYPAL.asyncData.collect = mockCollect; + const { collect } = loadFraudnet(fraudnetInputs); + await collect(); + expect(mockCollect).toBeCalled(); + }); + + it("should suppress the error if collect fails", async () => { + const mockCollect = vi.fn().mockRejectedValue("fraudnet collect fail"); + window.PAYPAL.asyncData.collect = mockCollect; + + const { collect } = loadFraudnet(fraudnetInputs); + + await expect(collect).not.toThrow(); + }); + }); + + describe("createConfigScript", () => { + it("sets up the config script properly", async () => { + const inputs = { + env: "test", + cspNonce: "test-nonce", + clientMetadataID: "test-cmid", + appName: "local-test-connect", + }; + const expectedText = { + f: inputs.clientMetadataID, + s: inputs.appName, + io: true, + cb1: "fnCallback", + }; + + await createConfigScript(inputs); + + expect(createElementSpy).toBeCalledWith("script"); + expect(actual.getAttribute("nonce")).toEqual(inputs.cspNonce); + expect(actual.getAttribute("type")).toEqual("application/json"); + expect(actual.getAttribute("id")).toEqual("fconfig"); + expect(actual.getAttribute("fncls")).toEqual(FRAUDNET_FNCLS); + expect(actual.text).toEqual(JSON.stringify(expectedText)); + expect(appendChildSpy).toBeCalledWith(actual); + }); + }); + + describe("createFraudnetScript()", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + const inputs = { + env: "test", + cspNonce: "test-nonce", + }; + + it("sets up the fraudnet script properly", async () => { + // $FlowIgnore + actual.addEventListener = vi.fn((event, cb) => { + if (event === "load") { + cb(); + } + }); + + await createFraudnetScript(inputs); + + expect(createElementSpy).toBeCalledWith("script"); + expect(actual.getAttribute("nonce")).toEqual(inputs.cspNonce); + expect(actual.getAttribute("src")).toEqual(FRAUDNET_URL[inputs.env]); + expect(appendChildSpy).toBeCalledWith(actual); + }); + + it("rejects if loading errors out", async () => { + // $FlowIgnore + actual.addEventListener = vi.fn((event, cb) => { + if (event === "error") { + cb(event); + } + }); + + await expect(() => createFraudnetScript(inputs)).rejects.toThrow( + "Fraudnet failed to load." + ); + }); + + it("rejects if loading aborts", async () => { + // $FlowIgnore + actual.addEventListener = vi.fn((event, cb) => { + if (event === "abort") { + cb(event); + } + }); + + await expect(() => createFraudnetScript(inputs)).rejects.toThrow( + "Fraudnet load was aborted." + ); + }); + }); +}); diff --git a/src/global.test.js b/src/global.test.js new file mode 100644 index 00000000..fb88703a --- /dev/null +++ b/src/global.test.js @@ -0,0 +1,202 @@ +/* @flow */ +import { afterEach, describe, it, expect } from "vitest"; +import { PLATFORM, PROTOCOL } from "@paypal/sdk-constants/src"; + +import { + getSDKHost, + getHost, + getProtocol, + getHostName, + getPort, + getDefaultServiceStageHost, + getDefaultAPIStageHost, + getStageHost, + getFundingEligibility, + getAPIStageHost, + getDebug, + getComponents, + getPath, + getEnv, + getDefaultStageHost, + getVersion, + getCorrelationID, + getPlatform, + getExperimentation, +} from "./global"; + +describe(`globals cases`, () => { + afterEach(() => { + window.__STAGE_HOST__ = "mock://sandbox.paypal.com"; + delete window.__PROTOCOL__; + delete window.__SERVICE_STAGE_HOST__; + delete window.__COMPONENTS__; + delete window.__FUNDING_ELIGIBILITY__; + }); + + it("should successfully get the host", () => { + const expectedResult = "test.paypal.com"; + const result = getHost(); + expect(result).toEqual(expectedResult); + }); + + it("should successfully get the hostname", () => { + const expectedResult = "test.paypal.com"; + const result = getHostName(); + expect(result).toEqual(expectedResult); + }); + + it("should successfully get the port", () => { + const expectedResult = 8000; + const result = getPort(); + expect(result).toEqual(expectedResult); + }); + + it("should successfully get the path", () => { + const expectedResult = "/sdk/js"; + const result = getPath(); + expect(result).toEqual(expectedResult); + }); + + it("should successfully get the env", () => { + const expectedResult = "test"; + const result = getEnv(); + expect(result).toEqual(expectedResult); + }); + + it('should get the default stage host when "window.__STAGE_HOST__" is undefined', () => { + window.__STAGE_HOST__ = undefined; + const result = getDefaultStageHost(); + expect(result).toBeUndefined(); + }); + + it("should successfully get the default stage host", () => { + const expectedResult = "mock://sandbox.paypal.com"; + const result = getDefaultStageHost(); + expect(result).toEqual(expectedResult); + }); + + it("should successfully get the version", () => { + const expectedResult = "1.0.45"; + const result = getVersion(); + expect(result).toEqual(expectedResult); + }); + + it("should successfully get the correlation id", () => { + const expectedResult = "abc123"; + const result = getCorrelationID(); + expect(result).toEqual(expectedResult); + }); + + it("should successfully get the SDK host", () => { + const result = getSDKHost(); + expect(result).toEqual(__SDK_HOST__); + }); + + it("should successfully get the default protocol", () => { + const result = getProtocol(); + expect(result).toEqual(PROTOCOL.HTTPS); + }); + + it("should successfully get the global protocol", () => { + window.__PROTOCOL__ = "http"; + const result = getProtocol(); + expect(result).toEqual(PROTOCOL.HTTP); + }); + + it("should get the default service stage host when undefined", () => { + window.__SERVICE_STAGE_HOST__ = undefined; + const result = getDefaultServiceStageHost(); + expect(result).toBeUndefined(); + }); + + it("should successfully get the default service stage host", () => { + window.__SERVICE_STAGE_HOST__ = "mock://sandbox.paypal.com"; + const result = getDefaultServiceStageHost(); + expect(result).toEqual(__SERVICE_STAGE_HOST__); + }); + + it("should successfully identify desktop platform", () => { + const result = getPlatform(); + expect(result).toEqual(PLATFORM.DESKTOP); + }); + + it("should get the API stage from the default service stage host", () => { + window.__SERVICE_STAGE_HOST__ = "mock://sandbox.paypal.com"; + const result = getDefaultAPIStageHost(); + expect(result).toEqual(window.__SERVICE_STAGE_HOST__); + }); + + it("should get the API stage from the default stage host", () => { + window.__SERVICE_STAGE_HOST__ = undefined; + const result = getDefaultAPIStageHost(); + expect(result).toEqual(__STAGE_HOST__); + }); + + it("should get the API stage when undefined", () => { + window.__STAGE_HOST__ = window.__SERVICE_STAGE_HOST__ = undefined; + const result = getDefaultAPIStageHost(); + expect(result).toBeUndefined(); + }); + + it("should successfully get the stage host", () => { + const result = getStageHost(); + expect(result).toEqual(__STAGE_HOST__); + }); + + it("should successfully get the API stage host", () => { + const result = getAPIStageHost(); + expect(result).toEqual(__STAGE_HOST__); + }); + + it("should get the API stage host when undefined", () => { + window.__STAGE_HOST__ = window.__SERVICE_STAGE_HOST__ = undefined; + const result = getAPIStageHost(); + expect(result).toBeUndefined(); + }); + + it("should successfully get the debug flag", () => { + window.__DEBUG__ = true; + const result = getDebug(); + expect(result).toEqual(window.__DEBUG__); + }); + + it("should successfully get the components list", () => { + const expectedComponents = ["buttons", "venmo"]; + window.__COMPONENTS__ = expectedComponents; + const result = getComponents(); + expect(result).toEqual(expectedComponents); + }); + + it("should successfully get the funding eligibility type", () => { + window.__FUNDING_ELIGIBILITY__ = "credit"; + const result = getFundingEligibility(); + expect(result).toEqual(window.__FUNDING_ELIGIBILITY__); + }); + + it("should successfully get experimation value", () => { + window.__EXPERIMENTATION__ = { + __EXPERIENCE__: "1234, 4321", + __TREATMENT__: "8765,7890", + }; + const expectedResult = { + experience: "1234, 4321", + treatment: "8765,7890", + }; + const result = getExperimentation(); + expect(result).toEqual(expectedResult); + }); + + it("should get experimation null value", () => { + window.__EXPERIMENTATION__ = null; + const expectedResult = null; + const result = getExperimentation(); + expect(result).toEqual(expectedResult); + }); + + it("should get experimation empty value", () => { + window.__EXPERIMENTATION__ = {}; + const expectedResult = {}; + const result = getExperimentation(); + expect(result).toEqual(expectedResult); + }); +}); diff --git a/src/graphql.test.js b/src/graphql.test.js new file mode 100644 index 00000000..7fbe891e --- /dev/null +++ b/src/graphql.test.js @@ -0,0 +1,88 @@ +/* @flow */ + +import { describe, it, expect, vi, afterAll } from "vitest"; +import { request } from "@krakenjs/belter/src"; + +import { callGraphQL, getGraphQLFundingEligibility } from "./graphql"; + +vi.mock("@krakenjs/belter/src", async () => { + const actual = await vi.importActual("@krakenjs/belter/src"); + return { + ...actual, + getCurrentScript: vi.fn(() => ({ + src: "https://mock-sdk.com/sdk/js?client-id='test'", + attributes: [], + hasAttribute: vi.fn(), + })), + request: vi.fn(), + }; +}); + +describe("graphql cases", () => { + afterAll(() => { + vi.clearAllMocks(); + }); + + it("callGraphQL should fail when graphql returns a non-200 status", async () => { + const expectedStatus = 404; + // $FlowIgnore + request.mockResolvedValue({ body: {}, status: expectedStatus }); + await expect(() => + callGraphQL({ query: `non200Status {}` }) + ).rejects.toThrow(`/graphql returned status ${expectedStatus}`); + }); + + it("callGraphQL should throw an exception when the response body contains errors", async () => { + const expectedError = "unexpected error"; + // $FlowIgnore + request.mockResolvedValue({ body: { errors: [expectedError] } }); + await expect(() => + callGraphQL({ query: `graphqlErrors {}` }) + ).rejects.toThrow(expectedError); + }); + + it("callGraphQL should return a valid body response", async () => { + // $FlowIgnore + request.mockResolvedValue({ + body: { data: { received: true } }, + status: 200, + }); + + // $FlowIgnore[prop-missing] + const { received } = await callGraphQL({ + query: `validResponseBody {}`, + }); + expect(received).toBe(true); + }); + + it("getGraphQLFundingEligibility should throw an error when fundingEligibility is not in the response", async () => { + const expectedErrorMessage = + "GraphQL fundingEligibility returned no fundingEligibility object"; + // $FlowIgnore + request.mockResolvedValue({ body: { data: {} }, status: 200 }); + + await expect(() => + getGraphQLFundingEligibility("noFundingEligiblity") + ).rejects.toThrow(expectedErrorMessage); + }); + + it("getGraphQLFundingEligibility should return the fundingEligibility", async () => { + // $FlowIgnore + request.mockResolvedValue({ + body: { + data: { + clientID: "Somethingsomething", + fundingEligibility: { + clientId: "a-funding-eligiblity-client-id", + }, + }, + }, + status: 200, + }); + + const result = await getGraphQLFundingEligibility( + "fundingEligibilitySuccess" + ); + expect(result).toEqual({ clientId: "a-funding-eligiblity-client-id" }); + }); +}); diff --git a/src/meta.js b/src/meta.js index 7186d753..ab41f96b 100644 --- a/src/meta.js +++ b/src/meta.js @@ -21,6 +21,7 @@ export function getSDKMeta(): string { const url = getScriptUrl(); const scriptAttrs = getSDKAttributes(); + const attrs = {}; for (const attr of Object.keys(scriptAttrs)) { if (ALLOWED_ATTRS.indexOf(attr) !== -1) { diff --git a/src/meta.test.js b/src/meta.test.js new file mode 100644 index 00000000..e70577ab --- /dev/null +++ b/src/meta.test.js @@ -0,0 +1,82 @@ +/* @flow */ +import { describe, it, vi, beforeEach, expect } from "vitest"; +import { getCurrentScript, memoize } from "@krakenjs/belter/src"; + +import { makeMockScriptElement } from "../test/helpers"; + +import { getSDKMeta } from "./meta"; + +const clientId = "foobar123"; +const mockScriptSrc = `https://test.paypal.com/sdk/js?client-id=${clientId}`; + +vi.mock("@krakenjs/belter/src", async () => { + const actual = await vi.importActual("@krakenjs/belter/src"); + return { + ...actual, + getCurrentScript: vi.fn(() => { + return makeMockScriptElement(mockScriptSrc); + }), + }; +}); + +describe(`meta cases`, () => { + beforeEach(() => { + memoize.clear(); + vi.clearAllMocks(); + }); + + it("should successfully create a meta payload with script src url", () => { + const meta = getSDKMeta(); + + expect(meta).toEqual(expect.any(String)); + const { url } = JSON.parse(window.atob(meta)); + expect(url).toEqual(mockScriptSrc); + }); + + it("should successfully create a meta payload with merchant id", () => { + const expectedMerchantIds = "abcd1234,abcd5678"; + const merchantIdKey = "data-merchant-id"; + const sdkUrl = `${mockScriptSrc}&${merchantIdKey}=*`; + const mockElement = makeMockScriptElement(sdkUrl); + mockElement.setAttribute(merchantIdKey, expectedMerchantIds); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + const meta = getSDKMeta(); + const resultMeta = JSON.parse(window.atob(meta)); + expect(resultMeta.attrs).toEqual( + expect.objectContaining({ + [merchantIdKey]: expectedMerchantIds, + }) + ); + }); + + it("should construct a valid script url with data-popups-disabled attribute", () => { + const disablePops = true; + const popupsDisabledKey = "data-popups-disabled"; + const sdkUrl = `${mockScriptSrc}&${popupsDisabledKey}=${disablePops.toString()}`; + const mockElement = makeMockScriptElement(sdkUrl); + mockElement.setAttribute(popupsDisabledKey, disablePops.toString()); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + const meta = getSDKMeta(); + + const resultMeta = JSON.parse(window.atob(meta)); + expect(resultMeta.attrs[popupsDisabledKey]).toEqual(disablePops.toString()); + }); + + it("should successfully create a meta payload with data-csp-nonce", () => { + const dataCSPNonce = "12345"; + const cspNonceKey = "data-csp-nonce"; + const sdkUrl = `${mockScriptSrc}&${cspNonceKey}=${dataCSPNonce}`; + const mockElement = makeMockScriptElement(sdkUrl); + mockElement.setAttribute(cspNonceKey, dataCSPNonce); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + const meta = getSDKMeta(); + const resultMeta = JSON.parse(window.atob(meta)); + expect(resultMeta.attrs[cspNonceKey]).toEqual(dataCSPNonce); + }); +}); diff --git a/src/script.js b/src/script.js index 5db986e1..ef7bee03 100644 --- a/src/script.js +++ b/src/script.js @@ -10,7 +10,6 @@ import { getCurrentScript, memoize, stringifyError, - getScript, } from "@krakenjs/belter/src"; import { COUNTRY, @@ -49,18 +48,6 @@ const buildScriptNotFoundError = (host, path, error) => { }; export const getSDKScript: GetSDKScript = memoize(() => { - if (__TEST__) { - const script = getScript({ - host: getSDKHost(), - path: getPath(), - reverse: true, - }); - if (!script) { - throw buildScriptNotFoundError(getSDKHost(), getPath()); - } - return script; - } - try { return getCurrentScript(); } catch (error) { @@ -73,6 +60,7 @@ type GetSDKAttributes = () => { [string]: string }; export const getSDKAttributes: GetSDKAttributes = memoize(() => { const sdkScript = getSDKScript(); const result = {}; + for (const attr of sdkScript.attributes) { if (attr.name.indexOf("data-") === 0) { result[attr.name] = attr.value; @@ -107,7 +95,8 @@ export const getSDKQueryParam: GetSDKQueryParam = (name: string, def: T) => { }; export function getScriptUrl(): string { - const src = getSDKScript().getAttribute("src"); + const script = getSDKScript(); + const src = script.getAttribute("src"); if (!src) { throw new Error(`Can not find src for sdk script`); } @@ -127,7 +116,6 @@ export function getSDKQueryParamBool( export function getClientID(): string { const clientID = getSDKQueryParam(SDK_QUERY_KEYS.CLIENT_ID); - if (!clientID) { throw new Error( `Expected ${SDK_QUERY_KEYS.CLIENT_ID} parameter in sdk url` @@ -342,29 +330,29 @@ export function getSDKToken(): ?string { return getSDKAttribute(SDK_SETTINGS.SDK_TOKEN); } -// whether in zoid window +/* v8 ignore next 3 */ export function isChildWindow(): boolean { return Boolean(window.xprops); } -// istanbul ignore next +/* v8 ignore next 3 */ export function getUserAccessToken(): ?string { // pass } -// istanbul ignore next +/* v8 ignore next 3 */ export function getUserAuthCode(): ?string { // pass } // Remove -// istanbul ignore next +/* v8 ignore next 3 */ export function getCountry(): $Values { return getLocale().country; } // Remove -// istanbul ignore next +/* v8 ignore next 3 */ export function getLang(): $Values { return getLocale().lang; } diff --git a/src/script.test.js b/src/script.test.js new file mode 100644 index 00000000..1281afb5 --- /dev/null +++ b/src/script.test.js @@ -0,0 +1,566 @@ +/* @flow */ +/* eslint max-lines: off */ +import { describe, it, afterEach, beforeEach, expect, vi } from "vitest"; +import { base64encode, getCurrentScript, memoize } from "@krakenjs/belter/src"; + +import { makeMockScriptElement } from "../test/helpers"; + +import { + getClientID, + getIntent, + getCurrency, + getVault, + getCommit, + getClientToken, + getPartnerAttributionID, + getMerchantID, + getClientAccessToken, + getSDKIntegrationSource, + getPageType, + getLocale, + getMerchantRequestedPopupsDisabled, + getScriptUrl, + getEnableFunding, + getDisableFunding, + getDisableCard, + getBuyerCountry, + getAmount, + getUserIDToken, + getCSPNonce, + getEnableThreeDomainSecure, + getUserExperienceFlow, + isChildWindow, +} from "./script"; +import { CLIENT_ID_ALIAS } from "./config"; + +const clientId = "foobar123"; +const mockScriptSrc = `https://test.paypal.com/sdk/js?client-id=${clientId}`; + +vi.mock("@krakenjs/belter/src", async () => { + const actual = await vi.importActual("@krakenjs/belter/src"); + return { + ...actual, + getCurrentScript: vi.fn(() => { + return makeMockScriptElement(mockScriptSrc); + }), + }; +}); + +describe(`script cases`, () => { + beforeEach(() => { + Object.defineProperty(window.navigator, "languages", { + value: [], + writable: true, + }); + Object.defineProperty(window.navigator, "language", { + value: "", + writable: true, + }); + }); + + afterEach(() => { + memoize.clear(); + vi.clearAllMocks(); + }); + + it("should successfully get a client id", () => { + expect(getClientID()).toEqual(clientId); + }); + + it("should error out when client id not passed", () => { + // $FlowIgnore + getCurrentScript.mockReturnValue( + makeMockScriptElement("https://test.paypal.com/sdk/js?") + ); + expect(getClientID).toThrow("Expected client-id parameter in sdk url"); + }); + + it("should successfully get a client id alias", () => { + const clientID = "sb"; + // $FlowIgnore + getCurrentScript.mockReturnValue( + makeMockScriptElement( + `https://test.paypal.com/sdk/js?client-id=${clientID}` + ) + ); + expect(getClientID()).toEqual(CLIENT_ID_ALIAS[clientID]); + }); + + it("should successfully get a merchant id", () => { + const merchantID = "abc987"; + const sdkUrl = `${mockScriptSrc}&merchant-id=${merchantID}`; + // $FlowIgnore + getCurrentScript.mockReturnValue(makeMockScriptElement(sdkUrl)); + + const mID = getMerchantID(); + expect(mID[0]).toEqual(merchantID); + }); + + it("should error out when merchant-id is * but data-merchant-id not passed", () => { + const merchantID = "*"; + const sdkUrl = `${mockScriptSrc}&merchant-id=${merchantID}`; + // $FlowIgnore + getCurrentScript.mockReturnValue(makeMockScriptElement(sdkUrl)); + + expect(getMerchantID).toThrow( + `Must pass data-merchant-id when merchant-id=${merchantID} passed in url` + ); + }); + + it("should error out when merchant-id is * but only one merchant id in data-merchant-id", () => { + const merchantID = "*"; + const dataMerchantIDs = "abc123"; + const sdkUrl = `${mockScriptSrc}&merchant-id=${merchantID}`; + const mockElement = makeMockScriptElement(sdkUrl); + mockElement.setAttribute("data-merchant-id", dataMerchantIDs); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getMerchantID).toThrow( + "Must pass multiple merchant ids to data-merchant-id. If passing a single id, pass merchant-id=XYZ in url" + ); + }); + + it("should error out when merchant-id is * but duplicated merchant id in data-merchant-id", () => { + const merchantID = "*"; + const dataMerchantIDs = "abc123,abc456,abc123"; + const sdkUrl = `${mockScriptSrc}&merchant-id=${merchantID}`; + const mockElement = makeMockScriptElement(sdkUrl); + mockElement.setAttribute("data-merchant-id", dataMerchantIDs); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getMerchantID).toThrow( + "Duplicates data-merchant-id. Must pass unique merchant ids to data-merchant-id." + ); + }); + + it("should successfully get merchant ids", () => { + const merchantID = "*"; + const dataMerchantIDs = "abc123,abc345"; + + const sdkUrl = `${mockScriptSrc}&merchant-id=${merchantID}`; + const mockElement = makeMockScriptElement(sdkUrl); + mockElement.setAttribute("data-merchant-id", dataMerchantIDs); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getMerchantID().join()).toEqual(dataMerchantIDs); + }); + + it("should successfully get an intent", () => { + const intent = "authorize"; + const sdkUrl = `${mockScriptSrc}&intent=${intent}`; + const mockElement = makeMockScriptElement(sdkUrl); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getIntent()).toEqual(intent); + }); + + it("should successfully get a currency", () => { + const currency = "EUR"; + const sdkUrl = `${mockScriptSrc}¤cy=${currency}`; + const mockElement = makeMockScriptElement(sdkUrl); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getCurrency()).toEqual(currency); + }); + + it("should successfully get vault", () => { + const vault = true; + const sdkUrl = `${mockScriptSrc}&vault=${vault.toString()}`; + const mockElement = makeMockScriptElement(sdkUrl); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getVault()).toEqual(vault); + }); + + it("should successfully get commit", () => { + const commit = false; + + const sdkUrl = `${mockScriptSrc}&commit=${commit.toString()}`; + const mockElement = makeMockScriptElement(sdkUrl); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getCommit()).toEqual(commit); + }); + + it("should successfully get client token", () => { + const clientToken = "abc-xyz-123"; + + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-client-token", clientToken); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getClientToken()).toEqual(clientToken); + }); + + it("should not error out when client token not passed", () => { + const mockElement = makeMockScriptElement(mockScriptSrc); + + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getClientToken()).toBeUndefined(); + }); + + it("should successfully get client access token", () => { + const clientAccessToken = "abc12354321"; + const clientToken = base64encode( + JSON.stringify({ + paypal: { + accessToken: clientAccessToken, + }, + }) + ); + + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-client-token", clientToken); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getClientAccessToken()).toEqual(clientAccessToken); + }); + + it("should successfully get partner attribution id", () => { + const partnerAttributionID = "abc-xyz-123"; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute( + "data-partner-attribution-id", + partnerAttributionID + ); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getPartnerAttributionID()).toEqual(partnerAttributionID); + }); + + it("should successfully get sdk integration source", () => { + const SDKIntegrationSource = "spbf"; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute( + "data-sdk-integration-source", + SDKIntegrationSource + ); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getSDKIntegrationSource()).toEqual(SDKIntegrationSource); + }); + + it("should successfully get popup disabled attribute as true when set to true", () => { + const popupsDisabled = true; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-popups-disabled", popupsDisabled.toString()); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getMerchantRequestedPopupsDisabled()).toEqual(popupsDisabled); + }); + + it("should successfully get popup disabled attribute as false when set to false", () => { + const popupsDisabled = false; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-popups-disabled", popupsDisabled.toString()); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getMerchantRequestedPopupsDisabled()).toEqual(popupsDisabled); + }); + + it("should successfully get popup disabled attribute as false when not set", () => { + const expectedPopupsDisabled = false; + const mockElement = makeMockScriptElement(mockScriptSrc); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getMerchantRequestedPopupsDisabled()).toEqual( + expectedPopupsDisabled + ); + }); + + it("should successfully get the page type", () => { + const pageType = "home"; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-page-type", pageType); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getPageType()).toEqual(pageType); + }); + + it("should successfully get the page type if not same case", () => { + const pageType = "Home"; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-page-type", pageType); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getPageType()).toEqual(pageType.toLowerCase()); + }); + + it("should throw error if invalid page type", () => { + const pageType = "abc"; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-page-type", pageType); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getPageType).toThrow(`Invalid page type, '${pageType}'`); + }); + + it("should default to empty page-type if none provided", () => { + const mockElement = makeMockScriptElement(mockScriptSrc); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getPageType()).toEqual(""); + }); + + it("should successfully get locale from script", () => { + const expectedLocale = "es_ES"; + + const sdkUrl = `${mockScriptSrc}&locale=${expectedLocale}`; + const mockElement = makeMockScriptElement(sdkUrl); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + const result = getLocale(); + const receivedLocal = `${result.lang}_${result.country}`; + expect(receivedLocal).toEqual(expectedLocale); + }); + + it("should successfully get locale from browser settings", () => { + const expectedLocale = "fr_FR"; + window.navigator.languages = [expectedLocale]; // eslint-disable-line compat/compat + + const mockElement = makeMockScriptElement(mockScriptSrc); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + const result = getLocale(); + const receivedLocal = `${result.lang}_${result.country}`; + expect(receivedLocal).toEqual(expectedLocale); + }); + + it("should infer locale country from language", () => { + const expectedLocale = "ja_JP"; + window.navigator.languages = ["ja"]; // eslint-disable-line compat/compat + + const mockElement = makeMockScriptElement(mockScriptSrc); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + const result = getLocale(); + const receivedLocal = `${result.lang}_${result.country}`; + expect(receivedLocal).toEqual(expectedLocale); + }); + + it("should return default if unable to infer locale country", () => { + const expectedLocale = "en_US"; + window.navigator.languages = ["es"]; // eslint-disable-line compat/compat + + const mockElement = makeMockScriptElement(mockScriptSrc); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + const result = getLocale(); + const receivedLocal = `${result.lang}_${result.country}`; + expect(receivedLocal).toEqual(expectedLocale); + }); + + it("should return default locale if none detected", () => { + const expectedLocale = "en_US"; + + const mockElement = makeMockScriptElement(mockScriptSrc); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + const result = getLocale(); + const receivedLocal = `${result.lang}_${result.country}`; + expect(receivedLocal).toEqual(expectedLocale); + }); + + it("should return default locale from country when only country was detected", () => { + window.navigator.languages = ["zz_US"]; // eslint-disable-line compat/compat + window.navigator.language = undefined; + const expectedLocale = "en_US"; + + const mockElement = makeMockScriptElement(mockScriptSrc); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + const result = getLocale(); + const receivedLocal = `${result.lang}_${result.country}`; + expect(receivedLocal).toEqual(expectedLocale); + }); + + it("should return computed lang when locale is zh_HK", () => { + const expectedLang = "zh_Hant"; + const inputLocale = "zh_HK"; + const sdkUrl = `${mockScriptSrc}&locale=${inputLocale}`; + const mockElement = makeMockScriptElement(sdkUrl); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + const result = getLocale(); + expect(result.lang).toEqual(expectedLang); + }); + + it("should return the right computed lang when locale is en_Hk", () => { + const expectedLang = "en"; + + const inputLocale = `${expectedLang}_HK`; + const sdkUrl = `${mockScriptSrc}&locale=${inputLocale}`; + const mockElement = makeMockScriptElement(sdkUrl); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + const result = getLocale(); + expect(result.lang).toEqual(expectedLang); + }); + + it("getScriptUrl should return the src of the script element", () => { + const mockElement = makeMockScriptElement(mockScriptSrc); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + const result = getScriptUrl(); + + expect(result).toEqual(mockScriptSrc); + }); + + describe("getEnableFunding()", () => { + it("getEnableFunding should return an empty array when enable-funding is not configure", () => { + const result = getEnableFunding(); + expect(result).toEqual([]); + }); + + it("getEnableFunding should return a valid array when enable-funding is configure", () => { + const expectedFunding = "paypal"; + const sdkUrl = `${mockScriptSrc}&enable-funding=${expectedFunding}`; + const mockElement = makeMockScriptElement(sdkUrl); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + const result = getEnableFunding(); + expect(result).toEqual([expectedFunding]); + }); + }); + describe("getDisableFunding()", () => { + it("getDisableFunding should return an empty array when disable-funding is not configure", () => { + const result = getDisableFunding(); + + expect(result).toEqual([]); + }); + + it("getDisableFunding should return a valid array when disable-funding is configure", () => { + const disableFunding = "paypal"; + const sdkUrl = `${mockScriptSrc}&disable-funding=${disableFunding}`; + const mockElement = makeMockScriptElement(sdkUrl); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + const result = getDisableFunding(); + expect(result).toEqual([disableFunding]); + }); + }); + + describe("getDisableCard", () => { + it("getDisableCard should return an empty array when disable-card is not configure", () => { + const result = getDisableCard(); + expect(result).toEqual([]); + }); + + it("getDisableCard should return a valid array when disable-card is configure", () => { + const disableCard = "paypal"; + const sdkUrl = `${mockScriptSrc}&disable-card=${disableCard}`; + const mockElement = makeMockScriptElement(sdkUrl); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + const result = getDisableCard(); + expect(result).toEqual([disableCard]); + }); + }); + + describe("getBuyerCountry()", () => { + it("getBuyerCountry should return the buyer country", () => { + const buyerCountry = "US"; + const sdkUrl = `${mockScriptSrc}&buyer-country=${buyerCountry}`; + const mockElement = makeMockScriptElement(sdkUrl); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + const result = getBuyerCountry(); + expect(result).toEqual(buyerCountry); + }); + }); + + describe("getAmount()", () => { + it("getAmount should return and error when the amount format is not correct", () => { + const inputAmount = 10; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-amount", inputAmount.toString()); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + expect(getAmount).toThrow(`Invalid amount: ${inputAmount}`); + }); + + it("getAmount should return the amount", () => { + const inputAmount = "10.00"; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-amount", inputAmount); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + const result = getAmount(); + expect(result).toEqual(inputAmount); + }); + }); + + it("getUserIDToken return a token string", () => { + const inputToken = "some-token"; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-user-id-token", inputToken); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + const result = getUserIDToken(); + expect(result).toEqual(inputToken); + }); + + it("getCSPNonce should return a data-csp-nonce string", () => { + const inputCspNonce = "some-csp-nonce"; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-csp-nonce", inputCspNonce); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + const result = getCSPNonce(); + expect(result).toEqual(inputCspNonce); + }); + + it('getEnableThreeDomainSecure should return "true"', () => { + const inputEnable3DS = true; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-enable-3ds", inputEnable3DS.toString()); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + const result = getEnableThreeDomainSecure(); + expect(result).toEqual(inputEnable3DS); + }); + + it("getUserExperienceFlow should return a valid string", () => { + const inputUserFlow = "flow"; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-user-experience-flow", inputUserFlow); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + const result = getUserExperienceFlow(); + expect(result).toEqual(inputUserFlow); + }); + + it('isChildWindow should return "false" when is not a child zoid window', () => { + const result = isChildWindow(); + expect(result).toEqual(false); + }); +}); diff --git a/src/session.js b/src/session.js index 4f61b37d..3ce88eff 100644 --- a/src/session.js +++ b/src/session.js @@ -9,7 +9,7 @@ export function getClientMetadataID(): ?string { return getSDKAttribute(SDK_SETTINGS.CLIENT_METADATA_ID); } -function getSDKStorage(): Storage { +export function getSDKStorage(): Storage { return getStorage({ name: getNamespace(), stickySessionId: getClientMetadataID() || "", diff --git a/src/session.test.js b/src/session.test.js new file mode 100644 index 00000000..1b84ec0a --- /dev/null +++ b/src/session.test.js @@ -0,0 +1,78 @@ +/* @flow */ +import { describe, it, afterEach, expect, vi } from "vitest"; +import { getCurrentScript, memoize, getStorage } from "@krakenjs/belter/src"; + +import { makeMockScriptElement } from "../test/helpers"; + +import { + getStorageState, + getStorageID, + getSessionState, + getClientMetadataID, + getSDKStorage, +} from "./session"; + +const clientId = "foobar123"; +const mockScriptSrc = `https://test.paypal.com/sdk/js?client-id=${clientId}`; + +vi.mock("@krakenjs/belter/src", async () => { + const actual = await vi.importActual("@krakenjs/belter/src"); + return { + ...actual, + getCurrentScript: vi.fn(() => { + return makeMockScriptElement(mockScriptSrc); + }), + getStorage: vi.fn((args) => actual.getStorage(args)), + }; +}); + +describe("session cases", () => { + afterEach(() => { + vi.clearAllMocks(); + memoize.clear(); + }); + + it("getStorageState", () => { + const result = getStorageState((storage) => storage); + + if (!result.hasOwnProperty("id")) { + throw new Error(`should get storage state object, but got ${result}`); + } + }); + + it("getStorageID", () => { + const result = getStorageID(); + + if (typeof result !== "string") { + throw new TypeError(`should get the storage id, but got ${result}`); + } + }); + + it("getSessionState", () => { + const result = getSessionState((state) => state); + + if (Object.entries(result).length > 0) { + throw new Error(`should get an empty state object, but got ${result}`); + } + }); + + it("getClientMetadataID", () => { + const mockMerchantIds = "some-client-meta-data-id"; + const mockElement = makeMockScriptElement(mockScriptSrc); + mockElement.setAttribute("data-client-metadata-id", mockMerchantIds); + // $FlowIgnore + getCurrentScript.mockReturnValue(mockElement); + + const result = getClientMetadataID(); + expect(result).toEqual(mockMerchantIds); + }); + + it("uses getStorage to retrieve the storage", () => { + getSDKStorage(); + + expect(getStorage).toBeCalledWith({ + name: expect.any(String), + stickySessionId: expect.any(String), + }); + }); +}); diff --git a/src/tracking.js b/src/tracking.js index c2841c38..cb83add0 100644 --- a/src/tracking.js +++ b/src/tracking.js @@ -45,7 +45,6 @@ export function getSDKInitTime(): number { export function setupLogger() { const logger = getLogger(); - sdkInitTime = Date.now(); logger.addPayloadBuilder(() => { diff --git a/src/tracking.test.js b/src/tracking.test.js new file mode 100644 index 00000000..e11ce9df --- /dev/null +++ b/src/tracking.test.js @@ -0,0 +1,36 @@ +/* @flow */ +import { describe, it, vi, expect } from "vitest"; + +import { makeMockScriptElement } from "../test/helpers"; + +import { getSDKInitTime, setupLogger } from "./tracking"; + +const clientId = "foobar123"; +const mockScriptSrc = `https://test.paypal.com/sdk/js?client-id=${clientId}`; + +vi.mock("@krakenjs/belter/src", async () => { + const actual = await vi.importActual("@krakenjs/belter/src"); + return { + ...actual, + getCurrentScript: vi.fn(() => { + return makeMockScriptElement(mockScriptSrc); + }), + }; +}); + +describe(`tracking cases`, () => { + it("should throw an Error", () => { + const errorMessage = "SDK not initialized"; + expect(getSDKInitTime).toThrow(errorMessage); + }); + + it("should find a saved Date instance", () => { + setupLogger(); + expect(getSDKInitTime()).toEqual(expect.any(Number)); + }); + + it("setupLogger should setup logger with IEIntranet enabled", () => { + window.document.documentMode = true; + setupLogger(); + }); +}); diff --git a/test/client/api.js b/test/client/api.js deleted file mode 100644 index 0a06d0db..00000000 --- a/test/client/api.js +++ /dev/null @@ -1,193 +0,0 @@ -/* @flow */ - -import { $mockEndpoint } from "@krakenjs/sync-browser-mocks/dist/sync-browser-mocks"; - -import { createAccessToken, createOrder } from "../../src/api"; - -describe("api cases", () => { - const expectedToken = - "A21AAKNZBaqilFBC4dVVz-tr-ySIT78NREeBidy3lkGdr-EA8wbhGrByPayhgnJRPE5xg4QW46moDbCFjZ13i1GH-Ax4SjtjA"; - const defaultAuthResponse = { - scope: "https://uri.paypal.com/services/invoicing", - access_token: expectedToken, - token_type: "Bearer", - app_id: "APP-80W284485P519543T", - expires_in: 31838, - nonce: "2022-03-07T22:41:38ZqHkiC0_odfzFwo27_X0wVuF67STYq39KRplBeeyY2bk", - }; - const mockAuthEndpoint = function (data = defaultAuthResponse) { - $mockEndpoint - .register({ - method: "POST", - uri: `${window.location.protocol}//${window.location.host}/v1/oauth2/token`, - data, - }) - .listen(); - }; - const mockCreateOrder = function (data) { - $mockEndpoint - .register({ - method: "POST", - uri: `${window.location.protocol}//${window.location.host}/v2/checkout/orders`, - data, - }) - .listen(); - }; - let order; - - beforeEach(() => { - order = { - intent: "capture", - purchase_units: [ - { - amount: { - value: "10.00", - currency_code: "USD", - }, - }, - ], - }; - }); - - it("createAccessToken should return a valid token", async () => { - mockAuthEndpoint(); - const result = await createAccessToken("testClient"); - - if (result !== expectedToken) { - throw new Error( - `should receive token equals '${expectedToken}', but got: ${String( - result - )}` - ); - } - }); - - it("createAccessToken should return invalid client argument", async () => { - mockAuthEndpoint({ - error: "invalid_client", - }); - - try { - await createAccessToken("testClient"); - } catch (err) { - if (!err.message.startsWith("Auth Api invalid client id:")) { - throw new Error( - `should throw an error message starting with 'Auth Api invalid client id:', but got: '${err}'` - ); - } - } - }); - - it("createAccessToken should return an error message when response is an empty object", async () => { - mockAuthEndpoint({}); - - try { - await createAccessToken("testClient"); - } catch (err) { - if (!err.message.startsWith("Auth Api response error:")) { - throw new Error( - `should throw an error message starting with 'Auth Api response error:', but got: '${err}'` - ); - } - } - }); - - it("createOrder should throw an error when clientId is null", async () => { - const expectedErrorMessage = "Client ID not passed"; - - try { - // $FlowIgnore[incompatible-call] - await createOrder(null); - } catch (err) { - if (err.message !== expectedErrorMessage) { - throw new Error( - `should throw an error with message '${expectedErrorMessage}', but got: '${err.message}'` - ); - } - } - }); - - it("createOrder should throw an error when order is null", async () => { - const expectedErrorMessage = "Expected order details to be passed"; - - try { - // $FlowIgnore[incompatible-call] - await createOrder("testClient"); - } catch (err) { - if (err.message !== expectedErrorMessage) { - throw new Error( - `should throw an error with message '${expectedErrorMessage}', but got: '${err.message}'` - ); - } - } - }); - - it("createOrder should throw an error when order intent does not match with query parameters intent", async () => { - const expectedErrorMessage = - "Unexpected intent: authorize passed to order.create. Please ensure you are passing /sdk/js?intent=authorize in the paypal script tag."; - order.intent = "authorize"; - - try { - // $FlowIgnore[incompatible-call] - await createOrder("testClient", order); - } catch (err) { - if (err.message !== expectedErrorMessage) { - throw new Error( - `should throw an error with message '${expectedErrorMessage}', but got: '${err.message}'` - ); - } - } - }); - - it("createOrder should throw an error when order currency does not match with query parameters currency", async () => { - const expectedErrorMessage = - "Unexpected currency: AUD passed to order.create. Please ensure you are passing /sdk/js?currency=AUD in the paypal script tag."; - order.purchase_units[0].amount.currency_code = "AUD"; - - try { - await createOrder("testClient", order); - } catch (err) { - if (err.message !== expectedErrorMessage) { - throw new Error( - `should throw an error with message '${expectedErrorMessage}', but got: '${err.message}'` - ); - } - } - }); - - it("createOrder should throw an error when order identifier is not in the server response", async () => { - const expectedErrorMessage = "Order Api response error:"; - - mockAuthEndpoint(); - mockCreateOrder({}); - - try { - await createOrder("testClient", order); - } catch (err) { - if (!err.message.startsWith(expectedErrorMessage)) { - throw new Error( - `should and error starting with the string "${expectedErrorMessage}", but got: ${err.message}` - ); - } - } - }); - - it("createOrder should return a valid orderId", async () => { - const expectedOrderId = "9BL31648CM342010L"; - - mockAuthEndpoint(); - mockCreateOrder({ - id: expectedOrderId, - status: "CREATED", - links: [], - }); - - const result = await createOrder("testClient", order); - - if (result !== expectedOrderId) { - throw new Error( - `should return orderId "${expectedOrderId}", but got: ${String(result)}` - ); - } - }); -}); diff --git a/test/client/common.js b/test/client/common.js deleted file mode 100644 index 9e37cd28..00000000 --- a/test/client/common.js +++ /dev/null @@ -1,24 +0,0 @@ -/* @flow */ - -import { noop } from "@krakenjs/belter/src"; - -import { insertMockSDKScript } from "../../src"; - -function clearErrorListener() { - // eslint-disable-next-line unicorn/prefer-add-event-listener - window.onerror = noop; -} - -beforeEach(() => { - clearErrorListener(); - insertMockSDKScript(); -}); - -window.console.karma = function consoleKarma() { - const karma = - window.karma || - (window.top && window.top.karma) || - (window.opener && window.opener.karma); - karma.log("debug", arguments); - console.log.apply(console, arguments); // eslint-disable-line no-console -}; diff --git a/test/client/config.js b/test/client/config.js deleted file mode 100644 index 6bc4029d..00000000 --- a/test/client/config.js +++ /dev/null @@ -1,57 +0,0 @@ -/* @flow */ - -import { - getPayPalLoggerDomain, - buildPayPalUrl, - buildPayPalAPIUrl, - getPayPalLoggerUrl, -} from "../../src"; - -beforeEach(() => { - window.__ENV__ = "test"; -}); - -describe(`config cases`, () => { - it("should successfully get the paypal logger domain", () => { - const expectedPayPalDomain = "mock://www.paypal.com"; - - if (getPayPalLoggerDomain() !== expectedPayPalDomain) { - throw new Error( - `Expected paypal logger domain to be ${expectedPayPalDomain}, got ${getPayPalLoggerDomain()}` - ); - } - }); - - it("should successfully build a paypal url", () => { - const expectedPayPalUrl = `${window.location.protocol}//${window.location.host}/foo/bar`; - const result = buildPayPalUrl("/foo/bar"); - - if (result !== expectedPayPalUrl) { - throw new Error( - `Expected paypal url to be ${expectedPayPalUrl}, got ${result}` - ); - } - }); - - it("should successfully build a paypal api url", () => { - const expectedPayPalUrl = `${window.location.protocol}//${window.location.host}/bar/baz`; - const result = buildPayPalAPIUrl("/bar/baz"); - - if (result !== expectedPayPalUrl) { - throw new Error( - `Expected paypal api url to be ${expectedPayPalUrl}, got ${result}` - ); - } - }); - - it("should successfully build a paypal logger url", () => { - const expectedPayPalUrl = `${window.location.protocol}//${window.location.host}/xoplatform/logger/api/logger`; - const result = getPayPalLoggerUrl(); - - if (result !== expectedPayPalUrl) { - throw new Error( - `Expected paypal logger url to be ${expectedPayPalUrl}, got ${result}` - ); - } - }); -}); diff --git a/test/client/domains.js b/test/client/domains.js deleted file mode 100644 index d13c338d..00000000 --- a/test/client/domains.js +++ /dev/null @@ -1,160 +0,0 @@ -/* @flow */ -import { ENV } from "@paypal/sdk-constants/src"; - -import { - getPayPalDomainRegex, - getVenmoDomainRegex, - isPayPalTrustedDomain, -} from "../../src"; -import { - getPayPalLoggerDomain, - getAuthAPIUrl, - getOrderAPIUrl, -} from "../../src/domains"; - -beforeEach(() => { - window.__ENV__ = "test"; -}); - -describe(`domains test`, () => { - it("should successfully match valid paypal domain", () => { - const validDomains = [ - "master.qa.paypal.com", - "test-env.qa.paypal.com:3000", - "geo.qa.paypal.com", - "www.paypal.com:3080", - "www.paypal.cn", - "www.paypal.cn:3000", - "www.mschina.qa.paypal.cn", - "www.paypal.com", - ]; - - for (const domain of validDomains) { - if (!domain.match(getPayPalDomainRegex())) { - throw new Error(`${domain} must match the regex`); - } - } - }); - - it("should not match invalid paypal domains", () => { - const invalidDomains = [ - "www.paypal.com.example.com", - "www.paypal.cn.example.com", - ]; - - for (const domain of invalidDomains) { - if (domain.match(getPayPalDomainRegex())) { - throw new Error(`${domain} must not match the regex`); - } - } - }); - - it("should successfully match valid venmo domain", () => { - const validDomains = [ - "https://venmo.com", - "http://www.venmo.com", - "https://id.venmo.com", - "http://www.venmo.com:8000", - "https://account.qa.venmo.com", - "http://www.account.qa.venmo.com", - "https://account.qa.venmo.com", - "https://account.venmo.com", - ]; - - for (const domain of validDomains) { - if (!domain.match(getVenmoDomainRegex())) { - throw new Error(`${domain} must match the regex`); - } - } - }); - - it("should successfully match valid venmo testing domain", () => { - window.__ENV__ = "local"; - const validDomains = ["https://localhost.venmo.com"]; - - for (const domain of validDomains) { - if (!domain.match(getVenmoDomainRegex())) { - throw new Error(`${domain} must match the regex`); - } - } - }); - - it("should not match invalid venmo domains", () => { - const invalidDomains = [ - "www.venmo.com.example.com", - "www.venmo.cn.example.com", - "www.venmo.com", - ]; - - for (const domain of invalidDomains) { - if (domain.match(getVenmoDomainRegex())) { - throw new Error(`${domain} must not match the regex`); - } - } - }); - - it("isPayPalTrustedDomain should return true", () => { - window.__ENV__ = ENV.LOCAL; - const result = isPayPalTrustedDomain(); - - if (!result) { - throw new Error("should get true, but got false"); - } - }); - - it("getPayPalLoggerDomain should return the logger domain when is a local environment", () => { - window.__ENV__ = ENV.LOCAL; - const result = getPayPalLoggerDomain(); - - if (result !== "https://mock://sandbox.paypal.com") { - throw new Error( - `should get the logger domain "https://mock://sandbox.paypal.com", but got: ${result}` - ); - } - }); - - it("getPayPalLoggerDomain should thrown an Error when is a local environment and the stage host is undefined", () => { - window.__ENV__ = ENV.LOCAL; - window.__STAGE_HOST__ = undefined; - try { - getPayPalLoggerDomain(); - } catch (err) { - if (err.message !== "No stage host found") { - throw new Error( - `should thrown exception with message "No stage host found", but got ${err.message}` - ); - } - } - window.__STAGE_HOST__ = "mock://sandbox.paypal.com"; - }); - - it("getAuthAPIUrl should return a valid authentication string URL", () => { - const url = new URL(getAuthAPIUrl()); // eslint-disable-line compat/compat - - if ( - `${url.protocol}//${url.hostname}` !== "http://localhost" || - url.pathname !== "/v1/oauth2/token" - ) { - throw new Error( - `should get the logger domain "${window.location.protocol}//${ - window.location.host - }/v1/oauth2/token", but got: ${url.toString()}` - ); - } - }); - - it("getOrderAPIUrl should return a valid order string URL", () => { - const url = new URL(getOrderAPIUrl()); // eslint-disable-line compat/compat - - if ( - `${url.protocol}//${url.hostname}` !== "http://localhost" || - url.pathname !== "/v2/checkout/orders" - ) { - throw new Error( - `should get the logger domain "${window.location.protocol}//${ - window.location.host - }/v2/checkout/orders", but got: ${url.toString()}` - ); - } - }); -}); diff --git a/test/client/global.js b/test/client/global.js deleted file mode 100644 index f6dabb42..00000000 --- a/test/client/global.js +++ /dev/null @@ -1,359 +0,0 @@ -/* @flow */ - -import { PLATFORM, PROTOCOL } from "@paypal/sdk-constants/src"; - -import { - getSDKHost, - getHost, - getProtocol, - getHostName, - getPort, - getDefaultServiceStageHost, - getDefaultAPIStageHost, - getStageHost, - getFundingEligibility, - getAPIStageHost, - getDebug, - getComponents, - getPath, - getEnv, - getDefaultStageHost, - getVersion, - getCorrelationID, - getPlatform, - getExperimentation, -} from "../../src"; - -describe(`globals cases`, () => { - afterEach(() => { - window.__STAGE_HOST__ = "mock://sandbox.paypal.com"; - delete window.__PROTOCOL__; - delete window.__SERVICE_STAGE_HOST__; - delete window.__COMPONENTS__; - delete window.__FUNDING_ELIGIBILITY__; - }); - - it("should successfully get the host", () => { - const expectedResult = "test.paypal.com"; - const result = getHost(); - - if (expectedResult !== result) { - throw new Error(`Expected host to be ${expectedResult}, got ${result}`); - } - }); - - it("should successfully get the hostname", () => { - const expectedResult = "test.paypal.com"; - const result = getHostName(); - - if (expectedResult !== result) { - throw new Error( - `Expected hostname to be ${expectedResult}, got ${result}` - ); - } - }); - - it("should successfully get the port", () => { - const expectedResult = 8000; - const result = getPort(); - - if (expectedResult !== result) { - throw new Error(`Expected port to be ${expectedResult}, got ${result}`); - } - }); - - it("should successfully get the path", () => { - const expectedResult = "/sdk/js"; - const result = getPath(); - - if (expectedResult !== result) { - throw new Error(`Expected path to be ${expectedResult}, got ${result}`); - } - }); - - it("should successfully get the env", () => { - const expectedResult = "test"; - const result = getEnv(); - - if (expectedResult !== result) { - throw new Error(`Expected env to be ${expectedResult}, got ${result}`); - } - }); - - it('should get the default stage host when "window.__STAGE_HOST__" is undefined', () => { - window.__STAGE_HOST__ = undefined; - const result = getDefaultStageHost(); - - if (result !== undefined) { - throw new Error( - `Expected default stage host to be undefined, got ${String(result)}` - ); - } - }); - - it("should successfully get the default stage host", () => { - const expectedResult = "mock://sandbox.paypal.com"; - const result = getDefaultStageHost(); - - if (expectedResult !== result) { - throw new Error( - `Expected default stage host to be ${expectedResult}, got ${String( - result - )}` - ); - } - }); - - it("should successfully get the version", () => { - const expectedResult = "1.0.45"; - const result = getVersion(); - - if (expectedResult !== result) { - throw new Error( - `Expected version to be ${expectedResult}, got ${result}` - ); - } - }); - - it("should successfully get the correlation id", () => { - const expectedResult = "abc123"; - const result = getCorrelationID(); - - if (expectedResult !== result) { - throw new Error( - `Expected correlation id to be ${expectedResult}, got ${result}` - ); - } - - window.__CORRELATION_ID__ = "def345"; - - const newExpectedResult = "def345"; - const newResult = getCorrelationID(); - - if (newExpectedResult !== newResult) { - throw new Error( - `Expected correlation id to be ${newExpectedResult}, got ${newResult}` - ); - } - - delete window.__CORRELATION_ID__; - }); - - it("should successfully get the SDK host", () => { - const result = getSDKHost(); - - if (__SDK_HOST__ !== result) { - throw new Error(`Expected SDK host to be ${__SDK_HOST__}, got ${result}`); - } - }); - - it("should successfully get the default protocol", () => { - const result = getProtocol(); - - if (PROTOCOL.HTTPS !== result) { - throw new Error( - `Expected protocol to be ${PROTOCOL.HTTPS}, got ${result}` - ); - } - }); - - it("should successfully get the global protocol", () => { - window.__PROTOCOL__ = "http"; - const result = getProtocol(); - - if (PROTOCOL.HTTP !== result) { - throw new Error( - `Expected set protocol to be ${PROTOCOL.HTTP}, got ${result}` - ); - } - }); - - it("should get the default service stage host when undefined", () => { - window.__SERVICE_STAGE_HOST__ = undefined; - const result = getDefaultServiceStageHost(); - - if (result !== undefined) { - throw new Error( - `Expected to be undefine the default service stage host, got ${String( - result - )}` - ); - } - }); - - it("should successfully get the default service stage host", () => { - window.__SERVICE_STAGE_HOST__ = "mock://sandbox.paypal.com"; - const result = getDefaultServiceStageHost(); - - if (__SERVICE_STAGE_HOST__ !== result) { - throw new Error(`Expected to be the default service stage host`); - } - }); - - it("should successfully identify desktop platform", () => { - const result = getPlatform(); - - if (PLATFORM.DESKTOP !== result) { - throw new Error(`Expected to be desktop platform, got ${result}`); - } - }); - - it("should get the API stage from the default service stage host", () => { - window.__SERVICE_STAGE_HOST__ = "mock://sandbox.paypal.com"; - const result = getDefaultAPIStageHost(); - - if (window.__SERVICE_STAGE_HOST__ !== result) { - throw new Error( - `Expected default API stage host to be ${ - window.__SERVICE_STAGE_HOST__ - }, got ${result || ""}` - ); - } - }); - - it("should get the API stage from the default stage host", () => { - window.__SERVICE_STAGE_HOST__ = undefined; - const result = getDefaultAPIStageHost(); - - if (__STAGE_HOST__ !== result) { - throw new Error( - `Expected default API stage host to be ${window.__STAGE_HOST__}, got ${ - result || "" - }` - ); - } - }); - - it("should get the API stage when undefined", () => { - window.__STAGE_HOST__ = window.__SERVICE_STAGE_HOST__ = undefined; - const result = getDefaultAPIStageHost(); - - if (result !== undefined) { - throw new Error( - `Expected API stage to be undefined, but got ${String(result)}` - ); - } - }); - - it("should successfully get the stage host", () => { - const result = getStageHost(); - - if (__STAGE_HOST__ !== result) { - throw new Error( - `Expected stage host to be ${window.__STAGE_HOST__}, got ${ - result || "" - }` - ); - } - }); - - it("should successfully get the API stage host", () => { - const result = getAPIStageHost(); - - if (__STAGE_HOST__ !== result) { - throw new Error( - `Expected API stage host to be ${window.__STAGE_HOST__}, got ${ - result || "" - }` - ); - } - }); - - it("should get the API stage host when undefined", () => { - window.__STAGE_HOST__ = window.__SERVICE_STAGE_HOST__ = undefined; - const result = getAPIStageHost(); - - if (result !== undefined) { - throw new Error( - `Expected API stage host to be undefined, got ${String(result)}` - ); - } - }); - - it("should successfully get the debug flag", () => { - window.__DEBUG__ = true; - const result = getDebug(); - - if (window.__DEBUG__ !== result) { - throw new Error( - `Expected debug flag to be ${ - window.__DEBUG__ - }, got ${result.toString()}` - ); - } - }); - - it("should successfully get the components list", () => { - window.__COMPONENTS__ = ["buttons", "venmo"]; - const result = getComponents(); - - if (result[0] !== "buttons" || result[1] !== "venmo") { - throw new Error( - `Expected components to be ${ - window.__COMPONENTS__ - }, got ${result.toString()}` - ); - } - }); - - it("should successfully get the funding eligibility type", () => { - window.__FUNDING_ELIGIBILITY__ = "credit"; - const result = getFundingEligibility(); - - if (window.__FUNDING_ELIGIBILITY__ !== result) { - throw new Error( - `Expected funding eligibility type to be ${ - window.__FUNDING_ELIGIBILITY__ - }, got ${result.toString()}` - ); - } - }); - - it("should successfully get experimation value", () => { - window.__EXPERIMENTATION__ = { - __EXPERIENCE__: "1234, 4321", - __TREATMENT__: "8765,7890", - }; - const expectedResult = { - experience: "1234, 4321", - treatment: "8765,7890", - }; - const result = getExperimentation(); - - if (JSON.stringify(result) !== JSON.stringify(expectedResult)) { - throw new Error( - `Expected experimation to be ${JSON.stringify( - expectedResult - )}, got ${JSON.stringify(result)}` - ); - } - }); - - it("should get experimation null value", () => { - window.__EXPERIMENTATION__ = null; - const expectedResult = null; - const result = getExperimentation(); - - if (result !== expectedResult) { - throw new Error( - `Expected experimation to be ${String(expectedResult)}, got ${String( - result - )}` - ); - } - }); - - it("should get experimation empty value", () => { - window.__EXPERIMENTATION__ = {}; - const expectedResult = {}; - const result = getExperimentation(); - - if (JSON.stringify(result) !== JSON.stringify(expectedResult)) { - throw new Error( - `Expected experimation to be ${JSON.stringify( - expectedResult - )}, got ${JSON.stringify(result)}` - ); - } - }); -}); diff --git a/test/client/graphql.js b/test/client/graphql.js deleted file mode 100644 index 739940d8..00000000 --- a/test/client/graphql.js +++ /dev/null @@ -1,100 +0,0 @@ -/* @flow */ - -import { $mockEndpoint } from "@krakenjs/sync-browser-mocks/dist/sync-browser-mocks"; - -import { callGraphQL, getGraphQLFundingEligibility } from "../../src/graphql"; -import { insertMockSDKScript } from "../../src"; - -describe("graphql cases", () => { - const mockGraphQl = function (data, status = 200) { - $mockEndpoint - .register({ - method: "POST", - uri: `${window.location.protocol}//${window.location.host}/graphql`, - status, - data, - }) - .listen(); - }; - - it("callGraphQL should fail with status code 404 when the URL was not found", async () => { - mockGraphQl({}, 404); - try { - await callGraphQL({ - query: "query {}", - }); - } catch (err) { - if (err.message !== "/graphql returned status 404") { - throw new Error( - `should throw an error message "/graphql returned status 404", but got: ${err.message}` - ); - } - } - }); - - it("callGraphQL should throw an exception when the response body contains errors", async () => { - const sourceData = { errors: ["unexpected error"] }; - - mockGraphQl(sourceData); - try { - await callGraphQL({ - query: "query {}", - }); - } catch (err) { - if (JSON.stringify(sourceData.errors[0]) !== err.message) { - throw new Error( - `should throw an error message "${sourceData.errors[0]}", but got: ${err.message}` - ); - } - } - }); - - it("callGraphQL should return a valid body response", async () => { - const sourceData = { data: { received: true } }; - - mockGraphQl(sourceData); - // $FlowIgnore[prop-missing] - const { received } = await callGraphQL({ - query: "query {}", - }); - - if (sourceData.data.received !== received) { - throw new Error( - `should return a valid data response, but got: ${String(received)}` - ); - } - }); - - it("getGraphQLFundingEligibility should throw an error when fundingEligibility is not in the response", async () => { - const expectedErrorMessage = - "GraphQL fundingEligibility returned no fundingEligibility object"; - - try { - await getGraphQLFundingEligibility("fields"); - } catch (err) { - if (err.message !== expectedErrorMessage) { - throw new Error( - `should throw an error message "${expectedErrorMessage}", but got: ${err.message}` - ); - } - } - }); - - it("getGraphQLFundingEligibility should return the fundingEligibility", async () => { - insertMockSDKScript({ - query: { - "enable-funding": "paypal", - "disable-funding": "venmo", - "disable-card": "braintree", - }, - }); - mockGraphQl({ data: { fundingEligibility: {} } }); - const result = await getGraphQLFundingEligibility("field: field"); - - if (!result) { - throw new Error( - `should return finding eligibility as "true", but got: ${result}` - ); - } - }); -}); diff --git a/test/client/index.js b/test/client/index.js deleted file mode 100644 index 3d5ba288..00000000 --- a/test/client/index.js +++ /dev/null @@ -1,14 +0,0 @@ -/* @flow */ - -import "./common"; -import "./meta"; -import "./script"; -import "./scriptUtils"; -import "./config"; -import "./global"; -import "./logger"; -import "./domains"; -import "./session"; -import "./api"; -import "./tracking"; -import "./graphql"; diff --git a/test/client/logger.js b/test/client/logger.js deleted file mode 100644 index c03858b6..00000000 --- a/test/client/logger.js +++ /dev/null @@ -1,226 +0,0 @@ -/* @flow */ - -import { - $mockEndpoint, - patchXmlHttpRequest, -} from "@krakenjs/sync-browser-mocks/dist/sync-browser-mocks"; -import { ZalgoPromise } from "@krakenjs/zalgo-promise/src"; - -import { getLogger, insertMockSDKScript } from "../../src"; - -describe("logger tests", () => { - before(() => { - patchXmlHttpRequest(); - }); - - it("should log and flush with all expected keys", () => { - insertMockSDKScript({ - query: { - "client-id": "foobarbaz", - "merchant-id": "hello123", - }, - attributes: { - "data-partner-attribution-id": "myattributionid", - "data-sdk-integration-source": "spbf", - }, - }); - - const logger = getLogger(); - - let logData; - - const logEndpoint = $mockEndpoint.register({ - method: "POST", - uri: `${window.location.protocol}//${window.location.host}/xoplatform/logger/api/logger`, - handler: (req) => { - logData = req.data; - return {}; - }, - }); - - // eslint-disable-next-line compat/compat - window.navigator.sendBeacon = (url, data) => { - logData = JSON.parse(data); - }; - - logger.info("foo", { bar: "baz" }); - logger.track({ hello: "world" }); - - logEndpoint.expectCalls(); - - return logger.flush().then(() => { - if (!logData) { - throw new Error(`Expected log data to be populated`); - } - - const event = logData.events.find((e) => e.event === "foo"); - - if (!event) { - throw new Error(`Expected to find foo event`); - } - - const expectedPayload = { - referer: window.location.host, - env: "test", - bar: "baz", - }; - - for (const key of Object.keys(expectedPayload)) { - if (event.payload[key] !== expectedPayload[key]) { - throw new Error( - `Expected logger payload value ${key} to be ${expectedPayload[key]} - got ${event.payload[key]}` - ); - } - } - - const expectedTracking = { - feed_name: "payments_sdk", - serverside_data_source: "checkout", - client_id: "foobarbaz", - seller_id: "hello123", - page_session_id: /^[a-zA-Z0-9_-]+$/, - referer_url: window.location.host, - locale: "en_US", - integration_identifier: "foobarbaz", - bn_code: "myattributionid", - sdk_name: "payments_sdk", - sdk_version: "1.0.45", - user_agent: window.navigator.userAgent, - user_action: "commit", - context_correlation_id: "abc123", - sdk_integration_source: "spbf", - }; - - const tracking = logData.tracking.find((e) => e.hello === "world"); - - if (!tracking) { - throw new Error(`Expected to find hello=world event`); - } - - for (const key of Object.keys(expectedTracking)) { - if (!tracking[key]) { - throw new Error(`Expected logger tracking value ${key} to be passed`); - } else if ( - expectedTracking[key] instanceof RegExp && - !tracking[key].match(expectedTracking[key]) - ) { - throw new Error( - `Expected logger tracking value ${key} to be ${expectedTracking[ - key - ].toString()} - got ${tracking[key]}` - ); - } else if ( - typeof expectedTracking[key] === "string" && - tracking[key] !== expectedTracking[key] - ) { - throw new Error( - `Expected logger tracking value ${key} to be ${expectedTracking[key]} - got ${tracking[key]}` - ); - } - } - }); - }); - - it("should auto-log on any unhandled errors", () => { - const logger = getLogger(); - - let logData; - - const logEndpoint = $mockEndpoint.register({ - method: "POST", - uri: `${window.location.protocol}//${window.location.host}/xoplatform/logger/api/logger`, - handler: (req) => { - logData = req.data; - return {}; - }, - }); - - // eslint-disable-next-line compat/compat - window.navigator.sendBeacon = (url, data) => { - logData = JSON.parse(data); - }; - - ZalgoPromise.try(() => { - throw new Error(`meep`); - }); - - logEndpoint.expectCalls(); - - return logger.flush().then(() => { - if (!logData) { - throw new Error(`Expected log data to be populated`); - } - - const event = logData.events.find((e) => e.event === "unhandled_error"); - - if (!event) { - throw new Error(`Expected to find unhandled_error event`); - } - - const expectedPayload = { - err: /meep/, - }; - - for (const key of Object.keys(expectedPayload)) { - if (!event.payload[key]) { - throw new Error(`Expected logger tracking value ${key} to be passed`); - } else if ( - expectedPayload[key] instanceof RegExp && - !event.payload[key].match(expectedPayload[key]) - ) { - throw new Error( - `Expected logger tracking value ${key} to be ${expectedPayload[ - key - ].toString()} - got ${event.payload[key]}` - ); - } else if ( - typeof expectedPayload[key] === "string" && - event.payload[key] !== expectedPayload[key] - ) { - throw new Error( - `Expected logger tracking value ${key} to be ${expectedPayload[ - key - ].toString()} - got ${event.payload[key]}` - ); - } - } - - const expectedTracking = { - ext_error_code: "payments_sdk_error", - ext_error_desc: /meep/, - }; - - const tracking = logData.tracking.find( - (e) => e.ext_error_code === "payments_sdk_error" - ); - - if (!tracking) { - throw new Error( - `Expected to find ext_error_code=payments_sdk_error event` - ); - } - - for (const key of Object.keys(expectedTracking)) { - if (!tracking[key]) { - throw new Error(`Expected logger tracking value ${key} to be passed`); - } else if ( - expectedTracking[key] instanceof RegExp && - !tracking[key].match(expectedTracking[key]) - ) { - throw new Error( - `Expected logger tracking value ${key} to be ${expectedTracking[ - key - ].toString()} - got ${tracking[key]}` - ); - } else if ( - typeof expectedTracking[key] === "string" && - tracking[key] !== expectedTracking[key] - ) { - throw new Error( - `Expected logger tracking value ${key} to be ${expectedTracking[key]} - got ${tracking[key]}` - ); - } - } - }); - }); -}); diff --git a/test/client/meta.js b/test/client/meta.js deleted file mode 100644 index 41ca53e1..00000000 --- a/test/client/meta.js +++ /dev/null @@ -1,111 +0,0 @@ -/* @flow */ - -import { getSDKMeta, insertMockSDKScript } from "../../src"; - -describe(`meta cases`, () => { - it("should successfully create a meta payload", () => { - const expectedUrl = insertMockSDKScript({ - query: { - "client-id": "foobar", - }, - }); - - const meta = getSDKMeta(); - - if (!meta) { - throw new Error(`Expected meta string to be returned`); - } - - const { url } = JSON.parse(window.atob(meta)); - - if (url !== expectedUrl) { - throw new Error(`Expected sdk url to be ${expectedUrl}, got ${url}`); - } - }); - - it("should successfully create a meta payload with merchant id", () => { - const expectedMerchantIds = "abcd1234,abcd5678"; - - insertMockSDKScript({ - query: { - "client-id": "foobar", - "merchant-id": "*", - }, - attributes: { - "data-merchant-id": expectedMerchantIds, - }, - }); - - const meta = getSDKMeta(); - - if (!meta) { - throw new Error(`Expected meta string to be returned`); - } - - const { - attrs: { "data-merchant-id": merchantIds }, - } = JSON.parse(window.atob(meta)); - - if (merchantIds !== expectedMerchantIds) { - throw new Error( - `Expected sdk merchant ids to be ${expectedMerchantIds}, got ${merchantIds}` - ); - } - }); - - it("should construct a valid script url with data-popups-disabled attribute", () => { - insertMockSDKScript({ - query: { - "client-id": "foobar", - }, - attributes: { - "data-popups-disabled": "true", - }, - }); - - const meta = getSDKMeta(); - - if (!meta) { - throw new Error(`Expected meta string to be returned`); - } - - const { - attrs: { "data-popups-disabled": dataPopupDisabled }, - } = JSON.parse(window.atob(meta)); - - if (dataPopupDisabled !== "true") { - throw new Error( - `Expected sdk dataPopupDisabled to be true , got ${dataPopupDisabled}` - ); - } - }); - - it("should successfully create a meta payload with data-csp-nonce", () => { - const dataCSPNonce = "12345"; - - insertMockSDKScript({ - query: { - "client-id": "foobar", - }, - attributes: { - "data-csp-nonce": dataCSPNonce, - }, - }); - - const meta = getSDKMeta(); - - if (!meta) { - throw new Error(`Expected meta string to be returned`); - } - - const { - attrs: { "data-csp-nonce": nonce }, - } = JSON.parse(window.atob(meta)); - - if (nonce !== dataCSPNonce) { - throw new Error( - `Expected sdk data-csp-nonce to be ${dataCSPNonce}, got ${nonce}` - ); - } - }); -}); diff --git a/test/client/script.js b/test/client/script.js deleted file mode 100644 index 5faaeed6..00000000 --- a/test/client/script.js +++ /dev/null @@ -1,586 +0,0 @@ -/* @flow */ -/* eslint max-lines: off */ - -import { base64encode } from "@krakenjs/belter/src"; - -import { - getClientID, - getIntent, - getCurrency, - getVault, - getCommit, - getClientToken, - getPartnerAttributionID, - getMerchantID, - getClientAccessToken, - getSDKIntegrationSource, - insertMockSDKScript, - getPageType, - getLocale, - getMerchantRequestedPopupsDisabled, -} from "../../src"; - -describe(`script cases`, () => { - beforeEach(() => { - Object.defineProperty(window.navigator, "languages", { - value: [], - writable: true, - }); - Object.defineProperty(window.navigator, "language", { - value: "", - writable: true, - }); - }); - - it("should successfully get a client id", () => { - const clientID = "foobar123"; - - const url = insertMockSDKScript({ - query: { - "client-id": clientID, - }, - }); - - if (clientID !== getClientID()) { - throw new Error( - `Expected client id to be ${clientID}, got ${getClientID()} from ${url}` - ); - } - }); - - it("should error out when client id not passed", () => { - let error; - - insertMockSDKScript({ - query: { - "client-id": "", - }, - }); - - try { - getClientID(); - } catch (err) { - error = err; - } - - if (!error) { - throw new Error(`Expected error to be thrown`); - } - }); - - it("should successfully get a client id alias", () => { - const clientID = "sb"; - - const url = insertMockSDKScript({ - query: { - "client-id": clientID, - }, - }); - - if (clientID === getClientID()) { - throw new Error(`Expected client id to not be ${clientID}, got ${url}`); - } - }); - - it("should successfully get a merchant id", () => { - const merchantID = "abc987"; - const url = insertMockSDKScript({ - query: { - "merchant-id": merchantID, - }, - }); - - const mID = getMerchantID(); - - if (merchantID !== (mID && mID[0])) { - throw new Error( - `Expected merchant id to be ${merchantID}, got ${ - (mID && mID[0]) || "undefined" - } from ${url}` - ); - } - }); - - it("should error out when merchant-id is * but data-merchant-id not passed", () => { - const merchantID = "*"; - let error; - - insertMockSDKScript({ - query: { - "merchant-id": merchantID, - }, - }); - - try { - getMerchantID(); - } catch (err) { - error = err; - } - - if (!error) { - throw new Error(`Expected error to be thrown`); - } - }); - - it("should error out when merchant-id is * but only one merchant id in data-merchant-id", () => { - const merchantID = "*"; - const dataMerchantIDs = "abc123"; - let error; - - insertMockSDKScript({ - query: { - "merchant-id": merchantID, - }, - attributes: { - "data-merchant-id": dataMerchantIDs, - }, - }); - - try { - getMerchantID(); - } catch (err) { - error = err; - } - - if (!error) { - throw new Error(`Expected error to be thrown`); - } - }); - - it("should error out when merchant-id is * but duplicated merchant id in data-merchant-id", () => { - const merchantID = "*"; - const dataMerchantIDs = "abc123,abc456,abc123"; - let error; - - insertMockSDKScript({ - query: { - "merchant-id": merchantID, - }, - attributes: { - "data-merchant-id": dataMerchantIDs, - }, - }); - - try { - getMerchantID(); - } catch (err) { - error = err; - } - - if (!error) { - throw new Error(`Expected error to be thrown`); - } - }); - - it("should successfully get merchant ids", () => { - const merchantID = "*"; - const dataMerchantIDs = "abc123,abc345"; - - const url = insertMockSDKScript({ - query: { - "merchant-id": merchantID, - }, - attributes: { - "data-merchant-id": dataMerchantIDs, - }, - }); - - const mID = getMerchantID(); - - if (dataMerchantIDs !== mID.join()) { - throw new Error( - `Expected merchant id to be ${merchantID}, got ${ - mID.join() || "undefined" - } from ${url}` - ); - } - }); - - it("should successfully get an intent", () => { - const intent = "authorize"; - - const url = insertMockSDKScript({ - query: { - intent, - }, - }); - - if (intent !== getIntent()) { - throw new Error( - `Expected intent to be ${intent}, got ${getIntent()} from ${url}` - ); - } - }); - - it("should successfully get a currency", () => { - const currency = "EUR"; - - const url = insertMockSDKScript({ - query: { - currency, - }, - }); - - if (currency !== getCurrency()) { - throw new Error( - `Expected currency to be ${currency}, got ${getCurrency()} from ${url}` - ); - } - }); - - it("should successfully get vault", () => { - const vault = true; - - const url = insertMockSDKScript({ - query: { - vault: vault.toString(), - }, - }); - - if (vault !== getVault()) { - throw new Error( - `Expected vault to be ${vault.toString()}, got ${getVault().toString()} from ${url}` - ); - } - }); - - it("should successfully get commit", () => { - const commit = false; - - const url = insertMockSDKScript({ - query: { - commit: commit.toString(), - }, - }); - - if (commit !== getCommit()) { - throw new Error( - `Expected vault to be ${commit.toString()}, got ${getCommit().toString()} from ${url}` - ); - } - }); - - it("should successfully get client token", () => { - const clientToken = "abc-xyz-123"; - - const url = insertMockSDKScript({ - attributes: { - "data-client-token": clientToken, - }, - }); - - if (clientToken !== getClientToken()) { - throw new Error( - `Expected client token to be ${clientToken}, got ${ - getClientToken() || "undefined" - } from ${url}` - ); - } - }); - - it("should not error out when client token not passed", () => { - let error; - - try { - getClientToken(); - } catch (err) { - error = err; - } - - if (error) { - throw new Error(`Expected error to not be thrown`); - } - }); - - it("should successfully get client access token", () => { - const clientAccessToken = "abc12354321"; - const clientToken = base64encode( - JSON.stringify({ - paypal: { - accessToken: clientAccessToken, - }, - }) - ); - - const url = insertMockSDKScript({ - attributes: { - "data-client-token": clientToken, - }, - }); - - if (clientAccessToken !== getClientAccessToken()) { - throw new Error( - `Expected client access token to be ${clientAccessToken}, got ${ - getClientAccessToken() || "undefined" - } from ${url}` - ); - } - }); - - it("should successfully get partner attribution id", () => { - const partnerAttributionID = "abc-xyz-123"; - - const url = insertMockSDKScript({ - attributes: { - "data-partner-attribution-id": partnerAttributionID, - }, - }); - - if (partnerAttributionID !== getPartnerAttributionID()) { - throw new Error( - `Expected client token to be ${partnerAttributionID}, got ${ - getPartnerAttributionID() || "undefined" - } from ${url}` - ); - } - }); - - it("should successfully get sdk integration source", () => { - const SDKIntegrationSource = "spbf"; - - const url = insertMockSDKScript({ - attributes: { - "data-sdk-integration-source": SDKIntegrationSource, - }, - }); - - if (SDKIntegrationSource !== getSDKIntegrationSource()) { - throw new Error( - `Expected client token to be ${SDKIntegrationSource}, got ${ - getSDKIntegrationSource() || "undefined" - } from ${url}` - ); - } - }); - - it("should successfully get popup disabled attribute as true when set to true", () => { - const url = insertMockSDKScript({ - attributes: { - "data-popups-disabled": "true", - }, - }); - const value = getMerchantRequestedPopupsDisabled(); - if (value !== true) { - throw new Error( - `Expected merchantRequestedPopupDisabled attribute to be true, got ${String( - value - )} from ${url}` - ); - } - }); - - it("should successfully get popup disabled attribute as false when set to false", () => { - const url = insertMockSDKScript({ - attributes: { - "data-popups-disabled": "false", - }, - }); - const value = getMerchantRequestedPopupsDisabled(); - if (value !== false) { - throw new Error( - `Expected merchantRequestedPopupDisabled attribute to be false, got ${String( - value - )} from ${url}` - ); - } - }); - - it("should successfully get popup disabled attribute as false when not set", () => { - const url = insertMockSDKScript({ - attributes: {}, - }); - const value = getMerchantRequestedPopupsDisabled(); - if (value !== false) { - throw new Error( - `Expected merchantRequestedPopupDisabled attribute to be false, got ${String( - value - )} from ${url}` - ); - } - }); - - it("should successfully get the page type", () => { - const pageType = "home"; - const url = insertMockSDKScript({ - attributes: { - "data-page-type": pageType, - }, - }); - - if (pageType !== getPageType()) { - throw new Error( - `Expected page type to be ${pageType}, got ${ - getPageType() || "undefined" - } from ${url}` - ); - } - }); - - it("should successfully get the page type if not same case", () => { - try { - const pageType = "Home"; - insertMockSDKScript({ - attributes: { - "data-page-type": pageType, - }, - }); - } catch (error) { - throw new Error( - `Passing in different case but correct value should pass.` - ); - } - }); - - it("should throw error if invalid page type", () => { - try { - const pageType = "abc"; - insertMockSDKScript({ - attributes: { - "data-page-type": pageType, - }, - }); - throw new Error(`Invalid page type should have thrown an Error.`); - } catch (error) { - // pass - } - }); - - it("should set empty page type if not set", () => { - const url = insertMockSDKScript({}); - - if (getPageType() !== "") { - throw new Error( - `Expected page type to be empty, got ${ - getPageType() || "undefined" - } from ${url}` - ); - } - }); - - it("should successfully get locale from script", () => { - const expectedLocale = "es_ES"; - - const url = insertMockSDKScript({ - query: { - locale: expectedLocale, - }, - }); - - const localeObject = getLocale(); - const receivedLocal = `${localeObject.lang}_${localeObject.country}`; - if (expectedLocale !== receivedLocal) { - throw new Error( - `Expected locale to be ${expectedLocale}, got ${receivedLocal} from ${url}` - ); - } - }); - - it("should successfully get locale from browser settings", () => { - const expectedLocale = "fr_FR"; - window.navigator.languages = [expectedLocale]; // eslint-disable-line compat/compat - - const localeObject = getLocale(); - const receivedLocale = `${localeObject.lang}_${localeObject.country}`; - - if (expectedLocale !== receivedLocale) { - throw new Error( - `Expected locale to be ${expectedLocale}, got ${receivedLocale}` - ); - } - }); - - it("should infer locale country from language", () => { - const expectedLocale = "ja_JP"; - window.navigator.languages = ["ja"]; // eslint-disable-line compat/compat - - const localeObject = getLocale(); - const receivedLocale = `${localeObject.lang}_${localeObject.country}`; - - if (expectedLocale !== receivedLocale) { - throw new Error( - `Expected locale to be ${expectedLocale}, got ${receivedLocale}` - ); - } - }); - - it("should return default if unable to infer locale country", () => { - const expectedLocale = "en_US"; - window.navigator.languages = ["es"]; // eslint-disable-line compat/compat - - const localeObject = getLocale(); - const receivedLocale = `${localeObject.lang}_${localeObject.country}`; - - if (expectedLocale !== receivedLocale) { - throw new Error( - `Expected locale to be ${expectedLocale}, got ${receivedLocale}` - ); - } - }); - - it("should return default locale if none detected", () => { - const expectedLocale = "en_US"; - - const localeObject = getLocale(); - const receivedLocale = `${localeObject.lang}_${localeObject.country}`; - - if (expectedLocale !== receivedLocale) { - throw new Error( - `Expected locale to be ${expectedLocale}, got ${receivedLocale}` - ); - } - }); - - it("should return default locale from country when only country was detected", () => { - const previousLanguages = window.navigator.languages; // eslint-disable-line compat/compat - const previousLanguage = window.navigator.language; - - window.navigator.languages = ["zz_US"]; // eslint-disable-line compat/compat - window.navigator.language = undefined; - const expectedLocale = "en_US"; - - const localeObject = getLocale(); - const receivedLocale = `${localeObject.lang}_${localeObject.country}`; - - if (expectedLocale !== receivedLocale) { - throw new Error( - `Expected locale to be ${expectedLocale}, got ${receivedLocale}` - ); - } - window.navigator.languages = previousLanguages; // eslint-disable-line compat/compat - window.navigator.language = previousLanguage; - }); - - it("should return computed lang when locale is zh_HK", () => { - const expectedLang = "zh_Hant"; - - const url = insertMockSDKScript({ - query: { - locale: "zh_HK", - }, - }); - - const { lang: receivedLang } = getLocale(); - if (expectedLang !== receivedLang) { - throw new Error( - `Expected lag to be ${expectedLang}, got ${receivedLang} from ${url}` - ); - } - }); - - it("should return the right computed lang when locale is en_Hk", () => { - const expectedLang = "en"; - - const url = insertMockSDKScript({ - query: { - locale: `${expectedLang}_HK`, - }, - }); - - const { lang: receivedLang } = getLocale(); - if (expectedLang !== receivedLang) { - throw new Error( - `Expected lag to be ${expectedLang}, got ${receivedLang} from ${url}` - ); - } - }); -}); diff --git a/test/client/scriptUtils.js b/test/client/scriptUtils.js deleted file mode 100644 index e9d1dfa9..00000000 --- a/test/client/scriptUtils.js +++ /dev/null @@ -1,233 +0,0 @@ -/* @flow */ -import { - getScriptUrl, - getEnableFunding, - getDisableFunding, - getDisableCard, - getBuyerCountry, - getAmount, - getUserIDToken, - getCSPNonce, - getEnableThreeDomainSecure, - getUserExperienceFlow, - getSDKToken, - isChildWindow, -} from "../../src/script"; -import { insertMockSDKScript } from "../../src"; - -describe(`script utils cases`, () => { - it("getScriptUrl should return the src of the script element", () => { - const result = getScriptUrl(); - - if (result !== "https://test.paypal.com/sdk/js?client-id=abcxyz123") { - throw new Error( - `should found the script src "https://test.paypal.com/sdk/js?client-id=abcxyz123", but got: ${result}` - ); - } - }); - - it("getEnableFunding should return an empty array when enable-funding is not configure", () => { - const result = getEnableFunding(); - - if (result.length > 0) { - throw new Error( - `should return and empty array, but got: ${result.toString()}` - ); - } - }); - - it("getEnableFunding should return a valid array when enable-funding is configure", () => { - insertMockSDKScript({ - query: { - "enable-funding": "paypal", - }, - }); - const result = getEnableFunding(); - - if (result[0] !== "paypal") { - throw new Error( - `should return a valid array ["paypal"], but got: ${result.toString()}` - ); - } - }); - - it("getDisableFunding should return an empty array when disable-funding is not configure", () => { - const result = getDisableFunding(); - - if (result.length > 0) { - throw new Error( - `should return and empty array, but got: ${result.toString()}` - ); - } - }); - - it("getDisableFunding should return a valid array when disable-funding is configure", () => { - insertMockSDKScript({ - query: { - "disable-funding": "paypal", - }, - }); - const result = getDisableFunding(); - - if (result[0] !== "paypal") { - throw new Error( - `should return a valid array ["paypal"], but got: ${result.toString()}` - ); - } - }); - - it("getDisableCard should return an empty array when disable-card is not configure", () => { - const result = getDisableCard(); - - if (result.length > 0) { - throw new Error( - `should return and empty array, but got: ${result.toString()}` - ); - } - }); - - it("getDisableCard should return a valid array when disable-card is configure", () => { - insertMockSDKScript({ - query: { - "disable-card": "paypal", - }, - }); - const result = getDisableCard(); - - if (result[0] !== "paypal") { - throw new Error( - `should return a valid array ["paypal"], but got: ${result.toString()}` - ); - } - }); - - it("getBuyerCountry should return the buyer country", () => { - insertMockSDKScript({ - query: { - "buyer-country": "US", - }, - }); - const result = getBuyerCountry(); - - if (result !== "US") { - throw new Error( - `should return US as the buyer country, but got: ${String(result)}` - ); - } - }); - - it("getAmount should return and error when the amount format is not correct", () => { - insertMockSDKScript({ - attributes: { - "data-amount": "10", - }, - }); - try { - getAmount(); - } catch (err) { - if (err.message !== "Invalid amount: 10") { - throw new Error( - `should throw an Error with incorrect amount format message` - ); - } - } - }); - - it("getAmount should return the amount", () => { - insertMockSDKScript({ - attributes: { - "data-amount": "10.00", - }, - }); - const result = getAmount(); - - if (result !== "10.00") { - throw new Error( - `should return an amount equals to "10.00", but got: ${String(result)}` - ); - } - }); - - it("getUserIDToken return a token string", () => { - insertMockSDKScript({ - attributes: { - "data-user-id-token": "token", - }, - }); - const result = getUserIDToken(); - - if (result !== "token") { - throw new Error( - `should return the "token" word, but got: ${String(result)}` - ); - } - }); - - it("getSDKToken should return a sdk-token string", () => { - insertMockSDKScript({ - attributes: { - "data-sdk-client-token": "sdk-token", - }, - }); - const result = getSDKToken(); - - if (result !== "sdk-token") { - throw new Error( - `should return the "sdk-token" word, but got: ${String(result)}` - ); - } - }); - - it("getCSPNonce should return a data-csp-nonce string", () => { - insertMockSDKScript({ - attributes: { - "data-csp-nonce": "csp-none", - }, - }); - const result = getCSPNonce(); - - if (result !== "csp-none") { - throw new Error( - `should return the "csp-none" word, but got: ${String(result)}` - ); - } - }); - - it('getEnableThreeDomainSecure should return "true"', () => { - insertMockSDKScript({ - attributes: { - "data-enable-3ds": "true", - }, - }); - const result = getEnableThreeDomainSecure(); - - if (!result) { - throw new Error( - `should has enable the three domain secure, but got: ${String(result)}` - ); - } - }); - - it("getUserExperienceFlow should return a valid string", () => { - insertMockSDKScript({ - attributes: { - "data-user-experience-flow": "flow", - }, - }); - const result = getUserExperienceFlow(); - - if (result !== "flow") { - throw new Error(`should the "flow" word, but got: ${String(result)}`); - } - }); - - it('isChildWindow should return "false" when is not a child zoid window', () => { - const result = isChildWindow(); - - if (result) { - throw new Error( - `shouldn't be a child zoid window, but got: ${String(result)}` - ); - } - }); -}); diff --git a/test/client/session.js b/test/client/session.js deleted file mode 100644 index df7117e0..00000000 --- a/test/client/session.js +++ /dev/null @@ -1,49 +0,0 @@ -/* @flow */ - -import { - getStorageState, - getStorageID, - getSessionState, - getClientMetadataID, -} from "../../src/session"; -import { insertMockSDKScript } from "../../src/test"; - -describe("session cases", () => { - it("getStorageState", () => { - const result = getStorageState((storage) => storage); - - if (!result.hasOwnProperty("id")) { - throw new Error(`should get storage state object, but got ${result}`); - } - }); - - it("getStorageID", () => { - const result = getStorageID(); - - if (typeof result !== "string") { - throw new TypeError(`should get the storage id, but got ${result}`); - } - }); - - it("getSessionState", () => { - const result = getSessionState((state) => state); - - if (Object.entries(result).length > 0) { - throw new Error(`should get an empty state object, but got ${result}`); - } - }); - - it("getClientMetadataID", () => { - const mockMerchantIds = "ids"; - insertMockSDKScript({ - attributes: { - "data-client-metadata-id": mockMerchantIds, - }, - }); - const result = getClientMetadataID(); - - if (result !== mockMerchantIds) { - throw new Error(`should get the storage id, but got ${String(result)}`); - } - }); -}); diff --git a/test/client/tracking.js b/test/client/tracking.js deleted file mode 100644 index 76a517aa..00000000 --- a/test/client/tracking.js +++ /dev/null @@ -1,41 +0,0 @@ -/* @flow */ - -import { getSDKInitTime, setupLogger } from "../../src/tracking"; - -describe(`tracking cases`, () => { - it("should throw an Error", async () => { - const errorMessage = "SDK not initialized"; - /* eslint-disable import/no-unresolved */ - const { getSDKInitTime: getSDKInitTimeA } = await import( - // $FlowFixMe - "../../src/tracking?q=1" - ); - /* eslint-enable */ - - try { - getSDKInitTimeA(); - throw new Error(`No exception launched`); - } catch (err) { - if (err.message !== errorMessage) { - throw new Error( - `should get a valid Error message, instead got: ${err.message}` - ); - } - } - }); - - it("should find a saved Date instance", () => { - try { - Date.parse(getSDKInitTime().toString()); - } catch (err) { - throw new Error( - `should get a valid Date instance, instead got an error: ${err.message}` - ); - } - }); - - it("setupLogger should setup logger with IEIntranet enabled", () => { - window.document.documentMode = true; - setupLogger(); - }); -}); diff --git a/test/globals.js b/test/globals.js index e81fc729..ff758af6 100644 --- a/test/globals.js +++ b/test/globals.js @@ -1,13 +1,6 @@ -/* @flow */ -import type { GetExperimentation } from "../src/types"; +/* eslint flowtype/require-valid-file-annotation: off, flowtype/require-return-type: off */ -type TestGlobals = {| - [string]: string | number | boolean | (() => string | (() => number)), - __COMPONENTS__: $ReadOnlyArray, - __EXPERIMENTATION__: GetExperimentation, -|}; - -export const sdkClientTestGlobals: TestGlobals = { +export const sdkClientTestGlobals = { __PORT__: 8000, __STAGE_HOST__: "sandbox.paypal.com", __HOST__: "test.paypal.com", @@ -15,6 +8,7 @@ export const sdkClientTestGlobals: TestGlobals = { __SDK_HOST__: "test.paypal.com", __PATH__: "/sdk/js", + __ENV__: "test", __VERSION__: "1.0.45", __CORRELATION_ID__: "abc123", __NAMESPACE__: "paypaltest", diff --git a/test/helpers.js b/test/helpers.js new file mode 100644 index 00000000..97599805 --- /dev/null +++ b/test/helpers.js @@ -0,0 +1,10 @@ +/* @flow */ + +export function makeMockScriptElement(scrtipSrc: string): HTMLScriptElement { + const mockElement = document.createElement("script"); + mockElement.setAttribute("src", scrtipSrc); + // eslint-disable-next-line compat/compat + document.body?.appendChild(mockElement); + // $FlowIgnore + return mockElement; +} diff --git a/test/index.js b/test/index.js deleted file mode 100644 index 4ef7cdc4..00000000 --- a/test/index.js +++ /dev/null @@ -1,3 +0,0 @@ -/* @flow */ - -import "./client"; diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 00000000..d0d171dd --- /dev/null +++ b/vite.config.js @@ -0,0 +1,58 @@ +/* eslint-disable eslint-comments/disable-enable-pair */ +/* eslint-disable spaced-comment */ +/* eslint-disable import/no-default-export */ +/// + +/* @flow */ +import { flowPlugin, esbuildFlowPlugin } from "@bunchtogether/vite-plugin-flow"; +import { defineConfig } from "vite"; + +const define = { + __DEBUG__: false, + __TEST__: true, + __WEB__: true, + + __SDK_HOST__: true, + __PATH__: true, + + __PAYPAL_DOMAIN__: true, + __PAYPAL_API_DOMAIN__: true, + + __POST_ROBOT__: JSON.stringify({ + __GLOBAL_KEY__: `__post_robot__`, + __AUTO_SETUP__: false, + __IE_POPUP_SUPPORT__: false, + __GLOBAL_MESSAGE_SUPPORT__: true, + __SCRIPT_NAMESPACE__: false, + }), + __PAYPAL_CHECKOUT__: JSON.stringify({ + _MAJOR_VERSION__: "", + __MINOR_VERSION__: "", + }), +}; + +// $FlowIssue +export default defineConfig({ + define, + esbuild: { + define, + }, + test: { + setupFiles: ["vitestSetup.js"], + environment: "jsdom", + clearMocks: true, + include: ["**src/**/*.test.js", "**/server/**/*.test.js"], + coverage: { + provider: "v8", + reportsDirectory: "./coverage", + include: ["src/**/*.js"], + }, + }, + optimizeDeps: { + esbuildOptions: { + plugins: [esbuildFlowPlugin()], + }, + }, + // $FlowIssue + plugins: [flowPlugin({ exclude: "" })], +}); diff --git a/vitestSetup.js b/vitestSetup.js new file mode 100644 index 00000000..a183ea4b --- /dev/null +++ b/vitestSetup.js @@ -0,0 +1,10 @@ +/* @flow */ +import { sdkClientTestGlobals } from "./test/globals"; + +const applyEnvs = () => { + Object.keys(sdkClientTestGlobals).forEach((k) => { + window[k] = sdkClientTestGlobals[k]; + }); +}; + +applyEnvs(); diff --git a/webpack.config.js b/webpack.config.js index e2276b58..39777ec4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,8 +1,8 @@ /* @flow */ /* eslint import/no-nodejs-modules: off */ - -import type { WebpackConfig } from "@krakenjs/webpack-config-grumbler/index.flow"; -import { getWebpackConfig } from "@krakenjs/webpack-config-grumbler"; +// $FlowIgnore +import type { WebpackConfig } from "@krakenjs/grumbler-scripts/config/types"; +import { getWebpackConfig } from "@krakenjs/grumbler-scripts/config/webpack.config"; import { sdkClientTestGlobals } from "./test/globals";