' ); + + editor.editing.view.fire( 'enter', data ); + + // Only enter command should be executed. + expect( data.preventDefault.called ).to.be.true; + expect( execSpy.calledOnce ).to.be.true; + expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'enter' ); + } ); + + it( 'does nothing if selection is in a non-empty block (at the beginning) in a block quote', () => { + const data = fakeEventData(); + const execSpy = sinon.spy( editor, 'execute' ); + + setModelData( doc, 'xx[]
' ); + + editor.editing.view.fire( 'enter', data ); + + // Only enter command should be executed. + expect( data.preventDefault.called ).to.be.true; + expect( execSpy.calledOnce ).to.be.true; + expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'enter' ); + } ); + + it( 'does nothing if selection is not collapsed', () => { + const data = fakeEventData(); + const execSpy = sinon.spy( editor, 'execute' ); + + setModelData( doc, '[]xx
' ); + + editor.editing.view.fire( 'enter', data ); + + // Only enter command should be executed. + expect( data.preventDefault.called ).to.be.true; + expect( execSpy.calledOnce ).to.be.true; + expect( execSpy.args[ 0 ][ 0 ] ).to.equal( 'enter' ); + } ); + + it( 'does not interfere with a similar handler in the list feature', () => { + const data = fakeEventData(); + + setModelData( doc, + '[ ]
' + + '' + + 'a ' + + '[] ' + + '
' + + '' + + 'a ' + + '[] ' + + '
[]
' + + 'a [] b
' + + 'a
' + + 'b
' + + 'a []
' + + 'a
' ); + + expect( command ).to.have.property( 'value', false ); + } ); + + it( 'is false when selection starts in a blockless space', () => { + doc.schema.allow( { name: '$text', inside: '$root' } ); + + setModelData( doc, 'x[]x' ); + + expect( command ).to.have.property( 'value', false ); + } ); + + it( 'is true when selection is in a block quote', () => { + setModelData( doc, 'y]y
' ); + + expect( command ).to.have.property( 'value', true ); + } ); + + it( 'is true when selection starts in a block quote', () => { + setModelData( doc, 'x[]x
x[x
' ); + + expect( command ).to.have.property( 'isEnabled', true ); + } ); + + it( 'is true when selection starts in a block which can be wrapped with blockQuote', () => { + setModelData( doc, 'x[]x
' + + 'x[]x
abc
x{}x
def
' + ); + } ); + + it( 'should wrap multiple blocks', () => { + setModelData( + doc, + '' + + '' + ); + + expect( getViewData( editor.editing.view ) ).to.equal( + 'a[bc ' + + 'xx ' + + 'de]f ' + + '
' + ); + } ); + + it( 'should merge with an existing quote', () => { + setModelData( + doc, + 'a{bc xx
de}f
' + + 'x]x yy
' + + '' + + 'a[bc ' + + 'x]x ' + + 'yy ' + + '
a{bc x}x
yy
def
' + ); + } ); + + it( 'should not merge with a quote preceding the current block', () => { + setModelData( + doc, + '' + + 'abc
' + + 'abc
' + ); + + expect( getViewData( editor.editing.view ) ).to.equal( + 'x[]x
' + + 'abc
' + ); + } ); + + it( 'should not merge with a quote following the current block', () => { + setModelData( + doc, + 'x{}x
' + ); + + editor.execute( 'blockQuote' ); + + expect( getModelData( doc ) ).to.equal( + 'abc
' + + 'x[]x
' + ); + + expect( getViewData( editor.editing.view ) ).to.equal( + 'abc
' + + 'x{}x
' + ); + } ); + + it( 'should merge with an existing quote (more blocks)', () => { + setModelData( + doc, + 'abc
' + + 'x]x
' + + '' + + 'a[bc ' + + 'def ' + + 'x]x ' + + '
a{bc def
x}x
ghi
' + ); + } ); + + it( 'should not wrap non-block content', () => { + setModelData( + doc, + '' + + '' + + 'a[bc ' + + '
' + + '' + ); + + expect( getViewData( editor.editing.view ) ).to.equal( + 'de]f ' + + '
a{bc
' + ); + } ); + + it( 'should correctly wrap and merge groups of blocks', () => { + setModelData( + doc, + 'de}f
' + + 'ghi
' + + 'a[bc
' + + 'def ghi
' + ); + + expect( getViewData( editor.editing.view ) ).to.equal( + 'jk]l
' + + 'a{bc
' + + 'def
ghi
' + ); + } ); + + it( 'should correctly merge a couple of subsequent quotes', () => { + setModelData( + doc, + 'jk}l
' + + 'def
' + + 'jkl
' + + '' + + 'a[bc ' + + 'def ' + + 'ghi ' + + 'jkl ' + + 'mn]o ' + + '
x
' + + '' + + '' + + 'a{bc
' + + 'def
' + + 'ghi
' + + 'jkl
' + + 'mn}o
' + + '
y
' + ); + } ); + } ); + + describe( 'removing quote', () => { + it( 'should unwrap a single block', () => { + setModelData( + doc, + '' + + 'x[]x
abc
x{}x
def
' + ); + } ); + + it( 'should unwrap multiple blocks', () => { + setModelData( + doc, + '' + + '' + ); + + editor.execute( 'blockQuote' ); + + expect( getModelData( doc ) ).to.equal( + 'a[bc ' + + 'xx ' + + 'de]f ' + + '
a{bc
xx
de}f
' + ); + } ); + + it( 'should unwrap only the selected blocks - at the beginning', () => { + setModelData( + doc, + '' + + '' + + 'a[b]c ' + + 'xx ' + + '
' + + '' + + 'xx ' + + '
xx
a{b}c
xx
yy
' + ); + } ); + + it( 'should unwrap only the selected blocks - at the end', () => { + setModelData( + doc, + '' + + '' + + 'abc ' + + 'x[x ' + + '
' + + '' + + 'abc ' + + '
abc
x{x
de}f
' + ); + } ); + + it( 'should unwrap only the selected blocks - in the middle', () => { + setModelData( + doc, + '' + + '' + + 'abc ' + + 'c[]de ' + + 'fgh ' + + '
' + + 'abc
' + + 'fgh
xx
' + + '' + + 'abc
c{}de
' + + '' + + 'fgh
xx
' + ); + } ); + + it( 'should remove multiple quotes', () => { + setModelData( + doc, + '' + + 'a[bc
' + + 'def ghi
' + ); + + editor.execute( 'blockQuote' ); + + expect( getModelData( doc ) ).to.equal( + 'de]f ghi
' + ); + + expect( getViewData( editor.editing.view ) ).to.equal( + 'ghi
a{bc
' + + 'xx
' + + 'def
ghi
' + + 'yy
' + + 'de}f
' + + '' + ); + } ); + } ); + } ); +} ); diff --git a/tests/blockquoteengine.js b/tests/blockquoteengine.js new file mode 100644 index 0000000..cb991fb --- /dev/null +++ b/tests/blockquoteengine.js @@ -0,0 +1,71 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import BlockQuoteEngine from '../src/blockquoteengine'; +import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; +import ListEngine from '@ckeditor/ckeditor5-list/src/listengine'; + +import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; +import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model'; + +import BlockQuoteCommand from '../src/blockquotecommand'; + +describe( 'BlockQuoteEngine', () => { + let editor, doc; + + beforeEach( () => { + return VirtualTestEditor.create( { + plugins: [ BlockQuoteEngine, Paragraph ] + } ) + .then( newEditor => { + editor = newEditor; + + doc = editor.document; + } ); + } ); + + afterEach( () => { + editor.destroy(); + } ); + + it( 'adds a blockQuote command', () => { + expect( editor.commands.get( 'blockQuote' ) ).to.be.instanceOf( BlockQuoteCommand ); + } ); + + it( 'allows for blockQuote in the $root', () => { + expect( doc.schema.check( { name: 'blockQuote', inside: '$root' } ) ).to.be.true; + } ); + + it( 'allows for $block in blockQuote', () => { + expect( doc.schema.check( { name: '$block', inside: 'blockQuote' } ) ).to.be.true; + expect( doc.schema.check( { name: 'paragraph', inside: 'blockQuote' } ) ).to.be.true; + } ); + + it( 'adds converters to the data pipeline', () => { + const data = 'ghi
'; + + editor.setData( data ); + + expect( getModelData( doc ) ).to.equal( 'x
' ); + expect( editor.getData() ).to.equal( data ); + } ); + + it( 'adds a converter to the view pipeline', () => { + setModelData( doc, '[]x
' ); + + expect( editor.getData() ).to.equal( 'x
' ); + } ); + + it( 'allows list items inside blockQuote', () => { + return VirtualTestEditor.create( { + plugins: [ BlockQuoteEngine, Paragraph, ListEngine ] + } ) + .then( editor => { + editor.setData( 'x
' ); + + expect( editor.getData() ).to.equal( '
- xx
' ); + } ); + } ); +} ); diff --git a/tests/manual/blockquote.html b/tests/manual/blockquote.html new file mode 100644 index 0000000..5bde4c1 --- /dev/null +++ b/tests/manual/blockquote.html @@ -0,0 +1,18 @@ +
- xx
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
+++Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat.
+ +Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
+++Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris.
++
+- Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
+