/** * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ 'use strict'; import { stringify, parse } from '/tests/engine/_utils/view.js'; import DocumentFragment from '/ckeditor5/engine/treeview/documentfragment.js'; import Element from '/ckeditor5/engine/treeview/element.js'; import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js'; import ContainerElement from '/ckeditor5/engine/treeview/containerelement.js'; import Text from '/ckeditor5/engine/treeview/text.js'; import Selection from '/ckeditor5/engine/treeview/selection.js'; import Range from '/ckeditor5/engine/treeview/range.js'; describe( 'view test utils', () => { describe( 'stringify', () => { it( 'should write text', () => { const text = new Text( 'foobar' ); expect( stringify( text ) ).to.equal( 'foobar' ); } ); it( 'should write elements and texts', () => { const text = new Text( 'foobar' ); const b = new Element( 'b', null, text ); const p = new Element( 'p', null, b ); expect( stringify( p ) ).to.equal( '

foobar

' ); } ); it( 'should write elements with attributes', () => { const text = new Text( 'foobar' ); const b = new Element( 'b', { foo: 'bar' }, text ); const p = new Element( 'p', { baz: 'qux', bar: 'taz', class: 'short wide' }, b ); expect( stringify( p ) ).to.equal( '

foobar

' ); } ); it( 'should write selection ranges inside elements', () => { const text1 = new Text( 'foobar' ); const text2 = new Text( 'bazqux' ); const b1 = new Element( 'b', null, text1 ); const b2 = new Element( 'b', null, text2 ); const p = new Element( 'p', null, [ b1, b2 ] ); const range = Range.createFromParentsAndOffsets( p, 1, p, 2 ); const selection = new Selection(); selection.addRange( range ); expect( stringify( p, selection ) ).to.equal( '

foobar[bazqux]

' ); } ); it( 'should write collapsed selection ranges inside elements', () => { const text = new Text( 'foobar' ); const p = new Element( 'p', null, text ); const range = Range.createFromParentsAndOffsets( p, 0, p, 0 ); const selection = new Selection(); selection.addRange( range ); expect( stringify( p, selection ) ).to.equal( '

[]foobar

' ); } ); it( 'should write selection ranges inside text', () => { const text1 = new Text( 'foobar' ); const text2 = new Text( 'bazqux' ); const b1 = new Element( 'b', null, text1 ); const b2 = new Element( 'b', null, text2 ); const p = new Element( 'p', null, [ b1, b2 ] ); const range = Range.createFromParentsAndOffsets( text1, 1, text1, 5 ); const selection = new Selection(); selection.addRange( range ); expect( stringify( p, selection ) ).to.equal( '

f{ooba}rbazqux

' ); } ); it( 'should write collapsed selection ranges inside texts', () => { const text = new Text( 'foobar' ); const p = new Element( 'p', null, text ); const range = Range.createFromParentsAndOffsets( text, 0, text, 0 ); const selection = new Selection(); selection.addRange( range ); expect( stringify( p, selection ) ).to.equal( '

{}foobar

' ); } ); it( 'should write ranges that start inside text end ends between elements', () => { const text1 = new Text( 'foobar' ); const text2 = new Text( 'bazqux' ); const b1 = new Element( 'b', null, text1 ); const b2 = new Element( 'b', null, text2 ); const p = new Element( 'p', null, [ b1, b2 ] ); const range = Range.createFromParentsAndOffsets( p, 0, text2, 5 ); const selection = new Selection(); selection.addRange( range ); expect( stringify( p, selection ) ).to.equal( '

[foobarbazqu}x

' ); } ); it( 'should write elements types as namespaces when needed', () => { const text = new Text( 'foobar' ); const b = new AttributeElement( 'b', null, text ); const p = new ContainerElement( 'p', null, b ); expect( stringify( p, null, { showType: true } ) ) .to.equal( 'foobar' ); } ); it( 'should not write element type when type is not specified', () => { const p = new Element( 'p' ); expect( stringify( p, null, { showType: true } ) ).to.equal( '

' ); } ); it( 'should write elements priorities when needed', () => { const text = new Text( 'foobar' ); const b = new AttributeElement( 'b', null, text ); const p = new ContainerElement( 'p', null, b ); expect( stringify( p, null, { showPriority: true } ) ) .to.equal( '

foobar

' ); } ); it( 'should parse DocumentFragment as root', () => { const text1 = new Text( 'foobar' ); const text2 = new Text( 'bazqux' ); const b1 = new Element( 'b', null, text1 ); const b2 = new Element( 'b', null, text2 ); const fragment = new DocumentFragment( [ b1, b2 ] ); expect( stringify( fragment, null ) ).to.equal( 'foobarbazqux' ); } ); it( 'should not write ranges outside elements', () => { const text = new Text( 'foobar' ); const b = new Element( 'b', null, text ); const p = new Element( 'p', null, b ); const range1 = Range.createFromParentsAndOffsets( p, 0, p, 5 ); const range2 = Range.createFromParentsAndOffsets( p, -1, p, 1 ); const range3 = Range.createFromParentsAndOffsets( text, 0, text, 7 ); const range4 = Range.createFromParentsAndOffsets( text, -1, text, 2 ); const range5 = Range.createFromParentsAndOffsets( text, 6, text, 8 ); const selection = new Selection(); selection.addRange( range1 ); expect( stringify( p, selection ) ).to.equal( '

[foobar

' ); selection.setRanges( [ range2 ] ); expect( stringify( p, selection ) ).to.equal( '

foobar]

' ); selection.setRanges( [ range3 ] ); expect( stringify( p, selection ) ).to.equal( '

{foobar

' ); selection.setRanges( [ range4 ] ); expect( stringify( p, selection ) ).to.equal( '

fo}obar

' ); selection.setRanges( [ range5 ] ); expect( stringify( p, selection ) ).to.equal( '

foobar{

' ); } ); it( 'should write multiple ranges from selection #1', () => { const text1 = new Text( 'foobar' ); const text2 = new Text( 'bazqux' ); const b1 = new Element( 'b', null, text1 ); const b2 = new Element( 'b', null, text2 ); const p = new Element( 'p', null, [ b1, b2 ] ); const range1 = Range.createFromParentsAndOffsets( p, 0, p, 1 ); const range2 = Range.createFromParentsAndOffsets( p, 1, p, 1 ); const selection = new Selection(); selection.setRanges( [ range2, range1 ] ); expect( stringify( p, selection ) ).to.equal( '

[foobar][]bazqux

' ); } ); it( 'should write multiple ranges from selection #2', () => { const text1 = new Text( 'foobar' ); const text2 = new Text( 'bazqux' ); const b = new Element( 'b', null, text1 ); const p = new Element( 'p', null, [ b, text2 ] ); const range1 = Range.createFromParentsAndOffsets( p, 0, p, 1 ); const range2 = Range.createFromParentsAndOffsets( text2, 0, text2, 3 ); const range3 = Range.createFromParentsAndOffsets( text2, 3, text2, 4 ); const range4 = Range.createFromParentsAndOffsets( p, 1, p, 1 ); const selection = new Selection(); selection.setRanges( [ range1, range2, range3, range4 ] ); expect( stringify( p, selection ) ).to.equal( '

[foobar][]{baz}{q}ux

' ); } ); } ); describe( 'parse', () => { it( 'should parse elements and texts', () => { const view = parse( 'foobar' ); const element = new Element( 'b' ); expect( view ).to.be.instanceof( Element ); expect( view.isSimilar( element ) ).to.be.true; expect( view.getChildCount() ).to.equal( 1 ); const text = view.getChild( 0 ); expect( text ).to.be.instanceof( Text ); expect( text.data ).to.equal( 'foobar' ); } ); it( 'should parse element attributes', () => { const view = parse( '' ); const element = new Element( 'b', { name: 'foo', title: 'bar', class: 'foo bar', style: 'color:red;' } ); expect( view ).to.be.instanceof( Element ); expect( view.isSimilar( element ) ).to.be.true; expect( view.getChildCount() ).to.equal( 0 ); } ); it( 'should parse element type', () => { const view1 = parse( '' ); const attribute = new AttributeElement( 'b' ); const view2 = parse( '' ); const container = new ContainerElement( 'p' ); expect( view1 ).to.be.instanceof( AttributeElement ); expect( view1.isSimilar( attribute ) ).to.be.true; expect( view2 ).to.be.instanceof( ContainerElement ); expect( view2.isSimilar( container ) ).to.be.true; } ); it( 'should parse element priority', () => { const parsed1 = parse( '' ); const attribute1 = new AttributeElement( 'b' ); attribute1.priority = 12; const parsed2 = parse( '' ); const attribute2 = new AttributeElement( 'b' ); attribute2.priority = 44; parsed1.isSimilar( attribute1 ); expect( parsed1.isSimilar( attribute1 ) ).to.be.true; expect( parsed2.isSimilar( attribute2 ) ).to.be.true; } ); it( 'should create DocumentFragment when multiple elements on root', () => { const view = parse( '' ); expect( view ).to.be.instanceOf( DocumentFragment ); expect( view.getChildCount() ).to.equal( 2 ); expect( view.getChild( 0 ).isSimilar( new Element( 'b' ) ) ).to.be.true; expect( view.getChild( 1 ).isSimilar( new Element( 'i' ) ) ).to.be.true; } ); it( 'should paste nested elements and texts', () => { const parsed = parse( 'foobarqux' ); expect( parsed.isSimilar( new ContainerElement( 'p' ) ) ).to.be.true; expect( parsed.getChildCount() ).to.equal( 2 ); expect( parsed.getChild( 0 ) ).to.be.instanceof( Text ).and.have.property( 'data' ).that.equal( 'foo' ); const b = parsed.getChild( 1 ); expect( b ).to.be.instanceof( AttributeElement ); expect( b.priority ).to.equal( 12 ); expect( b.getChildCount() ).to.equal( 2 ); expect( b.getChild( 0 ) ).to.be.instanceof( Text ).and.have.property( 'data' ).that.equal( 'bar' ); const i = b.getChild( 1 ); expect( i ).to.be.instanceof( AttributeElement ); expect( i.priority ).to.equal( 25 ); expect( i.getChild( 0 ) ).to.be.instanceof( Text ).and.have.property( 'data' ).that.equal( 'qux' ); } ); it( 'should parse selection range inside text', () => { const { view, selection } = parse( 'f{oo}b{}ar' ); expect( view ).to.be.instanceof( Text ); expect( view.data ).to.equal( 'foobar' ); expect( selection.rangeCount ).to.equal( 2 ); const ranges = Array.from( selection.getRanges() ); expect( ranges[ 0 ].isEqual( Range.createFromParentsAndOffsets( view, 1, view, 3 ) ) ).to.be.true; expect( ranges[ 1 ].isEqual( Range.createFromParentsAndOffsets( view, 4, view, 4 ) ) ).to.be.true; } ); it( 'should parse selection range between elements', () => { const { view, selection } = parse( '

[foobar][]

' ); expect( view ).to.be.instanceof( Element ); expect( view.getChildCount() ).to.equal( 1 ); const b = view.getChild( 0 ); expect( b ).to.be.instanceof( Element ); expect( b.name ).to.equal( 'b' ); expect( b.getChildCount() ).to.equal( 1 ); const text = b.getChild( 0 ); expect( text ).to.be.instanceof( Text ); expect( text.data ).to.equal( 'foobar' ); expect( selection.rangeCount ).to.equal( 2 ); const ranges = Array.from( selection.getRanges() ); expect( ranges[ 0 ].isEqual( Range.createFromParentsAndOffsets( view, 0, b, 1 ) ) ).to.be.true; expect( ranges[ 1 ].isEqual( Range.createFromParentsAndOffsets( view, 1, view, 1 ) ) ).to.be.true; } ); it( 'should parse ranges #1', () => { const { view, selection } = parse( 'foo{bar]' ); expect( view.isSimilar( new ContainerElement( 'p' ) ) ).to.be.true; expect( view.getChildCount() ).to.equal( 1 ); const text = view.getChild( 0 ); expect( text ).to.be.instanceof( Text ); expect( text.data ).to.equal( 'foobar' ); expect( selection.rangeCount ).to.equal( 1 ); expect( selection.getFirstRange().isEqual( Range.createFromParentsAndOffsets( text, 3, view, 1 ) ) ).to.be.true; } ); it( 'should parse ranges #2', () => { const { view, selection } = parse( '[foob}ar{baz]' ); expect( view.isSimilar( new AttributeElement( 'b' ) ) ).to.be.true; expect( view.getChildCount() ).to.equal( 2 ); const text1 = view.getChild( 0 ); expect( text1 ).to.be.instanceof( Text ); expect( text1.data ).to.equal( 'foobar' ); const i = view.getChild( 1 ); expect( i.isSimilar( new Element( 'i' ) ) ).to.be.true; expect( i.getChildCount() ).to.equal( 1 ); const text2 = i.getChild( 0 ); expect( text2 ).to.be.instanceof( Text ); expect( text2.data ).to.equal( 'baz' ); expect( selection.rangeCount ).to.equal( 2 ); const ranges = Array.from( selection.getRanges() ); expect( ranges[ 0 ].isEqual( Range.createFromParentsAndOffsets( view, 0, text1, 4 ) ) ).to.be.true; expect( ranges[ 1 ].isEqual( Range.createFromParentsAndOffsets( text2, 0, view, 2 ) ) ).to.be.true; } ); it( 'should use ranges order when provided', () => { const { view, selection } = parse( '{f}oo{b}arb{a}z', { order: [ 3, 1, 2 ] } ); expect( selection.rangeCount ).to.equal( 3 ); const ranges = Array.from( selection.getRanges() ); expect( ranges[ 0 ].isEqual( Range.createFromParentsAndOffsets( view, 3, view, 4 ) ) ).to.be.true; expect( ranges[ 1 ].isEqual( Range.createFromParentsAndOffsets( view, 7, view, 8 ) ) ).to.be.true; expect( ranges[ 2 ].isEqual( Range.createFromParentsAndOffsets( view, 0, view, 1 ) ) ).to.be.true; } ); it( 'should throw when ranges order does not include all ranges', () => { expect( () => { parse( '{}foobar{}', { order: [ 1 ] } ); } ).to.throw( Error ); } ); it( 'should throw when ranges order is invalid', () => { expect( () => { parse( '{}foobar{}', { order: [ 1, 4 ] } ); } ).to.throw( Error ); } ); it( 'should throw when element range delimiter is inside text node', () => { expect( () => { parse( 'foo[bar' ); } ).to.throw( Error ); } ); it( 'should throw when text range delimiter is inside empty text node', () => { expect( () => { parse( 'foo}' ); } ).to.throw( Error ); } ); it( 'should throw when end of range is found before start', () => { expect( () => { parse( 'fo}obar' ); } ).to.throw( Error ); } ); it( 'should throw when intersecting ranges found', () => { expect( () => { parse( '[fo{o}bar]' ); } ).to.throw( Error ); } ); it( 'should throw when opened ranges are left', () => { expect( () => { parse( 'fo{obar' ); } ).to.throw( Error ); } ); it( 'should throw when wrong tag name is provided #1', () => { expect( () => { parse( '' ); } ).to.throw( Error ); } ); it( 'should throw when wrong tag name is provided #2', () => { expect( () => { parse( '' ); } ).to.throw( Error ); } ); it( 'should throw when wrong tag name is provided #3', () => { expect( () => { parse( '' ); } ).to.throw( Error ); } ); } ); } );