Skip to content

Commit 58bca1b

Browse files
authored
Merge pull request #5246 from habibayman/feat/integrate-new-RTE
feat(texteditor): completely replace old with new TipTap editor 🎉
2 parents 715afa8 + e46709e commit 58bca1b

File tree

75 files changed

+431
-9414
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+431
-9414
lines changed

contentcuration/contentcuration/dev_urls.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from django.urls import include
99
from django.urls import path
1010
from django.urls import re_path
11-
from django.views.generic import TemplateView
1211
from drf_yasg import openapi
1312
from drf_yasg.views import get_schema_view
1413
from rest_framework import permissions
@@ -76,10 +75,3 @@ def file_server(request, storage_path=None):
7675
re_path(r"^api-auth/", include("rest_framework.urls", namespace="rest_framework")),
7776
re_path(r"^content/(?P<storage_path>.+)$", file_server),
7877
]
79-
80-
urlpatterns += [
81-
re_path(
82-
r"^editor-dev(?:/.*)?$",
83-
TemplateView.as_view(template_name="contentcuration/editor_dev.html"),
84-
),
85-
]

contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.spec.js

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import { AssessmentItemToolbarActions } from '../../constants';
44
import AnswersEditor from './AnswersEditor';
55
import { AssessmentItemTypes } from 'shared/constants';
66

7-
jest.mock('shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue');
8-
jest.mock('shared/views/MarkdownEditor/MarkdownViewer/MarkdownViewer.vue');
7+
jest.mock('shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue');
98

109
const clickNewAnswerBtn = async wrapper => {
1110
await wrapper.findComponent('[data-test="newAnswerBtn"]').trigger('click');
@@ -302,33 +301,37 @@ describe('AnswersEditor', () => {
302301
});
303302
});
304303

305-
// describe('on answer text update', () => {
306-
// beforeEach(async () => {
307-
// wrapper = mount(AnswersEditor, {
308-
// propsData: {
309-
// questionKind: AssessmentItemTypes.SINGLE_SELECTION,
310-
// answers: [
311-
// { answer: 'Mayonnaise (I mean you can, but...)', correct: true, order: 1 },
312-
// { answer: 'Peanut butter', correct: false, order: 2 },
313-
// ],
314-
// openAnswerIdx: 1,
315-
// },
316-
// });
317-
318-
// // only one editor is rendered at a time => "wrapper.find"
319-
// wrapper.findComponent({ name: 'MarkdownEditor' }).vm.$emit('update', 'European butter');
320-
// await wrapper.vm.$nextTick();
321-
// });
322-
323-
// it('emits update event with a payload containing updated answers', () => {
324-
// expect(wrapper.emitted().update).toBeTruthy();
325-
// expect(wrapper.emitted().update.length).toBe(1);
326-
// expect(wrapper.emitted().update[0][0]).toEqual([
327-
// { answer: 'Mayonnaise (I mean you can, but...)', correct: true, order: 1 },
328-
// { answer: 'European butter', correct: false, order: 2 },
329-
// ]);
330-
// });
331-
// });
304+
describe('on answer text update', () => {
305+
beforeEach(async () => {
306+
wrapper = mount(AnswersEditor, {
307+
propsData: {
308+
questionKind: AssessmentItemTypes.SINGLE_SELECTION,
309+
answers: [
310+
{ answer: 'Mayonnaise (I mean you can, but...)', correct: true, order: 1 },
311+
{ answer: 'Peanut butter', correct: false, order: 2 },
312+
],
313+
openAnswerIdx: 1,
314+
},
315+
});
316+
317+
const editors = wrapper.findAllComponents({ name: 'RichTextEditor' });
318+
editors.at(1).vm.$emit('update', 'European butter');
319+
320+
await wrapper.vm.$nextTick();
321+
});
322+
323+
it('emits update event with a payload containing updated answers', () => {
324+
expect(wrapper.emitted().update).toBeTruthy();
325+
expect(wrapper.emitted().update.length).toBe(1);
326+
327+
const emittedAnswers = JSON.parse(JSON.stringify(wrapper.emitted().update[0][0]));
328+
329+
expect(emittedAnswers).toEqual([
330+
{ answer: 'Mayonnaise (I mean you can, but...)', correct: true, order: 1 },
331+
{ answer: 'European butter', correct: false, order: 2 },
332+
]);
333+
});
334+
});
332335

333336
describe('on correct answer change', () => {
334337
beforeEach(async () => {

contentcuration/contentcuration/frontend/channelEdit/components/AnswersEditor/AnswersEditor.vue

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<div :class="indicatorClasses(answer)"></div>
2626
<VCardText :class="{ 'pb-0': !isAnswerOpen(answerIdx) }">
2727
<VLayout align-top>
28-
<VFlex xs1>
28+
<VFlex shrink>
2929
<!--
3030
VRadio cannot be used without VRadioGroup like VCheckbox but it can
3131
be solved by wrapping each VRadio to VRadioGroup
@@ -52,7 +52,7 @@
5252
/>
5353
</VFlex>
5454

55-
<VFlex xs7>
55+
<VFlex xs10>
5656
<keep-alive :max="5">
5757
<!-- Input question shows a text field with type of `number` -->
5858
<div v-if="isInputQuestion">
@@ -73,29 +73,22 @@
7373
</div>
7474

7575
<div v-else>
76-
<!-- <MarkdownEditor
77-
v-if="isAnswerOpen(answerIdx)"
76+
<!-- ?? analyticsLabel="Answer" -->
77+
<TipTapEditor
78+
v-model="answer.answer"
7879
class="editor"
79-
analyticsLabel="Answer"
80-
:markdown="answer.answer"
81-
:handleFileUpload="handleFileUpload"
82-
:getFileUpload="getFileUpload"
83-
:imagePreset="imagePreset"
80+
:mode="isAnswerOpen(answerIdx) ? 'edit' : 'view'"
8481
@update="updateAnswerText($event, answerIdx)"
8582
@minimize="emitClose"
83+
@open-editor="emitOpen(answerIdx)"
8684
/>
87-
<MarkdownViewer
88-
v-else
89-
:markdown="answer.answer"
90-
/> -->
91-
<TipTapEditor v-model="answer.answer" />
9285
</div>
9386
</keep-alive>
9487
</VFlex>
9588

9689
<VSpacer />
9790

98-
<VFlex>
91+
<VFlex shrink>
9992
<AssessmentItemToolbar
10093
:iconActionsConfig="toolbarIconActions"
10194
:canMoveUp="!isAnswerFirst(answerIdx)"
@@ -128,17 +121,13 @@
128121

129122
<script>
130123
131-
/* eslint-disable */
132-
133124
import AssessmentItemToolbar from '../AssessmentItemToolbar';
134125
import { AssessmentItemToolbarActions } from '../../constants';
135126
import { floatOrIntRegex, getCorrectAnswersIndices, mapCorrectAnswers } from '../../utils';
136127
import { AssessmentItemTypes } from 'shared/constants';
137128
import { swapElements } from 'shared/utils/helpers';
138129
import Checkbox from 'shared/views/form/Checkbox';
139130
140-
import MarkdownEditor from 'shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor';
141-
import MarkdownViewer from 'shared/views/MarkdownEditor/MarkdownViewer/MarkdownViewer';
142131
import TipTapEditor from 'shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue';
143132
144133
const updateAnswersOrder = answers => {
@@ -154,8 +143,6 @@
154143
name: 'AnswersEditor',
155144
components: {
156145
AssessmentItemToolbar,
157-
MarkdownEditor,
158-
MarkdownViewer,
159146
Checkbox,
160147
TipTapEditor,
161148
},
@@ -179,20 +166,6 @@
179166
type: Number,
180167
default: 0,
181168
},
182-
// Inject function to handle file uploads
183-
handleFileUpload: {
184-
type: Function,
185-
default: () => {},
186-
},
187-
// Inject function to get file upload object
188-
getFileUpload: {
189-
type: Function,
190-
default: () => {},
191-
},
192-
imagePreset: {
193-
type: String,
194-
default: null,
195-
},
196169
},
197170
data() {
198171
return {
@@ -290,7 +263,7 @@
290263
if (
291264
!this.shouldHaveOneCorrectAnswer &&
292265
JSON.stringify([...newIndices].sort()) ===
293-
JSON.stringify([...this.correctAnswersIndices].sort())
266+
JSON.stringify([...this.correctAnswersIndices].sort())
294267
) {
295268
return;
296269
}
@@ -429,9 +402,12 @@
429402
numberFieldErrorLabel: 'Answer must be a number',
430403
},
431404
};
405+
432406
</script>
433407

408+
434409
<style lang="scss" scoped>
410+
435411
$exercise-answer-correct: #4caf50;
436412
$exercise-answer-wrong: #ef5350;
437413
@@ -484,4 +460,5 @@
484460
::v-deep .no-border.v-text-field > .v-input__control > .v-input__slot::after {
485461
border-style: none;
486462
}
463+
487464
</style>

contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.spec.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import { assessmentItemKey } from '../../utils';
55
import AssessmentEditor from './AssessmentEditor';
66
import { AssessmentItemTypes, ValidationErrors, DELAYED_VALIDATION } from 'shared/constants';
77

8-
jest.mock('shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue');
9-
jest.mock('shared/views/MarkdownEditor/MarkdownViewer/MarkdownViewer.vue');
8+
jest.mock('shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue');
109

1110
const NODE_ID = 'node-id';
1211
const ITEM1 = {
@@ -184,10 +183,18 @@ describe('AssessmentEditor', () => {
184183

185184
expect(items.length).toBe(4);
186185

187-
expect(items.at(0).html()).toContain(ITEM1.question);
188-
expect(items.at(1).html()).toContain(ITEM2.question);
189-
expect(items.at(2).html()).toContain(ITEM3.question);
190-
expect(items.at(3).html()).toContain(ITEM4.question);
186+
expect(items.at(0).findComponent({ name: 'RichTextEditor' }).props('value')).toBe(
187+
ITEM1.question,
188+
);
189+
expect(items.at(1).findComponent({ name: 'RichTextEditor' }).props('value')).toBe(
190+
ITEM2.question,
191+
);
192+
expect(items.at(2).findComponent({ name: 'RichTextEditor' }).props('value')).toBe(
193+
ITEM3.question,
194+
);
195+
expect(items.at(3).findComponent({ name: 'RichTextEditor' }).props('value')).toBe(
196+
ITEM4.question,
197+
);
191198
});
192199

193200
it('renders items as closed', () => {

contentcuration/contentcuration/frontend/channelEdit/components/AssessmentEditor/AssessmentEditor.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
<VCardText>
2828
<VLayout align-start>
2929
<VFlex
30-
xs1
30+
:style="{ 'margin-right': '1.5rem' }"
31+
shrink
3132
mt-2
3233
>
3334
{{ idx + 1 }}
@@ -45,7 +46,7 @@
4546

4647
<VFlex
4748
v-else
48-
xs10
49+
xs11
4950
>
5051
<AssessmentItemEditor
5152
:item="item"

contentcuration/contentcuration/frontend/channelEdit/components/AssessmentItemEditor/AssessmentItemEditor.spec.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import { AssessmentItemTypes, ValidationErrors } from 'shared/constants';
77

88
const store = factory();
99

10-
jest.mock('shared/views/MarkdownEditor/MarkdownEditor/MarkdownEditor.vue');
11-
jest.mock('shared/views/MarkdownEditor/MarkdownViewer/MarkdownViewer.vue');
10+
jest.mock('shared/views/TipTapEditor/TipTapEditor/TipTapEditor.vue');
1211

1312
const listeners = {
1413
update: jest.fn(),
@@ -34,8 +33,7 @@ const openQuestion = async wrapper => {
3433
};
3534

3635
const updateQuestion = async (wrapper, newQuestionText) => {
37-
// only one editor is rendered at a time => "wrapper.find"
38-
wrapper.findComponent({ name: 'MarkdownEditor' }).vm.$emit('update', newQuestionText);
36+
wrapper.findComponent({ name: 'RichTextEditor' }).vm.$emit('update', newQuestionText);
3937
await wrapper.vm.$nextTick();
4038
};
4139

@@ -63,13 +61,16 @@ describe('AssessmentItemEditor', () => {
6361
listeners,
6462
});
6563

66-
expect(wrapper.html()).toContain('Exercise 2 - Question 2');
64+
// Find the first RichTextEditor component, which is used for the question.
65+
const questionEditor = wrapper.findComponent({ name: 'RichTextEditor' });
66+
expect(questionEditor.exists()).toBe(true);
6767

68-
// expect(wrapper.html()).toContain('Mayonnaise (I mean you can, but...)');
69-
// expect(wrapper.html()).toContain('Peanut butter');
68+
// Assert that it received the correct `value` prop.
69+
expect(questionEditor.props('value')).toBe('Exercise 2 - Question 2');
7070

71-
// expect(wrapper.html()).toContain("It's not healthy");
72-
// expect(wrapper.html()).toContain('Tasty!');
71+
// Check that the child editor components also exist.
72+
expect(wrapper.findComponent({ name: 'AnswersEditor' }).exists()).toBe(true);
73+
expect(wrapper.findComponent({ name: 'HintsEditor' }).exists()).toBe(true);
7374
});
7475

7576
describe('on question text update', () => {

0 commit comments

Comments
 (0)