/** * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license */ import { Client, syncClients, expectClients, clearBuffer } from './utils.js'; describe( 'transform', () => { let john, kate; beforeEach( () => { return Promise.all( [ Client.get( 'john' ).then( client => ( john = client ) ), Client.get( 'kate' ).then( client => ( kate = client ) ) ] ); } ); afterEach( () => { clearBuffer(); return Promise.all( [ john.destroy(), kate.destroy() ] ); } ); describe( 'attribute', () => { describe( 'by attribute', () => { it( 'in text at same path', () => { john.setData( '[Foo] Bar' ); kate.setData( 'Foo [Bar]' ); john.setAttribute( 'bold', true ); kate.setAttribute( 'italic', true ); syncClients(); expectClients( '<$text bold="true">Foo <$text italic="true">Bar' ); } ); it( 'in text at same path, then undo', () => { john.setData( '[Foo] Bar' ); kate.setData( 'Foo [Bar]' ); john.setAttribute( 'bold', true ); kate.setAttribute( 'italic', true ); syncClients(); expectClients( '<$text bold="true">Foo <$text italic="true">Bar' ); john.undo(); syncClients(); expectClients( 'Foo <$text italic="true">Bar' ); } ); it( 'in text in different path', () => { john.setData( 'F[o]oBar' ); kate.setData( 'FooB[a]r' ); john.setAttribute( 'bold', true ); kate.setAttribute( 'italic', true ); syncClients(); expectClients( 'F<$text bold="true">oo' + 'B<$text italic="true">ar' ); } ); it( 'in text with selection inside other client\'s selection #1', () => { john.setData( '[Foo Bar]' ); kate.setData( 'Fo[o B]ar' ); john.setAttribute( 'bold', true ); kate.setAttribute( 'italic', true ); syncClients(); expectClients( '' + '<$text bold="true">Fo<$text bold="true" italic="true">o B<$text bold="true">ar' + '' ); } ); it( 'in text with selection inside other client\'s selection #2', () => { john.setData( 'F[oo Ba]r' ); kate.setData( 'Foo [Bar]' ); john.setAttribute( 'bold', true ); kate.setAttribute( 'italic', true ); syncClients(); expectClients( '' + 'F' + '<$text bold="true">oo ' + '<$text bold="true" italic="true">Ba' + '<$text italic="true">r' + '' ); } ); it( 'in text with selection inside other client\'s selection #3', () => { john.setData( '<$text bold="true">[Foo Bar]' ); kate.setData( '<$text bold="true">Fo[o] Bar' ); john.setAttribute( 'italic', true ); kate.setAttribute( 'underline', true ); syncClients(); expectClients( '' + '<$text bold="true" italic="true">Fo' + '<$text bold="true" italic="true" underline="true">o' + '<$text bold="true" italic="true"> Bar' + '' ); } ); it( 'in text at same position', () => { john.setData( '[Foo Bar]' ); kate.setData( '[Foo Bar]' ); john.setAttribute( 'bold', true ); kate.setAttribute( 'italic', true ); syncClients(); expectClients( '<$text bold="true" italic="true">Foo Bar' ); } ); it( 'in element at same position', () => { john.setData( '[Foo Bar]' ); kate.setData( '[Foo Bar]' ); john.setAttribute( 'bold', true ); kate.setAttribute( 'italic', true ); syncClients(); expectClients( 'Foo Bar' ); } ); it( 'in collapsed selection', () => { john.setData( 'F[]oo Bar' ); kate.setData( 'F[]oo Bar' ); john.setAttribute( 'bold', true ); kate.setAttribute( 'italic', true ); syncClients(); expectClients( 'Foo Bar' ); } ); it( 'in same range, then change attribute', () => { john.setData( '[Foo Bar]' ); kate.setData( '[Foo Bar]' ); john.setAttribute( 'attr', 'foo' ); kate.setAttribute( 'attr', 'bar' ); syncClients(); expectClients( '<$text attr="foo">Foo Bar' ); kate.setAttribute( 'attr', 'bar' ); syncClients(); expectClients( '<$text attr="bar">Foo Bar' ); } ); // https://github.com/ckeditor/ckeditor5/issues/6265 it( 'on elements on different but intersecting "levels"', () => { john.setData( '[Foo
]' ); kate.setData( '[Foo]
' ); john.setAttribute( 'attr', 'foo' ); kate.setAttribute( 'attr', 'bar' ); syncClients(); expectClients( 'Foo
' ); } ); } ); describe( 'by insert', () => { it( 'text in different path', () => { john.setData( '[Foo]Bar' ); kate.setData( 'Foo[]Bar' ); john.setAttribute( 'bold', true ); kate.type( 'Abc' ); syncClients(); expectClients( '<$text bold="true">FooAbcBar' ); } ); it( 'text in different path #2', () => { john.setData( '[Foo]Bar' ); kate.setData( 'Foo[]Bar' ); john.setAttribute( 'bold', true ); kate.type( 'Abc' ); syncClients(); expectClients( 'FooAbcBar' ); } ); it( 'text at same path', () => { john.setData( '[F]oo' ); kate.setData( 'Foo[]' ); john.setAttribute( 'bold', true ); kate.type( 'Abc' ); syncClients(); expectClients( '<$text bold="true">FooAbc' ); } ); it( 'text inside other client\'s selection', () => { john.setData( '[Foo]' ); kate.setData( 'Fo[]o' ); john.setAttribute( 'bold', true ); kate.type( 'Abc' ); syncClients(); expectClients( '<$text bold="true">FoAbco' ); } ); it( 'element between changed elements', () => { john.setData( '[FooBar]' ); kate.setData( 'Foo[]Bar' ); john.setAttribute( 'bold', true ); kate.insert( 'Abc' ); syncClients(); expectClients( 'Foo' + 'Abc' + 'Bar' ); } ); it( 'text between changed nodes', () => { john.setData( '[FooBar]' ); kate.setData( 'FooB[]ar' ); john.setAttribute( 'bold', true ); kate.type( 'Abc' ); syncClients(); expectClients( '<$text bold="true">Foo' + '<$text bold="true">BAbcar' ); } ); it( 'element in different path', () => { john.setData( '[Foo]Bar' ); kate.setData( 'FooBar[]' ); john.setAttribute( 'bold', true ); kate.insert( 'Abc' ); syncClients(); expectClients( '<$text bold="true">Foo' + 'Bar' + 'Abc' ); } ); it( 'element in different path #2', () => { john.setData( '[Foo]Bar' ); kate.setData( 'FooBar[]' ); john.setAttribute( 'bold', true ); kate.insert( 'Abc' ); syncClients(); expectClients( 'Foo' + 'Bar' + 'Abc' ); } ); it( 'remove attribute from element in different path', () => { john.setData( '[]FooBar' ); kate.setData( 'Foo[Bar]' ); john.type( 'Abc' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'AbcFooBar' ); } ); it( 'remove attribute from text in different path', () => { john.setData( '[]Foo<$text bold="true">Bar' ); kate.setData( 'Foo<$text bold="true">[Bar]' ); john.type( 'Abc' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'AbcFooBar' ); } ); it( 'remove attribute from text while typing in client\'s range', () => { john.setData( 'F<$text bold="true">o[]o' ); kate.setData( 'F<$text bold="true">[oo]' ); john.type( 'Bar' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'FoBaro' ); } ); it( 'remove attribute from element in same path', () => { john.setData( '[]Foo' ); kate.setData( '[Foo]' ); john.type( 'Bar' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'BarFoo' ); } ); it( 'remove attribute from text with 2 attributes while typing in client\'s range', () => { john.setData( 'F<$text bold="true" italic="true">o[]o' ); kate.setData( 'F<$text bold="true" italic="true">[oo]' ); john.type( 'Bar', john.document.selection.getAttributes() ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'F<$text italic="true">oBaro' ); } ); it( 'multiple typing', () => { john.setData( '[Foo]' ); kate.setData( 'Fo[]o' ); john.setAttribute( 'bold', true ); kate.setSelection( [ 0, 2 ] ); kate.type( 'x' ); kate.setSelection( [ 0, 3 ] ); kate.type( 'x' ); kate.setSelection( [ 0, 4 ] ); kate.type( 'x' ); syncClients(); expectClients( '<$text bold="true">Foxxxo' ); } ); it( 'type inside element which attribute changes', () => { john.setData( '[]' ); kate.setData( '[]' ); john.setAttribute( 'bold', true ); kate.type( 'x' ); syncClients(); expectClients( 'x' ); } ); } ); describe( 'by move', () => { it( 'text in different path', () => { john.setData( '[Foo]Bar' ); kate.setData( 'FooB[ar]' ); john.setAttribute( 'bold', true ); kate.move( [ 1, 0 ] ); syncClients(); expectClients( '<$text bold="true">Foo' + 'arB' ); } ); it( 'text in different path #2', () => { john.setData( '[Foo]Bar' ); kate.setData( 'FooB[ar]' ); john.setAttribute( 'bold', true ); kate.move( [ 1, 0 ] ); syncClients(); expectClients( 'Foo' + 'arB' ); } ); it( 'text in same path', () => { john.setData( '[Foo] Bar' ); kate.setData( 'Foo B[ar]' ); john.setAttribute( 'bold', true ); kate.move( [ 0, 4 ] ); syncClients(); expectClients( '<$text bold="true">Foo arB' ); } ); it( 'text to other client\'s selection', () => { john.setData( '[Foo] Bar' ); kate.setData( 'Foo [Bar]' ); john.setAttribute( 'bold', true ); kate.move( [ 0, 1 ] ); syncClients(); expectClients( '<$text bold="true">FBar<$text bold="true">oo ' ); } ); it( 'text from other client\'s selection', () => { john.setData( '[Foo] Bar' ); kate.setData( 'F[oo] Bar' ); john.setAttribute( 'bold', true ); kate.move( [ 0, 7 ] ); syncClients(); expectClients( '<$text bold="true">F Bar<$text bold="true">oo' ); } ); it( 'text from other client\'s selection #2', () => { john.setData( '[Foo Bar]' ); kate.setData( 'F[oo] Bar' ); john.setAttribute( 'bold', true ); kate.move( [ 0, 7 ] ); syncClients(); expectClients( 'F Baroo' ); } ); it( 'remove attribute from element in different path', () => { john.setData( 'F[oo]Bar' ); kate.setData( 'Foo[Bar]' ); john.move( [ 1, 0 ] ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'F' + 'ooBar' ); } ); it( 'remove attribute from text in different path', () => { john.setData( 'F[oo]<$text bold="true">Bar' ); kate.setData( 'Foo<$text bold="true">[Bar]' ); john.move( [ 1, 0 ] ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'F' + 'ooBar' ); } ); it( 'remove attribute from text in same path', () => { john.setData( '[Fo]<$text bold="true">o' ); kate.setData( 'Fo<$text bold="true">[o]' ); john.move( [ 0, 3 ] ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'oFo' ); } ); it( 'remove attribute from element in same path', () => { john.setData( '[Fo]o' ); kate.setData( '[Foo]' ); john.move( [ 0, 3 ] ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'oFo' ); } ); it( 'remove attribute from text with 2 attributes in same path', () => { john.setData( '[Fo]<$text bold="true" italic="true">o' ); kate.setData( 'Fo<$text bold="true" italic="true">[o]' ); john.move( [ 0, 3 ] ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '<$text italic="true">oFo' ); } ); it( 'remove attribute from text in other user\'s selection', () => { john.setData( '<$text bold="true">[Foo]' ); kate.setData( '<$text bold="true">[Foo]' ); john.move( [ 1, 0 ] ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '' + 'Foo' ); } ); } ); describe( 'by wrap', () => { it( 'element into blockQuote in different path', () => { john.setData( '[Foo]Bar' ); kate.setData( 'Foo[Bar]' ); john.setAttribute( 'bold', true ); kate.wrap( 'blockQuote' ); syncClients(); expectClients( '<$text bold="true">Foo' + '
Bar
' ); } ); it( 'element into blockQuote in different path #2', () => { john.setData( '[Foo]Bar' ); kate.setData( 'Foo[Bar]' ); john.setAttribute( 'bold', true ); kate.wrap( 'blockQuote' ); syncClients(); expectClients( 'Foo' + '
Bar
' ); } ); it( 'element into blockQuote in same path #1', () => { john.setData( '[Foo]' ); kate.setData( '[Foo]' ); john.setAttribute( 'bold', true ); kate.wrap( 'blockQuote' ); syncClients(); expectClients( '
' + '<$text bold="true">Foo' + '
' ); } ); it( 'element into blockQuote in same path #2', () => { john.setData( '[Foo]' ); kate.setData( '[Foo]' ); john.setAttribute( 'bold', true ); kate.wrap( 'blockQuote' ); syncClients(); expectClients( '
' + 'Foo' + '
' ); } ); it( 'remove attribute from element in different path', () => { john.setData( '[Foo]Bar' ); kate.setData( 'Foo[Bar]' ); john.wrap( 'blockQuote' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '
' + 'Foo' + '
' + 'Bar' ); } ); it( 'remove attribute from text in different path', () => { john.setData( '[Foo]<$text bold="true">Bar' ); kate.setData( 'Foo<$text bold="true">[Bar]' ); john.wrap( 'blockQuote' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '
' + 'Foo' + '
' + 'Bar' ); } ); it( 'remove attribute from text in same path', () => { john.setData( '[Fo<$text bold="true">o]' ); kate.setData( 'Fo<$text bold="true">[o]' ); john.wrap( 'blockQuote' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '
Foo
' ); } ); it( 'remove attribute from element in same path', () => { john.setData( '[Foo]' ); kate.setData( '[Foo]' ); john.wrap( 'blockQuote' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '
Foo
' ); } ); it( 'remove attribute from text with 2 attributes in same path', () => { john.setData( '[Fo<$text bold="true" italic="true">o]' ); kate.setData( 'Fo<$text bold="true" italic="true">[o]' ); john.wrap( 'blockQuote' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '
' + 'Fo<$text italic="true">o' + '
' ); } ); } ); describe( 'by unwrap', () => { it( 'element in different path', () => { john.setData( '[Foo]
Bar
' ); kate.setData( 'Foo
[Bar]
' ); john.setAttribute( 'bold', true ); kate.unwrap(); syncClients(); expectClients( '<$text bold="true">Foo' + 'Bar' ); } ); it( 'element in different path #2', () => { john.setData( '[Foo]
Bar
' ); kate.setData( 'Foo
[Bar]
' ); john.setAttribute( 'bold', true ); kate.unwrap(); syncClients(); expectClients( 'Foo' + 'Bar' ); } ); it( 'text in different path', () => { john.setData( '[Foo]
Bar
' ); kate.setData( 'Foo
[]Bar
' ); john.setAttribute( 'bold', true ); kate.unwrap(); syncClients(); expectClients( '<$text bold="true">Foo' + '
Bar
' ); } ); it( 'element in same path #1', () => { john.setData( '
[Foo]
' ); kate.setData( '
[Foo]
' ); john.setAttribute( 'bold', true ); kate.unwrap(); syncClients(); expectClients( '<$text bold="true">Foo' ); } ); it( 'element in same path #2', () => { john.setData( '
[Foo]
' ); kate.setData( '
[Foo]
' ); john.setAttribute( 'bold', true ); kate.unwrap(); syncClients(); expectClients( 'Foo' ); } ); it( 'text in same path', () => { john.setData( '
[Foo]
' ); kate.setData( '
[]Foo
' ); john.setAttribute( 'bold', true ); kate.unwrap(); syncClients(); expectClients( '
<$text bold="true">Foo
' ); } ); it( 'remove attribute from element in different path', () => { john.setData( '
[Foo]
' + 'Bar' ); kate.setData( '
Foo
' + '[Bar]' ); john.unwrap(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'Foo' + 'Bar' ); } ); it( 'remove attribute from text in different path', () => { john.setData( '
[]Foo
' + '<$text bold="true">Bar' ); kate.setData( '
Foo
' + '<$text bold="true">[Bar]' ); john.unwrap(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '
Foo
' + 'Bar' ); } ); it( 'remove attribute from text in same path', () => { john.setData( '
[]Fo<$text bold="true">o
' ); kate.setData( '
Fo<$text bold="true">[o]
' ); john.unwrap(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '
Foo
' ); } ); it( 'remove attribute from element in same path', () => { john.setData( '
[]Foo
' ); kate.setData( '
[Foo]
' ); john.unwrap(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '
Foo
' ); } ); it( 'remove attribute from text with 2 attributes in same path', () => { john.setData( '
[]Fo<$text bold="true" italic="true">o
' ); kate.setData( '
Fo<$text bold="true" italic="true">[o]
' ); john.unwrap(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '
Fo<$text italic="true">o
' ); } ); } ); describe( 'by split', () => { it( 'text in different path', () => { john.setData( '[Foo]Bar' ); kate.setData( 'FooBa[]r' ); john.setAttribute( 'bold', true ); kate.split(); syncClients(); expectClients( '<$text bold="true">Foo' + 'Ba' + 'r' ); } ); it( 'text in different path #2', () => { john.setData( '[Foo]Bar' ); kate.setData( 'FooBa[]r' ); john.setAttribute( 'bold', true ); kate.split(); syncClients(); expectClients( 'Foo' + 'Ba' + 'r' ); } ); it( 'text in same path #1', () => { john.setData( '[Foo] Bar' ); kate.setData( 'Foo B[]ar' ); john.setAttribute( 'bold', true ); kate.split(); syncClients(); expectClients( '<$text bold="true">Foo B' + 'ar' ); } ); it( 'text in other user\'s selection', () => { john.setData( '[Foo]' ); kate.setData( 'Fo[]o' ); john.setAttribute( 'bold', true ); kate.split(); syncClients(); expect( '<$text bold="true">Fo' + '<$text bold="true">o' ); } ); it( 'text in other user\'s selection #2', () => { john.setData( '[Foo Bar]' ); kate.setData( 'Foo B[]ar' ); john.setAttribute( 'bold', true ); kate.split(); syncClients(); expectClients( 'Foo B' + 'ar' ); } ); it( 'text while changing attribute', () => { john.setData( '<$text attr="foo">Foo B[ar]' ); kate.setData( '<$text attr="foo">Foo B[]ar' ); john.setAttribute( 'attr', 'bar' ); kate.split(); syncClients(); expectClients( '<$text attr="foo">Foo B' + '<$text attr="bar">ar' ); } ); it( 'remove attribute from element in different path', () => { john.setData( 'F[]ooBar' ); kate.setData( 'Foo[Bar]' ); john.split(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'F' + 'oo' + 'Bar' ); } ); it( 'remove attribute from text in different path', () => { john.setData( 'F[]oo<$text bold="true">Bar' ); kate.setData( 'Foo<$text bold="true">[Bar]' ); john.split(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'F' + 'oo' + 'Bar' ); } ); it( 'remove attribute from text in same path #1', () => { john.setData( 'F[]o<$text bold="true">o' ); kate.setData( 'Fo<$text bold="true">[o]' ); john.split(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'F' + 'oo' ); } ); it( 'remove attribute from text in same path #2', () => { john.setData( 'F<$text bold="true">o[]o' ); kate.setData( 'F<$text bold="true">[oo]' ); john.split(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'Fo' + 'o' ); } ); it( 'remove attribute from element in same path', () => { john.setData( 'F[]oo' ); kate.setData( '[Foo]' ); john.split(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'F' + 'oo' ); } ); it( 'remove attribute from text with 2 attributes in same path', () => { john.setData( 'F<$text bold="true" italic="true">o[]o' ); kate.setData( 'F<$text bold="true" italic="true">[oo]' ); john.split(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'F<$text italic="true">o' + '<$text italic="true">o' ); } ); } ); describe( 'by remove', () => { it( 'text in different path', () => { john.setData( '[Foo]Bar' ); kate.setData( 'Foo[Bar]' ); john.setAttribute( 'bold', true ); kate.remove(); syncClients(); expectClients( '<$text bold="true">Foo' + '' ); } ); it( 'text in same path', () => { john.setData( '[Fo]o' ); kate.setData( 'Fo[o]' ); john.setAttribute( 'bold', true ); kate.remove(); syncClients(); expectClients( '<$text bold="true">Fo' ); } ); it( 'text in other user\'s selection', () => { john.setData( '[Foo]' ); kate.setData( 'F[oo]' ); john.setAttribute( 'bold', true ); kate.remove(); syncClients(); expectClients( '<$text bold="true">F' ); } ); it( 'remove attribute from element in different path', () => { john.setData( 'F[oo]Bar' ); kate.setData( 'Foo[Bar]' ); john.remove(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'F' + 'Bar' ); } ); it( 'remove attribute from text in different path', () => { john.setData( 'F[oo]<$text bold="true">Bar' ); kate.setData( 'Foo<$text bold="true">[Bar]' ); john.remove(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'F' + 'Bar' ); } ); it( 'remove attribute from text in same path #1', () => { john.setData( '[Fo]<$text bold="true">o' ); kate.setData( 'Fo<$text bold="true">[o]' ); john.remove(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'o' ); } ); it( 'remove attribute from text in same path #2', () => { john.setData( 'F[o<$text bold="true">o]zBar' ); kate.setData( 'Fo<$text bold="true">[oz]Bar' ); john.remove(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'FzBar' ); } ); it( 'remove attribute from element in same path', () => { john.setData( '[Fo]o' ); kate.setData( '[Foo]' ); john.remove(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'o' ); } ); it( 'remove attribute from text with 2 attributes in same path #1', () => { john.setData( '[Fo]<$text bold="true" italic="true">o' ); kate.setData( 'Fo<$text bold="true" italic="true">[o]' ); john.remove(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '<$text italic="true">o' ); } ); it( 'remove attribute from text with 2 attributes in same path #2', () => { john.setData( 'F[o<$text bold="true" italic="true">o]zBar' ); kate.setData( 'Fo<$text bold="true" italic="true">[oz]Bar' ); john.remove(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'F<$text italic="true">zBar' ); } ); it( 'remove attribute from text in other user\'s selection', () => { john.setData( '<$text bold="true">[Foo]' ); kate.setData( '<$text bold="true">[Foo]' ); john.remove(); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '' ); } ); } ); describe( 'by remove attribute', () => { it( 'from element in different path', () => { john.setData( '[Foo]Bar' ); kate.setData( 'Foo[Bar]' ); john.setAttribute( 'bold', true ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '<$text bold="true">Foo' + 'Bar' ); } ); it( 'from text in different path', () => { john.setData( '[Foo]<$text bold="true">Bar' ); kate.setData( 'Foo<$text bold="true">[Bar]' ); john.setAttribute( 'bold', true ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '<$text bold="true">Foo' + 'Bar' ); } ); it( 'from text in same path', () => { john.setData( '[Fo]<$text bold="true">o' ); kate.setData( 'Fo<$text bold="true">[o]' ); john.setAttribute( 'bold', true ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '<$text bold="true">Foo' ); } ); it( 'from element in same path', () => { john.setData( '[Fo]o' ); kate.setData( '[Foo]' ); john.setAttribute( 'italic', true ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '<$text italic="true">Foo' ); } ); it( 'from text with 2 attributes in same path', () => { john.setData( '[Fo]<$text bold="true" italic="true">o' ); kate.setData( 'Fo<$text bold="true" italic="true">[o]' ); john.setAttribute( 'bold', true ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '<$text bold="true">Fo<$text italic="true">o' ); } ); it( 'from text in other user\'s selection', () => { john.setData( '<$text bold="true">[Foo]' ); kate.setData( '<$text bold="true">[Foo]' ); john.setAttribute( 'italic', true ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( '<$text italic="true">Foo' ); } ); } ); describe( 'by marker', () => { it( 'in text at same path', () => { john.setData( '[Foo] Bar' ); kate.setData( 'Foo [Bar]' ); john.setAttribute( 'bold', true ); kate.setMarker( 'm1' ); syncClients(); expectClients( '<$text bold="true">Foo Bar' ); } ); it( 'in text at same path #2', () => { john.setData( '[Foo Bar]' ); kate.setData( 'Foo [Bar]' ); john.setAttribute( 'bold', true ); kate.setMarker( 'm1' ); syncClients(); expectClients( '' + 'Foo Bar' + '' ); } ); it( 'in text in different path', () => { john.setData( 'F[o]oBar' ); kate.setData( 'FooB[a]r' ); john.setAttribute( 'bold', true ); kate.setMarker( 'm1' ); syncClients(); expectClients( 'F<$text bold="true">oo' + 'Bar' ); } ); it( 'in text with selection inside other client\'s selection #1', () => { john.setData( '[Foo Bar]' ); kate.setData( 'Fo[o B]ar' ); john.setAttribute( 'bold', true ); kate.setMarker( 'm1' ); syncClients(); expectClients( '' + '<$text bold="true">Fo' + '<$text bold="true">o B<$text bold="true">ar' + '' ); } ); it( 'in text with selection inside other client\'s selection #2', () => { john.setData( 'F[oo Ba]r' ); kate.setData( 'Foo [Bar]' ); john.setAttribute( 'bold', true ); kate.setMarker( 'm1' ); syncClients(); expectClients( '' + 'F<$text bold="true">oo <$text bold="true">Bar' + '' ); } ); it( 'in text at same position', () => { john.setData( '[Foo Bar]' ); kate.setData( '[Foo Bar]' ); john.setAttribute( 'bold', true ); kate.setMarker( 'm1' ); syncClients(); expectClients( '<$text bold="true">Foo Bar' ); } ); it( 'remove attribute from element in different path', () => { john.setData( '[Foo]Bar' ); kate.setData( 'Foo[Bar]' ); john.setMarker( 'm1' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'Foo' + 'Bar' ); } ); it( 'remove attribute from text in different path', () => { john.setData( '[Foo]<$text bold="true">Bar' ); kate.setData( 'Foo<$text bold="true">[Bar]' ); john.setMarker( 'm1' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'Foo' + 'Bar' ); } ); it( 'remove attribute from text in same path', () => { john.setData( '[Fo]<$text bold="true">o' ); kate.setData( 'Fo<$text bold="true">[o]' ); john.setMarker( 'm1' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'Foo' ); } ); it( 'remove attribute from text in same path, then undo', () => { john.setData( '[Fo]<$text bold="true">o' ); kate.setData( 'Fo<$text bold="true">[o]' ); john.setMarker( 'm1' ); kate.removeAttribute( 'bold' ); syncClients(); kate.undo(); syncClients(); expectClients( 'Fo<$text bold="true">o' ); } ); it( 'remove attribute from text with 2 attributes in same path', () => { john.setData( '[Fo]<$text bold="true" italic="true">o' ); kate.setData( 'Fo<$text bold="true" italic="true">[o]' ); john.setMarker( 'm1' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'Fo<$text italic="true">o' ); } ); it( 'remove attribute from text in other user\'s selection', () => { john.setData( '<$text bold="true">[Foo]' ); kate.setData( '<$text bold="true">[Foo]' ); john.setMarker( 'm1' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'Foo' ); } ); } ); describe( 'by merge', () => { it( 'element into paragraph #1', () => { john.setData( 'FooB[ar]' ); kate.setData( 'Foo[]Bar' ); john.setAttribute( 'bold', 'true' ); kate.merge(); syncClients(); expectClients( 'FooB<$text bold="true">ar' ); } ); it( 'element into paragraph #2, then undo', () => { john.setData( 'Foo[Bar]' ); kate.setData( 'Foo[]Bar' ); john.setAttribute( 'bold', 'true' ); kate.merge(); syncClients(); expectClients( 'FooBar' ); kate.undo(); syncClients(); expectClients( 'FooBar' ); } ); it( 'element in same path', () => { john.setData( 'FooBar[Baz]' ); kate.setData( 'Foo[]BarBaz' ); john.setAttribute( 'bold', 'true' ); kate.merge(); syncClients(); expectClients( 'FooBarBaz' ); kate.undo(); john.undo(); syncClients(); expectClients( 'FooBarBaz' ); } ); } ); } ); describe( 'by rename', () => { it( 'element in different path', () => { john.setData( 'Foo[Ba]r' ); kate.setData( 'F[]ooBar' ); john.setAttribute( 'bold', true ); kate.rename( 'heading1' ); syncClients(); expectClients( 'Foo' + '<$text bold="true">Bar' ); } ); it( 'element in same path', () => { john.setData( '[Foo Bar]' ); kate.setData( '[]Foo Bar' ); john.setAttribute( 'bold', true ); kate.rename( 'heading1' ); syncClients(); expectClients( '<$text bold="true">Foo Bar' ); } ); it( 'element in user\'s selection', () => { john.setData( '[Foo]' ); kate.setData( '[Foo]' ); john.setAttribute( 'bold', true ); kate.rename( 'heading1' ); syncClients(); expectClients( '<$text bold="true">Foo' ); } ); it( 'element in user\'s selection, then undo', () => { john.setData( '[Foo]' ); kate.setData( '[Foo]' ); john.setAttribute( 'bold', true ); kate.rename( 'heading1' ); syncClients(); john.undo(); kate.undo(); syncClients(); expectClients( 'Foo' ); } ); it( 'remove attribute from element in different path', () => { john.setData( 'F[]ooBar' ); kate.setData( 'Foo[Bar]' ); john.rename( 'heading1' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'Foo' + 'Bar' ); } ); it( 'remove attribute from text in different path', () => { john.setData( 'F[]oo<$text bold="true">Bar' ); kate.setData( 'Foo<$text bold="true">[Bar]' ); john.rename( 'heading1' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'Foo' + 'Bar' ); } ); it( 'remove attribute from text in same path', () => { john.setData( 'F[]o<$text bold="true">o' ); kate.setData( 'Fo<$text bold="true">[o]' ); john.rename( 'heading1' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'Foo' ); } ); it( 'remove attribute from element in same path', () => { john.setData( 'F[]oo' ); kate.setData( '[Foo]' ); john.rename( 'heading1' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'Foo' ); } ); it( 'remove attribute from text with 2 attributes in same path', () => { john.setData( 'F[o]<$text bold="true" italic="true">o' ); kate.setData( 'Fo<$text bold="true" italic="true">[o]' ); john.rename( 'heading1' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'Fo<$text italic="true">o' ); } ); it( 'remove attribute from text in other user\'s selection', () => { john.setData( '<$text bold="true">[Foo]' ); kate.setData( '<$text bold="true">[Foo]' ); john.rename( 'heading1' ); kate.removeAttribute( 'bold' ); syncClients(); expectClients( 'Foo' ); } ); } ); } );