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

+ 20 - 0
packages/ckeditor5-page-break/tests/manual/pagebreak.html

@@ -0,0 +1,20 @@
+<button type="button" id="log-data" disabled>Log editor data</button>
+
+<div class="wrapper">
+	<div id="editor">
+		<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris viverra ipsum a sapien accumsan, in fringilla ligula congue. Suspendisse eget urna ac nulla dignissim sollicitudin vel non sem. Curabitur consequat nisi vel orci mollis tincidunt. Nam eget sapien non ligula aliquet commodo vel sed lectus. Sed arcu orci, vehicula vitae augue lobortis, posuere tristique nisl. Sed eleifend venenatis magna in elementum. Cras sit amet arcu mi. Suspendisse vel purus a ex maximus pharetra quis in massa. Mauris pellentesque leo sed mi faucibus molestie. Cras felis justo, volutpat sed erat at, lacinia fermentum nunc. Pellentesque est leo, dignissim at odio sit amet, vulputate placerat turpis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla eget pharetra enim. Donec fermentum ligula est, quis ultrices arcu tristique eu. Vivamus a dui sem.</p>
+		<div style="page-break-after: always"><span style="display: none;">&nbsp;</span></div>
+		<p>Nunc mattis vehicula quam, eu ultrices elit. Nam rutrum, magna vel pharetra fermentum, mauris lectus aliquam mauris, ultrices sagittis massa justo ut urna. Curabitur nec odio commodo, euismod orci quis, lacinia magna. Cras cursus id dui et eleifend. Fusce eget consequat ante. Quisque at odio diam. Praesent vel lacinia urna, vel hendrerit diam. Nulla facilisi. Curabitur finibus augue luctus mi dapibus rutrum. Vestibulum id mi quam. Donec accumsan felis lacus, ac luctus arcu sagittis eget. Suspendisse porttitor mattis magna, in finibus ligula suscipit non. Mauris dictum convallis vehicula.</p>
+	</div>
+</div>
+
+<style>
+	#editor {
+		margin: 0 auto;
+		max-width: 800px;
+	}
+
+	.wrapper {
+		padding: 50px 20px;
+	}
+</style>

+ 64 - 0
packages/ckeditor5-page-break/tests/manual/pagebreak.js

@@ -0,0 +1,64 @@
+/**
+ * @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 console, window, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
+import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload';
+import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage';
+import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
+import PageBreak from '../../src/pagebreak';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		cloudServices: CS_CONFIG,
+		plugins: [ ArticlePluginSet, ImageUpload, EasyImage, PageBreak ],
+		toolbar: [
+			'heading',
+			'|',
+			'bold', 'italic', 'numberedList', 'bulletedList',
+			'|',
+			'link', 'blockquote', 'imageUpload', 'insertTable', 'mediaEmbed',
+			'|',
+			'undo', 'redo',
+			'|',
+			'pageBreak'
+		],
+		image: {
+			styles: [
+				'full',
+				'alignLeft',
+				'alignRight'
+			],
+			toolbar: [
+				'imageStyle:alignLeft',
+				'imageStyle:full',
+				'imageStyle:alignRight',
+				'|',
+				'imageTextAlternative'
+			]
+		},
+		table: {
+			contentToolbar: [
+				'tableColumn',
+				'tableRow',
+				'mergeTableCells'
+			]
+		},
+	} )
+	.then( editor => {
+		window.editor = editor;
+
+		const logDataButton = document.querySelector( '#log-data' );
+
+		logDataButton.disabled = false;
+		logDataButton.addEventListener( 'click', () => {
+			console.log( window.editor.getData() );
+		} );
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 1 - 0
packages/ckeditor5-page-break/tests/manual/pagebreak.md

@@ -0,0 +1 @@
+There should be a page break widget between two paragraphs in the editor.

+ 18 - 0
packages/ckeditor5-page-break/tests/pagebreak.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 PageBreak from '../src/pagebreak';
+import PageBreakEditing from '../src/pagebreakediting';
+import PageBreakUI from '../src/pagebreakui';
+
+describe( 'PageBreak', () => {
+	it( 'should require PageBreakEditing and PageBreakUI', () => {
+		expect( PageBreak.requires ).to.deep.equal( [ PageBreakEditing, PageBreakUI ] );
+	} );
+
+	it( 'should be named', () => {
+		expect( PageBreak.pluginName ).to.equal( 'PageBreak' );
+	} );
+} );

+ 154 - 0
packages/ckeditor5-page-break/tests/pagebreakcommand.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 PageBreakEditing from '../src/pagebreakediting';
+
+describe( 'PageBreakCommand', () => {
+	let editor, model, editorElement, command;
+
+	testUtils.createSinonSandbox();
+
+	beforeEach( () => {
+		editorElement = document.createElement( 'div' );
+		document.body.appendChild( editorElement );
+
+		return ClassicTestEditor
+			.create( editorElement, {
+				plugins: [ Paragraph, PageBreakEditing ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+				model = editor.model;
+				command = editor.commands.get( 'pageBreak' );
+			} );
+	} );
+
+	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 page break element', () => {
+			setModelData( model, '[<pageBreak></pageBreak>]' );
+			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 page break', () => {
+			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 page break', () => {
+			model.schema.register( 'block', { inheritAllFrom: '$block' } );
+			model.schema.extend( 'paragraph', { allowIn: 'block' } );
+			// Block page break in block.
+			model.schema.addChildCheck( ( context, childDefinition ) => {
+				if ( childDefinition.name === 'pageBreak' && 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 page break in an empty root and select it', () => {
+			setModelData( model, '[]' );
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal( '[<pageBreak></pageBreak>]' );
+		} );
+
+		it( 'should split an element where selection is placed and insert a page break (non-collapsed selection)', () => {
+			setModelData( model, '<paragraph>f[o]o</paragraph>' );
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal(
+				'<paragraph>f</paragraph>[<pageBreak></pageBreak>]<paragraph>o</paragraph>'
+			);
+		} );
+
+		it( 'should split an element where selection is placed and insert a page break (collapsed selection)', () => {
+			setModelData( model, '<paragraph>fo[]o</paragraph>' );
+
+			command.execute();
+
+			expect( getModelData( model ) ).to.equal(
+				'<paragraph>fo</paragraph>[<pageBreak></pageBreak>]<paragraph>o</paragraph>'
+			);
+		} );
+	} );
+} );

+ 215 - 0
packages/ckeditor5-page-break/tests/pagebreakediting.js

@@ -0,0 +1,215 @@
+/**
+ * @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 PageBreakEditing from '../src/pagebreakediting';
+import PageBreakCommand from '../src/pagebreakcommand';
+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 { isWidget } from '@ckeditor/ckeditor5-widget/src/utils';
+import env from '@ckeditor/ckeditor5-utils/src/env';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+
+describe( 'PageBreakEditing', () => {
+	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: [ PageBreakEditing ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+				model = editor.model;
+				view = editor.editing.view;
+				viewDocument = view.document;
+			} );
+	} );
+
+	it( 'should be loaded', () => {
+		expect( editor.plugins.get( PageBreakEditing ) ).to.be.instanceOf( PageBreakEditing );
+	} );
+
+	it( 'should set proper schema rules', () => {
+		expect( model.schema.checkChild( [ '$root' ], 'pageBreak' ) ).to.be.true;
+
+		expect( model.schema.isObject( 'pageBreak' ) ).to.be.true;
+
+		expect( model.schema.checkChild( [ '$root', 'pageBreak' ], '$text' ) ).to.be.false;
+		expect( model.schema.checkChild( [ '$root', '$block' ], 'pageBreak' ) ).to.be.false;
+	} );
+
+	it( 'should register imageInsert command', () => {
+		expect( editor.commands.get( 'pageBreak' ) ).to.be.instanceOf( PageBreakCommand );
+	} );
+
+	describe( 'conversion in data pipeline', () => {
+		describe( 'model to view', () => {
+			it( 'should convert', () => {
+				setModelData( model, '<pageBreak></pageBreak>' );
+
+				expect( editor.getData() ).to.equal(
+					'<div style="page-break-after:always;"><span style="display:none;">&nbsp;</span></div>'
+				);
+			} );
+		} );
+
+		describe( 'view to model', () => {
+			it( 'should convert the page break code element', () => {
+				editor.setData( '<div style="page-break-after:always;"><span style="display:none;">&nbsp;</span></div>' );
+
+				expect( getModelData( model, { withoutSelection: true } ) )
+					.to.equal( '<pageBreak></pageBreak>' );
+			} );
+
+			it( 'should not convert in wrong context', () => {
+				model.schema.register( 'div', { inheritAllFrom: '$block' } );
+				model.schema.addChildCheck( ( ctx, childDef ) => {
+					if ( ctx.endsWith( '$root' ) && childDef.name == 'pageBreak' ) {
+						return false;
+					}
+				} );
+
+				editor.conversion.elementToElement( { model: 'div', view: 'div' } );
+
+				editor.setData( '<div><div style="page-break-after:always;"><span style="display:none;">&nbsp;</span></div></div>' );
+
+				expect( getModelData( model, { withoutSelection: true } ) )
+					.to.equal( '<div></div>' );
+			} );
+
+			it( 'should not convert if outer div has wrong styles', () => {
+				editor.setData( '<div style="page-break-after:auto;"><span style="display:none;">&nbsp;</span></div>' );
+
+				expect( getModelData( model, { withoutSelection: true } ) )
+					.to.equal( '' );
+			} );
+
+			it( 'should not convert if outer div has no children', () => {
+				editor.setData( '<div style="page-break-after:always;"></div>' );
+
+				expect( getModelData( model, { withoutSelection: true } ) )
+					.to.equal( '' );
+			} );
+
+			it( 'should not convert if outer div has too many children', () => {
+				editor.setData(
+					'<div style="page-break-after:always;">' +
+						'<span style="display:none;">&nbsp;</span>' +
+						'<span style="display:none;">&nbsp;</span>' +
+					'</div>'
+				);
+
+				expect( getModelData( model, { withoutSelection: true } ) )
+					.to.equal( '' );
+			} );
+
+			it( 'should not convert if inner span has wrong styles', () => {
+				editor.setData(
+					'<div style="page-break-after:always;">' +
+					'<span style="display:inline-block;">&nbsp;</span>' +
+					'</div>'
+				);
+
+				expect( getModelData( model, { withoutSelection: true } ) )
+					.to.equal( '' );
+			} );
+
+			it( 'should not convert if inner span has wrong styles', () => {
+				editor.setData(
+					'<div style="page-break-after:always;">' +
+					'<span style="display:inline-block;">&nbsp;</span>' +
+					'</div>'
+				);
+
+				expect( getModelData( model, { withoutSelection: true } ) )
+					.to.equal( '' );
+			} );
+
+			it( 'should not convert if inner span has any children', () => {
+				editor.setData(
+					'<div style="page-break-after:always;">' +
+						'<span style="display:none;">' +
+							'<span>Foo</span>' +
+						'</span>' +
+					'</div>'
+				);
+
+				expect( getModelData( model, { withoutSelection: true } ) )
+					.to.equal( '' );
+			} );
+
+			it( 'should not convert if inner span has text', () => {
+				editor.setData(
+					'<div style="page-break-after:always;">' +
+						'<span style="display:none;">Foo</span>' +
+					'</div>'
+				);
+
+				expect( getModelData( model, { withoutSelection: true } ) )
+					.to.equal( '' );
+			} );
+
+			it( 'should not convert inner span if outer div was already consumed', () => {
+				model.schema.register( 'section', { inheritAllFrom: '$block' } );
+				editor.conversion.elementToElement( { model: 'section', view: 'section' } );
+
+				model.schema.register( 'span', { inheritAllFrom: '$block' } );
+				editor.model.schema.extend( '$text', { allowAttributes: 'foo' } );
+
+				editor.conversion.attributeToElement( {
+					model: 'foo',
+					view: {
+						name: 'span',
+						styles: {
+							display: 'none'
+						}
+					}
+				} );
+
+				editor.setData(
+					'<section>' +
+						'<div style="page-break-after:always;">' +
+							'<span style="display:none;">&nbsp;</span>' +
+						'</div>' +
+						'<span style="display:none;">Foo</span>' +
+					'</section>'
+				);
+
+				expect( getModelData( model, { withoutSelection: true } ) )
+					.to.equal( '<pageBreak></pageBreak><section><$text foo="true">Foo</$text></section>' );
+			} );
+		} );
+	} );
+
+	describe( 'conversion in editing pipeline', () => {
+		describe( 'model to view', () => {
+			it( 'should convert', () => {
+				setModelData( model, '<pageBreak></pageBreak>' );
+
+				expect( getViewData( view, { withoutSelection: true } ) ).to.equal(
+					'<div class="ck-page-break ck-widget" contenteditable="false"></div>'
+				);
+			} );
+
+			it( 'converted element should be widgetized', () => {
+				setModelData( model, '<pageBreak></pageBreak>' );
+				const widget = viewDocument.getRoot().getChild( 0 );
+
+				expect( widget.name ).to.equal( 'div' );
+				expect( isPageBreakWidget( widget ) ).to.be.true;
+			} );
+		} );
+	} );
+
+	function isPageBreakWidget( viewElement ) {
+		return !!viewElement.getCustomProperty( 'pageBreak' ) && isWidget( viewElement );
+	}
+} );

+ 67 - 0
packages/ckeditor5-page-break/tests/pagebreakui.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 PageBreakEditing from '../src/pagebreakediting';
+import PageBreakUI from '../src/pagebreakui';
+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( 'PageBreakUI', () => {
+	let editor, editorElement, pageBreakView;
+
+	testUtils.createSinonSandbox();
+
+	beforeEach( () => {
+		editorElement = document.createElement( 'div' );
+		document.body.appendChild( editorElement );
+
+		return ClassicTestEditor
+			.create( editorElement, {
+				plugins: [ Paragraph, PageBreakEditing, PageBreakUI ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+
+				pageBreakView = editor.ui.componentFactory.create( 'pageBreak' );
+			} );
+	} );
+
+	afterEach( () => {
+		return editor.destroy()
+			.then( () => {
+				editorElement.remove();
+			} );
+	} );
+
+	it( 'should register pageBreak feature component', () => {
+		expect( pageBreakView ).to.be.instanceOf( ButtonView );
+		expect( pageBreakView.label ).to.equal( 'Page break' );
+		expect( pageBreakView.icon ).to.match( /<svg / );
+		expect( pageBreakView.isToggleable ).to.be.true;
+	} );
+
+	it( 'should execute pageBreak command on model execute event', () => {
+		const executeSpy = testUtils.sinon.spy( editor, 'execute' );
+
+		pageBreakView.fire( 'execute' );
+
+		sinon.assert.calledOnce( executeSpy );
+		sinon.assert.calledWithExactly( executeSpy, 'pageBreak' );
+	} );
+
+	it( 'should bind model to pageBreak command', () => {
+		const command = editor.commands.get( 'pageBreak' );
+
+		expect( pageBreakView.isEnabled ).to.be.true;
+
+		command.isEnabled = false;
+		expect( pageBreakView.isEnabled ).to.be.false;
+	} );
+} );