Skip to content

Commit

Permalink
[RTE] Add tests for Rich Text Editor (#4813)
Browse files Browse the repository at this point in the history
* Add test for handleBeforePasteEvent

* TableEdit Test

* Add test for chat composite

* Fix chat composite test

* Rename ChatAdapter to ChatComposite in test file

* Clean up

* Clean up

* Clean up

* Add cc

* CC

* Update test description

* Add empty test for CC

* Add test for false value
  • Loading branch information
palatter committed Jul 9, 2024
1 parent 6dd5a3c commit 25ffe01
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,20 @@

import { PasteType, BeforePasteEvent } from 'roosterjs-content-model-types';
import { removeImageElement } from './CopyPastePlugin';
/* @conditional-compile-remove(rich-text-editor-image-upload) */
import { handleBeforePasteEvent } from './CopyPastePlugin';
import { PluginEventType } from '../../utils/RichTextEditorUtils';

/* @conditional-compile-remove(rich-text-editor-image-upload) */
describe('handleBeforePasteEvent should work correctly', () => {
test('handleBeforePasteEvent should call onPaste', () => {
const event = getBeforePastePluginEvent(document.createDocumentFragment());
const onPaste = jest.fn();
handleBeforePasteEvent(event, onPaste);
expect(onPaste).toHaveBeenCalled();
});
});

describe('removeImageElement should work correctly', () => {
test('removeImageElement should remove all image elements when fragment only contains image children', () => {
const fragment = document.createDocumentFragment();
Expand All @@ -15,7 +27,7 @@ describe('removeImageElement should work correctly', () => {
container.appendChild(document.createElement('img'));
container.appendChild(document.createElement('img'));

const event = getPluginEvent(fragment);
const event = getBeforePastePluginEvent(fragment);

removeImageElement(event);
expect(fragment.getElementById(containerId)?.outerHTML).toEqual(undefined);
Expand All @@ -32,7 +44,7 @@ describe('removeImageElement should work correctly', () => {
layer3.appendChild(document.createElement('img'));
layer3.appendChild(document.createElement('img'));

const event = getPluginEvent(fragment);
const event = getBeforePastePluginEvent(fragment);

removeImageElement(event);
// When a message only contains an image, no content will be pasted.
Expand All @@ -47,38 +59,38 @@ describe('removeImageElement should work correctly', () => {
container.appendChild(document.createElement('img'));
container.appendChild(document.createElement('text'));

const event = getPluginEvent(fragment);
const event = getBeforePastePluginEvent(fragment);

removeImageElement(event);
expect(fragment.childNodes.length).toEqual(1);
expect(fragment.firstChild?.childNodes.length).toEqual(1);
expect(fragment.getElementById(containerId)?.outerHTML).toEqual('<div id="container"><text></text></div>');
});
});

const getPluginEvent = (fragment: DocumentFragment): BeforePasteEvent => {
return {
eventType: PluginEventType.BeforePaste,
clipboardData: {
types: ['text/plain', 'text/html'],
text: '',
rawHtml: '',
image: null,
customValues: {}
},
fragment: fragment,
htmlBefore: '',
htmlAfter: '',
htmlAttributes: {},
pasteType: 'normal' as PasteType,
domToModelOption: {
additionalAllowedTags: [],
additionalDisallowedTags: [],
additionalFormatParsers: {},
attributeSanitizers: {},
formatParserOverride: {},
processorOverride: {},
styleSanitizers: {}
}
};
const getBeforePastePluginEvent = (fragment: DocumentFragment): BeforePasteEvent => {
return {
eventType: PluginEventType.BeforePaste,
clipboardData: {
types: ['text/plain', 'text/html'],
text: '',
rawHtml: '',
image: null,
customValues: {}
},
fragment: fragment,
htmlBefore: '',
htmlAfter: '',
htmlAttributes: {},
pasteType: 'normal' as PasteType,
domToModelOption: {
additionalAllowedTags: [],
additionalDisallowedTags: [],
additionalFormatParsers: {},
attributeSanitizers: {},
formatParserOverride: {},
processorOverride: {},
styleSanitizers: {}
}
};
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ export default class CopyPastePlugin implements EditorPlugin {
}
}

const handleBeforePasteEvent = (
/**
* @internal
* Exported only for unit testing
*/
export const handleBeforePasteEvent = (
event: PluginEvent,
/* @conditional-compile-remove(rich-text-editor-image-upload) */ onPaste?: (event: {
content: DocumentFragment;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export class TableEditContextMenuProvider implements ContextMenuProvider<IContex
}

getContextMenuItems(node: Node): IContextualMenuItem[] | null {
// return this.items;
if (this.editor && isTableEditable(this.editor, node)) {
return this.items;
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/* @conditional-compile-remove(rich-text-editor-composite-support) */
import { ChatAdapter, ChatAdapterState } from './adapter/ChatAdapter';
/* @conditional-compile-remove(rich-text-editor-composite-support) */
import { registerIcons } from '@fluentui/react';
/* @conditional-compile-remove(rich-text-editor-composite-support) */
import { render, screen } from '@testing-library/react';
/* @conditional-compile-remove(rich-text-editor-composite-support) */
import '@testing-library/jest-dom';
/* @conditional-compile-remove(rich-text-editor-composite-support) */
import { COMPOSITE_LOCALE_ZH_TW } from '../localization/locales/zh-TW/CompositeLocale';
/* @conditional-compile-remove(rich-text-editor-composite-support) */
import { ChatComposite } from './ChatComposite';
/* @conditional-compile-remove(rich-text-editor-composite-support) */
import React from 'react';

/* @conditional-compile-remove(rich-text-editor-composite-support) */
function createMockChatAdapter(): ChatAdapter {
const chatAdapter = {} as ChatAdapter;
chatAdapter.onStateChange = jest.fn();
chatAdapter.offStateChange = jest.fn();
chatAdapter.fetchInitialData = jest.fn();
chatAdapter.loadPreviousChatMessages = jest.fn();
chatAdapter.getState = jest.fn(
(): ChatAdapterState => ({
userId: { kind: 'communicationUser', communicationUserId: 'test' },
displayName: 'test',
thread: {
chatMessages: {},
participants: {},
threadId: 'test',
readReceipts: [],
typingIndicators: [],
latestReadTime: new Date()
},
latestErrors: {},
error: undefined
})
);
return chatAdapter;
}

// Mock the richTextSendBoxWrapper component as it's lazy loaded in ChatComposite
/* @conditional-compile-remove(rich-text-editor-composite-support) */
function MockedRichTextSendBoxWrapper(): JSX.Element {
return <div id="richTextSendBoxWrapper">Mocked RichTextSendboxWrapper</div>;
}

/* @conditional-compile-remove(rich-text-editor-composite-support) */
jest.mock('../common/RichTextSendBoxWrapper', () => {
return {
RichTextSendBoxWrapper: MockedRichTextSendBoxWrapper
};
});

/* @conditional-compile-remove(rich-text-editor-composite-support) */
describe('ChatComposite', () => {
beforeEach(() => {
// Register icons used in ChatComposite to avoid warnings
registerIcons({
icons: {
chevrondown: <></>
}
});
});

test('Chat Composite should show RichTextSendBoxWrapper if it is enabled', async () => {
const mockBaseCompositeProps = {
fluentTheme: {},
icons: {},
locale: COMPOSITE_LOCALE_ZH_TW,
rtl: true,
onFetchAvatarPersonaData: jest.fn(),
options: {
richTextEditor: true
}
};

const mockChatAdapter = createMockChatAdapter();

render(<ChatComposite adapter={mockChatAdapter} {...mockBaseCompositeProps} />);
expect(await screen.findByText(/Mocked RichTextSendboxWrapper/)).toBeVisible();
});

test('Chat Composite should not show RichTextSendBoxWrapper if it is not enabled', async () => {
const mockBaseCompositeProps = {
fluentTheme: {},
icons: {},
locale: COMPOSITE_LOCALE_ZH_TW,
rtl: true,
onFetchAvatarPersonaData: jest.fn(),
options: {
richTextEditor: false
}
};

const mockChatAdapter = createMockChatAdapter();

render(<ChatComposite adapter={mockChatAdapter} {...mockBaseCompositeProps} />);
expect(screen.findByText(/Mocked RichTextSendboxWrapper/)).rejects.toThrow();
});
});

// Remove when rich-text-editor-composite-support is GA
describe('Empty Test', () => {
test.skip('Empty test for Conditional Compile case where no tests are included', (done) => done());
});

0 comments on commit 25ffe01

Please sign in to comment.