/**
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
/* global document, console */
import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
import BalloonEditor from '@ckeditor/ckeditor5-editor-balloon/src/ballooneditor';
import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview';
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
import Widget from '../src/widget';
import WidgetToolbarRepository from '../src/widgettoolbarrepository';
import {
isWidget,
toWidget,
centeredBalloonPositionForLongWidgets
} from '../src/utils';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
import View from '@ckeditor/ckeditor5-ui/src/view';
import { setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
describe( 'WidgetToolbarRepository', () => {
let editor, model, balloon, widgetToolbarRepository, editorElement;
testUtils.createSinonSandbox();
beforeEach( () => {
editorElement = document.createElement( 'div' );
document.body.appendChild( editorElement );
return ClassicTestEditor
.create( editorElement, {
plugins: [ Paragraph, FakeButton, WidgetToolbarRepository, FakeWidget, FakeChildWidget, BlockQuote ],
fake: {
toolbar: [ 'fake_button' ]
}
} )
.then( newEditor => {
editor = newEditor;
model = newEditor.model;
widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
balloon = editor.plugins.get( 'ContextualBalloon' );
} );
} );
afterEach( () => {
editorElement.remove();
return editor.destroy();
} );
it( 'should be loaded', () => {
expect( editor.plugins.get( WidgetToolbarRepository ) ).to.be.instanceOf( WidgetToolbarRepository );
} );
it( 'should work if balloon toolbar is not available', () => {
editorElement.remove();
editor.destroy();
editorElement = document.createElement( 'div' );
document.body.appendChild( editorElement );
expect( editor.plugins.has( 'BalloonToolbar' ) ).to.be.false;
expect( editor.plugins.has( WidgetToolbarRepository ) ).to.be.true;
} );
describe( 'register()', () => {
it( 'should create a widget toolbar and add it to the collection', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: () => null
} );
expect( widgetToolbarRepository._toolbarDefinitions.size ).to.equal( 1 );
expect( widgetToolbarRepository._toolbarDefinitions.get( 'fake' ) ).to.be.an( 'object' );
} );
it( 'should throw when adding two times widget with the same id', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: () => null
} );
expectToThrowCKEditorError( () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: () => null
} );
}, /^widget-toolbar-duplicated/, editor );
} );
it( 'should use a pre–defined aria-label for the toolbar', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: () => null
} );
const toolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake' ).view;
toolbarView.render();
expect( toolbarView.element.getAttribute( 'aria-label' ) ).to.equal( 'Widget toolbar' );
toolbarView.destroy();
} );
it( 'should use a custom aria-label when provided', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: () => null,
ariaLabel: 'Custom label'
} );
const toolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake' ).view;
toolbarView.render();
expect( toolbarView.element.getAttribute( 'aria-label' ) ).to.equal( 'Custom label' );
toolbarView.destroy();
} );
it( 'should not register a toolbar when passed an empty collection of the items', () => {
const consoleWarnStub = sinon.stub( console, 'warn' );
widgetToolbarRepository.register( 'fake', {
items: [],
getRelatedElement: () => null
} );
expect( widgetToolbarRepository._toolbarDefinitions.get( 'fake' ) ).to.be.undefined;
expect( consoleWarnStub.calledOnce ).to.equal( true );
expect( consoleWarnStub.firstCall.args[ 0 ] ).to.match( /^widget-toolbar-no-items:/ );
} );
} );
describe( 'integration tests', () => {
beforeEach( () => {
editor.ui.focusTracker.isFocused = true;
} );
it( 'toolbar should be visible when the `getRelatedElement` callback returns a selected widget element', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
setData( model, 'foo[]' );
const fakeWidgetToolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake' ).view;
expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView );
} );
it( 'toolbar should be hidden when the plugin gets disabled', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
setData( model, 'foo[]' );
widgetToolbarRepository.isEnabled = false;
expect( balloon.visibleView ).to.be.null;
} );
it( 'toolbar should be hidden when the plugin was disabled prior changing selection', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
widgetToolbarRepository.isEnabled = false;
setData( model, 'foo[]' );
expect( balloon.visibleView ).to.be.null;
} );
it( 'toolbar should be hidden when the `getRelatedElement` callback returns null', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
setData( model, '[foo]' );
expect( balloon.visibleView ).to.equal( null );
} );
it( 'toolbar should be hidden when the `getRelatedElement` callback returns null #2', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
setData( model, 'foo[]' );
model.change( writer => {
// Select the foo.
writer.setSelection( model.document.getRoot().getChild( 0 ), 'in' );
} );
expect( balloon.visibleView ).to.equal( null );
} );
it( 'toolbar should be removed from not visible balloon stack when the `getRelatedElement` callback returns null', () => {
balloon.add( {
view: new View(),
stackId: 'secondary',
position: {
target: {}
}
} );
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
setData( model, 'foo[]' );
const fakeWidgetToolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake' ).view;
expect( balloon.hasView( fakeWidgetToolbarView ) );
expect( balloon.visibleView ).to.not.equal( fakeWidgetToolbarView );
model.change( writer => {
// Select the foo.
writer.setSelection( model.document.getRoot().getChild( 0 ), 'in' );
} );
expect( balloon.hasView( fakeWidgetToolbarView ) ).to.equal( false );
} );
it( 'toolbar should be hidden when the editor ui lost focus', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
setData( model, 'foo[]' );
editor.ui.focusTracker.isFocused = false;
expect( balloon.visibleView ).to.equal( null );
} );
it( 'toolbar should do nothing with toolbar when the editor ui lost focus but toolbar is not a visible view', () => {
balloon.add( {
view: new View(),
stackId: 'secondary',
position: {
target: {}
}
} );
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
setData( model, 'foo[]' );
const fakeWidgetToolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake' ).view;
editor.ui.focusTracker.isFocused = false;
expect( balloon.hasView( fakeWidgetToolbarView ) ).to.equal( true );
} );
it( 'toolbar should update its position when other widget is selected', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
setData( model, '[]' );
model.change( writer => {
// Select the second widget.
writer.setSelection( model.document.getRoot().getChild( 1 ), 'on' );
} );
const fakeWidgetToolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake' ).view;
expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView );
} );
it( 'it should be possible to create a widget toolbar for content inside the widget', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidgetContent
} );
setData( model, '[foo]' );
const fakeWidgetToolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake' ).view;
expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView );
} );
it( 'toolbar should not engage when is in the balloon yet invisible', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
const fakeWidgetToolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake' ).view;
setData( model, '[]' );
expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView );
const lastView = new View();
lastView.element = document.createElement( 'div' );
balloon.add( {
view: lastView,
position: {
target: document.body
}
} );
expect( balloon.visibleView ).to.equal( lastView );
editor.ui.fire( 'update' );
expect( balloon.visibleView ).to.equal( lastView );
} );
// #60
it( 'should show up only for the related element which is deepest in the view document', () => {
// The point of this widget is to provide a getRelatedElement function that
// returns a super–shallow related element which is ignored but satisfies code coverage.
widgetToolbarRepository.register( 'dummy', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: () => editor.editing.view.document.getRoot()
} );
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
widgetToolbarRepository.register( 'fake-child', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeChildWidget
} );
setData( model,
'foo' +
'' +
'foo' +
'[]' +
'' );
const fakeChildWidgetToolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake-child' ).view;
expect( balloon.visibleView ).to.equal( fakeChildWidgetToolbarView );
} );
// #60
it( 'should attach to the new related view element upon selecting another widget', () => {
const view = editor.editing.view;
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
widgetToolbarRepository.register( 'fake-child', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeChildWidget
} );
setData( model,
'foo' +
'[' +
'foo' +
'' +
']' );
const fakeWidgetToolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake' ).view;
const fakeChildWidgetToolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake-child' ).view;
expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView );
const fakeChildViewElement = view.document.getRoot().getChild( 1 ).getChild( 1 );
const updatePositionSpy = sinon.spy( balloon, 'add' );
view.change( writer => {
// []
writer.setSelection( fakeChildViewElement, 'on' );
} );
expect( balloon.visibleView ).to.equal( fakeChildWidgetToolbarView );
expect( updatePositionSpy.firstCall.args[ 0 ].position.target ).to.equal(
view.domConverter.viewToDom( fakeChildViewElement ) );
} );
it( 'should not update balloon position when toolbar is in not visible stack', () => {
const customView = new View();
sinon.spy( balloon.view, 'pin' );
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
setData( model,
'foo' +
'[]'
);
balloon.add( {
stackId: 'custom',
view: customView,
position: { target: {} }
} );
balloon.showStack( 'custom' );
const fakeWidgetToolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake' ).view;
expect( balloon.visibleView ).to.equal( customView );
expect( balloon.hasView( fakeWidgetToolbarView ) ).to.equal( true );
const spy = testUtils.sinon.spy( balloon, 'updatePosition' );
editor.ui.fire( 'update' );
sinon.assert.notCalled( spy );
} );
it( 'should update balloon position when stack with toolbar is switched in rotator to visible', () => {
const view = editor.editing.view;
const customView = new View();
sinon.spy( balloon.view, 'pin' );
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
setData( model,
'foo' +
'[]'
);
const fakeViewElement = view.document.getRoot().getChild( 1 );
const fakeDomElement = editor.editing.view.domConverter.mapViewToDom( fakeViewElement );
const fakeWidgetToolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake' ).view;
expect( balloon.view.pin.lastCall.args[ 0 ].target ).to.equal( fakeDomElement );
balloon.add( {
stackId: 'custom',
view: customView,
position: { target: {} }
} );
balloon.showStack( 'custom' );
expect( balloon.visibleView ).to.equal( customView );
expect( balloon.hasView( fakeWidgetToolbarView ) ).to.equal( true );
editor.execute( 'blockQuote' );
balloon.showStack( 'main' );
expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView );
expect( balloon.hasView( customView ) ).to.equal( true );
expect( balloon.view.pin.lastCall.args[ 0 ].target ).to.not.equal( fakeDomElement );
const newFakeViewElement = view.document.getRoot().getChild( 1 ).getChild( 0 );
const newFakeDomElement = editor.editing.view.domConverter.mapViewToDom( newFakeViewElement );
expect( balloon.view.pin.lastCall.args[ 0 ].target ).to.equal( newFakeDomElement );
} );
it( 'toolbar should use one of pre-defined positions when attaching to a widget', () => {
const editingView = editor.editing.view;
const balloonAddSpy = sinon.spy( balloon, 'add' );
const defaultPositions = BalloonPanelView.defaultPositions;
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
setData( model, 'foo[]' );
const fakeWidgetToolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake' ).view;
const widgetViewElement = editingView.document.getRoot().getChild( 1 );
sinon.assert.calledOnce( balloonAddSpy );
sinon.assert.calledWithExactly( balloonAddSpy, {
view: fakeWidgetToolbarView,
position: {
target: editingView.domConverter.mapViewToDom( widgetViewElement ),
positions: [
defaultPositions.northArrowSouth,
defaultPositions.northArrowSouthWest,
defaultPositions.northArrowSouthEast,
defaultPositions.southArrowNorth,
defaultPositions.southArrowNorthWest,
defaultPositions.southArrowNorthEast,
centeredBalloonPositionForLongWidgets
]
},
balloonClassName: 'ck-toolbar-container'
} );
} );
} );
} );
describe( 'WidgetToolbarRepository - integration with the BalloonToolbar', () => {
let clock, editor, model, balloon, balloonToolbar, widgetToolbarRepository, editorElement;
testUtils.createSinonSandbox();
beforeEach( () => {
editorElement = document.createElement( 'div' );
document.body.appendChild( editorElement );
clock = testUtils.sinon.useFakeTimers();
return BalloonEditor
.create( editorElement, {
plugins: [ Paragraph, FakeButton, WidgetToolbarRepository, FakeWidget, Bold ],
balloonToolbar: [ 'bold' ],
fake: {
toolbar: [ 'fake_button' ]
}
} )
.then( newEditor => {
editor = newEditor;
model = newEditor.model;
widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );
balloon = editor.plugins.get( 'ContextualBalloon' );
balloonToolbar = editor.plugins.get( 'BalloonToolbar' );
editor.ui.focusTracker.isFocused = true;
} );
} );
afterEach( () => {
editorElement.remove();
return editor.destroy();
} );
it( 'balloon toolbar should be hidden when the widget is selected', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
const fakeWidgetToolbarView = widgetToolbarRepository._toolbarDefinitions.get( 'fake' ).view;
editor.editing.view.document.isFocused = true;
setData( model, '[]foo' );
clock.tick( 200 );
expect( balloon.visibleView ).to.equal( fakeWidgetToolbarView );
} );
it( 'balloon toolbar should be visible when the widget is not selected', () => {
widgetToolbarRepository.register( 'fake', {
items: editor.config.get( 'fake.toolbar' ),
getRelatedElement: getSelectedFakeWidget
} );
editor.editing.view.document.isFocused = true;
setData( model, '[foo]' );
clock.tick( 200 );
expect( balloon.visibleView ).to.equal( balloonToolbar.toolbarView );
} );
describe( 'disableable', () => {
describe( 'isEnabled', () => {
it( 'is enabled by default', () => {
expect( widgetToolbarRepository.isEnabled ).to.be.true;
} );
it( 'fires change event', () => {
const spy = sinon.spy();
widgetToolbarRepository.on( 'change:isEnabled', spy );
widgetToolbarRepository.isEnabled = false;
expect( spy.calledOnce ).to.be.true;
} );
} );
describe( 'forceDisabled() / clearForceDisabled()', () => {
it( 'forceDisabled() should disable the plugin', () => {
widgetToolbarRepository.forceDisabled( 'foo' );
widgetToolbarRepository.isEnabled = true;
expect( widgetToolbarRepository.isEnabled ).to.be.false;
} );
it( 'clearForceDisabled() should enable the plugin', () => {
widgetToolbarRepository.forceDisabled( 'foo' );
widgetToolbarRepository.clearForceDisabled( 'foo' );
expect( widgetToolbarRepository.isEnabled ).to.be.true;
} );
it( 'clearForceDisabled() used with wrong identifier should not enable the plugin', () => {
widgetToolbarRepository.forceDisabled( 'foo' );
widgetToolbarRepository.clearForceDisabled( 'bar' );
widgetToolbarRepository.isEnabled = true;
expect( widgetToolbarRepository.isEnabled ).to.be.false;
} );
it( 'using forceDisabled() twice with the same identifier should not have any effect', () => {
widgetToolbarRepository.forceDisabled( 'foo' );
widgetToolbarRepository.forceDisabled( 'foo' );
widgetToolbarRepository.clearForceDisabled( 'foo' );
expect( widgetToolbarRepository.isEnabled ).to.be.true;
} );
it( 'plugin is enabled only after all disables were cleared', () => {
widgetToolbarRepository.forceDisabled( 'foo' );
widgetToolbarRepository.forceDisabled( 'bar' );
widgetToolbarRepository.clearForceDisabled( 'foo' );
widgetToolbarRepository.isEnabled = true;
expect( widgetToolbarRepository.isEnabled ).to.be.false;
widgetToolbarRepository.clearForceDisabled( 'bar' );
expect( widgetToolbarRepository.isEnabled ).to.be.true;
} );
it( 'plugin should remain disabled if isEnabled has a callback disabling it', () => {
widgetToolbarRepository.on( 'set:isEnabled', evt => {
evt.return = false;
evt.stop();
} );
widgetToolbarRepository.forceDisabled( 'foo' );
widgetToolbarRepository.clearForceDisabled( 'foo' );
widgetToolbarRepository.isEnabled = true;
expect( widgetToolbarRepository.isEnabled ).to.be.false;
} );
} );
} );
} );
function getSelectedFakeWidget( selection ) {
const viewElement = selection.getSelectedElement();
if ( viewElement && isWidget( viewElement ) && !!viewElement.getCustomProperty( 'fakeWidget' ) ) {
return viewElement;
}
return null;
}
function getSelectedFakeChildWidget( selection ) {
const viewElement = selection.getSelectedElement();
if ( viewElement && isWidget( viewElement ) && !!viewElement.getCustomProperty( 'fakeChildWidget' ) ) {
return viewElement;
}
return null;
}
function getSelectedFakeWidgetContent( selection ) {
const pos = selection.getFirstPosition();
let node = pos.parent;
while ( node ) {
if ( node.is( 'element' ) && isWidget( node ) && node.getCustomProperty( 'fakeWidget' ) ) {
return node;
}
node = node.parent;
}
return null;
}
// Plugin that adds fake_button to editor's component factory.
class FakeButton extends Plugin {
init() {
this.editor.ui.componentFactory.add( 'fake_button', locale => {
const view = new ButtonView( locale );
view.set( {
label: 'fake button'
} );
return view;
} );
}
}
// Simple widget plugin
// It registers `` block in model and represents `div` in the view.
// It allows having text inside self.
class FakeWidget extends Plugin {
static get requires() {
return [ Widget ];
}
init() {
const editor = this.editor;
const schema = editor.model.schema;
schema.register( 'fake-widget', {
isObject: true,
isBlock: true,
allowWhere: '$block'
} );
schema.extend( '$text', { allowIn: 'fake-widget' } );
schema.extend( 'paragraph', { allowIn: 'fake-widget' } );
const conversion = editor.conversion;
conversion.for( 'dataDowncast' ).elementToElement( {
model: 'fake-widget',
view: ( modelElement, viewWriter ) => {
return viewWriter.createContainerElement( 'div' );
}
} );
conversion.for( 'editingDowncast' ).elementToElement( {
model: 'fake-widget',
view: ( modelElement, viewWriter ) => {
const fakeWidget = viewWriter.createContainerElement( 'div' );
viewWriter.setCustomProperty( 'fakeWidget', true, fakeWidget );
return toWidget( fakeWidget, viewWriter, { label: 'fake-widget' } );
}
} );
conversion.for( 'upcast' ).elementToElement( {
view: {
name: 'div'
},
model: ( view, modelWriter ) => {
return modelWriter.createElement( 'fake-widget' );
}
} );
}
}
// A simple child widget plugin
// It registers `` block in model and represents `div` in the view.
// It allows having text inside self.
class FakeChildWidget extends Plugin {
static get requires() {
return [ Widget ];
}
init() {
const editor = this.editor;
const schema = editor.model.schema;
schema.register( 'fake-child-widget', {
isObject: true,
isBlock: true,
allowWhere: '$block',
allowIn: 'fake-widget'
} );
schema.extend( '$text', { allowIn: 'fake-child-widget' } );
schema.extend( 'paragraph', { allowIn: 'fake-child-widget' } );
const conversion = editor.conversion;
conversion.for( 'dataDowncast' ).elementToElement( {
model: 'fake-child-widget',
view: ( modelElement, viewWriter ) => {
return viewWriter.createContainerElement( 'div' );
}
} );
conversion.for( 'editingDowncast' ).elementToElement( {
model: 'fake-child-widget',
view: ( modelElement, viewWriter ) => {
const fakeWidget = viewWriter.createContainerElement( 'div' );
viewWriter.setCustomProperty( 'fakeChildWidget', true, fakeWidget );
return toWidget( fakeWidget, viewWriter, { label: 'fake-child-widget' } );
}
} );
conversion.for( 'upcast' ).elementToElement( {
view: {
name: 'div'
},
model: ( view, modelWriter ) => {
return modelWriter.createElement( 'fake-child-widget' );
}
} );
}
}