%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /www/varak.net/wiki.varak.net/extensions/VisualEditor/lib/ve/tests/dm/
Upload File :
Create Path :
Current File : /www/varak.net/wiki.varak.net/extensions/VisualEditor/lib/ve/tests/dm/ve.dm.Document.test.js

/*!
 * VisualEditor DataModel Document tests.
 *
 * @copyright 2011-2016 VisualEditor Team and others; see http://ve.mit-license.org
 */

QUnit.module( 've.dm.Document' );

/* Tests */

QUnit.test( 'constructor', 12, function ( assert ) {
	var data, htmlDoc,
		doc = ve.dm.example.createExampleDocument();
	assert.equalNodeTree( doc.getDocumentNode(), ve.dm.example.tree, 'node tree matches example data' );
	assert.throws(
		function () {
			doc = new ve.dm.Document( [
				{ type: '/paragraph' },
				{ type: 'paragraph' }
			] );
			doc.buildNodeTree();
		},
		Error,
		'unbalanced input causes exception'
	);
	assert.throws(
		function () {
			doc = new ve.dm.Document( [
				{ type: 'paragraph' },
				{ type: 'inlineImage' },
				{ type: '/paragraph' }
			] );
			doc.buildNodeTree();
		},
		Error,
		'unclosed inline node causes exception'
	);

	doc = new ve.dm.Document( [ 'a', 'b', 'c', 'd' ] );
	assert.equalNodeTree(
		doc.getDocumentNode(),
		new ve.dm.DocumentNode( [ new ve.dm.TextNode( 4 ) ] ),
		'plain text input is handled correctly'
	);
	assert.deepEqualWithDomElements( doc.getMetadata(), new Array( 5 ),
		'sparse metadata array is created'
	);
	assert.strictEqual( doc.getHtmlDocument().body.innerHTML, '', 'Empty HTML document is created' );

	htmlDoc = ve.createDocumentFromHtml( 'abcd' );
	doc = new ve.dm.Document( [ 'a', 'b', 'c', 'd' ], htmlDoc );
	assert.strictEqual( doc.getHtmlDocument(), htmlDoc, 'Provided HTML document is used' );

	data = new ve.dm.ElementLinearData(
		new ve.dm.IndexValueStore(),
		[ { type: 'paragraph' }, { type: '/paragraph' } ]
	);
	doc = new ve.dm.Document( data );
	assert.equalNodeTree(
		doc.getDocumentNode(),
		new ve.dm.DocumentNode( [ new ve.dm.ParagraphNode( { type: 'paragraph' } ) ] ),
		'empty paragraph no longer has a text node'
	);
	assert.strictEqual( doc.data, data, 'ElementLinearData is stored by reference' );

	doc = ve.dm.example.createExampleDocument( 'withMeta' );
	assert.equalLinearDataWithDom( doc.getStore(), doc.getData(), ve.dm.example.withMetaPlainData,
		'metadata is stripped out of the linear model'
	);
	assert.equalLinearDataWithDom( doc.getStore(), doc.getMetadata(), ve.dm.example.withMetaMetaData,
		'metadata is put in the meta-linmod'
	);
	assert.equalNodeTree(
		doc.getDocumentNode(),
		new ve.dm.DocumentNode( [
			new ve.dm.ParagraphNode( ve.dm.example.withMetaPlainData[ 0 ], [ new ve.dm.TextNode( 9 ) ] ),
			new ve.dm.InternalListNode( ve.dm.example.withMetaPlainData[ 11 ] )
		] ),
		'node tree does not contain metadata'
	);
} );

QUnit.test( 'getData', 1, function ( assert ) {
	var doc = ve.dm.example.createExampleDocument(),
		expectedData = ve.dm.example.preprocessAnnotations( ve.copy( ve.dm.example.data ) );
	assert.equalLinearDataWithDom( doc.getStore(), doc.getData(), expectedData.getData() );
} );

QUnit.test( 'getFullData', 1, function ( assert ) {
	var doc = ve.dm.example.createExampleDocument( 'withMeta' );
	assert.equalLinearDataWithDom( doc.getStore(), doc.getFullData(), ve.dm.example.withMeta );
} );

QUnit.test( 'cloneFromRange', function ( assert ) {
	var i, doc2, doc = ve.dm.example.createExampleDocument( 'internalData' ),
		cases = [
			{
				msg: 'first internal item',
				doc: 'internalData',
				range: new ve.Range( 7, 12 ),
				expectedData: doc.data.slice( 7, 12 ).concat( doc.data.slice( 5, 21 ) )
			},
			{
				msg: 'second internal item',
				doc: 'internalData',
				range: doc.getInternalList().getItemNode( 1 ).getRange(),
				expectedData: doc.data.slice( 14, 19 ).concat( doc.data.slice( 5, 21 ) )
			},
			{
				msg: 'paragraph at the start',
				doc: 'internalData',
				range: new ve.Range( 0, 5 ),
				expectedData: doc.data.slice( 0, 21 )
			},
			{
				msg: 'paragraph at the end',
				doc: 'internalData',
				range: new ve.Range( 21, 27 ),
				expectedData: doc.data.slice( 21, 27 ).concat( doc.data.slice( 5, 21 ) )
			}
		];
	QUnit.expect( 4 * cases.length );
	for ( i = 0; i < cases.length; i++ ) {
		doc = ve.dm.example.createExampleDocument( cases[ i ].doc );
		doc2 = doc.cloneFromRange( cases[ i ].range );
		assert.deepEqual( doc2.data.data, cases[ i ].expectedData,
			cases[ i ].msg + ': sliced data' );
		assert.notStrictEqual( doc2.data[ 0 ], cases[ i ].expectedData[ 0 ],
			cases[ i ].msg + ': data is cloned, not the same' );
		assert.deepEqual( doc2.store, doc.store,
			cases[ i ].msg + ': store is copied' );
		assert.notStrictEqual( doc2.store, doc.store,
			cases[ i ].msg + ': store is a clone, not the same' );
	}
} );

QUnit.test( 'getRelativeOffset', function ( assert ) {
	var i, j,
		expectCount = 0,
		documentModel = ve.dm.example.createExampleDocument( 'alienData' ),
		tests = [
			{
				direction: 1,
				unit: 'character',
				cases: [
					{ input: 0, output: 1 },
					{ input: 2, output: 3 },
					{ input: 3, output: 4 },
					{ input: 4, output: 5 },
					{ input: 6, output: 7 },
					{ input: 7, output: 9 },
					{ input: 10, output: 10 }
				]
			},
			{
				direction: 1,
				unit: 'word',
				cases: [
					{ input: 0, output: 1 },
					{ input: 2, output: 3 },
					{ input: 3, output: 4 },
					{ input: 4, output: 5 },
					{ input: 6, output: 7 },
					{ input: 7, output: 9 },
					{ input: 10, output: 10 }
				]
			},
			{
				direction: -1,
				unit: 'character',
				cases: [
					{ input: 10, output: 9 },
					{ input: 8, output: 7 },
					{ input: 7, output: 6 },
					{ input: 6, output: 5 },
					{ input: 4, output: 3 },
					{ input: 3, output: 1 },
					{ input: 0, output: 0 }
				]
			},
			{
				direction: -1,
				unit: 'word',
				cases: [
					{ input: 10, output: 9 },
					{ input: 8, output: 7 },
					{ input: 7, output: 6 },
					{ input: 6, output: 5 },
					{ input: 4, output: 3 },
					{ input: 3, output: 1 },
					{ input: 0, output: 0 }
				]
			}
		];
	for ( i = 0; i < tests.length; i++ ) {
		for ( j = 0; j < tests[ i ].cases.length; j++ ) {
			assert.strictEqual(
				documentModel.getRelativeOffset(
					tests[ i ].cases[ j ].input,
					tests[ i ].direction,
					tests[ i ].unit
				),
				tests[ i ].cases[ j ].output,
				tests[ i ].cases[ j ].input + ', ' + tests[ i ].direction + ', ' + tests[ i ].unit
			);
		}
		expectCount += tests[ i ].cases.length;
	}
	QUnit.expect( expectCount );
} );

QUnit.test( 'getRelativeRange', function ( assert ) {
	var documentModel, i, j,
		expectCount = 0,
		tests = [
			{
				data: [
					{ type: 'paragraph' }, // 0
					'a', // 1
					{ type: 'alienInline' }, // 2
					{ type: '/alienInline' }, // 3
					'b', // 4
					{ type: '/paragraph' } // 5
				],
				cases: [
					{
						direction: 1,
						given: new ve.Range( 1 ),
						expected: new ve.Range( 2 )
					},
					{
						direction: 1,
						given: new ve.Range( 2 ),
						expected: new ve.Range( 2, 4 )
					},
					{
						direction: 1,
						given: new ve.Range( 2, 4 ),
						expected: new ve.Range( 4 )
					},

					{
						direction: 1,
						expand: true,
						given: new ve.Range( 1 ),
						expected: new ve.Range( 1, 2 )
					},
					{
						direction: 1,
						expand: true,
						given: new ve.Range( 1, 2 ),
						expected: new ve.Range( 1, 4 )
					},
					{
						direction: 1,
						expand: true,
						given: new ve.Range( 1, 4 ),
						expected: new ve.Range( 1, 5 )
					}
				]
			},
			{
				data: [
					{ type: 'paragraph' }, // 0
					{ type: 'alienInline' }, // 1
					{ type: '/alienInline' }, // 2
					{ type: 'alienInline' }, // 3
					{ type: '/alienInline' }, // 4
					{ type: '/paragraph' } // 5
				],
				cases: [
					{
						direction: 1,
						given: new ve.Range( 3 ),
						expected: new ve.Range( 3, 5 )
					},
					{
						direction: 1,
						expand: true,
						given: new ve.Range( 1, 3 ),
						expected: new ve.Range( 1, 5 )
					},
					{
						direction: -1,
						expand: true,
						given: new ve.Range( 1, 5 ),
						expected: new ve.Range( 1, 3 )
					},
					{
						direction: 1,
						expand: true,
						given: new ve.Range( 5, 1 ),
						expected: new ve.Range( 5, 3 )
					}
				]
			},
			{
				data: ve.copy( ve.dm.example.alienData ),
				cases: [
					{
						direction: 1,
						given: new ve.Range( 0 ),
						expected: new ve.Range( 0, 2 )
					},
					{
						direction: 1,
						given: new ve.Range( 0, 2 ),
						expected: new ve.Range( 3 )
					},
					{
						direction: 1,
						given: new ve.Range( 3 ),
						expected: new ve.Range( 4 )
					},
					{
						direction: 1,
						given: new ve.Range( 4 ),
						expected: new ve.Range( 4, 6 )
					},
					{
						direction: 1,
						given: new ve.Range( 4, 6 ),
						expected: new ve.Range( 6 )
					},
					{
						direction: 1,
						given: new ve.Range( 6 ),
						expected: new ve.Range( 7 )
					},
					{
						direction: 1,
						given: new ve.Range( 7 ),
						expected: new ve.Range( 8, 10 )
					},
					{
						direction: 1,
						given: new ve.Range( 10 ),
						expected: new ve.Range( 10 )
					},
					{
						direction: -1,
						given: new ve.Range( 10 ),
						expected: new ve.Range( 10, 8 )
					},
					{
						direction: -1,
						given: new ve.Range( 10, 8 ),
						expected: new ve.Range( 7 )
					},
					{
						direction: -1,
						given: new ve.Range( 7 ),
						expected: new ve.Range( 6 )
					},
					{
						direction: -1,
						given: new ve.Range( 6 ),
						expected: new ve.Range( 6, 4 )
					},
					{
						direction: -1,
						given: new ve.Range( 6, 4 ),
						expected: new ve.Range( 4 )
					},
					{
						direction: -1,
						given: new ve.Range( 4 ),
						expected: new ve.Range( 3 )
					},
					{
						direction: -1,
						given: new ve.Range( 3 ),
						expected: new ve.Range( 2, 0 )
					},
					{
						direction: -1,
						given: new ve.Range( 2, 0 ),
						expected: new ve.Range( 0 )
					}
				]
			}
		];
	for ( i = 0; i < tests.length; i++ ) {
		documentModel = new ve.dm.Document( tests[ i ].data );
		for ( j = 0; j < tests[ i ].cases.length; j++ ) {
			expectCount++;
			assert.equalRange(
				documentModel.getRelativeRange(
					tests[ i ].cases[ j ].given,
					tests[ i ].cases[ j ].direction,
					'character',
					!!tests[ i ].cases[ j ].expand
				),
				tests[ i ].cases[ j ].expected,
				'Test document ' + i +
				', range ' + tests[ i ].cases[ j ].given.toJSON() +
				', direction ' + tests[ i ].cases[ j ].direction
			);
		}
	}
	QUnit.expect( expectCount );
} );

QUnit.test( 'getBranchNodeFromOffset', function ( assert ) {
	var i, j, node,
		doc = ve.dm.example.createExampleDocument(),
		root = doc.getDocumentNode().getRoot(),
		expected = [
			[], // 0 - document
			[ 0 ], // 1 - heading
			[ 0 ], // 2 - heading
			[ 0 ], // 3 - heading
			[ 0 ], // 4 - heading
			[], // 5 - document
			[ 1 ], // 6 - table
			[ 1, 0 ], // 7 - tableSection
			[ 1, 0, 0 ], // 7 - tableRow
			[ 1, 0, 0, 0 ], // 8 - tableCell
			[ 1, 0, 0, 0, 0 ], // 9 - paragraph
			[ 1, 0, 0, 0, 0 ], // 10 - paragraph
			[ 1, 0, 0, 0 ], // 11 - tableCell
			[ 1, 0, 0, 0, 1 ], // 12 - list
			[ 1, 0, 0, 0, 1, 0 ], // 13 - listItem
			[ 1, 0, 0, 0, 1, 0, 0 ], // 14 - paragraph
			[ 1, 0, 0, 0, 1, 0, 0 ], // 15 - paragraph
			[ 1, 0, 0, 0, 1, 0 ], // 16 - listItem
			[ 1, 0, 0, 0, 1, 0, 1 ], // 17 - list
			[ 1, 0, 0, 0, 1, 0, 1, 0 ], // 18 - listItem
			[ 1, 0, 0, 0, 1, 0, 1, 0, 0 ], // 19 - paragraph
			[ 1, 0, 0, 0, 1, 0, 1, 0, 0 ], // 20 - paragraph
			[ 1, 0, 0, 0, 1, 0, 1, 0 ], // 21 - listItem
			[ 1, 0, 0, 0, 1, 0, 1 ], // 22 - list
			[ 1, 0, 0, 0, 1, 0 ], // 23 - listItem
			[ 1, 0, 0, 0, 1 ], // 24 - list
			[ 1, 0, 0, 0 ], // 25 - tableCell
			[ 1, 0, 0, 0, 2 ], // 26 - list
			[ 1, 0, 0, 0, 2, 0 ], // 27 - listItem
			[ 1, 0, 0, 0, 2, 0, 0 ], // 28 - paragraph
			[ 1, 0, 0, 0, 2, 0, 0 ], // 29 - paragraph
			[ 1, 0, 0, 0, 2, 0 ], // 30 - listItem
			[ 1, 0, 0, 0, 2 ], // 31 - list
			[ 1, 0, 0, 0 ], // 32 - tableCell
			[ 1, 0, 0 ], // 33 - tableRow
			[ 1, 0 ], // 33 - tableSection
			[ 1 ], // 34 - table
			[], // 35- document
			[ 2 ], // 36 - preformatted
			[ 2 ], // 37 - preformatted
			[ 2 ], // 38 - preformatted
			[ 2 ], // 39 - preformatted
			[ 2 ], // 40 - preformatted
			[], // 41 - document
			[ 3 ], // 42 - definitionList
			[ 3, 0 ], // 43 - definitionListItem
			[ 3, 0, 0 ], // 44 - paragraph
			[ 3, 0, 0 ], // 45 - paragraph
			[ 3, 0 ], // 46 - definitionListItem
			[ 3 ], // 47 - definitionList
			[ 3, 1 ], // 48 - definitionListItem
			[ 3, 1, 0 ], // 49 - paragraph
			[ 3, 1, 0 ], // 50 - paragraph
			[ 3, 1 ], // 51 - definitionListItem
			[ 3 ], // 52 - definitionList
			[], // 53 - document
			[ 4 ], // 54 - paragraph
			[ 4 ], // 55 - paragraph
			[], // 56 - document
			[ 5 ], // 57 - paragraph
			[ 5 ], // 58 - paragraph
			[] // 59 - document
		];
	QUnit.expect( expected.length );
	for ( i = 0; i < expected.length; i++ ) {
		node = root;
		for ( j = 0; j < expected[ i ].length; j++ ) {
			node = node.children[ expected[ i ][ j ] ];
		}
		assert.ok( node === doc.getBranchNodeFromOffset( i ), 'reference at offset ' + i );
	}
} );

QUnit.test( 'hasSlugAtOffset', function ( assert ) {
	var i, l,
		expected = {
			0: true,
			10: true
		},
		doc = ve.dm.example.createExampleDocument( 'alienData' );

	QUnit.expect( doc.data.getLength() + 1 );

	for ( i = 0, l = doc.data.getLength(); i <= l; i++ ) {
		assert.strictEqual( doc.hasSlugAtOffset( i ), !!expected[ i ], 'hasSlugAtOffset ' + i + ' = ' + !!expected[ i ] );
	}

} );

QUnit.test( 'getDataFromNode', 3, function ( assert ) {
	var doc = ve.dm.example.createExampleDocument(),
		expectedData = ve.dm.example.preprocessAnnotations( ve.copy( ve.dm.example.data ) );
	assert.deepEqual(
		doc.getDataFromNode( doc.getDocumentNode().getChildren()[ 0 ] ),
		expectedData.slice( 1, 4 ),
		'branch with leaf children'
	);
	assert.deepEqual(
		doc.getDataFromNode( doc.getDocumentNode().getChildren()[ 1 ] ),
		expectedData.slice( 6, 36 ),
		'branch with branch children'
	);
	assert.deepEqual(
		doc.getDataFromNode( doc.getDocumentNode().getChildren()[ 2 ].getChildren()[ 1 ] ),
		[],
		'leaf without children'
	);
} );

QUnit.test( 'getOuterLength', 1, function ( assert ) {
	var doc = ve.dm.example.createExampleDocument();
	assert.strictEqual(
		doc.getDocumentNode().getOuterLength(),
		ve.dm.example.data.length,
		'document does not have elements around it'
	);
} );

QUnit.test( 'rebuildNodes', 2, function ( assert ) {
	var tree,
		doc = ve.dm.example.createExampleDocument(),
		documentNode = doc.getDocumentNode();
	// Rebuild table without changes
	doc.rebuildNodes( documentNode, 1, 1, 5, 32 );
	assert.equalNodeTree(
		documentNode,
		ve.dm.example.tree,
		'rebuild without changes'
	);

	// XXX: Create a new document node tree from the old one
	tree = new ve.dm.DocumentNode( ve.dm.example.tree.getChildren() );
	// Replace table with paragraph
	doc.data.batchSplice( 5, 32, [ { type: 'paragraph' }, 'a', 'b', 'c', { type: '/paragraph' } ] );
	tree.splice( 1, 1, new ve.dm.ParagraphNode(
		doc.data.getData( 5 ), [ new ve.dm.TextNode( 3 ) ]
	) );
	// Rebuild with changes
	doc.rebuildNodes( documentNode, 1, 1, 5, 5 );
	assert.equalNodeTree(
		documentNode,
		tree,
		'replace table with paragraph'
	);
} );

QUnit.test( 'selectNodes', function ( assert ) {
	var i, doc, expectedSelection,
		mainDoc = ve.dm.example.createExampleDocument(),
		cases = ve.dm.example.selectNodesCases;

	function resolveNode( item ) {
		var newItem = ve.extendObject( {}, item );
		newItem.node = ve.dm.example.lookupNode.apply(
			ve.dm.example, [ doc.getDocumentNode() ].concat( item.node )
		);
		return newItem;
	}

	QUnit.expect( cases.length );
	for ( i = 0; i < cases.length; i++ ) {
		doc = cases[ i ].doc ? ve.dm.example.createExampleDocument( cases[ i ].doc ) : mainDoc;
		expectedSelection = cases[ i ].expected.map( resolveNode );
		assert.equalNodeSelection(
			doc.selectNodes( cases[ i ].range, cases[ i ].mode ), expectedSelection, cases[ i ].msg
		);
	}
} );

QUnit.test( 'rangeInsideOneLeafNode', function ( assert ) {
	var i,
		doc = ve.dm.example.createExampleDocument(),
		cases = [
			{
				range: new ve.Range( 1, 4 ),
				result: true
			},
			{
				range: new ve.Range( 4, 1 ),
				result: true
			},
			{
				range: new ve.Range( 0, 5 ),
				result: false
			},
			{
				range: new ve.Range( 0, 4 ),
				result: false
			},
			{
				range: new ve.Range( 0 ),
				result: false
			},
			{
				range: new ve.Range( 5 ),
				result: false
			},
			{
				range: new ve.Range( 1 ),
				result: true
			},
			{
				range: new ve.Range( 5, 1 ),
				result: false
			},
			{
				range: new ve.Range( 4, 6 ),
				result: false
			}
		];

	QUnit.expect( cases.length );
	for ( i = 0; i < cases.length; i++ ) {
		assert.strictEqual(
			doc.rangeInsideOneLeafNode( cases[ i ].range ),
			cases[ i ].result,
			'Range ' + cases[ i ].range.from + ', ' + cases[ i ].range.to + ' ' +
			( cases[ i ].result ? 'is' : 'isn\'t' ) + ' inside one leaf node'
		);
	}
} );
QUnit.test( 'shallowCloneFromRange', function ( assert ) {
	var i, expectedData, slice, range, doc,
		cases = [
			{
				msg: 'empty range',
				range: new ve.Range( 2 ),
				expected: [
					{ type: 'paragraph', internal: { generated: 'empty' } },
					{ type: 'paragraph' }
				],
				originalRange: new ve.Range( 1 ),
				balancedRange: new ve.Range( 1 )
			},
			{
				msg: 'range with one character',
				range: new ve.Range( 2, 3 ),
				expected: [
					{ type: 'heading', attributes: { level: 1 }, internal: { generated: 'wrapper' } },
					[ 'b', [ ve.dm.example.bold ] ],
					{ type: '/heading' }
				],
				originalRange: new ve.Range( 1, 2 ),
				balancedRange: new ve.Range( 1, 2 )
			},
			{
				msg: 'range with two characters',
				range: new ve.Range( 2, 4 ),
				expected: [
					{ type: 'heading', attributes: { level: 1 }, internal: { generated: 'wrapper' } },
					[ 'b', [ ve.dm.example.bold ] ],
					[ 'c', [ ve.dm.example.italic ] ],
					{ type: '/heading' }
				],
				originalRange: new ve.Range( 1, 3 ),
				balancedRange: new ve.Range( 1, 3 )
			},
			{
				msg: 'range with two characters and a header closing',
				range: new ve.Range( 2, 5 ),
				expected: [
					{ type: 'heading', attributes: { level: 1 } },
					[ 'b', [ ve.dm.example.bold ] ],
					[ 'c', [ ve.dm.example.italic ] ],
					{ type: '/heading' }
				],
				originalRange: new ve.Range( 1, 4 )
			},
			{
				msg: 'range with one character, a header closing and a table opening',
				range: new ve.Range( 3, 6 ),
				expected: [
					{ type: 'heading', attributes: { level: 1 } },
					[ 'c', [ ve.dm.example.italic ] ],
					{ type: '/heading' },
					{ type: 'table' },
					{ type: '/table' }
				],
				originalRange: new ve.Range( 1, 4 )
			},
			{
				msg: 'range from a paragraph into a list',
				range: new ve.Range( 15, 21 ),
				expected: [
					{ type: 'paragraph' },
					'e',
					{ type: '/paragraph' },
					{ type: 'list', attributes: { style: 'bullet' } },
					{ type: 'listItem' },
					{ type: 'paragraph' },
					'f',
					{ type: '/paragraph' },
					{ type: '/listItem' },
					{ type: '/list' }
				],
				originalRange: new ve.Range( 1, 7 )
			},
			{
				msg: 'range from a paragraph inside a nested list into the next list',
				range: new ve.Range( 20, 27 ),
				expected: [
					{ type: 'list', attributes: { style: 'bullet' } },
					{ type: 'listItem' },
					{ type: 'list', attributes: { style: 'bullet' } },
					{ type: 'listItem' },
					{ type: 'paragraph' },
					'f',
					{ type: '/paragraph' },
					{ type: '/listItem' },
					{ type: '/list' },
					{ type: '/listItem' },
					{ type: '/list' },
					{ type: 'list', attributes: { style: 'number' } },
					{ type: '/list' }
				],
				originalRange: new ve.Range( 5, 12 )
			},
			{
				msg: 'range from a paragraph inside a nested list out of both lists',
				range: new ve.Range( 20, 26 ),
				expected: [
					{ type: 'list', attributes: { style: 'bullet' } },
					{ type: 'listItem' },
					{ type: 'list', attributes: { style: 'bullet' } },
					{ type: 'listItem' },
					{ type: 'paragraph' },
					'f',
					{ type: '/paragraph' },
					{ type: '/listItem' },
					{ type: '/list' },
					{ type: '/listItem' },
					{ type: '/list' }
				],
				originalRange: new ve.Range( 5, 11 )
			},
			{
				msg: 'range from a paragraph inside a nested list out of the outer listItem',
				range: new ve.Range( 20, 25 ),
				expected: [
					{ type: 'list', attributes: { style: 'bullet' } },
					{ type: 'listItem' },
					{ type: 'list', attributes: { style: 'bullet' } },
					{ type: 'listItem' },
					{ type: 'paragraph' },
					'f',
					{ type: '/paragraph' },
					{ type: '/listItem' },
					{ type: '/list' },
					{ type: '/listItem' },
					{ type: '/list' }
				],
				originalRange: new ve.Range( 5, 10 ),
				balancedRange: new ve.Range( 1, 10 )
			},
			{
				msg: 'table cell',
				range: new ve.Range( 8, 34 ),
				expected: [
					{ type: 'table' },
					{ type: 'tableSection', attributes: { style: 'body' } },
					{ type: 'tableRow' },
					{ type: 'tableCell', attributes: { style: 'data' } },
					{ type: 'paragraph' },
					'd',
					{ type: '/paragraph' },
					{ type: 'list', attributes: { style: 'bullet' } },
					{ type: 'listItem' },
					{ type: 'paragraph' },
					'e',
					{ type: '/paragraph' },
					{ type: 'list', attributes: { style: 'bullet' } },
					{ type: 'listItem' },
					{ type: 'paragraph' },
					'f',
					{ type: '/paragraph' },
					{ type: '/listItem' },
					{ type: '/list' },
					{ type: '/listItem' },
					{ type: '/list' },
					{ type: 'list', attributes: { style: 'number' } },
					{ type: 'listItem' },
					{ type: 'paragraph' },
					'g',
					{ type: '/paragraph' },
					{ type: '/listItem' },
					{ type: '/list' },
					{ type: '/tableCell' },
					{ type: '/tableRow' },
					{ type: '/tableSection' },
					{ type: '/table' }
				],
				originalRange: new ve.Range( 3, 29 ),
				balancedRange: new ve.Range( 3, 29 )
			},
			{
				doc: 'inlineAtEdges',
				msg: 'inline node at start',
				range: new ve.Range( 1, 3 ),
				expected: [
					{ type: 'paragraph', internal: { generated: 'wrapper' } },
					ve.dm.example.image.data,
					{ type: '/inlineImage' },
					{ type: '/paragraph' }
				],
				originalRange: new ve.Range( 1, 3 ),
				balancedRange: new ve.Range( 1, 3 )
			},
			{
				doc: 'inlineAtEdges',
				msg: 'inline node at end',
				range: new ve.Range( 6, 8 ),
				expected: [
					{ type: 'paragraph', internal: { generated: 'wrapper' } },
					{ type: 'alienInline', originalDomElements: $( '<foobar />' ).toArray() },
					{ type: '/alienInline' },
					{ type: '/paragraph' }
				],
				originalRange: new ve.Range( 1, 3 ),
				balancedRange: new ve.Range( 1, 3 )
			},
			{
				doc: 'inlineAtEdges',
				msg: 'inline node at start with text',
				range: new ve.Range( 1, 5 ),
				expected: [
					{ type: 'paragraph', internal: { generated: 'wrapper' } },
					ve.dm.example.image.data,
					{ type: '/inlineImage' },
					'F', 'o',
					{ type: '/paragraph' }
				],
				originalRange: new ve.Range( 1, 5 ),
				balancedRange: new ve.Range( 1, 5 )
			},
			{
				doc: 'inlineAtEdges',
				msg: 'inline node at end with text',
				range: new ve.Range( 4, 8 ),
				expected: [
					{ type: 'paragraph', internal: { generated: 'wrapper' } },
					'o', 'o',
					{ type: 'alienInline', originalDomElements: $( '<foobar />' ).toArray() },
					{ type: '/alienInline' },
					{ type: '/paragraph' }
				],
				originalRange: new ve.Range( 1, 5 ),
				balancedRange: new ve.Range( 1, 5 )
			}
		];
	QUnit.expect( 3 * cases.length );
	for ( i = 0; i < cases.length; i++ ) {
		doc = ve.dm.example.createExampleDocument( cases[ i ].doc );
		expectedData = ve.dm.example.preprocessAnnotations( cases[ i ].expected.slice(), doc.getStore() ).getData();
		range = new ve.Range( 0, cases[ i ].expected.length );
		expectedData = expectedData.concat( [
			{ type: 'internalList' },
			{ type: '/internalList' }
		] );
		slice = doc.shallowCloneFromRange( cases[ i ].range );
		assert.equalLinearDataWithDom(
			doc.getStore(),
			slice.getData(),
			expectedData,
			cases[ i ].msg + ': data'
		);
		assert.equalRange(
			slice.originalRange,
			cases[ i ].originalRange || range,
			cases[ i ].msg + ': original range'
		);
		assert.equalRange(
			slice.balancedRange,
			cases[ i ].balancedRange || range,
			cases[ i ].msg + ': balanced range'
		);
	}
} );

QUnit.test( 'protection against double application of transactions', 1, function ( assert ) {
	var testDocument = ve.dm.example.createExampleDocument(),
		tx = new ve.dm.Transaction();
	tx.pushRetain( 1 );
	tx.pushReplace( testDocument, 1, 0, [ 'H', 'e', 'l', 'l', 'o' ] );
	testDocument.commit( tx );
	assert.throws(
		function () {
			testDocument.commit( tx );
		},
		Error,
		'exception thrown when trying to commit an already-committed transaction'
	);
} );

QUnit.test( 'getNearestCursorOffset', function ( assert ) {
	var i, dir,
		doc = ve.dm.converter.getModelFromDom(
			ve.createDocumentFromHtml( ve.dm.example.html )
		),
		expected = {
			// 10 offsets per row
			'-1': [
				1, 1, 2, 3, 4, 4, 4, 4, 4, 4,
				10, 11, 11, 11, 11, 15, 16, 16, 16, 16,
				20, 21, 21, 21, 21, 21, 21, 21, 21, 29,
				30, 30, 30, 30, 30, 30, 30, 30, 38, 39,
				39, 41, 42, 42, 42, 42, 46, 47, 47, 47,
				47, 51, 52, 52, 52, 52, 56, 57, 57, 59,
				60, 60, 60
			],
			1: [
				1, 1, 2, 3, 4, 10, 10, 10, 10, 10,
				10, 11, 15, 15, 15, 15, 16, 20, 20, 20,
				20, 21, 29, 29, 29, 29, 29, 29, 29, 29,
				30, 38, 38, 38, 38, 38, 38, 38, 38, 39,
				41, 41, 42, 46, 46, 46, 46, 47, 51, 51,
				51, 51, 52, 56, 56, 56, 56, 57, 59, 59,
				60, 60, 60
			]
		};

	QUnit.expect( doc.data.getLength() * 2 );

	for ( dir = -1; dir <= 1; dir += 2 ) {
		for ( i = 0; i < doc.data.getLength(); i++ ) {
			assert.strictEqual(
				doc.getNearestCursorOffset( i, dir ),
				expected[ dir ][ i ],
				'Direction: ' + dir + ' Offset: ' + i
			);
		}
	}
} );

Zerion Mini Shell 1.0