From 22d042d7fd3709fbbf9963ab9ae5f4908a1298e5 Mon Sep 17 00:00:00 2001 From: Chris F <5827964+cshfang@users.noreply.github.com> Date: Tue, 19 Mar 2024 11:57:53 -0700 Subject: [PATCH] chore: Refactor setConflictHandler to avoid directly exporting let (#13143) Co-authored-by: erinleigh90 <106691284+erinleigh90@users.noreply.github.com> --- .../pinpoint/apis/dispatchEvent.test.ts | 12 ++++- .../pinpoint/apis/setConflictHandler.test.ts | 34 ++++-------- .../utils/conflictHandlerManager.test.ts | 37 +++++++++++++ packages/notifications/jest.config.js | 2 +- .../providers/pinpoint/apis/dispatchEvent.ts | 25 +++++---- .../pinpoint/apis/setConflictHandler.ts | 40 +++----------- .../pinpoint/utils/conflictHandlerManager.ts | 52 +++++++++++++++++++ .../providers/pinpoint/utils/index.ts | 4 ++ 8 files changed, 134 insertions(+), 72 deletions(-) create mode 100644 packages/notifications/__tests__/inAppMessaging/providers/pinpoint/utils/conflictHandlerManager.test.ts create mode 100644 packages/notifications/src/inAppMessaging/providers/pinpoint/utils/conflictHandlerManager.ts diff --git a/packages/notifications/__tests__/inAppMessaging/providers/pinpoint/apis/dispatchEvent.test.ts b/packages/notifications/__tests__/inAppMessaging/providers/pinpoint/apis/dispatchEvent.test.ts index 5de2bdfb5cc..4a4552d89a0 100644 --- a/packages/notifications/__tests__/inAppMessaging/providers/pinpoint/apis/dispatchEvent.test.ts +++ b/packages/notifications/__tests__/inAppMessaging/providers/pinpoint/apis/dispatchEvent.test.ts @@ -6,8 +6,12 @@ import { dispatchEvent, initializeInAppMessaging, } from '../../../../../src/inAppMessaging/providers/pinpoint/apis'; -import { processInAppMessages } from '../../../../../src/inAppMessaging/providers/pinpoint/utils'; import { + getConflictHandler, + processInAppMessages, +} from '../../../../../src/inAppMessaging/providers/pinpoint/utils'; +import { + closestExpiryMessage, inAppMessages, simpleInAppMessages, simpleInAppMessagingEvent, @@ -20,6 +24,7 @@ jest.mock('../../../../../src/inAppMessaging/providers/pinpoint/utils'); jest.mock('../../../../../src/eventListeners'); const mockDefaultStorage = defaultStorage as jest.Mocked; +const mockGetConflictHandler = getConflictHandler as jest.Mock; const mockNotifyEventListeners = notifyEventListeners as jest.Mock; const mockProcessInAppMessages = processInAppMessages as jest.Mock; @@ -28,6 +33,10 @@ describe('dispatchEvent', () => { initializeInAppMessaging(); }); beforeEach(() => { + mockGetConflictHandler.mockReturnValue(() => inAppMessages[0]); + }); + afterEach(() => { + mockGetConflictHandler.mockReset(); mockDefaultStorage.setItem.mockClear(); mockNotifyEventListeners.mockClear(); }); @@ -49,6 +58,7 @@ describe('dispatchEvent', () => { }); it('handles conflicts through default conflict handler', async () => { + mockGetConflictHandler.mockReturnValue(() => closestExpiryMessage); mockDefaultStorage.getItem.mockResolvedValueOnce( JSON.stringify(simpleInAppMessages), ); diff --git a/packages/notifications/__tests__/inAppMessaging/providers/pinpoint/apis/setConflictHandler.test.ts b/packages/notifications/__tests__/inAppMessaging/providers/pinpoint/apis/setConflictHandler.test.ts index 4c58f87bafa..09387f13671 100644 --- a/packages/notifications/__tests__/inAppMessaging/providers/pinpoint/apis/setConflictHandler.test.ts +++ b/packages/notifications/__tests__/inAppMessaging/providers/pinpoint/apis/setConflictHandler.test.ts @@ -1,49 +1,33 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { defaultStorage } from '@aws-amplify/core'; import { - dispatchEvent, initializeInAppMessaging, setConflictHandler, } from '../../../../../src/inAppMessaging/providers/pinpoint/apis'; -import { processInAppMessages } from '../../../../../src/inAppMessaging/providers/pinpoint/utils'; -import { - customHandledMessage, - inAppMessages, - simpleInAppMessagingEvent, -} from '../../../../testUtils/data'; -import { notifyEventListeners } from '../../../../../src/eventListeners'; -import { InAppMessage } from '../../../../../src/inAppMessaging/types'; +import { setConflictHandler as setConflictHandlerInteral } from '../../../../../src/inAppMessaging/providers/pinpoint/utils'; jest.mock('@aws-amplify/core'); jest.mock('@aws-amplify/core/internals/utils'); jest.mock('../../../../../src/inAppMessaging/providers/pinpoint/utils'); jest.mock('../../../../../src/eventListeners'); -const mockDefaultStorage = defaultStorage as jest.Mocked; -const mockNotifyEventListeners = notifyEventListeners as jest.Mock; -const mockProcessInAppMessages = processInAppMessages as jest.Mock; +const mockSetConflictHandlerInteral = setConflictHandlerInteral as jest.Mock; describe('setConflictHandler', () => { beforeAll(() => { initializeInAppMessaging(); }); - beforeEach(() => { - mockDefaultStorage.setItem.mockClear(); - mockNotifyEventListeners.mockClear(); + + afterEach(() => { + mockSetConflictHandlerInteral.mockClear(); }); - it('can register a custom conflict handler', async () => { - const customConflictHandler = (messages: InAppMessage[]) => - messages.find(message => message.id === 'custom-handled')!; - mockProcessInAppMessages.mockReturnValueOnce(inAppMessages); + it('can register a custom conflict handler', async () => { + const customConflictHandler = jest.fn(); setConflictHandler(customConflictHandler); - await dispatchEvent(simpleInAppMessagingEvent); - - expect(mockNotifyEventListeners).toHaveBeenCalledWith( - 'messageReceived', - customHandledMessage, + expect(mockSetConflictHandlerInteral).toHaveBeenCalledWith( + customConflictHandler, ); }); }); diff --git a/packages/notifications/__tests__/inAppMessaging/providers/pinpoint/utils/conflictHandlerManager.test.ts b/packages/notifications/__tests__/inAppMessaging/providers/pinpoint/utils/conflictHandlerManager.test.ts new file mode 100644 index 00000000000..4d4c6b3dfe8 --- /dev/null +++ b/packages/notifications/__tests__/inAppMessaging/providers/pinpoint/utils/conflictHandlerManager.test.ts @@ -0,0 +1,37 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + getConflictHandler, + setConflictHandler, +} from '../../../../../src/inAppMessaging/providers/pinpoint/utils/conflictHandlerManager'; +import { + closestExpiryMessage, + customHandledMessage, + inAppMessages, +} from '../../../../testUtils/data'; + +describe('conflictHandlerManager', () => { + const newConflictHandler = jest.fn(() => customHandledMessage); + + afterEach(() => { + newConflictHandler.mockClear(); + }); + + it('has a default conflict handler to start', () => { + const defaultConflictHandler = getConflictHandler(); + expect(defaultConflictHandler).toBeDefined(); + expect(defaultConflictHandler(inAppMessages)).toStrictEqual( + closestExpiryMessage, + ); + }); + + it('can set and get a custom conflict handler', () => { + setConflictHandler(newConflictHandler); + const customConflictHandler = getConflictHandler(); + expect(customConflictHandler).toBe(newConflictHandler); + expect(customConflictHandler(inAppMessages)).toStrictEqual( + customHandledMessage, + ); + }); +}); diff --git a/packages/notifications/jest.config.js b/packages/notifications/jest.config.js index 94bcc49fb00..76f3094c2cf 100644 --- a/packages/notifications/jest.config.js +++ b/packages/notifications/jest.config.js @@ -2,7 +2,7 @@ module.exports = { ...require('../../jest.config'), coverageThreshold: { global: { - branches: 74, + branches: 73, functions: 90, lines: 91, statements: 92, diff --git a/packages/notifications/src/inAppMessaging/providers/pinpoint/apis/dispatchEvent.ts b/packages/notifications/src/inAppMessaging/providers/pinpoint/apis/dispatchEvent.ts index 42a5340aaf2..83523db023f 100644 --- a/packages/notifications/src/inAppMessaging/providers/pinpoint/apis/dispatchEvent.ts +++ b/packages/notifications/src/inAppMessaging/providers/pinpoint/apis/dispatchEvent.ts @@ -1,20 +1,22 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { - PINPOINT_KEY_PREFIX, - STORAGE_KEY_SUFFIX, - processInAppMessages, -} from '../utils'; -import { InAppMessage } from '../../../types'; import flatten from 'lodash/flatten.js'; import { defaultStorage } from '@aws-amplify/core'; + import { notifyEventListeners } from '../../../../eventListeners'; import { assertServiceError } from '../../../errors'; +import { InAppMessage } from '../../../types'; +import { assertIsInitialized } from '../../../utils'; import { DispatchEventInput } from '../types'; +import { + PINPOINT_KEY_PREFIX, + STORAGE_KEY_SUFFIX, + getConflictHandler, + processInAppMessages, +} from '../utils'; import { syncMessages } from './syncMessages'; -import { conflictHandler, setConflictHandler } from './setConflictHandler'; -import { assertIsInitialized } from '../../../utils'; +import { setConflictHandler } from './setConflictHandler'; /** * Triggers an In-App message to be displayed. Use this after your campaigns have been synced to the device using @@ -27,13 +29,13 @@ import { assertIsInitialized } from '../../../utils'; * your own logic for resolving message conflicts. * * @param input The input object that holds the event to be dispatched. - * + * * @throws validation: {@link InAppMessagingValidationErrorCode} - Thrown when the provided parameters or library * configuration is incorrect, or if In App messaging hasn't been initialized. * @throws service exceptions - Thrown when the underlying Pinpoint service returns an error. - * + * * @returns A promise that will resolve when the operation is complete. - * + * * @example * ```ts * // Sync message before disptaching an event @@ -54,6 +56,7 @@ export async function dispatchEvent(input: DispatchEventInput): Promise { ); const flattenedMessages = flatten(messages); if (flattenedMessages.length > 0) { + const conflictHandler = getConflictHandler(); notifyEventListeners( 'messageReceived', conflictHandler(flattenedMessages), diff --git a/packages/notifications/src/inAppMessaging/providers/pinpoint/apis/setConflictHandler.ts b/packages/notifications/src/inAppMessaging/providers/pinpoint/apis/setConflictHandler.ts index ec6bdd6b91e..5efdb62f734 100644 --- a/packages/notifications/src/inAppMessaging/providers/pinpoint/apis/setConflictHandler.ts +++ b/packages/notifications/src/inAppMessaging/providers/pinpoint/apis/setConflictHandler.ts @@ -1,12 +1,9 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { InAppMessage } from '../../../types'; import { assertIsInitialized } from '../../../utils'; -import { InAppMessageConflictHandler, SetConflictHandlerInput } from '../types'; - -export let conflictHandler: InAppMessageConflictHandler = - defaultConflictHandler; +import { SetConflictHandlerInput } from '../types'; +import { setConflictHandler as setConflictHandlerInteral } from '../utils'; /** * Set a conflict handler that will be used to resolve conflicts that may emerge @@ -15,12 +12,12 @@ export let conflictHandler: InAppMessageConflictHandler = * @remark * The conflict handler is not persisted across app restarts and so must be set again before dispatching an event for * any custom handling to take effect. - * + * * @throws validation: {@link InAppMessagingValidationErrorCode} - Thrown when the provided parameters or library * configuration is incorrect, or if In App messaging hasn't been initialized. - * + * * @param input The input object that holds the conflict handler to be used. - * + * * @example * ```ts * // Sync messages before dispatching an event @@ -42,30 +39,5 @@ export let conflictHandler: InAppMessageConflictHandler = */ export function setConflictHandler(input: SetConflictHandlerInput): void { assertIsInitialized(); - conflictHandler = input; -} - -function defaultConflictHandler(messages: InAppMessage[]): InAppMessage { - // default behavior is to return the message closest to expiry - // this function assumes that messages processed by providers already filters out expired messages - const sorted = messages.sort((a, b) => { - const endDateA = a.metadata?.endDate; - const endDateB = b.metadata?.endDate; - // if both message end dates are falsy or have the same date string, treat them as equal - if (endDateA === endDateB) { - return 0; - } - // if only message A has an end date, treat it as closer to expiry - if (endDateA && !endDateB) { - return -1; - } - // if only message B has an end date, treat it as closer to expiry - if (!endDateA && endDateB) { - return 1; - } - // otherwise, compare them - return new Date(endDateA) < new Date(endDateB) ? -1 : 1; - }); - // always return the top sorted - return sorted[0]; + setConflictHandlerInteral(input); } diff --git a/packages/notifications/src/inAppMessaging/providers/pinpoint/utils/conflictHandlerManager.ts b/packages/notifications/src/inAppMessaging/providers/pinpoint/utils/conflictHandlerManager.ts new file mode 100644 index 00000000000..e6f27849b1e --- /dev/null +++ b/packages/notifications/src/inAppMessaging/providers/pinpoint/utils/conflictHandlerManager.ts @@ -0,0 +1,52 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { InAppMessage } from '../../../types'; +import { InAppMessageConflictHandler, SetConflictHandlerInput } from '../types'; + +/** + * The default conflict handler. Can be overridden by `setConflictHandler`. + */ +let conflictHandler: InAppMessageConflictHandler = ( + messages: InAppMessage[], +): InAppMessage => { + // default behavior is to return the message closest to expiry + // this function assumes that messages processed by providers already filters out expired messages + const sorted = messages.sort((a, b) => { + const endDateA = a.metadata?.endDate; + const endDateB = b.metadata?.endDate; + // if both message end dates are falsy or have the same date string, treat them as equal + if (endDateA === endDateB) { + return 0; + } + // if only message A has an end date, treat it as closer to expiry + if (endDateA && !endDateB) { + return -1; + } + // if only message B has an end date, treat it as closer to expiry + if (!endDateA && endDateB) { + return 1; + } + // otherwise, compare them + return new Date(endDateA) < new Date(endDateB) ? -1 : 1; + }); + // always return the top sorted + return sorted[0]; +}; + +/** + * Sets conflict handler. + * + * @internal + */ +export const setConflictHandler = (input: SetConflictHandlerInput): void => { + conflictHandler = input; +}; + +/** + * Returns the current conflict handler. + * + * @internal + */ +export const getConflictHandler = (): InAppMessageConflictHandler => + conflictHandler; diff --git a/packages/notifications/src/inAppMessaging/providers/pinpoint/utils/index.ts b/packages/notifications/src/inAppMessaging/providers/pinpoint/utils/index.ts index 2a41e79a9b0..32a132d4e9d 100644 --- a/packages/notifications/src/inAppMessaging/providers/pinpoint/utils/index.ts +++ b/packages/notifications/src/inAppMessaging/providers/pinpoint/utils/index.ts @@ -1,6 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +export { + getConflictHandler, + setConflictHandler, +} from './conflictHandlerManager'; export { resolveConfig } from './resolveConfig'; export { resolveCredentials } from './resolveCredentials'; export { getInAppMessagingUserAgentString } from './userAgent';