Skip to content

Commit 825fe8d

Browse files
Text only mode tests for Composites (#4848)
1 parent 8aa3956 commit 825fe8d

File tree

2 files changed

+177
-49
lines changed

2 files changed

+177
-49
lines changed

packages/react-composites/src/composites/ChatComposite/ChatComposite.test.tsx

Lines changed: 163 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,58 +12,62 @@ import '@testing-library/jest-dom';
1212
/* @conditional-compile-remove(rich-text-editor-composite-support) */
1313
import { COMPOSITE_LOCALE_ZH_TW } from '../localization/locales/zh-TW/CompositeLocale';
1414
/* @conditional-compile-remove(rich-text-editor-composite-support) */
15-
import { ChatComposite } from './ChatComposite';
15+
import { ChatComposite, ChatCompositeProps } from './ChatComposite';
1616
/* @conditional-compile-remove(rich-text-editor-composite-support) */
1717
import React from 'react';
18-
1918
/* @conditional-compile-remove(rich-text-editor-composite-support) */
20-
function createMockChatAdapter(): ChatAdapter {
21-
const chatAdapter = {} as ChatAdapter;
22-
chatAdapter.onStateChange = jest.fn();
23-
chatAdapter.offStateChange = jest.fn();
24-
chatAdapter.fetchInitialData = jest.fn();
25-
chatAdapter.loadPreviousChatMessages = jest.fn();
26-
chatAdapter.getState = jest.fn(
27-
(): ChatAdapterState => ({
28-
userId: { kind: 'communicationUser', communicationUserId: 'test' },
29-
displayName: 'test',
30-
thread: {
31-
chatMessages: {},
32-
participants: {},
33-
threadId: 'test',
34-
readReceipts: [],
35-
typingIndicators: [],
36-
latestReadTime: new Date()
37-
},
38-
latestErrors: {},
39-
error: undefined
40-
})
41-
);
42-
return chatAdapter;
43-
}
19+
import { RichTextSendBoxProps } from '@internal/react-components';
20+
/* @conditional-compile-remove(rich-text-editor-composite-support) */
21+
import { RichTextSendBoxWrapper } from '../common/RichTextSendBoxWrapper';
22+
/* @conditional-compile-remove(rich-text-editor-composite-support) */
23+
import { removeImageTags } from './ImageUpload/ImageUploadUtils';
4424

4525
// Mock the richTextSendBoxWrapper component as it's lazy loaded in ChatComposite
4626
/* @conditional-compile-remove(rich-text-editor-composite-support) */
47-
function MockedRichTextSendBoxWrapper(): JSX.Element {
48-
return <div id="richTextSendBoxWrapper">Mocked RichTextSendboxWrapper</div>;
27+
function MockedRichTextSendBoxWrapperComponent(): JSX.Element {
28+
return <div data-testid="rich-text-editor-test">Mocked RichTextSendboxWrapper</div>;
4929
}
50-
5130
/* @conditional-compile-remove(rich-text-editor-composite-support) */
52-
jest.mock('../common/RichTextSendBoxWrapper', () => {
53-
return {
54-
RichTextSendBoxWrapper: MockedRichTextSendBoxWrapper
55-
};
56-
});
31+
jest.mock('../common/RichTextSendBoxWrapper');
32+
/* @conditional-compile-remove(rich-text-editor-composite-support) */
33+
const mockedRichTextSendBoxWrapper = jest.mocked(RichTextSendBoxWrapper);
5734

5835
/* @conditional-compile-remove(rich-text-editor-composite-support) */
5936
describe('ChatComposite', () => {
37+
const createMockChatAdapter = (): ChatAdapter => {
38+
const chatAdapter = {} as ChatAdapter;
39+
chatAdapter.onStateChange = jest.fn();
40+
chatAdapter.offStateChange = jest.fn();
41+
chatAdapter.fetchInitialData = jest.fn();
42+
chatAdapter.loadPreviousChatMessages = jest.fn();
43+
chatAdapter.getState = jest.fn(
44+
(): ChatAdapterState => ({
45+
userId: { kind: 'communicationUser', communicationUserId: 'test' },
46+
displayName: 'test',
47+
thread: {
48+
chatMessages: {},
49+
participants: {},
50+
threadId: 'test',
51+
readReceipts: [],
52+
typingIndicators: [],
53+
latestReadTime: new Date()
54+
},
55+
latestErrors: {}
56+
})
57+
);
58+
return chatAdapter;
59+
};
60+
6061
beforeEach(() => {
62+
jest.restoreAllMocks();
6163
// Register icons used in ChatComposite to avoid warnings
6264
registerIcons({
6365
icons: {
6466
chevrondown: <></>
6567
}
6668
});
69+
70+
mockedRichTextSendBoxWrapper.mockImplementation(MockedRichTextSendBoxWrapperComponent);
6771
});
6872

6973
test('Chat Composite should show RichTextSendBoxWrapper if it is enabled', async () => {
@@ -81,7 +85,8 @@ describe('ChatComposite', () => {
8185
const mockChatAdapter = createMockChatAdapter();
8286

8387
render(<ChatComposite adapter={mockChatAdapter} {...mockBaseCompositeProps} />);
84-
expect(await screen.findByText(/Mocked RichTextSendboxWrapper/)).toBeVisible();
88+
const mockSendBox = await screen.findByTestId('rich-text-editor-test');
89+
expect(mockSendBox).toBeVisible();
8590
});
8691

8792
test('Chat Composite should not show RichTextSendBoxWrapper if it is not enabled', async () => {
@@ -99,7 +104,129 @@ describe('ChatComposite', () => {
99104
const mockChatAdapter = createMockChatAdapter();
100105

101106
render(<ChatComposite adapter={mockChatAdapter} {...mockBaseCompositeProps} />);
102-
expect(screen.findByText(/Mocked RichTextSendboxWrapper/)).rejects.toThrow();
107+
expect(screen.queryByTestId('rich-text-editor-test')).toBeNull();
108+
});
109+
});
110+
111+
/* @conditional-compile-remove(rich-text-editor-composite-support) */
112+
describe('ChatComposite - text only mode', () => {
113+
const createMockChatAdapter = (textOnlyChat: boolean): ChatAdapter => {
114+
const chatAdapter = {} as ChatAdapter;
115+
chatAdapter.onStateChange = jest.fn();
116+
chatAdapter.offStateChange = jest.fn();
117+
chatAdapter.fetchInitialData = jest.fn();
118+
chatAdapter.loadPreviousChatMessages = jest.fn();
119+
chatAdapter.getState = jest.fn(
120+
(): ChatAdapterState => ({
121+
//Text only mode is available for Teams meetings only
122+
userId: { kind: 'microsoftTeamsUser', microsoftTeamsUserId: 'test' },
123+
displayName: 'test',
124+
thread: {
125+
chatMessages: {},
126+
participants: {},
127+
threadId: 'test',
128+
readReceipts: [],
129+
typingIndicators: [],
130+
latestReadTime: new Date(),
131+
properties: {
132+
messagingPolicy: { textOnlyChat: textOnlyChat },
133+
createdBy: { kind: 'microsoftTeamsUser', microsoftTeamsUserId: 'test' }
134+
}
135+
},
136+
latestErrors: {}
137+
})
138+
);
139+
return chatAdapter;
140+
};
141+
142+
const mockBaseCompositeProps = (adapter: ChatAdapter, richTextEditor: boolean): ChatCompositeProps => {
143+
return {
144+
adapter: adapter,
145+
fluentTheme: {},
146+
icons: {},
147+
locale: COMPOSITE_LOCALE_ZH_TW,
148+
rtl: true,
149+
onFetchAvatarPersonaData: jest.fn(),
150+
options: {
151+
richTextEditor: richTextEditor,
152+
attachmentOptions: {
153+
uploadOptions: {
154+
handleAttachmentSelection: () => {}
155+
}
156+
}
157+
}
158+
};
159+
};
160+
161+
beforeEach(() => {
162+
jest.restoreAllMocks();
163+
// Register icons used in ChatComposite to avoid warnings
164+
registerIcons({
165+
icons: {
166+
chevrondown: <></>
167+
}
168+
});
169+
mockedRichTextSendBoxWrapper.mockImplementation(MockedRichTextSendBoxWrapperComponent);
170+
});
171+
172+
test('Chat Composite should set onPaste callback when text only mode is on', async () => {
173+
let onPaste: ((event: { content: DocumentFragment }) => void) | undefined = undefined;
174+
expect.assertions(2);
175+
function Wrapper(props: RichTextSendBoxProps): JSX.Element {
176+
onPaste = props.onPaste;
177+
return <MockedRichTextSendBoxWrapperComponent />;
178+
}
179+
mockedRichTextSendBoxWrapper.mockImplementation(Wrapper);
180+
const mockChatAdapter = createMockChatAdapter(true);
181+
render(<ChatComposite {...mockBaseCompositeProps(mockChatAdapter, true)} />);
182+
await screen.findByTestId('rich-text-editor-test');
183+
expect(onPaste).toBeDefined();
184+
if (onPaste !== undefined) {
185+
const onPasteFunction = onPaste as (event: { content: DocumentFragment }) => void;
186+
expect(onPasteFunction).toBe(removeImageTags);
187+
} else {
188+
fail('onPaste is undefined');
189+
}
190+
});
191+
192+
test('Chat Composite with rich text send box should not show attachments button when text only mode is on', async () => {
193+
const mockChatAdapter = createMockChatAdapter(true);
194+
render(<ChatComposite {...mockBaseCompositeProps(mockChatAdapter, true)} />);
195+
expect(screen.queryByTestId('attachment-upload-button')).toBeNull();
196+
});
197+
198+
test('Chat Composite with plain text send box should not show attachments button when text only mode is on', async () => {
199+
const mockChatAdapter = createMockChatAdapter(true);
200+
render(<ChatComposite {...mockBaseCompositeProps(mockChatAdapter, false)} />);
201+
expect(screen.queryByTestId('attachment-upload-button')).toBeNull();
202+
});
203+
204+
test('Chat Composite should set onPaste callback to undefined when text only mode is off', async () => {
205+
let onPaste: ((event: { content: DocumentFragment }) => void) | undefined = undefined;
206+
expect.assertions(1);
207+
function Wrapper(props: RichTextSendBoxProps): JSX.Element {
208+
onPaste = props.onPaste;
209+
return <MockedRichTextSendBoxWrapperComponent />;
210+
}
211+
mockedRichTextSendBoxWrapper.mockImplementation(Wrapper);
212+
const mockChatAdapter = createMockChatAdapter(false);
213+
render(<ChatComposite {...mockBaseCompositeProps(mockChatAdapter, true)} />);
214+
await screen.findByTestId('rich-text-editor-test');
215+
expect(onPaste).toBeUndefined();
216+
});
217+
218+
test('Chat Composite with rich text send box should show attachments button when text only mode is off', async () => {
219+
const mockChatAdapter = createMockChatAdapter(false);
220+
render(<ChatComposite {...mockBaseCompositeProps(mockChatAdapter, true)} />);
221+
const attachmentsButton = await screen.findByTestId('attachment-upload-button');
222+
expect(attachmentsButton).toBeDefined();
223+
});
224+
225+
test('Chat Composite with plain text send box should show attachments button when text only mode is off', async () => {
226+
const mockChatAdapter = createMockChatAdapter(false);
227+
render(<ChatComposite {...mockBaseCompositeProps(mockChatAdapter, false)} />);
228+
const attachmentsButton = await screen.findByTestId('attachment-upload-button');
229+
expect(attachmentsButton).toBeDefined();
103230
});
104231
});
105232

packages/react-composites/src/composites/ChatComposite/ChatScreen.tsx

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ import { useImageUpload } from './ImageUpload/useImageUpload';
6868
import { removeImageTags } from './ImageUpload/ImageUploadUtils';
6969
/* @conditional-compile-remove(rich-text-editor-image-upload) */
7070
import type { ChatAdapterState } from './adapter/ChatAdapter';
71+
/* @conditional-compile-remove(rich-text-editor-image-upload) */
72+
import { isMicrosoftTeamsUserIdentifier } from '@azure/communication-common';
7173

7274
/**
7375
* @private
@@ -134,13 +136,20 @@ export const ChatScreen = (props: ChatScreenProps): JSX.Element => {
134136
useImageUpload();
135137
/* @conditional-compile-remove(rich-text-editor-image-upload) */
136138
const [textOnlyChat, setTextOnlyChat] = useState(false);
139+
/* @conditional-compile-remove(rich-text-editor-image-upload) */
140+
const [isACSChat, setIsACSChat] = useState(false);
137141

138142
/* @conditional-compile-remove(rich-text-editor-image-upload) */
139143
useEffect(() => {
140144
const updateChatState = (newState: ChatAdapterState): void => {
141145
setTextOnlyChat(newState.thread.properties?.messagingPolicy?.textOnlyChat === true);
146+
if (newState.thread.properties?.createdBy) {
147+
setIsACSChat(!isMicrosoftTeamsUserIdentifier(newState.thread.properties?.createdBy));
148+
}
142149
};
150+
// set initial state for textOnlyChat and isACSChat
143151
updateChatState(adapter.getState());
152+
144153
adapter.onStateChange(updateChatState);
145154
return () => {
146155
adapter.offStateChange(updateChatState);
@@ -515,17 +524,6 @@ export const ChatScreen = (props: ChatScreenProps): JSX.Element => {
515524
[/* @conditional-compile-remove(rich-text-editor-image-upload) */ handleInlineImageUploadAction, messageThreadProps]
516525
);
517526

518-
/* @conditional-compile-remove(rich-text-editor-image-upload) */
519-
const onPasteHandler = useCallback(
520-
(event: { content: DocumentFragment }) => {
521-
const threadCreatedBy = adapter.getState().thread?.properties?.createdBy;
522-
if (threadCreatedBy?.kind !== 'microsoftTeamsUser' || textOnlyChat) {
523-
removeImageTags(event);
524-
}
525-
},
526-
[adapter, textOnlyChat]
527-
);
528-
529527
/* @conditional-compile-remove(rich-text-editor-image-upload) */
530528
const onCancelEditMessageHandler = useCallback(() => {
531529
handleInlineImageUploadAction({ type: AttachmentUploadActionType.Clear });
@@ -542,9 +540,10 @@ export const ChatScreen = (props: ChatScreenProps): JSX.Element => {
542540

543541
/* @conditional-compile-remove(rich-text-editor-composite-support) */
544542
const richTextEditorOptions = useMemo(() => {
543+
const onPasteCallback = isACSChat || textOnlyChat ? removeImageTags : undefined;
545544
return options?.richTextEditor
546545
? {
547-
/* @conditional-compile-remove(rich-text-editor-image-upload) */ onPaste: onPasteHandler,
546+
/* @conditional-compile-remove(rich-text-editor-image-upload) */ onPaste: onPasteCallback,
548547
/* @conditional-compile-remove(rich-text-editor-image-upload) */
549548
onUploadInlineImage: onUploadInlineImage,
550549
/* @conditional-compile-remove(rich-text-editor-image-upload) */
@@ -561,7 +560,9 @@ export const ChatScreen = (props: ChatScreenProps): JSX.Element => {
561560
/* @conditional-compile-remove(rich-text-editor-image-upload) */
562561
onUploadInlineImage,
563562
/* @conditional-compile-remove(rich-text-editor-image-upload) */
564-
onPasteHandler,
563+
isACSChat,
564+
/* @conditional-compile-remove(rich-text-editor-image-upload) */
565+
textOnlyChat,
565566
options?.richTextEditor
566567
]);
567568

0 commit comments

Comments
 (0)