Skip to content

Commit 28aeebb

Browse files
authored
Merge pull request learningequality#5985 from Abhishek-Punhani/Issue5966
feat: add format prop to TipTapEditor to support HTML content alongside markdown and include unit tests
2 parents bf22397 + fad4ec3 commit 28aeebb

2 files changed

Lines changed: 105 additions & 18 deletions

File tree

contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,10 @@
190190
document.removeEventListener('click', handleClickOutside);
191191
});
192192
193-
const getMarkdownContent = () => {
194-
if (!editor.value || !isReady.value || !editor.value.storage?.markdown) {
195-
return '';
196-
}
193+
const getContent = () => {
194+
if (!editor.value || !isReady.value) return '';
195+
if (props.format === 'html') return editor.value.getHTML();
196+
if (!editor.value.storage?.markdown) return '';
197197
return editor.value.storage.markdown.getMarkdown();
198198
};
199199
@@ -217,7 +217,8 @@
217217
watch(
218218
() => props.value,
219219
newValue => {
220-
const processedContent = preprocessMarkdown(newValue);
220+
const processedContent =
221+
props.format === 'html' ? newValue : preprocessMarkdown(newValue);
221222
222223
if (!editor.value) {
223224
initializeEditor(processedContent, props.mode, {
@@ -226,8 +227,7 @@
226227
return;
227228
}
228229
229-
const editorContent = getMarkdownContent();
230-
if (editorContent !== newValue) {
230+
if (getContent() !== newValue) {
231231
isUpdatingFromOutside = true;
232232
editor.value.commands.setContent(processedContent, false);
233233
nextTick(() => {
@@ -242,18 +242,11 @@
242242
watch(
243243
() => editor.value?.state,
244244
() => {
245-
if (
246-
!editor.value ||
247-
!isReady.value ||
248-
isUpdatingFromOutside ||
249-
!editor.value.storage?.markdown
250-
) {
251-
return;
252-
}
245+
if (!editor.value || !isReady.value || isUpdatingFromOutside) return;
253246
254-
const markdown = getMarkdownContent();
255-
if (markdown !== props.value) {
256-
emit('update', markdown);
247+
const content = getContent();
248+
if (content !== props.value) {
249+
emit('update', content);
257250
}
258251
},
259252
{ deep: true },
@@ -312,6 +305,11 @@
312305
type: String,
313306
default: null,
314307
},
308+
format: {
309+
type: String,
310+
default: 'markdown',
311+
validator: v => ['markdown', 'html'].includes(v),
312+
},
315313
},
316314
emits: ['update', 'minimize', 'open-editor'],
317315
});
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import TipTapEditor from '../TipTapEditor/TipTapEditor.vue';
2+
3+
function makeEditorStub({ markdownOut, htmlOut }) {
4+
return {
5+
storage: {
6+
markdown: { getMarkdown: () => markdownOut },
7+
},
8+
getHTML: () => htmlOut,
9+
};
10+
}
11+
12+
function getContent(editor, isReady, format) {
13+
if (!editor || !isReady) return '';
14+
if (format === 'html') return editor.getHTML();
15+
if (!editor.storage?.markdown) return '';
16+
return editor.storage.markdown.getMarkdown();
17+
}
18+
describe('TipTapEditor — format prop declaration', () => {
19+
const { format: formatProp } = TipTapEditor.props;
20+
21+
it('exists on the component', () => {
22+
expect(formatProp).toBeDefined();
23+
});
24+
25+
it('defaults to markdown', () => {
26+
expect(formatProp.default).toBe('markdown');
27+
});
28+
29+
it('validator accepts markdown', () => {
30+
expect(formatProp.validator('markdown')).toBe(true);
31+
});
32+
33+
it('validator accepts html', () => {
34+
expect(formatProp.validator('html')).toBe(true);
35+
});
36+
37+
it('validator rejects anything else', () => {
38+
expect(formatProp.validator('xml')).toBe(false);
39+
expect(formatProp.validator('')).toBe(false);
40+
expect(formatProp.validator('JSON')).toBe(false);
41+
});
42+
});
43+
44+
describe('TipTapEditor — getContent() logic', () => {
45+
const MARKDOWN = '**bold**';
46+
const HTML = '<p><strong>bold</strong></p>';
47+
48+
describe('when editor is not ready', () => {
49+
it('returns empty string when editor is null', () => {
50+
expect(getContent(null, true, 'markdown')).toBe('');
51+
});
52+
53+
it('returns empty string when isReady is false', () => {
54+
const editor = makeEditorStub({ markdownOut: MARKDOWN, htmlOut: HTML });
55+
expect(getContent(editor, false, 'markdown')).toBe('');
56+
});
57+
});
58+
59+
describe('format="markdown" (default)', () => {
60+
it('returns markdown from storage', () => {
61+
const editor = makeEditorStub({ markdownOut: MARKDOWN, htmlOut: HTML });
62+
expect(getContent(editor, true, 'markdown')).toBe(MARKDOWN);
63+
});
64+
65+
it('returns empty string when markdown storage is absent', () => {
66+
const editor = { storage: {}, getHTML: () => HTML };
67+
expect(getContent(editor, true, 'markdown')).toBe('');
68+
});
69+
});
70+
71+
describe('format="html"', () => {
72+
it('returns HTML from editor.getHTML()', () => {
73+
const editor = makeEditorStub({ markdownOut: MARKDOWN, htmlOut: HTML });
74+
expect(getContent(editor, true, 'html')).toBe(HTML);
75+
});
76+
77+
it('does not call getMarkdown() in html mode', () => {
78+
const getMarkdown = jest.fn(() => MARKDOWN);
79+
const editor = { storage: { markdown: { getMarkdown } }, getHTML: () => HTML };
80+
getContent(editor, true, 'html');
81+
expect(getMarkdown).not.toHaveBeenCalled();
82+
});
83+
84+
it('works even when markdown storage is absent', () => {
85+
const editor = { storage: {}, getHTML: () => HTML };
86+
expect(getContent(editor, true, 'html')).toBe(HTML);
87+
});
88+
});
89+
});

0 commit comments

Comments
 (0)