Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Refactor setConflictHandler to avoid directly exporting let #13143

Merged
merged 4 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading