This repository has been archived by the owner on Jun 26, 2020. It is now read-only.
/
imagecaptionediting.js
493 lines (402 loc) · 18.3 KB
/
imagecaptionediting.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
/**
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
import ImageCaptionEditing from '../../src/imagecaption/imagecaptionediting';
import ImageEditing from '../../src/image/imageediting';
import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import ViewAttributeElement from '@ckeditor/ckeditor5-engine/src/view/attributeelement';
import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element';
import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
describe( 'ImageCaptionEditing', () => {
let editor, model, doc, view;
beforeEach( () => {
return VirtualTestEditor
.create( {
plugins: [ ImageCaptionEditing, ImageEditing, UndoEditing, Paragraph ]
} )
.then( newEditor => {
editor = newEditor;
model = editor.model;
doc = model.document;
view = editor.editing.view;
model.schema.register( 'widget' );
model.schema.extend( 'widget', { allowIn: '$root' } );
model.schema.extend( 'caption', { allowIn: 'widget' } );
model.schema.extend( '$text', { allowIn: 'widget' } );
editor.conversion.elementToElement( {
model: 'widget',
view: 'widget'
} );
} );
} );
it( 'should be loaded', () => {
expect( editor.plugins.get( ImageCaptionEditing ) ).to.be.instanceOf( ImageCaptionEditing );
} );
it( 'should set proper schema rules', () => {
expect( model.schema.checkChild( [ '$root', 'image' ], 'caption' ) ).to.be.true;
expect( model.schema.checkChild( [ '$root', 'image', 'caption' ], '$text' ) ).to.be.true;
expect( model.schema.isLimit( 'caption' ) ).to.be.true;
expect( model.schema.checkChild( [ '$root', 'image', 'caption' ], 'caption' ) ).to.be.false;
model.schema.extend( '$block', { allowAttributes: 'aligmnent' } );
expect( model.schema.checkAttribute( [ '$root', 'image', 'caption' ], 'alignment' ) ).to.be.false;
} );
describe( 'data pipeline', () => {
describe( 'view to model', () => {
it( 'should convert figcaption inside image figure', () => {
editor.setData( '<figure class="image"><img src="foo.png" /><figcaption>foo bar</figcaption></figure>' );
expect( getModelData( model, { withoutSelection: true } ) )
.to.equal( '<image src="foo.png"><caption>foo bar</caption></image>' );
} );
it( 'should add empty caption if there is no figcaption', () => {
editor.setData( '<figure class="image"><img src="foo.png" /></figure>' );
expect( getModelData( model, { withoutSelection: true } ) )
.to.equal( '<image src="foo.png"><caption></caption></image>' );
} );
it( 'should not convert figcaption inside other elements than image', () => {
editor.setData( '<widget><figcaption>foobar</figcaption></widget>' );
expect( getModelData( model, { withoutSelection: true } ) )
.to.equal( '<widget>foobar</widget>' );
} );
} );
describe( 'model to view', () => {
it( 'should convert caption element to figcaption', () => {
setModelData( model, '<image src="img.png"><caption>Foo bar baz.</caption></image>' );
expect( editor.getData() ).to.equal(
'<figure class="image"><img src="img.png"><figcaption>Foo bar baz.</figcaption></figure>'
);
} );
it( 'should not convert caption to figcaption if it\'s empty', () => {
setModelData( model, '<image src="img.png"><caption></caption></image>' );
expect( editor.getData() ).to.equal( '<figure class="image"><img src="img.png"></figure>' );
} );
it( 'should not convert caption from other elements', () => {
setModelData( model, '<widget>foo bar<caption></caption></widget>' );
expect( editor.getData() ).to.equal( '<widget>foo bar</widget>' );
} );
} );
} );
describe( 'editing pipeline', () => {
describe( 'model to view', () => {
it( 'should convert caption element to figcaption contenteditable', () => {
setModelData( model, '<image src="img.png"><caption>Foo bar baz.</caption></image>' );
expect( getViewData( view, { withoutSelection: true } ) ).to.equal(
'<figure class="ck ck-widget image" contenteditable="false">' +
'<img src="img.png"></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable" ' +
'contenteditable="true" data-placeholder="Enter image caption">' +
'Foo bar baz.' +
'</figcaption>' +
'</figure>'
);
} );
it( 'should convert caption to element with proper CSS class if it\'s empty', () => {
setModelData( model, '<paragraph>foo</paragraph><image src="img.png"><caption></caption></image>' );
expect( getViewData( view, { withoutSelection: true } ) ).to.equal(
'<p>foo</p>' +
'<figure class="ck ck-widget image" contenteditable="false">' +
'<img src="img.png"></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable ck-hidden ck-placeholder" ' +
'contenteditable="true" data-placeholder="Enter image caption">' +
'</figcaption>' +
'</figure>'
);
} );
it( 'should not convert caption from other elements', () => {
setModelData( model, '<widget>foo bar<caption></caption></widget>' );
expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '<widget>foo bar</widget>' );
} );
it( 'should not convert when element is already consumed', () => {
editor.editing.downcastDispatcher.on(
'insert:caption',
( evt, data, conversionApi ) => {
conversionApi.consumable.consume( data.item, 'insert' );
const imageFigure = conversionApi.mapper.toViewElement( data.range.start.parent );
const viewElement = new ViewAttributeElement( 'span' );
const viewPosition = ViewPosition.createAt( imageFigure, 'end' );
conversionApi.mapper.bindElements( data.item, viewElement );
conversionApi.writer.insert( viewPosition, viewElement );
},
{ priority: 'high' }
);
setModelData( model, '<image src="img.png"><caption>Foo bar baz.</caption></image>' );
expect( getViewData( view, { withoutSelection: true } ) ).to.equal(
'<figure class="ck ck-widget image" contenteditable="false"><img src="img.png"></img><span></span>Foo bar baz.</figure>'
);
} );
it( 'should show caption when something is inserted inside', () => {
setModelData( model, '<paragraph>foo</paragraph><image src="img.png"><caption></caption></image>' );
const image = doc.getRoot().getChild( 1 );
const caption = image.getChild( 0 );
model.change( writer => {
writer.insertText( 'foo bar', caption );
} );
expect( getViewData( view ) ).to.equal(
'<p>{}foo</p>' +
'<figure class="ck ck-widget image" contenteditable="false">' +
'<img src="img.png"></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable" ' +
'contenteditable="true" data-placeholder="Enter image caption">' +
'foo bar' +
'</figcaption>' +
'</figure>'
);
} );
it( 'should hide when everything is removed from caption', () => {
setModelData( model, '<paragraph>foo</paragraph><image src="img.png"><caption>foo bar baz</caption></image>' );
const image = doc.getRoot().getChild( 1 );
const caption = image.getChild( 0 );
model.change( writer => {
writer.remove( ModelRange.createIn( caption ) );
} );
expect( getViewData( view ) ).to.equal(
'<p>{}foo</p>' +
'<figure class="ck ck-widget image" contenteditable="false">' +
'<img src="img.png"></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable ck-hidden ck-placeholder" ' +
'contenteditable="true" data-placeholder="Enter image caption">' +
'</figcaption>' +
'</figure>'
);
} );
it( 'should show when not everything is removed from caption', () => {
setModelData( model, '<paragraph>foo</paragraph><image src="img.png"><caption>foo bar baz</caption></image>' );
const image = doc.getRoot().getChild( 1 );
const caption = image.getChild( 0 );
model.change( writer => {
writer.remove( ModelRange.createFromParentsAndOffsets( caption, 0, caption, 8 ) );
} );
expect( getViewData( view ) ).to.equal(
'<p>{}foo</p>' +
'<figure class="ck ck-widget image" contenteditable="false">' +
'<img src="img.png"></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable" ' +
'contenteditable="true" data-placeholder="Enter image caption">baz</figcaption>' +
'</figure>'
);
} );
} );
} );
describe( 'inserting image to the document', () => {
it( 'should add caption element if image does not have it', () => {
model.change( writer => {
writer.insertElement( 'image', { src: '', alt: '' }, doc.getRoot() );
} );
expect( getModelData( model ) ).to.equal(
'[<image alt="" src=""><caption></caption></image>]<paragraph></paragraph>'
);
expect( getViewData( view ) ).to.equal(
'[<figure class="ck ck-widget image" contenteditable="false">' +
'<img alt="" src=""></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable ck-placeholder" ' +
'contenteditable="true" data-placeholder="Enter image caption">' +
'</figcaption>' +
'</figure>]' +
'<p></p>'
);
} );
it( 'should not add caption element if image already have it', () => {
const caption = new ModelElement( 'caption', null, 'foo bar' );
const image = new ModelElement( 'image', { src: '', alt: '' }, caption );
model.change( writer => {
writer.insert( image, doc.getRoot() );
} );
expect( getModelData( model ) ).to.equal(
'[<image alt="" src=""><caption>foo bar</caption></image>]<paragraph></paragraph>'
);
expect( getViewData( view ) ).to.equal(
'[<figure class="ck ck-widget image" contenteditable="false">' +
'<img alt="" src=""></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable" ' +
'contenteditable="true" data-placeholder="Enter image caption">' +
'foo bar' +
'</figcaption>' +
'</figure>]' +
'<p></p>'
);
} );
it( 'should not add caption element twice', () => {
const image = new ModelElement( 'image', { src: '', alt: '' } );
const caption = new ModelElement( 'caption' );
model.change( writer => {
// Since we are adding an empty image, this should trigger caption fixer.
writer.insert( image, doc.getRoot() );
// Add caption just after the image is inserted, in same batch.
writer.insert( caption, image );
} );
// Check whether caption fixer added redundant caption.
expect( getModelData( model ) ).to.equal(
'[<image alt="" src=""><caption></caption></image>]<paragraph></paragraph>'
);
expect( getViewData( view ) ).to.equal(
'[<figure class="ck ck-widget image" contenteditable="false">' +
'<img alt="" src=""></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable ck-placeholder" ' +
'contenteditable="true" data-placeholder="Enter image caption"></figcaption>' +
'</figure>]' +
'<p></p>'
);
} );
it( 'should do nothing for other changes than insert', () => {
setModelData( model, '<image src=""><caption>foo bar</caption></image>' );
const image = doc.getRoot().getChild( 0 );
model.change( writer => {
writer.setAttribute( 'alt', 'alt text', image );
} );
expect( getModelData( model, { withoutSelection: true } ) ).to.equal(
'<image alt="alt text" src=""><caption>foo bar</caption></image>'
);
} );
} );
describe( 'editing view', () => {
it( 'image should have empty figcaption element when is selected', () => {
setModelData( model, '<paragraph>foo</paragraph>[<image src=""><caption></caption></image>]' );
expect( getViewData( view ) ).to.equal(
'<p>foo</p>' +
'[<figure class="ck ck-widget image" contenteditable="false">' +
'<img src=""></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable ck-placeholder" ' +
'contenteditable="true" data-placeholder="Enter image caption">' +
'</figcaption>' +
'</figure>]'
);
} );
it( 'image should have empty figcaption element with hidden class when not selected', () => {
setModelData( model, '<paragraph>[]foo</paragraph><image src=""><caption></caption></image>' );
expect( getViewData( view ) ).to.equal(
'<p>{}foo</p>' +
'<figure class="ck ck-widget image" contenteditable="false">' +
'<img src=""></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable ck-hidden ck-placeholder" ' +
'contenteditable="true" data-placeholder="Enter image caption">' +
'</figcaption>' +
'</figure>'
);
} );
it( 'should not add additional figcaption if one is already present', () => {
setModelData( model, '<paragraph>foo</paragraph>[<image src=""><caption>foo bar</caption></image>]' );
expect( getViewData( view ) ).to.equal(
'<p>foo</p>' +
'[<figure class="ck ck-widget image" contenteditable="false">' +
'<img src=""></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable" ' +
'contenteditable="true" data-placeholder="Enter image caption">foo bar</figcaption>' +
'</figure>]'
);
} );
it( 'should add hidden class to figcaption when caption is empty and image is no longer selected', () => {
setModelData( model, '<paragraph>foo</paragraph>[<image src=""><caption></caption></image>]' );
model.change( writer => {
writer.setSelection( null );
} );
expect( getViewData( view ) ).to.equal(
'<p>{}foo</p>' +
'<figure class="ck ck-widget image" contenteditable="false">' +
'<img src=""></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable ck-hidden ck-placeholder" ' +
'contenteditable="true" data-placeholder="Enter image caption">' +
'</figcaption>' +
'</figure>'
);
} );
it( 'should not remove figcaption when selection is inside it even when it is empty', () => {
setModelData( model, '<image src=""><caption>[foo bar]</caption></image>' );
model.change( writer => {
writer.remove( doc.selection.getFirstRange() );
} );
expect( getViewData( view ) ).to.equal(
'<figure class="ck ck-widget image" contenteditable="false">' +
'<img src=""></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable ck-placeholder" ' +
'contenteditable="true" data-placeholder="Enter image caption">' +
'[]' +
'</figcaption>' +
'</figure>'
);
} );
it( 'should not remove figcaption when selection is moved from it to its image', () => {
setModelData( model, '<image src=""><caption>[foo bar]</caption></image>' );
const image = doc.getRoot().getChild( 0 );
model.change( writer => {
writer.remove( doc.selection.getFirstRange() );
writer.setSelection( ModelRange.createOn( image ) );
} );
expect( getViewData( view ) ).to.equal(
'[<figure class="ck ck-widget image" contenteditable="false">' +
'<img src=""></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable ck-placeholder" ' +
'contenteditable="true" data-placeholder="Enter image caption"></figcaption>' +
'</figure>]'
);
} );
it( 'should not remove figcaption when selection is moved from it to other image', () => {
setModelData( model, '<image src=""><caption>[foo bar]</caption></image><image src=""><caption></caption></image>' );
const image = doc.getRoot().getChild( 1 );
model.change( writer => {
writer.setSelection( ModelRange.createOn( image ) );
} );
expect( getViewData( view ) ).to.equal(
'<figure class="ck ck-widget image" contenteditable="false">' +
'<img src=""></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable" ' +
'contenteditable="true" data-placeholder="Enter image caption">foo bar</figcaption>' +
'</figure>' +
'[<figure class="ck ck-widget image" contenteditable="false">' +
'<img src=""></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable ck-placeholder" ' +
'contenteditable="true" data-placeholder="Enter image caption"></figcaption>' +
'</figure>]'
);
} );
describe( 'undo/redo integration', () => {
it( 'should create view element after redo', () => {
setModelData( model, '<paragraph>foo</paragraph><image src=""><caption>[foo bar baz]</caption></image>' );
const modelRoot = doc.getRoot();
const modelImage = modelRoot.getChild( 1 );
const modelCaption = modelImage.getChild( 0 );
// Remove text and selection from caption.
model.change( writer => {
writer.remove( ModelRange.createIn( modelCaption ) );
writer.setSelection( null );
} );
// Check if there is no figcaption in the view.
expect( getViewData( view ) ).to.equal(
'<p>{}foo</p>' +
'<figure class="ck ck-widget image" contenteditable="false">' +
'<img src=""></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable ck-hidden ck-placeholder" ' +
'contenteditable="true" data-placeholder="Enter image caption">' +
'</figcaption>' +
'</figure>'
);
editor.execute( 'undo' );
// Check if figcaption is back with contents.
expect( getViewData( view ) ).to.equal(
'<p>foo</p>' +
'<figure class="ck ck-widget image" contenteditable="false">' +
'<img src=""></img>' +
'<figcaption class="ck ck-editor__editable ck-editor__nested-editable" ' +
'contenteditable="true" data-placeholder="Enter image caption">' +
'{foo bar baz}' +
'</figcaption>' +
'</figure>'
);
} );
it( 'undo should work after inserting the image', () => {
setModelData( model, '<paragraph>foo[]</paragraph>' );
model.change( writer => {
const image = writer.createElement( 'image', { src: '/foo.png' } );
writer.insert( image, doc.getRoot() );
} );
expect( getModelData( model ) ).to.equal( '<image src="/foo.png"><caption></caption></image><paragraph>foo[]</paragraph>' );
editor.execute( 'undo' );
expect( getModelData( model ) ).to.equal( '<paragraph>foo[]</paragraph>' );
} );
} );
} );
} );