/** * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md. */ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import ImageEngine from '../../src/image/imageengine'; 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 { isImageWidget } from '../../src/image/utils'; import normalizeHtml from '@ckeditor/ckeditor5-utils/tests/_utils/normalizehtml'; describe( 'ImageEngine', () => { let editor, model, document, view, viewDocument; beforeEach( () => { return VirtualTestEditor .create( { plugins: [ ImageEngine ] } ) .then( newEditor => { editor = newEditor; model = editor.model; document = model.document; view = editor.editing.view; viewDocument = view.document; } ); } ); it( 'should be loaded', () => { expect( editor.plugins.get( ImageEngine ) ).to.be.instanceOf( ImageEngine ); } ); it( 'should set proper schema rules', () => { expect( model.schema.checkChild( [ '$root' ], 'image' ) ).to.be.true; expect( model.schema.checkAttribute( [ '$root', 'image' ], 'src' ) ).to.be.true; expect( model.schema.checkAttribute( [ '$root', 'image' ], 'alt' ) ).to.be.true; expect( model.schema.isObject( 'image' ) ).to.be.true; expect( model.schema.checkChild( [ '$root', 'image' ], 'image' ) ).to.be.false; expect( model.schema.checkChild( [ '$root', 'image' ], '$text' ) ).to.be.false; expect( model.schema.checkChild( [ '$root', '$block' ], 'image' ) ).to.be.false; } ); describe( 'conversion in data pipeline', () => { describe( 'model to view', () => { it( 'should convert', () => { setModelData( model, 'alt text' ); expect( editor.getData() ).to.equal( '
alt text
' ); } ); it( 'should convert without alt attribute', () => { setModelData( model, '' ); expect( editor.getData() ).to.equal( '
' ); } ); it( 'should convert srcset attribute to srcset and sizes attribute', () => { setModelData( model, 'alt text' ); expect( normalizeHtml( editor.getData() ) ).to.equal( '
' + 'alt text' + '
' ); } ); it( 'should convert srcset attribute to width, srcset and add sizes attribute', () => { setModelData( model, '' + '' ); expect( normalizeHtml( editor.getData() ) ).to.equal( '
' + 'alt text' + '
' ); } ); it( 'should not convert srcset attribute if is already consumed', () => { editor.data.downcastDispatcher.on( 'attribute:srcset:image', ( evt, data, consumable ) => { const modelImage = data.item; consumable.consume( modelImage, evt.name ); }, { priority: 'high' } ); setModelData( model, '' + '' ); expect( editor.getData() ).to.equal( '
' + 'alt text' + '
' ); } ); it( 'should not convert srcset attribute if has wrong data', () => { setModelData( model, '' + '' ); const image = document.getRoot().getChild( 0 ); model.change( writer => { writer.removeAttribute( 'srcset', image ); } ); expect( editor.getData() ).to.equal( '
' + 'alt text' + '
' ); } ); } ); describe( 'view to model', () => { it( 'should convert image figure', () => { editor.setData( '
alt text
' ); expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( 'alt text' ); } ); it( 'should not convert if there is no image class', () => { editor.setData( '
My quote
' ); expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' ); } ); it( 'should not convert if there is no img inside #1', () => { editor.setData( '
' ); expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' ); } ); it( 'should not convert if there is no img inside #2', () => { editor.setData( '
test
' ); expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' ); } ); it( 'should convert without alt attribute', () => { editor.setData( '
' ); expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' ); } ); it( 'should not convert without src attribute', () => { editor.setData( '
alt text
' ); expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' ); } ); 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( '
alt text
' ); expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '
' ); } ); it( 'should not convert if img is already consumed', () => { editor.data.upcastDispatcher.on( 'element:figure', ( evt, data, conversionApi ) => { const img = data.viewItem.getChild( 0 ); conversionApi.consumable.consume( img, { name: true } ); }, { priority: 'high' } ); editor.setData( '
alt text
' ); expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' ); } ); it( 'should not convert if figure is already consumed', () => { editor.data.upcastDispatcher.on( 'element:figure', ( evt, data, conversionApi ) => { conversionApi.consumable.consume( data.viewItem, { name: true, class: 'image' } ); }, { priority: 'high' } ); editor.setData( '
alt text
' ); expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( '' ); } ); it( 'should dispatch conversion for nested elements', () => { const conversionSpy = sinon.spy(); editor.data.upcastDispatcher.on( 'element:figcaption', conversionSpy ); editor.setData( '
alt text
' ); sinon.assert.calledOnce( conversionSpy ); } ); it( 'should convert bare img element', () => { editor.setData( 'alt text' ); expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( 'alt text' ); } ); it( 'should not convert alt attribute on non-img element', () => { model.schema.register( 'div', { inheritAllFrom: '$block', allowAttributes: 'alt' } ); editor.conversion.elementToElement( { model: 'div', view: 'div' } ); editor.setData( '
' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( '
' ); } ); it( 'should convert image with srcset attribute', () => { editor.setData( '
' + 'alt text' + '
' ); expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( 'alt text' ); } ); it( 'should convert image with srcset and width attributes', () => { editor.setData( '
' + 'alt text' + '
' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( 'alt text' ); } ); it( 'should ignore sizes attribute', () => { editor.setData( '
' + 'alt text' + '
' ); expect( getModelData( model, { withoutSelection: true } ) ) .to.equal( 'alt text' ); } ); describe( 'should autohoist images', () => { beforeEach( () => { model.schema.register( 'div', { inheritAllFrom: '$block' } ); editor.conversion.elementToElement( { model: 'div', view: 'div' } ); } ); it( 'image between non-hoisted elements', () => { editor.setData( '
foofoobar
' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( '
foo
' + 'foo' + '
bar
' ); } ); it( 'multiple images', () => { editor.setData( '
foofoobafoor
' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( '
foo
' + 'foo' + '
ba
' + 'foo' + '
r
' ); } ); it( 'images on borders of parent', () => { editor.setData( '
foofoobarfoo
' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( 'foo' + '
foobar
' + 'foo' ); } ); it( 'images are only content of parent', () => { editor.setData( '
foofoo
' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( 'foo' + 'foo' ); } ); it( 'deep autohoisting #1', () => { model.schema.extend( 'div', { allowIn: 'div' } ); editor.setData( '
foo
xxfoo
bar
' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( '
' + 'foo' + '
' + 'xx' + '
' + '
' + 'foo' + '
bar
' ); } ); it( 'deep autohoisting #2', () => { model.schema.extend( 'div', { allowIn: 'div' } ); editor.setData( '
x
' + '
foo
' + '
y
' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( '
x
foo
y
' ); } ); it( 'should not break a limiting element', () => { model.schema.register( 'limit', { inheritAllFrom: '$block', isLimit: true } ); model.schema.extend( 'div', { allowIn: 'limit' } ); editor.conversion.elementToElement( { model: 'limit', view: 'limit' } ); editor.setData( '
foofoobar
' ); // element does not have converters so it is not converted. expect( getModelData( model, { withoutSelection: true } ) ).to.equal( '
foobar
' ); } ); it( 'should not convert and autohoist image element without src attribute (which is not allowed by schema)', () => { editor.setData( '
foofoobar
' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( '
foobar
' ); } ); } ); } ); } ); describe( 'conversion in editing pipeline', () => { describe( 'model to view', () => { it( 'should convert', () => { setModelData( model, 'alt text' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '
alt text
' ); } ); it( 'converted element should be widgetized', () => { setModelData( model, 'alt text' ); const figure = viewDocument.getRoot().getChild( 0 ); expect( figure.name ).to.equal( 'figure' ); expect( isImageWidget( figure ) ).to.be.true; } ); it( 'should convert attribute change', () => { setModelData( model, 'alt text' ); const image = document.getRoot().getChild( 0 ); model.change( writer => { writer.setAttribute( 'alt', 'new text', image ); } ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '
new text
' ); } ); it( 'should convert attribute removal', () => { setModelData( model, 'alt text' ); const image = document.getRoot().getChild( 0 ); model.change( writer => { writer.removeAttribute( 'alt', image ); } ); expect( getViewData( view, { withoutSelection: true } ) ) .to.equal( '
' ); } ); it( 'should not convert change if is already consumed', () => { setModelData( model, 'alt text' ); const image = document.getRoot().getChild( 0 ); editor.editing.downcastDispatcher.on( 'attribute:alt:image', ( evt, data, consumable ) => { consumable.consume( data.item, 'attribute:alt' ); }, { priority: 'high' } ); model.change( writer => { writer.removeAttribute( 'alt', image ); } ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '
alt text
' ); } ); it( 'should convert srcset attribute to srcset and sizes', () => { setModelData( model, '' + '' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '
' + 'alt text' + '
' ); } ); it( 'should not convert srcset attribute if has wrong data', () => { setModelData( model, '' + '' ); const image = document.getRoot().getChild( 0 ); model.change( writer => { writer.removeAttribute( 'srcset', image ); } ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '
' + 'alt text' + '
' ); } ); it( 'should convert srcset attribute to srcset, width and sizes', () => { setModelData( model, '' + '' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '
' + 'alt text' + '
' ); } ); it( 'should remove sizes and srcsset attribute when srcset attribute is removed from model', () => { setModelData( model, '' ); const image = document.getRoot().getChild( 0 ); model.change( writer => { writer.removeAttribute( 'srcset', image ); } ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '
' + '' + '
' ); } ); it( 'should remove width, sizes and srcsset attribute when srcset attribute is removed from model', () => { setModelData( model, '' + '' ); const image = document.getRoot().getChild( 0 ); model.change( writer => { writer.removeAttribute( 'srcset', image ); } ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '
' + '' + '
' ); } ); it( 'should not convert srcset attribute if is already consumed', () => { editor.editing.downcastDispatcher.on( 'attribute:srcset:image', ( evt, data, consumable ) => { const modelImage = data.item; consumable.consume( modelImage, evt.name ); }, { priority: 'high' } ); setModelData( model, '' + '' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '
' + 'alt text' + '
' ); } ); } ); } ); } );