%PDF- %PDF-
Direktori : /www/varak.net/wiki.varak.net/extensions/VisualEditor/lib/ve/tests/ce/ |
Current File : /www/varak.net/wiki.varak.net/extensions/VisualEditor/lib/ve/tests/ce/ve.ce.test.js |
/*! * VisualEditor ContentEditable tests. * * @copyright 2011-2016 VisualEditor Team and others; see http://ve.mit-license.org */ QUnit.module( 've.ce' ); /* Tests */ QUnit.test( 'getDomHash/getDomText (with ve.dm.Converter)', function ( assert ) { var i, view, documentView, cases = [ { msg: 'Nested annotations', html: '<p><span>a<b><a href="#">b</a></b><span> </span><i>c</i>d</span></p>', hash: '<DIV><P><SPAN>#<B><A>#</A></B><SPAN>#</SPAN><I>#</I>#</SPAN></P></DIV>', text: 'ab cd' }, { msg: 'Inline nodes produce snowmen', html: '<p>Foo <span rel="ve:Alien">Alien</span> bar</p>', hash: '<DIV><P>#<SPAN>#</SPAN>#</P></DIV>', text: 'Foo ☃☃ bar' }, { msg: 'About grouped aliens produce one pair of snowmen', html: '<p>Foo ' + '<span about="g1" rel="ve:Alien">Alien</span>' + '<span about="g1" rel="ve:Alien">Aliens</span>' + '<span about="g1" rel="ve:Alien">Alien³</span> bar</p>', hash: '<DIV><P>#<SPAN>#</SPAN><SPAN>#</SPAN><SPAN>#</SPAN>#</P></DIV>', text: 'Foo ☃☃ bar' }, { msg: 'Block slugs are ignored', html: '<table><tr><td>Foo</td></tr></table>', hash: '<DIV><TABLE><TBODY><TR><TD><P>#</P></TD></TR></TBODY></TABLE></DIV>', text: 'Foo' } ]; QUnit.expect( cases.length * 2 ); for ( i = 0; i < cases.length; i++ ) { view = ve.test.utils.createSurfaceViewFromHtml( cases[ i ].html ); documentView = view.getDocument(); assert.strictEqual( ve.ce.getDomHash( documentView.getDocumentNode().$element[ 0 ] ), cases[ i ].hash, 'getDomHash: ' + cases[ i ].msg ); assert.strictEqual( ve.ce.getDomText( documentView.getDocumentNode().$element[ 0 ] ), cases[ i ].text, 'getDomText: ' + cases[ i ].msg ); view.destroy(); } } ); QUnit.test( 'getDomHash/getDomText (without ve.dm.Converter)', function ( assert ) { var i, view, element, cases = [ { msg: 'Block slugs are ignored', html: '<div><p>foo</p><div class="ve-ce-branchNode-blockSlug">x</div><p>bar</p></div>', hash: '<DIV><P>#</P><P>#</P></DIV>', text: 'foobar' }, { msg: 'Cursor holders are ignored', html: '<div><p>foo</p><div class="ve-ce-cursorHolder">x</div><p>bar</p></div>', hash: '<DIV><P>#</P><P>#</P></DIV>', text: 'foobar' } ]; QUnit.expect( cases.length * 2 ); view = ve.test.utils.createSurfaceViewFromHtml( '' ); element = view.getDocument().getDocumentNode().$element[ 0 ]; for ( i = 0; i < cases.length; i++ ) { element.innerHTML = cases[ i ].html; assert.strictEqual( ve.ce.getDomHash( element.firstChild ), cases[ i ].hash, 'getDomHash: ' + cases[ i ].msg ); assert.strictEqual( ve.ce.getDomText( element.firstChild ), cases[ i ].text, 'getDomText: ' + cases[ i ].msg ); } view.destroy(); } ); QUnit.test( 'getOffset', function ( assert ) { var i, view, documentModel, documentView, expected = 0, testCases = [ { msg: 'Empty paragraph', html: '<p></p>', // CE HTML summary: // <p><span [inlineSlug]><img /></span></p> // Linmod: // [<p>, </p>] expected: [ 0, 1, 1, 1, 1, 1, 2 ] }, { msg: 'Annotations', html: '<p><i><b>Foo</b></i></p>', // Linmod: // [<p>, F, o, o, </p>] expected: [ 0, 1, 1, 1, 1, 2, 3, 4, 4, 4, 4, 5 ] }, { msg: 'Multiple siblings', html: '<p><b><i>Foo</i><s><u>Bar</u><span>Baz</span></s></b></p>', // Linmod: // [<p>, F, o, o, B, a, r, B, a, z, </p>] expected: [ 0, 1, 1, 1, 1, 2, 3, 4, 4, 4, 4, 4, 4, 5, 6, 7, 7, 7, 7, 7, 8, 9, 10, 10, 10, 10, 10, 11 ] }, { msg: 'Annotated alien', html: '<p>Foo<b><span rel="ve:Alien">Bar</span></b>Baz</p>', // CE HTML summary; // <p>Foo<b><span [alien]>Bar</span></b>Baz</p> // Linmod: // [<p>, F, o, o, <alineinline>, </alineinline>, B, a, z, </p>] expected: [ 0, 1, 1, 2, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 7, 8, 9, 9, 10 ] }, { msg: 'Block alien', html: '<p>Foo</p><div rel="ve:Alien">Bar</div><p>Baz</p>', // Linmod: // [<p>, F, o, o, </p>, <alienBlock>, </alienBlock>, <p>, B, a, z, </p>] expected: [ 0, 1, 1, 2, 3, 4, 4, 5, 6, 6, 6, 6, 6, 6, 7, 8, 8, 9, 10, 11, 11, 12 ] }, { msg: 'Table with block slugs', html: '<table><tr><td>Foo</td></tr></table>', // CE HTML summary; // <div [slug]>(ignored)</div> // <table><tbody><tr><td> // <p>Foo</p> // </td></tr></tbody></table> // <div [slug]>(ignored)</div> // Linmod: // [<table>, <tbody>, <tr>, <td>, <p>, F, o, o, </p>, </td>, </tr>, </tbody>, </table>] expected: [ 0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 8, 9, 10, 11, 12, 13, 13 ] }, { msg: 'Paragraph with inline slugs', html: '<p><span rel="ve:Alien">Foo</span><span rel="ve:Alien">Bar</span><br></p>', // CE HTML summary: // <p><span [inlineSlug]><img /></span><span [alien]>Foo</span> // <span [inlineSlug]><img /></span><span [alien]>Bar</span> // <span [inlineSlug]><img /></span><br></br><span [inlineSlug]><img /></span></p> // Linmod: // [<p>, <alineinline>, </alineinline>, <alineinline>, </alineinline>, <break>, </break>, </p>] expected: [ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 5, 5, 5, 5, 6, 7, 7, 7, 7, 7, 8 ] } ]; for ( i = 0; i < testCases.length; i++ ) { expected += testCases[ i ].expected.length; } QUnit.expect( expected ); function testOffsets( parent, testCase, expectedIndex ) { var i; switch ( parent.nodeType ) { case Node.ELEMENT_NODE: for ( i = 0; i <= parent.childNodes.length; i++ ) { expectedIndex++; assert.strictEqual( ve.ce.getOffset( parent, i ), testCase.expected[ expectedIndex ], testCase.msg + ': offset ' + i + ' in <' + parent.nodeName.toLowerCase() + '>' ); if ( parent.childNodes[ i ] && !$( parent.childNodes[ i ] ).hasClass( 've-ce-branchNode-blockSlug' ) ) { expectedIndex = testOffsets( parent.childNodes[ i ], testCase, expectedIndex ); } } break; case Node.TEXT_NODE: for ( i = 0; i <= parent.data.length; i++ ) { expectedIndex++; assert.strictEqual( ve.ce.getOffset( parent, i ), testCase.expected[ expectedIndex ], testCase.msg + ': offset ' + i + ' in "' + parent.data + '"' ); } break; } return expectedIndex; } for ( i = 0; i < testCases.length; i++ ) { view = ve.test.utils.createSurfaceViewFromHtml( testCases[ i ].html ); documentModel = view.getModel().getDocument(); documentView = view.getDocument(); testOffsets( documentView.getDocumentNode().$element[ 0 ], testCases[ i ], -1 ); view.destroy(); } } ); // TODO: ve.ce.getOffsetOfSlug QUnit.test( 'isShortcutKey', 3, function ( assert ) { assert.strictEqual( ve.ce.isShortcutKey( { ctrlKey: true } ), true, 'ctrlKey' ); assert.strictEqual( ve.ce.isShortcutKey( { metaKey: true } ), true, 'metaKey' ); assert.strictEqual( ve.ce.isShortcutKey( {} ), false, 'Not set' ); } ); QUnit.test( 'nextCursorOffset', function ( assert ) { var i, len, tests, elt, test, img, nextOffset; function dumpnode( node ) { if ( node.nodeType === 3 ) { return '#' + node.data; } else { return node.nodeName.toLowerCase(); } } tests = [ { html: '<p>foo<img>bar</p>', expected: [ '#bar', 0 ] }, { html: '<p>foo<b><i><img></i></b></p>', expected: [ 'i', 1 ] }, { html: '<p><b>foo</b><img>bar</p>', expected: [ '#bar', 0 ] }, { html: '<p>foo<b><i><img></i></b></p>', expected: [ 'i', 1 ] }, { html: '<p><b>foo</b><img></p>', expected: [ 'p', 2 ] }, { html: '<p><img><b>foo</b></p>', expected: [ 'p', 1 ] }, { html: '<p><b>foo</b><img><b>bar</b></p>', expected: [ 'p', 2 ] } ]; QUnit.expect( tests.length ); elt = ve.createDocumentFromHtml( '' ).createElement( 'div' ); for ( i = 0, len = tests.length; i < len; i++ ) { test = tests[ i ]; elt.innerHTML = test.html; img = elt.getElementsByTagName( 'img' )[ 0 ]; nextOffset = ve.ce.nextCursorOffset( img ); assert.deepEqual( [ dumpnode( nextOffset.node ), nextOffset.offset ], test.expected, test.html ); } } ); QUnit.test( 'resolveTestOffset', function ( assert ) { var i, ilen, j, jlen, tests, test, testOffset, elt, pre, post, count, dom; tests = [ [ 'o', 'k' ], // TODO: doesn't handle tags correctly yet! // ['w', '<b>', 'x', 'y', '</b>', 'z'], // ['q', '<b>', 'r', '<b>', 's', 't', '</b>', 'u', '</b>', 'v'] [ 'h', 'e', 'l', 'l', 'o' ] ]; count = 0; for ( i = 0, ilen = tests.length; i < ilen; i++ ) { count += tests[ i ].length + 1; } QUnit.expect( 2 * count ); dom = ve.createDocumentFromHtml( '' ); elt = dom.createElement( 'div' ); for ( i = 0, ilen = tests.length; i < ilen; i++ ) { test = tests[ i ]; elt.innerHTML = test.join( '' ); for ( j = 0, jlen = test.length; j < jlen + 1; j++ ) { testOffset = new ve.ce.TestOffset( 'forward', j ); pre = test.slice( 0, j ).join( '' ); post = test.slice( j ).join( '' ); assert.strictEqual( testOffset.resolve( elt ).slice, pre + '|' + post ); testOffset = new ve.ce.TestOffset( 'backward', j ); pre = test.slice( 0, jlen - j ).join( '' ); post = test.slice( jlen - j ).join( '' ); assert.strictEqual( testOffset.resolve( elt ).slice, pre + '|' + post ); } } } ); QUnit.test( 'fakeImes', function ( assert ) { var i, ilen, j, jlen, view, testRunner, testName, testActions, seq, testInfo, action, args, count, foundEndLoop, testsFailAt, failAt, died, fakePreventDefault; if ( Function.prototype.bind === undefined ) { // Assume we are in PhantomJS (which breaks different tests than a real browser) testsFailAt = ve.ce.imetestsPhantomFailAt; } else { testsFailAt = ve.ce.imetestsFailAt; } // count tests count = 0; for ( i = 0, ilen = ve.ce.imetests.length; i < ilen; i++ ) { testName = ve.ce.imetests[ i ][ 0 ]; testActions = ve.ce.imetests[ i ][ 1 ]; // For the test that there is at least one endLoop count++; for ( j = 1, jlen = testActions.length; j < jlen; j++ ) { action = testActions[ j ].action; if ( action === 'endLoop' ) { // For the test that the model and CE surface are in sync count++; } } } QUnit.expect( count ); // TODO: make this function actually affect the events triggered fakePreventDefault = function () {}; for ( i = 0, ilen = ve.ce.imetests.length; i < ilen; i++ ) { testName = ve.ce.imetests[ i ][ 0 ]; failAt = testsFailAt[ testName ] || null; testActions = ve.ce.imetests[ i ][ 1 ]; foundEndLoop = false; // First element is the testInfo testInfo = testActions[ 0 ]; view = ve.test.utils.createSurfaceViewFromHtml( testInfo.startDom || '' ); view.getModel().setLinearSelection( new ve.Range( 1 ) ); testRunner = new ve.ce.TestRunner( view ); // start at 1 to omit the testInfo died = false; for ( j = 1, jlen = testActions.length; j < jlen; j++ ) { action = testActions[ j ].action; args = testActions[ j ].args; seq = testActions[ j ].seq; if ( !died ) { if ( action === 'sendEvent' ) { // TODO: make preventDefault work args[ 1 ].preventDefault = fakePreventDefault; } try { testRunner[ action ].apply( testRunner, args ); } catch ( ex ) { died = ex; } } // Check synchronization at the end of each event loop if ( action === 'endLoop' ) { foundEndLoop = true; if ( failAt === null || seq < failAt ) { // If no expected failure yet, test the code ran and the // model and CE surface are in sync if ( died ) { testRunner.failDied( assert, testName, seq, died ); } else { testRunner.testEqual( assert, testName, seq ); } } else if ( seq === failAt ) { // If *at* expected failure, check something failed if ( died ) { testRunner.ok( assert, testName + ' (fail expected)', seq ); } else { testRunner.testNotEqual( assert, testName + ' (fail expected)', seq ); } } else { // If *after* expected failure, allow anything testRunner.ok( assert, testName, seq ); } } } // Test that there is at least one endLoop assert.strictEqual( foundEndLoop, true, testName + ' found at least one endLoop' ); view.destroy(); } } ); QUnit.test( 'isAfterAnnotationBoundary', function ( assert ) { var tests, i, iLen, test, node, j, jLen, div = ve.createDocumentFromHtml( '' ).createElement( 'div' ); div.innerHTML = 'Q<b>R<i>S</i>T</b><s>U</s>V<u>W</u>'; // In the following tests, the 'path' properties are a list of descent offsets to find a // particular descendant node from the top-level div. E.g. a path of [ 5, 7 ] refers to // the node div.childNodes[ 5 ].childNodes[ 7 ] . tests = [ { path: [], offset: 0, expected: false }, { path: [ 0 ], offset: 0, expected: false }, { path: [ 0 ], offset: 1, expected: false }, { path: [], offset: 1, expected: false }, { path: [ 1 ], offset: 0, expected: true }, { path: [ 1, 0 ], offset: 0, expected: true }, { path: [ 1, 0 ], offset: 1, expected: false }, { path: [ 1 ], offset: 1, expected: false }, { path: [ 1, 1 ], offset: 0, expected: true }, { path: [ 1, 1, 0 ], offset: 0, expected: true }, { path: [ 1, 1, 0 ], offset: 1, expected: false }, { path: [ 1, 1 ], offset: 1, expected: false }, { path: [ 1 ], offset: 2, expected: true }, { path: [ 1, 2 ], offset: 0, expected: true }, { path: [ 1, 2 ], offset: 1, expected: false }, { path: [ 1 ], offset: 3, expected: false }, // The next position *is* a after a boundary (though also before one) { path: [], offset: 2, expected: true }, { path: [ 2 ], offset: 0, expected: true }, { path: [ 2, 0 ], offset: 0, expected: true }, { path: [ 2, 0 ], offset: 1, expected: false }, { path: [ 2 ], offset: 1, expected: false }, { path: [], offset: 3, expected: true }, { path: [ 3 ], offset: 0, expected: true }, { path: [ 3 ], offset: 1, expected: false }, { path: [], offset: 4, expected: false }, { path: [ 4 ], offset: 0, expected: true }, { path: [ 4, 0 ], offset: 0, expected: true }, { path: [ 4, 0 ], offset: 1, expected: false }, { path: [ 4 ], offset: 1, expected: false }, { path: [], offset: 5, expected: true } ]; QUnit.expect( tests.length ); for ( i = 0, iLen = tests.length; i < iLen; i++ ) { test = tests[ i ]; node = div; for ( j = 0, jLen = test.path.length; j < jLen; j++ ) { node = node.childNodes[ test.path[ j ] ]; } assert.strictEqual( ve.ce.isAfterAnnotationBoundary( node, test.offset ), test.expected, 'node=' + test.path.join( ',' ) + ' offset=' + test.offset ); } } );