/**
 * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
 */

import { BlockQuoteEditing } from '@ckeditor/ckeditor5-block-quote';
import { HeadingEditing } from '@ckeditor/ckeditor5-heading';
import { ModelElement, _getModelData, _setModelData, _getViewData } from '@ckeditor/ckeditor5-engine';
import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
import { TableEditing } from '@ckeditor/ckeditor5-table';
import { GeneralHtmlSupport } from '@ckeditor/ckeditor5-html-support';
import { AlignmentEditing } from '@ckeditor/ckeditor5-alignment';

import { VirtualTestEditor } from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor.js';
import { ClassicTestEditor } from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor.js';
import { getCode, env } from '@ckeditor/ckeditor5-utils';
import { testUtils } from '@ckeditor/ckeditor5-core/tests/_utils/utils.js';

import { TodoListEditing } from '../../src/todolist/todolistediting.js';
import { ListEditing } from '../../src/list/listediting.js';
import { ListCommand } from '../../src/list/listcommand.js';
import { CheckTodoListCommand } from '../../src/todolist/checktodolistcommand.js';
import { TodoCheckboxChangeObserver } from '../../src/todolist/todocheckboxchangeobserver.js';
import { ListPropertiesEditing } from '../../src/listproperties/listpropertiesediting.js';

import { stubUid } from '../list/_utils/uid.js';

describe( 'TodoListEditing', () => {
	let editor, model, view, editorElement, modelRoot;

	testUtils.createSinonSandbox();

	beforeEach( async () => {
		editorElement = document.createElement( 'div' );
		document.body.appendChild( editorElement );

		editor = await createEditor();

		model = editor.model;
		modelRoot = model.document.getRoot();
		view = editor.editing.view;

		stubUid();

		// Remove downcast strategy for listItemId to avoid having to take it into account in all tests.
		editor.plugins.get( 'ListEditing' )._downcastStrategies.splice( editor.plugins.get( 'ListEditing' )._downcastStrategies.findIndex(
			strategy => strategy.attributeName === 'listItemId' ), 1 );
	} );

	afterEach( async () => {
		editorElement.remove();

		await editor.destroy();
	} );

	it( 'should have pluginName', () => {
		expect( TodoListEditing.pluginName ).to.equal( 'TodoListEditing' );
	} );

	it( 'should have `isOfficialPlugin` static flag set to `true`', () => {
		expect( TodoListEditing.isOfficialPlugin ).to.be.true;
	} );

	it( 'should have `isPremiumPlugin` static flag set to `false`', () => {
		expect( TodoListEditing.isPremiumPlugin ).to.be.false;
	} );

	it( 'should load ListEditing', () => {
		expect( TodoListEditing.requires ).to.have.members( [ ListEditing ] );
	} );

	describe( 'commands', () => {
		it( 'should register todoList command', () => {
			const command = editor.commands.get( 'todoList' );

			expect( command ).to.be.instanceOf( ListCommand );
			expect( command ).to.have.property( 'type', 'todo' );
		} );

		it( 'should register checkTodoList command', () => {
			const command = editor.commands.get( 'checkTodoList' );

			expect( command ).to.be.instanceOf( CheckTodoListCommand );
		} );
	} );

	it( 'should register TodoCheckboxChangeObserver', () => {
		expect( view.getObserver( TodoCheckboxChangeObserver ) ).to.be.instanceOf( TodoCheckboxChangeObserver );
	} );

	it( 'should set proper schema rules', () => {
		const paragraph = new ModelElement( 'paragraph', { listItemId: 'foo', listType: 'todo' } );
		const heading = new ModelElement( 'heading1', { listItemId: 'foo', listType: 'todo' } );
		const blockQuote = new ModelElement( 'blockQuote', { listItemId: 'foo', listType: 'todo' } );
		const table = new ModelElement( 'table', { listItemId: 'foo', listType: 'todo' }, [ ] );

		expect( model.schema.checkAttribute( [ '$root', paragraph ], 'todoListChecked' ) ).to.be.true;
		expect( model.schema.checkAttribute( [ '$root', heading ], 'todoListChecked' ) ).to.be.true;
		expect( model.schema.checkAttribute( [ '$root', blockQuote ], 'todoListChecked' ) ).to.be.true;
		expect( model.schema.checkAttribute( [ '$root', table ], 'todoListChecked' ) ).to.be.true;
	} );

	describe( 'upcast', () => {
		it( 'should convert li with a checkbox before the first text node as a to-do list item', () => {
			testUpcast(
				'<ul><li><input type="checkbox">foo</li></ul>',
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>'
			);
		} );

		it( 'should convert the full markup generated by the editor', () => {
			testUpcast(
				'<ul><li><input type="checkbox">foo</li></ul>',
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>'
			);

			testUpcast(
				editor.getData(),
				'<paragraph listIndent="0" listItemId="a01" listType="todo">foo</paragraph>'
			);
		} );

		it( 'should convert li with checked checkbox as checked to-do list item', () => {
			testUpcast(
				'<ul>' +
					'<li><input type="checkbox" checked="checked">a</li>' +
					'<li><input type="checkbox" checked="anything">b</li>' +
					'<li><input type="checkbox" checked>c</li>' +
				'</ul>',
				'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">a</paragraph>' +
				'<paragraph listIndent="0" listItemId="a01" listType="todo" todoListChecked="true">b</paragraph>' +
				'<paragraph listIndent="0" listItemId="a02" listType="todo" todoListChecked="true">c</paragraph>'
			);
		} );

		it( 'should not convert li with checkbox in the middle of the text', () => {
			testUpcast(
				'<ul><li>Foo<input type="checkbox">Bar</li></ul>',
				'<paragraph listIndent="0" listItemId="a00" listType="bulleted">FooBar</paragraph>'
			);
		} );

		it( 'should split items with checkboxes - bulleted list', () => {
			testUpcast(
				'<ul>' +
					'<li>foo</li>' +
					'<li><input type="checkbox">bar</li>' +
					'<li>baz</li>' +
				'</ul>',
				'<paragraph listIndent="0" listItemId="a00" listType="bulleted">foo</paragraph>' +
				'<paragraph listIndent="0" listItemId="a01" listType="todo">bar</paragraph>' +
				'<paragraph listIndent="0" listItemId="a02" listType="bulleted">baz</paragraph>'
			);
		} );

		it( 'should split items with checkboxes - numbered list', () => {
			testUpcast(
				'<ol>' +
					'<li>foo</li>' +
					'<li><input type="checkbox">bar</li>' +
					'<li>baz</li>' +
				'</ol>',
				'<paragraph listIndent="0" listItemId="a00" listType="numbered">foo</paragraph>' +
				'<paragraph listIndent="0" listItemId="a01" listType="todo">bar</paragraph>' +
				'<paragraph listIndent="0" listItemId="a02" listType="numbered">baz</paragraph>'
			);
		} );

		it( 'should convert li with a checkbox in a nested list', () => {
			testUpcast(
				'<ul>' +
					'<li>' +
						'<input type="checkbox">' +
						'foo' +
						'<ul><li><input type="checkbox">foo</li></ul>' +
					'</li>' +
				'</ul>',
				'<paragraph listIndent="0" listItemId="a01" listType="todo">foo</paragraph>' +
				'<paragraph listIndent="1" listItemId="a00" listType="todo">foo</paragraph>'
			);
		} );

		it( 'should convert li with checkboxes in a nested lists (bulleted > todo > todo)', () => {
			testUpcast(
				'<ul>' +
					'<li>' +
						'<ul>' +
							'<li>' +
								'<input type="checkbox">foo' +
								'<ul><li><input type="checkbox">bar</li></ul>' +
							'</li>' +
						'</ul>' +
					'</li>' +
				'</ul>',
				'<paragraph listIndent="0" listItemId="a02" listType="bulleted"></paragraph>' +
				'<paragraph listIndent="1" listItemId="a01" listType="todo">foo</paragraph>' +
				'<paragraph listIndent="2" listItemId="a00" listType="todo">bar</paragraph>'
			);
		} );

		it( 'should convert li with a checkbox and a paragraph', () => {
			testUpcast(
				'<ul>' +
					'<li>' +
						'<input type="checkbox">' +
						'<p>foo</p>' +
					'</li>' +
				'</ul>',
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>'
			);
		} );

		it( 'should convert li with a checkbox and a paragraph ( when checked )', () => {
			testUpcast(
				'<ul>' +
					'<li>' +
						'<input type="checkbox" checked="checked">' +
						'<p>foo</p>' +
					'</li>' +
				'</ul>',
				'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">foo</paragraph>'
			);
		} );

		it( 'should convert nested li with a checkbox and a paragraph ( when checked )', () => {
			testUpcast(
				'<ul>' +
					'<li>' +
						'<input type="checkbox">' +
						'<ul>' +
							'<li>' +
								'<input type="checkbox" checked="checked">' +
								'<p>foo</p>' +
							'</li>' +
						'</ul>' +
					'</li>' +
				'</ul>',
				'<paragraph listIndent="0" listItemId="a01" listType="todo"></paragraph>' +
				'<paragraph listIndent="1" listItemId="a00" listType="todo" todoListChecked="true">foo</paragraph>'
			);
		} );

		it( 'should not convert nested li if it was already consumed', () => {
			editor.data.upcastDispatcher.on( 'element:li', ( evt, data, conversionApi ) => {
				conversionApi.consumable.consume( data.viewItem, { name: true } );
			}, { priority: 'highest' } );

			editor.setData(
				'<ul>' +
					'<li>' +
						'<input type="checkbox" checked="checked">' +
						'<p>foo</p>' +
					'</li>' +
				'</ul>'
			);

			expect( _getModelData( model, { withoutSelection: true } ) ).to.equal( '<paragraph></paragraph>' );
		} );

		it( 'should convert li with a checkbox and two paragraphs', () => {
			testUpcast(
				'<ul>' +
					'<li>' +
						'<input type="checkbox">' +
						'<p>foo</p>' +
						'<p>bar</p>' +
					'</li>' +
				'</ul>',
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>' +
				'<paragraph listIndent="0" listItemId="a00" listType="todo">bar</paragraph>'
			);
		} );

		it( 'should convert li with a checkbox and a blockquote', () => {
			testUpcast(
				'<ul>' +
					'<li>' +
						'<input type="checkbox">' +
						'<blockquote>foo</blockquote>' +
					'</li>' +
				'</ul>',
				'<blockQuote listIndent="0" listItemId="a00" listType="todo">' +
					'<paragraph>foo</paragraph>' +
				'</blockQuote>'
			);
		} );

		it( 'should convert li with a checkbox and a heading', () => {
			testUpcast(
				'<ul>' +
					'<li>' +
						'<input type="checkbox">' +
						'<h2>foo</h2>' +
					'</li>' +
				'</ul>',
				'<heading1 listIndent="0" listItemId="a00" listType="todo">foo</heading1>'
			);
		} );

		it( 'should convert li with a checkbox and a table', () => {
			testUpcast(
				'<ul>' +
					'<li>' +
						'<input type="checkbox">' +
						'<table><tr><td>foo</td></tr></table>' +
					'</li>' +
				'</ul>',
				'<table listIndent="0" listItemId="a00" listType="todo">' +
					'<tableRow>' +
						'<tableCell>' +
							'<paragraph>foo</paragraph>' +
						'</tableCell>' +
					'</tableRow>' +
				'</table>'
			);
		} );

		it( 'should not convert checkbox if consumed by other converter', () => {
			model.schema.register( 'input', { inheritAllFrom: '$inlineObject' } );
			editor.conversion.elementToElement( { model: 'input', view: 'input', converterPriority: 'high' } );

			testUpcast(
				'<ul>' +
					'<li><input type="checkbox">foo</li>' +
				'</ul>',
				'<paragraph listIndent="0" listItemId="a00" listType="bulleted"><input></input>foo</paragraph>'
			);
		} );

		it( 'should not convert label element if already consumed', () => {
			model.schema.register( 'label', { inheritAllFrom: '$inlineObject', allowChildren: '$text' } );
			editor.conversion.elementToElement( { model: 'label', view: 'label', converterPriority: 'high' } );

			testUpcast(
				'<ul>' +
					'<li><label class="todo-list__label"><input type="checkbox">foo</label></li>' +
				'</ul>',
				'<paragraph listIndent="0" listItemId="a00" listType="bulleted"><label>foo</label></paragraph>'
			);
		} );
	} );

	describe( 'upcast - list properties integration', () => {
		let editor, model;

		beforeEach( async () => {
			editor = await VirtualTestEditor.create( {
				plugins: [ Paragraph, TodoListEditing, ListPropertiesEditing ],
				list: {
					properties: {
						startIndex: true
					}
				}
			} );

			model = editor.model;
			view = editor.editing.view;
		} );

		afterEach( () => {
			return editor.destroy();
		} );

		it( 'should not convert list style on to-do list', () => {
			editor.setData(
				'<ul style="list-style-type:circle;">' +
					'<li><input type="checkbox">Foo</li>' +
					'<li><input type="checkbox">Bar</li>' +
				'</ul>'
			);

			expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup(
				'<paragraph listIndent="0" listItemId="a00" listType="todo">Foo</paragraph>' +
				'<paragraph listIndent="0" listItemId="a01" listType="todo">Bar</paragraph>'
			);
		} );

		it( 'should not convert list start on to-do list', () => {
			editor.setData(
				'<ol start="2">' +
					'<li><input type="checkbox">Foo</li>' +
					'<li><input type="checkbox">Bar</li>' +
				'</ol>'
			);

			expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup(
				'<paragraph listIndent="0" listItemId="a00" listType="todo">Foo</paragraph>' +
				'<paragraph listIndent="0" listItemId="a01" listType="todo">Bar</paragraph>'
			);
		} );
	} );

	describe( 'upcast - GHS integration', () => {
		let element, editor, model;

		beforeEach( async () => {
			element = document.createElement( 'div' );
			document.body.appendChild( element );

			editor = await ClassicTestEditor.create( element, {
				plugins: [ Paragraph, TodoListEditing, GeneralHtmlSupport ],
				htmlSupport: {
					allow: [
						{
							name: /./,
							styles: true,
							attributes: true,
							classes: true
						}
					]
				}
			} );

			model = editor.model;
			view = editor.editing.view;
		} );

		afterEach( async () => {
			element.remove();
			await editor.destroy();
		} );

		it( 'should consume all to-do list related elements and attributes so GHS will not handle them (with description)', () => {
			editor.setData(
				'<ul class="todo-list">' +
					'<li>' +
						'<label class="todo-list__label">' +
							'<input type="checkbox" disabled="disabled" checked="checked">' +
							'<span class="todo-list__label__description">foo</span>' +
						'</label>' +
					'</li>' +
				'</ul>'
			);

			expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup(
				'<paragraph' +
						' htmlLiAttributes="{}" htmlUlAttributes="{}"' +
						' listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">' +
					'foo' +
				'</paragraph>'
			);
		} );

		it( 'should consume all to-do list related elements and attributes so GHS will not handle them (without description)', () => {
			editor.setData(
				'<ul class="todo-list">' +
					'<li>' +
						'<label class="todo-list__label todo-list__label_without-description">' +
							'<input type="checkbox" disabled="disabled">' +
						'</label>' +
						'<h2>foo</h2>' +
					'</li>' +
				'</ul>'
			);

			expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup(
				'<htmlH2 htmlLiAttributes="{}" htmlUlAttributes="{}" listIndent="0" listItemId="a00" listType="todo">foo</htmlH2>'
			);
		} );

		it( 'should not consume other label elements', () => {
			editor.setData( '<p><label>foo</label></p>' );

			expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup(
				'<paragraph><$text htmlLabel="{}">foo</$text></paragraph>'
			);
		} );
	} );

	describe( 'downcast - editing', () => {
		it( 'should convert a todo list item', () => {
			testEditing(
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>',
				'<ul class="todo-list">' +
					'<li>' +
						'<span class="todo-list__label">' +
							'<span contenteditable="false">' +
								'<input tabindex="-1" type="checkbox"></input>' +
							'</span>' +
							'<span class="todo-list__label__description">' +
								'foo' +
							'</span>' +
						'</span>' +
					'</li>' +
				'</ul>'
			);
		} );

		it( 'should convert a nested todo list item', () => {
			testEditing(
				'<paragraph listIndent="0" listItemId="a01" listType="todo">foo</paragraph>' +
				'<paragraph listIndent="1" listItemId="a00" listType="todo">foo</paragraph>',
				'<ul class="todo-list">' +
					'<li>' +
						'<span class="todo-list__label">' +
							'<span contenteditable="false">' +
								'<input tabindex="-1" type="checkbox"></input>' +
							'</span>' +
							'<span class="todo-list__label__description">' +
								'foo' +
							'</span>' +
						'</span>' +
						'<ul class="todo-list">' +
							'<li>' +
								'<span class="todo-list__label">' +
									'<span contenteditable="false">' +
										'<input tabindex="-1" type="checkbox"></input>' +
									'</span>' +
									'<span class="todo-list__label__description">' +
										'foo' +
									'</span>' +
								'</span>' +
							'</li>' +
						'</ul>' +
					'</li>' +
				'</ul>'
			);
		} );

		it( 'should convert to-do list item mixed with bulleted list items', () => {
			testEditing(
				'<paragraph listIndent="0" listItemId="a00" listType="bulleted">foo</paragraph>' +
				'<paragraph listIndent="0" listItemId="a01" listType="todo">bar</paragraph>' +
				'<paragraph listIndent="0" listItemId="a02" listType="bulleted">baz</paragraph>',
				'<ul>' +
					'<li>' +
						'<span class="ck-list-bogus-paragraph">foo</span>' +
					'</li>' +
				'</ul>' +
				'<ul class="todo-list">' +
					'<li>' +
						'<span class="todo-list__label">' +
							'<span contenteditable="false">' +
								'<input tabindex="-1" type="checkbox"></input>' +
							'</span>' +
							'<span class="todo-list__label__description">bar</span>' +
						'</span>' +
					'</li>' +
				'</ul>' +
				'<ul>' +
					'<li><span class="ck-list-bogus-paragraph">baz</span></li>' +
				'</ul>'
			);
		} );

		it( 'should convert to-do list item mixed with numbered list items', () => {
			testEditing(
				'<paragraph listIndent="0" listItemId="a00" listType="numbered">foo</paragraph>' +
				'<paragraph listIndent="0" listItemId="a01" listType="todo">bar</paragraph>' +
				'<paragraph listIndent="0" listItemId="a02" listType="numbered">baz</paragraph>',
				'<ol>' +
					'<li>' +
						'<span class="ck-list-bogus-paragraph">foo</span>' +
					'</li>' +
				'</ol>' +
				'<ul class="todo-list">' +
					'<li>' +
						'<span class="todo-list__label">' +
							'<span contenteditable="false">' +
								'<input tabindex="-1" type="checkbox"></input>' +
							'</span>' +
							'<span class="todo-list__label__description">bar</span>' +
						'</span>' +
					'</li>' +
				'</ul>' +
				'<ol>' +
					'<li><span class="ck-list-bogus-paragraph">baz</span></li>' +
				'</ol>'
			);
		} );

		it( 'should wrap a checkbox and first paragraph in a span with a special label class', () => {
			testEditing(
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>' +
				'<paragraph listIndent="0" listItemId="a00" listType="todo">bar</paragraph>',
				'<ul class="todo-list">' +
					'<li>' +
						'<span class="todo-list__label">' +
							'<span contenteditable="false">' +
								'<input tabindex="-1" type="checkbox"></input>' +
							'</span>' +
							'<span class="todo-list__label__description">' +
								'foo' +
							'</span>' +
						'</span>' +
						'<p>bar</p>' +
					'</li>' +
				'</ul>'
			);
		} );

		it( 'should wrap only a checkbox in a span if first element is a blockquote', () => {
			testEditing(
				'<blockQuote listIndent="0" listItemId="a00" listType="todo">' +
					'<paragraph>foo</paragraph>' +
				'</blockQuote>',
				'<ul class="todo-list">' +
					'<li>' +
						'<span class="todo-list__label todo-list__label_without-description">' +
							'<span contenteditable="false">' +
								'<input tabindex="-1" type="checkbox"></input>' +
							'</span>' +
						'</span>' +
						'<blockquote>' +
							'<p>foo</p>' +
						'</blockquote>' +
					'</li>' +
				'</ul>'
			);
		} );

		it( 'should wrap only a checkbox in a span if first element is a heading', () => {
			testEditing(
				'<heading1 listIndent="0" listItemId="a00" listType="todo">foo</heading1>',
				'<ul class="todo-list">' +
					'<li>' +
						'<span class="todo-list__label todo-list__label_without-description">' +
							'<span contenteditable="false">' +
								'<input tabindex="-1" type="checkbox"></input>' +
							'</span>' +
						'</span>' +
						'<h2>foo</h2>' +
					'</li>' +
				'</ul>'
			);
		} );

		it( 'should wrap only a checkbox in a span if first element is a table', () => {
			testEditing(
				'<table listIndent="0" listItemId="a00" listType="todo">' +
					'<tableRow>' +
						'<tableCell>' +
							'<paragraph>foo</paragraph>' +
						'</tableCell>' +
					'</tableRow>' +
				'</table>',
				'<ul class="todo-list">' +
					'<li>' +
						'<span class="todo-list__label todo-list__label_without-description">' +
							'<span contenteditable="false">' +
								'<input tabindex="-1" type="checkbox"></input>' +
							'</span>' +
						'</span>' +
						'<figure class="ck-widget ck-widget_with-selection-handle table" contenteditable="false">' +
							'<div class="ck ck-widget__selection-handle"></div>' +
							'<table>' +
								'<tbody>' +
									'<tr>' +
										'<td class="ck-editor__editable ck-editor__nested-editable" ' +
											'contenteditable="true" tabindex="-1">' +
											'<span class="ck-table-bogus-paragraph">foo</span>' +
										'</td>' +
									'</tr>' +
								'</tbody>' +
							'</table>' +
						'</figure>' +
					'</li>' +
				'</ul>'
			);
		} );

		it( 'should not use description span if there is an alignment set on the paragraph', () => {
			_setModelData( model,
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>'
			);

			expect( _getViewData( view, { withoutSelection: true } ) ).to.equalMarkup(
				'<ul class="todo-list">' +
					'<li>' +
						'<span class="todo-list__label">' +
							'<span contenteditable="false">' +
								'<input tabindex="-1" type="checkbox"></input>' +
							'</span>' +
							'<span class="todo-list__label__description">foo</span>' +
						'</span>' +
					'</li>' +
				'</ul>'
			);

			editor.execute( 'alignment', { value: 'right' } );

			expect( _getViewData( view, { withoutSelection: true } ) ).to.equalMarkup(
				'<ul class="todo-list">' +
					'<li>' +
						'<span class="todo-list__label todo-list__label_without-description">' +
							'<span contenteditable="false">' +
								'<input tabindex="-1" type="checkbox"></input>' +
							'</span>' +
						'</span>' +
						'<p style="text-align:right">' +
							'foo' +
						'</p>' +
					'</li>' +
				'</ul>'
			);

			editor.execute( 'alignment', { value: 'left' } );

			expect( _getViewData( view, { withoutSelection: true } ) ).to.equalMarkup(
				'<ul class="todo-list">' +
					'<li>' +
						'<span class="todo-list__label">' +
							'<span contenteditable="false">' +
								'<input tabindex="-1" type="checkbox"></input>' +
							'</span>' +
							'<span class="todo-list__label__description">foo</span>' +
						'</span>' +
					'</li>' +
				'</ul>'
			);
		} );

		it( 'should use description span even if there is an selection attribute on block', () => {
			_setModelData( model,
				'<paragraph listIndent="0" listItemId="a00" listType="todo">[]</paragraph>'
			);

			model.change( writer => writer.setSelectionAttribute( 'bold', true ) );

			expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup(
				'<paragraph listIndent="0" listItemId="a00" listType="todo" selection:bold="true"></paragraph>'
			);

			expect( _getViewData( view, { withoutSelection: true } ) ).to.equalMarkup(
				'<ul class="todo-list">' +
					'<li>' +
						'<span class="todo-list__label">' +
							'<span contenteditable="false">' +
								'<input tabindex="-1" type="checkbox"></input>' +
							'</span>' +
							'<span class="todo-list__label__description"></span>' +
						'</span>' +
					'</li>' +
				'</ul>'
			);
		} );
	} );

	describe( 'downcast - data', () => {
		it( 'should convert a todo list item', () => {
			testData(
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>',
				'<ul class="todo-list">' +
					'<li>' +
						'<label class="todo-list__label">' +
							'<input type="checkbox" disabled="disabled">' +
							'<span class="todo-list__label__description">foo</span>' +
						'</label>' +
					'</li>' +
				'</ul>'
			);
		} );

		it( 'should convert a nested todo list item', () => {
			testData(
				'<paragraph listIndent="0" listItemId="a01" listType="todo">foo</paragraph>' +
				'<paragraph listIndent="1" listItemId="a00" listType="todo">foo</paragraph>',
				'<ul class="todo-list">' +
					'<li>' +
						'<label class="todo-list__label">' +
							'<input type="checkbox" disabled="disabled">' +
							'<span class="todo-list__label__description">foo</span>' +
						'</label>' +
						'<ul class="todo-list">' +
							'<li>' +
								'<label class="todo-list__label">' +
									'<input type="checkbox" disabled="disabled">' +
										'<span class="todo-list__label__description">' +
											'foo' +
										'</span>' +
									'</label>' +
								'</li>' +
						'</ul>' +
					'</li>' +
				'</ul>'
			);
		} );

		it( 'should convert to-do list item mixed with bulleted list items', () => {
			testData(
				'<paragraph listIndent="0" listItemId="a00" listType="bulleted">foo</paragraph>' +
				'<paragraph listIndent="0" listItemId="a01" listType="todo">bar</paragraph>' +
				'<paragraph listIndent="0" listItemId="a02" listType="bulleted">baz</paragraph>',
				'<ul>' +
					'<li>foo</li>' +
				'</ul>' +
				'<ul class="todo-list">' +
					'<li>' +
						'<label class="todo-list__label">' +
							'<input type="checkbox" disabled="disabled">' +
							'<span class="todo-list__label__description">bar</span>' +
						'</label>' +
					'</li>' +
				'</ul>' +
				'<ul>' +
					'<li>baz</li>' +
				'</ul>'
			);
		} );

		it( 'should convert to-do list item mixed with numbered list items', () => {
			testData(
				'<paragraph listIndent="0" listItemId="a00" listType="numbered">foo</paragraph>' +
				'<paragraph listIndent="0" listItemId="a01" listType="todo">bar</paragraph>' +
				'<paragraph listIndent="0" listItemId="a02" listType="numbered">baz</paragraph>',
				'<ol>' +
					'<li>foo</li>' +
				'</ol>' +
				'<ul class="todo-list">' +
					'<li>' +
						'<label class="todo-list__label">' +
							'<input type="checkbox" disabled="disabled">' +
							'<span class="todo-list__label__description">bar</span>' +
						'</label>' +
					'</li>' +
				'</ul>' +
				'<ol>' +
					'<li>baz</li>' +
				'</ol>'
			);
		} );

		it( 'should wrap a checkbox and first paragraph in a label element', () => {
			testData(
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>' +
				'<paragraph listIndent="0" listItemId="a00" listType="todo">bar</paragraph>',
				'<ul class="todo-list">' +
					'<li>' +
						'<label class="todo-list__label">' +
							'<input type="checkbox" disabled="disabled">' +
							'<span class="todo-list__label__description">foo</span>' +
						'</label>' +
						'<p>bar</p>' +
					'</li>' +
				'</ul>'
			);
		} );

		it( 'should wrap only a checkbox in a label element if first element is a blockquote', () => {
			testData(
				'<blockQuote listIndent="0" listItemId="a00" listType="todo">' +
					'<paragraph>foo</paragraph>' +
				'</blockQuote>',
				'<ul class="todo-list">' +
					'<li>' +
						'<label class="todo-list__label todo-list__label_without-description">' +
							'<input type="checkbox" disabled="disabled">' +
						'</label>' +
						'<blockquote><p>foo</p></blockquote>' +
					'</li>' +
				'</ul>'
			);
		} );

		it( 'should wrap only a checkbox in a label element if first element is a heading', () => {
			testData(
				'<heading1 listIndent="0" listItemId="a00" listType="todo">foo</heading1>',
				'<ul class="todo-list">' +
					'<li>' +
						'<label class="todo-list__label todo-list__label_without-description">' +
							'<input type="checkbox" disabled="disabled">' +
						'</label>' +
						'<h2>foo</h2>' +
					'</li>' +
				'</ul>'
			);
		} );

		it( 'should wrap only a checkbox in a label element if first element is a table', () => {
			testData(
				'<table listIndent="0" listItemId="a00" listType="todo">' +
					'<tableRow>' +
						'<tableCell>' +
							'<paragraph>foo</paragraph>' +
						'</tableCell>' +
					'</tableRow>' +
				'</table>',
				'<ul class="todo-list">' +
					'<li>' +
						'<label class="todo-list__label todo-list__label_without-description">' +
							'<input type="checkbox" disabled="disabled">' +
						'</label>' +
						'<figure class="table">' +
							'<table>' +
								'<tbody>' +
									'<tr>' +
										'<td>foo</td>' +
									'</tr>' +
								'</tbody>' +
							'</table>' +
						'</figure>' +
					'</li>' +
				'</ul>'
			);
		} );

		it( 'should convert a todo list item with alignment set', () => {
			testData(
				'<paragraph listIndent="0" listItemId="a00" listType="todo" alignment="right">foo</paragraph>',

				'<ul class="todo-list">' +
					'<li>' +
						'<label class="todo-list__label todo-list__label_without-description">' +
							'<input type="checkbox" disabled="disabled">' +
						'</label>' +
						'<p style="text-align:right;">foo</p>' +
					'</li>' +
				'</ul>'
			);
		} );
	} );

	describe( 'postfixers', () => {
		describe( '`todoListChecked` attribute should be the same in all blocks of a single list item', () => {
			it( 'should add missing `todoListChecked` attribute to other blocks', () => {
				testPostfixer(
					'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a00" listType="todo">bar</paragraph>' +
					'<heading1 listIndent="0" listItemId="a00" listType="todo">baz</heading1>',

					'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">bar</paragraph>' +
					'<heading1 listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">baz</heading1>'
				);
			} );

			it( 'should add missing `todoListChecked` attribute to other blocks excluding nested list items', () => {
				testPostfixer(
					'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a00" listType="todo">bar</paragraph>' +
					'<paragraph listIndent="1" listItemId="a01" listType="todo">nested 1</paragraph>' +
					'<paragraph listIndent="1" listItemId="a01" listType="todo">nested 2</paragraph>' +
					'<heading1 listIndent="0" listItemId="a00" listType="todo">baz</heading1>',

					'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">bar</paragraph>' +
					'<paragraph listIndent="1" listItemId="a01" listType="todo">nested 1</paragraph>' +
					'<paragraph listIndent="1" listItemId="a01" listType="todo">nested 2</paragraph>' +
					'<heading1 listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">baz</heading1>'
				);
			} );

			it( 'should remove `todoListChecked` from other blocks', () => {
				testPostfixer(
					'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">bar</paragraph>' +
					'<heading1 listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">baz</heading1>',

					'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a00" listType="todo">bar</paragraph>' +
					'<heading1 listIndent="0" listItemId="a00" listType="todo">baz</heading1>'
				);
			} );

			it( 'should remove `todoListChecked` from other blocks excluding nested list items', () => {
				testPostfixer(
					'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">bar</paragraph>' +
					'<paragraph listIndent="1" listItemId="a01" listType="todo" todoListChecked="true">nested 1</paragraph>' +
					'<paragraph listIndent="1" listItemId="a01" listType="todo" todoListChecked="true">nested 2</paragraph>' +
					'<heading1 listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">baz</heading1>',

					'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a00" listType="todo">bar</paragraph>' +
					'<paragraph listIndent="1" listItemId="a01" listType="todo" todoListChecked="true">nested 1</paragraph>' +
					'<paragraph listIndent="1" listItemId="a01" listType="todo" todoListChecked="true">nested 2</paragraph>' +
					'<heading1 listIndent="0" listItemId="a00" listType="todo">baz</heading1>'
				);
			} );
		} );

		describe( '`todoListChecked` attribute should be applied only to todo list items', () => {
			it( 'should remove `todoListChecked` from elements other than todo list items', () => {
				testPostfixer(
					'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="bulleted" todoListChecked="true">foo</paragraph>' +
					'<heading1 listIndent="0" listItemId="a02" listType="numbered" todoListChecked="true">baz</heading1>',

					'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="bulleted">foo</paragraph>' +
					'<heading1 listIndent="0" listItemId="a02" listType="numbered">baz</heading1>'
				);
			} );

			it( 'should remove `todoListChecked` attribute from list items that are converted from todo to bulleted type', () => {
				_setModelData( model,
					'[<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="todo" todoListChecked="true">foo</paragraph>' +
					'<heading1 listIndent="0" listItemId="a02" listType="todo" todoListChecked="true">baz</heading1>]'
				);

				expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup(
					'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="todo" todoListChecked="true">foo</paragraph>' +
					'<heading1 listIndent="0" listItemId="a02" listType="todo" todoListChecked="true">baz</heading1>'
				);

				editor.execute( 'bulletedList' );

				expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup(
					'<paragraph listIndent="0" listItemId="a00" listType="bulleted">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="bulleted">foo</paragraph>' +
					'<heading1 listIndent="0" listItemId="a02" listType="bulleted">baz</heading1>'
				);
			} );
		} );
	} );

	describe( 'accessibility', () => {
		let announcerSpy;

		beforeEach( () => {
			announcerSpy = sinon.spy( editor.ui.ariaLiveAnnouncer, 'announce' );
		} );

		it( 'should announce entering and leaving list (multiBlock = false)', async () => {
			await editor.destroy();

			editor = await createEditor( {
				list: {
					multiBlock: false
				}
			} );

			model = editor.model;
			modelRoot = model.document.getRoot();
			announcerSpy = sinon.spy( editor.ui.ariaLiveAnnouncer, 'announce' );

			_setModelData( model,
				'<paragraph>[Foo]</paragraph>' +
				'<listItem listType="todo" listIndent="0">1</listItem>' +
				'<listItem listType="todo" listIndent="0" todoListChecked="true">2</listItem>' +
				'<paragraph>Foo</paragraph>'
			);

			moveSelection( [ 1, 0 ], [ 1, 1 ] );
			expectAnnounce( 'Entering a to-do list' );

			moveSelection( [ 3, 0 ], [ 3, 1 ] );
			expectAnnounce( 'Leaving a to-do list' );
		} );

		it( 'should announce entering and leaving list', () => {
			_setModelData( model,
				'<paragraph>[Foo]</paragraph>' +
				'<paragraph listType="todo" listIndent="0">1</paragraph>' +
				'<paragraph listType="todo" listIndent="0" todoListChecked="true">2</paragraph>' +
				'<paragraph>Foo</paragraph>'
			);

			moveSelection( [ 1, 0 ], [ 1, 1 ] );
			expectAnnounce( 'Entering a to-do list' );

			moveSelection( [ 3, 0 ], [ 3, 1 ] );
			expectAnnounce( 'Leaving a to-do list' );
		} );

		it( 'should announce entering and leaving list once, even if there is nested list', () => {
			_setModelData( model,
				'<paragraph>[Foo]</paragraph>' +
				'<paragraph listType="todo" listIndent="0">1</paragraph>' +
				'<paragraph listType="todo" listIndent="1">1</paragraph>' +
				'<paragraph listType="todo" listIndent="0" todoListChecked="true">2</paragraph>' +
				'<paragraph>Foo</paragraph>'
			);

			moveSelection( [ 1, 0 ], [ 1, 1 ] );
			expectAnnounce( 'Entering a to-do list' );

			moveSelection( [ 2, 0 ], [ 2, 1 ] );
			expectNotToAnnounce( 'Leaving a to-do list' );

			moveSelection( [ 4, 0 ], [ 4, 1 ] );
			expectAnnounce( 'Leaving a to-do list' );
		} );

		function expectNotToAnnounce( message ) {
			expect( announcerSpy ).not.to.be.calledWithExactly( message );
		}

		function expectAnnounce( message ) {
			expect( announcerSpy ).to.be.calledWithExactly( message );
		}

		function moveSelection( startPath, endPath ) {
			model.change( writer => {
				writer.setSelection( createRange( modelRoot, startPath, modelRoot, endPath ) );
			} );
		}

		function createRange( startElement, startPath, endElement, endPath ) {
			return model.createRange(
				model.createPositionFromPath( startElement, startPath ),
				model.createPositionFromPath( endElement, endPath )
			);
		}
	} );

	describe( 'user interaction events', () => {
		it( 'should toggle check state of selected to-do list item on keystroke', () => {
			const command = editor.commands.get( 'checkTodoList' );

			sinon.spy( command, 'execute' );

			const domEvtDataStub = {
				keyCode: getCode( 'enter' ),
				preventDefault: sinon.spy(),
				stopPropagation: sinon.spy()
			};

			if ( env.isMac ) {
				domEvtDataStub.metaKey = true;
			} else {
				domEvtDataStub.ctrlKey = true;
			}

			view.document.fire( 'keydown', domEvtDataStub );

			sinon.assert.calledOnce( command.execute );

			view.document.fire( 'keydown', domEvtDataStub );

			sinon.assert.calledTwice( command.execute );
		} );

		it( 'should toggle check state of a to-do list item on clicking the checkbox', () => {
			_setModelData( model,
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>'
			);

			const command = editor.commands.get( 'checkTodoList' );

			sinon.spy( command, 'execute' );

			view.getDomRoot().querySelector( 'input' ).dispatchEvent( new Event( 'change', { 'bubbles': true } ) );

			sinon.assert.calledOnce( command.execute );

			expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup(
				'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">foo</paragraph>'
			);
		} );

		it( 'should toggle check state of a to-do list item on todoCheckboxChange event with input target element', () => {
			_setModelData( model,
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>'
			);

			const command = editor.commands.get( 'checkTodoList' );

			sinon.spy( command, 'execute' );

			view.document.fire( 'todoCheckboxChange', {
				target: view.document.getRoot().getChild( 0 ).getChild( 0 ).getChild( 0 ).getChild( 0 ).getChild( 0 )
			} );

			sinon.assert.calledOnce( command.execute );

			expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup(
				'<paragraph listIndent="0" listItemId="a00" listType="todo" todoListChecked="true">foo</paragraph>'
			);
		} );

		it( 'should not toggle check state of a to-do list item on todoCheckboxChange event without target element', () => {
			_setModelData( model,
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>'
			);

			const command = editor.commands.get( 'checkTodoList' );

			sinon.spy( command, 'execute' );

			view.document.fire( 'todoCheckboxChange', {} );

			sinon.assert.notCalled( command.execute );

			expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup(
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>'
			);
		} );

		it( 'should not toggle check state of a to-do list item on todoCheckboxChange event with target element', () => {
			_setModelData( model,
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>'
			);

			const command = editor.commands.get( 'checkTodoList' );

			sinon.spy( command, 'execute' );

			view.document.fire( 'todoCheckboxChange', {
				target: view.document.getRoot()
			} );

			sinon.assert.notCalled( command.execute );

			expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup(
				'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>'
			);
		} );

		describe( 'arrow keys', () => {
			it( 'should move collapsed selection at start of following todo list item on right arrow in todo list item', () => {
				_setModelData( model,
					'<paragraph listIndent="0" listItemId="a00" listType="todo">foo[]</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="todo">bar</paragraph>'
				);

				const eventData = {
					keyCode: getCode( 'arrowRight' ),
					preventDefault: () => {},
					stopPropagation: () => {}
				};

				view.document.fire( 'keydown', eventData );

				expect( _getModelData( model ) ).to.equalMarkup(
					'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="todo">[]bar</paragraph>'
				);
			} );

			it( 'should move collapsed selection at start of following todo list item on right arrow in paragraph', () => {
				_setModelData( model,
					'<paragraph>foo[]</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="todo">bar</paragraph>'
				);

				const eventData = {
					keyCode: getCode( 'arrowRight' ),
					preventDefault: () => {},
					stopPropagation: () => {}
				};

				view.document.fire( 'keydown', eventData );

				expect( _getModelData( model ) ).to.equalMarkup(
					'<paragraph>foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="todo">[]bar</paragraph>'
				);
			} );

			it( 'should do nothing if selection is at end of the last todo list item and right arrow is pressed', () => {
				_setModelData( model,
					'<paragraph listIndent="0" listItemId="a00" listType="todo">foo[]</paragraph>'
				);

				const eventData = {
					keyCode: getCode( 'arrowRight' ),
					preventDefault: sinon.spy(),
					stopPropagation: sinon.spy(),
					domTarget: view.getDomRoot()
				};

				view.document.fire( 'keydown', eventData );

				sinon.assert.notCalled( eventData.preventDefault );
				sinon.assert.notCalled( eventData.stopPropagation );

				expect( _getModelData( model ) ).to.equalMarkup(
					'<paragraph listIndent="0" listItemId="a00" listType="todo">foo[]</paragraph>'
				);
			} );

			it( 'should not move non-collapsed selection at start of following todo list item on right arrow key in todo list item', () => {
				_setModelData( model,
					'<paragraph listIndent="0" listItemId="a00" listType="todo">fo[o]</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="todo">bar</paragraph>'
				);

				const eventData = {
					keyCode: getCode( 'arrowRight' ),
					preventDefault: sinon.spy(),
					stopPropagation: sinon.spy(),
					domTarget: view.getDomRoot()
				};

				view.document.fire( 'keydown', eventData );

				sinon.assert.notCalled( eventData.preventDefault );
				sinon.assert.notCalled( eventData.stopPropagation );

				expect( _getModelData( model ) ).to.equalMarkup(
					'<paragraph listIndent="0" listItemId="a00" listType="todo">fo[o]</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="todo">bar</paragraph>'
				);
			} );

			it( 'should not move non-collapsed selection at start of following todo list item on right arrow key in paragraph', () => {
				_setModelData( model,
					'<paragraph>fo[o]</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="todo">bar</paragraph>'
				);

				const eventData = {
					keyCode: getCode( 'arrowRight' ),
					preventDefault: sinon.spy(),
					stopPropagation: sinon.spy(),
					domTarget: view.getDomRoot()
				};

				view.document.fire( 'keydown', eventData );

				sinon.assert.notCalled( eventData.preventDefault );
				sinon.assert.notCalled( eventData.stopPropagation );

				expect( _getModelData( model ) ).to.equalMarkup(
					'<paragraph>fo[o]</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="todo">bar</paragraph>'
				);
			} );

			it( 'should move a collapsed selection to the end of the preceding todo list item on left arrow', () => {
				_setModelData( model,
					'<paragraph listIndent="0" listItemId="a00" listType="todo">foo</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="todo">[]bar</paragraph>'
				);

				const eventData = {
					keyCode: getCode( 'arrowLeft' ),
					preventDefault: () => {},
					stopPropagation: () => {},
					domTarget: view.getDomRoot()
				};

				view.document.fire( 'keydown', eventData );

				expect( _getModelData( model ) ).to.equalMarkup(
					'<paragraph listIndent="0" listItemId="a00" listType="todo">foo[]</paragraph>' +
					'<paragraph listIndent="0" listItemId="a01" listType="todo">bar</paragraph>'
				);
			} );

			it( 'should do nothing if selection is at start of first element which is a todo list item and left arrow is pressed', () => {
				_setModelData( model,
					'<paragraph listIndent="0" listItemId="a00" listType="todo">[]foo</paragraph>'
				);

				const eventData = {
					keyCode: getCode( 'arrowLeft' ),
					preventDefault: sinon.spy(),
					stopPropagation: sinon.spy(),
					domTarget: view.getDomRoot()
				};

				view.document.fire( 'keydown', eventData );

				sinon.assert.notCalled( eventData.preventDefault );
				sinon.assert.notCalled( eventData.stopPropagation );

				expect( _getModelData( model ) ).to.equalMarkup(
					'<paragraph listIndent="0" listItemId="a00" listType="todo">[]foo</paragraph>'
				);
			} );
		} );
	} );

	async function createEditor( config = {} ) {
		return ClassicTestEditor.create( editorElement, {
			plugins: [ Paragraph, TodoListEditing, BlockQuoteEditing, TableEditing, HeadingEditing, AlignmentEditing ],
			...config
		} );
	}

	function testUpcast( input, output ) {
		editor.setData( input );
		expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup( output );
	}

	function testEditing( input, output ) {
		_setModelData( model, input );
		expect( _getViewData( view, { withoutSelection: true } ) ).to.equalMarkup( output );
	}

	function testData( input, output ) {
		_setModelData( model, input );
		expect( editor.getData( { skipListItemIds: true } ) ).to.equalMarkup( output );
	}

	function testPostfixer( input, output ) {
		_setModelData( model, input );
		expect( _getModelData( model, { withoutSelection: true } ) ).to.equalMarkup( output );
	}
} );
