Skip to content

Commit

Permalink
chore: Refactor setConflictHandler to avoid directly exporting let (#…
Browse files Browse the repository at this point in the history
…13143)

Co-authored-by: erinleigh90 <[email protected]>
  • Loading branch information
cshfang and erinleigh90 authored Mar 19, 2024
1 parent 57feb5b commit 22d042d
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -20,6 +24,7 @@ jest.mock('../../../../../src/inAppMessaging/providers/pinpoint/utils');
jest.mock('../../../../../src/eventListeners');

const mockDefaultStorage = defaultStorage as jest.Mocked<typeof defaultStorage>;
const mockGetConflictHandler = getConflictHandler as jest.Mock;
const mockNotifyEventListeners = notifyEventListeners as jest.Mock;
const mockProcessInAppMessages = processInAppMessages as jest.Mock;

Expand All @@ -28,6 +33,10 @@ describe('dispatchEvent', () => {
initializeInAppMessaging();
});
beforeEach(() => {
mockGetConflictHandler.mockReturnValue(() => inAppMessages[0]);
});
afterEach(() => {
mockGetConflictHandler.mockReset();
mockDefaultStorage.setItem.mockClear();
mockNotifyEventListeners.mockClear();
});
Expand All @@ -49,6 +58,7 @@ describe('dispatchEvent', () => {
});

it('handles conflicts through default conflict handler', async () => {
mockGetConflictHandler.mockReturnValue(() => closestExpiryMessage);
mockDefaultStorage.getItem.mockResolvedValueOnce(
JSON.stringify(simpleInAppMessages),
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<typeof defaultStorage>;
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,
);
});
});
Original file line number Diff line number Diff line change
@@ -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,
);
});
});
2 changes: 1 addition & 1 deletion packages/notifications/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module.exports = {
...require('../../jest.config'),
coverageThreshold: {
global: {
branches: 74,
branches: 73,
functions: 90,
lines: 91,
statements: 92,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -54,6 +56,7 @@ export async function dispatchEvent(input: DispatchEventInput): Promise<void> {
);
const flattenedMessages = flatten(messages);
if (flattenedMessages.length > 0) {
const conflictHandler = getConflictHandler();
notifyEventListeners(
'messageReceived',
conflictHandler(flattenedMessages),
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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);
}
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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';
Expand Down

0 comments on commit 22d042d

Please sign in to comment.