%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.SurfaceFragment.test.js |
/*! * VisualEditor DataModel SurfaceFragment tests. * * @copyright 2011-2016 VisualEditor Team and others; see http://ve.mit-license.org */ QUnit.module( 've.dm.SurfaceFragment' ); /* Tests */ QUnit.test( 'constructor', 5, function ( assert ) { var fragment, doc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ); surface.setLinearSelection( new ve.Range( 1 ) ); fragment = new ve.dm.SurfaceFragment( surface ); // Default range and autoSelect assert.strictEqual( fragment.getSurface(), surface, 'surface reference is stored' ); assert.strictEqual( fragment.getDocument(), doc, 'document reference is stored' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 1 ), 'range is taken from surface' ); assert.strictEqual( fragment.willAutoSelect(), true, 'auto select by default' ); // AutoSelect fragment = new ve.dm.SurfaceFragment( surface, null, 'truthy' ); assert.strictEqual( fragment.willAutoSelect(), false, 'noAutoSelect values are boolean' ); } ); QUnit.test( 'update', 3, function ( assert ) { var doc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ), fragment1 = surface.getLinearFragment( new ve.Range( 55, 61 ) ), fragment2 = surface.getLinearFragment( new ve.Range( 55, 61 ) ), fragment3 = surface.getLinearFragment( new ve.Range( 55, 61 ) ); fragment1.wrapNodes( [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' } ] ); assert.equalRange( fragment2.getSelection().getRange(), new ve.Range( 55, 69 ), 'fragment range changes after wrapNodes' ); surface.undo(); assert.equalRange( fragment3.getSelection().getRange(), new ve.Range( 55, 61 ), 'fragment range restored after undo' ); fragment1 = surface.getLinearFragment( new ve.Range( 1 ) ); surface.breakpoint(); fragment1.insertContent( '01' ); surface.breakpoint(); fragment1 = fragment1.collapseToEnd(); fragment1.insertContent( '234' ); fragment2 = fragment1.clone(); surface.undo(); fragment1.insertContent( '5678' ); assert.equalRange( fragment2.getSelection().getRange(), new ve.Range( 3, 7 ), 'Range created during truncated undo point still translates correctly' ); } ); QUnit.test( 'getSelectedModels', 4, function ( assert ) { var doc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ); assert.deepEqual( surface.getLinearFragment( new ve.Range( 1, 3 ) ).getSelectedModels(), [], 'Empty' ); assert.deepEqual( surface.getLinearFragment( new ve.Range( 2, 3 ) ).getSelectedModels(), [ doc.data.store.value( ve.dm.example.boldIndex ) ], 'Bold annotation' ); assert.deepEqual( surface.getLinearFragment( new ve.Range( 1, 3 ) ).getSelectedModels( true ), [ doc.getDocumentNode().children[ 0 ].children[ 0 ], doc.data.store.value( ve.dm.example.boldIndex ) ], 'Bold annotation and text node' ); assert.deepEqual( surface.getLinearFragment( new ve.Range( 39, 41 ) ).getSelectedModels(), [ doc.getDocumentNode().children[ 2 ].children[ 1 ] ], 'Inline image node' ); } ); QUnit.test( 'getAnnotations', 4, function ( assert ) { var tableSelection, doc = ve.dm.example.createExampleDocument( 'annotatedTable' ), tableRange = new ve.Range( 0, 52 ), surface = new ve.dm.Surface( doc ); tableSelection = new ve.dm.TableSelection( doc, tableRange, 0, 0, 1, 0 ); assert.deepEqual( surface.getFragment( tableSelection ).getAnnotations().getIndexes(), [ ve.dm.example.boldIndex, ve.dm.example.strongIndex ], 'Comparable annotations: [B] ∩ [Strong] = [B,Strong] ' ); tableSelection = new ve.dm.TableSelection( doc, tableRange, 0, 0, 2, 0 ); assert.deepEqual( surface.getFragment( tableSelection ).getAnnotations().getIndexes(), [], 'Non-comparable annotations: [B] ∩ [Strong] ∩ [I] = [] ' ); tableSelection = new ve.dm.TableSelection( doc, tableRange, 0, 1, 1, 1 ); assert.deepEqual( surface.getFragment( tableSelection ).getAnnotations().getIndexes(), [ ve.dm.example.boldIndex, ve.dm.example.strongIndex ], 'Non-comparable in first cell: [B,I] ∩ [Strong] = [B,Strong]' ); tableSelection = new ve.dm.TableSelection( doc, tableRange, 0, 0, 2, 0 ); assert.deepEqual( surface.getFragment( tableSelection ).getAnnotations( true ).getIndexes(), [ ve.dm.example.boldIndex, ve.dm.example.strongIndex, ve.dm.example.italicIndex ], 'Get all annotations' ); } ); QUnit.test( 'hasAnnotations', 2, function ( assert ) { var doc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ); assert.strictEqual( surface.getLinearFragment( new ve.Range( 1, 2 ) ).hasAnnotations(), false, 'Plain text has none' ); assert.strictEqual( surface.getLinearFragment( new ve.Range( 2, 3 ) ).hasAnnotations(), true, 'Bold text has some' ); } ); QUnit.test( 'adjustLinearSelection', 4, function ( assert ) { var doc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ), fragment = surface.getLinearFragment( new ve.Range( 20, 21 ) ), adjustedFragment = fragment.adjustLinearSelection( -19, 35 ); assert.ok( fragment !== adjustedFragment, 'adjustLinearSelection produces a new fragment' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 20, 21 ), 'old fragment is not changed' ); assert.equalRange( adjustedFragment.getSelection().getRange(), new ve.Range( 1, 56 ), 'new range is used' ); adjustedFragment = fragment.adjustLinearSelection(); assert.deepEqual( adjustedFragment, fragment, 'fragment is clone if no parameters supplied' ); } ); QUnit.test( 'truncateLinearSelection', 4, function ( assert ) { var range = new ve.Range( 100, 200 ), doc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ), fragment = surface.getLinearFragment( range ); assert.equalRange( fragment.truncateLinearSelection( 50 ).getSelection().getRange(), new ve.Range( 100, 150 ), 'truncate 50' ); assert.equalRange( fragment.truncateLinearSelection( 150 ).getSelection().getRange(), range, 'truncate 150 does nothing' ); assert.equalRange( fragment.truncateLinearSelection( -50 ).getSelection().getRange(), new ve.Range( 150, 200 ), 'truncate -50' ); assert.equalRange( fragment.truncateLinearSelection( -150 ).getSelection().getRange(), range, 'truncate -150 does nothing' ); } ); QUnit.test( 'collapseToStart/End', 6, function ( assert ) { var doc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ), fragment = surface.getLinearFragment( new ve.Range( 20, 21 ) ), collapsedFragment = fragment.collapseToStart(); assert.ok( fragment !== collapsedFragment, 'collapseToStart produces a new fragment' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 20, 21 ), 'old fragment is not changed' ); assert.equalRange( collapsedFragment.getSelection().getRange(), new ve.Range( 20 ), 'new range is used' ); collapsedFragment = fragment.collapseToEnd(); assert.ok( fragment !== collapsedFragment, 'collapseToEnd produces a new fragment' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 20, 21 ), 'old fragment is not changed' ); assert.equalRange( collapsedFragment.getSelection().getRange(), new ve.Range( 21 ), 'range is at end when collapseToEnd is set' ); } ); QUnit.test( 'expandLinearSelection (annotation)', function ( assert ) { var i, fragment, doc = ve.dm.example.createExampleDocumentFromData( [ { type: 'paragraph' }, 'F', 'o', 'o', [ 'b', [ ve.dm.example.bold ] ], [ 'a', [ ve.dm.example.bold ] ], [ 'r', [ ve.dm.example.bold ] ], [ 'b', [ ve.dm.example.bold, ve.dm.example.italic ] ], [ 'a', [ ve.dm.example.bold, ve.dm.example.italic ] ], [ 'z', [ ve.dm.example.bold, ve.dm.example.italic ] ], { type: '/paragraph' }, { type: 'internalList' }, { type: '/internalList' } ] ), surface = new ve.dm.Surface( doc ), cases = [ { msg: 'expands to bold annotation', annotation: ve.dm.example.bold, range: new ve.Range( 5, 6 ), expected: new ve.Range( 4, 10 ) }, { msg: 'direction preserved', annotation: ve.dm.example.bold, range: new ve.Range( 6, 5 ), expected: new ve.Range( 10, 4 ) }, { msg: 'overlaps existing selection', annotation: ve.dm.example.bold, range: new ve.Range( 2, 7 ), expected: new ve.Range( 2, 10 ) }, { msg: 'no change when annotation not present', annotation: ve.dm.example.italic, range: new ve.Range( 5, 6 ), expected: new ve.Range( 5, 6 ) }, { msg: 'no change when no annotations present', annotation: ve.dm.example.bold, range: new ve.Range( 1, 2 ), expected: new ve.Range( 1, 2 ) }, { msg: 'matches nested annotation', annotation: ve.dm.example.italic, range: new ve.Range( 9, 10 ), expected: new ve.Range( 7, 10 ) } ]; QUnit.expect( cases.length ); for ( i = 0; i < cases.length; i++ ) { fragment = surface.getLinearFragment( cases[ i ].range ).expandLinearSelection( 'annotation', ve.dm.example.createAnnotation( cases[ i ].annotation ) ); assert.equalHash( fragment.getSelection().getRange(), cases[ i ].expected, cases[ i ].msg ); } } ); QUnit.test( 'expandLinearSelection (closest)', function ( assert ) { var i, fragment, surface, doc = ve.dm.example.createExampleDocument(), cases = [ { msg: 've.dm.BranchNode selects surrounding paragraph', range: new ve.Range( 1 ), type: ve.dm.BranchNode, expected: new ve.dm.LinearSelection( doc, new ve.Range( 0, 5 ) ) }, { msg: 've.dm.BranchNode selects surrounding paragraph in empty paragraph', doc: 'alienWithEmptyData', range: new ve.Range( 1 ), type: ve.dm.BranchNode, expected: new ve.dm.LinearSelection( doc, new ve.Range( 0, 2 ) ) }, { msg: 've.dm.BranchNode selects surrounding paragraph when entire paragrpah selected', range: new ve.Range( 1, 4 ), type: ve.dm.BranchNode, expected: new ve.dm.LinearSelection( doc, new ve.Range( 0, 5 ) ) }, { msg: 'invalid type results in null fragment', range: new ve.Range( 20, 21 ), type: function () {}, expected: new ve.dm.NullSelection( doc ) } ]; QUnit.expect( cases.length ); for ( i = 0; i < cases.length; i++ ) { surface = new ve.dm.Surface( ve.dm.example.createExampleDocument( cases[ i ].doc ) ); fragment = surface.getLinearFragment( cases[ i ].range ).expandLinearSelection( 'closest', cases[ i ].type ); assert.equalHash( fragment.getSelection(), cases[ i ].expected, cases[ i ].msg ); } } ); QUnit.test( 'expandLinearSelection (word)', 1, function ( assert ) { var i, doc, surface, fragment, newFragment, range, word, cases = [ { phrase: 'the quick brown fox', range: new ve.Range( 6, 13 ), expected: 'quick brown', msg: 'range starting and ending in latin words' }, { phrase: 'the quick brown fox', range: new ve.Range( 18, 12 ), expected: 'brown fox', msg: 'backwards range starting and ending in latin words' }, { phrase: 'the quick brown fox', range: new ve.Range( 7 ), expected: 'quick', msg: 'zero-length range' } ]; QUnit.expect( cases.length * 2 ); for ( i = 0; i < cases.length; i++ ) { doc = new ve.dm.Document( cases[ i ].phrase.split( '' ) ); surface = new ve.dm.Surface( doc ); fragment = surface.getLinearFragment( cases[ i ].range ); newFragment = fragment.expandLinearSelection( 'word' ); range = newFragment.getSelection().getRange(); word = cases[ i ].phrase.substring( range.start, range.end ); assert.strictEqual( word, cases[ i ].expected, cases[ i ].msg + ': text' ); assert.strictEqual( cases[ i ].range.isBackwards(), range.isBackwards(), cases[ i ].msg + ': range direction' ); } } ); QUnit.test( 'removeContent', 6, function ( assert ) { var doc = ve.dm.example.createExampleDocument(), originalDoc = ve.dm.example.createExampleDocument(), expectedDoc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ), fragment = surface.getLinearFragment( new ve.Range( 1, 56 ) ), expectedData = ve.copy( expectedDoc.data.slice( 0, 1 ) ) .concat( ve.copy( expectedDoc.data.slice( 4, 5 ) ) ) .concat( ve.copy( expectedDoc.data.slice( 55 ) ) ); fragment.removeContent(); assert.deepEqual( doc.getData(), expectedData, 'removing content drops fully covered nodes and strips partially covered ones' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 1, 3 ), 'removing content results in a fragment covering just remaining structure' ); surface.undo(); assert.deepEqual( doc.getData(), originalDoc.getData(), 'content restored after undo' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 1, 56 ), 'range restored after undo' ); fragment = surface.getLinearFragment( new ve.Range( 1, 4 ) ); fragment.removeContent(); assert.deepEqual( doc.getData( new ve.Range( 0, 2 ) ), [ { type: 'heading', attributes: { level: 1 } }, { type: '/heading' } ], 'removing content empties node' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 1 ), 'removing content collapses range' ); } ); ve.test.utils.runSurfaceFragmentDeleteTest = function ( assert, html, range, directionAfterRemove, expectedData, expectedRange, msg ) { var data, doc, surface, fragment; if ( html ) { doc = ve.dm.converter.getModelFromDom( ve.createDocumentFromHtml( html ) ); } else { doc = ve.dm.example.createExampleDocument(); } surface = new ve.dm.Surface( doc ); fragment = surface.getLinearFragment( range ); data = ve.copy( fragment.getDocument().getFullData() ); expectedData( data ); fragment.delete( directionAfterRemove ); assert.deepEqualWithDomElements( fragment.getDocument().getFullData(), data, msg + ': data' ); assert.equalRange( fragment.getSelection().getRange(), expectedRange, msg + ': range' ); }; QUnit.test( 'delete', function ( assert ) { var i, cases = [ { range: new ve.Range( 1, 4 ), directionAfterRemove: -1, expectedData: function ( data ) { data.splice( 1, 3 ); }, expectedRange: new ve.Range( 1 ), msg: 'Selection deleted by backspace' }, { range: new ve.Range( 1, 4 ), directionAfterRemove: 1, expectedData: function ( data ) { data.splice( 1, 3 ); }, expectedRange: new ve.Range( 1 ), msg: 'Selection deleted by delete' }, { range: new ve.Range( 39, 41 ), directionAfterRemove: 1, expectedData: function ( data ) { data.splice( 39, 2 ); }, expectedRange: new ve.Range( 39 ), msg: 'Focusable node deleted if selected first' }, { range: new ve.Range( 39, 41 ), expectedData: function ( data ) { data.splice( 39, 2 ); }, expectedRange: new ve.Range( 39 ), msg: 'Focusable node deleted by cut' }, { range: new ve.Range( 0, 63 ), directionAfterRemove: -1, expectedData: function ( data ) { data.splice( 0, 61, { type: 'paragraph' }, { type: '/paragraph' } ); }, expectedRange: new ve.Range( 1 ), msg: 'Backspace after select all spanning entire document creates empty paragraph' }, { html: '<div rel="ve:Alien">Foo</div><p>Bar</p>', range: new ve.Range( 0, 6 ), directionAfterRemove: -1, expectedData: function ( data ) { data.splice( 0, 7, { type: 'paragraph' }, { type: '/paragraph' } ); }, expectedRange: new ve.Range( 1 ), msg: 'Delete all when document starts with a focusable node' }, { html: '<div rel="ve:Alien">Foo</div><p>Bar</p><div rel="ve:Alien">Baz</div>', range: new ve.Range( 0, 9 ), directionAfterRemove: -1, expectedData: function ( data ) { data.splice( 0, 9, { type: 'paragraph' }, { type: '/paragraph' } ); }, expectedRange: new ve.Range( 1 ), msg: 'Delete all when document starts and ends with a focusable node' } ]; QUnit.expect( cases.length * 2 ); for ( i = 0; i < cases.length; i++ ) { ve.test.utils.runSurfaceFragmentDeleteTest( assert, cases[ i ].html, cases[ i ].range, cases[ i ].directionAfterRemove, cases[ i ].expectedData, cases[ i ].expectedRange, cases[ i ].msg ); } } ); QUnit.test( 'insertContent', 11, function ( assert ) { var doc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ), fragment = surface.getLinearFragment( new ve.Range( 3, 4 ) ); fragment.insertContent( [ 'a' ], true ); assert.deepEqual( doc.getData( new ve.Range( 3, 4 ) ), [ [ 'a', [ ve.dm.example.italicIndex ] ] ], 'inserting content (annotate=true) replaces selection with new annotated content' ); fragment = surface.getLinearFragment( new ve.Range( 3, 4 ) ); fragment.insertContent( [ 'b' ] ); assert.deepEqual( doc.getData( new ve.Range( 3, 4 ) ), [ 'b' ], 'inserting content (annotate=false) replaces selection with new plain content' ); fragment = surface.getLinearFragment( new ve.Range( 1, 4 ) ); fragment.insertContent( [ '1', '2', '3' ] ); assert.deepEqual( doc.getData( new ve.Range( 1, 4 ) ), [ '1', '2', '3' ], 'inserting content replaces selection with new content' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 1, 4 ), 'inserting content results in range around content' ); surface.breakpoint(); fragment = surface.getLinearFragment( new ve.Range( 4 ) ); fragment.insertContent( '321' ); assert.deepEqual( doc.getData( new ve.Range( 4, 7 ) ), [ '3', '2', '1' ], 'strings get converted into data when inserting content' ); surface.undo(); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 4 ), 'range restored after undo' ); fragment = surface.getLinearFragment( new ve.Range( 0 ) ); fragment.insertContent( 'foo\nbar' ); assert.deepEqual( doc.getData( new ve.Range( 0, 10 ) ), [ { type: 'paragraph' }, 'f', 'o', 'o', { type: '/paragraph' }, { type: 'paragraph' }, 'b', 'a', 'r', { type: '/paragraph' } ], 'newlines converted to paragraphs' ); fragment = surface.getLinearFragment( new ve.Range( 1 ) ); fragment.insertContent( [ { type: 'table' }, { type: '/table' } ] ); assert.deepEqual( doc.getData( new ve.Range( 0, 2 ) ), [ { type: 'table' }, { type: '/table' } ], 'table insertion at start of heading is moved outside of heading' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 0, 2 ), 'range covers inserted content in moved position (left)' ); // Set up document and surface from scratch doc = ve.dm.example.createExampleDocument(); surface = new ve.dm.Surface( doc ); fragment = surface.getLinearFragment( new ve.Range( 4 ) ); fragment.insertContent( [ { type: 'list' }, { type: '/list' } ] ); assert.deepEqual( doc.getData( new ve.Range( 5, 7 ) ), [ { type: 'list' }, { type: '/list' } ], 'list insertion at end of heading is moved outside of heading' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 5, 7 ), 'range covers inserted content in moved position (right)' ); } ); QUnit.test( 'changeAttributes', 1, function ( assert ) { var doc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ), fragment = surface.getLinearFragment( new ve.Range( 0, 5 ) ); fragment.changeAttributes( { level: 3 } ); assert.deepEqual( doc.getData( new ve.Range( 0, 1 ) ), [ { type: 'heading', attributes: { level: 3 } } ], 'changing attributes affects covered nodes' ); } ); QUnit.test( 'wrapNodes/unwrapNodes', 10, function ( assert ) { var doc = ve.dm.example.createExampleDocument(), originalDoc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ), fragment = surface.getLinearFragment( new ve.Range( 55, 61 ) ); // Make 2 paragraphs into 2 lists of 1 item each fragment.wrapNodes( [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' } ] ); assert.deepEqual( doc.getData( new ve.Range( 55, 69 ) ), [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' }, { type: 'paragraph' }, 'l', { type: '/paragraph' }, { type: '/listItem' }, { type: '/list' }, { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' }, { type: 'paragraph' }, 'm', { type: '/paragraph' }, { type: '/listItem' }, { type: '/list' } ], 'wrapping nodes can add multiple levels of wrapping to multiple elements' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 55, 69 ), 'new range contains wrapping elements' ); fragment.unwrapNodes( 0, 2 ); assert.deepEqual( doc.getData(), originalDoc.getData(), 'unwrapping 2 levels restores document to original state' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 55, 61 ), 'range after unwrapping is same as original range' ); // Make a 1 paragraph into 1 list with 1 item fragment = surface.getLinearFragment( new ve.Range( 9, 12 ) ); fragment.wrapNodes( [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' } ] ); assert.deepEqual( doc.getData( new ve.Range( 9, 16 ) ), [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' }, { type: 'paragraph' }, 'd', { type: '/paragraph' }, { type: '/listItem' }, { type: '/list' } ], 'wrapping nodes can add multiple levels of wrapping to a single element' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 9, 16 ), 'new range contains wrapping elements' ); fragment.unwrapNodes( 0, 2 ); assert.deepEqual( doc.getData(), originalDoc.getData(), 'unwrapping 2 levels restores document to original state' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 9, 12 ), 'range after unwrapping is same as original range' ); fragment = surface.getLinearFragment( new ve.Range( 8, 34 ) ); fragment.unwrapNodes( 3, 1 ); assert.deepEqual( fragment.getData(), doc.getData( new ve.Range( 5, 29 ) ), 'unwrapping multiple outer nodes and an inner node' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 5, 29 ), 'new range contains inner elements' ); } ); QUnit.test( 'rewrapNodes', 4, function ( assert ) { var doc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ), fragment = surface.getLinearFragment( new ve.Range( 43, 55 ) ), expectedDoc = ve.dm.example.createExampleDocument(), expectedSurface = new ve.dm.Surface( expectedDoc ), expectedFragment = expectedSurface.getLinearFragment( new ve.Range( 43, 55 ) ), expectedData; // set up wrapped nodes in example document fragment.wrapNodes( [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' } ] ); expectedFragment.wrapNodes( [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' } ] ); // range is now 43, 59 // Compare a rewrap operation with its equivalent unwrap + wrap // This type of test can only exist if the intermediate state is valid fragment.rewrapNodes( 2, [ { type: 'definitionList' }, { type: 'definitionListItem', attributes: { style: 'term' } } ] ); expectedFragment.unwrapNodes( 0, 2 ); expectedFragment.wrapNodes( [ { type: 'definitionList' }, { type: 'definitionListItem', attributes: { style: 'term' } } ] ); assert.deepEqual( doc.getData(), expectedDoc.getData(), 'rewrapping multiple nodes via a valid intermediate state produces the same document as unwrapping then wrapping' ); assert.equalHash( fragment.getSelection(), expectedFragment.getSelection(), 'new range contains rewrapping elements' ); // Rewrap paragrphs as headings // The intermediate stage (plain text attached to the document) would be invalid // if performed as an unwrap and a wrap expectedData = ve.copy( doc.getData() ); fragment = surface.getLinearFragment( new ve.Range( 59, 65 ) ); fragment.rewrapNodes( 1, [ { type: 'heading', attributes: { level: 1 } } ] ); expectedData.splice( 59, 1, { type: 'heading', attributes: { level: 1 } } ); expectedData.splice( 61, 1, { type: '/heading' } ); expectedData.splice( 62, 1, { type: 'heading', attributes: { level: 1 } } ); expectedData.splice( 64, 1, { type: '/heading' } ); assert.deepEqual( doc.getData(), expectedData, 'rewrapping paragraphs as headings' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 59, 65 ), 'new range contains rewrapping elements' ); } ); QUnit.test( 'wrapAllNodes', 10, function ( assert ) { var doc = ve.dm.example.createExampleDocument(), originalDoc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ), fragment = surface.getLinearFragment( new ve.Range( 55, 61 ) ), expectedData = ve.copy( doc.getData() ); // Make 2 paragraphs into 1 lists of 1 item with 2 paragraphs fragment.wrapAllNodes( [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' } ] ); assert.deepEqual( doc.getData( new ve.Range( 55, 65 ) ), [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' }, { type: 'paragraph' }, 'l', { type: '/paragraph' }, { type: 'paragraph' }, 'm', { type: '/paragraph' }, { type: '/listItem' }, { type: '/list' } ], 'wrapping nodes can add multiple levels of wrapping to multiple elements' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 55, 65 ), 'new range contains wrapping elements' ); fragment.unwrapNodes( 0, 2 ); assert.deepEqual( doc.getData(), originalDoc.getData(), 'unwrapping 2 levels restores document to original state' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 55, 61 ), 'range after unwrapping is same as original range' ); // Make a 1 paragraph into 1 list with 1 item fragment = surface.getLinearFragment( new ve.Range( 9, 12 ) ); fragment.wrapAllNodes( [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' } ] ); assert.deepEqual( doc.getData( new ve.Range( 9, 16 ) ), [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' }, { type: 'paragraph' }, 'd', { type: '/paragraph' }, { type: '/listItem' }, { type: '/list' } ], 'wrapping nodes can add multiple levels of wrapping to a single element' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 9, 16 ), 'new range contains wrapping elements' ); fragment.unwrapNodes( 0, 2 ); assert.deepEqual( doc.getData(), originalDoc.getData(), 'unwrapping 2 levels restores document to original state' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 9, 12 ), 'range after unwrapping is same as original range' ); fragment = surface.getLinearFragment( new ve.Range( 5, 37 ) ); assert.throws( function () { fragment.unwrapNodes( 0, 20 ); }, /cannot unwrap by greater depth/, 'error thrown trying to unwrap more nodes that it is possible to contain' ); expectedData.splice( 5, 4 ); expectedData.splice( 29, 4 ); fragment.unwrapNodes( 0, 4 ); assert.deepEqual( doc.getData(), expectedData, 'unwrapping 4 levels (table, tableSection, tableRow and tableCell)' ); } ); QUnit.test( 'rewrapAllNodes', 6, function ( assert ) { var expectedData, doc = ve.dm.example.createExampleDocument(), originalDoc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ), fragment = surface.getLinearFragment( new ve.Range( 5, 37 ) ), expectedDoc = ve.dm.example.createExampleDocument(), expectedSurface = new ve.dm.Surface( expectedDoc ), expectedFragment = expectedSurface.getLinearFragment( new ve.Range( 5, 37 ) ); // Compare a rewrap operation with its equivalent unwrap + wrap // This type of test can only exist if the intermediate state is valid fragment.rewrapAllNodes( 4, [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' } ] ); expectedFragment.unwrapNodes( 0, 4 ); expectedFragment.wrapAllNodes( [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' } ] ); assert.deepEqual( doc.getData(), expectedDoc.getData(), 'rewrapping multiple nodes via a valid intermediate state produces the same document as unwrapping then wrapping' ); assert.equalHash( fragment.getSelection(), expectedFragment.getSelection(), 'new range contains rewrapping elements' ); // Reverse of first test fragment.rewrapAllNodes( 2, [ { type: 'table' }, { type: 'tableSection', attributes: { style: 'body' } }, { type: 'tableRow' }, { type: 'tableCell', attributes: { style: 'data' } } ] ); expectedData = originalDoc.getData(); assert.deepEqual( doc.getData(), expectedData, 'rewrapping multiple nodes via a valid intermediate state produces the same document as unwrapping then wrapping' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 5, 37 ), 'new range contains rewrapping elements' ); // Rewrap a heading as a paragraph // The intermediate stage (plain text attached to the document) would be invalid // if performed as an unwrap and a wrap fragment = surface.getLinearFragment( new ve.Range( 0, 5 ) ); fragment.rewrapAllNodes( 1, [ { type: 'paragraph' } ] ); expectedData.splice( 0, 1, { type: 'paragraph' } ); expectedData.splice( 4, 1, { type: '/paragraph' } ); assert.deepEqual( doc.getData(), expectedData, 'rewrapping a heading as a paragraph' ); assert.equalRange( fragment.getSelection().getRange(), new ve.Range( 0, 5 ), 'new range contains rewrapping elements' ); } ); QUnit.test( 'isolateAndUnwrap', 1, function ( assert ) { ve.test.utils.runIsolateTest( assert, 'heading', new ve.Range( 12, 20 ), function ( data ) { data.splice( 11, 0, { type: 'listItem' } ); data.splice( 12, 1 ); data.splice( 20, 1, { type: '/listItem' } ); }, 'isolating paragraph in list item "Item 2" for heading' ); } );