diff --git a/jest.config.js b/jest.config.js index eb98ab09b..482f53a77 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,7 +4,7 @@ module.exports = { setupFilesAfterEnv: [ '/node_modules/@testing-library/jest-native/extend-expect', ], - testMatch: ['/src/__tests__/**/*.(test|spec).[jt]s?(x)'], + testMatch: ['/src/**/*.(test|spec).[jt]s?(x)'], transformIgnorePatterns: [ 'node_modules/(?!(react-native|@react-native|@react-navigation|react-native-screens|react-native-safe-area-context|react-native-gesture-handler|react-native-webview|react-native-vector-icons)/)', ], diff --git a/package.json b/package.json index 1df396726..ef832a4ad 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "add_build_info": "node scripts/autoCreatePackageInfo.js", "example": "yarn workspace @iterable/react-native-sdk-example", "test": "jest", + "test:watch": "jest --watch", "test:coverage": "jest --coverage", "typecheck": "tsc", "lint": "eslint \"**/*.{js,ts,tsx}\"", @@ -70,10 +71,10 @@ "@react-native/eslint-config": "0.79.3", "@react-native/metro-config": "0.79.3", "@react-native/typescript-config": "0.79.3", - "@react-navigation/native": "^6.1.18", + "@react-navigation/native": "^7.1.14", "@release-it/conventional-changelog": "^9.0.2", "@testing-library/jest-native": "^5.4.3", - "@testing-library/react-native": "^12.7.2", + "@testing-library/react-native": "^13.3.3", "@types/jest": "^29.5.5", "@types/react": "^19.0.0", "@types/react-native-vector-icons": "^6.4.18", @@ -91,15 +92,15 @@ "react": "19.0.0", "react-native": "0.79.3", "react-native-builder-bob": "^0.40.4", - "react-native-gesture-handler": "^2.24.0", - "react-native-safe-area-context": "^5.1.0", - "react-native-screens": "^4.9.1", + "react-native-gesture-handler": "^2.26.0", + "react-native-safe-area-context": "^5.4.0", + "react-native-screens": "^4.10.0", "react-native-vector-icons": "^10.2.0", - "react-native-webview": "^13.13.1", + "react-native-webview": "^13.14.1", "react-test-renderer": "19.0.0", "release-it": "^17.10.0", "turbo": "^1.10.7", - "typedoc": "^0.26.11", + "typedoc": "^0.28.13", "typedoc-plugin-coverage": "^3.3.0", "typedoc-plugin-mermaid": "^1.12.0", "typescript": "^5.2.2" diff --git a/src/__mocks__/MockRNIterableAPI.ts b/src/__mocks__/MockRNIterableAPI.ts index 0c099f170..390263153 100644 --- a/src/__mocks__/MockRNIterableAPI.ts +++ b/src/__mocks__/MockRNIterableAPI.ts @@ -66,10 +66,16 @@ export class MockRNIterableAPI { MockRNIterableAPI.attributionInfo = attributionInfo; } - static initializeWithApiKey = jest.fn(); + static initializeWithApiKey = jest.fn().mockResolvedValue(true); + + static initialize2WithApiKey = jest.fn().mockResolvedValue(true); + + static wakeApp = jest.fn() static setInAppShowResponse = jest.fn(); + static passAlongAuthToken = jest.fn(); + static async getInAppMessages(): Promise { return await new Promise((resolve) => { resolve(MockRNIterableAPI.messages); diff --git a/src/__tests__/Iterable.test.ts b/src/__tests__/Iterable.test.ts deleted file mode 100644 index 4ba387deb..000000000 --- a/src/__tests__/Iterable.test.ts +++ /dev/null @@ -1,433 +0,0 @@ -import { NativeEventEmitter } from 'react-native'; - -import { MockLinking } from '../__mocks__/MockLinking'; -import { MockRNIterableAPI } from '../__mocks__/MockRNIterableAPI'; -import { IterableLogger } from '../core'; -// import from the same location that consumers import from -import { - Iterable, - IterableAction, - IterableActionContext, - IterableActionSource, - IterableAttributionInfo, - IterableCommerceItem, - IterableConfig, - IterableDataRegion, - IterableEventName, - IterableLogLevel, -} from '..'; -import { TestHelper } from './TestHelper'; - -describe('Iterable', () => { - beforeEach(() => { - jest.clearAllMocks(); - Iterable.logger = new IterableLogger(new IterableConfig()); - }); - - it('setEmail_getEmail_email_returnsEmail', async () => { - Iterable.logger.log('setEmail_getEmail_email_returnsEmail'); - const result = 'user@example.com'; - // GIVEN an email - const email = 'user@example.com'; - // WHEN Iterable.setEmail is called with the given email - Iterable.setEmail(email); - // THEN Iterable.getEmail returns the given email - return await Iterable.getEmail().then((mail) => { - expect(mail).toBe(result); - }); - }); - test('setUserId_getUserId_userId_returnsUserId', async () => { - Iterable.logger.log('setUserId_getUserId_userId_returnsUserId'); - const result = 'user1'; - // GIVEN an userId - const userId = 'user1'; - // WHEN Iterable.setUserId is called with the given userId - Iterable.setUserId(userId); - // THEN Iterable.getUserId returns the given userId - return await Iterable.getUserId().then((id) => { - expect(id).toBe(result); - }); - }); - test('disableDeviceForCurrentUser_noParams_methodCalled', () => { - Iterable.logger.log('disableDeviceForCurrentUser_noParams_methodCalled'); - // GIVEN no parameters - // WHEN Iterable.disableDeviceForCurrentUser is called - Iterable.disableDeviceForCurrentUser(); - // THEN corresponding method is called on RNITerableAPI - expect(MockRNIterableAPI.disableDeviceForCurrentUser).toBeCalled(); - }); - test('getLastPushPayload_noParams_returnLastPushPayload', async () => { - Iterable.logger.log('getLastPushPayload_noParams_returnLastPushPayload'); - const result = { var1: 'val1', var2: true }; - // GIVEN no parameters - // WHEN the lastPushPayload is set - MockRNIterableAPI.lastPushPayload = { var1: 'val1', var2: true }; - // THEN the lastPushPayload is returned when getLastPushPayload is called - return await Iterable.getLastPushPayload().then((payload) => { - expect(payload).toEqual(result); - }); - }); - test('trackPushOpenWithCampaignId_pushParams_methodCalled', () => { - Iterable.logger.log('getLastPushPayload_noParams_returnLastPushPayload'); - // GIVEN the following parameters - const campaignId = 123; - const templateId = 234; - const messageId = 'someMessageId'; - const appAlreadyRunning = false; - const dataFields = { dataFieldKey: 'dataFieldValue' }; - // WHEN Iterable.trackPushOpenWithCampaignId is called - Iterable.trackPushOpenWithCampaignId( - campaignId, - templateId, - messageId, - appAlreadyRunning, - dataFields - ); - // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.trackPushOpenWithCampaignId).toBeCalledWith( - campaignId, - templateId, - messageId, - appAlreadyRunning, - dataFields - ); - }); - test('updateCart_items_methodCalled', () => { - Iterable.logger.log('updateCart_items_methodCalled'); - // GIVEN list of items - const items = [new IterableCommerceItem('id1', 'Boba Tea', 18, 26)]; - // WHEN Iterable.updateCart is called - Iterable.updateCart(items); - // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.updateCart).toBeCalledWith(items); - }); - test('trackPurchase_params_methodCalled', () => { - Iterable.logger.log('trackPurchase_params_methodCalled'); - // GIVEN the following parameters - const total = 10; - const items = [new IterableCommerceItem('id1', 'Boba Tea', 18, 26)]; - const dataFields = { dataFieldKey: 'dataFieldValue' }; - // WHEN Iterable.trackPurchase is called - Iterable.trackPurchase(total, items, dataFields); - // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.trackPurchase).toBeCalledWith( - total, - items, - dataFields - ); - }); - test('trackPurchase_paramsWithOptionalFields_methodCalled', () => { - Iterable.logger.log('trackPurchase_paramsWithOptionalFields_methodCalled'); - // GIVEN the following parameters - const total = 5; - const items = [ - new IterableCommerceItem( - 'id', - 'swordfish', - 64, - 1, - 'SKU', - 'description', - 'url', - 'imageUrl', - ['sword', 'shield'] - ), - ]; - const dataFields = { key: 'value' }; - // WHEN Iterable.trackPurchase is called - Iterable.trackPurchase(total, items, dataFields); - // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.trackPurchase).toBeCalledWith( - total, - items, - dataFields - ); - }); - test('trackEvent_params_methodCalled', () => { - Iterable.logger.log('trackPurchase_paramsWithOptionalFields_methodCalled'); - // GIVEN the following parameters - const name = 'EventName'; - const dataFields = { DatafieldKey: 'DatafieldValue' }; - // WHEN Iterable.trackEvent is called - Iterable.trackEvent(name, dataFields); - // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.trackEvent).toBeCalledWith(name, dataFields); - }); - test('setAttributionInfo_getAttributionInfo_attributionInfo_returnsAttributionInfo', async () => { - Iterable.logger.log( - 'setAttributionInfo_getAttributionInfo_attributionInfo_returnsAttributionInfo' - ); - // GIVEN attribution info - const campaignId = 1234; - const templateId = 5678; - const messageId = 'qwer'; - // WHEN Iterable.setAttributionInfo is called with the given attribution info - Iterable.setAttributionInfo( - new IterableAttributionInfo(campaignId, templateId, messageId) - ); - // THEN Iterable.getAttrbutionInfo returns the given attribution info - return await Iterable.getAttributionInfo().then((attributionInfo) => { - expect(attributionInfo?.campaignId).toBe(campaignId); - expect(attributionInfo?.templateId).toBe(templateId); - expect(attributionInfo?.messageId).toBe(messageId); - }); - }); - test('updateUser_params_methodCalled', () => { - Iterable.logger.log('updateUser_params_methodCalled'); - // GIVEN the following parameters - const dataFields = { field: 'value1' }; - // WHEN Iterable.updateUser is called - Iterable.updateUser(dataFields, false); - // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.updateUser).toBeCalledWith(dataFields, false); - }); - test('updateEmail_email_methodCalled', () => { - Iterable.logger.log('updateEmail_email_methodCalled'); - // GIVEN the new email - const newEmail = 'woo@newemail.com'; - // WHEN Iterable.updateEmail is called - Iterable.updateEmail(newEmail); - // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.updateEmail).toBeCalledWith(newEmail, undefined); - }); - test('updateEmail_emailAndToken_methodCalled', () => { - Iterable.logger.log('updateEmail_emailAndToken_methodCalled'); - // GIVEN the new email and a token - const newEmail = 'woo@newemail.com'; - const newToken = 'token2'; - // WHEN Iterable.updateEmail is called - Iterable.updateEmail(newEmail, newToken); - // THEN corresponding function is called on RNITerableAPI - expect(MockRNIterableAPI.updateEmail).toBeCalledWith(newEmail, newToken); - }); - test('iterableConfig_noParams_defaultValues', () => { - Iterable.logger.log('iterableConfig_noParams_defaultValues'); - // GIVEN no parameters - // WHEN config is initialized - const config = new IterableConfig(); - // THEN config has default values - expect(config.pushIntegrationName).toBe(undefined); - expect(config.autoPushRegistration).toBe(true); - expect(config.checkForDeferredDeeplink).toBe(false); - expect(config.inAppDisplayInterval).toBe(30.0); - expect(config.urlHandler).toBe(undefined); - expect(config.customActionHandler).toBe(undefined); - expect(config.inAppHandler).toBe(undefined); - expect(config.authHandler).toBe(undefined); - expect(config.logLevel).toBe(IterableLogLevel.info); - expect(config.logReactNativeSdkCalls).toBe(true); - expect(config.expiringAuthTokenRefreshPeriod).toBe(60.0); - expect(config.allowedProtocols).toEqual([]); - expect(config.androidSdkUseInMemoryStorageForInApps).toBe(false); - expect(config.useInMemoryStorageForInApps).toBe(false); - expect(config.dataRegion).toBe(IterableDataRegion.US); - expect(config.encryptionEnforced).toBe(false); - }); - test('iterableConfig_noParams_defaultDictValues', () => { - Iterable.logger.log('iterableConfig_noParams_defaultDictValues'); - // GIVEN no parameters - // WHEN config is initialized and converted to a dictionary - const configDict = new IterableConfig().toDict(); - // THEN config has default dictionary values - expect(configDict.pushIntegrationName).toBe(undefined); - expect(configDict.autoPushRegistration).toBe(true); - expect(configDict.inAppDisplayInterval).toBe(30.0); - expect(configDict.urlHandlerPresent).toBe(false); - expect(configDict.customActionHandlerPresent).toBe(false); - expect(configDict.inAppHandlerPresent).toBe(false); - expect(configDict.authHandlerPresent).toBe(false); - expect(configDict.logLevel).toBe(IterableLogLevel.info); - expect(configDict.expiringAuthTokenRefreshPeriod).toBe(60.0); - expect(configDict.allowedProtocols).toEqual([]); - expect(configDict.androidSdkUseInMemoryStorageForInApps).toBe(false); - expect(configDict.useInMemoryStorageForInApps).toBe(false); - expect(configDict.dataRegion).toBe(IterableDataRegion.US); - expect(configDict.encryptionEnforced).toBe(false); - }); - test('urlHandler_canOpenUrlSetToTrueAndUrlHandlerReturnsFalse_openUrlCalled', async () => { - Iterable.logger.log( - 'urlHandler_canOpenUrlSetToTrueAndUrlHandlerReturnsFalse_openUrlCalled' - ); - // sets up event emitter - const nativeEmitter = new NativeEventEmitter(); - nativeEmitter.removeAllListeners(IterableEventName.handleUrlCalled); - // sets up config file and urlHandler function - // urlHandler set to return false - const config = new IterableConfig(); - config.urlHandler = jest.fn((_url: string, _: IterableActionContext) => { - return false; - }); - // initialize Iterable object - Iterable.initialize('apiKey', config); - // GIVEN canOpenUrl set to return a promise that resolves to true - MockLinking.canOpenURL = jest.fn(async () => { - return await new Promise((resolve) => { - resolve(true); - }); - }); - MockLinking.openURL.mockReset(); - const expectedUrl = 'https://somewhere.com'; - const actionDict = { type: 'openUrl' }; - const dict = { - url: expectedUrl, - context: { action: actionDict, source: 'inApp' }, - }; - // WHEN handleUrlCalled event is emitted - nativeEmitter.emit(IterableEventName.handleUrlCalled, dict); - // THEN urlHandler and MockLinking is called with expected url - return await TestHelper.delayed(0, () => { - expect(config.urlHandler).toBeCalledWith(expectedUrl, dict.context); - expect(MockLinking.openURL).toBeCalledWith(expectedUrl); - }); - }); - test('urlHandler_canOpenUrlSetToFalseAndUrlHandlerReturnsFalse_openUrlNotCalled', async () => { - Iterable.logger.log( - 'urlHandler_canOpenUrlSetToFalseAndUrlHandlerReturnsFalse_openUrlNotCalled' - ); - // sets up event emitter - const nativeEmitter = new NativeEventEmitter(); - nativeEmitter.removeAllListeners(IterableEventName.handleUrlCalled); - // sets up config file and urlHandler function - // urlHandler set to return false - const config = new IterableConfig(); - config.urlHandler = jest.fn((_url: string, _: IterableActionContext) => { - return false; - }); - // initialize Iterable object - Iterable.initialize('apiKey', config); - // GIVEN canOpenUrl set to return a promise that resolves to false - MockLinking.canOpenURL = jest.fn(async () => { - return await new Promise((resolve) => { - resolve(false); - }); - }); - MockLinking.openURL.mockReset(); - const expectedUrl = 'https://somewhere.com'; - const actionDict = { type: 'openUrl' }; - const dict = { - url: expectedUrl, - context: { action: actionDict, source: 'inApp' }, - }; - // WHEN handleUrlCalled event is emitted - nativeEmitter.emit(IterableEventName.handleUrlCalled, dict); - // THEN urlHandler is called and MockLinking.openURL is not called - return await TestHelper.delayed(0, () => { - expect(config.urlHandler).toBeCalledWith(expectedUrl, dict.context); - expect(MockLinking.openURL).not.toBeCalled(); - }); - }); - test('urlHandler_canOpenUrlSetToTrueAndUrlHandlerReturnsTrue_openUrlNotCalled', async () => { - Iterable.logger.log( - 'urlHandler_canOpenUrlSetToTrueAndUrlHandlerReturnsTrue_openUrlNotCalled' - ); - // sets up event emitter - const nativeEmitter = new NativeEventEmitter(); - nativeEmitter.removeAllListeners(IterableEventName.handleUrlCalled); - // sets up config file and urlHandler function - // urlHandler set to return true - const config = new IterableConfig(); - config.urlHandler = jest.fn((_url: string, _: IterableActionContext) => { - return true; - }); - // initialize Iterable object - Iterable.initialize('apiKey', config); - // GIVEN canOpenUrl set to return a promise that resolves to true - MockLinking.canOpenURL = jest.fn(async () => { - return await new Promise((resolve) => { - resolve(true); - }); - }); - MockLinking.openURL.mockReset(); - const expectedUrl = 'https://somewhere.com'; - const actionDict = { type: 'openUrl' }; - const dict = { - url: expectedUrl, - context: { action: actionDict, source: 'inApp' }, - }; - // WHEN handleUrlCalled event is emitted - nativeEmitter.emit(IterableEventName.handleUrlCalled, dict); - // THEN urlHandler is called and MockLinking.openURL is not called - return await TestHelper.delayed(0, () => { - expect(config.urlHandler).toBeCalledWith(expectedUrl, dict.context); - expect(MockLinking.openURL).not.toBeCalled(); - }); - }); - test('customActionHandler_actionNameAndActionData_customActionHandlerCalled', () => { - Iterable.logger.log( - 'customActionHandler_actionNameAndActionData_customActionHandlerCalled' - ); - // sets up event emitter - const nativeEmitter = new NativeEventEmitter(); - nativeEmitter.removeAllListeners( - IterableEventName.handleCustomActionCalled - ); - // sets up config file and customActionHandler function - // customActionHandler set to return true - const config = new IterableConfig(); - config.customActionHandler = jest.fn( - (_action: IterableAction, _context: IterableActionContext) => { - return true; - } - ); - // initialize Iterable object - Iterable.initialize('apiKey', config); - // GIVEN custom action name and custom action data - const actionName = 'zeeActionName'; - const actionData = 'zeeActionData'; - const actionDict = { type: actionName, data: actionData }; - const actionSource = IterableActionSource.inApp; - const dict = { - action: actionDict, - context: { action: actionDict, source: IterableActionSource.inApp }, - }; - // WHEN handleCustomActionCalled event is emitted - nativeEmitter.emit(IterableEventName.handleCustomActionCalled, dict); - // THEN customActionHandler is called with expected action and expected context - const expectedAction = new IterableAction(actionName, actionData); - const expectedContext = new IterableActionContext( - expectedAction, - actionSource - ); - expect(config.customActionHandler).toBeCalledWith( - expectedAction, - expectedContext - ); - }); - test('handleAppLink_link_methodCalled', () => { - Iterable.logger.log('handleAppLink_link_methodCalled'); - // GIVEN a link - const link = 'https://somewhere.com/link/something'; - // WHEN Iterable.handleAppLink is called - Iterable.handleAppLink(link); - // THEN corresponding function is called on RNITerableAPI - expect(MockRNIterableAPI.handleAppLink).toBeCalledWith(link); - }); - test('updateSubscriptions_params_methodCalled', () => { - Iterable.logger.log('update subscriptions is called'); - // GIVEN the following parameters - const emailListIds = [1, 2, 3]; - const unsubscribedChannelIds = [4, 5, 6]; - const unsubscribedMessageTypeIds = [7, 8]; - const subscribedMessageTypeIds = [9]; - const campaignId = 10; - const templateId = 11; - // WHEN Iterable.updateSubscriptions is called - Iterable.updateSubscriptions( - emailListIds, - unsubscribedChannelIds, - unsubscribedMessageTypeIds, - subscribedMessageTypeIds, - campaignId, - templateId - ); - // THEN corresponding function is called on RNIterableAPI - expect(MockRNIterableAPI.updateSubscriptions).toBeCalledWith( - emailListIds, - unsubscribedChannelIds, - unsubscribedMessageTypeIds, - subscribedMessageTypeIds, - campaignId, - templateId - ); - }); -}); diff --git a/src/core/classes/Iterable.test.ts b/src/core/classes/Iterable.test.ts new file mode 100644 index 000000000..4c044165e --- /dev/null +++ b/src/core/classes/Iterable.test.ts @@ -0,0 +1,915 @@ +import { NativeEventEmitter, Platform } from "react-native"; + +import { MockLinking } from "../../__mocks__/MockLinking"; +import { MockRNIterableAPI } from "../../__mocks__/MockRNIterableAPI"; +import { IterableLogger } from ".."; +// import from the same location that consumers import from +import { + Iterable, + IterableAction, + IterableActionContext, + IterableActionSource, + IterableAttributionInfo, + IterableCommerceItem, + IterableConfig, + IterableDataRegion, + IterableEventName, + IterableLogLevel, + IterableInAppMessage, + IterableInAppCloseSource, + IterableInAppDeleteSource, + IterableInAppLocation, + IterableInAppTrigger, + IterableInAppTriggerType, + IterableAuthResponse, + IterableInAppShowResponse, +} from "../.."; +import { TestHelper } from "../../__tests__/TestHelper"; + +const getDefaultConfig = () => { + const config = new IterableConfig(); + config.logReactNativeSdkCalls = false; + return config; +}; + +describe("Iterable", () => { + beforeEach(() => { + jest.clearAllMocks(); + const config = getDefaultConfig(); + Iterable.logger = new IterableLogger(config); + }); + + afterEach(() => { + // Clean up all event listeners to prevent Jest worker process hanging + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(IterableEventName.handleUrlCalled); + nativeEmitter.removeAllListeners(IterableEventName.handleInAppCalled); + nativeEmitter.removeAllListeners( + IterableEventName.handleCustomActionCalled + ); + nativeEmitter.removeAllListeners(IterableEventName.handleAuthCalled); + nativeEmitter.removeAllListeners(IterableEventName.handleAuthSuccessCalled); + nativeEmitter.removeAllListeners(IterableEventName.handleAuthFailureCalled); + + // Clear any pending timers + jest.clearAllTimers(); + }); + + describe("setEmail", () => { + it("should set the email", async () => { + const result = "user@example.com"; + // GIVEN an email + const email = "user@example.com"; + // WHEN Iterable.setEmail is called with the given email + Iterable.setEmail(email); + // THEN Iterable.getEmail returns the given email + return await Iterable.getEmail().then((mail) => { + expect(mail).toBe(result); + }); + }); + }); + + describe("setUserId", () => { + it("should set the userId", async () => { + const result = "user1"; + // GIVEN an userId + const userId = "user1"; + // WHEN Iterable.setUserId is called with the given userId + Iterable.setUserId(userId); + // THEN Iterable.getUserId returns the given userId + return await Iterable.getUserId().then((id) => { + expect(id).toBe(result); + }); + }); + }); + + describe("disableDeviceForCurrentUser", () => { + it("should disable the device for the current user", () => { + // GIVEN no parameters + // WHEN Iterable.disableDeviceForCurrentUser is called + Iterable.disableDeviceForCurrentUser(); + // THEN corresponding method is called on RNITerableAPI + expect(MockRNIterableAPI.disableDeviceForCurrentUser).toBeCalled(); + }); + }); + + describe("getLastPushPayload", () => { + it("should return the last push payload", async () => { + const result = { var1: "val1", var2: true }; + // GIVEN no parameters + // WHEN the lastPushPayload is set + MockRNIterableAPI.lastPushPayload = { var1: "val1", var2: true }; + // THEN the lastPushPayload is returned when getLastPushPayload is called + return await Iterable.getLastPushPayload().then((payload) => { + expect(payload).toEqual(result); + }); + }); + }); + + describe("trackPushOpenWithCampaignId", () => { + it("should track the push open with the campaign id", () => { + // GIVEN the following parameters + const campaignId = 123; + const templateId = 234; + const messageId = "someMessageId"; + const appAlreadyRunning = false; + const dataFields = { dataFieldKey: "dataFieldValue" }; + // WHEN Iterable.trackPushOpenWithCampaignId is called + Iterable.trackPushOpenWithCampaignId( + campaignId, + templateId, + messageId, + appAlreadyRunning, + dataFields + ); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.trackPushOpenWithCampaignId).toBeCalledWith( + campaignId, + templateId, + messageId, + appAlreadyRunning, + dataFields + ); + }); + }); + + describe("updateCart", () => { + it("should call IterableAPI.updateCart with the correct items", () => { + // GIVEN list of items + const items = [new IterableCommerceItem("id1", "Boba Tea", 18, 26)]; + // WHEN Iterable.updateCart is called + Iterable.updateCart(items); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.updateCart).toBeCalledWith(items); + }); + }); + + describe("trackPurchase", () => { + it("should track the purchase", () => { + // GIVEN the following parameters + const total = 10; + const items = [new IterableCommerceItem("id1", "Boba Tea", 18, 26)]; + const dataFields = { dataFieldKey: "dataFieldValue" }; + // WHEN Iterable.trackPurchase is called + Iterable.trackPurchase(total, items, dataFields); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.trackPurchase).toBeCalledWith( + total, + items, + dataFields + ); + }); + + it("should track the purchase when called with optional fields", () => { + // GIVEN the following parameters + const total = 5; + const items = [ + new IterableCommerceItem( + "id", + "swordfish", + 64, + 1, + "SKU", + "description", + "url", + "imageUrl", + ["sword", "shield"] + ), + ]; + const dataFields = { key: "value" }; + // WHEN Iterable.trackPurchase is called + Iterable.trackPurchase(total, items, dataFields); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.trackPurchase).toBeCalledWith( + total, + items, + dataFields + ); + }); + }); + + describe("trackEvent", () => { + it("should call IterableAPI.trackEvent with the correct name and dataFields", () => { + // GIVEN the following parameters + const name = "EventName"; + const dataFields = { DatafieldKey: "DatafieldValue" }; + // WHEN Iterable.trackEvent is called + Iterable.trackEvent(name, dataFields); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.trackEvent).toBeCalledWith(name, dataFields); + }); + }); + + describe("setAttributionInfo", () => { + it("should set the attribution info", async () => { + // GIVEN attribution info + const campaignId = 1234; + const templateId = 5678; + const messageId = "qwer"; + // WHEN Iterable.setAttributionInfo is called with the given attribution info + Iterable.setAttributionInfo( + new IterableAttributionInfo(campaignId, templateId, messageId) + ); + // THEN Iterable.getAttrbutionInfo returns the given attribution info + return await Iterable.getAttributionInfo().then((attributionInfo) => { + expect(attributionInfo?.campaignId).toBe(campaignId); + expect(attributionInfo?.templateId).toBe(templateId); + expect(attributionInfo?.messageId).toBe(messageId); + }); + }); + }); + + describe("updateUser", () => { + it("should update the user", () => { + // GIVEN the following parameters + const dataFields = { field: "value1" }; + // WHEN Iterable.updateUser is called + Iterable.updateUser(dataFields, false); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.updateUser).toBeCalledWith(dataFields, false); + }); + }); + + describe("updateEmail", () => { + it("should call IterableAPI.updateEmail with the correct email", () => { + // GIVEN the new email + const newEmail = "woo@newemail.com"; + // WHEN Iterable.updateEmail is called + Iterable.updateEmail(newEmail); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.updateEmail).toBeCalledWith(newEmail, undefined); + }); + + it("should call IterableAPI.updateEmail with the correct email and token", () => { + // GIVEN the new email and a token + const newEmail = "woo@newemail.com"; + const newToken = "token2"; + // WHEN Iterable.updateEmail is called + Iterable.updateEmail(newEmail, newToken); + // THEN corresponding function is called on RNITerableAPI + expect(MockRNIterableAPI.updateEmail).toBeCalledWith(newEmail, newToken); + }); + }); + + describe("iterableConfig", () => { + it("should have default values", () => { + // GIVEN no parameters + // WHEN config is initialized + const config = new IterableConfig(); + // THEN config has default values + expect(config.pushIntegrationName).toBe(undefined); + expect(config.autoPushRegistration).toBe(true); + expect(config.checkForDeferredDeeplink).toBe(false); + expect(config.inAppDisplayInterval).toBe(30.0); + expect(config.urlHandler).toBe(undefined); + expect(config.customActionHandler).toBe(undefined); + expect(config.inAppHandler).toBe(undefined); + expect(config.authHandler).toBe(undefined); + expect(config.logLevel).toBe(IterableLogLevel.info); + expect(config.logReactNativeSdkCalls).toBe(true); + expect(config.expiringAuthTokenRefreshPeriod).toBe(60.0); + expect(config.allowedProtocols).toEqual([]); + expect(config.androidSdkUseInMemoryStorageForInApps).toBe(false); + expect(config.useInMemoryStorageForInApps).toBe(false); + expect(config.dataRegion).toBe(IterableDataRegion.US); + expect(config.encryptionEnforced).toBe(false); + const configDict = config.toDict(); + expect(configDict.pushIntegrationName).toBe(undefined); + expect(configDict.autoPushRegistration).toBe(true); + expect(configDict.inAppDisplayInterval).toBe(30.0); + expect(configDict.urlHandlerPresent).toBe(false); + expect(configDict.customActionHandlerPresent).toBe(false); + expect(configDict.inAppHandlerPresent).toBe(false); + expect(configDict.authHandlerPresent).toBe(false); + expect(configDict.logLevel).toBe(IterableLogLevel.info); + expect(configDict.expiringAuthTokenRefreshPeriod).toBe(60.0); + expect(configDict.allowedProtocols).toEqual([]); + expect(configDict.androidSdkUseInMemoryStorageForInApps).toBe(false); + expect(configDict.useInMemoryStorageForInApps).toBe(false); + expect(configDict.dataRegion).toBe(IterableDataRegion.US); + expect(configDict.encryptionEnforced).toBe(false); + }); + }); + + describe("urlHandler", () => { + it("should open the url when canOpenURL returns true and urlHandler returns false", async () => { + // sets up event emitter + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(IterableEventName.handleUrlCalled); + // sets up config file and urlHandler function + // urlHandler set to return false + const config = new IterableConfig(); + config.logReactNativeSdkCalls = false; + config.urlHandler = jest.fn((_url: string, _: IterableActionContext) => { + return false; + }); + // initialize Iterable object + Iterable.initialize("apiKey", config); + // GIVEN canOpenUrl set to return a promise that resolves to true + MockLinking.canOpenURL = jest.fn(async () => { + return await new Promise((resolve) => { + resolve(true); + }); + }); + MockLinking.openURL.mockReset(); + const expectedUrl = "https://somewhere.com"; + const actionDict = { type: "openUrl" }; + const dict = { + url: expectedUrl, + context: { action: actionDict, source: "inApp" }, + }; + // WHEN handleUrlCalled event is emitted + nativeEmitter.emit(IterableEventName.handleUrlCalled, dict); + // THEN urlHandler and MockLinking is called with expected url + return await TestHelper.delayed(0, () => { + expect(config.urlHandler).toBeCalledWith(expectedUrl, dict.context); + expect(MockLinking.openURL).toBeCalledWith(expectedUrl); + }); + }); + + it("should not open the url when canOpenURL returns false and urlHandler returns false", async () => { + // sets up event emitter + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(IterableEventName.handleUrlCalled); + // sets up config file and urlHandler function + // urlHandler set to return false + const config = new IterableConfig(); + config.logReactNativeSdkCalls = false; + config.urlHandler = jest.fn((_url: string, _: IterableActionContext) => { + return false; + }); + // initialize Iterable object + Iterable.initialize("apiKey", config); + // GIVEN canOpenUrl set to return a promise that resolves to false + MockLinking.canOpenURL = jest.fn(async () => { + return await new Promise((resolve) => { + resolve(false); + }); + }); + MockLinking.openURL.mockReset(); + const expectedUrl = "https://somewhere.com"; + const actionDict = { type: "openUrl" }; + const dict = { + url: expectedUrl, + context: { action: actionDict, source: "inApp" }, + }; + // WHEN handleUrlCalled event is emitted + nativeEmitter.emit(IterableEventName.handleUrlCalled, dict); + // THEN urlHandler is called and MockLinking.openURL is not called + return await TestHelper.delayed(0, () => { + expect(config.urlHandler).toBeCalledWith(expectedUrl, dict.context); + expect(MockLinking.openURL).not.toBeCalled(); + }); + }); + + it("should not open the url when canOpenURL returns true and urlHandler returns true", async () => { + // sets up event emitter + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(IterableEventName.handleUrlCalled); + // sets up config file and urlHandler function + // urlHandler set to return true + const config = new IterableConfig(); + config.logReactNativeSdkCalls = false; + config.urlHandler = jest.fn((_url: string, _: IterableActionContext) => { + return true; + }); + // initialize Iterable object + Iterable.initialize("apiKey", config); + // GIVEN canOpenUrl set to return a promise that resolves to true + MockLinking.canOpenURL = jest.fn(async () => { + return await new Promise((resolve) => { + resolve(true); + }); + }); + MockLinking.openURL.mockReset(); + const expectedUrl = "https://somewhere.com"; + const actionDict = { type: "openUrl" }; + const dict = { + url: expectedUrl, + context: { action: actionDict, source: "inApp" }, + }; + // WHEN handleUrlCalled event is emitted + nativeEmitter.emit(IterableEventName.handleUrlCalled, dict); + // THEN urlHandler is called and MockLinking.openURL is not called + return await TestHelper.delayed(0, () => { + expect(config.urlHandler).toBeCalledWith(expectedUrl, dict.context); + expect(MockLinking.openURL).not.toBeCalled(); + }); + }); + }); + + describe("customActionHandler", () => { + it("should be called with the correct action and context", () => { + // sets up event emitter + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners( + IterableEventName.handleCustomActionCalled + ); + // sets up config file and customActionHandler function + // customActionHandler set to return true + const config = new IterableConfig(); + config.logReactNativeSdkCalls = false; + config.customActionHandler = jest.fn( + (_action: IterableAction, _context: IterableActionContext) => { + return true; + } + ); + // initialize Iterable object + Iterable.initialize("apiKey", config); + // GIVEN custom action name and custom action data + const actionName = "zeeActionName"; + const actionData = "zeeActionData"; + const actionDict = { type: actionName, data: actionData }; + const actionSource = IterableActionSource.inApp; + const dict = { + action: actionDict, + context: { action: actionDict, source: IterableActionSource.inApp }, + }; + // WHEN handleCustomActionCalled event is emitted + nativeEmitter.emit(IterableEventName.handleCustomActionCalled, dict); + // THEN customActionHandler is called with expected action and expected context + const expectedAction = new IterableAction(actionName, actionData); + const expectedContext = new IterableActionContext( + expectedAction, + actionSource + ); + expect(config.customActionHandler).toBeCalledWith( + expectedAction, + expectedContext + ); + }); + }); + + describe("handleAppLink", () => { + it("should call IterableAPI.handleAppLink", () => { + // GIVEN a link + const link = "https://somewhere.com/link/something"; + // WHEN Iterable.handleAppLink is called + Iterable.handleAppLink(link); + // THEN corresponding function is called on RNITerableAPI + expect(MockRNIterableAPI.handleAppLink).toBeCalledWith(link); + }); + }); + + describe("updateSubscriptions", () => { + it("should call IterableAPI.updateSubscriptions with the correct parameters", () => { + // GIVEN the following parameters + const emailListIds = [1, 2, 3]; + const unsubscribedChannelIds = [4, 5, 6]; + const unsubscribedMessageTypeIds = [7, 8]; + const subscribedMessageTypeIds = [9]; + const campaignId = 10; + const templateId = 11; + // WHEN Iterable.updateSubscriptions is called + Iterable.updateSubscriptions( + emailListIds, + unsubscribedChannelIds, + unsubscribedMessageTypeIds, + subscribedMessageTypeIds, + campaignId, + templateId + ); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.updateSubscriptions).toBeCalledWith( + emailListIds, + unsubscribedChannelIds, + unsubscribedMessageTypeIds, + subscribedMessageTypeIds, + campaignId, + templateId + ); + }); + }); + + describe("initialize", () => { + it("should call IterableAPI.initializeWithApiKey and save the config", async () => { + // GIVEN an API key and config + const apiKey = "test-api-key"; + const config = new IterableConfig(); + config.logReactNativeSdkCalls = false; + config.logLevel = IterableLogLevel.debug; + // WHEN Iterable.initialize is called + const result = await Iterable.initialize(apiKey, config); + // THEN corresponding function is called on RNIterableAPI and config is saved + expect(MockRNIterableAPI.initializeWithApiKey).toBeCalledWith( + apiKey, + config.toDict(), + expect.any(String) + ); + expect(Iterable.savedConfig).toBe(config); + expect(result).toBe(true); + }); + + it("should give the default config if no config is provided", async () => { + // GIVEN an API key + const apiKey = "test-api-key"; + // WHEN Iterable.initialize is called + const result = await Iterable.initialize(apiKey); + // THEN corresponding function is called on RNIterableAPI and config is saved + expect(Iterable.savedConfig).toStrictEqual(new IterableConfig()); + expect(result).toBe(true); + }); + }); + + describe("initialize2", () => { + it("should call IterableAPI.initialize2WithApiKey with an endpoint and save the config", async () => { + // GIVEN an API key, config, and endpoint + const apiKey = "test-api-key"; + const config = new IterableConfig(); + config.logReactNativeSdkCalls = false; + const apiEndPoint = "https://api.staging.iterable.com"; + // WHEN Iterable.initialize2 is called + const result = await Iterable.initialize2(apiKey, config, apiEndPoint); + // THEN corresponding function is called on RNIterableAPI and config is saved + expect(MockRNIterableAPI.initialize2WithApiKey).toBeCalledWith( + apiKey, + config.toDict(), + expect.any(String), + apiEndPoint + ); + expect(Iterable.savedConfig).toBe(config); + expect(result).toBe(true); + }); + + it("should give the default config if no config is provided", async () => { + // GIVEN an API key + const apiKey = "test-api-key"; + const apiEndPoint = "https://api.staging.iterable.com"; + // WHEN Iterable.initialize is called + const result = await Iterable.initialize2(apiKey, undefined, apiEndPoint); + // THEN corresponding function is called on RNIterableAPI and config is saved + expect(Iterable.savedConfig).toStrictEqual(new IterableConfig()); + expect(result).toBe(true); + }); + }); + + describe("wakeApp", () => { + it("should call IterableAPI.wakeApp on Android", () => { + // GIVEN Android platform + const originalPlatform = Platform.OS; + Object.defineProperty(Platform, "OS", { + value: "android", + writable: true, + }); + // WHEN Iterable.wakeApp is called + Iterable.wakeApp(); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.wakeApp).toBeCalled(); + // Restore original platform + Object.defineProperty(Platform, "OS", { + value: originalPlatform, + writable: true, + }); + }); + + it("should not call IterableAPI.wakeApp on iOS", () => { + // GIVEN iOS platform + const originalPlatform = Platform.OS; + Object.defineProperty(Platform, "OS", { + value: "ios", + writable: true, + }); + // WHEN Iterable.wakeApp is called + Iterable.wakeApp(); + // THEN corresponding function is not called on RNIterableAPI + expect(MockRNIterableAPI.wakeApp).not.toBeCalled(); + // Restore original platform + Object.defineProperty(Platform, "OS", { + value: originalPlatform, + writable: true, + }); + }); + }); + + describe("trackInAppOpen", () => { + it("should call IterableAPI.trackInAppOpen with the correct parameters", () => { + // GIVEN an in-app message and location + const message = new IterableInAppMessage( + "1234", + 4567, + new IterableInAppTrigger(IterableInAppTriggerType.immediate), + new Date(), + new Date(), + false, + undefined, + undefined, + false, + 0 + ); + const location = IterableInAppLocation.inApp; + // WHEN Iterable.trackInAppOpen is called + Iterable.trackInAppOpen(message, location); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.trackInAppOpen).toBeCalledWith( + message.messageId, + location + ); + }); + }); + + describe("trackInAppClick", () => { + it("should call IterableAPI.trackInAppClick with the correct parameters", () => { + // GIVEN an in-app message, location, and clicked URL + const message = new IterableInAppMessage( + "1234", + 4567, + new IterableInAppTrigger(IterableInAppTriggerType.immediate), + new Date(), + new Date(), + false, + undefined, + undefined, + false, + 0 + ); + const location = IterableInAppLocation.inApp; + const clickedUrl = "https://www.example.com"; + // WHEN Iterable.trackInAppClick is called + Iterable.trackInAppClick(message, location, clickedUrl); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.trackInAppClick).toBeCalledWith( + message.messageId, + location, + clickedUrl + ); + }); + }); + + describe("trackInAppClose", () => { + it("should call IterableAPI.trackInAppClose with the correct parameters", () => { + // GIVEN an in-app message, location, and source (no URL) + const message = new IterableInAppMessage( + "1234", + 4567, + new IterableInAppTrigger(IterableInAppTriggerType.immediate), + new Date(), + new Date(), + false, + undefined, + undefined, + false, + 0 + ); + const location = IterableInAppLocation.inApp; + const source = IterableInAppCloseSource.back; + // WHEN Iterable.trackInAppClose is called + Iterable.trackInAppClose(message, location, source); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.trackInAppClose).toBeCalledWith( + message.messageId, + location, + source, + undefined + ); + }); + + it("should call IterableAPI.trackInAppClose with a clicked URL when provided", () => { + // GIVEN an in-app message, location, source, and clicked URL + const message = new IterableInAppMessage( + "1234", + 4567, + new IterableInAppTrigger(IterableInAppTriggerType.immediate), + new Date(), + new Date(), + false, + undefined, + undefined, + false, + 0 + ); + const location = IterableInAppLocation.inApp; + const source = IterableInAppCloseSource.back; + const clickedUrl = "https://www.example.com"; + // WHEN Iterable.trackInAppClose is called + Iterable.trackInAppClose(message, location, source, clickedUrl); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.trackInAppClose).toBeCalledWith( + message.messageId, + location, + source, + clickedUrl + ); + }); + }); + + describe("inAppConsume", () => { + it("should call IterableAPI.inAppConsume with the correct parameters", () => { + // GIVEN an in-app message, location, and delete source + const message = new IterableInAppMessage( + "1234", + 4567, + new IterableInAppTrigger(IterableInAppTriggerType.immediate), + new Date(), + new Date(), + false, + undefined, + undefined, + false, + 0 + ); + const location = IterableInAppLocation.inApp; + const source = IterableInAppDeleteSource.deleteButton; + // WHEN Iterable.inAppConsume is called + Iterable.inAppConsume(message, location, source); + // THEN corresponding function is called on RNIterableAPI + expect(MockRNIterableAPI.inAppConsume).toBeCalledWith( + message.messageId, + location, + source + ); + }); + }); + + describe("getVersionFromPackageJson", () => { + it("should return the version from the package.json file", () => { + // GIVEN no parameters + // WHEN Iterable.getVersionFromPackageJson is called + const version = Iterable.getVersionFromPackageJson(); + // THEN a version string is returned + expect(typeof version).toBe("string"); + expect(version.length).toBeGreaterThan(0); + }); + }); + + describe("setupEventHandlers", () => { + it("should call inAppHandler when handleInAppCalled event is emitted", () => { + // sets up event emitter + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(IterableEventName.handleInAppCalled); + // sets up config file and inAppHandler function + const config = new IterableConfig(); + config.logReactNativeSdkCalls = false; + config.inAppHandler = jest.fn((_message: IterableInAppMessage) => { + return IterableInAppShowResponse.show; + }); + // initialize Iterable object + Iterable.initialize("apiKey", config); + // GIVEN message dictionary + const messageDict = { + messageId: "1234", + campaignId: 4567, + trigger: { type: 0 }, + createdAt: new Date().toISOString(), + expiresAt: new Date().toISOString(), + saveToInbox: false, + inboxMetadata: undefined, + customPayload: undefined, + read: false, + priorityLevel: 0, + }; + // WHEN handleInAppCalled event is emitted + nativeEmitter.emit(IterableEventName.handleInAppCalled, messageDict); + // THEN inAppHandler is called and setInAppShowResponse is called + expect(config.inAppHandler).toBeCalledWith( + expect.any(IterableInAppMessage) + ); + expect(MockRNIterableAPI.setInAppShowResponse).toBeCalledWith( + IterableInAppShowResponse.show + ); + }); + + describe("authHandler", () => { + it("should call authHandler when handleAuthCalled event is emitted", async () => { + // sets up event emitter + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(IterableEventName.handleAuthCalled); + nativeEmitter.removeAllListeners( + IterableEventName.handleAuthSuccessCalled + ); + nativeEmitter.removeAllListeners( + IterableEventName.handleAuthFailureCalled + ); + // sets up config file and authHandler function + const config = new IterableConfig(); + config.logReactNativeSdkCalls = false; + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + const authResponse = new IterableAuthResponse(); + authResponse.authToken = "test-token"; + authResponse.successCallback = successCallback; + authResponse.failureCallback = failureCallback; + config.authHandler = jest.fn(() => { + return Promise.resolve(authResponse); + }); + // initialize Iterable object + Iterable.initialize("apiKey", config); + // GIVEN auth handler returns AuthResponse + // WHEN handleAuthCalled event is emitted + nativeEmitter.emit(IterableEventName.handleAuthCalled); + // WHEN handleAuthSuccessCalled event is emitted + nativeEmitter.emit(IterableEventName.handleAuthSuccessCalled); + // THEN passAlongAuthToken is called with the token and success callback is called after timeout + return await TestHelper.delayed(1100, () => { + expect(MockRNIterableAPI.passAlongAuthToken).toBeCalledWith( + "test-token" + ); + expect(successCallback).toBeCalled(); + expect(failureCallback).not.toBeCalled(); + }); + }); + + it("should call authHandler when handleAuthFailureCalled event is emitted", async () => { + // sets up event emitter + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(IterableEventName.handleAuthCalled); + nativeEmitter.removeAllListeners( + IterableEventName.handleAuthSuccessCalled + ); + nativeEmitter.removeAllListeners( + IterableEventName.handleAuthFailureCalled + ); + // sets up config file and authHandler function + const config = new IterableConfig(); + config.logReactNativeSdkCalls = false; + const successCallback = jest.fn(); + const failureCallback = jest.fn(); + const authResponse = new IterableAuthResponse(); + authResponse.authToken = "test-token"; + authResponse.successCallback = successCallback; + authResponse.failureCallback = failureCallback; + config.authHandler = jest.fn(() => { + // Why are we resolving when this is a failure? + return Promise.resolve(authResponse); + }); + // initialize Iterable object + Iterable.initialize("apiKey", config); + // GIVEN auth handler returns AuthResponse + // WHEN handleAuthCalled event is emitted + nativeEmitter.emit(IterableEventName.handleAuthCalled); + // WHEN handleAuthFailureCalled event is emitted + nativeEmitter.emit(IterableEventName.handleAuthFailureCalled); + // THEN passAlongAuthToken is called with the token and failure callback is called after timeout + return await TestHelper.delayed(1100, () => { + expect(MockRNIterableAPI.passAlongAuthToken).toBeCalledWith( + "test-token" + ); + expect(failureCallback).toBeCalled(); + expect(successCallback).not.toBeCalled(); + }); + }); + + it("should call authHandler when handleAuthCalled event is emitted and returns a string token", async () => { + // sets up event emitter + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(IterableEventName.handleAuthCalled); + // sets up config file and authHandler function + const config = new IterableConfig(); + config.logReactNativeSdkCalls = false; + config.authHandler = jest.fn(() => { + return Promise.resolve("string-token"); + }); + // initialize Iterable object + Iterable.initialize("apiKey", config); + // GIVEN auth handler returns string token + // WHEN handleAuthCalled event is emitted + nativeEmitter.emit(IterableEventName.handleAuthCalled); + // THEN passAlongAuthToken is called with the string token + return await TestHelper.delayed(100, () => { + expect(MockRNIterableAPI.passAlongAuthToken).toBeCalledWith( + "string-token" + ); + }); + }); + + it("should call authHandler when handleAuthCalled event is emitted and returns an unexpected response", () => { + // sets up event emitter + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(IterableEventName.handleAuthCalled); + // sets up config file and authHandler function + const config = new IterableConfig(); + config.logReactNativeSdkCalls = false; + config.authHandler = jest.fn(() => { + return Promise.resolve({ unexpected: "object" } as unknown as + | string + | IterableAuthResponse); + }); + // initialize Iterable object + Iterable.initialize("apiKey", config); + // GIVEN auth handler returns unexpected response + // WHEN handleAuthCalled event is emitted + nativeEmitter.emit(IterableEventName.handleAuthCalled); + // THEN error is logged (we can't easily test console.log, but we can verify no crash) + expect(MockRNIterableAPI.passAlongAuthToken).not.toBeCalled(); + }); + + it("should call authHandler when handleAuthCalled event is emitted and rejects the promise", () => { + // sets up event emitter + const nativeEmitter = new NativeEventEmitter(); + nativeEmitter.removeAllListeners(IterableEventName.handleAuthCalled); + // sets up config file and authHandler function + const config = new IterableConfig(); + config.logReactNativeSdkCalls = false; + config.authHandler = jest.fn(() => { + return Promise.reject(new Error("Auth failed")); + }); + // initialize Iterable object + Iterable.initialize("apiKey", config); + // GIVEN auth handler rejects promise + // WHEN handleAuthCalled event is emitted + nativeEmitter.emit(IterableEventName.handleAuthCalled); + // THEN error is logged (we can't easily test console.log, but we can verify no crash) + expect(MockRNIterableAPI.passAlongAuthToken).not.toBeCalled(); + }); + }); + }); +}); diff --git a/src/core/classes/Iterable.ts b/src/core/classes/Iterable.ts index 704403099..3f56de76f 100644 --- a/src/core/classes/Iterable.ts +++ b/src/core/classes/Iterable.ts @@ -948,9 +948,11 @@ export class Iterable { if (Platform.OS === 'android') { //Give enough time for Activity to wake up. - setTimeout(() => { + const timeoutId = setTimeout(() => { callUrlHandler(url, context); }, 1000); + // Use unref() to prevent the timeout from keeping the process alive + timeoutId.unref(); } else { callUrlHandler(url, context); } @@ -995,7 +997,7 @@ export class Iterable { (promiseResult as IterableAuthResponse).authToken ); - setTimeout(() => { + const timeoutId = setTimeout(() => { if ( authResponseCallback === IterableAuthResponseResult.SUCCESS ) { @@ -1014,6 +1016,8 @@ export class Iterable { ); } }, 1000); + // Use unref() to prevent the timeout from keeping the process alive + timeoutId.unref(); } else if (typeof promiseResult === typeof '') { //If promise only returns string RNIterableAPI.passAlongAuthToken(promiseResult as string); diff --git a/tsdoc.json b/tsdoc.json index 890d46359..f4ac01a01 100644 --- a/tsdoc.json +++ b/tsdoc.json @@ -1,7 +1,3 @@ { - "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", - "extends": [ - "typedoc/tsdoc.json", - "typedoc-plugin-mermaid/tsdoc.json" - ] -} \ No newline at end of file + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json" +} diff --git a/yarn.lock b/yarn.lock index 241f48812..d9961ac53 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2964,6 +2964,19 @@ __metadata: languageName: node linkType: hard +"@gerrit0/mini-shiki@npm:^3.12.0": + version: 3.13.0 + resolution: "@gerrit0/mini-shiki@npm:3.13.0" + dependencies: + "@shikijs/engine-oniguruma": ^3.13.0 + "@shikijs/langs": ^3.13.0 + "@shikijs/themes": ^3.13.0 + "@shikijs/types": ^3.13.0 + "@shikijs/vscode-textmate": ^10.0.2 + checksum: 748d28e2dce67fac31cf36e97d849fc2bc60762b98a13c7bb50b6be181656c12ea58c5c6af7955fee99018b53fc9fd72dbf3a0552de7ad5845688b6c03312270 + languageName: node + linkType: hard + "@hapi/hoek@npm:^9.0.0, @hapi/hoek@npm:^9.3.0": version: 9.3.0 resolution: "@hapi/hoek@npm:9.3.0" @@ -3111,10 +3124,10 @@ __metadata: "@react-native/eslint-config": 0.79.3 "@react-native/metro-config": 0.79.3 "@react-native/typescript-config": 0.79.3 - "@react-navigation/native": ^6.1.18 + "@react-navigation/native": ^7.1.14 "@release-it/conventional-changelog": ^9.0.2 "@testing-library/jest-native": ^5.4.3 - "@testing-library/react-native": ^12.7.2 + "@testing-library/react-native": ^13.3.3 "@types/jest": ^29.5.5 "@types/react": ^19.0.0 "@types/react-native-vector-icons": ^6.4.18 @@ -3132,15 +3145,15 @@ __metadata: react: 19.0.0 react-native: 0.79.3 react-native-builder-bob: ^0.40.4 - react-native-gesture-handler: ^2.24.0 - react-native-safe-area-context: ^5.1.0 - react-native-screens: ^4.9.1 + react-native-gesture-handler: ^2.26.0 + react-native-safe-area-context: ^5.4.0 + react-native-screens: ^4.10.0 react-native-vector-icons: ^10.2.0 - react-native-webview: ^13.13.1 + react-native-webview: ^13.14.1 react-test-renderer: 19.0.0 release-it: ^17.10.0 turbo: ^1.10.7 - typedoc: ^0.26.11 + typedoc: ^0.28.13 typedoc-plugin-coverage: ^3.3.0 typedoc-plugin-mermaid: ^1.12.0 typescript: ^5.2.2 @@ -3221,6 +3234,13 @@ __metadata: languageName: node linkType: hard +"@jest/diff-sequences@npm:30.0.1": + version: 30.0.1 + resolution: "@jest/diff-sequences@npm:30.0.1" + checksum: e5f931ca69c15a9b3a9b23b723f51ffc97f031b2f3ca37f901333dab99bd4dfa1ad4192a5cd893cd1272f7602eb09b9cfb5fc6bb62a0232c96fb8b5e96094970 + languageName: node + linkType: hard + "@jest/environment@npm:^29.7.0": version: 29.7.0 resolution: "@jest/environment@npm:29.7.0" @@ -3266,6 +3286,13 @@ __metadata: languageName: node linkType: hard +"@jest/get-type@npm:30.1.0": + version: 30.1.0 + resolution: "@jest/get-type@npm:30.1.0" + checksum: e2a95fbb49ce2d15547db8af5602626caf9b05f62a5e583b4a2de9bd93a2bfe7175f9bbb2b8a5c3909ce261d467b6991d7265bb1d547cb60e7e97f571f361a70 + languageName: node + linkType: hard + "@jest/globals@npm:^29.7.0": version: 29.7.0 resolution: "@jest/globals@npm:29.7.0" @@ -3315,6 +3342,15 @@ __metadata: languageName: node linkType: hard +"@jest/schemas@npm:30.0.5": + version: 30.0.5 + resolution: "@jest/schemas@npm:30.0.5" + dependencies: + "@sinclair/typebox": ^0.34.0 + checksum: 7a4fc4166f688947c22d81e61aaf2cb22f178dbf6ee806b0931b75136899d426a72a8330762f27f0cf6f79da0d2a56f49a22fe09f5f80df95a683ed237a0f3b0 + languageName: node + linkType: hard + "@jest/schemas@npm:^29.6.3": version: 29.6.3 resolution: "@jest/schemas@npm:29.6.3" @@ -4150,6 +4186,23 @@ __metadata: languageName: node linkType: hard +"@react-navigation/core@npm:^7.12.4": + version: 7.12.4 + resolution: "@react-navigation/core@npm:7.12.4" + dependencies: + "@react-navigation/routers": ^7.5.1 + escape-string-regexp: ^4.0.0 + nanoid: ^3.3.11 + query-string: ^7.1.3 + react-is: ^19.1.0 + use-latest-callback: ^0.2.4 + use-sync-external-store: ^1.5.0 + peerDependencies: + react: ">= 18.2.0" + checksum: 84eb6d003d09c271e9e5e8f34c4a440447a82d81df7bd2f87dc4d56f851a5c74b1ec6e488cc2702cf16fde7d33bb55a58fe1dbd14e6cbdd513583b33227c2471 + languageName: node + linkType: hard + "@react-navigation/elements@npm:^1.3.31": version: 1.3.31 resolution: "@react-navigation/elements@npm:1.3.31" @@ -4178,7 +4231,7 @@ __metadata: languageName: node linkType: hard -"@react-navigation/native@npm:^6.0.8, @react-navigation/native@npm:^6.1.18": +"@react-navigation/native@npm:^6.0.8": version: 6.1.18 resolution: "@react-navigation/native@npm:6.1.18" dependencies: @@ -4193,6 +4246,22 @@ __metadata: languageName: node linkType: hard +"@react-navigation/native@npm:^7.1.14": + version: 7.1.17 + resolution: "@react-navigation/native@npm:7.1.17" + dependencies: + "@react-navigation/core": ^7.12.4 + escape-string-regexp: ^4.0.0 + fast-deep-equal: ^3.1.3 + nanoid: ^3.3.11 + use-latest-callback: ^0.2.4 + peerDependencies: + react: ">= 18.2.0" + react-native: "*" + checksum: f0caa70f777c32861ce23a834d3afe6891c7829016649bf1491ba6b540fd4443dd6c5e6d8b44f58b92efed6074ea986e04b88ff84e9e19c09d68d9302ebd977a + languageName: node + linkType: hard + "@react-navigation/routers@npm:^6.1.9": version: 6.1.9 resolution: "@react-navigation/routers@npm:6.1.9" @@ -4202,6 +4271,15 @@ __metadata: languageName: node linkType: hard +"@react-navigation/routers@npm:^7.5.1": + version: 7.5.1 + resolution: "@react-navigation/routers@npm:7.5.1" + dependencies: + nanoid: ^3.3.11 + checksum: 49f04894f7e8b8e2c16abb96bbc1a9775a02341bb00fb9c0d9ce97f8d82613c27570921f2b854f8fd1639c29309df05345aa734124d48bdbcb5a934055b8af12 + languageName: node + linkType: hard + "@react-navigation/stack@npm:^6.3.21": version: 6.4.1 resolution: "@react-navigation/stack@npm:6.4.1" @@ -4235,55 +4313,48 @@ __metadata: languageName: node linkType: hard -"@shikijs/core@npm:1.23.0": - version: 1.23.0 - resolution: "@shikijs/core@npm:1.23.0" +"@shikijs/engine-oniguruma@npm:^3.13.0": + version: 3.13.0 + resolution: "@shikijs/engine-oniguruma@npm:3.13.0" dependencies: - "@shikijs/engine-javascript": 1.23.0 - "@shikijs/engine-oniguruma": 1.23.0 - "@shikijs/types": 1.23.0 - "@shikijs/vscode-textmate": ^9.3.0 - "@types/hast": ^3.0.4 - hast-util-to-html: ^9.0.3 - checksum: be923c4cc4732f94d17d2672dc448f764c480296bf9f9e5fa17dc3681235ba8216d7552fce05339258f01977b548e471c56c90ed14925abcc22a8217187ab8cd + "@shikijs/types": 3.13.0 + "@shikijs/vscode-textmate": ^10.0.2 + checksum: a0add08a52718270158f56b59d523f211fa56ee46d737f42108ebe8c84ba59ce2342db84c6c0f6b127767b10a0d91fb1a2925c7d6be06fa92923272c2c7c3b10 languageName: node linkType: hard -"@shikijs/engine-javascript@npm:1.23.0": - version: 1.23.0 - resolution: "@shikijs/engine-javascript@npm:1.23.0" +"@shikijs/langs@npm:^3.13.0": + version: 3.13.0 + resolution: "@shikijs/langs@npm:3.13.0" dependencies: - "@shikijs/types": 1.23.0 - "@shikijs/vscode-textmate": ^9.3.0 - oniguruma-to-es: 0.1.2 - checksum: e6cc266d7c221a5bb88ece8402ec1951e2d20a7fd075af7eff14c459f5ffe001eeb2371e2ec4e351cd637d4a99a9d96d97d2ef5d87eca924aa1c3690b9287dca + "@shikijs/types": 3.13.0 + checksum: 7eafa1b7e0399c3932a2231bb3f317c98388fab6f2a280e53370a3617e67ba2aab88d1354280c65c036feeff3344dfc7323a3f7e0e17211a385b9982bb05c90c languageName: node linkType: hard -"@shikijs/engine-oniguruma@npm:1.23.0": - version: 1.23.0 - resolution: "@shikijs/engine-oniguruma@npm:1.23.0" +"@shikijs/themes@npm:^3.13.0": + version: 3.13.0 + resolution: "@shikijs/themes@npm:3.13.0" dependencies: - "@shikijs/types": 1.23.0 - "@shikijs/vscode-textmate": ^9.3.0 - checksum: e617cb94d11f917eb931ff8532b0d6db137a0324299be0b60a0c9c9c9befa6b2f5d3ce11a2a2c5948f0dffacd8dca65cab42a9579bbda19e11b0a49644d0b75d + "@shikijs/types": 3.13.0 + checksum: 00cc605ea77e3443f1b111d44b792dd94d55ab86a5ffd5c9a040bc7e9ce40a687738fb8d41b205367cb6ae09927c97930d7804d446a9c05349c2aca266166375 languageName: node linkType: hard -"@shikijs/types@npm:1.23.0": - version: 1.23.0 - resolution: "@shikijs/types@npm:1.23.0" +"@shikijs/types@npm:3.13.0, @shikijs/types@npm:^3.13.0": + version: 3.13.0 + resolution: "@shikijs/types@npm:3.13.0" dependencies: - "@shikijs/vscode-textmate": ^9.3.0 + "@shikijs/vscode-textmate": ^10.0.2 "@types/hast": ^3.0.4 - checksum: af6f9296c14dc8656fee4bbce420fd8da682f97a0300fed3bb8546af7fb375bb8144fc14be5f8d5c0698ab956044ca3dea475cd7a3285b8a66f56a01d56bf69f + checksum: 524ddea254f5c7dddd7a365d681e16376129ae80a2d8ce748b486644a17f0ebfde6fab5eefadb3f9008d718e987195c183e19bdeac9605413e5a22cd5b152fa9 languageName: node linkType: hard -"@shikijs/vscode-textmate@npm:^9.3.0": - version: 9.3.0 - resolution: "@shikijs/vscode-textmate@npm:9.3.0" - checksum: 6635b4f41f958db502545d7c55cb51b803986cff38402963404cfd725449dc6ad760b6c342e916fc620f998b67baa23bea52299d379f20837fc47af9271d601d +"@shikijs/vscode-textmate@npm:^10.0.2": + version: 10.0.2 + resolution: "@shikijs/vscode-textmate@npm:10.0.2" + checksum: e68f27a3dc1584d7414b8acafb9c177a2181eb0b06ef178d8609142f49d28d85fd10ab129affde40a45a7d9238997e457ce47931b3a3815980e2b98b2d26724c languageName: node linkType: hard @@ -4317,6 +4388,13 @@ __metadata: languageName: node linkType: hard +"@sinclair/typebox@npm:^0.34.0": + version: 0.34.41 + resolution: "@sinclair/typebox@npm:0.34.41" + checksum: dbcfdc55caef47ef5b728c2bc6979e50d00ee943b63eaaf604551be9a039187cdd256d810b790e61fdf63131df54b236149aef739d83bfe9a594a9863ac28115 + languageName: node + linkType: hard + "@sindresorhus/merge-streams@npm:^2.1.0": version: 2.3.0 resolution: "@sindresorhus/merge-streams@npm:2.3.0" @@ -4359,22 +4437,23 @@ __metadata: languageName: node linkType: hard -"@testing-library/react-native@npm:^12.7.2": - version: 12.7.2 - resolution: "@testing-library/react-native@npm:12.7.2" +"@testing-library/react-native@npm:^13.3.3": + version: 13.3.3 + resolution: "@testing-library/react-native@npm:13.3.3" dependencies: - jest-matcher-utils: ^29.7.0 - pretty-format: ^29.7.0 + jest-matcher-utils: ^30.0.5 + picocolors: ^1.1.1 + pretty-format: ^30.0.5 redent: ^3.0.0 peerDependencies: - jest: ">=28.0.0" - react: ">=16.8.0" - react-native: ">=0.59" - react-test-renderer: ">=16.8.0" + jest: ">=29.0.0" + react: ">=18.2.0" + react-native: ">=0.71" + react-test-renderer: ">=18.2.0" peerDependenciesMeta: jest: optional: true - checksum: 7e3d8ab7d549823fcf438c17353e6c40386da88bbb1edfbd0747282a28c673597be27fdc2fa1f3a7d8786b77c72bb2e37f67ad2c9134225e9b68db97838f77e2 + checksum: 5688918384ce834e3667a56b72c8b776a2f9a5afae0a2738e7d0077f342b3ade7eca628cbe122943201caee75f3718379ef7b3ca00cd50c4ee607b4131d09505 languageName: node linkType: hard @@ -4451,7 +4530,7 @@ __metadata: languageName: node linkType: hard -"@types/hast@npm:^3.0.0, @types/hast@npm:^3.0.4": +"@types/hast@npm:^3.0.4": version: 3.0.4 resolution: "@types/hast@npm:3.0.4" dependencies: @@ -4512,15 +4591,6 @@ __metadata: languageName: node linkType: hard -"@types/mdast@npm:^4.0.0": - version: 4.0.4 - resolution: "@types/mdast@npm:4.0.4" - dependencies: - "@types/unist": "*" - checksum: 20c4e9574cc409db662a35cba52b068b91eb696b3049e94321219d47d34c8ccc99a142be5c76c80a538b612457b03586bc2f6b727a3e9e7530f4c8568f6282ee - languageName: node - linkType: hard - "@types/minimist@npm:^1.2.2": version: 1.2.5 resolution: "@types/minimist@npm:1.2.5" @@ -4602,7 +4672,7 @@ __metadata: languageName: node linkType: hard -"@types/unist@npm:*, @types/unist@npm:^3.0.0": +"@types/unist@npm:*": version: 3.0.3 resolution: "@types/unist@npm:3.0.3" checksum: 96e6453da9e075aaef1dc22482463898198acdc1eeb99b465e65e34303e2ec1e3b1ed4469a9118275ec284dc98019f63c3f5d49422f0e4ac707e5ab90fb3b71a @@ -4931,7 +5001,7 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.0.0, @ungap/structured-clone@npm:^1.2.0": +"@ungap/structured-clone@npm:^1.2.0": version: 1.2.0 resolution: "@ungap/structured-clone@npm:1.2.0" checksum: 4f656b7b4672f2ce6e272f2427d8b0824ed11546a601d8d5412b9d7704e83db38a8d9f402ecdf2b9063fc164af842ad0ec4a55819f621ed7e7ea4d1efcc74524 @@ -5155,7 +5225,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^5.0.0": +"ansi-styles@npm:^5.0.0, ansi-styles@npm:^5.2.0": version: 5.2.0 resolution: "ansi-styles@npm:5.2.0" checksum: d7f4e97ce0623aea6bc0d90dcd28881ee04cba06c570b97fd3391bd7a268eedfd9d5e2dd4fdcbdd82b8105df5faf6f24aaedc08eaf3da898e702db5948f63469 @@ -5839,13 +5909,6 @@ __metadata: languageName: node linkType: hard -"ccount@npm:^2.0.0": - version: 2.0.1 - resolution: "ccount@npm:2.0.1" - checksum: 48193dada54c9e260e0acf57fc16171a225305548f9ad20d5471e0f7a8c026aedd8747091dccb0d900cde7df4e4ddbd235df0d8de4a64c71b12f0d3303eeafd4 - languageName: node - linkType: hard - "chalk@npm:5.4.1, chalk@npm:^5.3.0": version: 5.4.1 resolution: "chalk@npm:5.4.1" @@ -5881,20 +5944,6 @@ __metadata: languageName: node linkType: hard -"character-entities-html4@npm:^2.0.0": - version: 2.1.0 - resolution: "character-entities-html4@npm:2.1.0" - checksum: 7034aa7c7fa90309667f6dd50499c8a760c3d3a6fb159adb4e0bada0107d194551cdbad0714302f62d06ce4ed68565c8c2e15fdef2e8f8764eb63fa92b34b11d - languageName: node - linkType: hard - -"character-entities-legacy@npm:^3.0.0": - version: 3.0.0 - resolution: "character-entities-legacy@npm:3.0.0" - checksum: 7582af055cb488b626d364b7d7a4e46b06abd526fb63c0e4eb35bcb9c9799cc4f76b39f34fdccef2d1174ac95e53e9ab355aae83227c1a2505877893fce77731 - languageName: node - linkType: hard - "chardet@npm:^0.7.0": version: 0.7.0 resolution: "chardet@npm:0.7.0" @@ -6133,13 +6182,6 @@ __metadata: languageName: node linkType: hard -"comma-separated-tokens@npm:^2.0.0": - version: 2.0.3 - resolution: "comma-separated-tokens@npm:2.0.3" - checksum: e3bf9e0332a5c45f49b90e79bcdb4a7a85f28d6a6f0876a94f1bb9b2bfbdbbb9292aac50e1e742d8c0db1e62a0229a106f57917e2d067fca951d81737651700d - languageName: node - linkType: hard - "command-exists@npm:^1.2.8": version: 1.2.9 resolution: "command-exists@npm:1.2.9" @@ -6865,13 +6907,6 @@ __metadata: languageName: node linkType: hard -"dequal@npm:^2.0.0": - version: 2.0.3 - resolution: "dequal@npm:2.0.3" - checksum: 8679b850e1a3d0ebbc46ee780d5df7b478c23f335887464023a631d1b9af051ad4a6595a44220f9ff8ff95a8ddccf019b5ad778a976fd7bbf77383d36f412f90 - languageName: node - linkType: hard - "destroy@npm:1.2.0": version: 1.2.0 resolution: "destroy@npm:1.2.0" @@ -6886,15 +6921,6 @@ __metadata: languageName: node linkType: hard -"devlop@npm:^1.0.0": - version: 1.1.0 - resolution: "devlop@npm:1.1.0" - dependencies: - dequal: ^2.0.0 - checksum: d2ff650bac0bb6ef08c48f3ba98640bb5fec5cce81e9957eb620408d1bab1204d382a45b785c6b3314dc867bb0684936b84c6867820da6db97cbb5d3c15dd185 - languageName: node - linkType: hard - "diff-sequences@npm:^29.6.3": version: 29.6.3 resolution: "diff-sequences@npm:29.6.3" @@ -6996,13 +7022,6 @@ __metadata: languageName: node linkType: hard -"emoji-regex-xs@npm:^1.0.0": - version: 1.0.0 - resolution: "emoji-regex-xs@npm:1.0.0" - checksum: c33be159da769836f83281f2802d90169093ebf3c2c1643d6801d891c53beac5ef785fd8279f9b02fa6dc6c47c367818e076949f1e13bd1b3f921b416de4cbea - languageName: node - linkType: hard - "emoji-regex@npm:^10.3.0": version: 10.4.0 resolution: "emoji-regex@npm:10.4.0" @@ -8462,34 +8481,6 @@ __metadata: languageName: node linkType: hard -"hast-util-to-html@npm:^9.0.3": - version: 9.0.3 - resolution: "hast-util-to-html@npm:9.0.3" - dependencies: - "@types/hast": ^3.0.0 - "@types/unist": ^3.0.0 - ccount: ^2.0.0 - comma-separated-tokens: ^2.0.0 - hast-util-whitespace: ^3.0.0 - html-void-elements: ^3.0.0 - mdast-util-to-hast: ^13.0.0 - property-information: ^6.0.0 - space-separated-tokens: ^2.0.0 - stringify-entities: ^4.0.0 - zwitch: ^2.0.4 - checksum: e0b6f6fdba5f0075a593a0b1f0807c11a24ccfcb8403caea7d71eaffd7a958c995917e69fccc9055fbfa05a8b9d6b1cab306200bb82ad143530fdf4f33dcc311 - languageName: node - linkType: hard - -"hast-util-whitespace@npm:^3.0.0": - version: 3.0.0 - resolution: "hast-util-whitespace@npm:3.0.0" - dependencies: - "@types/hast": ^3.0.0 - checksum: 41d93ccce218ba935dc3c12acdf586193c35069489c8c8f50c2aa824c00dec94a3c78b03d1db40fa75381942a189161922e4b7bca700b3a2cc779634c351a1e4 - languageName: node - linkType: hard - "hermes-estree@npm:0.23.1": version: 0.23.1 resolution: "hermes-estree@npm:0.23.1" @@ -8579,13 +8570,6 @@ __metadata: languageName: node linkType: hard -"html-void-elements@npm:^3.0.0": - version: 3.0.0 - resolution: "html-void-elements@npm:3.0.0" - checksum: 59be397525465a7489028afa064c55763d9cccd1d7d9f630cca47137317f0e897a9ca26cef7e745e7cff1abc44260cfa407742b243a54261dfacd42230e94fce - languageName: node - linkType: hard - "http-cache-semantics@npm:^4.1.1": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" @@ -9607,6 +9591,18 @@ __metadata: languageName: node linkType: hard +"jest-diff@npm:30.2.0": + version: 30.2.0 + resolution: "jest-diff@npm:30.2.0" + dependencies: + "@jest/diff-sequences": 30.0.1 + "@jest/get-type": 30.1.0 + chalk: ^4.1.2 + pretty-format: 30.2.0 + checksum: 62fd17d3174316bf0140c2d342ac5ad84574763fa78fc4dd4e5ee605f121699033c9bfb7507ba8f1c5cc7fa95539a19abab13d3909a5aec1b447ab14d03c5386 + languageName: node + linkType: hard + "jest-diff@npm:^29.0.1, jest-diff@npm:^29.7.0": version: 29.7.0 resolution: "jest-diff@npm:29.7.0" @@ -9707,6 +9703,18 @@ __metadata: languageName: node linkType: hard +"jest-matcher-utils@npm:^30.0.5": + version: 30.2.0 + resolution: "jest-matcher-utils@npm:30.2.0" + dependencies: + "@jest/get-type": 30.1.0 + chalk: ^4.1.2 + jest-diff: 30.2.0 + pretty-format: 30.2.0 + checksum: 33154f3fc10b19608af7f8bc91eec129f9aba0a3d89f74ffbae659159c8e2dea69c85ef1d742b1d5dd6a8be57503d77d37351edc86ce9ef3f57ecc8585e0b154 + languageName: node + linkType: hard + "jest-message-util@npm:^29.7.0": version: 29.7.0 resolution: "jest-message-util@npm:29.7.0" @@ -10551,23 +10559,6 @@ __metadata: languageName: node linkType: hard -"mdast-util-to-hast@npm:^13.0.0": - version: 13.2.0 - resolution: "mdast-util-to-hast@npm:13.2.0" - dependencies: - "@types/hast": ^3.0.0 - "@types/mdast": ^4.0.0 - "@ungap/structured-clone": ^1.0.0 - devlop: ^1.0.0 - micromark-util-sanitize-uri: ^2.0.0 - trim-lines: ^3.0.0 - unist-util-position: ^5.0.0 - unist-util-visit: ^5.0.0 - vfile: ^6.0.0 - checksum: 7e5231ff3d4e35e1421908437577fd5098141f64918ff5cc8a0f7a8a76c5407f7a3ee88d75f7a1f7afb763989c9f357475fa0ba8296c00aaff1e940098fe86a6 - languageName: node - linkType: hard - "mdurl@npm:^2.0.0": version: 2.0.0 resolution: "mdurl@npm:2.0.0" @@ -11094,48 +11085,6 @@ __metadata: languageName: node linkType: hard -"micromark-util-character@npm:^2.0.0": - version: 2.1.1 - resolution: "micromark-util-character@npm:2.1.1" - dependencies: - micromark-util-symbol: ^2.0.0 - micromark-util-types: ^2.0.0 - checksum: e9e409efe4f2596acd44587e8591b722bfc041c1577e8fe0d9c007a4776fb800f9b3637a22862ad2ba9489f4bdf72bb547fce5767dbbfe0a5e6760e2a21c6495 - languageName: node - linkType: hard - -"micromark-util-encode@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-util-encode@npm:2.0.1" - checksum: be890b98e78dd0cdd953a313f4148c4692cc2fb05533e56fef5f421287d3c08feee38ca679f318e740530791fc251bfe8c80efa926fcceb4419b269c9343d226 - languageName: node - linkType: hard - -"micromark-util-sanitize-uri@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-util-sanitize-uri@npm:2.0.1" - dependencies: - micromark-util-character: ^2.0.0 - micromark-util-encode: ^2.0.0 - micromark-util-symbol: ^2.0.0 - checksum: d01517840c17de67aaa0b0f03bfe05fac8a41d99723cd8ce16c62f6810e99cd3695364a34c335485018e5e2c00e69031744630a1b85c6868aa2f2ca1b36daa2f - languageName: node - linkType: hard - -"micromark-util-symbol@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-util-symbol@npm:2.0.1" - checksum: fb7346950550bc85a55793dda94a8b3cb3abc068dbd7570d1162db7aee803411d06c0a5de4ae59cd945f46143bdeadd4bba02a02248fa0d18cc577babaa00044 - languageName: node - linkType: hard - -"micromark-util-types@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-util-types@npm:2.0.1" - checksum: 630aac466628a360962f478f69421599c53ff8b3080765201b7be3b3a4be7f4c5b73632b9a6dd426b9e06035353c18acccee637d6c43d9b0bf1c31111bbb88a7 - languageName: node - linkType: hard - "micromatch@npm:^4.0.4": version: 4.0.8 resolution: "micromatch@npm:4.0.8" @@ -11399,6 +11348,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 3be20d8866a57a6b6d218e82549711c8352ed969f9ab3c45379da28f405363ad4c9aeb0b39e9abc101a529ca65a72ff9502b00bf74a912c4b64a9d62dfd26c29 + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -11710,17 +11668,6 @@ __metadata: languageName: node linkType: hard -"oniguruma-to-es@npm:0.1.2": - version: 0.1.2 - resolution: "oniguruma-to-es@npm:0.1.2" - dependencies: - emoji-regex-xs: ^1.0.0 - regex: ^4.4.0 - regex-recursion: ^4.1.0 - checksum: 30d0a1396dbcb52cd7539c02e074e7b8e3d01ee96046123d09abfaa903f09a3340ff611c88b651a951e66f5237ddc0dfc1119fc553124098f4a2ca5632258103 - languageName: node - linkType: hard - "open@npm:10.1.0": version: 10.1.0 resolution: "open@npm:10.1.0" @@ -12168,6 +12115,17 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:30.2.0, pretty-format@npm:^30.0.5": + version: 30.2.0 + resolution: "pretty-format@npm:30.2.0" + dependencies: + "@jest/schemas": 30.0.5 + ansi-styles: ^5.2.0 + react-is: ^18.3.1 + checksum: 4c54f5ed8bcf450df9d5d70726c3373f26896845a9704f5a4a835913dacea794fabb5de4ab19fabb0d867de496f9fc8bf854ccdb661c45af334026308557d622 + languageName: node + linkType: hard + "pretty-format@npm:^26.6.2": version: 26.6.2 resolution: "pretty-format@npm:26.6.2" @@ -12245,13 +12203,6 @@ __metadata: languageName: node linkType: hard -"property-information@npm:^6.0.0": - version: 6.5.0 - resolution: "property-information@npm:6.5.0" - checksum: 6e55664e2f64083b715011e5bafaa1e694faf36986c235b0907e95d09259cc37c38382e3cc94a4c3f56366e05336443db12c8a0f0968a8c0a1b1416eebfc8f53 - languageName: node - linkType: hard - "proto-list@npm:~1.2.1": version: 1.2.4 resolution: "proto-list@npm:1.2.4" @@ -12439,7 +12390,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.0.0": +"react-is@npm:^18.0.0, react-is@npm:^18.3.1": version: 18.3.1 resolution: "react-is@npm:18.3.1" checksum: e20fe84c86ff172fc8d898251b7cc2c43645d108bf96d0b8edf39b98f9a2cae97b40520ee7ed8ee0085ccc94736c4886294456033304151c3f94978cec03df21 @@ -12453,6 +12404,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^19.1.0": + version: 19.1.1 + resolution: "react-is@npm:19.1.1" + checksum: e60ed01c27fe4d22b08f8a31f18831d144a801d08a909ca31fb1d02721b4f4cde0759148d6341f660a4d6ce54a78e22b8b39520b67e2e76254e583885868ab43 + languageName: node + linkType: hard + "react-native-builder-bob@npm:^0.30.2": version: 0.30.2 resolution: "react-native-builder-bob@npm:0.30.2" @@ -12542,6 +12500,20 @@ __metadata: languageName: node linkType: hard +"react-native-gesture-handler@npm:^2.26.0": + version: 2.28.0 + resolution: "react-native-gesture-handler@npm:2.28.0" + dependencies: + "@egjs/hammerjs": ^2.0.17 + hoist-non-react-statics: ^3.3.0 + invariant: ^2.2.4 + peerDependencies: + react: "*" + react-native: "*" + checksum: 7bcd7db784b12565fdd5916bbebc2d3511a63159ca553d33e430008940ba7d209f1e85ef02968a920ed19c414fabe7d2c18cc0e967dd4889aae266788562d1e9 + languageName: node + linkType: hard + "react-native-is-edge-to-edge@npm:^1.1.7": version: 1.1.7 resolution: "react-native-is-edge-to-edge@npm:1.1.7" @@ -12552,6 +12524,16 @@ __metadata: languageName: node linkType: hard +"react-native-is-edge-to-edge@npm:^1.2.1": + version: 1.2.1 + resolution: "react-native-is-edge-to-edge@npm:1.2.1" + peerDependencies: + react: "*" + react-native: "*" + checksum: 8fb6d8ab7b953c7d7cec8c987cef24f1c5348a293a85cb49c7c53b54ef110c0ca746736ae730e297603c8c76020df912e93915fb17518c4f2f91143757177aba + languageName: node + linkType: hard + "react-native-safe-area-context@npm:^5.1.0": version: 5.4.1 resolution: "react-native-safe-area-context@npm:5.4.1" @@ -12562,6 +12544,30 @@ __metadata: languageName: node linkType: hard +"react-native-safe-area-context@npm:^5.4.0": + version: 5.6.1 + resolution: "react-native-safe-area-context@npm:5.6.1" + peerDependencies: + react: "*" + react-native: "*" + checksum: f346615d5f8f26c0c8459d29c149ea3f66684b8ae79cea6fd48d118d039851a69a92955d67b455d0e7ab46639155c4357ebf58ec1859b2377ee459e2a04b602b + languageName: node + linkType: hard + +"react-native-screens@npm:^4.10.0": + version: 4.16.0 + resolution: "react-native-screens@npm:4.16.0" + dependencies: + react-freeze: ^1.0.0 + react-native-is-edge-to-edge: ^1.2.1 + warn-once: ^0.1.0 + peerDependencies: + react: "*" + react-native: "*" + checksum: 71bebbead1d8f886b80b70cf9d69b0179e035fb425fae84fbcbb2930167220cb90c2ee70b26d3fd94f940fa3e6ce325b0ec2e283d039d5abb29bf6898c58e485 + languageName: node + linkType: hard + "react-native-screens@npm:^4.9.1": version: 4.11.1 resolution: "react-native-screens@npm:4.11.1" @@ -12604,6 +12610,19 @@ __metadata: languageName: node linkType: hard +"react-native-webview@npm:^13.14.1": + version: 13.16.0 + resolution: "react-native-webview@npm:13.16.0" + dependencies: + escape-string-regexp: ^4.0.0 + invariant: 2.2.4 + peerDependencies: + react: "*" + react-native: "*" + checksum: a6fefc1bc977c1d121057c91696edb08ca170421a6d6ad5a9bcda279a20eca84aaaf553eca3a8e51b621e22fd473afa8bc994888807fb7db154eb9d7acb87928 + languageName: node + linkType: hard + "react-native@npm:0.79.3": version: 0.79.3 resolution: "react-native@npm:0.79.3" @@ -12838,29 +12857,6 @@ __metadata: languageName: node linkType: hard -"regex-recursion@npm:^4.1.0": - version: 4.2.1 - resolution: "regex-recursion@npm:4.2.1" - dependencies: - regex-utilities: ^2.3.0 - checksum: 4f39d856e76d39fca70360f6700d60dc495d30ef217070779e13372e1cc86b46d8b039245214d14fcc4a382fab23ac65024f51f623ab68d8ec65f123fe20db29 - languageName: node - linkType: hard - -"regex-utilities@npm:^2.3.0": - version: 2.3.0 - resolution: "regex-utilities@npm:2.3.0" - checksum: 41408777df45cefe1b276281030213235aa1143809c4c10eb5573d2cc27ff2c4aa746c6f4d4c235e3d2f4830eff76b28906ce82fbe72895beca8e15204c2da51 - languageName: node - linkType: hard - -"regex@npm:^4.4.0": - version: 4.4.0 - resolution: "regex@npm:4.4.0" - checksum: 3ee7e223949343cb081b4117a64460a0c98bd4378285075605f3c32d71e2a5cabd9296b18df0468573b2f8038a25607ac36087ae6d6b6a1d9e118be5c214aba7 - languageName: node - linkType: hard - "regexp.prototype.flags@npm:^1.5.2": version: 1.5.2 resolution: "regexp.prototype.flags@npm:1.5.2" @@ -13410,20 +13406,6 @@ __metadata: languageName: node linkType: hard -"shiki@npm:^1.16.2": - version: 1.23.0 - resolution: "shiki@npm:1.23.0" - dependencies: - "@shikijs/core": 1.23.0 - "@shikijs/engine-javascript": 1.23.0 - "@shikijs/engine-oniguruma": 1.23.0 - "@shikijs/types": 1.23.0 - "@shikijs/vscode-textmate": ^9.3.0 - "@types/hast": ^3.0.4 - checksum: f23c2e86aedc0aa9745f2e29b796cee2729ecd4a2c2a95aacd38027e653a9e9489524745d4e1b4e4d26f1a87a158eec1eaeb797f2699a6afeb38baf563c145bd - languageName: node - linkType: hard - "side-channel@npm:^1.0.4, side-channel@npm:^1.0.6": version: 1.0.6 resolution: "side-channel@npm:1.0.6" @@ -13571,13 +13553,6 @@ __metadata: languageName: node linkType: hard -"space-separated-tokens@npm:^2.0.0": - version: 2.0.2 - resolution: "space-separated-tokens@npm:2.0.2" - checksum: 202e97d7ca1ba0758a0aa4fe226ff98142073bcceeff2da3aad037968878552c3bbce3b3231970025375bbba5aee00c5b8206eda408da837ab2dc9c0f26be990 - languageName: node - linkType: hard - "spdx-correct@npm:^3.0.0": version: 3.2.0 resolution: "spdx-correct@npm:3.2.0" @@ -13834,16 +13809,6 @@ __metadata: languageName: node linkType: hard -"stringify-entities@npm:^4.0.0": - version: 4.0.4 - resolution: "stringify-entities@npm:4.0.4" - dependencies: - character-entities-html4: ^2.0.0 - character-entities-legacy: ^3.0.0 - checksum: ac1344ef211eacf6cf0a0a8feaf96f9c36083835b406560d2c6ff5a87406a41b13f2f0b4c570a3b391f465121c4fd6822b863ffb197e8c0601a64097862cc5b5 - languageName: node - linkType: hard - "strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": version: 6.0.1 resolution: "strip-ansi@npm:6.0.1" @@ -14105,13 +14070,6 @@ __metadata: languageName: node linkType: hard -"trim-lines@npm:^3.0.0": - version: 3.0.1 - resolution: "trim-lines@npm:3.0.1" - checksum: e241da104682a0e0d807222cc1496b92e716af4db7a002f4aeff33ae6a0024fef93165d49eab11aa07c71e1347c42d46563f91dfaa4d3fb945aa535cdead53ed - languageName: node - linkType: hard - "trim-newlines@npm:^4.0.2": version: 4.1.1 resolution: "trim-newlines@npm:4.1.1" @@ -14371,20 +14329,20 @@ __metadata: languageName: node linkType: hard -"typedoc@npm:^0.26.11": - version: 0.26.11 - resolution: "typedoc@npm:0.26.11" +"typedoc@npm:^0.28.13": + version: 0.28.13 + resolution: "typedoc@npm:0.28.13" dependencies: + "@gerrit0/mini-shiki": ^3.12.0 lunr: ^2.3.9 markdown-it: ^14.1.0 minimatch: ^9.0.5 - shiki: ^1.16.2 - yaml: ^2.5.1 + yaml: ^2.8.1 peerDependencies: - typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x + typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x bin: typedoc: bin/typedoc - checksum: 9ed037ec3c10e487268078768eb68c5e68769343f71605c772c022b1b55445d34e17fba48e70ec49f535fbd27ab33ce58211f340103fc161c8367d4c6731bc11 + checksum: 238b567661d4118eaf1bc61696ce2129dc0f0d4bd9b0928942bdd40ab9165df842143a04bc1fd8c7c1c2a8978ad2b48118f4bfcd753be322476568a8cc27e355 languageName: node linkType: hard @@ -14506,54 +14464,6 @@ __metadata: languageName: node linkType: hard -"unist-util-is@npm:^6.0.0": - version: 6.0.0 - resolution: "unist-util-is@npm:6.0.0" - dependencies: - "@types/unist": ^3.0.0 - checksum: f630a925126594af9993b091cf807b86811371e465b5049a6283e08537d3e6ba0f7e248e1e7dab52cfe33f9002606acef093441137181b327f6fe504884b20e2 - languageName: node - linkType: hard - -"unist-util-position@npm:^5.0.0": - version: 5.0.0 - resolution: "unist-util-position@npm:5.0.0" - dependencies: - "@types/unist": ^3.0.0 - checksum: f89b27989b19f07878de9579cd8db2aa0194c8360db69e2c99bd2124a480d79c08f04b73a64daf01a8fb3af7cba65ff4b45a0b978ca243226084ad5f5d441dde - languageName: node - linkType: hard - -"unist-util-stringify-position@npm:^4.0.0": - version: 4.0.0 - resolution: "unist-util-stringify-position@npm:4.0.0" - dependencies: - "@types/unist": ^3.0.0 - checksum: e2e7aee4b92ddb64d314b4ac89eef7a46e4c829cbd3ee4aee516d100772b490eb6b4974f653ba0717a0071ca6ea0770bf22b0a2ea62c65fcba1d071285e96324 - languageName: node - linkType: hard - -"unist-util-visit-parents@npm:^6.0.0": - version: 6.0.1 - resolution: "unist-util-visit-parents@npm:6.0.1" - dependencies: - "@types/unist": ^3.0.0 - unist-util-is: ^6.0.0 - checksum: 08927647c579f63b91aafcbec9966dc4a7d0af1e5e26fc69f4e3e6a01215084835a2321b06f3cbe7bf7914a852830fc1439f0fc3d7153d8804ac3ef851ddfa20 - languageName: node - linkType: hard - -"unist-util-visit@npm:^5.0.0": - version: 5.0.0 - resolution: "unist-util-visit@npm:5.0.0" - dependencies: - "@types/unist": ^3.0.0 - unist-util-is: ^6.0.0 - unist-util-visit-parents: ^6.0.0 - checksum: 9ec42e618e7e5d0202f3c191cd30791b51641285732767ee2e6bcd035931032e3c1b29093f4d7fd0c79175bbc1f26f24f26ee49770d32be76f8730a652a857e6 - languageName: node - linkType: hard - "universal-user-agent@npm:^6.0.0": version: 6.0.1 resolution: "universal-user-agent@npm:6.0.1" @@ -14653,6 +14563,24 @@ __metadata: languageName: node linkType: hard +"use-latest-callback@npm:^0.2.4": + version: 0.2.4 + resolution: "use-latest-callback@npm:0.2.4" + peerDependencies: + react: ">=16.8" + checksum: 60c3a6b1b6567e1794f9e48cd86b8cde8a149485cc2fed60570f69ec3b157f6812e0ff0a877f0b971592fb9254b1363cc21c120fd1fc993b1dad1406c69211df + languageName: node + linkType: hard + +"use-sync-external-store@npm:^1.5.0": + version: 1.5.0 + resolution: "use-sync-external-store@npm:1.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 5e639c9273200adb6985b512c96a3a02c458bc8ca1a72e91da9cdc6426144fc6538dca434b0f99b28fb1baabc82e1c383ba7900b25ccdcb43758fb058dc66c34 + languageName: node + linkType: hard + "util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" @@ -14695,26 +14623,6 @@ __metadata: languageName: node linkType: hard -"vfile-message@npm:^4.0.0": - version: 4.0.2 - resolution: "vfile-message@npm:4.0.2" - dependencies: - "@types/unist": ^3.0.0 - unist-util-stringify-position: ^4.0.0 - checksum: 964e7e119f4c0e0270fc269119c41c96da20afa01acb7c9809a88365c8e0c64aa692fafbd952669382b978002ecd7ad31ef4446d85e8a22cdb62f6df20186c2d - languageName: node - linkType: hard - -"vfile@npm:^6.0.0": - version: 6.0.3 - resolution: "vfile@npm:6.0.3" - dependencies: - "@types/unist": ^3.0.0 - vfile-message: ^4.0.0 - checksum: 152b6729be1af70df723efb65c1a1170fd483d41086557da3651eea69a1dd1f0c22ea4344834d56d30734b9185bcab63e22edc81d3f0e9bed8aa4660d61080af - languageName: node - linkType: hard - "vlq@npm:^1.0.0": version: 1.0.1 resolution: "vlq@npm:1.0.1" @@ -15023,12 +14931,12 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.5.1": - version: 2.6.0 - resolution: "yaml@npm:2.6.0" +"yaml@npm:^2.8.1": + version: 2.8.1 + resolution: "yaml@npm:2.8.1" bin: yaml: bin.mjs - checksum: e5e74fd75e01bde2c09333d529af9fbb5928c5f7f01bfdefdcb2bf753d4ef489a45cab4deac01c9448f55ca27e691612b81fe3c3a59bb8cb5b0069da0f92cf0b + checksum: 35b46150d48bc1da2fd5b1521a48a4fa36d68deaabe496f3c3fa9646d5796b6b974f3930a02c4b5aee6c85c860d7d7f79009416724465e835f40b87898c36de4 languageName: node linkType: hard @@ -15125,10 +15033,3 @@ __metadata: checksum: 1c474d4b30a8c130e679279c5c2c33a0d48eba9684ffa0252cc64846c121fb56c3f25457fef902edbe1e2d7a7872130073a9fc8e795299d75e13fa3f5f548f1b languageName: node linkType: hard - -"zwitch@npm:^2.0.4": - version: 2.0.4 - resolution: "zwitch@npm:2.0.4" - checksum: f22ec5fc2d5f02c423c93d35cdfa83573a3a3bd98c66b927c368ea4d0e7252a500df2a90a6b45522be536a96a73404393c958e945fdba95e6832c200791702b6 - languageName: node - linkType: hard