Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit e6e92e9

Browse files
authored
Merge pull request #981 from ckeditor/t/699
Fix: `LiveSelection` will correctly set its properties in case of a non-collapsed default range. This will fix loading data which starts with an object element. Closes #699.
2 parents 31d8ce5 + 5abc583 commit e6e92e9

File tree

4 files changed

+232
-41
lines changed

4 files changed

+232
-41
lines changed

src/model/liveselection.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export default class LiveSelection extends Selection {
8888
get isCollapsed() {
8989
const length = this._ranges.length;
9090

91-
return length === 0 ? true : super.isCollapsed;
91+
return length === 0 ? this._document._getDefaultRange().isCollapsed : super.isCollapsed;
9292
}
9393

9494
/**
@@ -102,7 +102,7 @@ export default class LiveSelection extends Selection {
102102
* @inheritDoc
103103
*/
104104
get focus() {
105-
return super.focus || this._document._getDefaultRange().start;
105+
return super.focus || this._document._getDefaultRange().end;
106106
}
107107

108108
/**

tests/model/document/document.js

Lines changed: 102 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -280,12 +280,18 @@ describe( 'Document', () => {
280280

281281
beforeEach( () => {
282282
doc.schema.registerItem( 'paragraph', '$block' );
283+
283284
doc.schema.registerItem( 'emptyBlock' );
284285
doc.schema.allow( { name: 'emptyBlock', inside: '$root' } );
285-
doc.schema.registerItem( 'widget', '$block' );
286+
287+
doc.schema.registerItem( 'widget' );
286288
doc.schema.allow( { name: 'widget', inside: '$root' } );
287289
doc.schema.objects.add( 'widget' );
288290

291+
doc.schema.registerItem( 'blockWidget', '$block' );
292+
doc.schema.allow( { name: 'blockWidget', inside: '$root' } );
293+
doc.schema.objects.add( 'blockWidget' );
294+
289295
doc.createRoot();
290296
selection = doc.selection;
291297
} );
@@ -373,34 +379,6 @@ describe( 'Document', () => {
373379
null
374380
);
375381

376-
test(
377-
'should select nearest object - both',
378-
'<widget></widget>[]<widget></widget>',
379-
'both',
380-
'[<widget></widget>]<widget></widget>'
381-
);
382-
383-
test(
384-
'should select nearest object - forward',
385-
'<paragraph></paragraph>[]<widget></widget>',
386-
'forward',
387-
'<paragraph></paragraph>[<widget></widget>]'
388-
);
389-
390-
test(
391-
'should select nearest object - forward',
392-
'<paragraph></paragraph>[]<widget></widget>',
393-
'forward',
394-
'<paragraph></paragraph>[<widget></widget>]'
395-
);
396-
397-
test(
398-
'should select nearest object - backward',
399-
'<widget></widget>[]<paragraph></paragraph>',
400-
'backward',
401-
'[<widget></widget>]<paragraph></paragraph>'
402-
);
403-
404382
test(
405383
'should move forward when placed at root start',
406384
'[]<paragraph></paragraph><paragraph></paragraph>',
@@ -415,6 +393,101 @@ describe( 'Document', () => {
415393
'<paragraph></paragraph><paragraph>[]</paragraph>'
416394
);
417395

396+
describe( 'in case of objects which do not allow text inside', () => {
397+
test(
398+
'should select nearest object (o[]o) - both',
399+
'<widget></widget>[]<widget></widget>',
400+
'both',
401+
'[<widget></widget>]<widget></widget>'
402+
);
403+
404+
test(
405+
'should select nearest object (o[]o) - forward',
406+
'<widget></widget>[]<widget></widget>',
407+
'forward',
408+
'<widget></widget>[<widget></widget>]'
409+
);
410+
411+
test(
412+
'should select nearest object (o[]o) - backward',
413+
'<widget></widget>[]<widget></widget>',
414+
'both',
415+
'[<widget></widget>]<widget></widget>'
416+
);
417+
418+
test(
419+
'should select nearest object (p[]o) - forward',
420+
'<paragraph></paragraph>[]<widget></widget>',
421+
'forward',
422+
'<paragraph></paragraph>[<widget></widget>]'
423+
);
424+
425+
test(
426+
'should select nearest object (o[]p) - both',
427+
'<widget></widget>[]<paragraph></paragraph>',
428+
'both',
429+
'[<widget></widget>]<paragraph></paragraph>'
430+
);
431+
432+
test(
433+
'should select nearest object (o[]p) - backward',
434+
'<widget></widget>[]<paragraph></paragraph>',
435+
'backward',
436+
'[<widget></widget>]<paragraph></paragraph>'
437+
);
438+
439+
test(
440+
'should select nearest object ([]o) - both',
441+
'[]<widget></widget><paragraph></paragraph>',
442+
'both',
443+
'[<widget></widget>]<paragraph></paragraph>'
444+
);
445+
446+
test(
447+
'should select nearest object ([]o) - forward',
448+
'[]<widget></widget><paragraph></paragraph>',
449+
'forward',
450+
'[<widget></widget>]<paragraph></paragraph>'
451+
);
452+
453+
test(
454+
'should select nearest object (o[]) - both',
455+
'<paragraph></paragraph><widget></widget>[]',
456+
'both',
457+
'<paragraph></paragraph>[<widget></widget>]'
458+
);
459+
460+
test(
461+
'should select nearest object (o[]) - backward',
462+
'<paragraph></paragraph><widget></widget>[]',
463+
'both',
464+
'<paragraph></paragraph>[<widget></widget>]'
465+
);
466+
} );
467+
468+
describe( 'in case of objects which allow text inside', () => {
469+
test(
470+
'should select nearest object which allows text (o[]o) - both',
471+
'<blockWidget></blockWidget>[]<blockWidget></blockWidget>',
472+
'both',
473+
'[<blockWidget></blockWidget>]<blockWidget></blockWidget>'
474+
);
475+
476+
test(
477+
'should select nearest object (o[]p) - both',
478+
'<blockWidget></blockWidget>[]<paragraph></paragraph>',
479+
'both',
480+
'[<blockWidget></blockWidget>]<paragraph></paragraph>'
481+
);
482+
483+
test(
484+
'should select nearest object which allows text ([]o) - both',
485+
'[]<blockWidget></blockWidget><paragraph></paragraph>',
486+
'both',
487+
'[<blockWidget></blockWidget>]<paragraph></paragraph>'
488+
);
489+
} );
490+
418491
function test( testName, data, direction, expected ) {
419492
it( testName, () => {
420493
setData( doc, data );

tests/model/liveselection.js

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ describe( 'LiveSelection', () => {
4343
selection = doc.selection;
4444
doc.schema.registerItem( 'p', '$block' );
4545

46+
doc.schema.registerItem( 'widget' );
47+
doc.schema.objects.add( 'widget' );
48+
4649
liveRange = new LiveRange( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) );
4750
range = new Range( new Position( root, [ 2 ] ), new Position( root, [ 2, 2 ] ) );
4851
} );
@@ -99,9 +102,66 @@ describe( 'LiveSelection', () => {
99102
} );
100103

101104
describe( 'isCollapsed', () => {
102-
it( 'should return true for default range', () => {
105+
it( 'should be true for the default range (in text position)', () => {
103106
expect( selection.isCollapsed ).to.be.true;
104107
} );
108+
109+
it( 'should be false for the default range (object selection) ', () => {
110+
root.insertChildren( 0, new Element( 'widget' ) );
111+
112+
expect( selection.isCollapsed ).to.be.false;
113+
} );
114+
115+
it( 'should back off to the default algorithm if selection has ranges', () => {
116+
selection.addRange( range );
117+
118+
expect( selection.isCollapsed ).to.be.false;
119+
} );
120+
} );
121+
122+
describe( 'anchor', () => {
123+
it( 'should equal the default range\'s start (in text position)', () => {
124+
const expectedPos = new Position( root, [ 0, 0 ] );
125+
126+
expect( selection.anchor.isEqual( expectedPos ) ).to.be.true;
127+
} );
128+
129+
it( 'should equal the default range\'s start (object selection)', () => {
130+
root.insertChildren( 0, new Element( 'widget' ) );
131+
132+
const expectedPos = new Position( root, [ 0 ] );
133+
134+
expect( selection.anchor.isEqual( expectedPos ) ).to.be.true;
135+
} );
136+
137+
it( 'should back off to the default algorithm if selection has ranges', () => {
138+
selection.addRange( range );
139+
140+
expect( selection.anchor.isEqual( range.start ) ).to.be.true;
141+
} );
142+
} );
143+
144+
describe( 'focus', () => {
145+
it( 'should equal the default range\'s end (in text position)', () => {
146+
const expectedPos = new Position( root, [ 0, 0 ] );
147+
148+
expect( selection.focus.isEqual( expectedPos ) ).to.be.true;
149+
} );
150+
151+
it( 'should equal the default range\'s end (object selection)', () => {
152+
root.insertChildren( 0, new Element( 'widget' ) );
153+
154+
const expectedPos = new Position( root, [ 1 ] );
155+
156+
expect( selection.focus.isEqual( expectedPos ) ).to.be.true;
157+
expect( selection.focus.isEqual( selection.getFirstRange().end ) ).to.be.true;
158+
} );
159+
160+
it( 'should back off to the default algorithm if selection has ranges', () => {
161+
selection.addRange( range );
162+
163+
expect( selection.focus.isEqual( range.end ) ).to.be.true;
164+
} );
105165
} );
106166

107167
describe( 'rangeCount', () => {
@@ -118,7 +178,7 @@ describe( 'LiveSelection', () => {
118178
} );
119179
} );
120180

121-
describe( 'addRange', () => {
181+
describe( 'addRange()', () => {
122182
it( 'should convert added Range to LiveRange', () => {
123183
selection.addRange( range );
124184

@@ -149,7 +209,7 @@ describe( 'LiveSelection', () => {
149209
} );
150210
} );
151211

152-
describe( 'collapse', () => {
212+
describe( 'collapse()', () => {
153213
it( 'detaches all existing ranges', () => {
154214
selection.addRange( range );
155215
selection.addRange( liveRange );
@@ -161,7 +221,7 @@ describe( 'LiveSelection', () => {
161221
} );
162222
} );
163223

164-
describe( 'destroy', () => {
224+
describe( 'destroy()', () => {
165225
it( 'should unbind all events', () => {
166226
selection.addRange( liveRange );
167227
selection.addRange( range );
@@ -181,7 +241,7 @@ describe( 'LiveSelection', () => {
181241
} );
182242
} );
183243

184-
describe( 'setFocus', () => {
244+
describe( 'setFocus()', () => {
185245
it( 'modifies default range', () => {
186246
const startPos = selection.getFirstPosition();
187247
const endPos = Position.createAt( root, 'end' );
@@ -206,7 +266,7 @@ describe( 'LiveSelection', () => {
206266
} );
207267
} );
208268

209-
describe( 'removeAllRanges', () => {
269+
describe( 'removeAllRanges()', () => {
210270
let spy, ranges;
211271

212272
beforeEach( () => {
@@ -249,7 +309,7 @@ describe( 'LiveSelection', () => {
249309
} );
250310
} );
251311

252-
describe( 'setRanges', () => {
312+
describe( 'setRanges()', () => {
253313
it( 'should throw an error when range is invalid', () => {
254314
expect( () => {
255315
selection.setRanges( [ { invalid: 'range' } ] );
@@ -295,7 +355,7 @@ describe( 'LiveSelection', () => {
295355
} );
296356
} );
297357

298-
describe( 'getFirstRange', () => {
358+
describe( 'getFirstRange()', () => {
299359
it( 'should return default range if no ranges were added', () => {
300360
const firstRange = selection.getFirstRange();
301361

@@ -304,7 +364,7 @@ describe( 'LiveSelection', () => {
304364
} );
305365
} );
306366

307-
describe( 'getLastRange', () => {
367+
describe( 'getLastRange()', () => {
308368
it( 'should return default range if no ranges were added', () => {
309369
const lastRange = selection.getLastRange();
310370

@@ -313,7 +373,7 @@ describe( 'LiveSelection', () => {
313373
} );
314374
} );
315375

316-
describe( 'createFromSelection', () => {
376+
describe( 'createFromSelection()', () => {
317377
it( 'should return a LiveSelection instance', () => {
318378
selection.addRange( range, true );
319379

tests/tickets/699.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md.
4+
*/
5+
6+
/* globals document */
7+
8+
import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
9+
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
10+
11+
import buildViewConverter from '../../src/conversion/buildviewconverter';
12+
import buildModelConverter from '../../src/conversion/buildmodelconverter';
13+
14+
import { getData as getModelData } from '../../src/dev-utils/model';
15+
import { getData as getViewData } from '../../src/dev-utils/view';
16+
17+
describe( 'Bug ckeditor5-engine#699', () => {
18+
let element;
19+
20+
beforeEach( () => {
21+
element = document.createElement( 'div' );
22+
23+
document.body.appendChild( element );
24+
} );
25+
26+
afterEach( () => {
27+
element.remove();
28+
} );
29+
30+
it( 'the engine sets the initial selection on the first widget', () => {
31+
return ClassicTestEditor
32+
.create( element, { plugins: [ Paragraph, WidgetPlugin ] } )
33+
.then( editor => {
34+
editor.setData( '<widget></widget><p>foo</p>' );
35+
36+
expect( getModelData( editor.document ) ).to.equal( '[<widget></widget>]<paragraph>foo</paragraph>' );
37+
expect( getViewData( editor.editing.view ) ).to.equal( '[<widget></widget>]<p>foo</p>' );
38+
39+
return editor.destroy();
40+
} );
41+
} );
42+
} );
43+
44+
function WidgetPlugin( editor ) {
45+
const schema = editor.document.schema;
46+
47+
schema.registerItem( 'widget' );
48+
schema.allow( { name: 'widget', inside: '$root' } );
49+
schema.objects.add( 'widget' );
50+
51+
buildModelConverter().for( editor.data.modelToView, editor.editing.modelToView )
52+
.fromElement( 'widget' )
53+
.toElement( 'widget' );
54+
55+
buildViewConverter().for( editor.data.viewToModel )
56+
.fromElement( 'widget' )
57+
.toElement( 'widget' );
58+
}

0 commit comments

Comments
 (0)