%PDF- %PDF-
Direktori : /www/varak.net/wiki.varak.net/extensions/VisualEditor/lib/ve/tests/dm/ |
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 ); } } } );