diff --git a/.gitignore b/.gitignore index 814b4267..8f52f7de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules +coverage lib/dist/ demos/react/dist demos/npm/dist diff --git a/jest.config.js b/jest.config.js index 77806b70..693057ee 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,13 @@ /** @type {import('jest').Config} */ const config = { testEnvironment: "jsdom", - setupFiles: ["/setup-jest.js"], + resetMocks: false, + setupFiles: ["/setup-jest.js", "jest-localstorage-mock"], + setupFilesAfterEnv: ["/setup-env.js"], + testEnvironmentOptions: { + customExportConditions: [""], + }, + collectCoverage: true, }; module.exports = config; diff --git a/lib/addons/gpt.test.js b/lib/addons/gpt.test.js index d02b363b..4ea499c3 100644 --- a/lib/addons/gpt.test.js +++ b/lib/addons/gpt.test.js @@ -1,4 +1,5 @@ import OptableSDK from "../sdk"; +import { TEST_HOST, TEST_SITE } from "../test/mocks.ts"; import "./gpt.ts"; describe("OptableSDK - installGPTSecureSignals", () => { @@ -6,7 +7,7 @@ describe("OptableSDK - installGPTSecureSignals", () => { beforeEach(() => { // Initialize the SDK instance - SDK = new OptableSDK({ host: "localhost", site: "test" }); + SDK = new OptableSDK({ host: TEST_HOST, site: TEST_SITE }); // Reset global googletag object window.googletag = { cmd: [], secureSignalProviders: [] }; diff --git a/lib/addons/topics-api.test.js b/lib/addons/topics-api.test.js index c30d4910..249b7bb3 100644 --- a/lib/addons/topics-api.test.js +++ b/lib/addons/topics-api.test.js @@ -1,11 +1,12 @@ import OptableSDK from "../sdk"; +import { TEST_HOST, TEST_SITE } from "../test/mocks.ts"; import "./topics-api.ts"; describe("OptableSDK - ingestTopics", () => { let SDK; beforeEach(() => { - SDK = new OptableSDK({ host: "localhost", site: "test" }); + SDK = new OptableSDK({ host: TEST_HOST, site: TEST_SITE }); // Mock the profile method SDK.profile = jest.fn(); }); diff --git a/lib/addons/try-identify.test.js b/lib/addons/try-identify.test.js index 8237fee3..c09df52c 100644 --- a/lib/addons/try-identify.test.js +++ b/lib/addons/try-identify.test.js @@ -1,12 +1,13 @@ import OptableSDK from "../sdk"; import "./try-identify"; +import { TEST_HOST, TEST_SITE } from "../test/mocks.ts"; describe("tryIdentifyFromParams", () => { var SDK = null; beforeEach(() => { delete window.location; - SDK = new OptableSDK({ host: "localhost", site: "test" }); + SDK = new OptableSDK({ host: TEST_HOST, site: TEST_SITE }); SDK.identify = jest.fn(); }); diff --git a/lib/core/storage.test.js b/lib/core/storage.test.js index c6d5702a..694af7e8 100644 --- a/lib/core/storage.test.js +++ b/lib/core/storage.test.js @@ -29,6 +29,12 @@ describe("LocalStorage", () => { expect(store.getTargeting()).toBeNull(); }); + test("allows to set targeting with empty value", () => { + const store = new LocalStorage(randomConfig()); + expect(store.setTargeting()); + expect(store.getTargeting()).toBeNull(); + }); + test("allows to store and retrieve a site config", () => { const store = new LocalStorage(randomConfig()); expect(store.getSite()).toBeNull(); @@ -39,6 +45,12 @@ describe("LocalStorage", () => { expect(store.getSite()).toBeNull(); }); + test("allows to set site with empty value", () => { + const store = new LocalStorage(randomConfig()); + expect(store.setSite()); + expect(store.getSite()).toBeNull(); + }); + test("auto fixes v1 targeting as v2 base on audience key presence", () => { const store = new LocalStorage(randomConfig()); window.localStorage.setItem(store.targetingV1Key, JSON.stringify({ k1: ["v1"], k2: ["v2"] })); diff --git a/lib/edge/passport.ts b/lib/edge/passport.ts new file mode 100644 index 00000000..2b7a2a21 --- /dev/null +++ b/lib/edge/passport.ts @@ -0,0 +1,5 @@ +type EdgePassport = { + passport: string; +}; + +export type { EdgePassport }; diff --git a/lib/edge/site.ts b/lib/edge/site.ts index e67c3959..465bbe88 100644 --- a/lib/edge/site.ts +++ b/lib/edge/site.ts @@ -19,6 +19,7 @@ type AuctionConfig = { type SiteResponse = { interestGroupPixel: string; auctionConfig?: AuctionConfig | null; + auctionConfigURL?: string; getTopicsURL: string; }; @@ -40,5 +41,6 @@ function SiteFromCache(config: ResolvedConfig): SiteResponse | null { return ls.getSite(); } -export { Site, SiteResponse, SiteFromCache, Size, AuctionConfig }; +export { Site, SiteFromCache }; export default Site; +export type { SiteResponse, Size, AuctionConfig }; diff --git a/lib/edge/targeting.ts b/lib/edge/targeting.ts index 19498976..b1a63a1c 100644 --- a/lib/edge/targeting.ts +++ b/lib/edge/targeting.ts @@ -103,5 +103,6 @@ function TargetingKeyValues(tdata: TargetingResponse | null): TargetingKeyValues return result; } -export { Targeting, TargetingFromCache, TargetingClearCache, TargetingResponse, PrebidORTB2, TargetingKeyValues }; +export { Targeting, TargetingFromCache, TargetingClearCache, PrebidORTB2, TargetingKeyValues }; export default Targeting; +export type { TargetingResponse }; diff --git a/lib/edge/tokenize.ts b/lib/edge/tokenize.ts index 608fa6f9..88737529 100644 --- a/lib/edge/tokenize.ts +++ b/lib/edge/tokenize.ts @@ -23,5 +23,6 @@ function Tokenize(config: ResolvedConfig, id: string): Promise }); } -export { Tokenize, TokenizeRequest, TokenizeResponse }; +export { Tokenize }; export default Tokenize; +export type { TokenizeRequest, TokenizeResponse }; diff --git a/lib/edge/uid2_token.ts b/lib/edge/uid2_token.ts index 788c4841..cb3d08ae 100644 --- a/lib/edge/uid2_token.ts +++ b/lib/edge/uid2_token.ts @@ -22,3 +22,4 @@ function Uid2Token(config: ResolvedConfig, id: string): Promise { - const expected = "e:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3"; - - expect(OptableSDK.eid("123")).toEqual(expected); - expect(OptableSDK.eid("123 ")).toEqual(expected); - expect(OptableSDK.eid(" 123")).toEqual(expected); - expect(OptableSDK.eid(" 123 ")).toEqual(expected); -}); - -test("eid ignores case", () => { - const var1 = "tEsT@FooBarBaz.CoM"; - const var2 = "test@foobarbaz.com"; - const var3 = "TEST@FOOBARBAZ.COM"; - const var4 = "TeSt@fOObARbAZ.cOm"; - const eid = OptableSDK.eid(var1); - - expect(eid).toEqual(OptableSDK.eid(var2)); - expect(eid).toEqual(OptableSDK.eid(var3)); - expect(eid).toEqual(OptableSDK.eid(var4)); -}); - -test("cid rejects non-string id", () => { - expect(() => OptableSDK.cid(1)).toThrow(); -}); - -test("cid prefixes with c: by default", () => { - expect(OptableSDK.cid("abc")).toEqual("c:abc"); -}); - -test("cid accepts a custom variant", () => { - expect(OptableSDK.cid("abc", 0)).toEqual("c:abc"); - for (let i = 1; i < 10; i++) { - expect(OptableSDK.cid("abc", i)).toEqual(`c${i}:abc`); - } - - expect(() => OptableSDK.cid("abc", -1)).toThrow(); - expect(() => OptableSDK.cid("abc", 10)).toThrow(); - expect(() => OptableSDK.cid("abc", "1")).toThrow(); -}); - -test("cid trim spaces", () => { - expect(OptableSDK.cid(" \n abc\t")).toEqual("c:abc"); -}); - -test("cid preserve case", () => { - expect(OptableSDK.cid("ABCD")).toEqual("c:ABCD"); -}); diff --git a/lib/sdk.test.ts b/lib/sdk.test.ts new file mode 100644 index 00000000..2b32054a --- /dev/null +++ b/lib/sdk.test.ts @@ -0,0 +1,470 @@ +import { SiteResponse } from "edge/site"; +import OptableSDK from "./sdk"; +import { TEST_BASE_URL, TEST_HOST, TEST_SITE } from "./test/mocks"; + +describe("eid", () => { + test("is correct", () => { + const expected = "e:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3"; + + expect(OptableSDK.eid("123")).toEqual(expected); + expect(OptableSDK.eid("123 ")).toEqual(expected); + expect(OptableSDK.eid(" 123")).toEqual(expected); + expect(OptableSDK.eid(" 123 ")).toEqual(expected); + }); + + test("ignores case", () => { + const var1 = "tEsT@FooBarBaz.CoM"; + const var2 = "test@foobarbaz.com"; + const var3 = "TEST@FOOBARBAZ.COM"; + const var4 = "TeSt@fOObARbAZ.cOm"; + const eid = OptableSDK.eid(var1); + + expect(eid).toEqual(OptableSDK.eid(var2)); + expect(eid).toEqual(OptableSDK.eid(var3)); + expect(eid).toEqual(OptableSDK.eid(var4)); + }); +}); + +describe("cid", () => { + test("rejects non-string id", () => { + expect(() => OptableSDK.cid(1 as unknown as string)).toThrow(); + }); + + test("prefixes with c: by default", () => { + expect(OptableSDK.cid("abc")).toEqual("c:abc"); + }); + + test("accepts a custom variant", () => { + expect(OptableSDK.cid("abc", 0)).toEqual("c:abc"); + for (let i = 1; i < 10; i++) { + expect(OptableSDK.cid("abc", i)).toEqual(`c${i}:abc`); + } + + expect(() => OptableSDK.cid("abc", -1)).toThrow(); + expect(() => OptableSDK.cid("abc", 10)).toThrow(); + expect(() => OptableSDK.cid("abc", "1" as unknown as number)).toThrow(); + }); + + test("trim spaces", () => { + expect(OptableSDK.cid(" \n abc\t")).toEqual("c:abc"); + }); + + test("preserve case", () => { + expect(OptableSDK.cid("ABCD")).toEqual("c:ABCD"); + }); +}); + +const defaultConfig = { + host: TEST_HOST, + site: TEST_SITE, +}; + +const defaultConsent = { + deviceAccess: true, + reg: null, +}; + +describe("Breaking change detection: if typescript complains or a test fails it's likely a breaking change has occurred.", () => { + beforeEach(() => localStorage.clear()); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: constructor with cookies and initPassport set", async () => { + new OptableSDK({ + ...defaultConfig, + cookies: false, + initPassport: false, + }); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: constructor not set", async () => { + new OptableSDK({ ...defaultConfig }); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: identify", async () => { + await new OptableSDK({ ...defaultConfig }).identify("c:a1a335b8216658319f96a4b0c718557ba41dd1f5"); + await new OptableSDK({ ...defaultConfig }).identify( + "c:a1a335b8216658319f96a4b0c718557ba41dd1f5", + "other-identifier" + ); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: witness", async () => { + await new OptableSDK({ ...defaultConfig }).witness("event"); + await new OptableSDK({ ...defaultConfig }).witness("event", { property: "value" }); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: profile", async () => { + await new OptableSDK({ ...defaultConfig }).profile({ + propString: "", + propBool: true, + propNumber: 3, + }); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: targeting", async () => { + const result = await new OptableSDK({ ...defaultConfig }).targeting("c:a1a335b8216658319f96a4b0c718557ba41dd1f5"); + ["audience", "user"].forEach((key) => expect(Object.keys(result)).toContain(key)); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: uid2Token", async () => { + await new OptableSDK({ ...defaultConfig }).uid2Token("c:a1a335b8216658319f96a4b0c718557ba41dd1f5"); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: targetingFromCache", async () => { + const result = new OptableSDK({ ...defaultConfig }).targetingFromCache(); + expect(result).toBeNull(); + + await new OptableSDK({ ...defaultConfig }).targeting("c:a1a335b8216658319f96a4b0c718557ba41dd1f5"); + const result2 = new OptableSDK({ ...defaultConfig }).targetingFromCache(); + expect(typeof result2).toBe("object"); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: site", async () => { + const result = await new OptableSDK({ ...defaultConfig }).site(); + ["interestGroupPixel", "auctionConfigURL", "auctionConfig", "getTopicsURL"].forEach((key) => + expect(Object.keys(result)).toContain(key) + ); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: siteFromCache", async () => { + const result = new OptableSDK({ ...defaultConfig }).siteFromCache(); + expect(result).toBeNull(); + + await new OptableSDK({ ...defaultConfig }).site(); + const result2 = new OptableSDK({ ...defaultConfig }).siteFromCache() as SiteResponse; + ["interestGroupPixel", "auctionConfigURL", "auctionConfig", "getTopicsURL"].forEach((key) => + expect(Object.keys(result2)).toContain(key) + ); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: targetingClearCache", () => { + new OptableSDK({ ...defaultConfig }).targetingClearCache(); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: prebidORTB2", async () => { + const result = await new OptableSDK({ ...defaultConfig }).prebidORTB2(); + expect(Object.keys(result)).toContain("user"); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: prebidORTB2FromCache", async () => { + const result = new OptableSDK({ ...defaultConfig }).prebidORTB2FromCache(); + expect(Object.keys(result)).toContain("user"); + + await new OptableSDK({ ...defaultConfig }).prebidORTB2(); + const result2 = new OptableSDK({ ...defaultConfig }).prebidORTB2FromCache(); + expect(Object.keys(result2)).toContain("user"); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: targetingKeyValues", async () => { + const result = await new OptableSDK({ ...defaultConfig }).targetingKeyValues(); + expect(typeof result).toBe("object"); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: targetingKeyValuesFromCache", () => { + const result = new OptableSDK({ ...defaultConfig }).targetingKeyValuesFromCache(); + expect(typeof result).toBe("object"); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: tokenize", async () => { + const result = await new OptableSDK({ ...defaultConfig }).tokenize("myid"); + ["User"].forEach((key) => expect(Object.keys(result)).toContain(key)); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: eid", async () => { + const myId = OptableSDK.eid("myid"); + expect(typeof myId).toBe("string"); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: sha256", async () => { + const shaValue = OptableSDK.sha256("someValue"); + expect(typeof shaValue).toBe("string"); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: cid", async () => { + const cidValue = OptableSDK.cid("someValue"); + expect(typeof cidValue).toBe("string"); + + const cidValueWithVariant = OptableSDK.cid("someValue", 4); + expect(typeof cidValueWithVariant).toBe("string"); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: TargetingKeyValues", async () => { + const result = OptableSDK.TargetingKeyValues({ audience: [], user: [] }); + expect(typeof result).toBe("object"); + OptableSDK.TargetingKeyValues({ audience: [] }); + OptableSDK.TargetingKeyValues({ user: [] }); + OptableSDK.TargetingKeyValues({}); + }); + + test("TEST SHOULD NEVER NEED TO BE UPDATED, UNLESS MAJOR VERSION UPDATE: PrebidORTB2", async () => { + const result = OptableSDK.PrebidORTB2({ audience: [], user: [] }); + expect(typeof result).toBe("object"); + expect(Object.keys(result)).toContain("user"); + }); +}); + +describe("behavior testing of", () => { + beforeEach(() => { + localStorage.clear(); + jest.clearAllMocks(); + }); + + test("constructor with cookies and initPassport set to false initializes without localStorage", async () => { + const fetchSpy = jest.spyOn(window, "fetch"); + const sdk = new OptableSDK({ + ...defaultConfig, + cookies: false, + initPassport: false, + }); + expect(sdk).toBeInstanceOf(OptableSDK); + expect(sdk.dcn).toEqual({ + consent: { + ...defaultConsent, + }, + host: "hostmock.com", + site: "site", + cookies: false, + initPassport: false, + }); + await sdk["init"]; + expect(localStorage.setItem).toBeCalledTimes(0); + + // Testing of cookies param was applied as expected + await sdk.identify("c:a1a335b8216658319f96a4b0c718557ba41dd1f5"); + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "POST", + _bodyText: '["c:a1a335b8216658319f96a4b0c718557ba41dd1f5"]', + url: `${TEST_BASE_URL}/identify?osdk=web-0.0.0-experimental&cookies=no&passport=`, + }) + ); + + // Testing of passport is sent when cookies is false (retrieved from the first call) + await sdk.identify("c:a1a335b8216658319f96a4b0c718557ba41dd1f6"); + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "POST", + _bodyText: '["c:a1a335b8216658319f96a4b0c718557ba41dd1f6"]', + url: `${TEST_BASE_URL}/identify?osdk=web-0.0.0-experimental&cookies=no&passport=PASSPORT`, + }) + ); + }); + + test("constructor with cookies and initPassport properties not provided initializes with localStorage and cookies", async () => { + const fetchSpy = jest.spyOn(window, "fetch"); + const sdk = new OptableSDK({ ...defaultConfig }); + expect(sdk).toBeInstanceOf(OptableSDK); + expect(sdk.dcn).toEqual({ + consent: { + ...defaultConsent, + }, + host: "hostmock.com", + site: "site", + cookies: true, + initPassport: true, + }); + await sdk["init"]; + expect(window.localStorage.setItem).toHaveBeenLastCalledWith( + expect.stringContaining("OPTABLE_SITE"), + expect.objectContaining({}) + ); + + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "GET", + bodyUsed: false, + url: expect.stringContaining("config?osdk=web-0.0.0-experimental&cookies=yes"), + }) + ); + + // Testing of cookies param was applied as expected + await sdk.identify("c:a1a335b8216658319f96a4b0c718557ba41dd1f5"); + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "POST", + _bodyText: '["c:a1a335b8216658319f96a4b0c718557ba41dd1f5"]', + url: `${TEST_BASE_URL}/identify?osdk=web-0.0.0-experimental&cookies=yes`, + }) + ); + }); + + test("identify", async () => { + const fetchSpy = jest.spyOn(window, "fetch"); + const sdk = new OptableSDK({ ...defaultConfig }); + + await sdk.identify("c:a1a335b8216658319f96a4b0c718557ba41dd1f5"); + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "POST", + _bodyText: '["c:a1a335b8216658319f96a4b0c718557ba41dd1f5"]', + url: expect.stringContaining("identify"), + }) + ); + + await sdk.identify("some-email@optable.co"); + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "POST", + _bodyText: '["some-email@optable.co"]', + url: expect.stringContaining("identify"), + }) + ); + + await sdk.identify( + "some-email2@optable.co", + undefined as unknown as string, + null as unknown as string, + "", + 0 as unknown as string, + "some-email3@optable.co" + ); + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "POST", + _bodyText: '["some-email2@optable.co","some-email3@optable.co"]', + url: expect.stringContaining("identify"), + }) + ); + }); + + test("profile", async () => { + const fetchSpy = jest.spyOn(window, "fetch"); + const sdk = new OptableSDK({ ...defaultConfig }); + + await sdk.profile({ someProp: "someValue", someBool: true, someNumber: 3 }); + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "POST", + _bodyText: '{"traits":{"someProp":"someValue","someBool":true,"someNumber":3}}', + url: expect.stringContaining("profile"), + }) + ); + }); + + test("witness", async () => { + const fetchSpy = jest.spyOn(window, "fetch"); + const sdk = new OptableSDK({ ...defaultConfig }); + + await sdk.witness("someEvent", { someProp: "someValue", someBool: true, someNumber: 3 }); + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "POST", + _bodyText: '{"event":"someEvent","properties":{"someProp":"someValue","someBool":true,"someNumber":3}}', + url: expect.stringContaining("witness"), + }) + ); + }); + + test("targeting", async () => { + const fetchSpy = jest.spyOn(window, "fetch"); + const sdk = new OptableSDK({ ...defaultConfig }); + + const initialResultFromCache = sdk.targetingFromCache(); + expect(initialResultFromCache).toBeNull(); + expect(fetchSpy).not.toHaveBeenCalledWith(expect.objectContaining({ url: expect.stringContaining("targeting") })); + + const targeting = await sdk.targeting(); + expect(targeting).toEqual({ audience: [], user: [] }); + + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "GET", + url: expect.stringContaining("v2/targeting?id=__passport__"), + }) + ); + + const targetingWithParam = await sdk.targeting("someId"); + expect(targetingWithParam).toBeDefined(); + + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "GET", + url: expect.stringContaining("v2/targeting?id=someId"), + }) + ); + + const latestResultFromCache = sdk.targetingFromCache(); + expect(latestResultFromCache).toEqual(targetingWithParam); + + sdk.targetingClearCache(); + expect(sdk.targetingFromCache()).toBeNull(); + }); + + test("site", async () => { + const fetchSpy = jest.spyOn(window, "fetch"); + const sdk = new OptableSDK({ ...defaultConfig, initPassport: false }); + + const initialResultFromCache = sdk.siteFromCache(); + expect(initialResultFromCache).toBeNull(); + expect(fetchSpy).not.toHaveBeenCalled(); + + const site = await sdk.site(); + expect(site).toEqual({ + auctionConfig: null, + auctionConfigURL: "", + getTopicsURL: "https://ads.optable.co/ca/topics/v1/get?origin=70cc15ee-484c-4d26-8868-c949a5c084b8", + interestGroupPixel: "", + }); + + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "GET", + url: expect.stringContaining("config"), + }) + ); + + const latestResultFromCache = sdk.siteFromCache(); + expect(latestResultFromCache).toEqual(site); + }); + + test("prebidORTB2", async () => { + const fetchSpy = jest.spyOn(window, "fetch"); + const sdk = new OptableSDK({ ...defaultConfig }); + + const initialResultFromCache = sdk.prebidORTB2FromCache(); + expect(initialResultFromCache).toEqual({ user: { data: [], ext: { eids: [] } } }); + expect(fetchSpy).not.toHaveBeenCalledWith(expect.objectContaining({ url: expect.stringContaining("targeting") })); + + const prebid = await sdk.prebidORTB2(); + expect(prebid).toEqual({ user: { data: [], ext: { eids: [] } } }); + + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "GET", + url: expect.stringContaining("targeting"), + }) + ); + }); + + test("targetingKeyValues", async () => { + const fetchSpy = jest.spyOn(window, "fetch"); + const sdk = new OptableSDK({ ...defaultConfig }); + + const initialResultFromCache = sdk.targetingKeyValuesFromCache(); + expect(initialResultFromCache).toEqual({}); + expect(fetchSpy).not.toHaveBeenCalledWith(expect.objectContaining({ url: expect.stringContaining("targeting") })); + + const prebid = await sdk.targetingKeyValues(); + expect(prebid).toEqual({}); + + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "GET", + url: expect.stringContaining("targeting"), + }) + ); + }); + + test("tokenize", async () => { + const fetchSpy = jest.spyOn(window, "fetch"); + const sdk = new OptableSDK({ ...defaultConfig }); + + await sdk.tokenize("someId"); + expect(fetchSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ + method: "POST", + _bodyText: '{"id":"someId"}', + url: expect.stringContaining("v1/tokenize"), + }) + ); + }); +}); diff --git a/lib/sdk.ts b/lib/sdk.ts index e5fc3e05..5493b265 100644 --- a/lib/sdk.ts +++ b/lib/sdk.ts @@ -4,7 +4,7 @@ import { getConfig } from "./config"; import type { WitnessProperties } from "./edge/witness"; import type { ProfileTraits } from "./edge/profile"; import { Identify } from "./edge/identify"; -import { Uid2Token } from "./edge/uid2_token"; +import { Uid2Token, Uid2TokenResponse } from "./edge/uid2_token"; import { Resolve, ResolveResponse } from "./edge/resolve"; import { Site, SiteResponse, SiteFromCache } from "./edge/site"; import { @@ -33,7 +33,7 @@ class OptableSDK { this.init = this.dcn.initPassport ? Site(this.dcn).then(noop).catch(noop) : Promise.resolve(); } - async identify(...ids: string[]) { + async identify(...ids: string[]): Promise { await this.init; return Identify( this.dcn, @@ -41,7 +41,7 @@ class OptableSDK { ); } - async uid2Token(id: string) { + async uid2Token(id: string): Promise { await this.init; return Uid2Token(this.dcn, id); } @@ -63,7 +63,7 @@ class OptableSDK { return SiteFromCache(this.dcn); } - targetingClearCache() { + targetingClearCache(): void { TargetingClearCache(this.dcn); } diff --git a/lib/test/handlers.ts b/lib/test/handlers.ts new file mode 100644 index 00000000..ffedd920 --- /dev/null +++ b/lib/test/handlers.ts @@ -0,0 +1,71 @@ +import { http, HttpResponse } from "msw"; +import { TEST_BASE_URL } from "./mocks"; +import { Uid2TokenResponse } from "edge/uid2_token"; +import { SiteResponse } from "edge/site"; +import { TokenizeResponse } from "edge/tokenize"; +import { TargetingResponse } from "edge/targeting"; +import { EdgePassport } from "edge/passport"; + +const ok200 = { + status: 200, +}; + +const passport: EdgePassport = { + passport: "PASSPORT", +}; + +const handlers = [ + http.get(`${TEST_BASE_URL}/config`, async ({}) => { + const data: SiteResponse = { + interestGroupPixel: "", + auctionConfigURL: "", + auctionConfig: null, + getTopicsURL: "https://ads.optable.co/ca/topics/v1/get?origin=70cc15ee-484c-4d26-8868-c949a5c084b8", + }; + return HttpResponse.json({ ...data, ...passport }, ok200); + }), + + http.post(`${TEST_BASE_URL}/identify`, async ({}) => { + return HttpResponse.json({ ...passport }, ok200); + }), + + http.post(`${TEST_BASE_URL}/uid2/token`, async ({}) => { + const data: Uid2TokenResponse = { + advertising_token: "gfsdgsdfgsdeagdfs", + RefreshToken: "dasdasdasdas", + IdentityExpires: 1734459312780, + RefreshFrom: 1734462312780, + RefreshExpires: 2734462312780, + RefreshResponseKey: "gdsfgfsd", + }; + return HttpResponse.json({ ...data, ...passport }, ok200); + }), + + http.post(`${TEST_BASE_URL}/witness`, async ({}) => { + return HttpResponse.json({ ...passport }, ok200); + }), + + http.post(`${TEST_BASE_URL}/profile`, async ({}) => { + return HttpResponse.json({ ...passport }, ok200); + }), + + http.post(`${TEST_BASE_URL}/v1/tokenize`, async ({}) => { + const data: TokenizeResponse = { + User: { + data: [], + ext: undefined, + }, + }; + return HttpResponse.json({ ...data, ...passport }, ok200); + }), + + http.get(`${TEST_BASE_URL}/v2/targeting`, async ({}) => { + const data: TargetingResponse = { + audience: [], + user: [], + }; + return HttpResponse.json({ ...data, ...passport }, ok200); + }), +]; + +export { handlers }; diff --git a/lib/test/mocks.ts b/lib/test/mocks.ts new file mode 100644 index 00000000..a994abf5 --- /dev/null +++ b/lib/test/mocks.ts @@ -0,0 +1,5 @@ +const TEST_HOST = "hostmock.com"; +const TEST_SITE = "site"; +const TEST_BASE_URL = `https://${TEST_HOST}/${TEST_SITE}`; + +export { TEST_HOST, TEST_SITE, TEST_BASE_URL }; diff --git a/lib/test/server.ts b/lib/test/server.ts new file mode 100644 index 00000000..52d1c441 --- /dev/null +++ b/lib/test/server.ts @@ -0,0 +1,5 @@ +import { setupServer } from "msw/node"; +import { handlers } from "./handlers"; + +const server = setupServer(...handlers); +export { server }; diff --git a/package-lock.json b/package-lock.json index 0655bb90..b392b4aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,8 @@ "core-js": "^3.7.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "jest-localstorage-mock": "^2.4.26", + "msw": "^2.6.9", "prettier": "^3.3.3", "shellcheck": "^3.0.0", "typescript": "^5.2.2", @@ -1253,6 +1255,37 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@bundled-es-modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cookie": "^0.7.2" + } + }, + "node_modules/@bundled-es-modules/statuses": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", + "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", + "dev": true, + "license": "ISC", + "dependencies": { + "statuses": "^2.0.1" + } + }, + "node_modules/@bundled-es-modules/tough-cookie": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", + "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/tough-cookie": "^4.0.5", + "tough-cookie": "^4.1.4" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", @@ -1262,6 +1295,131 @@ "node": ">=10.0.0" } }, + "node_modules/@inquirer/confirm": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.1.tgz", + "integrity": "sha512-vVLSbGci+IKQvDOtzpPTCOiEJCNidHcAq9JYVoWTW0svb5FiwSLotkM+JXNXejfjnzVYV9n0DTBythl9+XgTxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.2.tgz", + "integrity": "sha512-bHd96F3ezHg1mf/J0Rb4CV8ndCN0v28kUlrHqP7+ECm1C/A+paB7Xh2lbMk6x+kweQC+rZOxM/YeKikzxco8bQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.9", + "@inquirer/type": "^3.0.2", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@inquirer/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@inquirer/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.9.tgz", + "integrity": "sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.2.tgz", + "integrity": "sha512-ZhQ4TvhwHZF+lGhQ2O/rsjo80XoZR5/5qhOY3t6FJuX5XBg5Be8YzYTvaUGJnc12AUGI2nr4QSUE4PhKSigx7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -2523,6 +2681,49 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mswjs/interceptors": { + "version": "0.37.5", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.37.5.tgz", + "integrity": "sha512-AAwRb5vXFcY4L+FvZ7LZusDuZ0vEe0Zm8ohn1FM6/X7A3bj4mqmkAcGRWuvC2JwSygNwHAAmMnAI73vPHeqsHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -2597,6 +2798,13 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -2708,6 +2916,13 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/statuses": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz", + "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", @@ -3552,6 +3767,16 @@ "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", "dev": true }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -3654,6 +3879,16 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/core-js": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.7.0.tgz", @@ -4747,6 +4982,16 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/graphql": { + "version": "16.10.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz", + "integrity": "sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -4804,6 +5049,13 @@ "node": ">= 0.4" } }, + "node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -4984,6 +5236,13 @@ "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", "dev": true }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6372,6 +6631,16 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, + "node_modules/jest-localstorage-mock": { + "version": "2.4.26", + "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.26.tgz", + "integrity": "sha512-owAJrYnjulVlMIXOYQIPRCCn3MmqI3GzgfZCXdD3/pmwrIvFMXcKVWZ+aMc44IzaASapg0Z4SEFxR+v5qxDA2w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.16.0" + } + }, "node_modules/jest-matcher-utils": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", @@ -8263,6 +8532,74 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/msw": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.7.0.tgz", + "integrity": "sha512-BIodwZ19RWfCbYTxWTUfTXc+sg4OwjCAgxU1ZsgmggX/7S3LdUifsbUPJs61j0rWb19CZRGY5if77duhc0uXzw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@bundled-es-modules/cookie": "^2.0.1", + "@bundled-es-modules/statuses": "^1.0.1", + "@bundled-es-modules/tough-cookie": "^0.1.6", + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.37.0", + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/until": "^2.1.0", + "@types/cookie": "^0.6.0", + "@types/statuses": "^2.0.4", + "graphql": "^16.8.1", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "strict-event-emitter": "^0.5.1", + "type-fest": "^4.26.1", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/node_modules/type-fest": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.31.0.tgz", + "integrity": "sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -8369,6 +8706,13 @@ "node": ">=6" } }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -8465,6 +8809,13 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -9101,6 +9452,23 @@ "node": ">=8" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -10040,6 +10408,19 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } }, "dependencies": { @@ -11154,12 +11535,123 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@bundled-es-modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", + "dev": true, + "requires": { + "cookie": "^0.7.2" + } + }, + "@bundled-es-modules/statuses": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", + "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", + "dev": true, + "requires": { + "statuses": "^2.0.1" + } + }, + "@bundled-es-modules/tough-cookie": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", + "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", + "dev": true, + "requires": { + "@types/tough-cookie": "^4.0.5", + "tough-cookie": "^4.1.4" + } + }, "@discoveryjs/json-ext": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", "dev": true }, + "@inquirer/confirm": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.1.tgz", + "integrity": "sha512-vVLSbGci+IKQvDOtzpPTCOiEJCNidHcAq9JYVoWTW0svb5FiwSLotkM+JXNXejfjnzVYV9n0DTBythl9+XgTxg==", + "dev": true, + "requires": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2" + } + }, + "@inquirer/core": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.2.tgz", + "integrity": "sha512-bHd96F3ezHg1mf/J0Rb4CV8ndCN0v28kUlrHqP7+ECm1C/A+paB7Xh2lbMk6x+kweQC+rZOxM/YeKikzxco8bQ==", + "dev": true, + "requires": { + "@inquirer/figures": "^1.0.9", + "@inquirer/type": "^3.0.2", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "@inquirer/figures": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.9.tgz", + "integrity": "sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ==", + "dev": true + }, + "@inquirer/type": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.2.tgz", + "integrity": "sha512-ZhQ4TvhwHZF+lGhQ2O/rsjo80XoZR5/5qhOY3t6FJuX5XBg5Be8YzYTvaUGJnc12AUGI2nr4QSUE4PhKSigx7g==", + "dev": true, + "requires": {} + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -12142,6 +12634,42 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@mswjs/interceptors": { + "version": "0.37.5", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.37.5.tgz", + "integrity": "sha512-AAwRb5vXFcY4L+FvZ7LZusDuZ0vEe0Zm8ohn1FM6/X7A3bj4mqmkAcGRWuvC2JwSygNwHAAmMnAI73vPHeqsHA==", + "dev": true, + "requires": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + } + }, + "@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true + }, + "@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "requires": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true + }, "@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -12213,6 +12741,12 @@ "@babel/types": "^7.20.7" } }, + "@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true + }, "@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -12320,6 +12854,12 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "@types/statuses": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz", + "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==", + "dev": true + }, "@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", @@ -12953,6 +13493,12 @@ "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", "dev": true }, + "cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -13041,6 +13587,12 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true + }, "core-js": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.7.0.tgz", @@ -13862,6 +14414,12 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "graphql": { + "version": "16.10.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz", + "integrity": "sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==", + "dev": true + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -13898,6 +14456,12 @@ "function-bind": "^1.1.2" } }, + "headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true + }, "html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -14026,6 +14590,12 @@ "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==", "dev": true }, + "is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -15143,6 +15713,12 @@ } } }, + "jest-localstorage-mock": { + "version": "2.4.26", + "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.26.tgz", + "integrity": "sha512-owAJrYnjulVlMIXOYQIPRCCn3MmqI3GzgfZCXdD3/pmwrIvFMXcKVWZ+aMc44IzaASapg0Z4SEFxR+v5qxDA2w==", + "dev": true + }, "jest-matcher-utils": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", @@ -16524,6 +17100,46 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "msw": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.7.0.tgz", + "integrity": "sha512-BIodwZ19RWfCbYTxWTUfTXc+sg4OwjCAgxU1ZsgmggX/7S3LdUifsbUPJs61j0rWb19CZRGY5if77duhc0uXzw==", + "dev": true, + "requires": { + "@bundled-es-modules/cookie": "^2.0.1", + "@bundled-es-modules/statuses": "^1.0.1", + "@bundled-es-modules/tough-cookie": "^0.1.6", + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.37.0", + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/until": "^2.1.0", + "@types/cookie": "^0.6.0", + "@types/statuses": "^2.0.4", + "graphql": "^16.8.1", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "strict-event-emitter": "^0.5.1", + "type-fest": "^4.26.1", + "yargs": "^17.7.2" + }, + "dependencies": { + "type-fest": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.31.0.tgz", + "integrity": "sha512-yCxltHW07Nkhv/1F6wWBr8kz+5BGMfP+RbRSYFnegVb0qV/UMT0G0ElBloPVerqn4M2ZV80Ir1FtCcYv1cT6vQ==", + "dev": true + } + } + }, + "mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -16611,6 +17227,12 @@ "mimic-fn": "^2.1.0" } }, + "outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -16680,6 +17302,12 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -17168,6 +17796,18 @@ } } }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, + "strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -17828,6 +18468,12 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true } } } diff --git a/package.json b/package.json index 35b50282..b17c3de8 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,8 @@ "core-js": "^3.7.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "jest-localstorage-mock": "^2.4.26", + "msw": "^2.6.9", "prettier": "^3.3.3", "shellcheck": "^3.0.0", "typescript": "^5.2.2", diff --git a/setup-env.js b/setup-env.js new file mode 100644 index 00000000..3a860fb0 --- /dev/null +++ b/setup-env.js @@ -0,0 +1,5 @@ +import { server } from "./lib/test/server"; + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); diff --git a/setup-jest.js b/setup-jest.js index 7a2808d5..ad4b574d 100644 --- a/setup-jest.js +++ b/setup-jest.js @@ -1 +1,8 @@ import "whatwg-fetch"; +import { TextEncoder } from "node:util"; +import { TransformStream } from "node:stream/web"; +import { BroadcastChannel } from "node:worker_threads"; + +global.TextEncoder = TextEncoder; +global.TransformStream = TransformStream; +global.BroadcastChannel = BroadcastChannel;