/**
* @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 ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
import TextWatcher from '../src/textwatcher';
describe( 'TextWatcher', () => {
let editor, model, doc;
let watcher, matchedDataSpy, matchedSelectionSpy, unmatchedSpy, testCallbackStub;
testUtils.createSinonSandbox();
beforeEach( () => {
return ModelTestEditor.create()
.then( newEditor => {
editor = newEditor;
model = editor.model;
doc = model.document;
testCallbackStub = sinon.stub();
matchedDataSpy = sinon.spy();
matchedSelectionSpy = sinon.spy();
unmatchedSpy = sinon.spy();
model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
setData( model, 'foo []' );
watcher = new TextWatcher( model, testCallbackStub, () => {} );
watcher.on( 'matched:data', matchedDataSpy );
watcher.on( 'matched:selection', matchedSelectionSpy );
watcher.on( 'unmatched', unmatchedSpy );
} );
} );
afterEach( () => {
sinon.restore();
if ( editor ) {
return editor.destroy();
}
} );
describe( '#isEnabled', () => {
it( 'should be enabled after initialization', () => {
expect( watcher.isEnabled ).to.be.true;
} );
it( 'should be disabled after setting #isEnabled to false', () => {
watcher.isEnabled = false;
expect( watcher.isEnabled ).to.be.false;
} );
} );
describe( 'testCallback', () => {
it( 'should evaluate text before caret for data changes', () => {
model.change( writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.calledOnce( testCallbackStub );
sinon.assert.calledWithExactly( testCallbackStub, 'foo @' );
} );
it( 'should not evaluate text for not collapsed selection', () => {
model.change( writer => {
const start = writer.createPositionAt( doc.getRoot().getChild( 0 ), 0 );
writer.setSelection( writer.createRange( start, start.getShiftedBy( 1 ) ) );
} );
sinon.assert.notCalled( testCallbackStub );
} );
it( 'should evaluate text for selection changes', () => {
model.change( writer => {
writer.setSelection( doc.getRoot().getChild( 0 ), 1 );
} );
sinon.assert.calledOnce( testCallbackStub );
sinon.assert.calledWithExactly( testCallbackStub, 'f' );
} );
it( 'should evaluate text before caret up to ', () => {
model.schema.register( 'softBreak', {
allowWhere: '$text',
isInline: true
} );
model.change( writer => {
writer.insertElement( 'softBreak', doc.selection.getFirstPosition() );
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.calledOnce( testCallbackStub );
sinon.assert.calledWithExactly( testCallbackStub, '@' );
} );
it( 'should not evaluate text for transparent batches', () => {
model.enqueueChange( 'transparent', writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.notCalled( testCallbackStub );
} );
it( 'should not evaluate text when watcher is disabled', () => {
watcher.isEnabled = false;
model.change( writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.notCalled( testCallbackStub );
} );
it( 'should evaluate text when watcher is enabled again', () => {
watcher.isEnabled = false;
model.change( writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.notCalled( testCallbackStub );
watcher.isEnabled = true;
model.change( writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.calledOnce( testCallbackStub );
sinon.assert.calledWithExactly( testCallbackStub, 'foo @@' );
} );
} );
describe( 'events', () => {
describe( '"matched:data"', () => {
it( 'should be fired when test callback returns true for model data changes', () => {
testCallbackStub.returns( true );
model.change( writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.calledOnce( testCallbackStub );
sinon.assert.calledOnce( matchedDataSpy );
sinon.assert.notCalled( matchedSelectionSpy );
sinon.assert.notCalled( unmatchedSpy );
} );
it( 'should be fired with additional data when test callback returns true for model data changes', () => {
const additionalData = { abc: 'xyz' };
testCallbackStub.returns( additionalData );
model.change( writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.calledOnce( testCallbackStub );
sinon.assert.calledOnce( matchedDataSpy );
sinon.assert.notCalled( matchedSelectionSpy );
sinon.assert.notCalled( unmatchedSpy );
expect( matchedDataSpy.firstCall.args[ 1 ] ).to.deep.include( additionalData );
} );
} );
it( 'should not fire "matched:data" event when watcher is disabled' +
' (even when test callback returns true for model data changes)', () => {
watcher.isEnabled = false;
testCallbackStub.returns( true );
model.change( writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.notCalled( testCallbackStub );
sinon.assert.notCalled( matchedDataSpy );
sinon.assert.notCalled( matchedSelectionSpy );
sinon.assert.notCalled( unmatchedSpy );
} );
it( 'should fire "matched:selection" event when test callback returns true for model data changes', () => {
testCallbackStub.returns( true );
model.enqueueChange( 'transparent', writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
model.change( writer => {
writer.setSelection( doc.getRoot().getChild( 0 ), 0 );
} );
sinon.assert.calledOnce( testCallbackStub );
sinon.assert.notCalled( matchedDataSpy );
sinon.assert.calledOnce( matchedSelectionSpy );
sinon.assert.notCalled( unmatchedSpy );
} );
it( 'should not fire "matched:selection" event when when watcher is disabled' +
' (even when test callback returns true for model data changes)', () => {
watcher.isEnabled = false;
testCallbackStub.returns( true );
model.enqueueChange( 'transparent', writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
model.change( writer => {
writer.setSelection( doc.getRoot().getChild( 0 ), 0 );
} );
sinon.assert.notCalled( testCallbackStub );
sinon.assert.notCalled( matchedDataSpy );
sinon.assert.notCalled( matchedSelectionSpy );
sinon.assert.notCalled( unmatchedSpy );
} );
it( 'should not fire "matched" event when test callback returns false', () => {
testCallbackStub.returns( false );
model.change( writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.calledOnce( testCallbackStub );
sinon.assert.notCalled( matchedDataSpy );
sinon.assert.notCalled( matchedSelectionSpy );
sinon.assert.notCalled( unmatchedSpy );
} );
it( 'should fire "unmatched" event when test callback returns false when it was previously matched', () => {
testCallbackStub.returns( true );
model.change( writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.calledOnce( testCallbackStub );
sinon.assert.calledOnce( matchedDataSpy );
sinon.assert.notCalled( unmatchedSpy );
testCallbackStub.returns( false );
model.change( writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.calledTwice( testCallbackStub );
sinon.assert.calledOnce( matchedDataSpy );
sinon.assert.calledOnce( unmatchedSpy );
} );
it( 'should fire "umatched" event when selection is expanded', () => {
testCallbackStub.returns( true );
model.change( writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.calledOnce( testCallbackStub );
sinon.assert.calledOnce( matchedDataSpy );
sinon.assert.notCalled( matchedSelectionSpy );
sinon.assert.notCalled( unmatchedSpy );
model.change( writer => {
const start = writer.createPositionAt( doc.getRoot().getChild( 0 ), 0 );
writer.setSelection( writer.createRange( start, start.getShiftedBy( 1 ) ) );
} );
sinon.assert.calledOnce( testCallbackStub );
sinon.assert.calledOnce( matchedDataSpy );
sinon.assert.notCalled( matchedSelectionSpy );
sinon.assert.calledOnce( unmatchedSpy );
} );
it( 'should not fire "umatched" event when selection is expanded if watcher is disabled', () => {
watcher.isEnabled = false;
testCallbackStub.returns( true );
model.change( writer => {
writer.insertText( '@', doc.selection.getFirstPosition() );
} );
sinon.assert.notCalled( testCallbackStub );
sinon.assert.notCalled( matchedDataSpy );
sinon.assert.notCalled( matchedSelectionSpy );
sinon.assert.notCalled( unmatchedSpy );
model.change( writer => {
const start = writer.createPositionAt( doc.getRoot().getChild( 0 ), 0 );
writer.setSelection( writer.createRange( start, start.getShiftedBy( 1 ) ) );
} );
sinon.assert.notCalled( testCallbackStub );
sinon.assert.notCalled( matchedDataSpy );
sinon.assert.notCalled( matchedSelectionSpy );
sinon.assert.notCalled( unmatchedSpy );
} );
} );
} );