Files
mattermost/webapp/channels/src/plugins/published_editor.test.tsx
T
b9cea25748 MM-69912: let plugins bring their own editor extensions and read structured content (#37678)
* MM-69912: let plugins bring their own editor extensions and read structured content

* lint

* Claude PR feedback

* Claude AI review second pass

* gate autosave on load failure and harden json-mode parse path

* fix stale hasContentError handle and scope the error latch to load

* shorten required comment

---------

Co-authored-by: Nevyana Angelova <nevyangelova@Nevy-Macbook-16-2025.local>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-07-30 15:57:22 +03:00

133 lines
4.8 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {act, render} from '@testing-library/react';
import React from 'react';
import type {
PublishedEditorComponentId,
PublishedFormattingBarHandle,
PublishedSuggestionProviderId,
PublishedWysiwygEditorHandle,
} from '@mattermost/shared/types/global';
jest.mock('components/advanced_text_editor/wysiwyg_editor/wysiwyg_editor', () => {
const ReactMock = require('react') as typeof import('react');
return {
__esModule: true,
default: ReactMock.forwardRef((_props: unknown, ref: React.Ref<unknown>) => {
ReactMock.useImperativeHandle(ref, () => ({
insertText: () => {},
focus: () => {},
blur: () => {},
getInputBox: () => null,
getEditor: () => null,
hasContentError: () => false,
}));
return null;
}),
};
});
jest.mock('components/advanced_text_editor/formatting_bar/formatting_bar', () => {
const ReactMock = require('react') as typeof import('react');
return {
__esModule: true,
default: ReactMock.forwardRef((_props: unknown, ref: React.Ref<unknown>) => {
ReactMock.useImperativeHandle(ref, () => ({
openLinkPopover: () => {},
}));
return null;
}),
};
});
const {isPublishedEditorComponent, publishedEditorUtils, publishedSuggestionProviders} =
require('./published_editor') as typeof import('./published_editor');
describe('publishedEditorUtils', () => {
test('exposes the three allowlisted components', () => {
expect(typeof publishedEditorUtils.WysiwygEditor).toBe('object');
expect(typeof publishedEditorUtils.SuggestionList).toBe('function');
expect(typeof publishedEditorUtils.FormattingBar).toBe('object');
});
test('exposes every provider constructor and each is newable', () => {
const ids: PublishedSuggestionProviderId[] = ['AtMention', 'ChannelMention', 'Command', 'Emoticon'];
for (const id of ids) {
expect(typeof publishedEditorUtils.providers[id]).toBe('function');
expect(publishedEditorUtils.providers[id]).toBe(publishedSuggestionProviders[id]);
}
});
test('providers with a no-arg constructor can be instantiated directly', () => {
const instance = new publishedEditorUtils.providers.Emoticon();
expect(instance.triggerCharacter).toBe(':');
expect(typeof instance.handlePretextChanged).toBe('function');
});
});
describe('WysiwygEditor handle forwarding', () => {
test('ref receives the published handle after Suspense resolves', async () => {
const ref = React.createRef<PublishedWysiwygEditorHandle>();
await act(async () => {
render(
<publishedEditorUtils.WysiwygEditor
ref={ref}
value=''
onChange={() => {}}
onSubmit={() => {}}
channelId='c'
/>,
);
});
const handle = ref.current;
expect(handle).not.toBeNull();
expect(typeof handle!.insertText).toBe('function');
expect(typeof handle!.focus).toBe('function');
expect(typeof handle!.blur).toBe('function');
expect(typeof handle!.getInputBox).toBe('function');
expect(typeof handle!.getEditor).toBe('function');
expect(typeof handle!.hasContentError).toBe('function');
});
});
describe('FormattingBar handle forwarding', () => {
test('ref receives the published handle after Suspense resolves', async () => {
const ref = React.createRef<PublishedFormattingBarHandle>();
await act(async () => {
render(
<publishedEditorUtils.FormattingBar
ref={ref}
applyFormatting={() => {}}
disableControls={false}
location='center'
/>,
);
});
expect(ref.current).not.toBeNull();
expect(typeof ref.current!.openLinkPopover).toBe('function');
});
});
describe('isPublishedEditorComponent', () => {
test('is true for every published component id', () => {
const ids: PublishedEditorComponentId[] = ['wysiwyg_editor', 'suggestion_list', 'formatting_bar'];
for (const id of ids) {
expect(isPublishedEditorComponent(id)).toBe(true);
}
});
test('is false for ids the running web app does not publish', () => {
expect(isPublishedEditorComponent('not_a_real_component')).toBe(false);
expect(isPublishedEditorComponent('')).toBe(false);
expect(isPublishedEditorComponent('textbox')).toBe(false);
});
});