Skip to content

Commit ca0b255

Browse files
authored
Merge pull request #2398 from tf/editor-commenting
Integrate commenting into the editor
2 parents e6619cd + 1271af5 commit ca0b255

Some content is hidden

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

41 files changed

+755
-82
lines changed

entry_types/scrolled/app/helpers/pageflow_scrolled/packs_helper.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ def scrolled_editor_packs(entry)
5050
end
5151

5252
def scrolled_editor_stylesheet_packs(entry)
53-
Pageflow.config_for(entry).additional_editor_packs.stylesheet_paths(entry) +
53+
['pageflow-scrolled-editor'] +
54+
Pageflow.config_for(entry).additional_editor_packs.stylesheet_paths(entry) +
5455
Pageflow.config_for(entry).additional_frontend_packs.paths(entry)
5556
end
5657

entry_types/scrolled/package/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/node_modules
22
/contentElements
33
/editor.js
4+
/editor.css
45
/frontend
56
/frontend-server.js
67
/contentElements-editor.js

entry_types/scrolled/package/config/webpack.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ module.exports = {
8080
]
8181
},
8282
'pageflow-scrolled-frontend-inlineEditing': {
83-
import: ['pageflow-scrolled/frontend/inlineEditing.css']
83+
import: [
84+
'pageflow-scrolled/frontend/inlineEditing.css',
85+
'pageflow-scrolled/review.css'
86+
]
8487
},
8588
'pageflow-scrolled-frontend-commenting': {
8689
import: [

entry_types/scrolled/package/jest.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ module.exports = {
2222

2323
moduleNameMapper: {
2424
'^pageflow-scrolled/contentElements-frontend$': '<rootDir>/src/contentElements/frontend',
25+
"^pageflow-scrolled/editor\\.css$": "<rootDir>/spec/support/jest/editor-css-stub",
26+
"^pageflow-scrolled/review\\.css$": "<rootDir>/spec/support/jest/review-css-stub",
2527
"^pageflow-scrolled/([^/]*)$": "<rootDir>/src/$1",
2628

2729
// Make specs run even if ignored json file is not present

entry_types/scrolled/package/spec/editor/controllers/PreviewMessageController-spec.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'editor/config';
22
import {editor} from 'pageflow-scrolled/editor';
3+
import {features} from 'pageflow/frontend';
34
import {ScrolledEntry} from 'editor/models/ScrolledEntry';
45
import {PreviewMessageController} from 'editor/controllers/PreviewMessageController';
56
import {InsertContentElementDialogView} from 'editor/views/InsertContentElementDialogView';
@@ -47,6 +48,31 @@ describe('PreviewMessageController', () => {
4748
})).resolves.toMatchObject({type: 'ACK'});
4849
});
4950

51+
it('sends REVIEW_STATE_RESET to iframe after READY when commenting enabled', () => {
52+
features.enable('frontend', ['commenting']);
53+
jest.spyOn(window, 'fetch').mockResolvedValue({
54+
ok: true,
55+
json: () => Promise.resolve({currentUser: {id: 1}, commentThreads: []})
56+
});
57+
58+
const entry = factories.entry(ScrolledEntry, {}, {entryTypeSeed: normalizeSeed()});
59+
const iframeWindow = createIframeWindow();
60+
61+
controller = new PreviewMessageController({entry, iframeWindow});
62+
63+
return expect(new Promise(resolve => {
64+
iframeWindow.addEventListener('message', event => {
65+
if (event.data.type === 'REVIEW_STATE_RESET') {
66+
resolve(event.data);
67+
}
68+
});
69+
window.postMessage({type: 'READY'}, '*');
70+
})).resolves.toMatchObject({
71+
type: 'REVIEW_STATE_RESET',
72+
payload: {currentUser: {id: 1}, commentThreads: []}
73+
});
74+
});
75+
5076
it('sets current section index in model on CHANGE_SECTION message', () => {
5177
const entry = factories.entry(ScrolledEntry, {}, {entryTypeSeed: normalizeSeed()});
5278
const iframeWindow = createIframeWindow();
@@ -469,6 +495,22 @@ describe('PreviewMessageController', () => {
469495
})).resolves.toBe('/');
470496
});
471497

498+
it('navigates to comments route on SELECTED message for contentElementComments', () => {
499+
const editor = factories.editorApi();
500+
const entry = factories.entry(ScrolledEntry, {}, {
501+
entryTypeSeed: normalizeSeed({
502+
contentElements: [{id: 1}]
503+
})
504+
});
505+
const iframeWindow = createIframeWindow();
506+
controller = new PreviewMessageController({entry, iframeWindow, editor});
507+
508+
return expect(new Promise(resolve => {
509+
editor.on('navigate', resolve);
510+
window.postMessage({type: 'SELECTED', payload: {id: 1, type: 'contentElementComments'}}, '*');
511+
})).resolves.toBe('/scrolled/content_elements/1/comments');
512+
});
513+
472514
it('updates configuration on UPDATE_CONTENT_ELEMENT message', () => {
473515
const entry = factories.entry(ScrolledEntry, {}, {
474516
entryTypeSeed: normalizeSeed({
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import '@testing-library/jest-dom/extend-expect';
2+
import BackboneEvents from 'backbone-events-standalone';
3+
4+
import {ContentElementCommentsView} from 'editor/views/ContentElementCommentsView';
5+
6+
import {useFakeTranslations, renderBackboneView} from 'pageflow/testHelpers';
7+
import {useEditorGlobals} from 'support';
8+
import {act, waitFor} from '@testing-library/react';
9+
10+
describe('ContentElementCommentsView', () => {
11+
const {createEntry} = useEditorGlobals();
12+
13+
useFakeTranslations({
14+
'pageflow_scrolled.review.add_comment_placeholder': 'Add a comment...',
15+
'pageflow_scrolled.review.new_topic': 'New topic',
16+
'pageflow_scrolled.review.send': 'Send'
17+
});
18+
19+
it('displays threads from session state', () => {
20+
const entry = createEntry({
21+
contentElements: [{id: 1, permaId: 10, typeName: 'textBlock'}]
22+
});
23+
entry.reviewSession = fakeReviewSession({
24+
currentUser: {id: 1},
25+
commentThreads: [{
26+
id: 1,
27+
subjectType: 'ContentElement',
28+
subjectId: 10,
29+
comments: [{id: 100, body: 'Looks good', creatorName: 'Alice'}]
30+
}]
31+
});
32+
33+
const view = new ContentElementCommentsView({
34+
entry,
35+
model: entry.contentElements.get(1),
36+
editor: {}
37+
});
38+
39+
const {getByText} = renderBackboneView(view);
40+
view.onShow();
41+
42+
expect(getByText('Looks good')).toBeInTheDocument();
43+
});
44+
45+
it('updates when session emits change:thread', async () => {
46+
const entry = createEntry({
47+
contentElements: [{id: 1, permaId: 10, typeName: 'textBlock'}]
48+
});
49+
entry.reviewSession = fakeReviewSession({
50+
currentUser: {id: 1},
51+
commentThreads: []
52+
});
53+
54+
const view = new ContentElementCommentsView({
55+
entry,
56+
model: entry.contentElements.get(1),
57+
editor: {}
58+
});
59+
60+
const {getByText} = renderBackboneView(view);
61+
act(() => view.onShow());
62+
63+
act(() => {
64+
entry.reviewSession.trigger('change:thread', {
65+
id: 1,
66+
subjectType: 'ContentElement',
67+
subjectId: 10,
68+
comments: [{id: 100, body: 'New comment', creatorName: 'Bob'}]
69+
});
70+
});
71+
72+
await waitFor(() => {
73+
expect(getByText('New comment')).toBeInTheDocument();
74+
});
75+
});
76+
});
77+
78+
function fakeReviewSession(state = null) {
79+
const session = {
80+
state,
81+
createThread: jest.fn().mockResolvedValue(),
82+
createComment: jest.fn().mockResolvedValue()
83+
};
84+
85+
Object.assign(session, BackboneEvents);
86+
return session;
87+
}

entry_types/scrolled/package/spec/frontend/features/contentElementSelection-spec.js

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import React from 'react';
22
import {frontend} from 'frontend';
3+
import {features} from 'pageflow/frontend';
34

45
import {useInlineEditingPageObjects, renderEntry} from 'support/pageObjects';
56
import {fakeParentWindow} from 'support';
67
import {changeLocationHash} from 'support/changeLocationHash';
78
import '@testing-library/jest-dom/extend-expect'
8-
import {act} from '@testing-library/react';
9+
import {act, fireEvent, waitFor} from '@testing-library/react';
910

1011
describe('content element selection', () => {
1112
useInlineEditingPageObjects();
@@ -99,4 +100,48 @@ describe('content element selection', () => {
99100

100101
expect(mainContentElement.isSelected()).toBe(false);
101102
});
103+
104+
it('marks selection rect as selected when comment badge is clicked', async () => {
105+
features.enable('frontend', ['commenting']);
106+
107+
const {getByRole, getContentElementByTestId} = renderEntry({
108+
seed: {
109+
contentElements: [{
110+
id: 1,
111+
permaId: 10,
112+
typeName: 'withTestId',
113+
configuration: {testId: 5}
114+
}]
115+
}
116+
});
117+
118+
act(() => {
119+
window.dispatchEvent(new MessageEvent('message', {
120+
data: {
121+
type: 'REVIEW_STATE_RESET',
122+
payload: {
123+
currentUser: {id: 1},
124+
commentThreads: [{
125+
id: 1,
126+
subjectType: 'ContentElement',
127+
subjectId: 10,
128+
comments: [{id: 100, body: 'Review this'}]
129+
}]
130+
}
131+
},
132+
origin: window.location.origin
133+
}));
134+
});
135+
136+
await waitFor(() => {
137+
expect(getByRole('status')).toBeInTheDocument();
138+
});
139+
140+
const contentElement = getContentElementByTestId(5);
141+
expect(contentElement.isSelected()).toBe(false);
142+
143+
fireEvent.click(getByRole('status'));
144+
145+
expect(contentElement.isSelected()).toBe(true);
146+
});
102147
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import '@testing-library/jest-dom/extend-expect';
2+
import {act, waitFor} from '@testing-library/react';
3+
import {features} from 'pageflow/frontend';
4+
5+
import {useInlineEditingPageObjects, renderEntry} from 'support/pageObjects';
6+
import {fakeParentWindow} from 'support';
7+
8+
describe('editor comment badges', () => {
9+
useInlineEditingPageObjects();
10+
11+
beforeEach(() => {
12+
fakeParentWindow();
13+
window.parent.postMessage = jest.fn();
14+
features.enable('frontend', ['commenting']);
15+
});
16+
17+
it('does not display comment icon when element is not selected', () => {
18+
const {queryByRole} = renderEntry({
19+
seed: {
20+
contentElements: [{
21+
typeName: 'withTestId',
22+
permaId: 10,
23+
configuration: {testId: 5}
24+
}]
25+
}
26+
});
27+
28+
expect(queryByRole('status')).not.toBeInTheDocument();
29+
});
30+
31+
it('displays dot badge when threads exist and element is not selected', async () => {
32+
const {getByRole} = renderEntry({
33+
seed: {
34+
contentElements: [{
35+
typeName: 'withTestId',
36+
permaId: 10,
37+
configuration: {testId: 5}
38+
}]
39+
}
40+
});
41+
42+
act(() => {
43+
window.dispatchEvent(new MessageEvent('message', {
44+
data: {
45+
type: 'REVIEW_STATE_RESET',
46+
payload: {
47+
currentUser: {id: 1},
48+
commentThreads: [{
49+
id: 1,
50+
subjectType: 'ContentElement',
51+
subjectId: 10,
52+
comments: [{id: 100, body: 'Review this'}]
53+
}]
54+
}
55+
},
56+
origin: window.location.origin
57+
}));
58+
});
59+
60+
await waitFor(() => {
61+
expect(getByRole('status')).toBeInTheDocument();
62+
expect(getByRole('status')).not.toHaveTextContent(/\d/);
63+
});
64+
});
65+
});

entry_types/scrolled/package/spec/frontend/features/selectedMessage-spec.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React from 'react';
22
import {frontend, WidgetSelectionRect} from 'frontend';
3+
import {features} from 'pageflow/frontend';
34

45
import {useInlineEditingPageObjects, renderEntry} from 'support/pageObjects';
56
import {fakeParentWindow} from 'support';
6-
import {fireEvent} from '@testing-library/react';
7+
import {act, fireEvent, waitFor} from '@testing-library/react';
78
import '@testing-library/jest-dom/extend-expect'
89

910
describe('SELECTED message', () => {
@@ -185,4 +186,43 @@ describe('SELECTED message', () => {
185186
payload: {id: 'header', type: 'widget'}
186187
}, expect.anything());
187188
});
189+
190+
it('is posted with type contentElementComments when comment badge is clicked', async () => {
191+
features.enable('frontend', ['commenting']);
192+
193+
const {getByRole} = renderEntry({
194+
seed: {
195+
contentElements: [{id: 1, permaId: 10, typeName: 'withTestId', configuration: {testId: 5}}]
196+
}
197+
});
198+
199+
act(() => {
200+
window.dispatchEvent(new MessageEvent('message', {
201+
data: {
202+
type: 'REVIEW_STATE_RESET',
203+
payload: {
204+
currentUser: {id: 1},
205+
commentThreads: [{
206+
id: 1,
207+
subjectType: 'ContentElement',
208+
subjectId: 10,
209+
comments: [{id: 100, body: 'Review this'}]
210+
}]
211+
}
212+
},
213+
origin: window.location.origin
214+
}));
215+
});
216+
217+
await waitFor(() => {
218+
expect(getByRole('status')).toBeInTheDocument();
219+
});
220+
221+
fireEvent.click(getByRole('status'));
222+
223+
expect(window.parent.postMessage).toHaveBeenCalledWith({
224+
type: 'SELECTED',
225+
payload: {id: 1, type: 'contentElementComments'}
226+
}, expect.anything());
227+
});
188228
});

0 commit comments

Comments
 (0)