From b7a5c641a6c74e11622839799995dc780ebe2c61 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:42:26 +0000 Subject: [PATCH 1/3] Initial plan From 87df86f11fae225bfee07e503741f0a4e5156b3b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 13:58:01 +0000 Subject: [PATCH 2/3] Add unit tests for lookupTranslation and maskNotifications Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com> --- .../___tests__/maskIdentifiers/index.test.ts | 127 +++++++++++++++++- .../src/___tests__/translations/index.test.ts | 48 ++++++- 2 files changed, 172 insertions(+), 3 deletions(-) diff --git a/plugin-hrm-form/src/___tests__/maskIdentifiers/index.test.ts b/plugin-hrm-form/src/___tests__/maskIdentifiers/index.test.ts index 72ab68a3b9..388131e392 100644 --- a/plugin-hrm-form/src/___tests__/maskIdentifiers/index.test.ts +++ b/plugin-hrm-form/src/___tests__/maskIdentifiers/index.test.ts @@ -19,15 +19,42 @@ import { Manager } from '@twilio/flex-ui'; import each from 'jest-each'; -import { maskConversationServiceUserNames } from '../../maskIdentifiers'; +import { + maskConversationServiceUserNames, + maskNotifications, + maskChannelStringsWithIdentifiers, +} from '../../maskIdentifiers'; import { getInitializedCan } from '../../permissions/rules'; import { PermissionActions } from '../../permissions/actions'; +import { lookupTranslation } from '../../translations'; jest.mock('../../permissions/rules', () => ({ getInitializedCan: jest.fn(), })); +jest.mock('../../translations', () => ({ + lookupTranslation: jest.fn(), +})); + +jest.mock('@twilio/flex-ui', () => ({ + Manager: { + getInstance: jest.fn(), + }, + NotificationIds: { + NewChatMessage: 'NewChatMessage', + }, + DefaultTaskChannels: { + ChatSms: { name: 'ChatSms' }, + }, + MessageList: { + Content: { remove: jest.fn() }, + }, +})); + +const mockLookupTranslation = lookupTranslation as jest.MockedFunction; + const mockGetInitializedCan = getInitializedCan as jest.MockedFunction; +const mockManagerGetInstance = Manager.getInstance as jest.MockedFunction; describe('maskConversationServiceUserNames', () => { let mockManager: any; @@ -391,3 +418,101 @@ describe('maskConversationServiceUserNames', () => { }); }); }); + +describe('maskNotifications', () => { + const createChannelType = () => ({ + notifications: { + override: {} as Record void>, + }, + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('sets an override handler for NewChatMessage notifications', () => { + const channelType = createChannelType(); + maskNotifications(channelType as any); + expect(typeof channelType.notifications.override.NewChatMessage).toBe('function'); + }); + + test('override handler sets browser notification title using lookupTranslation', () => { + mockLookupTranslation.mockReturnValue('Masked Chat Message'); + const channelType = createChannelType(); + maskNotifications(channelType as any); + + const notification = { options: { browser: { title: '' } } }; + channelType.notifications.override.NewChatMessage(notification); + + expect(mockLookupTranslation).toHaveBeenCalledWith('BrowserNotification-ChatMessage-MaskedTitle'); + expect(notification.options.browser.title).toBe('Masked Chat Message'); + }); +}); + +describe('maskChannelStringsWithIdentifiers', () => { + let mockCan: jest.Mock; + + const createChannelType = (name = 'default') => ({ + name, + templates: { + IncomingTaskCanvas: { firstLine: '' }, + TaskListItem: { firstLine: '', secondLine: '' }, + CallCanvas: { firstLine: '' }, + TaskCanvasHeader: { title: '' }, + Supervisor: { + TaskCanvasHeader: { title: '' }, + TaskOverviewCanvas: { firstLine: '' }, + }, + TaskCard: { firstLine: '' }, + }, + notifications: { + override: {} as Record void>, + }, + }); + + beforeEach(() => { + mockCan = jest.fn(); + mockGetInitializedCan.mockReturnValue(mockCan); + mockManagerGetInstance.mockReturnValue({ strings: { MaskIdentifiers: 'MASKED' } } as any); + mockLookupTranslation.mockReturnValue('Masked Title'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('when VIEW_IDENTIFIERS permission is denied', () => { + beforeEach(() => { + mockCan.mockImplementation((action: string) => action !== PermissionActions.VIEW_IDENTIFIERS); + }); + + test('sets notification override for NewChatMessage', () => { + const channelType = createChannelType(); + maskChannelStringsWithIdentifiers(channelType as any); + expect(typeof channelType.notifications.override.NewChatMessage).toBe('function'); + }); + + test('notification override handler sets browser title using lookupTranslation', () => { + const channelType = createChannelType(); + maskChannelStringsWithIdentifiers(channelType as any); + + const notification = { options: { browser: { title: '' } } }; + channelType.notifications.override.NewChatMessage(notification); + + expect(mockLookupTranslation).toHaveBeenCalledWith('BrowserNotification-ChatMessage-MaskedTitle'); + expect(notification.options.browser.title).toBe('Masked Title'); + }); + }); + + describe('when VIEW_IDENTIFIERS permission is granted', () => { + beforeEach(() => { + mockCan.mockReturnValue(true); + }); + + test('does not set notification override', () => { + const channelType = createChannelType(); + maskChannelStringsWithIdentifiers(channelType as any); + expect(channelType.notifications.override.NewChatMessage).toBeUndefined(); + }); + }); +}); diff --git a/plugin-hrm-form/src/___tests__/translations/index.test.ts b/plugin-hrm-form/src/___tests__/translations/index.test.ts index 849363e8fc..5339f7b512 100644 --- a/plugin-hrm-form/src/___tests__/translations/index.test.ts +++ b/plugin-hrm-form/src/___tests__/translations/index.test.ts @@ -14,13 +14,20 @@ * along with this program. If not, see https://www.gnu.org/licenses/. */ -import { loadTranslations, initLocalization } from '../../translations'; +import { Manager } from '@twilio/flex-ui'; + +import { loadTranslations, initLocalization, lookupTranslation } from '../../translations'; -// Mock translation files at the top level jest.mock('../../translations/en.json', () => ({}), { virtual: true }); jest.mock('../../translations/en-US.json', () => ({}), { virtual: true }); jest.mock('../../translations/en-GB.json', () => ({}), { virtual: true }); +jest.mock('@twilio/flex-ui', () => ({ + Manager: { + getInstance: jest.fn(), + }, +})); + const mockGetAseloFeatureFlags = jest.fn(); const mockGetHrmConfig = jest.fn(); const mockGetDefinitionVersions = jest.fn(); @@ -160,3 +167,40 @@ describe('Hierarchical Translations', () => { }); }); }); + +describe('lookupTranslation', () => { + const mockManagerGetInstance = Manager.getInstance as jest.MockedFunction; + + beforeAll(() => { + // eslint-disable-next-line global-require + (global as any).Handlebars = require('handlebars'); + }); + + afterAll(() => { + delete (global as any).Handlebars; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('returns compiled string for a key that exists in Manager strings', () => { + mockManagerGetInstance.mockReturnValue({ strings: { MyKey: 'Hello World' } } as any); + expect(lookupTranslation('MyKey')).toBe('Hello World'); + }); + + test('falls back to using the code itself as template when key does not exist in Manager strings', () => { + mockManagerGetInstance.mockReturnValue({ strings: {} } as any); + expect(lookupTranslation('FallbackKey')).toBe('FallbackKey'); + }); + + test('passes parameters to the Handlebars template', () => { + mockManagerGetInstance.mockReturnValue({ strings: { Greeting: 'Hello {{name}}!' } } as any); + expect(lookupTranslation('Greeting', { name: 'World' })).toBe('Hello World!'); + }); + + test('uses empty parameters object by default', () => { + mockManagerGetInstance.mockReturnValue({ strings: { SimpleMsg: 'No params here' } } as any); + expect(lookupTranslation('SimpleMsg')).toBe('No params here'); + }); +}); From 85f01a7d00cde6dd6ddfb9457f7b49c7c326e97a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 16:25:15 +0000 Subject: [PATCH 3/3] Move maskNotifications tests into maskChannelStringsWithIdentifiers describe block Co-authored-by: stephenhand <1694716+stephenhand@users.noreply.github.com> --- .../___tests__/maskIdentifiers/index.test.ts | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/plugin-hrm-form/src/___tests__/maskIdentifiers/index.test.ts b/plugin-hrm-form/src/___tests__/maskIdentifiers/index.test.ts index 388131e392..2044270c95 100644 --- a/plugin-hrm-form/src/___tests__/maskIdentifiers/index.test.ts +++ b/plugin-hrm-form/src/___tests__/maskIdentifiers/index.test.ts @@ -19,11 +19,7 @@ import { Manager } from '@twilio/flex-ui'; import each from 'jest-each'; -import { - maskConversationServiceUserNames, - maskNotifications, - maskChannelStringsWithIdentifiers, -} from '../../maskIdentifiers'; +import { maskConversationServiceUserNames, maskChannelStringsWithIdentifiers } from '../../maskIdentifiers'; import { getInitializedCan } from '../../permissions/rules'; import { PermissionActions } from '../../permissions/actions'; import { lookupTranslation } from '../../translations'; @@ -419,36 +415,6 @@ describe('maskConversationServiceUserNames', () => { }); }); -describe('maskNotifications', () => { - const createChannelType = () => ({ - notifications: { - override: {} as Record void>, - }, - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('sets an override handler for NewChatMessage notifications', () => { - const channelType = createChannelType(); - maskNotifications(channelType as any); - expect(typeof channelType.notifications.override.NewChatMessage).toBe('function'); - }); - - test('override handler sets browser notification title using lookupTranslation', () => { - mockLookupTranslation.mockReturnValue('Masked Chat Message'); - const channelType = createChannelType(); - maskNotifications(channelType as any); - - const notification = { options: { browser: { title: '' } } }; - channelType.notifications.override.NewChatMessage(notification); - - expect(mockLookupTranslation).toHaveBeenCalledWith('BrowserNotification-ChatMessage-MaskedTitle'); - expect(notification.options.browser.title).toBe('Masked Chat Message'); - }); -}); - describe('maskChannelStringsWithIdentifiers', () => { let mockCan: jest.Mock;