Файловый менеджер - Редактировать - /var/www/html/mediawiki-1.43.1/extensions/VisualEditor/lib/ve/tests/dm/ve.dm.Document.test.js
Ðазад
/*! * VisualEditor DataModel Document tests. * * @copyright See AUTHORS.txt */ QUnit.module( 've.dm.Document' ); /* Tests */ QUnit.test( 'constructor', ( assert ) => { let doc = ve.dm.example.createExampleDocument(); assert.equalNodeTree( doc.getDocumentNode(), ve.dm.example.tree, 'node tree matches example data' ); assert.throws( () => { doc = new ve.dm.Document( [ { type: '/paragraph' }, { type: 'paragraph' } ] ); doc.buildNodeTree(); }, Error, 'unbalanced input causes exception' ); assert.throws( () => { doc = new ve.dm.Document( [ { type: 'paragraph' }, { type: 'inlineImage' }, { type: '/paragraph' } ] ); doc.buildNodeTree(); }, Error, 'unclosed inline node causes exception' ); assert.throws( () => { doc = new ve.dm.Document( [ { type: 'div' }, { type: 'paragraph' }, 'a', { type: '/div' }, { type: '/paragraph' } ] ); doc.buildNodeTree(); }, Error, 'mismatching close tags cause exception' ); assert.throws( () => { doc = new ve.dm.Document( [ { type: 'div' }, { type: 'paragraph' }, 'a', { type: '/paragraph' } ] ); doc.buildNodeTree(); }, Error, 'unclosed open tag causes exception' ); doc = new ve.dm.Document( [ ...'abcd' ] ); assert.equalNodeTree( doc.getDocumentNode(), new ve.dm.DocumentNode( [ new ve.dm.TextNode( 4 ) ] ), 'plain text input is handled correctly' ); assert.strictEqual( doc.getMetadata().join( ',' ), ',,,', 'Empty metadata array' ); assert.strictEqual( doc.getHtmlDocument().body.innerHTML, '', 'Empty HTML document is created' ); const htmlDoc = ve.createDocumentFromHtml( 'abcd' ); doc = new ve.dm.Document( [ ...'abcd' ], htmlDoc ); assert.strictEqual( doc.getHtmlDocument(), htmlDoc, 'Provided HTML document is used' ); const data = new ve.dm.ElementLinearData( new ve.dm.HashValueStore(), [ { 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' ); } ); QUnit.test( 'newBlankDocument', ( assert ) => { let doc = ve.dm.Document.static.newBlankDocument(); assert.true( doc instanceof ve.dm.Document, 'newBlankDocument creates a document' ); assert.strictEqual( doc.data.data[ 0 ].internal.generated, 'empty', 'Creates an "empty" paragraph with no args' ); doc = ve.dm.Document.static.newBlankDocument( 'wrapper' ); assert.strictEqual( doc.data.data[ 0 ].internal.generated, 'wrapper', 'Creates an "wrapper" paragraph when requested' ); doc = ve.dm.Document.static.newBlankDocument( null ); assert.false( 'internal' in doc.data.data[ 0 ], 'Creates an regular paragraph with null' ); } ); QUnit.test( 'getData', ( assert ) => { const 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', ( assert ) => { const doc = ve.dm.example.createExampleDocument( 'withMeta' ); assert.equalLinearDataWithDom( doc.getStore(), doc.getFullData(), ve.dm.example.withMeta ); } ); QUnit.test( 'cloneFromRange', ( assert ) => { const doc = ve.dm.example.createExampleDocument( 'internalData' ), cases = [ { msg: 'range undefined', doc: 'internalData', range: undefined, expectedData: doc.data.slice() }, { msg: 'first internal item', doc: 'internalData', range: new ve.Range( 7, 12 ), expectedData: [ ...doc.data.slice( 7, 12 ), ...doc.data.slice( 5, 21 ) ] }, { msg: 'second internal item', doc: 'internalData', range: doc.getInternalList().getItemNode( 1 ).getRange(), expectedData: [ ...doc.data.slice( 14, 19 ), ...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 ), ...doc.data.slice( 5, 21 ) ] } ]; cases.forEach( ( caseItem ) => { const doc1 = ve.dm.example.createExampleDocument( caseItem.doc ), doc2 = doc1.cloneFromRange( caseItem.range ); assert.deepEqual( doc2.data.data, caseItem.expectedData, caseItem.msg + ': sliced data' ); assert.notStrictEqual( doc2.data[ 0 ], caseItem.expectedData[ 0 ], caseItem.msg + ': data is cloned, not the same' ); assert.deepEqual( doc2.store, doc1.store, caseItem.msg + ': store is copied' ); assert.notStrictEqual( doc2.store, doc1.store, caseItem.msg + ': store is a clone, not the same' ); assert.strictEqual( doc2.getStorage(), doc1.getStorage(), caseItem.msg + ': persistentStorage is copied' ); } ); } ); QUnit.test( 'getRelativeOffset', ( assert ) => { const 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 } ] } ]; tests.forEach( ( testItem ) => { testItem.cases.forEach( ( caseItem ) => { assert.strictEqual( documentModel.getRelativeOffset( caseItem.input, testItem.direction, testItem.unit ), caseItem.output, caseItem.input + ', ' + testItem.direction + ', ' + testItem.unit ); } ); } ); } ); QUnit.test( 'getRelativeRange', ( assert ) => { const 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 ) } ] } ]; tests.forEach( ( testItem, i ) => { const documentModel = new ve.dm.Document( testItem.data ); testItem.cases.forEach( ( caseItem ) => { assert.equalRange( documentModel.getRelativeRange( caseItem.given, caseItem.direction, 'character', !!caseItem.expand ), caseItem.expected, 'Test document ' + i + ', range ' + caseItem.given.toJSON() + ', direction ' + caseItem.direction ); } ); } ); } ); QUnit.test( 'getBranchNodeFromOffset', ( assert ) => { const 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 ], // 8 - tableRow [ 1, 0, 0, 0 ], // 9 - tableCell [ 1, 0, 0, 0, 0 ], // 10 - paragraph [ 1, 0, 0, 0, 0 ], // 11 - paragraph [ 1, 0, 0, 0 ], // 12 - tableCell [ 1, 0, 0, 0, 1 ], // 13 - list [ 1, 0, 0, 0, 1, 0 ], // 14 - listItem [ 1, 0, 0, 0, 1, 0, 0 ], // 15 - paragraph [ 1, 0, 0, 0, 1, 0, 0 ], // 16 - paragraph [ 1, 0, 0, 0, 1, 0 ], // 17 - listItem [ 1, 0, 0, 0, 1, 0, 1 ], // 18 - list [ 1, 0, 0, 0, 1, 0, 1, 0 ], // 19 - listItem [ 1, 0, 0, 0, 1, 0, 1, 0, 0 ], // 20 - paragraph [ 1, 0, 0, 0, 1, 0, 1, 0, 0 ], // 21 - paragraph [ 1, 0, 0, 0, 1, 0, 1, 0 ], // 22 - listItem [ 1, 0, 0, 0, 1, 0, 1 ], // 23 - list [ 1, 0, 0, 0, 1, 0 ], // 24 - listItem [ 1, 0, 0, 0, 1 ], // 25 - list [ 1, 0, 0, 0 ], // 26 - tableCell [ 1, 0, 0, 0, 2 ], // 27 - list [ 1, 0, 0, 0, 2, 0 ], // 28 - listItem [ 1, 0, 0, 0, 2, 0, 0 ], // 29 - paragraph [ 1, 0, 0, 0, 2, 0, 0 ], // 30 - paragraph [ 1, 0, 0, 0, 2, 0 ], // 31 - listItem [ 1, 0, 0, 0, 2 ], // 32 - list [ 1, 0, 0, 0 ], // 33 - tableCell [ 1, 0, 0 ], // 34 - tableRow [ 1, 0 ], // 35 - tableSection [ 1 ], // 36 - table [], // 37- document [ 2 ], // 38 - preformatted [ 2 ], // 39 - preformatted [ 2 ], // 40 - preformatted [ 2 ], // 41 - preformatted [ 2 ], // 42 - preformatted [], // 43 - document [ 3 ], // 44 - definitionList [ 3, 0 ], // 45 - definitionListItem [ 3, 0, 0 ], // 46 - paragraph [ 3, 0, 0 ], // 47 - paragraph [ 3, 0 ], // 48 - definitionListItem [ 3 ], // 49 - definitionList [ 3, 1 ], // 50 - definitionListItem [ 3, 1, 0 ], // 51 - paragraph [ 3, 1, 0 ], // 52 - paragraph [ 3, 1 ], // 53 - definitionListItem [ 3 ], // 54 - definitionList [], // 55 - document [ 4 ], // 56 - paragraph [ 4 ], // 57 - paragraph [], // 58 - document [ 5 ], // 59 - paragraph [ 5 ], // 60 - paragraph [] // 61 - document ]; expected.forEach( ( expectedItem, i ) => { let node = root; expectedItem.forEach( ( offset ) => { node = node.children[ offset ]; } ); assert.strictEqual( node, doc.getBranchNodeFromOffset( i ), 'reference at offset ' + i ); } ); } ); QUnit.test( 'hasSlugAtOffset', ( assert ) => { const expected = { 0: true, 10: true }, doc = ve.dm.example.createExampleDocument( 'alienData' ); for ( let i = 0, l = doc.data.getLength(); i <= l; i++ ) { assert.strictEqual( doc.hasSlugAtOffset( i ), !!expected[ i ], 'hasSlugAtOffset ' + i + ' = ' + !!expected[ i ] ); } } ); QUnit.test( 'getDataFromNode', ( assert ) => { const 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', ( assert ) => { const doc = ve.dm.example.createExampleDocument(); assert.strictEqual( doc.getDocumentNode().getOuterLength(), ve.dm.example.data.length, 'document does not have elements around it' ); } ); QUnit.test( 'selectNodes', ( assert ) => { const mainDoc = ve.dm.example.createExampleDocument(), cases = ve.dm.example.selectNodesCases; function resolveNode( item, doc ) { const newItem = ve.extendObject( {}, item ); newItem.node = ve.dm.example.lookupNode( doc.getDocumentNode(), ...item.node ); return newItem; } cases.forEach( ( caseItem ) => { const doc = caseItem.doc ? ve.dm.example.createExampleDocument( caseItem.doc ) : mainDoc; const expectedSelection = caseItem.expected.map( ( item ) => resolveNode( item, doc ) ); assert.equalNodeSelection( doc.selectNodes( caseItem.range, caseItem.mode ), expectedSelection, caseItem.msg ); } ); } ); QUnit.test( 'rangeInsideOneLeafNode', ( assert ) => { const 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 } ]; cases.forEach( ( caseItem ) => { assert.strictEqual( doc.rangeInsideOneLeafNode( caseItem.range ), caseItem.result, 'Range ' + caseItem.range.from + ', ' + caseItem.range.to + ' ' + ( caseItem.result ? 'is' : 'isn\'t' ) + ' inside one leaf node' ); } ); } ); QUnit.test( 'shallowCloneFromRange', ( assert ) => { const cases = [ { msg: 'no range', range: undefined, expected: ve.copy( ve.dm.example.data.slice( 0, -2 ) ) }, { msg: 'collapsed range', range: new ve.Range( 2 ), expected: [ { type: 'paragraph', internal: { generated: 'empty' } }, { type: '/paragraph' } ], originalRange: new ve.Range( 1 ), balancedRange: new ve.Range( 1 ) }, { doc: 'alienWithEmptyData', msg: 'collapsed range in empty paragraph', range: new ve.Range( 1 ), expected: [ { type: 'paragraph', internal: { generated: 'empty' } }, { type: '/paragraph' } ], originalRange: new ve.Range( 1 ), balancedRange: new ve.Range( 1 ) }, { doc: 'alienData', msg: 'range ending inside alienBlock node', range: new ve.Range( 7, 9 ), expected: [ { type: 'paragraph' }, { type: '/paragraph' }, { type: 'alienBlock', originalDomElements: $.parseHTML( '<foobar />' ) }, { type: '/alienBlock' } ], originalRange: new ve.Range( 1, 3 ), balancedRange: new ve.Range( 0, 4 ) }, { 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: $.parseHTML( '<foobar />' ) }, { 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' }, ...'Fo', { 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' } }, ...'oo', { type: 'alienInline', originalDomElements: $.parseHTML( '<foobar />' ) }, { type: '/alienInline' }, { type: '/paragraph' } ], originalRange: new ve.Range( 1, 5 ), balancedRange: new ve.Range( 1, 5 ) } ]; cases.forEach( ( caseItem ) => { const doc = ve.dm.example.createExampleDocument( caseItem.doc ), expectedData = ve.dm.example.preprocessAnnotations( caseItem.expected.slice(), doc.getStore() ).getData().concat( [ { type: 'internalList' }, { type: '/internalList' } ] ), range = new ve.Range( 0, caseItem.expected.length ); const slice = doc.shallowCloneFromRange( caseItem.range ); assert.equalLinearDataWithDom( doc.getStore(), slice.getData(), expectedData, caseItem.msg + ': data' ); assert.equalRange( slice.originalRange, caseItem.originalRange || range, caseItem.msg + ': original range' ); assert.equalRange( slice.balancedRange, caseItem.balancedRange || range, caseItem.msg + ': balanced range' ); } ); } ); QUnit.test( 'protection against double application of transactions', ( assert ) => { const testDocument = ve.dm.example.createExampleDocument(), txBuilder = new ve.dm.TransactionBuilder(); txBuilder.pushRetain( 1 ); txBuilder.pushReplacement( testDocument, 1, 0, [ ...'Hello' ] ); testDocument.commit( txBuilder.getTransaction() ); assert.throws( () => { testDocument.commit( txBuilder.getTransaction() ); }, Error, 'exception thrown when trying to commit an already-committed transaction' ); } ); QUnit.test( 'getNearestCursorOffset', ( assert ) => { const cases = [ { name: 'example', doc: ve.dm.example.createExampleDocument(), 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 ] } }, { name: 'figcaption', doc: ve.dm.example.createExampleDocument( 'figcaption' ), expected: { // 10 offsets per row '-1': [ 1, 1, 2, 2, 2, 2, 6, 7, 7, 7, 7, 11, 12, 12, 12 ], 1: [ 1, 1, 2, 6, 6, 6, 6, 7, 11, 11, 11, 11, 12, 12, 12 ] } }, { name: 'internalData', doc: ve.dm.example.createExampleDocument( 'internalData' ), expected: { // TODO: Better define behaviour around ignoreChildren node // boundaries so the offsets are less erratic here. // 10 offsets per row '-1': [ 1, 1, 2, 3, 4, 4, 4, -1, 8, 9, 10, 11, 11, 4, -1, 15, 16, 17, 18, 18, 4, 4, 22, 23, 24, 25, 26 ], 1: [ 1, 1, 2, 3, 4, 4, 4, 8, 8, 9, 10, 11, -1, 4, 15, 15, 16, 17, 18, -1, 4, 22, 22, 23, 24, 25, 26 ] } } ]; cases.forEach( ( caseItem ) => { for ( let dir = -1; dir <= 1; dir += 2 ) { for ( let i = 0; i < caseItem.doc.data.getLength(); i++ ) { assert.strictEqual( caseItem.doc.getNearestCursorOffset( i, dir ), caseItem.expected[ dir ][ i ], 'Document "' + caseItem.name + '" - Direction: ' + dir + ' Offset: ' + i ); } } } ); } ); QUnit.test( 'Selection equality', ( assert ) => { const selections = [ new ve.dm.LinearSelection( new ve.Range( 1, 1 ) ), new ve.dm.LinearSelection( new ve.Range( 1, 3 ) ), new ve.dm.LinearSelection( new ve.Range( 3, 1 ) ), new ve.dm.TableSelection( new ve.Range( 4, 25 ), 0, 1, 2, 2 ), new ve.dm.NullSelection(), undefined, null, 'foo' ]; selections.forEach( ( iSel, i ) => { if ( !( iSel instanceof ve.dm.Selection ) ) { return; } selections.forEach( ( jSel, j ) => { assert.strictEqual( iSel.equals( jSel ), i === j, 'Selections ' + i + ' and ' + j ); } ); } ); } ); QUnit.test( 'findText (plain text)', ( assert ) => { const doc = ve.dm.converter.getModelFromDom( ve.createDocumentFromHtml( // 0 '<p>Foo bar fooq.</p>' + // 15 '<p>baz\nfoob</p>' + // 25 '<p>Liberté, Égalité, Fraternité.</p>' + // 56 '<p>ééé</p>' + // 61 '<p>Erdős, Malmö</p>' + // 75 '<p>İzlanda</p>' ) ), cases = [ { msg: 'Simple case insensitive', query: 'Foo', options: { noOverlaps: true }, ranges: [ new ve.Range( 1, 4 ), new ve.Range( 9, 12 ), new ve.Range( 20, 23 ) ] }, { msg: 'Simple case sensitive', query: 'Foo', options: { noOverlaps: true, caseSensitiveString: true }, matchCase: true, ranges: [ new ve.Range( 1, 4 ) ] }, { msg: 'Case insensitive regex', query: /fo[^ ]+/gi, options: { noOverlaps: true }, ranges: [ new ve.Range( 1, 4 ), new ve.Range( 9, 14 ), new ve.Range( 20, 24 ) ] }, { msg: 'Case sensitive regex', query: /fo[^ ]+/g, options: { noOverlaps: true }, ranges: [ new ve.Range( 9, 14 ), new ve.Range( 20, 24 ) ] }, { msg: 'Regex to end of line', query: /q.*/g, options: { noOverlaps: true }, ranges: [ new ve.Range( 12, 14 ) ] }, { msg: 'Overlapping regex', query: /[\s\S]*/g, options: { noOverlaps: true }, ranges: [ new ve.Range( 1, 14 ), new ve.Range( 16, 24 ), new ve.Range( 26, 55 ), new ve.Range( 57, 60 ), new ve.Range( 62, 74 ), new ve.Range( 76, 83 ) ] }, { msg: 'Literal newline', query: /\n/g, options: {}, ranges: [ new ve.Range( 19, 20 ) ] }, { msg: 'Anchor "^" matches at start of line, but does not match after literal newlines', query: /^./g, options: {}, ranges: [ new ve.Range( 1, 2 ), new ve.Range( 16, 17 ), new ve.Range( 26, 27 ), new ve.Range( 57, 58 ), new ve.Range( 62, 63 ), new ve.Range( 76, 77 ) ] }, { msg: 'Anchor "$" matches at end of line, but does not match before literal newlines', query: /.$/g, options: {}, ranges: [ new ve.Range( 13, 14 ), new ve.Range( 23, 24 ), new ve.Range( 54, 55 ), new ve.Range( 59, 60 ), new ve.Range( 73, 74 ), new ve.Range( 82, 83 ) ] }, { msg: 'Character which changes length after toLowerCase (İ) doesn\'t break later offsets', query: 'land', options: {}, ranges: [ new ve.Range( 78, 82 ) ] }, { msg: 'Diacritic insensitive & case sensitive match', query: 'Egalite', options: { diacriticInsensitiveString: true, caseSensitiveString: true }, ranges: [ new ve.Range( 35, 42 ) ] }, { msg: 'Diacritic insensitive but failing case sensitive', query: 'egalite', options: { diacriticInsensitiveString: true, caseSensitiveString: true }, ranges: [] }, { msg: 'Diacritic insensitive case insensitive match', query: 'egalite', options: { diacriticInsensitiveString: true }, ranges: [ new ve.Range( 35, 42 ) ] }, { msg: 'Diacritic insensitive & whole word match', query: 'Egalite', options: { diacriticInsensitiveString: true, wholeWord: true }, ranges: [ new ve.Range( 35, 42 ) ] }, { msg: 'Diacritic insensitive & whole word fail', query: 'Egal', options: { diacriticInsensitiveString: true, wholeWord: true }, ranges: [] }, { msg: 'Diacritic insensitive with overlaps', query: 'ee', options: { diacriticInsensitiveString: true }, ranges: [ new ve.Range( 57, 59 ), new ve.Range( 58, 60 ) ] }, { msg: 'Diacritic insensitive without overlaps', query: 'ee', options: { diacriticInsensitiveString: true, noOverlaps: true }, ranges: [ new ve.Range( 57, 59 ) ] }, { msg: 'ő matches o in English locale', query: 'Erdos', options: { diacriticInsensitiveString: true, noOverlaps: true }, ranges: [ new ve.Range( 62, 67 ) ] }, { msg: 'ő does not match o in Hungarian locale', lang: 'hu', query: 'Erdos', options: { diacriticInsensitiveString: true, noOverlaps: true }, ranges: [] }, { msg: 'ö matches o in German locale', lang: 'de', query: 'Malmo', options: { diacriticInsensitiveString: true, noOverlaps: true }, ranges: [ new ve.Range( 69, 74 ) ] }, { msg: 'ö does not match o in Swedish locale', lang: 'sv', query: 'Malmo', options: { diacriticInsensitiveString: true, noOverlaps: true }, ranges: [] } ]; cases.forEach( ( caseItem ) => { doc.lang = caseItem.lang || 'en'; const ranges = doc.findText( caseItem.query, caseItem.options ); assert.deepEqual( ranges, caseItem.ranges, caseItem.msg ); } ); } ); QUnit.test( 'findText (non-text content)', ( assert ) => { const doc = ve.dm.converter.getModelFromDom( ve.createDocumentFromHtml( // 1 3 5 7 9 // / / / / / '<p>ab<img/><img/>cd</p>' ) ), cases = [ { msg: 'Start of line anchor "^" does not match after nodes', query: /^./g, options: { noOverlaps: true }, ranges: [ new ve.Range( 1, 2 ) ] }, { msg: 'End of line anchor "$" does not match before nodes', query: /.$/g, options: { noOverlaps: true }, ranges: [ new ve.Range( 8, 9 ) ] }, { msg: 'A dot in regexp matches each node once', query: /./g, options: { noOverlaps: true }, ranges: [ new ve.Range( 1, 2 ), new ve.Range( 2, 3 ), new ve.Range( 3, 5 ), new ve.Range( 5, 7 ), new ve.Range( 7, 8 ), new ve.Range( 8, 9 ) ] }, { msg: 'Partial match of a node (start) is extended', query: /ab./g, options: { noOverlaps: true }, ranges: [ new ve.Range( 1, 5 ) ] }, { msg: 'Partial match of a node (end) is skipped', query: /.cd/g, options: { noOverlaps: true }, ranges: [] }, { msg: 'Partial match of a node (end) is skipped, but a shorter match is found', query: /.?cd/g, options: { noOverlaps: true }, ranges: [ new ve.Range( 7, 9 ) ] } ]; cases.forEach( ( caseItem ) => { const ranges = doc.findText( caseItem.query, caseItem.options ); assert.deepEqual( ranges, caseItem.ranges, caseItem.msg ); } ); } ); QUnit.test( 'fixupInsertion', ( assert ) => { const doc = new ve.dm.Document( [ { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' }, { type: 'paragraph' }, ...'ab', { type: '/paragraph' }, { type: '/listItem' }, { type: '/list' }, { type: 'internalList' }, { type: '/internalList' } ] ), surface = new ve.dm.Surface( doc ), nodeData = [ { type: 'listlessNode' }, 'x', { type: '/listlessNode' } ]; let fragment = surface.getLinearFragment( new ve.Range( 4, 4 ) ); // TODO: test other parts of fixupInsertion; childNodes and parentNodes. // Currently they're mostly tested implicitly in the paste tests // elsewhere. // We need a node with suggestedParentNodeTypes, and nothing in core actually uses this. So stub one out: function ListlessNode() { ListlessNode.super.apply( this, arguments ); } OO.inheritClass( ListlessNode, ve.dm.ContentBranchNode ); ListlessNode.static.name = 'listlessNode'; ListlessNode.static.suggestedParentNodeTypes = [ 'document', 'tableCell', 'div' ]; ve.dm.modelRegistry.register( ListlessNode ); fragment.insertContent( nodeData ); assert.deepEqual( doc.getData( new ve.Range( 3, 14 ) ), [ 'a', { type: '/paragraph' }, { type: '/listItem' }, { type: '/list' }, { type: 'listlessNode' }, 'x', { type: '/listlessNode' }, { type: 'list', attributes: { style: 'bullet' } }, { type: 'listItem' }, { type: 'paragraph' }, 'b' ], 'inserting a listlessNode into a listitem should isolate it from the list' ); surface.undo(); fragment = surface.getLinearFragment( new ve.Range( 8, 8 ) ); fragment.insertContent( nodeData ); assert.deepEqual( doc.getData( new ve.Range( 7, 11 ) ), [ { type: '/list' }, { type: 'listlessNode' }, 'x', { type: '/listlessNode' } ], 'inserting a listlessNode to the document root should not add any extra closing elements' ); ve.dm.modelRegistry.unregister( ListlessNode ); } ); QUnit.test( 'read-only and offset caching', ( assert ) => { const doc = ve.dm.example.createExampleDocument(), surface = new ve.dm.Surface( doc ); assert.strictEqual( doc.isReadOnly(), false, 'New document is not read-only' ); // Set up offset caching test assert.strictEqual( doc.getDocumentNode().children[ 1 ].getOffset(), 5, 'Second child node has offset 6' ); surface.getLinearFragment( new ve.Range( 1 ) ).insertContent( 'Test' ); doc.setReadOnly( true ); assert.strictEqual( doc.isReadOnly(), true, 'Document is read-only' ); // Offset caching test assert.strictEqual( doc.getDocumentNode().children[ 1 ].getOffset(), 9, 'Second child node offset has been translated, not cached from before' ); // Document modified after cache set doc.setReadOnly( false ); surface.getLinearFragment( new ve.Range( 1 ) ).insertContent( 'Quux' ); doc.setReadOnly( true ); assert.strictEqual( doc.getDocumentNode().children[ 1 ].getOffset(), 13, 'Second child node offset has been translated again, not cached from before' ); } );
| ver. 1.1 | |
.
| PHP 8.4.18 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка