/** * @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 */ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor'; import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'; import Typing from '@ckeditor/ckeditor5-typing/src/typing'; import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting'; import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor'; import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'; 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 ListStyleEditing from '../src/liststyleediting'; import TodoListEditing from '../src/todolistediting'; import ListStyleCommand from '../src/liststylecommand'; describe( 'ListStyleEditing', () => { let editor, model, view; beforeEach( () => { return VirtualTestEditor .create( { plugins: [ Paragraph, ListStyleEditing, UndoEditing ] } ) .then( newEditor => { editor = newEditor; model = editor.model; view = editor.editing.view; } ); } ); afterEach( () => { return editor.destroy(); } ); it( 'should have pluginName', () => { expect( ListStyleEditing.pluginName ).to.equal( 'ListStyleEditing' ); } ); it( 'should be loaded', () => { expect( editor.plugins.get( ListStyleEditing ) ).to.be.instanceOf( ListStyleEditing ); } ); describe( 'schema rules', () => { it( 'should allow set `listStyle` on the `listItem`', () => { expect( model.schema.checkAttribute( [ '$root', 'listItem' ], 'listStyle' ) ).to.be.true; } ); } ); describe( 'command', () => { it( 'should register listStyle command', () => { const command = editor.commands.get( 'listStyle' ); expect( command ).to.be.instanceOf( ListStyleCommand ); } ); } ); describe( 'conversion in data pipeline', () => { describe( 'model to data', () => { it( 'should convert single list (type: bulleted)', () => { setModelData( model, 'Foo' + 'Bar' ); expect( editor.getData() ).to.equal( '' ); } ); it( 'should convert single list (type: numbered)', () => { setModelData( model, 'Foo' + 'Bar' ); expect( editor.getData() ).to.equal( '
  1. Foo
  2. Bar
' ); } ); it( 'should convert single list (type: bulleted, style: default)', () => { setModelData( model, 'Foo' + 'Bar' ); expect( editor.getData() ).to.equal( '' ); } ); it( 'should convert single list (type: numbered, style: default)', () => { setModelData( model, 'Foo' + 'Bar' ); expect( editor.getData() ).to.equal( '
  1. Foo
  2. Bar
' ); } ); it( 'should convert single list (type: bulleted, style: circle)', () => { setModelData( model, 'Foo' + 'Bar' ); expect( editor.getData() ).to.equal( '' ); } ); it( 'should convert single list (type: numbered, style: upper-alpha)', () => { setModelData( model, 'Foo' + 'Bar' ); expect( editor.getData() ).to.equal( '
  1. Foo
  2. Bar
' ); } ); it( 'should convert nested bulleted lists (main: circle, nested: disc)', () => { setModelData( model, 'Foo 1' + 'Bar 1' + 'Bar 2' + 'Foo 2' + 'Foo 3' ); expect( editor.getData() ).to.equal( '' ); } ); it( 'should convert nested numbered lists (main: decimal-leading-zero, nested: lower-latin)', () => { setModelData( model, 'Foo 1' + 'Bar 1' + 'Bar 2' + 'Foo 2' + 'Foo 3' ); expect( editor.getData() ).to.equal( '
    ' + '
  1. Foo 1' + '
      ' + '
    1. Bar 1
    2. ' + '
    3. Bar 2
    4. ' + '
    ' + '
  2. ' + '
  3. Foo 2
  4. ' + '
  5. Foo 3
  6. ' + '
' ); } ); it( 'should convert nested mixed lists (ul>ol, main: square, nested: lower-roman)', () => { setModelData( model, 'Foo 1' + 'Bar 1' + 'Bar 2' + 'Foo 2' + 'Foo 3' ); expect( editor.getData() ).to.equal( '' ); } ); it( 'should produce nested lists (different `listIndent` attribute)', () => { setModelData( model, 'Foo 1' + 'Foo 2' + 'Bar 1' + 'Bar 2' ); expect( editor.getData() ).to.equal( '
    ' + '
  1. Foo 1
  2. ' + '
  3. Foo 2' + '
      ' + '
    1. Bar 1
    2. ' + '
    3. Bar 2
    4. ' + '
    ' + '
  4. ' + '
' ); } ); it( 'should produce two different lists (different `listType` attribute)', () => { setModelData( model, 'Foo 1' + 'Foo 2' + 'Bar 1' + 'Bar 2' ); expect( editor.getData() ).to.equal( '
    ' + '
  1. Foo 1
  2. ' + '
  3. Foo 2
  4. ' + '
' + '' ); } ); it( 'should produce two different lists (different `listStyle` attribute)', () => { setModelData( model, 'Foo 1' + 'Foo 2' + 'Bar 1' + 'Bar 2' ); expect( editor.getData() ).to.equal( '' + '' ); } ); it( 'should not allow to set the `listStyle` attribute in to-do list item', () => { setModelData( model, 'Foo' ); const listItem = model.document.getRoot().getChild( 0 ); expect( listItem.hasAttribute( 'listItem' ) ).to.be.false; model.change( writer => { writer.setAttribute( 'listType', 'foo', listItem ); } ); expect( listItem.hasAttribute( 'listItem' ) ).to.be.false; } ); } ); describe( 'view to model', () => { it( 'should convert single list (type: bulleted)', () => { editor.setData( '' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( 'Foo' + 'Bar' ); } ); it( 'should convert single list (type: numbered)', () => { editor.setData( '
  1. Foo
  2. Bar
' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( 'Foo' + 'Bar' ); } ); it( 'should convert single list (type: bulleted, style: circle)', () => { editor.setData( '' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( 'Foo' + 'Bar' ); } ); it( 'should convert single list (type: numbered, style: upper-alpha)', () => { editor.setData( '
  1. Foo
  2. Bar
' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( 'Foo' + 'Bar' ); } ); it( 'should convert nested and mixed lists', () => { editor.setData( '
    ' + '
  1. OL 1
  2. ' + '
  3. OL 2' + '' + '
  4. ' + '
  5. OL 3
  6. ' + '
' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( 'OL 1' + 'OL 2' + 'UL 1' + 'UL 2' + 'OL 3' ); } ); it( 'should convert when the list is in the middle of the content', () => { editor.setData( '

Paragraph.

' + '
    ' + '
  1. Foo
  2. ' + '
  3. Bar
  4. ' + '
' + '

Paragraph.

' ); expect( getModelData( model, { withoutSelection: true } ) ).to.equal( 'Paragraph.' + 'Foo' + 'Bar' + 'Paragraph.' ); } ); // See: #8262. describe( 'list conversion with surrounding text nodes', () => { let editor; beforeEach( () => { return VirtualTestEditor .create( { plugins: [ ListStyleEditing ] } ) .then( newEditor => { editor = newEditor; } ); } ); afterEach( () => { return editor.destroy(); } ); it( 'should convert a list if raw text is before the list', () => { editor.setData( 'Foo' ); expect( editor.getData() ).to.equal( '

Foo

' ); } ); it( 'should convert a list if raw text is after the list', () => { editor.setData( 'Foo' ); expect( editor.getData() ).to.equal( '

Foo

' ); } ); it( 'should convert a list if it is surrender by two text nodes', () => { editor.setData( 'FooFoo' ); expect( editor.getData() ).to.equal( '

Foo

Foo

' ); } ); } ); } ); } ); // At this moment editing and data pipelines produce exactly the same content. // Just a few tests will be enough here. `model to data` block contains all cases checked. describe( 'conversion in editing pipeline', () => { describe( 'model to view', () => { it( 'should convert single list (type: bulleted, style: default)', () => { setModelData( model, 'Foo' + 'Bar' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '' ); } ); it( 'should convert single list (type: bulleted, style: circle)', () => { setModelData( model, 'Foo' + 'Bar' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '' ); } ); it( 'should convert nested bulleted lists (main: circle, nested: disc)', () => { setModelData( model, 'Foo 1' + 'Bar 1' + 'Bar 2' + 'Foo 2' + 'Foo 3' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '' ); } ); // See: #8081. it( 'should convert properly nested list styles', () => { // ■ Level 0 // ▶ Level 0.1 // ○ Level 0.1.1 // ▶ Level 0.2 // ○ Level 0.2.1 setModelData( model, 'Level 0' + 'Level 0.1' + 'Level 0.1.1' + 'Level 0.2' + 'Level 0.2.1' ); expect( getViewData( view, { withoutSelection: true } ) ).to.equal( '' ); } ); } ); } ); describe( 'integrations', () => { describe( 'merging a list into a styled list', () => { it( 'should inherit the list style attribute when merging the same kind of lists (from top, merge a single item)', () => { setModelData( model, 'Foo Bar.[]' + 'Foo' + 'Bar' ); editor.execute( 'bulletedList' ); expect( getModelData( model ) ).to.equal( 'Foo Bar.[]' + 'Foo' + 'Bar' ); } ); it( 'should inherit the list style attribute when merging the same kind of lists (from top, merge a few items)', () => { setModelData( model, '[Foo Bar 1.' + 'Foo Bar 2.]' + 'Foo' + 'Bar' ); editor.execute( 'bulletedList' ); expect( getModelData( model ) ).to.equal( '[Foo Bar 1.' + 'Foo Bar 2.]' + 'Foo' + 'Bar' ); } ); it( 'should not inherit anything if there is no list below the inserted list', () => { setModelData( model, 'Foo Bar 1.[]' + 'Foo Bar 2.' ); editor.execute( 'bulletedList' ); expect( getModelData( model ) ).to.equal( 'Foo Bar 1.[]' + 'Foo Bar 2.' ); } ); it( 'should not inherit anything if replacing the entire content with a list', () => { setModelData( model, 'Foo Bar 1.[]' ); editor.execute( 'bulletedList' ); expect( getModelData( model ) ).to.equal( 'Foo Bar 1.[]' ); } ); it( 'should not inherit the list style attribute when merging different kind of lists (from top, merge a single item)', () => { setModelData( model, 'Foo Bar.[]' + 'Foo' + 'Bar' ); editor.execute( 'bulletedList' ); expect( getModelData( model ) ).to.equal( 'Foo Bar.[]' + 'Foo' + 'Bar' ); } ); it( 'should not inherit the list style attribute when merging different kind of lists (from bottom, merge a single item)', () => { setModelData( model, 'Foo' + 'Bar' + 'Foo Bar.[]' ); editor.execute( 'bulletedList' ); expect( getModelData( model ) ).to.equal( 'Foo' + 'Bar' + 'Foo Bar.[]' ); } ); it( 'should inherit the list style attribute when merging the same kind of lists (from bottom, merge a single item)', () => { setModelData( model, 'Foo' + 'Bar' + 'Foo Bar.[]' ); editor.execute( 'bulletedList' ); expect( getModelData( model ) ).to.equal( 'Foo' + 'Bar' + 'Foo Bar.[]' ); } ); it( 'should inherit the list style attribute from listIndent=0 element when merging the same kind of lists (from bottom)', () => { setModelData( model, 'Foo' + 'Bar' + 'Foo Bar' + 'Foo Bar.[]' ); editor.execute( 'bulletedList' ); expect( getModelData( model ) ).to.equal( 'Foo' + 'Bar' + 'Foo Bar' + 'Foo Bar.[]' ); } ); } ); describe( 'modifying "listType" attribute', () => { it( 'should inherit the list style attribute when the modified list is the same kind of the list as next sibling', () => { setModelData( model, 'Foo Bar.[]' + 'Foo' + 'Bar' ); editor.execute( 'bulletedList' ); expect( getModelData( model ) ).to.equal( 'Foo Bar.[]' + 'Foo' + 'Bar' ); } ); it( 'should inherit the list style attribute when the modified list is the same kind of the list as previous sibling', () => { setModelData( model, 'Foo' + 'Bar' + 'Foo Bar.[]' ); editor.execute( 'bulletedList' ); expect( getModelData( model ) ).to.equal( 'Foo' + 'Bar' + 'Foo Bar.[]' ); } ); it( 'should not inherit the list style attribute when the modified list already has defined it (next sibling check)', () => { setModelData( model, 'Foo Bar.[]' + 'Foo' + 'Bar' ); editor.execute( 'listStyle', { type: 'disc' } ); expect( getModelData( model ) ).to.equal( 'Foo Bar.[]' + 'Foo' + 'Bar' ); } ); it( 'should not inherit the list style attribute when the modified list already has defined it (previous sibling check)', () => { setModelData( model, 'Foo' + 'Bar' + 'Foo Bar.[]' ); editor.execute( 'listStyle', { type: 'disc' } ); expect( getModelData( model ) ).to.equal( 'Foo' + 'Bar' + 'Foo Bar.[]' ); } ); } ); describe( 'indenting lists', () => { it( 'should restore the default value for the list style attribute when indenting a single item', () => { setModelData( model, '1.' + '1A.' + '2B.' + '2.[]' + '3.' ); editor.execute( 'indentList' ); expect( getModelData( model ) ).to.equal( '1.' + '1A.' + '2B.' + '2.[]' + '3.' ); } ); it( 'should restore the default value for the list style attribute when indenting a few items', () => { setModelData( model, '1.' + '[2.' + '3.]' ); editor.execute( 'indentList' ); expect( getModelData( model ) ).to.equal( '1.' + '[2.' + '3.]' ); } ); it( 'should copy the value for the list style attribute when indenting a single item into a nested list (default value)', () => { setModelData( model, '1.' + '2.' + '3.[]' + '4.' ); editor.execute( 'indentList' ); expect( getModelData( model ) ).to.equal( '1.' + '2.' + '3.[]' + '4.' ); } ); it( 'should copy the value for the list style attribute when indenting a single item into a nested list (changed value)', () => { setModelData( model, '1.' + '2.' + '3.[]' + '4.' ); editor.execute( 'indentList' ); expect( getModelData( model ) ).to.equal( '1.' + '2.' + '3.[]' + '4.' ); } ); it( 'should copy the value for the list style attribute when indenting a single item into a nested list', () => { setModelData( model, '1.' + '2.[]' + '3.' + '4.' ); editor.execute( 'indentList' ); expect( getModelData( model ) ).to.equal( '1.' + '2.[]' + '3.' + '4.' ); } ); it( 'should copy the value for the list style attribute when indenting a single item into a nested list ' + '(many nested lists check)', () => { setModelData( model, '1.' + '2.' + '3.' + '4.[]' ); editor.execute( 'indentList' ); expect( getModelData( model ) ).to.equal( '1.' + '2.' + '3.' + '4.[]' ); } ); it( 'should inherit the list style attribute from nested list if the `listType` is other than indenting element', () => { setModelData( model, '1.' + '2.' + '3.[]' ); editor.execute( 'indentList' ); expect( getModelData( model ) ).to.equal( '1.' + '2.' + '3.[]' ); } ); // See: #8072. it( 'should not throw when indenting a list without any other content in the editor', () => { setModelData( model, 'Foo' + '[]' ); editor.execute( 'indentList' ); expect( getModelData( model ) ).to.equal( 'Foo' + '[]' ); } ); } ); describe( 'outdenting lists', () => { it( 'should inherit the list style attribute from parent list (change the first nested item)', () => { setModelData( model, '1.' + '2.[]' + '3.' ); editor.execute( 'outdentList' ); expect( getModelData( model ) ).to.equal( '1.' + '2.[]' + '3.' ); } ); it( 'should inherit the list style attribute from parent list (change the second nested item)', () => { setModelData( model, '1.' + '2.' + '3.[]' ); editor.execute( 'outdentList' ); expect( getModelData( model ) ).to.equal( '1.' + '2.' + '3.[]' ); } ); it( 'should inherit the list style attribute from parent list (modifying nested lists)', () => { setModelData( model, '1.' + '[2.' + '3.]' ); editor.execute( 'outdentList' ); expect( getModelData( model ) ).to.equal( '1.' + '[2.' + '3.]' ); } ); it( 'should inherit the list style attribute from parent list (outdenting many items, including the first one in the list)', () => { setModelData( model, '[1.' + '2.' + '3.]' + '4.' ); editor.execute( 'outdentList' ); expect( getModelData( model ) ).to.equal( '[1.' + '2.' + '3.]' + '4.' ); } ); it( 'should inherit the list style attribute from parent list (outdenting the first item that is a parent for next list)', () => { setModelData( model, '1.[]' + '2.' + '3.' + '4.' + '5.' ); editor.execute( 'outdentList' ); expect( getModelData( model ) ).to.equal( '1.[]' + '2.' + '3.' + '4.' + '5.' ); } ); it( 'should not inherit the list style if outdented the only one item in the list', () => { setModelData( model, '1.[]' + '2.' + '3.' ); editor.execute( 'outdentList' ); expect( getModelData( model ) ).to.equal( '1.[]' + '2.' + '3.' ); } ); it( 'should not inherit the list style if outdented the only one item in the list (a paragraph below the list)', () => { setModelData( model, '1.[]' + '2.' + '3.' + 'Foo' ); editor.execute( 'outdentList' ); expect( getModelData( model ) ).to.equal( '1.[]' + '2.' + '3.' + 'Foo' ); } ); it( 'should not inherit the list style attribute from parent list if the `listType` is other than outdenting element', () => { setModelData( model, '1.' + '2.[]' + '3.' ); editor.execute( 'outdentList' ); expect( getModelData( model ) ).to.equal( '1.' + '2.[]' + '3.' ); } ); it( 'should not do anything if there is no list after outdenting', () => { setModelData( model, '1.[]' ); editor.execute( 'outdentList' ); expect( getModelData( model ) ).to.equal( '1.[]' ); } ); } ); describe( 'indent/outdent + undo', () => { it( 'should use the same batch for indenting a list and updating `listType` attribute', () => { setModelData( model, '1.' + '1A.' + '2B.' + '2.[]' + '3.' ); editor.execute( 'indentList' ); editor.execute( 'undo' ); expect( getModelData( model ) ).to.equal( '1.' + '1A.' + '2B.' + '2.[]' + '3.' ); } ); it( 'should use the same batch for outdenting a list and updating `listType` attribute', () => { setModelData( model, '1.' + '2.[]' + '3.' ); editor.execute( 'outdentList' ); editor.execute( 'undo' ); expect( getModelData( model ) ).to.equal( '1.' + '2.[]' + '3.' ); } ); } ); describe( 'delete + undo', () => { let editor, model, view; beforeEach( () => { return VirtualTestEditor .create( { plugins: [ Paragraph, ListStyleEditing, Typing, UndoEditing ] } ) .then( newEditor => { editor = newEditor; model = editor.model; view = editor.editing.view; } ); } ); afterEach( () => { return editor.destroy(); } ); // See: #7930. it( 'should restore proper list style attribute after undo merging lists', () => { // ○ 1. // ○ 2. // ○ 3. // // ■ 1. // ■ 2. setModelData( model, '1.' + '2.' + '3.' + '[]' + '1.' + '2.' ); expect( getViewData( view, { withoutSelection: true } ), 'initial data' ).to.equal( '' + '

' + '' ); // After removing the paragraph. // ○ 1. // ○ 2. // ○ 3. // ○ 1. // ○ 2. editor.execute( 'delete' ); expect( getViewData( view, { withoutSelection: true } ), 'executing delete' ).to.equal( '' ); // After undo. // ○ 1. // ○ 2. // ○ 3. // // ■ 1. // ■ 2. editor.execute( 'undo' ); expect( getViewData( view, { withoutSelection: true } ), 'initial data' ).to.equal( '' + '

' + '' ); } ); } ); describe( 'todo list', () => { let editor, model; beforeEach( () => { return VirtualTestEditor .create( { // TodoListEditing is at the end by design. Check `ListStyleEditing.afterInit()` call. plugins: [ Paragraph, ListStyleEditing, TodoListEditing ] } ) .then( newEditor => { editor = newEditor; model = editor.model; } ); } ); afterEach( () => { return editor.destroy(); } ); it( 'should not add the `listStyle` attribute while creating a todo list', () => { setModelData( model, 'Foo[]' ); editor.execute( 'todoList' ); expect( getModelData( model ), 'Foo[]' ); } ); it( 'should not add the `listStyle` attribute while switching the list type', () => { setModelData( model, 'Foo[]' ); editor.execute( 'todoList' ); expect( getModelData( model ), 'Foo[]' ); } ); it( 'should remove the `listStyle` attribute while switching the list type that uses the list style feature', () => { setModelData( model, 'Foo[]' ); editor.execute( 'todoList' ); expect( getModelData( model ), 'Foo[]' ); } ); it( 'should not inherit the list style attribute when inserting a todo list item', () => { setModelData( model, 'Foo Bar.[]' + 'Foo' + 'Bar' ); editor.execute( 'todoList' ); expect( getModelData( model ) ).to.equal( 'Foo Bar.[]' + 'Foo' + 'Bar' ); } ); } ); describe( 'removing content between two lists', () => { let editor, model; beforeEach( () => { return VirtualTestEditor .create( { plugins: [ Paragraph, ListStyleEditing, Typing ] } ) .then( newEditor => { editor = newEditor; model = editor.model; } ); } ); afterEach( () => { return editor.destroy(); } ); it( 'should not do anything while removing a letter inside a listItem', () => { setModelData( model, '1.' + '2.[]' + '' + '1.' + '2.' ); editor.execute( 'delete' ); expect( getModelData( model ) ).to.equal( '1.' + '2[]' + '' + '1.' + '2.' ); } ); it( 'should not do anything if there is a non-listItem before the removed content', () => { setModelData( model, 'Foo' + '[]' + '1.' + '2.' ); editor.execute( 'delete' ); expect( getModelData( model ) ).to.equal( 'Foo[]' + '1.' + '2.' ); } ); it( 'should not do anything if there is a non-listItem after the removed content', () => { setModelData( model, '1.' + '2.' + '[]' + 'Foo' ); editor.execute( 'delete' ); expect( getModelData( model ) ).to.equal( '1.' + '2.[]' + 'Foo' ); } ); it( 'should not do anything if there is no element after the removed content', () => { setModelData( model, '1.' + '2.' + '[]' ); editor.execute( 'delete' ); expect( getModelData( model ) ).to.equal( '1.' + '2.[]' ); } ); it( 'should modify the the `listStyle` attribute for the merged (second) list when removing content between those lists', () => { setModelData( model, '1.' + '2.' + '[]' + '1.' + '2.' ); editor.execute( 'delete' ); expect( getModelData( model ) ).to.equal( '1.' + '2.[]' + '1.' + '2.' ); } ); it( 'should read the `listStyle` attribute from the most outer list', () => { setModelData( model, '1.' + '2.' + '2.1.' + '2.1.1' + '[]' + '1.' + '2.' ); editor.execute( 'delete' ); expect( getModelData( model ) ).to.equal( '1.' + '2.' + '2.1.' + '2.1.1[]' + '1.' + '2.' ); } ); it( 'should not modify the the `listStyle` attribute for the merged (second) list if merging different `listType` attribute', () => { setModelData( model, '1.' + '2.' + '[]' + '1.' + '2.' ); editor.execute( 'delete' ); expect( getModelData( model ) ).to.equal( '1.' + '2.[]' + '1.' + '2.' ); } ); it( 'should modify the the `listStyle` attribute for the merged (second) list when removing content from both lists', () => { setModelData( model, '1.' + '2.' + '[3.' + 'Foo' + '1.]' + '2.' ); editor.execute( 'delete' ); expect( getModelData( model ) ).to.equal( '1.' + '2.' + '[]' + '2.' ); } ); it( 'should modify the the `listStyle` attribute for the merged (second) list when typing over content from both lists', () => { setModelData( model, '1.' + '2.' + '[3.' + 'Foo' + '1.]' + '2.' ); editor.execute( 'input', { text: 'Foo' } ); expect( getModelData( model ) ).to.equal( '1.' + '2.' + 'Foo[]' + '2.' ); } ); it( 'should not modify the the `listStyle` if lists were not merged but the content was partially removed', () => { setModelData( model, '111.' + '222.' + '[333.' + 'Foo' + '1]11.' + '2.' ); editor.execute( 'delete' ); expect( getModelData( model ) ).to.equal( '111.' + '222.' + '[]11.' + '2.' ); } ); it( 'should not do anything while typing in a list item', () => { setModelData( model, '1.' + '2.[]' + '3.' + '' + '1.' + '2.' ); const modelChangeStub = sinon.stub( model, 'change' ).callThrough(); simulateTyping( ' Foo' ); // Each character calls `editor.model.change()`. expect( modelChangeStub.callCount ).to.equal( 4 ); expect( getModelData( model ) ).to.equal( '1.' + '2. Foo[]' + '3.' + '' + '1.' + '2.' ); } ); // See: #8073. it( 'should not crash when removing a content between intended lists', () => { setModelData( model, 'aaaa' + 'bb[bb' + 'cc]cc' + 'dddd' ); editor.execute( 'delete' ); expect( getModelData( model ) ).to.equal( 'aaaa' + 'bb[]cc' + 'dddd' ); } ); it( 'should read the `listStyle` attribute from the most outer selected list while removing content between lists', () => { setModelData( model, '1.' + '2.' + '2.1.' + '2.1.1[foo' + 'Foo' + '1.' + 'bar]2.' ); editor.execute( 'delete' ); expect( getModelData( model ) ).to.equal( '1.' + '2.' + '2.1.' + '2.1.1[]2.' ); } ); function simulateTyping( text ) { // While typing, every character is an atomic change. text.split( '' ).forEach( character => { editor.execute( 'input', { text: character } ); } ); } } ); // #8160 describe( 'pasting a list into another list', () => { let element; beforeEach( () => { element = document.createElement( 'div' ); document.body.append( element ); return ClassicTestEditor .create( element, { plugins: [ Paragraph, Clipboard, ListStyleEditing, UndoEditing ] } ) .then( newEditor => { editor = newEditor; model = editor.model; } ); } ); afterEach( () => { return editor.destroy() .then( () => { element.remove(); } ); } ); it( 'should inherit attributes from the previous sibling element (collapsed selection)', () => { setModelData( model, 'Foo' + 'Foo Bar' + '[]' + 'Bar' ); pasteHtml( editor, '' ); expect( getModelData( model ) ).to.equal( 'Foo' + 'Foo Bar' + 'Foo 1' + 'Foo 2[]' + 'Bar' ); } ); it( 'should inherit attributes from the previous sibling element (non-collapsed selection)', () => { setModelData( model, 'Foo' + 'Foo Bar' + '[Foo]' + 'Bar' ); pasteHtml( editor, '' ); expect( getModelData( model ) ).to.equal( 'Foo' + 'Foo Bar' + 'Foo 1' + 'Foo 2[]' + 'Bar' ); } ); it( 'should inherit attributes from the previous sibling element (non-collapsed selection over a few elements)', () => { setModelData( model, 'Foo' + 'Foo Bar' + '[Foo 1.' + 'Foo 2.' + 'Foo 3.]' + 'Bar' ); pasteHtml( editor, '' ); expect( getModelData( model ) ).to.equal( 'Foo' + 'Foo Bar' + 'Foo 1' + 'Foo 2[]' + 'Bar' ); } ); it( 'should do nothing when pasting the similar list', () => { setModelData( model, 'Foo' + 'Foo Bar' + '[]' + 'Bar' ); pasteHtml( editor, '
    ' + '
  1. Foo
  2. ' + '
' ); expect( getModelData( model ) ).to.equal( 'Foo' + 'Foo Bar' + 'Foo[]' + 'Bar' ); } ); it( 'should replace the entire list if selected', () => { setModelData( model, 'Foo' + '[Foo Bar]' + 'Bar' ); pasteHtml( editor, '' ); expect( getModelData( model ) ).to.equal( 'Foo' + 'Foo[]' + 'Bar' ); } ); function pasteHtml( editor, html ) { editor.editing.view.document.fire( 'paste', { dataTransfer: createDataTransfer( { 'text/html': html } ), stopPropagation() {}, preventDefault() {} } ); } function createDataTransfer( data ) { return { getData( type ) { return data[ type ]; }, setData() {} }; } } ); } ); } );