8
0
Эх сурвалжийг харах

Tests for LinkImageEditing.

Kamil Piechaczek 5 жил өмнө
parent
commit
8e875a1bd3

+ 1 - 0
packages/ckeditor5-link/package.json

@@ -20,6 +20,7 @@
     "@ckeditor/ckeditor5-clipboard": "^19.0.1",
     "@ckeditor/ckeditor5-editor-classic": "^19.0.1",
     "@ckeditor/ckeditor5-enter": "^19.0.1",
+    "@ckeditor/ckeditor5-image": "^19.0.1",
     "@ckeditor/ckeditor5-paragraph": "^19.1.0",
     "@ckeditor/ckeditor5-theme-lark": "^19.1.0",
     "@ckeditor/ckeditor5-typing": "^19.0.1",

+ 62 - 49
packages/ckeditor5-link/src/linkimageediting.js

@@ -8,7 +8,7 @@
  */
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import Image from '@ckeditor/ckeditor5-image/src/image';
+import ImageEditing from '@ckeditor/ckeditor5-image/src/image/imageediting';
 import LinkEditing from './linkediting';
 
 export default class LinkImageEditing extends Plugin {
@@ -16,7 +16,7 @@ export default class LinkImageEditing extends Plugin {
 	 * @inheritDoc
 	 */
 	static get requires() {
-		return [ Image, LinkEditing ];
+		return [ ImageEditing, LinkEditing ];
 	}
 
 	/**
@@ -32,8 +32,6 @@ export default class LinkImageEditing extends Plugin {
 		editor.model.schema.extend( 'image', { allowAttributes: [ 'linkHref' ] } );
 
 		editor.conversion.for( 'upcast' ).add( this._upcastLink() );
-		editor.conversion.for( 'upcast' ).add( this._upcastImageLink( 'img' ) );
-		editor.conversion.for( 'upcast' ).add( this._upcastImageLink( 'figure' ) );
 		editor.conversion.for( 'downcast' ).add( this._downcastImageLink() );
 	}
 
@@ -49,47 +47,48 @@ export default class LinkImageEditing extends Plugin {
 				const viewLink = data.viewItem;
 				const imageInLink = Array.from( viewLink.getChildren() ).find( child => child.name === 'img' );
 
-				if ( imageInLink ) {
-					// There's an image inside an <a> element - we consume it so it won't be picked up by the Link plugin.
-					const consumableAttributes = { attributes: [ 'href' ] };
+				if ( !imageInLink ) {
+					return;
+				}
 
-					// Consume the link so the default one will not convert it to $text attribute.
-					if ( !conversionApi.consumable.test( viewLink, consumableAttributes ) ) {
-						// Might be consumed by something else - i.e. other converter with priority=highest - a standard check.
-						return;
-					}
+				// There's an image inside an <a> element - we consume it so it won't be picked up by the Link plugin.
+				const consumableAttributes = { attributes: [ 'href' ] };
 
-					// Consume 'linkHref' attribute from link element.
-					conversionApi.consumable.consume( viewLink, consumableAttributes );
+				// Consume the `href` attribute so the default one will not convert it to $text attribute.
+				if ( !conversionApi.consumable.consume( viewLink, consumableAttributes ) ) {
+					// Might be consumed by something else - i.e. other converter with priority=highest - a standard check.
+					return;
 				}
-			}, { priority: 'high' } );
-		};
-	}
 
-	/**
-	 * Returns a converter for links that wraps the `<img>` element.
-	 *
-	 * @private
-	 * @param {String} elementName Name of the element to upcast.
-	 * @returns {Function}
-	 */
-	_upcastImageLink( elementName ) {
-		return dispatcher => {
-			dispatcher.on( `element:${ elementName }`, ( evt, data, conversionApi ) => {
-				const viewImage = data.viewItem;
-				const parent = viewImage.parent;
-
-				// Check only <img>/<figure> that are direct children of a link.
-				if ( parent.name === 'a' ) {
-					const modelImage = data.modelCursor.nodeBefore;
-					const linkHref = parent.getAttribute( 'href' );
-
-					if ( modelImage && linkHref ) {
-						// Set the linkHref attribute from link element on model image element.
-						conversionApi.writer.setAttribute( 'linkHref', linkHref, modelImage );
-					}
+				const linkHref = viewLink.getAttribute( 'href' );
+
+				// Missing the 'href' attribute.
+				if ( !linkHref ) {
+					return;
 				}
-			} );
+
+				// A full definition of the image feature.
+				// figure > a > img: parent of the link element is an image element.
+				let modelElement = data.modelCursor.parent;
+
+				if ( !modelElement.is( 'image' ) ) {
+					// a > img: parent of the link is not the image element. We need to convert it manually.
+					const conversionResult = conversionApi.convertItem( imageInLink, data.modelCursor );
+
+					// Set image range as conversion result.
+					data.modelRange = conversionResult.modelRange;
+
+					// Continue conversion where image conversion ends.
+					data.modelCursor = conversionResult.modelCursor;
+
+					modelElement = data.modelCursor.nodeBefore;
+				}
+
+				if ( modelElement && modelElement.is( 'image' ) ) {
+					// Set the linkHref attribute from link element on model image element.
+					conversionApi.writer.setAttribute( 'linkHref', linkHref, modelElement );
+				}
+			}, { priority: 'high' } );
 		};
 	}
 
@@ -103,19 +102,33 @@ export default class LinkImageEditing extends Plugin {
 		return dispatcher => {
 			dispatcher.on( 'attribute:linkHref:image', ( evt, data, conversionApi ) => {
 				// The image will be already converted - so it will be present in the view.
-				const viewImage = conversionApi.mapper.toViewElement( data.item );
-
-				// Below will wrap already converted image by newly created link element.
+				const viewFigure = conversionApi.mapper.toViewElement( data.item );
 				const writer = conversionApi.writer;
 
-				// 1. Create an empty link element.
-				const linkElement = writer.createContainerElement( 'a', { href: data.attributeNewValue } );
+				// But we need to check whether the link element exist.
+				const linkInImage = Array.from( viewFigure.getChildren() ).find( child => child.name === 'a' );
 
-				// 2. Insert link inside the associated image.
-				writer.insert( writer.createPositionAt( viewImage, 0 ), linkElement );
+				// If so, update the attribute if it's defined or remove the entire link if the attribute is empty.
+				if ( linkInImage ) {
+					if ( data.attributeNewValue ) {
+						writer.setAttribute( 'href', data.attributeNewValue, linkInImage );
+					} else {
+						const viewImage = Array.from( linkInImage.getChildren() ).find( child => child.name === 'img' );
 
-				// 3. Move the image to the link.
-				writer.move( writer.createRangeOn( viewImage.getChild( 1 ) ), writer.createPositionAt( linkElement, 0 ) );
+						writer.move( writer.createRangeOn( viewImage ), writer.createPositionAt( viewFigure, 0 ) );
+						writer.remove( linkInImage );
+					}
+				} else {
+					// But if it does not exist. Let's wrap already converted image by newly created link element.
+					// 1. Create an empty link element.
+					const linkElement = writer.createContainerElement( 'a', { href: data.attributeNewValue } );
+
+					// 2. Insert link inside the associated image.
+					writer.insert( writer.createPositionAt( viewFigure, 0 ), linkElement );
+
+					// 3. Move the image to the link.
+					writer.move( writer.createRangeOn( viewFigure.getChild( 1 ) ), writer.createPositionAt( linkElement, 0 ) );
+				}
 			} );
 		};
 	}

+ 18 - 0
packages/ckeditor5-link/tests/linkimage.js

@@ -0,0 +1,18 @@
+/**
+ * @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 LinkImage from '../src/linkimage';
+import LinkImageEditing from '../src/linkimageediting';
+import LinkImageUI from '../src/linkimageui';
+
+describe( 'LinkImage', () => {
+	it( 'should require LinkImageEditing and LinkImageUI', () => {
+		expect( LinkImage.requires ).to.deep.equal( [ LinkImageEditing, LinkImageUI ] );
+	} );
+
+	it( 'should be named', () => {
+		expect( LinkImage.pluginName ).to.equal( 'LinkImage' );
+	} );
+} );

+ 305 - 0
packages/ckeditor5-link/tests/linkimageediting.js

@@ -0,0 +1,305 @@
+/**
+ * @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 VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import LinkImageEditing from '../src/linkimageediting';
+import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import normalizeHtml from '@ckeditor/ckeditor5-utils/tests/_utils/normalizehtml';
+import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
+import ImageCaptionEditing from '@ckeditor/ckeditor5-image/src/imagecaption/imagecaptionediting';
+
+describe( 'LinkImageEditing', () => {
+	let editor, model, view;
+
+	beforeEach( () => {
+		return VirtualTestEditor
+			.create( {
+				plugins: [ Paragraph, LinkImageEditing ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+				model = editor.model;
+				view = editor.editing.view;
+			} );
+	} );
+
+	afterEach( () => {
+		return editor.destroy();
+	} );
+
+	it( 'should have pluginName', () => {
+		expect( LinkImageEditing.pluginName ).to.equal( 'LinkImageEditing' );
+	} );
+
+	it( 'should be loaded', () => {
+		expect( editor.plugins.get( LinkImageEditing ) ).to.be.instanceOf( LinkImageEditing );
+	} );
+
+	it( 'should set proper schema rules', () => {
+		expect( model.schema.checkAttribute( [ '$root', 'image' ], 'linkHref' ) ).to.be.true;
+	} );
+
+	describe( 'conversion in data pipeline', () => {
+		describe( 'model to view', () => {
+			it( 'should convert an image with a link', () => {
+				setModelData( model, '<image src="/assets/sample.png" alt="alt text" linkHref="http://ckeditor.com"></image>' );
+
+				expect( editor.getData() ).to.equal(
+					'<figure class="image"><a href="http://ckeditor.com"><img alt="alt text" src="/assets/sample.png"></a></figure>'
+				);
+			} );
+
+			it( 'should convert an image with a link and without alt attribute', () => {
+				setModelData( model, '<image src="/assets/sample.png" linkHref="http://ckeditor.com"></image>' );
+
+				expect( editor.getData() ).to.equal(
+					'<figure class="image"><a href="http://ckeditor.com"><img src="/assets/sample.png"></a></figure>'
+				);
+			} );
+
+			it( 'should convert srcset attribute to srcset and sizes attribute wrapped into a link', () => {
+				setModelData( model,
+					'<image src="/assets/sample.png" ' +
+						'linkHref="http://ckeditor.com" ' +
+						'srcset=\'{ "data": "small.png 148w, big.png 1024w" }\'>' +
+					'</image>'
+				);
+
+				expect( normalizeHtml( editor.getData() ) ).to.equal(
+					'<figure class="image">' +
+						'<a href="http://ckeditor.com">' +
+							'<img sizes="100vw" src="/assets/sample.png" srcset="small.png 148w, big.png 1024w"></img>' +
+						'</a>' +
+					'</figure>'
+				);
+			} );
+		} );
+
+		describe( 'view to model', () => {
+			describe( 'figure > a > img', () => {
+				it( 'should convert a link in an image figure', () => {
+					editor.setData(
+						'<figure class="image"><a href="http://ckeditor.com"><img src="/assets/sample.png" alt="alt text" /></a></figure>'
+					);
+
+					expect( getModelData( model, { withoutSelection: true } ) )
+						.to.equal( '<image alt="alt text" linkHref="http://ckeditor.com" src="/assets/sample.png"></image>' );
+				} );
+
+				it( 'should convert an image with a link and without alt attribute', () => {
+					editor.setData( '<figure class="image"><a href="http://ckeditor.com"><img src="/assets/sample.png" /></a></figure>' );
+
+					expect( getModelData( model, { withoutSelection: true } ) )
+						.to.equal( '<image linkHref="http://ckeditor.com" src="/assets/sample.png"></image>' );
+				} );
+
+				it( 'should not convert without src attribute', () => {
+					editor.setData( '<figure class="image"><a href="http://ckeditor.com"><img alt="alt text" /></a></figure>' );
+
+					expect( getModelData( model, { withoutSelection: true } ) )
+						.to.equal( '<paragraph></paragraph>' );
+				} );
+
+				it( 'should not convert in wrong context', () => {
+					model.schema.register( 'div', { inheritAllFrom: '$block' } );
+					model.schema.addChildCheck( ( ctx, childDef ) => {
+						if ( ctx.endsWith( '$root' ) && childDef.name == 'image' ) {
+							return false;
+						}
+					} );
+
+					editor.conversion.elementToElement( { model: 'div', view: 'div' } );
+
+					editor.setData(
+						'<div>' +
+							'<figure class="image">' +
+								'<a href="http://ckeditor.com">' +
+									'<img src="/assets/sample.png" alt="alt text" />' +
+								'</a>' +
+							'</figure>' +
+						'</div>' );
+
+					expect( getModelData( model, { withoutSelection: true } ) )
+						.to.equal( '<div></div>' );
+				} );
+
+				it( 'should not convert "a" element if is already consumed', () => {
+					editor.data.upcastDispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
+						conversionApi.consumable.consume( data.viewItem, { attributes: [ 'href' ] } );
+					}, { priority: 'highest' } );
+
+					editor.setData(
+						'<figure class="image"><a href="http://ckeditor.com"><img src="/assets/sample.png" alt="alt text" /></a></figure>'
+					);
+
+					expect( editor.getData() ).to.equal( '<figure class="image"><img src="/assets/sample.png" alt="alt text"></figure>' );
+				} );
+
+				it( 'should not convert if a link misses "href" attribute', () => {
+					editor.setData(
+						'<figure class="image"><a href=""><img src="/assets/sample.png" alt="alt text" /></a></figure>'
+					);
+
+					expect( getModelData( model, { withoutSelection: true } ) )
+						.to.equal( '<image alt="alt text" src="/assets/sample.png"></image>' );
+				} );
+
+				it( 'should convert a link without an image to a paragraph with the link', () => {
+					editor.setData(
+						'<figure class="image"><a href="http://ckeditor.com">Foo</a></figure>'
+					);
+
+					expect( getModelData( model, { withoutSelection: true } ) )
+						.to.equal( '<paragraph><$text linkHref="http://ckeditor.com">Foo</$text></paragraph>' );
+				} );
+			} );
+
+			describe( 'a > img', () => {
+				it( 'should convert a link in an image figure', () => {
+					editor.setData(
+						'<a href="http://ckeditor.com"><img src="/assets/sample.png" alt="alt text" /></a>'
+					);
+
+					expect( getModelData( model, { withoutSelection: true } ) )
+						.to.equal( '<image alt="alt text" linkHref="http://ckeditor.com" src="/assets/sample.png"></image>' );
+				} );
+
+				it( 'should convert an image with a link and without alt attribute', () => {
+					editor.setData( '<a href="http://ckeditor.com"><img src="/assets/sample.png" /></a>' );
+
+					expect( getModelData( model, { withoutSelection: true } ) )
+						.to.equal( '<image linkHref="http://ckeditor.com" src="/assets/sample.png"></image>' );
+				} );
+
+				it( 'should not convert without src attribute', () => {
+					editor.setData( '<a href="http://ckeditor.com"><img alt="alt text" /></a>' );
+
+					expect( getModelData( model, { withoutSelection: true } ) )
+						.to.equal( '<paragraph></paragraph>' );
+				} );
+
+				it( 'should not convert in wrong context', () => {
+					model.schema.register( 'div', { inheritAllFrom: '$block' } );
+					model.schema.addChildCheck( ( ctx, childDef ) => {
+						if ( ctx.endsWith( '$root' ) && childDef.name == 'image' ) {
+							return false;
+						}
+					} );
+
+					editor.conversion.elementToElement( { model: 'div', view: 'div' } );
+
+					editor.setData(
+						'<div>' +
+							'<a href="http://ckeditor.com">' +
+								'<img src="/assets/sample.png" alt="alt text" />' +
+							'</a>' +
+						'</div>' );
+
+					expect( getModelData( model, { withoutSelection: true } ) )
+						.to.equal( '<div></div>' );
+				} );
+
+				it( 'should not convert "a" element if is already consumed', () => {
+					editor.data.upcastDispatcher.on( 'element:a', ( evt, data, conversionApi ) => {
+						conversionApi.consumable.consume( data.viewItem, { attributes: [ 'href' ] } );
+					}, { priority: 'highest' } );
+
+					editor.setData(
+						'<a href="http://ckeditor.com"><img src="/assets/sample.png" alt="alt text" /></a>'
+					);
+
+					expect( editor.getData() ).to.equal( '<figure class="image"><img src="/assets/sample.png" alt="alt text"></figure>' );
+				} );
+
+				it( 'should not convert if a link misses "href" attribute', () => {
+					editor.setData(
+						'<a href=""><img src="/assets/sample.png" alt="alt text" /></a>'
+					);
+
+					expect( getModelData( model, { withoutSelection: true } ) )
+						.to.equal( '<image alt="alt text" src="/assets/sample.png"></image>' );
+				} );
+			} );
+
+			describe( 'figure > a > img + figcaption', () => {
+				it( 'should convert a link and the caption element', () => {
+					return VirtualTestEditor
+						.create( {
+							plugins: [ Paragraph, LinkImageEditing, ImageCaptionEditing ]
+						} )
+						.then( editor => {
+							editor.setData(
+								'<figure class="image">' +
+									'<a href="http://ckeditor.com">' +
+										'<img src="/assets/sample.png" alt="alt text" />' +
+									'</a>' +
+									'<figcaption>' +
+										'Foo Bar.' +
+									'</figcaption>' +
+								'</figure>'
+							);
+
+							expect( getModelData( editor.model, { withoutSelection: true } ) ).to.equal(
+								'<image alt="alt text" linkHref="http://ckeditor.com" src="/assets/sample.png">' +
+									'<caption>Foo Bar.</caption>' +
+								'</image>'
+							);
+
+							return editor.destroy();
+						} );
+				} );
+			} );
+		} );
+	} );
+
+	describe( 'conversion in editing pipeline', () => {
+		describe( 'model to view', () => {
+			it( 'should convert the image element', () => {
+				setModelData( model, '<image linkHref="http://ckeditor.com" src="/assets/sample.png" alt="alt text"></image>' );
+
+				expect( getViewData( view, { withoutSelection: true } ) ).to.equal(
+					'<figure class="ck-widget image" contenteditable="false">' +
+						'<a href="http://ckeditor.com">' +
+							'<img alt="alt text" src="/assets/sample.png"></img>' +
+						'</a>' +
+					'</figure>'
+				);
+			} );
+
+			it( 'should convert attribute change', () => {
+				setModelData( model, '<image linkHref="http://ckeditor.com" src="/assets/sample.png" alt="alt text"></image>' );
+				const image = model.document.getRoot().getChild( 0 );
+
+				model.change( writer => {
+					writer.setAttribute( 'linkHref', 'https://ckeditor.com/why-ckeditor/', image );
+				} );
+
+				expect( getViewData( view, { withoutSelection: true } ) ).to.equal(
+					'<figure class="ck-widget image" contenteditable="false">' +
+						'<a href="https://ckeditor.com/why-ckeditor/">' +
+							'<img alt="alt text" src="/assets/sample.png"></img>' +
+						'</a>' +
+					'</figure>'
+				);
+			} );
+
+			it( 'should convert attribute removal', () => {
+				setModelData( model, '<image linkHref="http://ckeditor.com" src="/assets/sample.png" alt="alt text"></image>' );
+				const image = model.document.getRoot().getChild( 0 );
+
+				model.change( writer => {
+					writer.removeAttribute( 'linkHref', image );
+				} );
+
+				expect( getViewData( view, { withoutSelection: true } ) ).to.equal(
+					'<figure class="ck-widget image" contenteditable="false">' +
+						'<img alt="alt text" src="/assets/sample.png"></img>' +
+					'</figure>'
+				);
+			} );
+		} );
+	} );
+} );

+ 35 - 0
packages/ckeditor5-link/tests/linkimageui.js

@@ -0,0 +1,35 @@
+/**
+ * @license Copyright (c) 2003-2020, 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 testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import LinkImageUI from '../src/linkimageui';
+
+describe( 'LinkImageUI', () => {
+	let editor, editorElement;
+
+	testUtils.createSinonSandbox();
+
+	beforeEach( () => {
+		editorElement = document.createElement( 'div' );
+		document.body.appendChild( editorElement );
+
+		return ClassicTestEditor
+			.create( editorElement, {
+				plugins: [ LinkImageUI ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+			} );
+	} );
+
+	afterEach( () => {
+		editorElement.remove();
+
+		return editor.destroy();
+	} );
+} );