8
0
Kamil Piechaczek 6 лет назад
Родитель
Сommit
0185364b45

+ 1 - 0
packages/ckeditor5-horizontal-line/package.json

@@ -19,6 +19,7 @@
     "@ckeditor/ckeditor5-editor-classic": "^12.1.3",
     "@ckeditor/ckeditor5-easy-image": "^11.0.5",
     "@ckeditor/ckeditor5-image": "^14.0.0",
+    "@ckeditor/ckeditor5-paragraph": "^11.0.5",
     "eslint": "^5.5.0",
     "eslint-config-ckeditor5": "^2.0.0",
     "husky": "^1.3.1",

+ 11 - 1
packages/ckeditor5-horizontal-line/src/utils.js

@@ -7,7 +7,7 @@
  * @module horizontal-rule/utils
  */
 
-import { findOptimalInsertionPosition, toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
+import { findOptimalInsertionPosition, isWidget, toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
 
 /**
  * @param {module:engine/view/element~Element} viewElement
@@ -22,6 +22,16 @@ export function toHorizontalRuleWidget( viewElement, writer, label ) {
 }
 
 /**
+ * Checks if a given view element is a horizontal rule widget.
+ *
+ * @param {module:engine/view/element~Element} viewElement
+ * @returns {Boolean}
+ */
+export function isHorizontalRuleWidget( viewElement ) {
+	return !!viewElement.getCustomProperty( 'horizontalRule' ) && isWidget( viewElement );
+}
+
+/**
  * Checks if the `horizontalRule` element can be inserted at current model selection.
  *
  * @param {module:engine/model/model~Model} model

+ 18 - 0
packages/ckeditor5-horizontal-line/tests/horizontalrule.js

@@ -0,0 +1,18 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import HorizontalRule from '../src/horizontalrule';
+import HorizontalRuleEditing from '../src/horizontalruleediting';
+import HorizontalRuleUI from '../src/horizontalruleui';
+
+describe( 'HorizontalRule', () => {
+	it( 'should require HorizontalRuleEditing and HorizontalRuleUI', () => {
+		expect( HorizontalRule.requires ).to.deep.equal( [ HorizontalRuleEditing, HorizontalRuleUI ] );
+	} );
+
+	it( 'should be named', () => {
+		expect( HorizontalRule.pluginName ).to.equal( 'HorizontalRule' );
+	} );
+} );

+ 154 - 0
packages/ckeditor5-horizontal-line/tests/horizontalrulecommand.js

@@ -0,0 +1,154 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals document */
+
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import HorizontalRuleEditing from '../src/horizontalruleediting';
+
+describe( 'HorizontalRuleCommand', () => {
+	let editor, model, editorElement, command;
+
+	testUtils.createSinonSandbox();
+
+	beforeEach( () => {
+		editorElement = document.createElement( 'div' );
+		document.body.appendChild( editorElement );
+
+		return ClassicTestEditor
+			.create( editorElement, {
+				plugins: [ Paragraph, HorizontalRuleEditing ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+				model = editor.model;
+				command = editor.commands.get( 'horizontalRule' );
+			} );
+	} );
+
+	afterEach( () => {
+		return editor.destroy()
+			.then( () => {
+				editorElement.remove();
+			} );
+	} );
+
+	describe( 'isEnabled', () => {
+		it( 'should be true when the selection directly in the root', () => {
+			model.enqueueChange( 'transparent', () => {
+				setModelData( model, '[]' );
+
+				command.refresh();
+				expect( command.isEnabled ).to.be.true;
+			} );
+		} );
+
+		it( 'should be true when the selection is in empty block', () => {
+			setModelData( model, '<paragraph>[]</paragraph>' );
+
+			expect( command.isEnabled ).to.be.true;
+		} );
+
+		it( 'should be true when the selection directly in a paragraph', () => {
+			setModelData( model, '<paragraph>foo[]</paragraph>' );
+			expect( command.isEnabled ).to.be.true;
+		} );
+
+		it( 'should be true when the selection directly in a block', () => {
+			model.schema.register( 'block', { inheritAllFrom: '$block' } );
+			model.schema.extend( '$text', { allowIn: 'block' } );
+			editor.conversion.for( 'downcast' ).elementToElement( { model: 'block', view: 'block' } );
+
+			setModelData( model, '<block>foo[]</block>' );
+			expect( command.isEnabled ).to.be.true;
+		} );
+
+		it( 'should be false when the selection is on other horizontal rule element', () => {
+			setModelData( model, '[<horizontalRule></horizontalRule>]' );
+			expect( command.isEnabled ).to.be.false;
+		} );
+
+		it( 'should be false when the selection is on other object', () => {
+			model.schema.register( 'object', { isObject: true, allowIn: '$root' } );
+			editor.conversion.for( 'downcast' ).elementToElement( { model: 'object', view: 'object' } );
+			setModelData( model, '[<object></object>]' );
+
+			expect( command.isEnabled ).to.be.false;
+		} );
+
+		it( 'should be true when the selection is inside block element inside isLimit element which allows horizontal rule', () => {
+			model.schema.register( 'table', { allowWhere: '$block', isLimit: true, isObject: true, isBlock: true } );
+			model.schema.register( 'tableRow', { allowIn: 'table', isLimit: true } );
+			model.schema.register( 'tableCell', { allowIn: 'tableRow', isLimit: true } );
+			model.schema.extend( '$block', { allowIn: 'tableCell' } );
+			editor.conversion.for( 'downcast' ).elementToElement( { model: 'table', view: 'table' } );
+			editor.conversion.for( 'downcast' ).elementToElement( { model: 'tableRow', view: 'tableRow' } );
+			editor.conversion.for( 'downcast' ).elementToElement( { model: 'tableCell', view: 'tableCell' } );
+
+			setModelData( model, '<table><tableRow><tableCell><paragraph>foo[]</paragraph></tableCell></tableRow></table>' );
+		} );
+
+		it( 'should be false when schema disallows horizontal rule', () => {
+			model.schema.register( 'block', { inheritAllFrom: '$block' } );
+			model.schema.extend( 'paragraph', { allowIn: 'block' } );
+			// Block horizontal rule in block.
+			model.schema.addChildCheck( ( context, childDefinition ) => {
+				if ( childDefinition.name === 'horizontalRule' && context.last.name === 'block' ) {
+					return false;
+				}
+			} );
+			editor.conversion.for( 'downcast' ).elementToElement( { model: 'block', view: 'block' } );
+
+			setModelData( model, '<block><paragraph>[]</paragraph></block>' );
+
+			expect( command.isEnabled ).to.be.false;
+		} );
+	} );
+
+	describe( 'execute()', () => {
+		it( 'should create a single batch', () => {
+			setModelData( model, '<paragraph>foo[]</paragraph>' );
+
+			const spy = sinon.spy();
+
+			model.document.on( 'change', spy );
+
+			command.execute();
+
+			sinon.assert.calledOnce( spy );
+		} );
+
+		it( 'should insert a horizontal rule in an empty root and select it', () => {
+			setModelData( model, '[]' );
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal( '[<horizontalRule></horizontalRule>]' );
+		} );
+
+		it( 'should split an element where selection is placed and insert a horizontal rule (non-collapsed selection)', () => {
+			setModelData( model, '<paragraph>f[o]o</paragraph>' );
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal(
+				'<paragraph>f</paragraph>[<horizontalRule></horizontalRule>]<paragraph>o</paragraph>'
+			);
+		} );
+
+		it( 'should split an element where selection is placed and insert a horizontal rule (collapsed selection)', () => {
+			setModelData( model, '<paragraph>fo[]o</paragraph>' );
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal(
+				'<paragraph>fo</paragraph>[<horizontalRule></horizontalRule>]<paragraph>o</paragraph>'
+			);
+		} );
+	} );
+} );

+ 107 - 0
packages/ckeditor5-horizontal-line/tests/horizontalruleediting.js

@@ -0,0 +1,107 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
+import HorizontalRuleEditing from '../src/horizontalruleediting';
+import HorizontalRuleCommand from '../src/horizontalrulecommand';
+import { isHorizontalRuleWidget } from '../src/utils';
+import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
+import env from '@ckeditor/ckeditor5-utils/src/env';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+
+describe( 'HorizontalRuleEditing', () => {
+	let editor, model, view, viewDocument;
+
+	testUtils.createSinonSandbox();
+
+	beforeEach( () => {
+		// Most tests assume non-edge environment but we do not set `contenteditable=false` on Edge so stub `env.isEdge`.
+		testUtils.sinon.stub( env, 'isEdge' ).get( () => false );
+
+		return VirtualTestEditor
+			.create( {
+				plugins: [ HorizontalRuleEditing ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+				model = editor.model;
+				view = editor.editing.view;
+				viewDocument = view.document;
+			} );
+	} );
+
+	it( 'should be loaded', () => {
+		expect( editor.plugins.get( HorizontalRuleEditing ) ).to.be.instanceOf( HorizontalRuleEditing );
+	} );
+
+	it( 'should set proper schema rules', () => {
+		expect( model.schema.checkChild( [ '$root' ], 'horizontalRule' ) ).to.be.true;
+
+		expect( model.schema.isObject( 'horizontalRule' ) ).to.be.true;
+
+		expect( model.schema.checkChild( [ '$root', 'horizontalRule' ], '$text' ) ).to.be.false;
+		expect( model.schema.checkChild( [ '$root', '$block' ], 'horizontalRule' ) ).to.be.false;
+	} );
+
+	it( 'should register imageInsert command', () => {
+		expect( editor.commands.get( 'horizontalRule' ) ).to.be.instanceOf( HorizontalRuleCommand );
+	} );
+
+	describe( 'conversion in data pipeline', () => {
+		describe( 'model to view', () => {
+			it( 'should convert', () => {
+				setModelData( model, '<horizontalRule></horizontalRule>' );
+
+				expect( editor.getData() ).to.equal( '<hr>' );
+			} );
+		} );
+
+		describe( 'view to model', () => {
+			it( 'should convert the <hr> element', () => {
+				editor.setData( '<hr>' );
+
+				expect( getModelData( model, { withoutSelection: true } ) )
+					.to.equal( '<horizontalRule></horizontalRule>' );
+			} );
+
+			it( 'should not convert in wrong context', () => {
+				model.schema.register( 'div', { inheritAllFrom: '$block' } );
+				model.schema.addChildCheck( ( ctx, childDef ) => {
+					if ( ctx.endsWith( '$root' ) && childDef.name == 'horizontalRule' ) {
+						return false;
+					}
+				} );
+
+				editor.conversion.elementToElement( { model: 'div', view: 'div' } );
+
+				editor.setData( '<div><hr></div>' );
+
+				expect( getModelData( model, { withoutSelection: true } ) )
+					.to.equal( '<div></div>' );
+			} );
+		} );
+	} );
+
+	describe( 'conversion in editing pipeline', () => {
+		describe( 'model to view', () => {
+			it( 'should convert', () => {
+				setModelData( model, '<horizontalRule></horizontalRule>' );
+
+				expect( getViewData( view, { withoutSelection: true } ) ).to.equal(
+					'<div class="ck-horizontal-rule ck-widget" contenteditable="false"><hr></hr></div>'
+				);
+			} );
+
+			it( 'converted element should be widgetized', () => {
+				setModelData( model, '<horizontalRule></horizontalRule>' );
+				const widget = viewDocument.getRoot().getChild( 0 );
+
+				expect( widget.name ).to.equal( 'div' );
+				expect( isHorizontalRuleWidget( widget ) ).to.be.true;
+			} );
+		} );
+	} );
+} );

+ 67 - 0
packages/ckeditor5-horizontal-line/tests/horizontalruleui.js

@@ -0,0 +1,67 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals document */
+
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import HorizontalRuleEditing from '../src/horizontalruleediting';
+import HorizontalRuleUI from '../src/horizontalruleui';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+
+describe( 'HorizontalRuleUI', () => {
+	let editor, editorElement, horizontalRuleView;
+
+	testUtils.createSinonSandbox();
+
+	beforeEach( () => {
+		editorElement = document.createElement( 'div' );
+		document.body.appendChild( editorElement );
+
+		return ClassicTestEditor
+			.create( editorElement, {
+				plugins: [ Paragraph, HorizontalRuleEditing, HorizontalRuleUI ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+
+				horizontalRuleView = editor.ui.componentFactory.create( 'horizontalRule' );
+			} );
+	} );
+
+	afterEach( () => {
+		return editor.destroy()
+			.then( () => {
+				editorElement.remove();
+			} );
+	} );
+
+	it( 'should register horizontalRule feature component', () => {
+		expect( horizontalRuleView ).to.be.instanceOf( ButtonView );
+		expect( horizontalRuleView.label ).to.equal( 'Horizontal rule' );
+		expect( horizontalRuleView.icon ).to.match( /<svg / );
+		expect( horizontalRuleView.isToggleable ).to.be.true;
+	} );
+
+	it( 'should execute horizontalRule command on model execute event', () => {
+		const executeSpy = testUtils.sinon.spy( editor, 'execute' );
+
+		horizontalRuleView.fire( 'execute' );
+
+		sinon.assert.calledOnce( executeSpy );
+		sinon.assert.calledWithExactly( executeSpy, 'horizontalRule' );
+	} );
+
+	it( 'should bind model to horizontalRule command', () => {
+		const command = editor.commands.get( 'horizontalRule' );
+
+		expect( horizontalRuleView.isEnabled ).to.be.true;
+
+		command.isEnabled = false;
+		expect( horizontalRuleView.isEnabled ).to.be.false;
+	} );
+} );