diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2015-12-17 09:15:42 +0100 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2015-12-17 09:44:51 +0100 |
commit | a1789ddde42033f1b05cc4929491214ee6e79383 (patch) | |
tree | 63615735c4ddffaaabf2428946bb26f90899f7bf /tests/qunit/suites | |
parent | 9e06a62f265e3a2aaabecc598d4bc617e06fa32d (diff) |
Update to MediaWiki 1.26.0
Diffstat (limited to 'tests/qunit/suites')
33 files changed, 1588 insertions, 872 deletions
diff --git a/tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js b/tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js index 4484467d..cf34fc11 100644 --- a/tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js @@ -1,7 +1,7 @@ ( function ( $ ) { QUnit.module( 'jquery.accessKeyLabel', QUnit.newMwEnvironment( { messages: { - 'brackets': '[$1]', + brackets: '[$1]', 'word-separator': ' ' } } ) ); @@ -9,23 +9,23 @@ var getAccessKeyPrefixTestData = [ // ua string, platform string, expected prefix // Internet Explorer - ['Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)', 'Win32', 'alt-'], - ['Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)', 'Win32', 'alt-'], - ['Mozilla/5.0 (Windows NT 6.3; Win64; x64; Trident/7.0; rv:11.0) like Gecko', 'Win64', 'alt-'], + [ 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)', 'Win32', 'alt-' ], + [ 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)', 'Win32', 'alt-' ], + [ 'Mozilla/5.0 (Windows NT 6.3; Win64; x64; Trident/7.0; rv:11.0) like Gecko', 'Win64', 'alt-' ], // Firefox - ['Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.19) Gecko/20110420 Firefox/3.5.19', 'MacIntel', 'ctrl-'], - ['Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.17) Gecko/20110422 Ubuntu/10.10 (maverick) Firefox/3.6.17', 'Linux i686', 'alt-shift-'], - ['Mozilla/5.0 (Windows NT 6.0; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', 'Win32', 'alt-shift-'], + [ 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.19) Gecko/20110420 Firefox/3.5.19', 'MacIntel', 'ctrl-' ], + [ 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.17) Gecko/20110422 Ubuntu/10.10 (maverick) Firefox/3.6.17', 'Linux i686', 'alt-shift-' ], + [ 'Mozilla/5.0 (Windows NT 6.0; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', 'Win32', 'alt-shift-' ], // Safari / Konqueror - ['Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; nl-nl) AppleWebKit/531.22.7 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7', 'MacIntel', 'ctrl-alt-'], - ['Mozilla/5.0 (Windows; U; Windows NT 6.0; cs-CZ) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7', 'Win32', 'alt-'], - ['Mozilla/5.0 (X11; Linux i686) KHTML/4.9.1 (like Gecko) Konqueror/4.9', 'Linux i686', 'ctrl-'], + [ 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; nl-nl) AppleWebKit/531.22.7 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7', 'MacIntel', 'ctrl-alt-' ], + [ 'Mozilla/5.0 (Windows; U; Windows NT 6.0; cs-CZ) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7', 'Win32', 'alt-' ], + [ 'Mozilla/5.0 (X11; Linux i686) KHTML/4.9.1 (like Gecko) Konqueror/4.9', 'Linux i686', 'ctrl-' ], // Opera - ['Opera/9.80 (Windows NT 5.1)', 'Win32', 'shift-esc-'], - ['Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36 OPR/15.0.1147.130', 'Win32', 'shift-esc-'], + [ 'Opera/9.80 (Windows NT 5.1)', 'Win32', 'shift-esc-' ], + [ 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36 OPR/15.0.1147.130', 'Win32', 'shift-esc-' ], // Chrome - ['Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30', 'MacIntel', 'ctrl-option-'], - ['Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.68 Safari/534.30', 'Linux i686', 'alt-shift-'] + [ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30', 'MacIntel', 'ctrl-option-' ], + [ 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.68 Safari/534.30', 'Linux i686', 'alt-shift-' ] ], // strings appended to title to make sure updateTooltipAccessKeys handles them correctly updateTooltipAccessKeysTestData = [ '', ' [a]', ' [test-a]', ' [alt-b]' ]; @@ -39,9 +39,9 @@ var i; for ( i = 0; i < getAccessKeyPrefixTestData.length; i++ ) { assert.equal( $.fn.updateTooltipAccessKeys.getAccessKeyPrefix( { - userAgent: getAccessKeyPrefixTestData[i][0], - platform: getAccessKeyPrefixTestData[i][1] - } ), getAccessKeyPrefixTestData[i][2], 'Correct prefix for ' + getAccessKeyPrefixTestData[i][0] ); + userAgent: getAccessKeyPrefixTestData[ i ][ 0 ], + platform: getAccessKeyPrefixTestData[ i ][ 1 ] + } ), getAccessKeyPrefixTestData[ i ][ 2 ], 'Correct prefix for ' + getAccessKeyPrefixTestData[ i ][ 0 ] ); } } ); @@ -52,13 +52,13 @@ // (no browser is known using such a short prefix, though) or "Alt+Umschalt+" in German Firefox. result = /^Title \[(.+)[aA]\]$/.exec( title ); assert.ok( result, 'title should match expected structure.' ); - assert.notEqual( result[1], 'test-', 'Prefix used for testing shouldn\'t be used in production.' ); + assert.notEqual( result[ 1 ], 'test-', 'Prefix used for testing shouldn\'t be used in production.' ); } ); QUnit.test( 'updateTooltipAccessKeys - no access key', updateTooltipAccessKeysTestData.length, function ( assert ) { var i, oldTitle, $input, newTitle; for ( i = 0; i < updateTooltipAccessKeysTestData.length; i++ ) { - oldTitle = 'Title' + updateTooltipAccessKeysTestData[i]; + oldTitle = 'Title' + updateTooltipAccessKeysTestData[ i ]; $input = $( makeInput( oldTitle ) ); $( '#qunit-fixture' ).append( $input ); newTitle = $input.updateTooltipAccessKeys().prop( 'title' ); @@ -70,7 +70,7 @@ $.fn.updateTooltipAccessKeys.setTestMode( true ); var i, oldTitle, $input, newTitle; for ( i = 0; i < updateTooltipAccessKeysTestData.length; i++ ) { - oldTitle = 'Title' + updateTooltipAccessKeysTestData[i]; + oldTitle = 'Title' + updateTooltipAccessKeysTestData[ i ]; $input = $( makeInput( oldTitle, 'a' ) ); $( '#qunit-fixture' ).append( $input ); newTitle = $input.updateTooltipAccessKeys().prop( 'title' ); diff --git a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js index e8c51214..a1b2e5c3 100644 --- a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js @@ -11,7 +11,7 @@ function findDivergenceIndex( a, b ) { var i = 0; - while ( i < a.length && i < b.length && a[i] === b[i] ) { + while ( i < a.length && i < b.length && a[ i ] === b[ i ] ) { i++; } return i; @@ -41,7 +41,7 @@ // Add two characters using scary black magic spanText = $span.text(); d = findDivergenceIndex( origText, spanText ); - spanTextNew = spanText.slice( 0, d ) + origText[d] + origText[d] + '...'; + spanTextNew = spanText.slice( 0, d ) + origText[ d ] + origText[ d ] + '...'; assert.gt( spanTextNew.length, spanText.length, 'Verify that the new span-length is indeed greater' ); diff --git a/tests/qunit/suites/resources/jquery/jquery.color.test.js b/tests/qunit/suites/resources/jquery/jquery.color.test.js index c8e8ac70..9afd793e 100644 --- a/tests/qunit/suites/resources/jquery/jquery.color.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.color.test.js @@ -10,7 +10,7 @@ $canvas.animate( { backgroundColor: '#000' }, 10 ).promise().then( function () { var endColors = $.colorUtil.getRGB( $canvas.css( 'background-color' ) ); - assert.deepEqual( endColors, [0, 0, 0], 'end state' ); + assert.deepEqual( endColors, [ 0, 0, 0 ], 'end state' ); } ); this.clock.tick( 20 ); diff --git a/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js b/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js index 39ae363c..00de895d 100644 --- a/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js @@ -4,25 +4,25 @@ QUnit.test( 'getRGB', 18, function ( assert ) { assert.strictEqual( $.colorUtil.getRGB(), undefined, 'No arguments' ); assert.strictEqual( $.colorUtil.getRGB( '' ), undefined, 'Empty string' ); - assert.deepEqual( $.colorUtil.getRGB( [0, 100, 255] ), [0, 100, 255], 'Parse array of rgb values' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgb(0,100,255)' ), [0, 100, 255], 'Parse simple rgb string' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgb(0, 100, 255)' ), [0, 100, 255], 'Parse simple rgb string with spaces' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgb(0%,20%,40%)' ), [0, 51, 102], 'Parse rgb string with percentages' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgb(0%, 20%, 40%)' ), [0, 51, 102], 'Parse rgb string with percentages and spaces' ); - assert.deepEqual( $.colorUtil.getRGB( '#f2ddee' ), [242, 221, 238], 'Hex string: 6 char lowercase' ); - assert.deepEqual( $.colorUtil.getRGB( '#f2DDEE' ), [242, 221, 238], 'Hex string: 6 char uppercase' ); - assert.deepEqual( $.colorUtil.getRGB( '#f2DdEe' ), [242, 221, 238], 'Hex string: 6 char mixed' ); - assert.deepEqual( $.colorUtil.getRGB( '#eee' ), [238, 238, 238], 'Hex string: 3 char lowercase' ); - assert.deepEqual( $.colorUtil.getRGB( '#EEE' ), [238, 238, 238], 'Hex string: 3 char uppercase' ); - assert.deepEqual( $.colorUtil.getRGB( '#eEe' ), [238, 238, 238], 'Hex string: 3 char mixed' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgba(0, 0, 0, 0)' ), [255, 255, 255], 'Zero rgba for Safari 3; Transparent (whitespace)' ); + assert.deepEqual( $.colorUtil.getRGB( [ 0, 100, 255 ] ), [ 0, 100, 255 ], 'Parse array of rgb values' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgb(0,100,255)' ), [ 0, 100, 255 ], 'Parse simple rgb string' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgb(0, 100, 255)' ), [ 0, 100, 255 ], 'Parse simple rgb string with spaces' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgb(0%,20%,40%)' ), [ 0, 51, 102 ], 'Parse rgb string with percentages' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgb(0%, 20%, 40%)' ), [ 0, 51, 102 ], 'Parse rgb string with percentages and spaces' ); + assert.deepEqual( $.colorUtil.getRGB( '#f2ddee' ), [ 242, 221, 238 ], 'Hex string: 6 char lowercase' ); + assert.deepEqual( $.colorUtil.getRGB( '#f2DDEE' ), [ 242, 221, 238 ], 'Hex string: 6 char uppercase' ); + assert.deepEqual( $.colorUtil.getRGB( '#f2DdEe' ), [ 242, 221, 238 ], 'Hex string: 6 char mixed' ); + assert.deepEqual( $.colorUtil.getRGB( '#eee' ), [ 238, 238, 238 ], 'Hex string: 3 char lowercase' ); + assert.deepEqual( $.colorUtil.getRGB( '#EEE' ), [ 238, 238, 238 ], 'Hex string: 3 char uppercase' ); + assert.deepEqual( $.colorUtil.getRGB( '#eEe' ), [ 238, 238, 238 ], 'Hex string: 3 char mixed' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgba(0, 0, 0, 0)' ), [ 255, 255, 255 ], 'Zero rgba for Safari 3; Transparent (whitespace)' ); // Perhaps this is a bug in colorUtil, but it is the current behavior so, let's keep // track of it, so we will know in case it would ever change. assert.strictEqual( $.colorUtil.getRGB( 'rgba(0,0,0,0)' ), undefined, 'Zero rgba without whitespace' ); - assert.deepEqual( $.colorUtil.getRGB( 'lightGreen' ), [144, 238, 144], 'Color names (lightGreen)' ); - assert.deepEqual( $.colorUtil.getRGB( 'transparent' ), [255, 255, 255], 'Color names (transparent)' ); + assert.deepEqual( $.colorUtil.getRGB( 'lightGreen' ), [ 144, 238, 144 ], 'Color names (lightGreen)' ); + assert.deepEqual( $.colorUtil.getRGB( 'transparent' ), [ 255, 255, 255 ], 'Color names (transparent)' ); assert.strictEqual( $.colorUtil.getRGB( 'mediaWiki' ), undefined, 'Inexisting color name' ); } ); @@ -37,9 +37,9 @@ // Re-create the rgbToHsl return array items, limited to two decimals. hsl = $.colorUtil.rgbToHsl( 144, 238, 144 ); - ret = [ dualDecimals( hsl[0] ), dualDecimals( hsl[1] ), dualDecimals( hsl[2] ) ]; + ret = [ dualDecimals( hsl[ 0 ] ), dualDecimals( hsl[ 1 ] ), dualDecimals( hsl[ 2 ] ) ]; - assert.deepEqual( ret, [0.33, 0.73, 0.75], 'rgb(144, 238, 144): hsl(0.33, 0.73, 0.75)' ); + assert.deepEqual( ret, [ 0.33, 0.73, 0.75 ], 'rgb(144, 238, 144): hsl(0.33, 0.73, 0.75)' ); } ); QUnit.test( 'hslToRgb', 1, function ( assert ) { @@ -47,9 +47,9 @@ rgb = $.colorUtil.hslToRgb( 0.3, 0.7, 0.8 ); // Re-create the hslToRgb return array items, rounded to whole numbers. - ret = [ Math.round( rgb[0] ), Math.round( rgb[1] ), Math.round( rgb[2] ) ]; + ret = [ Math.round( rgb[ 0 ] ), Math.round( rgb[ 1 ] ), Math.round( rgb[ 2 ] ) ]; - assert.deepEqual( ret, [183, 240, 168], 'hsl(0.3, 0.7, 0.8): rgb(183, 240, 168)' ); + assert.deepEqual( ret, [ 183, 240, 168 ], 'hsl(0.3, 0.7, 0.8): rgb(183, 240, 168)' ); } ); QUnit.test( 'getColorBrightness', 2, function ( assert ) { diff --git a/tests/qunit/suites/resources/jquery/jquery.hidpi.test.js b/tests/qunit/suites/resources/jquery/jquery.hidpi.test.js index 906369ee..8c628765 100644 --- a/tests/qunit/suites/resources/jquery/jquery.hidpi.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.hidpi.test.js @@ -6,6 +6,22 @@ assert.equal( typeof devicePixelRatio, 'number', '$.devicePixelRatio() returns a number' ); } ); + QUnit.test( 'bracketedDevicePixelRatio', 1, function ( assert ) { + var devicePixelRatio = $.devicePixelRatio(); + assert.equal( typeof devicePixelRatio, 'number', '$.bracketedDevicePixelRatio() returns a number' ); + } ); + + QUnit.test( 'bracketDevicePixelRatio', 8, function ( assert ) { + assert.equal( $.bracketDevicePixelRatio( 0.75 ), 1, '0.75 gives 1' ); + assert.equal( $.bracketDevicePixelRatio( 1 ), 1, '1 gives 1' ); + assert.equal( $.bracketDevicePixelRatio( 1.25 ), 1.5, '1.25 gives 1.5' ); + assert.equal( $.bracketDevicePixelRatio( 1.5 ), 1.5, '1.5 gives 1.5' ); + assert.equal( $.bracketDevicePixelRatio( 1.75 ), 2, '1.75 gives 2' ); + assert.equal( $.bracketDevicePixelRatio( 2 ), 2, '2 gives 2' ); + assert.equal( $.bracketDevicePixelRatio( 2.5 ), 2, '2.5 gives 2' ); + assert.equal( $.bracketDevicePixelRatio( 3 ), 2, '3 gives 2' ); + } ); + QUnit.test( 'matchSrcSet', 6, function ( assert ) { var srcset = 'onefive.png 1.5x, two.png 2x'; diff --git a/tests/qunit/suites/resources/jquery/jquery.localize.test.js b/tests/qunit/suites/resources/jquery/jquery.localize.test.js index 3ef27903..c503fc99 100644 --- a/tests/qunit/suites/resources/jquery/jquery.localize.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.localize.test.js @@ -76,8 +76,8 @@ html = '<div><span title-msg="title"><html:msg key="label" /></span></div>'; $lc = $( html ).localize( { keys: { - 'title': 'foo-' + x + '-title', - 'label': 'foo-' + x + '-label' + title: 'foo-' + x + '-title', + label: 'foo-' + x + '-label' } } ).find( 'span' ); @@ -88,7 +88,7 @@ html = '<div><span><html:msg key="foo-welcome" /></span></div>'; $lc = $( html ).localize( { params: { - 'foo-welcome': [sitename, 'yesterday'] + 'foo-welcome': [ sitename, 'yesterday' ] } } ).find( 'span' ); @@ -100,12 +100,12 @@ $lc = $( html ).localize( { prefix: 'foo-', keys: { - 'title': x + '-title', - 'label': x + '-label' + title: x + '-title', + label: x + '-label' }, params: { - 'title': [sitename, '3 minutes ago'], - 'label': [sitename, '3 minutes ago'] + title: [ sitename, '3 minutes ago' ], + label: [ sitename, '3 minutes ago' ] } } ).find( 'span' ); diff --git a/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js b/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js index 80405819..c51e4093 100644 --- a/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js @@ -336,4 +336,22 @@ this.clock.tick( 500 ); } ); + QUnit.test( 'cloned collapsibles can be made collapsible again', 2, function ( assert ) { + var test = this, + $collapsible = prepareCollapsible( + '<div class="mw-collapsible">' + loremIpsum + '</div>' + ), + $clone = $collapsible.clone() // clone without data and events + .appendTo( '#qunit-fixture' ).makeCollapsible(), + $content = $clone.find( '.mw-collapsible-content' ); + + assert.assertTrue( $content.is( ':visible' ), 'content is visible' ); + + $clone.on( 'afterCollapse.mw-collapsible', function () { + assert.assertTrue( $content.is( ':hidden' ), 'after collapsing: content is hidden' ); + } ); + + $clone.find( '.mw-collapsible-toggle a' ).trigger( 'click' ); + test.clock.tick( 500 ); + } ); }( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js b/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js index 795c2bbb..029edd55 100644 --- a/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js @@ -1,5 +1,14 @@ ( function ( $ ) { - QUnit.module( 'jquery.mwExtension', QUnit.newMwEnvironment() ); + QUnit.module( 'jquery.mwExtension', QUnit.newMwEnvironment( { + // This entire module is deprecated. + // Surpress deprecation warnings in test output. + setup: function () { + this.suppressWarnings(); + }, + teardown: function () { + this.restoreWarnings(); + } + } ) ); QUnit.test( 'String functions', 7, function ( assert ) { assert.equal( $.trimLeft( ' foo bar ' ), 'foo bar ', 'trimLeft' ); @@ -43,9 +52,9 @@ } ); QUnit.test( 'Comparison functions', 5, function ( assert ) { - assert.ok( $.compareArray( [0, 'a', [], [2, 'b'] ], [0, 'a', [], [2, 'b'] ] ), + assert.ok( $.compareArray( [ 0, 'a', [], [ 2, 'b' ] ], [ 0, 'a', [], [ 2, 'b' ] ] ), 'compareArray: Two deep arrays that are excactly the same' ); - assert.ok( !$.compareArray( [1], [2] ), 'compareArray: Two different arrays (false)' ); + assert.ok( !$.compareArray( [ 1 ], [ 2 ] ), 'compareArray: Two different arrays (false)' ); assert.ok( $.compareObject( {}, {} ), 'compareObject: Two empty objects' ); assert.ok( $.compareObject( { foo: 1 }, { foo: 1 } ), 'compareObject: Two the same objects' ); diff --git a/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js b/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js index 78c185f1..5d0ddebb 100644 --- a/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js @@ -1,13 +1,13 @@ -( function ($) { +( function ( $ ) { - QUnit.module('jquery.placeholder', QUnit.newMwEnvironment()); + QUnit.module( 'jquery.placeholder', QUnit.newMwEnvironment() ); - QUnit.test('caches results of feature tests', 2, function (assert) { - assert.strictEqual( typeof $.fn.placeholder.input, 'boolean', '$.fn.placeholder.input'); - assert.strictEqual( typeof $.fn.placeholder.textarea, 'boolean', '$.fn.placeholder.textarea'); - }); + QUnit.test( 'caches results of feature tests', 2, function ( assert ) { + assert.strictEqual( typeof $.fn.placeholder.input, 'boolean', '$.fn.placeholder.input' ); + assert.strictEqual( typeof $.fn.placeholder.textarea, 'boolean', '$.fn.placeholder.textarea' ); + } ); - if ($.fn.placeholder.input && $.fn.placeholder.textarea) { + if ( $.fn.placeholder.input && $.fn.placeholder.textarea ) { return; } @@ -20,126 +20,126 @@ '<input id="input-type-password" type="password" placeholder="e.g. hunter2">' + '<textarea id="textarea" name="message" placeholder="Your message goes here"></textarea>' + '</form>', - testElement = function ($el, assert) { + testElement = function ( $el, assert ) { - var el = $el[0], - placeholder = el.getAttribute('placeholder'); + var el = $el[ 0 ], + placeholder = el.getAttribute( 'placeholder' ); - assert.strictEqual($el.placeholder(), $el, 'should be chainable'); + assert.strictEqual( $el.placeholder(), $el, 'should be chainable' ); - assert.strictEqual(el.value, placeholder, 'should set `placeholder` text as `value`'); - assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); - assert.strictEqual($el.val(), '', 'valHooks works properly'); - assert.ok($el.hasClass('placeholder'), 'should have `placeholder` class'); + assert.strictEqual( el.value, placeholder, 'should set `placeholder` text as `value`' ); + assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' ); + assert.strictEqual( $el.val(), '', 'valHooks works properly' ); + assert.ok( $el.hasClass( 'placeholder' ), 'should have `placeholder` class' ); // test on focus $el.focus(); - assert.strictEqual(el.value, '', '`value` should be the empty string on focus'); - assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); - assert.strictEqual($el.val(), '', 'valHooks works properly'); - assert.ok(!$el.hasClass('placeholder'), 'should not have `placeholder` class on focus'); + assert.strictEqual( el.value, '', '`value` should be the empty string on focus' ); + assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' ); + assert.strictEqual( $el.val(), '', 'valHooks works properly' ); + assert.ok( !$el.hasClass( 'placeholder' ), 'should not have `placeholder` class on focus' ); // and unfocus (blur) again $el.blur(); - assert.strictEqual(el.value, placeholder, 'should set `placeholder` text as `value`'); - assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); - assert.strictEqual($el.val(), '', 'valHooks works properly'); - assert.ok($el.hasClass('placeholder'), 'should have `placeholder` class'); + assert.strictEqual( el.value, placeholder, 'should set `placeholder` text as `value`' ); + assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' ); + assert.strictEqual( $el.val(), '', 'valHooks works properly' ); + assert.ok( $el.hasClass( 'placeholder' ), 'should have `placeholder` class' ); // change the value - $el.val('lorem ipsum'); - assert.strictEqual($el.prop('value'), 'lorem ipsum', '`$el.val(string)` should change the `value` property'); - assert.strictEqual(el.value, 'lorem ipsum', '`$el.val(string)` should change the `value` attribute'); - assert.ok(!$el.hasClass('placeholder'), '`$el.val(string)` should remove `placeholder` class'); + $el.val( 'lorem ipsum' ); + assert.strictEqual( $el.prop( 'value' ), 'lorem ipsum', '`$el.val(string)` should change the `value` property' ); + assert.strictEqual( el.value, 'lorem ipsum', '`$el.val(string)` should change the `value` attribute' ); + assert.ok( !$el.hasClass( 'placeholder' ), '`$el.val(string)` should remove `placeholder` class' ); // and clear it again - $el.val(''); - assert.strictEqual($el.prop('value'), '', '`$el.val("")` should change the `value` property'); - assert.strictEqual(el.value, placeholder, '`$el.val("")` should change the `value` attribute'); - assert.ok($el.hasClass('placeholder'), '`$el.val("")` should re-enable `placeholder` class'); + $el.val( '' ); + assert.strictEqual( $el.prop( 'value' ), '', '`$el.val("")` should change the `value` property' ); + assert.strictEqual( el.value, placeholder, '`$el.val("")` should change the `value` attribute' ); + assert.ok( $el.hasClass( 'placeholder' ), '`$el.val("")` should re-enable `placeholder` class' ); // make sure the placeholder property works as expected. - assert.strictEqual($el.prop('placeholder'), placeholder, '$el.prop(`placeholder`) should return the placeholder value'); - $el.placeholder('new placeholder'); - assert.strictEqual(el.getAttribute('placeholder'), 'new placeholder', '$el.placeholder(<string>) should set the placeholder value'); - assert.strictEqual(el.value, 'new placeholder', '$el.placeholder(<string>) should update the displayed placeholder value'); - $el.placeholder(placeholder); + assert.strictEqual( $el.prop( 'placeholder' ), placeholder, '$el.prop(`placeholder`) should return the placeholder value' ); + $el.placeholder( 'new placeholder' ); + assert.strictEqual( el.getAttribute( 'placeholder' ), 'new placeholder', '$el.placeholder(<string>) should set the placeholder value' ); + assert.strictEqual( el.value, 'new placeholder', '$el.placeholder(<string>) should update the displayed placeholder value' ); + $el.placeholder( placeholder ); }; - QUnit.test('emulates placeholder for <input type=text>', 22, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); - testElement($('#input-type-text'), assert); - }); + QUnit.test( 'emulates placeholder for <input type=text>', 22, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); + testElement( $( '#input-type-text' ), assert ); + } ); - QUnit.test('emulates placeholder for <input type=search>', 22, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); - testElement($('#input-type-search'), assert); - }); + QUnit.test( 'emulates placeholder for <input type=search>', 22, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); + testElement( $( '#input-type-search' ), assert ); + } ); - QUnit.test('emulates placeholder for <input type=email>', 22, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); - testElement($('#input-type-email'), assert); - }); + QUnit.test( 'emulates placeholder for <input type=email>', 22, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); + testElement( $( '#input-type-email' ), assert ); + } ); - QUnit.test('emulates placeholder for <input type=url>', 22, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); - testElement($('#input-type-url'), assert); - }); + QUnit.test( 'emulates placeholder for <input type=url>', 22, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); + testElement( $( '#input-type-url' ), assert ); + } ); - QUnit.test('emulates placeholder for <input type=tel>', 22, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); - testElement($('#input-type-tel'), assert); - }); + QUnit.test( 'emulates placeholder for <input type=tel>', 22, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); + testElement( $( '#input-type-tel' ), assert ); + } ); - QUnit.test('emulates placeholder for <input type=password>', 13, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); + QUnit.test( 'emulates placeholder for <input type=password>', 13, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); var selector = '#input-type-password', - $el = $(selector), - el = $el[0], - placeholder = el.getAttribute('placeholder'); + $el = $( selector ), + el = $el[ 0 ], + placeholder = el.getAttribute( 'placeholder' ); - assert.strictEqual($el.placeholder(), $el, 'should be chainable'); + assert.strictEqual( $el.placeholder(), $el, 'should be chainable' ); // Re-select the element, as it gets replaced by another one in some browsers - $el = $(selector); - el = $el[0]; + $el = $( selector ); + el = $el[ 0 ]; - assert.strictEqual(el.value, placeholder, 'should set `placeholder` text as `value`'); - assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); - assert.strictEqual($el.val(), '', 'valHooks works properly'); - assert.ok($el.hasClass('placeholder'), 'should have `placeholder` class'); + assert.strictEqual( el.value, placeholder, 'should set `placeholder` text as `value`' ); + assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' ); + assert.strictEqual( $el.val(), '', 'valHooks works properly' ); + assert.ok( $el.hasClass( 'placeholder' ), 'should have `placeholder` class' ); // test on focus $el.focus(); // Re-select the element, as it gets replaced by another one in some browsers - $el = $(selector); - el = $el[0]; + $el = $( selector ); + el = $el[ 0 ]; - assert.strictEqual(el.value, '', '`value` should be the empty string on focus'); - assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); - assert.strictEqual($el.val(), '', 'valHooks works properly'); - assert.ok(!$el.hasClass('placeholder'), 'should not have `placeholder` class on focus'); + assert.strictEqual( el.value, '', '`value` should be the empty string on focus' ); + assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' ); + assert.strictEqual( $el.val(), '', 'valHooks works properly' ); + assert.ok( !$el.hasClass( 'placeholder' ), 'should not have `placeholder` class on focus' ); // and unfocus (blur) again $el.blur(); // Re-select the element, as it gets replaced by another one in some browsers - $el = $(selector); - el = $el[0]; + $el = $( selector ); + el = $el[ 0 ]; - assert.strictEqual(el.value, placeholder, 'should set `placeholder` text as `value`'); - assert.strictEqual($el.prop('value'), '', 'propHooks works properly'); - assert.strictEqual($el.val(), '', 'valHooks works properly'); - assert.ok($el.hasClass('placeholder'), 'should have `placeholder` class'); + assert.strictEqual( el.value, placeholder, 'should set `placeholder` text as `value`' ); + assert.strictEqual( $el.prop( 'value' ), '', 'propHooks works properly' ); + assert.strictEqual( $el.val(), '', 'valHooks works properly' ); + assert.ok( $el.hasClass( 'placeholder' ), 'should have `placeholder` class' ); - }); + } ); - QUnit.test('emulates placeholder for <textarea></textarea>', 22, function (assert) { - $('<div>').html(html).appendTo($('#qunit-fixture')); - testElement($('#textarea'), assert); - }); + QUnit.test( 'emulates placeholder for <textarea></textarea>', 22, function ( assert ) { + $( '<div>' ).html( html ).appendTo( $( '#qunit-fixture' ) ); + testElement( $( '#textarea' ), assert ); + } ); -}(jQuery)); +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js b/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js index 97a3ae12..032551d8 100644 --- a/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js @@ -12,32 +12,32 @@ setup: function () { this.liveMonths = mw.language.months; mw.language.months = { - 'keys': { - 'names': ['january', 'february', 'march', 'april', 'may_long', 'june', - 'july', 'august', 'september', 'october', 'november', 'december'], - 'genitive': ['january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen', - 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen', 'december-gen'], - 'abbrev': ['jan', 'feb', 'mar', 'apr', 'may', 'jun', - 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] + keys: { + names: [ 'january', 'february', 'march', 'april', 'may_long', 'june', + 'july', 'august', 'september', 'october', 'november', 'december' ], + genitive: [ 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen', + 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen', 'december-gen' ], + abbrev: [ 'jan', 'feb', 'mar', 'apr', 'may', 'jun', + 'jul', 'aug', 'sep', 'oct', 'nov', 'dec' ] }, - 'names': ['January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December'], - 'genitive': ['January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December'], - 'abbrev': ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + names: [ 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' ], + genitive: [ 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' ], + abbrev: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] }; }, teardown: function () { mw.language.months = this.liveMonths; }, config: { - wgContentLanguage: 'en', + wgPageContentLanguage: 'en', /* default date format of the content language */ wgDefaultDateFormat: 'dmy', /* These two are important for numeric interpretations */ - wgSeparatorTransformTable: ['', ''], - wgDigitTransformTable: ['', ''] + wgSeparatorTransformTable: [ '', '' ], + wgDigitTransformTable: [ '', '' ] } } ) ); @@ -57,17 +57,17 @@ QUnit.test( msg, data.length * 2, function ( assert ) { var extractedR, extractedF, parser; - if (callback !== undefined ) { + if ( callback !== undefined ) { callback(); } parser = $.tablesorter.getParser( parserId ); $.each( data, function ( index, testcase ) { - extractedR = parser.is( testcase[0] ); - extractedF = parser.format( testcase[0] ); + extractedR = parser.is( testcase[ 0 ] ); + extractedF = parser.format( testcase[ 0 ] ); - assert.strictEqual( extractedR, testcase[1], 'Detect: ' + testcase[3] ); - assert.strictEqual( extractedF, testcase[2], 'Sortkey: ' + testcase[3] ); + assert.strictEqual( extractedR, testcase[ 1 ], 'Detect: ' + testcase[ 3 ] ); + assert.strictEqual( extractedF, testcase[ 2 ], 'Sortkey: ' + testcase[ 3 ] ); } ); } ); @@ -82,139 +82,139 @@ ipv4 = [ // Some randomly generated fake IPs - ['0.0.0.0', true, 0, 'An IP address' ], - ['255.255.255.255', true, 255255255255, 'An IP address' ], - ['45.238.27.109', true, 45238027109, 'An IP address' ], - ['1.238.27.1', true, 1238027001, 'An IP address with small numbers' ], - ['238.27.1', false, 238027001, 'A malformed IP Address' ], - ['1', false, 1, 'A super malformed IP Address' ], - ['Just text', false, 0, 'A line with just text' ], - ['45.238.27.109Postfix', false, 45238027109, 'An IP address with a connected postfix' ], - ['45.238.27.109 postfix', false, 45238027109, 'An IP address with a seperated postfix' ] + [ '0.0.0.0', true, 0, 'An IP address' ], + [ '255.255.255.255', true, 255255255255, 'An IP address' ], + [ '45.238.27.109', true, 45238027109, 'An IP address' ], + [ '1.238.27.1', true, 1238027001, 'An IP address with small numbers' ], + [ '238.27.1', false, 238027001, 'A malformed IP Address' ], + [ '1', false, 1, 'A super malformed IP Address' ], + [ 'Just text', false, 0, 'A line with just text' ], + [ '45.238.27.109Postfix', false, 45238027109, 'An IP address with a connected postfix' ], + [ '45.238.27.109 postfix', false, 45238027109, 'An IP address with a seperated postfix' ] ]; parserTest( 'IPv4', 'IPAddress', ipv4 ); simpleMDYDatesInMDY = [ - ['January 17, 2010', true, 20100117, 'Long middle endian date'], - ['Jan 17, 2010', true, 20100117, 'Short middle endian date'], - ['1/17/2010', true, 20100117, 'Numeric middle endian date'], - ['01/17/2010', true, 20100117, 'Numeric middle endian date with padding on month'], - ['01/07/2010', true, 20100107, 'Numeric middle endian date with padding on day'], - ['01/07/0010', true, 20100107, 'Numeric middle endian date with padding on year'], - ['5.12.1990', true, 19900512, 'Numeric middle endian date with . separator'] + [ 'January 17, 2010', true, 20100117, 'Long middle endian date' ], + [ 'Jan 17, 2010', true, 20100117, 'Short middle endian date' ], + [ '1/17/2010', true, 20100117, 'Numeric middle endian date' ], + [ '01/17/2010', true, 20100117, 'Numeric middle endian date with padding on month' ], + [ '01/07/2010', true, 20100107, 'Numeric middle endian date with padding on day' ], + [ '01/07/0010', true, 20100107, 'Numeric middle endian date with padding on year' ], + [ '5.12.1990', true, 19900512, 'Numeric middle endian date with . separator' ] ]; parserTest( 'MDY Dates using mdy content language', 'date', simpleMDYDatesInMDY ); simpleMDYDatesInDMY = [ - ['January 17, 2010', true, 20100117, 'Long middle endian date'], - ['Jan 17, 2010', true, 20100117, 'Short middle endian date'], - ['1/17/2010', true, 20101701, 'Numeric middle endian date'], - ['01/17/2010', true, 20101701, 'Numeric middle endian date with padding on month'], - ['01/07/2010', true, 20100701, 'Numeric middle endian date with padding on day'], - ['01/07/0010', true, 20100701, 'Numeric middle endian date with padding on year'], - ['5.12.1990', true, 19901205, 'Numeric middle endian date with . separator'] + [ 'January 17, 2010', true, 20100117, 'Long middle endian date' ], + [ 'Jan 17, 2010', true, 20100117, 'Short middle endian date' ], + [ '1/17/2010', true, 20101701, 'Numeric middle endian date' ], + [ '01/17/2010', true, 20101701, 'Numeric middle endian date with padding on month' ], + [ '01/07/2010', true, 20100701, 'Numeric middle endian date with padding on day' ], + [ '01/07/0010', true, 20100701, 'Numeric middle endian date with padding on year' ], + [ '5.12.1990', true, 19901205, 'Numeric middle endian date with . separator' ] ]; parserTest( 'MDY Dates using dmy content language', 'date', simpleMDYDatesInDMY, function () { mw.config.set( { - 'wgDefaultDateFormat': 'dmy', - 'wgContentLanguage': 'de' + wgDefaultDateFormat: 'dmy', + wgPageContentLanguage: 'de' } ); } ); oldMDYDates = [ - ['January 19, 1400 BC', false, '99999999', 'BC'], - ['January 19, 1400BC', false, '99999999', 'Connected BC'], - ['January, 19 1400 B.C.', false, '99999999', 'B.C.'], - ['January 19, 1400 AD', false, '99999999', 'AD'], - ['January, 19 10', true, 20100119, 'AD'], - ['January, 19 1', false, '99999999', 'AD'] + [ 'January 19, 1400 BC', false, '99999999', 'BC' ], + [ 'January 19, 1400BC', false, '99999999', 'Connected BC' ], + [ 'January, 19 1400 B.C.', false, '99999999', 'B.C.' ], + [ 'January 19, 1400 AD', false, '99999999', 'AD' ], + [ 'January, 19 10', true, 20100119, 'AD' ], + [ 'January, 19 1', false, '99999999', 'AD' ] ]; parserTest( 'Very old MDY dates', 'date', oldMDYDates ); complexMDYDates = [ - ['January, 19 2010', true, 20100119, 'Comma after month'], - ['January 19, 2010', true, 20100119, 'Comma after day'], - ['January/19/2010', true, 20100119, 'Forward slash separator'], - ['04 22 1991', true, 19910422, 'Month with 0 padding'], - ['April 21 1991', true, 19910421, 'Space separation'], - ['04 22 1991', true, 19910422, 'Month with 0 padding'], - ['December 12 \'10', true, 20101212, ''], - ['Dec 12 \'10', true, 20101212, ''], - ['Dec. 12 \'10', true, 20101212, ''] + [ 'January, 19 2010', true, 20100119, 'Comma after month' ], + [ 'January 19, 2010', true, 20100119, 'Comma after day' ], + [ 'January/19/2010', true, 20100119, 'Forward slash separator' ], + [ '04 22 1991', true, 19910422, 'Month with 0 padding' ], + [ 'April 21 1991', true, 19910421, 'Space separation' ], + [ '04 22 1991', true, 19910422, 'Month with 0 padding' ], + [ 'December 12 \'10', true, 20101212, '' ], + [ 'Dec 12 \'10', true, 20101212, '' ], + [ 'Dec. 12 \'10', true, 20101212, '' ] ]; parserTest( 'MDY Dates', 'date', complexMDYDates ); clobberedDates = [ - ['January, 19 2010 - January, 20 2010', false, '99999999', 'Date range with hyphen'], - ['January, 19 2010 — January, 20 2010', false, '99999999', 'Date range with mdash'], - ['prefixJanuary, 19 2010', false, '99999999', 'Connected prefix'], - ['prefix January, 19 2010', false, '99999999', 'Prefix'], - ['December 12 2010postfix', false, '99999999', 'ConnectedPostfix'], - ['December 12 2010 postfix', false, '99999999', 'Postfix'], - ['A simple text', false, '99999999', 'Plain text in date sort'], - ['04l22l1991', false, '99999999', 'l char as separator'], - ['January\\19\\2010', false, '99999999', 'backslash as date separator'] + [ 'January, 19 2010 - January, 20 2010', false, '99999999', 'Date range with hyphen' ], + [ 'January, 19 2010 — January, 20 2010', false, '99999999', 'Date range with mdash' ], + [ 'prefixJanuary, 19 2010', false, '99999999', 'Connected prefix' ], + [ 'prefix January, 19 2010', false, '99999999', 'Prefix' ], + [ 'December 12 2010postfix', false, '99999999', 'ConnectedPostfix' ], + [ 'December 12 2010 postfix', false, '99999999', 'Postfix' ], + [ 'A simple text', false, '99999999', 'Plain text in date sort' ], + [ '04l22l1991', false, '99999999', 'l char as separator' ], + [ 'January\\19\\2010', false, '99999999', 'backslash as date separator' ] ]; parserTest( 'Clobbered Dates', 'date', clobberedDates ); MYDates = [ - ['December 2010', false, '99999999', 'Plain month year'], - ['Dec 2010', false, '99999999', 'Abreviated month year'], - ['12 2010', false, '99999999', 'Numeric month year'] + [ 'December 2010', false, '99999999', 'Plain month year' ], + [ 'Dec 2010', false, '99999999', 'Abreviated month year' ], + [ '12 2010', false, '99999999', 'Numeric month year' ] ]; parserTest( 'MY Dates', 'date', MYDates ); YDates = [ - ['2010', false, '99999999', 'Plain 4-digit year'], - ['876', false, '99999999', '3-digit year'], - ['76', false, '99999999', '2-digit year'], - ['\'76', false, '99999999', '2-digit millenium bug year'], - ['2010 BC', false, '99999999', '4-digit year BC'] + [ '2010', false, '99999999', 'Plain 4-digit year' ], + [ '876', false, '99999999', '3-digit year' ], + [ '76', false, '99999999', '2-digit year' ], + [ '\'76', false, '99999999', '2-digit millenium bug year' ], + [ '2010 BC', false, '99999999', '4-digit year BC' ] ]; parserTest( 'Y Dates', 'date', YDates ); currencyData = [ - ['1.02 $', true, 1.02, ''], - ['$ 3.00', true, 3, ''], - ['€ 2,99', true, 299, ''], - ['$ 1.00', true, 1, ''], - ['$3.50', true, 3.50, ''], - ['$ 1.50', true, 1.50, ''], - ['€ 0.99', true, 0.99, ''], - ['$ 299.99', true, 299.99, ''], - ['$ 2,299.99', true, 2299.99, ''], - ['$ 2,989', true, 2989, ''], - ['$ 2 299.99', true, 2299.99, ''], - ['$ 2 989', true, 2989, ''], - ['$ 2.989', true, 2.989, ''] + [ '1.02 $', true, 1.02, '' ], + [ '$ 3.00', true, 3, '' ], + [ '€ 2,99', true, 299, '' ], + [ '$ 1.00', true, 1, '' ], + [ '$3.50', true, 3.50, '' ], + [ '$ 1.50', true, 1.50, '' ], + [ '€ 0.99', true, 0.99, '' ], + [ '$ 299.99', true, 299.99, '' ], + [ '$ 2,299.99', true, 2299.99, '' ], + [ '$ 2,989', true, 2989, '' ], + [ '$ 2 299.99', true, 2299.99, '' ], + [ '$ 2 989', true, 2989, '' ], + [ '$ 2.989', true, 2.989, '' ] ]; parserTest( 'Currency', 'currency', currencyData ); transformedCurrencyData = [ - ['1.02 $', true, 102, ''], - ['$ 3.00', true, 300, ''], - ['€ 2,99', true, 2.99, ''], - ['$ 1.00', true, 100, ''], - ['$3.50', true, 350, ''], - ['$ 1.50', true, 150, ''], - ['€ 0.99', true, 99, ''], - ['$ 299.99', true, 29999, ''], - ['$ 2\'299,99', true, 2299.99, ''], - ['$ 2,989', true, 2.989, ''], - ['$ 2 299.99', true, 229999, ''], - ['2 989 $', true, 2989, ''], - ['299.99 $', true, 29999, ''], - ['2\'299,99 $', true, 2299.99, ''], - ['2,989 $', true, 2.989, ''], - ['2 299.99 $', true, 229999, ''], - ['2 989 $', true, 2989, ''] + [ '1.02 $', true, 102, '' ], + [ '$ 3.00', true, 300, '' ], + [ '€ 2,99', true, 2.99, '' ], + [ '$ 1.00', true, 100, '' ], + [ '$3.50', true, 350, '' ], + [ '$ 1.50', true, 150, '' ], + [ '€ 0.99', true, 99, '' ], + [ '$ 299.99', true, 29999, '' ], + [ '$ 2\'299,99', true, 2299.99, '' ], + [ '$ 2,989', true, 2.989, '' ], + [ '$ 2 299.99', true, 229999, '' ], + [ '2 989 $', true, 2989, '' ], + [ '299.99 $', true, 29999, '' ], + [ '2\'299,99 $', true, 2299.99, '' ], + [ '2,989 $', true, 2.989, '' ], + [ '2 299.99 $', true, 229999, '' ], + [ '2 989 $', true, 2989, '' ] ]; parserTest( 'Currency with european separators', 'currency', transformedCurrencyData, function () { mw.config.set( { // We expect 22'234.444,22 // Map from ascii separators => localized separators - wgSeparatorTransformTable: [', . ,', '\' , .'], - wgDigitTransformTable: ['', ''] + wgSeparatorTransformTable: [ ', . ,', '\' , .' ], + wgDigitTransformTable: [ '', '' ] } ); } ); diff --git a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js index f63aa27a..d4d0ce1e 100644 --- a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js @@ -1,5 +1,19 @@ ( function ( $, mw ) { - var header, + var header = [ 'Planet', 'Radius (km)' ], + + // Data set "planets" + mercury = [ 'Mercury', '2439.7' ], + venus = [ 'Venus', '6051.8' ], + earth = [ 'Earth', '6371.0' ], + mars = [ 'Mars', '3390.0' ], + jupiter = [ 'Jupiter', '69911' ], + saturn = [ 'Saturn', '58232' ], + planets = [ mercury, venus, earth, mars, jupiter, saturn ], + planetsAscName = [ earth, jupiter, mars, mercury, saturn, venus ], + planetsAscRadius = [ mercury, mars, venus, earth, saturn, jupiter ], + planetsRowspan, + planetsRowspanII, + planetsAscNameLegacy, // Data set "simple" a1 = [ 'A', '1' ], @@ -8,11 +22,12 @@ b1 = [ 'B', '1' ], b2 = [ 'B', '2' ], b3 = [ 'B', '3' ], - simple = [a2, b3, a1, a3, b2, b1], - simpleAsc = [a1, a2, a3, b1, b2, b3], - simpleDescasc = [b1, b2, b3, a1, a2, a3], + simple = [ a2, b3, a1, a3, b2, b1 ], + simpleAsc = [ a1, a2, a3, b1, b2, b3 ], + simpleDescasc = [ b1, b2, b3, a1, a2, a3 ], // Data set "colspan" + header4 = [ 'column1a', 'column1b', 'column1c', 'column2' ], aaa1 = [ 'A', 'A', 'A', '1' ], aab5 = [ 'A', 'A', 'B', '5' ], abc3 = [ 'A', 'B', 'C', '3' ], @@ -20,159 +35,145 @@ caa4 = [ 'C', 'A', 'A', '4' ], colspanInitial = [ aab5, aaa1, abc3, bbc2, caa4 ], - // Data set "planets" - mercury = [ 'Mercury', '2439.7' ], - venus = [ 'Venus', '6051.8' ], - earth = [ 'Earth', '6371.0' ], - mars = [ 'Mars', '3390.0' ], - jupiter = [ 'Jupiter', '69911' ], - saturn = [ 'Saturn', '58232' ], - planets = [mercury, venus, earth, mars, jupiter, saturn], - planetsAscName = [earth, jupiter, mars, mercury, saturn, venus], - planetsAscRadius = [mercury, mars, venus, earth, saturn, jupiter], - planetsRowspan, - planetsRowspanII, - planetsAscNameLegacy, - // Data set "ipv4" ipv4 = [ // Some randomly generated fake IPs - ['45.238.27.109'], - ['44.172.9.22'], - ['247.240.82.209'], - ['204.204.132.158'], - ['170.38.91.162'], - ['197.219.164.9'], - ['45.68.154.72'], - ['182.195.149.80'] + [ '45.238.27.109' ], + [ '44.172.9.22' ], + [ '247.240.82.209' ], + [ '204.204.132.158' ], + [ '170.38.91.162' ], + [ '197.219.164.9' ], + [ '45.68.154.72' ], + [ '182.195.149.80' ] ], ipv4Sorted = [ // Sort order should go octet by octet - ['44.172.9.22'], - ['45.68.154.72'], - ['45.238.27.109'], - ['170.38.91.162'], - ['182.195.149.80'], - ['197.219.164.9'], - ['204.204.132.158'], - ['247.240.82.209'] + [ '44.172.9.22' ], + [ '45.68.154.72' ], + [ '45.238.27.109' ], + [ '170.38.91.162' ], + [ '182.195.149.80' ], + [ '197.219.164.9' ], + [ '204.204.132.158' ], + [ '247.240.82.209' ] ], // Data set "umlaut" umlautWords = [ - ['Günther'], - ['Peter'], - ['Björn'], - ['Bjorn'], - ['Apfel'], - ['Äpfel'], - ['Strasse'], - ['Sträßschen'] + [ 'Günther' ], + [ 'Peter' ], + [ 'Björn' ], + [ 'Bjorn' ], + [ 'Apfel' ], + [ 'Äpfel' ], + [ 'Strasse' ], + [ 'Sträßschen' ] ], umlautWordsSorted = [ - ['Äpfel'], - ['Apfel'], - ['Björn'], - ['Bjorn'], - ['Günther'], - ['Peter'], - ['Sträßschen'], - ['Strasse'] + [ 'Äpfel' ], + [ 'Apfel' ], + [ 'Björn' ], + [ 'Bjorn' ], + [ 'Günther' ], + [ 'Peter' ], + [ 'Sträßschen' ], + [ 'Strasse' ] ], complexMDYDates = [ - ['January, 19 2010'], - ['April 21 1991'], - ['04 22 1991'], - ['5.12.1990'], - ['December 12 \'10'] + [ 'January, 19 2010' ], + [ 'April 21 1991' ], + [ '04 22 1991' ], + [ '5.12.1990' ], + [ 'December 12 \'10' ] ], complexMDYSorted = [ - ['5.12.1990'], - ['April 21 1991'], - ['04 22 1991'], - ['January, 19 2010'], - ['December 12 \'10'] + [ '5.12.1990' ], + [ 'April 21 1991' ], + [ '04 22 1991' ], + [ 'January, 19 2010' ], + [ 'December 12 \'10' ] ], currencyUnsorted = [ - ['1.02 $'], - ['$ 3.00'], - ['€ 2,99'], - ['$ 1.00'], - ['$3.50'], - ['$ 1.50'], - ['€ 0.99'] + [ '1.02 $' ], + [ '$ 3.00' ], + [ '€ 2,99' ], + [ '$ 1.00' ], + [ '$3.50' ], + [ '$ 1.50' ], + [ '€ 0.99' ] ], currencySorted = [ - ['€ 0.99'], - ['$ 1.00'], - ['1.02 $'], - ['$ 1.50'], - ['$ 3.00'], - ['$3.50'], + [ '€ 0.99' ], + [ '$ 1.00' ], + [ '1.02 $' ], + [ '$ 1.50' ], + [ '$ 3.00' ], + [ '$3.50' ], // Comma's sort after dots // Not intentional but test to detect changes - ['€ 2,99'] + [ '€ 2,99' ] ], numbers = [ - [ '12' ], - [ '7' ], - [ '13,000'], - [ '9' ], - [ '14' ], - [ '8.0' ] + [ '12' ], + [ '7' ], + [ '13,000' ], + [ '9' ], + [ '14' ], + [ '8.0' ] ], numbersAsc = [ - [ '7' ], - [ '8.0' ], - [ '9' ], - [ '12' ], - [ '14' ], - [ '13,000'] + [ '7' ], + [ '8.0' ], + [ '9' ], + [ '12' ], + [ '14' ], + [ '13,000' ] ], correctDateSorting1 = [ - ['01 January 2010'], - ['05 February 2010'], - ['16 January 2010'] + [ '01 January 2010' ], + [ '05 February 2010' ], + [ '16 January 2010' ] ], correctDateSortingSorted1 = [ - ['01 January 2010'], - ['16 January 2010'], - ['05 February 2010'] + [ '01 January 2010' ], + [ '16 January 2010' ], + [ '05 February 2010' ] ], correctDateSorting2 = [ - ['January 01 2010'], - ['February 05 2010'], - ['January 16 2010'] + [ 'January 01 2010' ], + [ 'February 05 2010' ], + [ 'January 16 2010' ] ], correctDateSortingSorted2 = [ - ['January 01 2010'], - ['January 16 2010'], - ['February 05 2010'] + [ 'January 01 2010' ], + [ 'January 16 2010' ], + [ 'February 05 2010' ] ]; QUnit.module( 'jquery.tablesorter', QUnit.newMwEnvironment( { setup: function () { this.liveMonths = mw.language.months; mw.language.months = { - 'keys': { - 'names': ['january', 'february', 'march', 'april', 'may_long', 'june', - 'july', 'august', 'september', 'october', 'november', 'december'], - 'genitive': ['january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen', - 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen', 'december-gen'], - 'abbrev': ['jan', 'feb', 'mar', 'apr', 'may', 'jun', - 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] + keys: { + names: [ 'january', 'february', 'march', 'april', 'may_long', 'june', + 'july', 'august', 'september', 'october', 'november', 'december' ], + genitive: [ 'january-gen', 'february-gen', 'march-gen', 'april-gen', 'may-gen', 'june-gen', + 'july-gen', 'august-gen', 'september-gen', 'october-gen', 'november-gen', 'december-gen' ], + abbrev: [ 'jan', 'feb', 'mar', 'apr', 'may', 'jun', + 'jul', 'aug', 'sep', 'oct', 'nov', 'dec' ] }, - 'names': ['January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December'], - 'genitive': ['January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December'], - 'abbrev': ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + names: [ 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' ], + genitive: [ 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' ], + abbrev: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ] }; }, teardown: function () { @@ -180,9 +181,9 @@ }, config: { wgDefaultDateFormat: 'dmy', - wgSeparatorTransformTable: ['', ''], - wgDigitTransformTable: ['', ''], - wgContentLanguage: 'en' + wgSeparatorTransformTable: [ '', '' ], + wgDigitTransformTable: [ '', '' ], + wgPageContentLanguage: 'en' } } ) ); @@ -192,7 +193,7 @@ * * @param {String[]} header * @param {String[][]} data - * @return jQuery + * @return {jQuery} */ function tableCreate( header, data ) { var i, @@ -210,7 +211,7 @@ for ( i = 0; i < data.length; i++ ) { /*jshint loopfunc: true */ $tr = $( '<tr>' ); - $.each( data[i], function ( j, str ) { + $.each( data[ i ], function ( j, str ) { var $td = $( '<td>' ); $td.text( str ).appendTo( $tr ); } ); @@ -223,7 +224,7 @@ * Extract text from table. * * @param {jQuery} $table - * @return String[][] + * @return {String[][]} */ function tableExtract( $table ) { var data = []; @@ -302,7 +303,6 @@ } // Sample data set using planets named and their radius - header = [ 'Planet', 'Radius (km)']; tableTest( 'Basic planet table: sorting initially - ascending by name', @@ -388,9 +388,6 @@ $table.find( '.headerSort:eq(1)' ).click().click(); } ); - - header = [ 'column1', 'column2' ]; - tableTest( 'Sorting multiple columns by passing sort list', header, @@ -466,7 +463,7 @@ // Pretend to click while pressing the multi-sort key var event = $.Event( 'click' ); - event[$table.data( 'tablesorter' ).config.sortMultiSortKey] = true; + event[ $table.data( 'tablesorter' ).config.sortMultiSortKey ] = true; $table.find( '.headerSort:eq(1)' ).trigger( event ); } ); @@ -500,10 +497,9 @@ } ); // Sorting with colspans - header = [ 'column1a', 'column1b', 'column1c', 'column2' ]; tableTest( 'Sorting with colspanned headers: spanned column', - header, + header4, colspanInitial, [ aaa1, aab5, abc3, bbc2, caa4 ], function ( $table ) { @@ -516,7 +512,7 @@ } ); tableTest( 'Sorting with colspanned headers: sort spanned column twice', - header, + header4, colspanInitial, [ caa4, bbc2, abc3, aab5, aaa1 ], function ( $table ) { @@ -530,7 +526,7 @@ } ); tableTest( 'Sorting with colspanned headers: subsequent column', - header, + header4, colspanInitial, [ aaa1, bbc2, abc3, caa4, aab5 ], function ( $table ) { @@ -543,7 +539,7 @@ } ); tableTest( 'Sorting with colspanned headers: sort subsequent column twice', - header, + header4, colspanInitial, [ aab5, caa4, abc3, bbc2, aaa1 ], function ( $table ) { @@ -557,42 +553,60 @@ } ); - tableTest( - 'Basic planet table: one unsortable column', - header, - planets, - planets, - function ( $table ) { - $table.find( 'tr:eq(0) > th:eq(0)' ).addClass( 'unsortable' ); + QUnit.test( 'Basic planet table: one unsortable column', 3, function ( assert ) { + var $table = tableCreate( header, planets ), + $cell; + $table.find( 'tr:eq(0) > th:eq(0)' ).addClass( 'unsortable' ); - $table.tablesorter(); - $table.find( 'tr:eq(0) > th:eq(0)' ).click(); - } - ); + $table.tablesorter(); + $table.find( 'tr:eq(0) > th:eq(0)' ).click(); + + assert.deepEqual( + tableExtract( $table ), + planets, + 'table not sorted' + ); + + $cell = $table.find( 'tr:eq(0) > th:eq(0)' ); + $table.find( 'tr:eq(0) > th:eq(1)' ).click(); + + assert.equal( + $cell.hasClass( 'headerSortUp' ) || $cell.hasClass( 'headerSortDown' ), + false, + 'after sort: no class headerSortUp or headerSortDown' + ); + + assert.equal( + $cell.attr( 'title' ), + undefined, + 'after sort: no title tag added' + ); + + } ); // Regression tests! tableTest( 'Bug 28775: German-style (dmy) short numeric dates', - ['Date'], + [ 'Date' ], [ // German-style dates are day-month-year - ['11.11.2011'], - ['01.11.2011'], - ['02.10.2011'], - ['03.08.2011'], - ['09.11.2011'] + [ '11.11.2011' ], + [ '01.11.2011' ], + [ '02.10.2011' ], + [ '03.08.2011' ], + [ '09.11.2011' ] ], [ // Sorted by ascending date - ['03.08.2011'], - ['02.10.2011'], - ['01.11.2011'], - ['09.11.2011'], - ['11.11.2011'] + [ '03.08.2011' ], + [ '02.10.2011' ], + [ '01.11.2011' ], + [ '09.11.2011' ], + [ '11.11.2011' ] ], function ( $table ) { mw.config.set( 'wgDefaultDateFormat', 'dmy' ); - mw.config.set( 'wgContentLanguage', 'de' ); + mw.config.set( 'wgPageContentLanguage', 'de' ); $table.tablesorter(); $table.find( '.headerSort:eq(0)' ).click(); @@ -601,22 +615,22 @@ tableTest( 'Bug 28775: American-style (mdy) short numeric dates', - ['Date'], + [ 'Date' ], [ // American-style dates are month-day-year - ['11.11.2011'], - ['01.11.2011'], - ['02.10.2011'], - ['03.08.2011'], - ['09.11.2011'] + [ '11.11.2011' ], + [ '01.11.2011' ], + [ '02.10.2011' ], + [ '03.08.2011' ], + [ '09.11.2011' ] ], [ // Sorted by ascending date - ['01.11.2011'], - ['02.10.2011'], - ['03.08.2011'], - ['09.11.2011'], - ['11.11.2011'] + [ '01.11.2011' ], + [ '02.10.2011' ], + [ '03.08.2011' ], + [ '09.11.2011' ], + [ '11.11.2011' ] ], function ( $table ) { mw.config.set( 'wgDefaultDateFormat', 'mdy' ); @@ -628,7 +642,7 @@ tableTest( 'Bug 17141: IPv4 address sorting', - ['IP'], + [ 'IP' ], ipv4, ipv4Sorted, function ( $table ) { @@ -638,7 +652,7 @@ ); tableTest( 'Bug 17141: IPv4 address sorting (reverse)', - ['IP'], + [ 'IP' ], ipv4, reversed( ipv4Sorted ), function ( $table ) { @@ -649,15 +663,15 @@ tableTest( 'Accented Characters with custom collation', - ['Name'], + [ 'Name' ], umlautWords, umlautWordsSorted, function ( $table ) { mw.config.set( 'tableSorterCollation', { - 'ä': 'ae', - 'ö': 'oe', - 'ß': 'ss', - 'ü': 'ue' + ä: 'ae', + ö: 'oe', + ß: 'ss', + ü: 'ue' } ); $table.tablesorter(); @@ -749,7 +763,7 @@ tableTest( 'Complex date parsing I', - ['date'], + [ 'date' ], complexMDYDates, complexMDYSorted, function ( $table ) { @@ -762,7 +776,7 @@ tableTest( 'Currency parsing I', - ['currency'], + [ 'currency' ], currencyUnsorted, currencySorted, function ( $table ) { @@ -772,7 +786,7 @@ ); planetsAscNameLegacy = planetsAscName.slice( 0 ); - planetsAscNameLegacy[4] = planetsAscNameLegacy[5]; + planetsAscNameLegacy[ 4 ] = planetsAscNameLegacy[ 5 ]; planetsAscNameLegacy.pop(); tableTest( @@ -801,7 +815,7 @@ $table.find( '.headerSort:eq(0)' ).click(); assert.equal( - $table.data( 'tablesorter' ).config.parsers[0].id, + $table.data( 'tablesorter' ).config.parsers[ 0 ].id, 'number', 'Correctly detected column content skipping sortbottom' ); @@ -989,7 +1003,7 @@ } ); tableTest( 'bug 8115: sort numbers with commas (ascending)', - ['Numbers'], numbers, numbersAsc, + [ 'Numbers' ], numbers, numbersAsc, function ( $table ) { $table.tablesorter(); $table.find( '.headerSort:eq(0)' ).click(); @@ -997,7 +1011,7 @@ ); tableTest( 'bug 8115: sort numbers with commas (descending)', - ['Numbers'], numbers, reversed( numbersAsc ), + [ 'Numbers' ], numbers, reversed( numbersAsc ), function ( $table ) { $table.tablesorter(); $table.find( '.headerSort:eq(0)' ).click().click(); @@ -1032,7 +1046,7 @@ tableTest( 'Correct date sorting I', - ['date'], + [ 'date' ], correctDateSorting1, correctDateSortingSorted1, function ( $table ) { @@ -1045,7 +1059,7 @@ tableTest( 'Correct date sorting II', - ['date'], + [ 'date' ], correctDateSorting2, correctDateSortingSorted2, function ( $table ) { @@ -1238,7 +1252,7 @@ '</tbody></table>' ); $table.tablesorter(); - assert.equal( $table.find( 'tr:eq(1) th:eq(1)').data('headerIndex'), + assert.equal( $table.find( 'tr:eq(1) th:eq(1)' ).data( 'headerIndex' ), 2, 'Incorrect index of sort header' ); @@ -1262,14 +1276,14 @@ tableTestHTML( 'Rowspan exploding with colspanned cells (2)', '<table class="sortable">' + - '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th><th>quux</th></tr></thead>' + + '<thead><tr><th>n</th><th>foo</th><th>bar</th><th>baz</th><th id="sortme">n2</th></tr></thead>' + '<tbody>' + - '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td><td>quux</td></tr>' + - '<tr><td>2</td><td colspan="2">foobar</td><td>quux</td></tr>' + + '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td><td>2</td></tr>' + + '<tr><td>2</td><td colspan="2">foobar</td><td>1</td></tr>' + '</tbody></table>', [ - [ '1', 'foo', 'bar', 'baz', 'quux' ], - [ '2', 'foobar', 'baz', 'quux' ] + [ '2', 'foobar', 'baz', '1' ], + [ '1', 'foo', 'bar', 'baz', '2' ] ] ); @@ -1345,4 +1359,39 @@ ] ); + QUnit.test( 'bug 105731 - incomplete rows in table body', 3, function ( assert ) { + var $table, parsers; + $table = $( + '<table class="sortable">' + + '<tr><th>A</th><th>B</th></tr>' + + '<tr><td>3</td></tr>' + + '<tr><td>1</td><td>2</td></tr>' + + '</table>' + ); + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + // now the first row have 2 columns + $table.find( '.headerSort:eq(1)' ).click(); + + parsers = $table.data( 'tablesorter' ).config.parsers; + + assert.equal( + parsers.length, + 2, + 'detectParserForColumn() detect 2 parsers' + ); + + assert.equal( + parsers[ 1 ].id, + 'number', + 'detectParserForColumn() detect parser.id "number" for second column' + ); + + assert.equal( + parsers[ 1 ].format( $table.find( 'tbody > tr > td:eq(1)' ).text() ), + 0, + 'empty cell is sorted as number 0' + ); + + } ); }( jQuery, mediaWiki ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js b/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js index 56b0fa92..2e6f05ed 100644 --- a/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js @@ -5,13 +5,13 @@ /** * Test factory for $.fn.textSelection( 'encapsulateText' ) * - * @param options {object} associative array containing: - * description {string} - * input {string} - * output {string} - * start {int} starting char for selection - * end {int} ending char for selection - * params {object} add'l parameters for $().textSelection( 'encapsulateText' ) + * @param {Object} options Associative configuration array + * @param {string} options.description Description + * @param {string} options.input Input + * @param {string} options.output Output + * @param {int} options.start Starting char for selection + * @param {int} options.end Ending char for selection + * @param {object} options.params Additional parameters for $().textSelection( 'encapsulateText' ) */ function encapsulateTest( options ) { var opt = $.extend( { @@ -237,23 +237,22 @@ } pos = $textarea.textSelection( 'getCaretPosition', { startAndEnd: true } ); - among( pos[0], options.start, 'Caret start should be where we set it.' ); - among( pos[1], options.end, 'Caret end should be where we set it.' ); + among( pos[ 0 ], options.start, 'Caret start should be where we set it.' ); + among( pos[ 1 ], options.end, 'Caret end should be where we set it.' ); } ); } caretSample = 'Some big text that we like to work with. Nothing fancy... you know what I mean?'; -/* - // @broken: Disabled per bug 34820 + /* @broken: Disabled per bug 34820 caretTest({ - description: 'getCaretPosition with original/empty selection - bug 31847 with IE 6/7/8', - text: caretSample, - start: [0, caretSample.length], // Opera and Firefox (prior to FF 6.0) default caret to the end of the box (caretSample.length) - end: [0, caretSample.length], // Other browsers default it to the beginning (0), so check both. - mode: 'get' + description: 'getCaretPosition with original/empty selection - bug 31847 with IE 6/7/8', + text: caretSample, + start: [0, caretSample.length], // Opera and Firefox (prior to FF 6.0) default caret to the end of the box (caretSample.length) + end: [0, caretSample.length], // Other browsers default it to the beginning (0), so check both. + mode: 'get' }); -*/ + */ caretTest( { description: 'set/getCaretPosition with forced empty selection', diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.ForeignApi.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.ForeignApi.test.js new file mode 100644 index 00000000..9d0fdf54 --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.ForeignApi.test.js @@ -0,0 +1,39 @@ +( function ( mw ) { + QUnit.module( 'mediawiki.ForeignApi', QUnit.newMwEnvironment( { + setup: function () { + this.server = this.sandbox.useFakeServer(); + this.server.respondImmediately = true; + this.clock = this.sandbox.useFakeTimers(); + }, + teardown: function () { + // https://github.com/jquery/jquery/issues/2453 + this.clock.tick(); + } + } ) ); + + QUnit.test( 'origin is included in GET requests', function ( assert ) { + QUnit.expect( 1 ); + var api = new mw.ForeignApi( '//localhost:4242/w/api.php' ); + + this.server.respond( function ( request ) { + assert.ok( request.url.match( /origin=/ ), 'origin is included in GET requests' ); + request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); + } ); + + api.get( {} ); + } ); + + QUnit.test( 'origin is included in POST requests', function ( assert ) { + QUnit.expect( 2 ); + var api = new mw.ForeignApi( '//localhost:4242/w/api.php' ); + + this.server.respond( function ( request ) { + assert.ok( request.requestBody.match( /origin=/ ), 'origin is included in POST request body' ); + assert.ok( request.url.match( /origin=/ ), 'origin is included in POST request URL, too' ); + request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); + } ); + + api.post( {} ); + } ); + +}( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js index b89526fb..56a346fb 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js @@ -1,15 +1,40 @@ -( function ( mw ) { +( function ( mw, $ ) { QUnit.module( 'mediawiki.api', QUnit.newMwEnvironment( { setup: function () { this.server = this.sandbox.useFakeServer(); + this.server.respondImmediately = true; + this.clock = this.sandbox.useFakeTimers(); + }, + teardown: function () { + // https://github.com/jquery/jquery/issues/2453 + this.clock.tick(); } } ) ); + function sequence( responses ) { + var i = 0; + return function ( request ) { + var response = responses[ i ]; + if ( response ) { + i++; + request.respond.apply( request, response ); + } + }; + } + + function sequenceBodies( status, headers, bodies ) { + jQuery.each( bodies, function ( i, body ) { + bodies[ i ] = [ status, headers, body ]; + } ); + return sequence( bodies ); + } + QUnit.test( 'Basic functionality', function ( assert ) { QUnit.expect( 2 ); - var api = new mw.Api(); + this.server.respond( [ 200, { 'Content-Type': 'application/json' }, '[]' ] ); + api.get( {} ) .done( function ( data ) { assert.deepEqual( data, [], 'If request succeeds without errors, resolve deferred' ); @@ -19,36 +44,26 @@ .done( function ( data ) { assert.deepEqual( data, [], 'Simple POST request' ); } ); - - this.server.respond( function ( request ) { - request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); - } ); } ); QUnit.test( 'API error', function ( assert ) { QUnit.expect( 1 ); - var api = new mw.Api(); + this.server.respond( [ 200, { 'Content-Type': 'application/json' }, + '{ "error": { "code": "unknown_action" } }' + ] ); + api.get( { action: 'doesntexist' } ) .fail( function ( errorCode ) { assert.equal( errorCode, 'unknown_action', 'API error should reject the deferred' ); } ); - - this.server.respond( function ( request ) { - request.respond( 200, { 'Content-Type': 'application/json' }, - '{ "error": { "code": "unknown_action" } }' - ); - } ); } ); QUnit.test( 'FormData support', function ( assert ) { QUnit.expect( 2 ); - var api = new mw.Api(); - api.post( { action: 'test' }, { contentType: 'multipart/form-data' } ); - this.server.respond( function ( request ) { if ( window.FormData ) { assert.ok( !request.url.match( /action=/ ), 'Request has no query string' ); @@ -59,27 +74,41 @@ } request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); } ); + + api.post( { action: 'test' }, { contentType: 'multipart/form-data' } ); } ); QUnit.test( 'Converting arrays to pipe-separated', function ( assert ) { QUnit.expect( 1 ); - var api = new mw.Api(); - api.get( { test: [ 'foo', 'bar', 'baz' ] } ); this.server.respond( function ( request ) { assert.ok( request.url.match( /test=foo%7Cbar%7Cbaz/ ), 'Pipe-separated value was submitted' ); request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); } ); + + api.get( { test: [ 'foo', 'bar', 'baz' ] } ); } ); - QUnit.test( 'getToken( pre-populated )', function ( assert ) { + QUnit.test( 'Omitting false booleans', function ( assert ) { QUnit.expect( 2 ); + var api = new mw.Api(); + + this.server.respond( function ( request ) { + assert.ok( !request.url.match( /foo/ ), 'foo query parameter is not present' ); + assert.ok( request.url.match( /bar=true/ ), 'bar query parameter is present with value true' ); + request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); + } ); + api.get( { foo: false, bar: true } ); + } ); + + QUnit.test( 'getToken() - cached', function ( assert ) { + QUnit.expect( 2 ); var api = new mw.Api(); // Get editToken for local wiki, this should not make - // a request as it should be retrieved from user.tokens. + // a request as it should be retrieved from mw.user.tokens. api.getToken( 'edit' ) .done( function ( token ) { assert.ok( token.length, 'Got a token' ); @@ -91,112 +120,128 @@ assert.equal( this.server.requests.length, 0, 'Requests made' ); } ); - QUnit.test( 'getToken()', function ( assert ) { - QUnit.expect( 5 ); + QUnit.test( 'getToken() - uncached', function ( assert ) { + QUnit.expect( 3 ); + var api = new mw.Api(); - var test = this, - api = new mw.Api(); + this.server.respondWith( /type=testuncached/, [ 200, { 'Content-Type': 'application/json' }, + '{ "tokens": { "testuncachedtoken": "good" } }' + ] ); // Get a token of a type that isn't prepopulated by user.tokens. // Could use "block" or "delete" here, but those could in theory // be added to user.tokens, use a fake one instead. - api.getToken( 'testaction' ) + api.getToken( 'testuncached' ) .done( function ( token ) { - assert.ok( token.length, 'Got testaction token' ); + assert.equal( token, 'good', 'The token' ); } ) .fail( function ( err ) { assert.equal( err, '', 'API error' ); } ); - api.getToken( 'testaction' ) + + api.getToken( 'testuncached' ) .done( function ( token ) { - assert.ok( token.length, 'Got testaction token (cached)' ); + assert.equal( token, 'good', 'The cached token' ); } ) .fail( function ( err ) { assert.equal( err, '', 'API error' ); } ); + assert.equal( this.server.requests.length, 1, 'Requests made' ); + } ); + + QUnit.test( 'getToken() - error', function ( assert ) { + QUnit.expect( 2 ); + var api = new mw.Api(); + + this.server.respondWith( /type=testerror/, sequenceBodies( 200, { 'Content-Type': 'application/json' }, + [ + '{ "error": { "code": "bite-me", "info": "Smite me, O Mighty Smiter" } }', + '{ "tokens": { "testerrortoken": "good" } }' + ] + ) ); + // Don't cache error (bug 65268) - api.getToken( 'testaction2' ) - .fail( function ( err ) { - assert.equal( err, 'bite-me', 'Expected error' ); - } ) - .always( function () { - // Make this request after the first one has finished. - // If we make it simultaneously we still want it to share - // the cache, but as soon as it is fulfilled as error we - // reject it so that the next one tries fresh. - api.getToken( 'testaction2' ) - .done( function ( token ) { - assert.ok( token.length, 'Got testaction2 token (error was not be cached)' ); - } ) - .fail( function ( err ) { - assert.equal( err, '', 'API error' ); - } ); - - assert.equal( test.server.requests.length, 3, 'Requests made' ); - - test.server.requests[2].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testaction2token": "0123abc" } }' - ); + api.getToken( 'testerror' ).fail( function ( err ) { + assert.equal( err, 'bite-me', 'Expected error' ); + + // Make this request after the first one has finished. + // If we make it simultaneously we still want it to share + // the cache, but as soon as it is fulfilled as error we + // reject it so that the next one tries fresh. + api.getToken( 'testerror' ).done( function ( token ) { + assert.equal( token, 'good', 'The token' ); } ); + } ); + } ); - this.server.requests[0].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testactiontoken": "0123abc" } }' - ); + QUnit.test( 'badToken()', function ( assert ) { + QUnit.expect( 2 ); + var api = new mw.Api(), + test = this; + + this.server.respondWith( /type=testbad/, sequenceBodies( 200, { 'Content-Type': 'application/json' }, + [ + '{ "tokens": { "testbadtoken": "bad" } }', + '{ "tokens": { "testbadtoken": "good" } }' + ] + ) ); + + api.getToken( 'testbad' ) + .then( function () { + api.badToken( 'testbad' ); + return api.getToken( 'testbad' ); + } ) + .then( function ( token ) { + assert.equal( token, 'good', 'The token' ); + assert.equal( test.server.requests.length, 2, 'Requests made' ); + } ); - this.server.requests[1].respond( 200, { 'Content-Type': 'application/json' }, - '{ "error": { "code": "bite-me", "info": "Smite me, O Mighty Smiter" } }' - ); } ); QUnit.test( 'postWithToken( tokenType, params )', function ( assert ) { QUnit.expect( 1 ); - var api = new mw.Api( { ajax: { url: '/postWithToken/api.php' } } ); - // - Requests token - // - Performs action=example - api.postWithToken( 'testsimpletoken', { action: 'example', key: 'foo' } ) + this.server.respondWith( 'GET', /type=testpost/, [ 200, { 'Content-Type': 'application/json' }, + '{ "tokens": { "testposttoken": "good" } }' + ] ); + this.server.respondWith( 'POST', /api/, function ( request ) { + if ( request.requestBody.match( /token=good/ ) ) { + request.respond( 200, { 'Content-Type': 'application/json' }, + '{ "example": { "foo": "quux" } }' + ); + } + } ); + + api.postWithToken( 'testpost', { action: 'example', key: 'foo' } ) .done( function ( data ) { assert.deepEqual( data, { example: { foo: 'quux' } } ); } ); - - this.server.requests[0].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testsimpletokentoken": "a-bad-token" } }' - ); - - this.server.requests[1].respond( 200, { 'Content-Type': 'application/json' }, - '{ "example": { "foo": "quux" } }' - ); } ); QUnit.test( 'postWithToken( tokenType, params with assert )', function ( assert ) { QUnit.expect( 2 ); - var api = new mw.Api( { ajax: { url: '/postWithToken/api.php' } } ); - api.postWithToken( 'testasserttoken', { action: 'example', key: 'foo', assert: 'user' } ) + this.server.respondWith( /assert=user/, [ 200, { 'Content-Type': 'application/json' }, + '{ "error": { "code": "assertuserfailed", "info": "Assertion failed" } }' + ] ); + + api.postWithToken( 'testassertpost', { action: 'example', key: 'foo', assert: 'user' } ) .fail( function ( errorCode ) { assert.equal( errorCode, 'assertuserfailed', 'getToken fails assert' ); } ); - assert.equal( this.server.requests.length, 1, 'Request for token made' ); - this.server.respondWith( /assert=user/, function ( request ) { - request.respond( - 200, - { 'Content-Type': 'application/json' }, - '{ "error": { "code": "assertuserfailed", "info": "Assertion failed" } }' - ); - } ); - - this.server.respond(); + assert.equal( this.server.requests.length, 1, 'Requests made' ); } ); QUnit.test( 'postWithToken( tokenType, params, ajaxOptions )', function ( assert ) { QUnit.expect( 3 ); - var api = new mw.Api(); + this.server.respond( [ 200, { 'Content-Type': 'application/json' }, '{ "example": "quux" }' ] ); + api.postWithToken( 'edit', { @@ -223,86 +268,111 @@ } ); assert.equal( this.server.requests.length, 2, 'Request made' ); - assert.equal( this.server.requests[0].requestHeaders['X-Foo'], 'Bar', 'Header sent' ); - - this.server.respond( function ( request ) { - request.respond( 200, { 'Content-Type': 'application/json' }, '{ "example": "quux" }' ); - } ); + assert.equal( this.server.requests[ 0 ].requestHeaders[ 'X-Foo' ], 'Bar', 'Header sent' ); } ); QUnit.test( 'postWithToken() - badtoken', function ( assert ) { QUnit.expect( 1 ); - var api = new mw.Api(); - // - Request: token + this.server.respondWith( /type=testbadtoken/, sequenceBodies( 200, { 'Content-Type': 'application/json' }, + [ + '{ "tokens": { "testbadtokentoken": "bad" } }', + '{ "tokens": { "testbadtokentoken": "good" } }' + ] + ) ); + this.server.respondWith( 'POST', /api/, function ( request ) { + if ( request.requestBody.match( /token=bad/ ) ) { + request.respond( 200, { 'Content-Type': 'application/json' }, + '{ "error": { "code": "badtoken" } }' + ); + } + if ( request.requestBody.match( /token=good/ ) ) { + request.respond( 200, { 'Content-Type': 'application/json' }, + '{ "example": { "foo": "quux" } }' + ); + } + } ); + + // - Request: new token -> bad // - Request: action=example -> badtoken error - // - Request: new token - // - Request: action=example + // - Request: new token -> good + // - Request: action=example -> success api.postWithToken( 'testbadtoken', { action: 'example', key: 'foo' } ) .done( function ( data ) { assert.deepEqual( data, { example: { foo: 'quux' } } ); } ); - - this.server.requests[0].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testbadtokentoken": "a-bad-token" } }' - ); - - this.server.requests[1].respond( 200, { 'Content-Type': 'application/json' }, - '{ "error": { "code": "badtoken" } }' - ); - - this.server.requests[2].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testbadtokentoken": "a-good-token" } }' - ); - - this.server.requests[3].respond( 200, { 'Content-Type': 'application/json' }, - '{ "example": { "foo": "quux" } }' - ); - } ); QUnit.test( 'postWithToken() - badtoken-cached', function ( assert ) { QUnit.expect( 2 ); + var sequenceA, + api = new mw.Api(); - var api = new mw.Api(); + this.server.respondWith( /type=testonce/, sequenceBodies( 200, { 'Content-Type': 'application/json' }, + [ + '{ "tokens": { "testoncetoken": "good-A" } }', + '{ "tokens": { "testoncetoken": "good-B" } }' + ] + ) ); + sequenceA = sequenceBodies( 200, { 'Content-Type': 'application/json' }, + [ + '{ "example": { "value": "A" } }', + '{ "error": { "code": "badtoken" } }' + ] + ); + this.server.respondWith( 'POST', /api/, function ( request ) { + if ( request.requestBody.match( /token=good-A/ ) ) { + sequenceA( request ); + } else if ( request.requestBody.match( /token=good-B/ ) ) { + request.respond( 200, { 'Content-Type': 'application/json' }, + '{ "example": { "value": "B" } }' + ); + } + } ); - // - Request: token + // - Request: new token -> A // - Request: action=example - api.postWithToken( 'testbadtokencache', { action: 'example', key: 'foo' } ) + api.postWithToken( 'testonce', { action: 'example', key: 'foo' } ) .done( function ( data ) { - assert.deepEqual( data, { example: { foo: 'quux' } } ); + assert.deepEqual( data, { example: { value: 'A' } } ); } ); - // - Cache: Try previously cached token - // - Request: action=example -> badtoken error - // - Request: new token - // - Request: action=example - api.postWithToken( 'testbadtokencache', { action: 'example', key: 'bar' } ) + // - Request: action=example w/ token A -> badtoken error + // - Request: new token -> B + // - Request: action=example w/ token B -> success + api.postWithToken( 'testonce', { action: 'example', key: 'bar' } ) .done( function ( data ) { - assert.deepEqual( data, { example: { bar: 'quux' } } ); + assert.deepEqual( data, { example: { value: 'B' } } ); } ); + } ); - this.server.requests[0].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testbadtokencachetoken": "a-good-token-once" } }' - ); - - this.server.requests[1].respond( 200, { 'Content-Type': 'application/json' }, - '{ "example": { "foo": "quux" } }' - ); - - this.server.requests[2].respond( 200, { 'Content-Type': 'application/json' }, - '{ "error": { "code": "badtoken" } }' - ); - - this.server.requests[3].respond( 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testbadtokencachetoken": "a-good-new-token" } }' - ); - - this.server.requests[4].respond( 200, { 'Content-Type': 'application/json' }, - '{ "example": { "bar": "quux" } }' - ); - + QUnit.module( 'mediawiki.api (2)', { + setup: function () { + var self = this, + requests = this.requests = []; + this.api = new mw.Api(); + this.sandbox.stub( jQuery, 'ajax', function () { + var request = $.extend( { + abort: self.sandbox.spy() + }, $.Deferred() ); + requests.push( request ); + return request; + } ); + } } ); -}( mediaWiki ) ); + QUnit.test( '#abort', 3, function ( assert ) { + this.api.get( { + a: 1 + } ); + this.api.post( { + b: 2 + } ); + this.api.abort(); + assert.ok( this.requests.length === 2, 'Check both requests triggered' ); + $.each( this.requests, function ( i, request ) { + assert.ok( request.abort.calledOnce, 'abort request number ' + i ); + } ); + } ); +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js new file mode 100644 index 00000000..10fcd5da --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js @@ -0,0 +1,35 @@ +( function ( mw, $ ) { + QUnit.module( 'mediawiki.api.upload', QUnit.newMwEnvironment( {} ) ); + + QUnit.test( 'Basic functionality', function ( assert ) { + QUnit.expect( 2 ); + var api = new mw.Api(); + assert.ok( api.upload ); + assert.throws( function () { + api.upload(); + } ); + } ); + + QUnit.test( 'Set up iframe upload', function ( assert ) { + QUnit.expect( 5 ); + var $iframe, $form, $input, + api = new mw.Api(); + + this.sandbox.stub( api, 'getEditToken', function () { + return $.Deferred().promise(); + } ); + + api.uploadWithIframe( $( '<input>' )[ 0 ], { filename: 'Testing API upload.jpg' } ); + + $iframe = $( 'iframe' ); + $form = $( 'form.mw-api-upload-form' ); + $input = $form.find( 'input[name=filename]' ); + + assert.ok( $form.length > 0 ); + assert.ok( $input.length > 0 ); + assert.ok( $iframe.length > 0 ); + assert.strictEqual( $form.prop( 'target' ), $iframe.prop( 'id' ) ); + assert.strictEqual( $input.val(), 'Testing API upload.jpg' ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js index 5965ab7b..64a51847 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js @@ -17,12 +17,12 @@ } ); api.watch( [ 'Foo' ] ).done( function ( items ) { - assert.equal( items[0].title, 'Foo' ); + assert.equal( items[ 0 ].title, 'Foo' ); } ); api.watch( [ 'Foo', 'Bar' ] ).done( function ( items ) { - assert.equal( items[0].title, 'Foo' ); - assert.equal( items[1].title, 'Bar' ); + assert.equal( items[ 0 ].title, 'Foo' ); + assert.equal( items[ 1 ].title, 'Bar' ); } ); // Requests are POST, match requestBody instead of url diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js new file mode 100644 index 00000000..2388497a --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js @@ -0,0 +1,38 @@ +( function ( mw, $ ) { + QUnit.module( 'mediawiki.RegExp' ); + + QUnit.test( 'escape', 16, function ( assert ) { + var specials, normal; + + specials = [ + '\\', + '{', + '}', + '(', + ')', + '[', + ']', + '|', + '.', + '?', + '*', + '+', + '-', + '^', + '$' + ]; + + normal = [ + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + 'abcdefghijklmnopqrstuvwxyz', + '0123456789' + ].join( '' ); + + $.each( specials, function ( i, str ) { + assert.propEqual( str.match( new RegExp( mw.RegExp.escape( str ) ) ), [ str ], 'Match ' + str ); + } ); + + assert.equal( mw.RegExp.escape( normal ), normal, 'Alphanumerals are left alone' ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js index c0afe07c..641a5a5d 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js @@ -100,33 +100,35 @@ // testing custom / localized namespace 100: 'Penguins' }, + // jscs: disable requireCamelCaseOrUpperCaseIdentifiers wgNamespaceIds: { - 'media': -2, - 'special': -1, + media: -2, + special: -1, '': 0, - 'talk': 1, - 'user': 2, - 'user_talk': 3, - 'wikipedia': 4, - 'wikipedia_talk': 5, - 'file': 6, - 'file_talk': 7, - 'mediawiki': 8, - 'mediawiki_talk': 9, - 'template': 10, - 'template_talk': 11, - 'help': 12, - 'help_talk': 13, - 'category': 14, - 'category_talk': 15, - 'image': 6, - 'image_talk': 7, - 'project': 4, - 'project_talk': 5, + talk: 1, + user: 2, + user_talk: 3, + wikipedia: 4, + wikipedia_talk: 5, + file: 6, + file_talk: 7, + mediawiki: 8, + mediawiki_talk: 9, + template: 10, + template_talk: 11, + help: 12, + help_talk: 13, + category: 14, + category_talk: 15, + image: 6, + image_talk: 7, + project: 4, + project_talk: 5, // Testing custom namespaces and aliases - 'penguins': 100, - 'antarctic_waterfowl': 100 + penguins: 100, + antarctic_waterfowl: 100 }, + // jscs: enable requireCamelCaseOrUpperCaseIdentifiers wgCaseSensitiveNamespaces: [] } } ) ); @@ -134,14 +136,14 @@ QUnit.test( 'constructor', cases.invalid.length, function ( assert ) { var i, title; for ( i = 0; i < cases.valid.length; i++ ) { - title = new mw.Title( cases.valid[i] ); + title = new mw.Title( cases.valid[ i ] ); } for ( i = 0; i < cases.invalid.length; i++ ) { /*jshint loopfunc:true */ - title = cases.invalid[i]; + title = cases.invalid[ i ]; assert.throws( function () { return new mw.Title( title ); - }, cases.invalid[i] ); + }, cases.invalid[ i ] ); } } ); @@ -149,21 +151,21 @@ var i; for ( i = 0; i < cases.valid.length; i++ ) { assert.equal( - $.type( mw.Title.newFromText( cases.valid[i] ) ), + $.type( mw.Title.newFromText( cases.valid[ i ] ) ), 'object', - cases.valid[i] + cases.valid[ i ] ); } for ( i = 0; i < cases.invalid.length; i++ ) { assert.equal( - $.type( mw.Title.newFromText( cases.invalid[i] ) ), + $.type( mw.Title.newFromText( cases.invalid[ i ] ) ), 'null', - cases.invalid[i] + cases.invalid[ i ] ); } } ); - QUnit.test( 'Basic parsing', 12, function ( assert ) { + QUnit.test( 'Basic parsing', 21, function ( assert ) { var title; title = new mw.Title( 'File:Foo_bar.JPG' ); @@ -181,6 +183,17 @@ title = new mw.Title( 'Foo#bar' ); assert.equal( title.getPrefixedText(), 'Foo' ); assert.equal( title.getFragment(), 'bar' ); + + title = new mw.Title( '.foo' ); + assert.equal( title.getPrefixedText(), '.foo' ); + assert.equal( title.getName(), '' ); + assert.equal( title.getNameText(), '' ); + assert.equal( title.getExtension(), 'foo' ); + assert.equal( title.getDotExtension(), '.foo' ); + assert.equal( title.getMain(), '.foo' ); + assert.equal( title.getMainText(), '.foo' ); + assert.equal( title.getPrefixedDb(), '.foo' ); + assert.equal( title.getPrefixedText(), '.foo' ); } ); QUnit.test( 'Transformation', 11, function ( assert ) { @@ -266,7 +279,7 @@ assert.equal( title.toString(), 'Article', 'Default config: No sensitive namespaces by default. First-letter becomes uppercase' ); // $wgCapitalLinks = false; - mw.config.set( 'wgCaseSensitiveNamespaces', [0, -2, 1, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15] ); + mw.config.set( 'wgCaseSensitiveNamespaces', [ 0, -2, 1, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15 ] ); title = new mw.Title( 'article' ); assert.equal( title.toString(), 'article', '$wgCapitalLinks=false: Article namespace is sensitive, first-letter case stays lowercase' ); @@ -309,8 +322,8 @@ assert.strictEqual( title.exists(), null, 'Return null with empty existance registry' ); // Basic registry, checks default to boolean - mw.Title.exist.set( ['Does_exist', 'User_talk:NeilK', 'Wikipedia:Sandbox_rules'], true ); - mw.Title.exist.set( ['Does_not_exist', 'User:John', 'Foobar'], false ); + mw.Title.exist.set( [ 'Does_exist', 'User_talk:NeilK', 'Wikipedia:Sandbox_rules' ], true ); + mw.Title.exist.set( [ 'Does_not_exist', 'User:John', 'Foobar' ], false ); title = new mw.Title( 'Project:Sandbox rules' ); assert.assertTrue( title.exists(), 'Return true for page titles marked as existing' ); @@ -327,7 +340,7 @@ title = new mw.Title( 'Foobar' ); assert.equal( title.getUrl(), '/wiki/Foobar', 'Basic functionality, getUrl uses mw.util.getUrl' ); - assert.equal( title.getUrl({ action: 'edit' }), '/wiki/Foobar?action=edit', 'Basic functionality, \'params\' parameter' ); + assert.equal( title.getUrl( { action: 'edit' } ), '/wiki/Foobar?action=edit', 'Basic functionality, \'params\' parameter' ); title = new mw.Title( 'John Doe', 3 ); assert.equal( title.getUrl(), '/wiki/User_talk:John_Doe', 'Escaping in title and namespace for urls' ); @@ -420,7 +433,7 @@ ]; for ( i = 0; i < cases.length; i++ ) { - thisCase = cases[i]; + thisCase = cases[ i ]; title = mw.Title.newFromImg( { src: thisCase.url } ); if ( thisCase.nameText !== undefined ) { @@ -467,28 +480,62 @@ ]; for ( i = 0; i < cases.length; i++ ) { - thisCase = cases[i]; + thisCase = cases[ i ]; title = mw.Title.newFromText( thisCase.text ); assert.equal( title.getRelativeText( thisCase.relativeTo ), thisCase.expectedResult ); } } ); - QUnit.test( 'newFromUserInput', 8, function ( assert ) { + QUnit.test( 'normalizeExtension', 5, function ( assert ) { + var extension, i, thisCase, prefix, + cases = [ + { + extension: 'png', + expected: 'png', + description: 'Extension already in canonical form' + }, + { + extension: 'PNG', + expected: 'png', + description: 'Extension lowercased in canonical form' + }, + { + extension: 'jpeg', + expected: 'jpg', + description: 'Extension changed in canonical form' + }, + { + extension: 'JPEG', + expected: 'jpg', + description: 'Extension lowercased and changed in canonical form' + }, + { + extension: '~~~', + expected: '', + description: 'Extension invalid and discarded' + } + ]; + + for ( i = 0; i < cases.length; i++ ) { + thisCase = cases[ i ]; + extension = mw.Title.normalizeExtension( thisCase.extension ); + + prefix = '[' + thisCase.description + '] '; + assert.equal( extension, thisCase.expected, prefix + 'Extension as expected' ); + } + } ); + + QUnit.test( 'newFromUserInput', 12, function ( assert ) { var title, i, thisCase, prefix, cases = [ { title: 'DCS0001557854455.JPG', - defaultNamespace: 0, - options: { - fileExtension: 'PNG' - }, expected: 'DCS0001557854455.JPG', description: 'Title in normal namespace without anything invalid but with "file extension"' }, { title: 'MediaWiki:Msg-awesome', - defaultNamespace: undefined, expected: 'MediaWiki:Msg-awesome', description: 'Full title (page in MediaWiki namespace) supplied as string' }, @@ -506,11 +553,21 @@ }, expected: 'File:The/Mw/Sound.kml', description: 'Page in File-namespace without explicit options' + }, + { + title: 'File:Foo.JPEG', + expected: 'File:Foo.JPEG', + description: 'Page in File-namespace with non-canonical extension' + }, + { + title: 'File:Foo.JPEG ', + expected: 'File:Foo.JPEG', + description: 'Page in File-namespace with trailing whitespace' } ]; for ( i = 0; i < cases.length; i++ ) { - thisCase = cases[i]; + thisCase = cases[ i ]; title = mw.Title.newFromUserInput( thisCase.title, thisCase.defaultNamespace, thisCase.options ); if ( thisCase.expected !== undefined ) { @@ -524,15 +581,14 @@ } } ); - QUnit.test( 'newFromFileName', 62, function ( assert ) { + QUnit.test( 'newFromFileName', 54, function ( assert ) { var title, i, thisCase, prefix, cases = [ { fileName: 'DCS0001557854455.JPG', typeOfName: 'Standard camera output', nameText: 'DCS0001557854455', - prefixedText: 'File:DCS0001557854455.JPG', - extensionDesired: 'jpg' + prefixedText: 'File:DCS0001557854455.JPG' }, { fileName: 'File:Sample.png', @@ -544,8 +600,7 @@ fileName: 'Treppe 2222 Test upload.jpg', typeOfName: 'File name with spaces in it and lower case file extension', nameText: 'Treppe 2222 Test upload', - prefixedText: 'File:Treppe 2222 Test upload.jpg', - extensionDesired: 'JPG' + prefixedText: 'File:Treppe 2222 Test upload.jpg' }, { fileName: 'I contain a \ttab.jpg', @@ -602,20 +657,6 @@ prefixedText: 'File:Dot. dot. dot' }, { - fileName: 'dot. dot ._dot', - typeOfName: 'File name with different file extension desired', - nameText: 'Dot. dot . dot', - prefixedText: 'File:Dot. dot . dot.png', - extensionDesired: 'png' - }, - { - fileName: 'fileWOExt', - typeOfName: 'File W/O extension with extension desired', - nameText: 'FileWOExt', - prefixedText: 'File:FileWOExt.png', - extensionDesired: 'png' - }, - { fileName: '𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢𠻗𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵𢫕𢭃𢯊𢱑𢱕𢳂𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵𢫕𢭃𢯊𢱑𢱕𢳂.png', typeOfName: 'File name longer than 240 bytes', nameText: '𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢𠻗𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵𢫕𢭃𢯊𢱑𢱕𢳂𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵', @@ -632,8 +673,8 @@ ]; for ( i = 0; i < cases.length; i++ ) { - thisCase = cases[i]; - title = mw.Title.newFromFileName( thisCase.fileName, thisCase.extensionDesired ); + thisCase = cases[ i ]; + title = mw.Title.newFromFileName( thisCase.fileName ); if ( thisCase.nameText !== undefined ) { prefix = '[' + thisCase.typeOfName + '] '; diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js index ba366553..51374bd8 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js @@ -11,7 +11,7 @@ } } ) ); - $.each( [true, false], function ( i, strictMode ) { + $.each( [ true, false ], function ( i, strictMode ) { QUnit.test( 'Basic construction and properties (' + ( strictMode ? '' : 'non-' ) + 'strict mode)', 2, function ( assert ) { var uriString, uri; uriString = 'http://www.ietf.org/rfc/rfc2396.txt'; @@ -76,8 +76,8 @@ } ); assert.strictEqual( uri.query.n, '1', 'Simple parameter with overrideKeys:false' ); - assert.strictEqual( uri.query.m[0], 'foo', 'Order of multi-value parameters with overrideKeys:true' ); - assert.strictEqual( uri.query.m[1], 'bar', 'Order of multi-value parameters with overrideKeys:true' ); + assert.strictEqual( uri.query.m[ 0 ], 'foo', 'Order of multi-value parameters with overrideKeys:true' ); + assert.strictEqual( uri.query.m[ 1 ], 'bar', 'Order of multi-value parameters with overrideKeys:true' ); assert.strictEqual( uri.query.m.length, 2, 'Number of mult-value field is correct' ); uri = new mw.Uri( 'ftp://usr:pwd@192.0.2.16/' ); @@ -270,7 +270,7 @@ assert.deepEqual( original.query, - { 'one': '1', 'two': '2' }, + { one: '1', two: '2' }, 'Properties is deep cloned (bug 37708)' ); } ); @@ -367,7 +367,7 @@ host: 'example.com', port: undefined, path: '/wiki/Foo', - query: { 'v': '2' }, + query: { v: '2' }, fragment: undefined }, 'basic object properties' diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js index 779a0ed4..399db914 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js @@ -65,9 +65,9 @@ QUnit.test( 'Plural Test for ' + langCode, tests.length, function ( assert ) { for ( var i = 0; i < tests.length; i++ ) { assert.equal( - mw.language.convertPlural( tests[i][0], tests[i][1] ), - tests[i][2], - tests[i][3] + mw.language.convertPlural( tests[ i ][ 0 ], tests[ i ][ 1 ] ), + tests[ i ][ 2 ], + tests[ i ][ 3 ] ); } } ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js index f5f199ea..7a13f0f7 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js @@ -102,7 +102,7 @@ } ); call = $.cookie.lastCall.args; - assert.strictEqual( call[0], 'myPrefixfoo' ); + assert.strictEqual( call[ 0 ], 'myPrefixfoo' ); assert.deepEqual( call[ 2 ], { expires: expiryDate, domain: 'myDomain', @@ -122,7 +122,7 @@ } ); call = $.cookie.lastCall.args; - assert.strictEqual( call[0], 'myPrefixfoo' ); + assert.strictEqual( call[ 0 ], 'myPrefixfoo' ); assert.deepEqual( call[ 2 ], { expires: date, domain: 'myDomain', diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js index 7c3f1ecb..587c8931 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js @@ -7,7 +7,7 @@ errorUrl = 'http://example.com', errorLine = '123', errorColumn = '45', - errorObject = new Error( 'Foo'), + errorObject = new Error( 'Foo' ), oldHandler = this.sandbox.stub(); this.sandbox.stub( mw, 'track' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.experiments.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.experiments.test.js new file mode 100644 index 00000000..774b2053 --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.experiments.test.js @@ -0,0 +1,63 @@ +( function ( mw ) { + + var getBucket = mw.experiments.getBucket; + + function createExperiment() { + return { + name: 'experiment', + enabled: true, + buckets: { + control: 0.25, + A: 0.25, + B: 0.25, + C: 0.25 + } + }; + } + + QUnit.module( 'mediawiki.experiments' ); + + QUnit.test( 'getBucket( experiment, token )', 4, function ( assert ) { + var experiment = createExperiment(), + token = '123457890'; + + assert.equal( + getBucket( experiment, token ), + getBucket( experiment, token ), + 'It returns the same bucket for the same experiment-token pair.' + ); + + // -------- + experiment = createExperiment(); + experiment.buckets = { + A: 0.314159265359 + }; + + assert.equal( + 'A', + getBucket( experiment, token ), + 'It returns the bucket if only one is defined.' + ); + + // -------- + experiment = createExperiment(); + experiment.enabled = false; + + assert.equal( + 'control', + getBucket( experiment, token ), + 'It returns "control" if the experiment is disabled.' + ); + + // -------- + experiment = createExperiment(); + experiment.buckets = {}; + + assert.equal( + 'control', + getBucket( experiment, token ), + 'It returns "control" if the experiment doesn\'t have any buckets.' + ); + } ); + +}( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js index 7e23e2ff..c088554a 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js @@ -1,5 +1,6 @@ ( function ( mw, $ ) { - var formatText, formatParse, formatnumTests, specialCharactersPageName, expectedListUsers, expectedEntrypoints, + var formatText, formatParse, formatnumTests, specialCharactersPageName, expectedListUsers, + expectedListUsersSitename, expectedEntrypoints, mwLanguageCache = {}, hasOwn = Object.hasOwnProperty; @@ -16,6 +17,8 @@ specialCharactersPageName = '"Who" wants to be a millionaire & live on \'Exotic Island\'?'; expectedListUsers = '注册<a title="Special:ListUsers" href="/wiki/Special:ListUsers">用户</a>'; + expectedListUsersSitename = '注册<a title="Special:ListUsers" href="/wiki/Special:ListUsers">用户' + + mw.config.get( 'wgSiteName' ) + '</a>'; expectedEntrypoints = '<a href="https://www.mediawiki.org/wiki/Manual:index.php">index.php</a>'; @@ -52,6 +55,7 @@ 'see-portal-url': '{{Int:portal-url}} is an important community page.', 'jquerymsg-test-statistics-users': '注册[[Special:ListUsers|用户]]', + 'jquerymsg-test-statistics-users-sitename': '注册[[Special:ListUsers|用户{{SITENAME}}]]', 'jquerymsg-test-version-entrypoints-index-php': '[https://www.mediawiki.org/wiki/Manual:index.php index.php]', @@ -70,7 +74,7 @@ */ function getMwLanguage( langCode ) { if ( !hasOwn.call( mwLanguageCache, langCode ) ) { - mwLanguageCache[langCode] = $.ajax( { + mwLanguageCache[ langCode ] = $.ajax( { url: mw.util.wikiScript( 'load' ), data: { skin: mw.config.get( 'skin' ), @@ -88,25 +92,37 @@ return mw.language; } ); } - return mwLanguageCache[langCode]; + return mwLanguageCache[ langCode ]; } /** * @param {Function[]} tasks List of functions that perform tasks * that may be asynchronous. Invoke the callback parameter when done. - * @param {Function} done When all tasks are done. - * @return + * @param {Function} complete Called when all tasks are done, or when the sequence is aborted. */ - function process( tasks, done ) { - function run() { + function process( tasks, complete ) { + /*jshint latedef:false */ + function abort() { + tasks.splice( 0, tasks.length ); + next(); + } + function next() { + if ( !tasks ) { + // This happens if after the process is completed, one of our callbacks is + // invoked. This can happen if a test timed out but the process was still + // running. In that case, ignore it. Don't invoke complete() a second time. + return; + } var task = tasks.shift(); if ( task ) { - task( run ); + task( next, abort ); } else { - done(); + // Remove tasks list to indicate the process is final. + tasks = null; + complete(); } } - run(); + next(); } QUnit.test( 'Replace', 16, function ( assert ) { @@ -306,9 +322,9 @@ QUnit.test( 'Match PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) { mw.messages.set( mw.libs.phpParserData.messages ); var tasks = $.map( mw.libs.phpParserData.tests, function ( test ) { - return function ( next ) { + return function ( next, abort ) { getMwLanguage( test.lang ) - .done( function ( langClass ) { + .then( function ( langClass ) { mw.config.set( 'wgUserLanguage', test.lang ); var parser = new mw.jqueryMsg.parser( { language: langClass } ); assert.equal( @@ -316,11 +332,10 @@ test.result, test.name ); - } ) - .fail( function () { + }, function () { assert.ok( false, 'Language "' + test.lang + '" failed to load.' ); } ) - .always( next ); + .then( next, abort ); }; } ); @@ -328,8 +343,9 @@ process( tasks, QUnit.start ); } ); - QUnit.test( 'Links', 6, function ( assert ) { - var expectedDisambiguationsText, + QUnit.test( 'Links', 14, function ( assert ) { + var testCases, + expectedDisambiguationsText, expectedMultipleBars, expectedSpecialCharacters; @@ -360,12 +376,24 @@ // Pipe trick is not supported currently, but should not parse as text either. mw.messages.set( 'pipe-trick', '[[Tampa, Florida|]]' ); + mw.messages.set( 'reverse-pipe-trick', '[[|Tampa, Florida]]' ); + mw.messages.set( 'empty-link', '[[]]' ); this.suppressWarnings(); assert.equal( formatParse( 'pipe-trick' ), '[[Tampa, Florida|]]', 'Pipe trick should not be parsed.' ); + assert.equal( + formatParse( 'reverse-pipe-trick' ), + '[[|Tampa, Florida]]', + 'Reverse pipe trick should not be parsed.' + ); + assert.equal( + formatParse( 'empty-link' ), + '[[]]', + 'Empty link should not be parsed.' + ); this.restoreWarnings(); expectedMultipleBars = '<a title="Main Page" href="/wiki/Main_Page">Main|Page</a>'; @@ -384,20 +412,159 @@ expectedSpecialCharacters, 'Special characters' ); + + mw.messages.set( 'leading-colon', '[[:File:Foo.jpg]]' ); + assert.htmlEqual( + formatParse( 'leading-colon' ), + '<a title="File:Foo.jpg" href="/wiki/File:Foo.jpg">File:Foo.jpg</a>', + 'Leading colon in links is stripped' + ); + + assert.htmlEqual( + formatParse( 'jquerymsg-test-statistics-users-sitename' ), + expectedListUsersSitename, + 'Piped wikilink with parser function in the text' + ); + + testCases = [ + [ + 'extlink-html-full', + 'asd [http://example.org <strong>Example</strong>] asd', + 'asd <a href="http://example.org"><strong>Example</strong></a> asd' + ], + [ + 'extlink-html-partial', + 'asd [http://example.org foo <strong>Example</strong> bar] asd', + 'asd <a href="http://example.org">foo <strong>Example</strong> bar</a> asd' + ], + [ + 'wikilink-html-full', + 'asd [[Example|<strong>Example</strong>]] asd', + 'asd <a title="Example" href="/wiki/Example"><strong>Example</strong></a> asd' + ], + [ + 'wikilink-html-partial', + 'asd [[Example|foo <strong>Example</strong> bar]] asd', + 'asd <a title="Example" href="/wiki/Example">foo <strong>Example</strong> bar</a> asd' + ] + ]; + + $.each( testCases, function () { + var + key = this[ 0 ], + input = this[ 1 ], + output = this[ 2 ]; + mw.messages.set( key, input ); + assert.htmlEqual( + formatParse( key ), + output, + 'HTML in links: ' + key + ); + } ); + } ); + + QUnit.test( 'Replacements in links', 14, function ( assert ) { + var testCases = [ + [ + 'extlink-param-href-full', + 'asd [$1 Example] asd', + 'asd <a href="http://example.com">Example</a> asd' + ], + [ + 'extlink-param-href-partial', + 'asd [$1/example Example] asd', + 'asd <a href="http://example.com/example">Example</a> asd' + ], + [ + 'extlink-param-text-full', + 'asd [http://example.org $2] asd', + 'asd <a href="http://example.org">Text</a> asd' + ], + [ + 'extlink-param-text-partial', + 'asd [http://example.org Example $2] asd', + 'asd <a href="http://example.org">Example Text</a> asd' + ], + [ + 'extlink-param-both-full', + 'asd [$1 $2] asd', + 'asd <a href="http://example.com">Text</a> asd' + ], + [ + 'extlink-param-both-partial', + 'asd [$1/example Example $2] asd', + 'asd <a href="http://example.com/example">Example Text</a> asd' + ], + [ + 'wikilink-param-href-full', + 'asd [[$1|Example]] asd', + 'asd <a title="Example" href="/wiki/Example">Example</a> asd' + ], + [ + 'wikilink-param-href-partial', + 'asd [[$1/Test|Example]] asd', + 'asd <a title="Example/Test" href="/wiki/Example/Test">Example</a> asd' + ], + [ + 'wikilink-param-text-full', + 'asd [[Example|$2]] asd', + 'asd <a title="Example" href="/wiki/Example">Text</a> asd' + ], + [ + 'wikilink-param-text-partial', + 'asd [[Example|Example $2]] asd', + 'asd <a title="Example" href="/wiki/Example">Example Text</a> asd' + ], + [ + 'wikilink-param-both-full', + 'asd [[$1|$2]] asd', + 'asd <a title="Example" href="/wiki/Example">Text</a> asd' + ], + [ + 'wikilink-param-both-partial', + 'asd [[$1/Test|Example $2]] asd', + 'asd <a title="Example/Test" href="/wiki/Example/Test">Example Text</a> asd' + ], + [ + 'wikilink-param-unpiped-full', + 'asd [[$1]] asd', + 'asd <a title="Example" href="/wiki/Example">Example</a> asd' + ], + [ + 'wikilink-param-unpiped-partial', + 'asd [[$1/Test]] asd', + 'asd <a title="Example/Test" href="/wiki/Example/Test">Example/Test</a> asd' + ] + ]; + + $.each( testCases, function () { + var + key = this[ 0 ], + input = this[ 1 ], + output = this[ 2 ], + paramHref = key.slice( 0, 8 ) === 'wikilink' ? 'Example' : 'http://example.com', + paramText = 'Text'; + mw.messages.set( key, input ); + assert.htmlEqual( + formatParse( key, paramHref, paramText ), + output, + 'Replacements in links: ' + key + ); + } ); } ); -// Tests that {{-transformation vs. general parsing are done as requested + // Tests that {{-transformation vs. general parsing are done as requested QUnit.test( 'Curly brace transformation', 16, function ( assert ) { var oldUserLang = mw.config.get( 'wgUserLanguage' ); - assertBothModes( assert, ['gender-msg', 'Bob', 'male'], 'Bob: blue', 'gender is resolved' ); + assertBothModes( assert, [ 'gender-msg', 'Bob', 'male' ], 'Bob: blue', 'gender is resolved' ); - assertBothModes( assert, ['plural-msg', 5], 'Found 5 items', 'plural is resolved' ); + assertBothModes( assert, [ 'plural-msg', 5 ], 'Found 5 items', 'plural is resolved' ); - assertBothModes( assert, ['grammar-msg'], 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'grammar is resolved' ); + assertBothModes( assert, [ 'grammar-msg' ], 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'grammar is resolved' ); mw.config.set( 'wgUserLanguage', 'en' ); - assertBothModes( assert, ['formatnum-msg', '987654321.654321'], '987,654,321.654', 'formatnum is resolved' ); + assertBothModes( assert, [ 'formatnum-msg', '987654321.654321' ], '987,654,321.654', 'formatnum is resolved' ); // Test non-{{ wikitext, where behavior differs @@ -501,7 +668,7 @@ 'curly-brace': '{{int:message}}', 'single-square-bracket': '[https://www.mediawiki.org/ MediaWiki]', 'double-square-bracket': '[[Some page]]', - 'regular': 'Other message' + regular: 'Other message' } ); oldGMF = mw.jqueryMsg.getMessageFunction; @@ -518,7 +685,7 @@ outerCalled = false; innerCalled = false; message = mw.message( key ); - message[format](); + message[ format ](); assert.strictEqual( outerCalled, shouldCall, 'Outer function called for ' + key ); assert.strictEqual( innerCalled, shouldCall, 'Inner function called for ' + key ); } @@ -645,9 +812,9 @@ mw.messages.set( 'formatnum-msg', '{{formatnum:$1}}' ); mw.messages.set( 'formatnum-msg-int', '{{formatnum:$1|R}}' ); var queue = $.map( formatnumTests, function ( test ) { - return function ( next ) { + return function ( next, abort ) { getMwLanguage( test.lang ) - .done( function ( langClass ) { + .then( function ( langClass ) { mw.config.set( 'wgUserLanguage', test.lang ); var parser = new mw.jqueryMsg.parser( { language: langClass } ); assert.equal( @@ -656,11 +823,10 @@ test.result, test.description ); - } ) - .fail( function () { + }, function () { assert.ok( false, 'Language "' + test.lang + '" failed to load' ); } ) - .always( next ); + .then( next, abort ); }; } ); QUnit.stop(); @@ -671,16 +837,16 @@ QUnit.test( 'HTML', 26, function ( assert ) { mw.messages.set( 'jquerymsg-italics-msg', '<i>Very</i> important' ); - assertBothModes( assert, ['jquerymsg-italics-msg'], mw.messages.get( 'jquerymsg-italics-msg' ), 'Simple italics unchanged' ); + assertBothModes( assert, [ 'jquerymsg-italics-msg' ], mw.messages.get( 'jquerymsg-italics-msg' ), 'Simple italics unchanged' ); mw.messages.set( 'jquerymsg-bold-msg', '<b>Strong</b> speaker' ); - assertBothModes( assert, ['jquerymsg-bold-msg'], mw.messages.get( 'jquerymsg-bold-msg' ), 'Simple bold unchanged' ); + assertBothModes( assert, [ 'jquerymsg-bold-msg' ], mw.messages.get( 'jquerymsg-bold-msg' ), 'Simple bold unchanged' ); mw.messages.set( 'jquerymsg-bold-italics-msg', 'It is <b><i>key</i></b>' ); - assertBothModes( assert, ['jquerymsg-bold-italics-msg'], mw.messages.get( 'jquerymsg-bold-italics-msg' ), 'Bold and italics nesting order preserved' ); + assertBothModes( assert, [ 'jquerymsg-bold-italics-msg' ], mw.messages.get( 'jquerymsg-bold-italics-msg' ), 'Bold and italics nesting order preserved' ); mw.messages.set( 'jquerymsg-italics-bold-msg', 'It is <i><b>vital</b></i>' ); - assertBothModes( assert, ['jquerymsg-italics-bold-msg'], mw.messages.get( 'jquerymsg-italics-bold-msg' ), 'Italics and bold nesting order preserved' ); + assertBothModes( assert, [ 'jquerymsg-italics-bold-msg' ], mw.messages.get( 'jquerymsg-italics-bold-msg' ), 'Italics and bold nesting order preserved' ); mw.messages.set( 'jquerymsg-italics-with-link', 'An <i>italicized [[link|wiki-link]]</i>' ); @@ -737,19 +903,17 @@ 'Mismatched HTML start and end tag treated as text' ); - // TODO (mattflaschen, 2013-03-18): It's not a security issue, but there's no real - // reason the htmlEmitter span needs to be here. It's an artifact of how emitting works. mw.messages.set( 'jquerymsg-script-and-external-link', '<script>alert( "jquerymsg-script-and-external-link test" );</script> [http://example.com <i>Foo</i> bar]' ); assert.htmlEqual( formatParse( 'jquerymsg-script-and-external-link' ), - '<script>alert( "jquerymsg-script-and-external-link test" );</script> <a href="http://example.com"><span class="mediaWiki_htmlEmitter"><i>Foo</i> bar</span></a>', + '<script>alert( "jquerymsg-script-and-external-link test" );</script> <a href="http://example.com"><i>Foo</i> bar</a>', 'HTML tags in external links not interfering with escaping of other tags' ); mw.messages.set( 'jquerymsg-link-script', '[http://example.com <script>alert( "jquerymsg-link-script test" );</script>]' ); assert.htmlEqual( formatParse( 'jquerymsg-link-script' ), - '<a href="http://example.com"><span class="mediaWiki_htmlEmitter"><script>alert( "jquerymsg-link-script test" );</script></span></a>', + '<a href="http://example.com"><script>alert( "jquerymsg-link-script test" );</script></a>', 'Non-whitelisted HTML tag in external link anchor treated as text' ); @@ -792,7 +956,7 @@ mw.messages.set( 'jquerymsg-wikitext-contents-script', '<i><script>Script inside</script></i>' ); assert.htmlEqual( formatParse( 'jquerymsg-wikitext-contents-script' ), - '<i><span class="mediaWiki_htmlEmitter"><script>Script inside</script></span></i>', + '<i><script>Script inside</script></i>', 'Contents of valid tag are treated as wikitext, so invalid HTML element is treated as text' ); @@ -832,4 +996,33 @@ assert.equal( logSpy.callCount, 2, 'mw.log.warn calls' ); } ); + QUnit.test( 'Integration', 4, function ( assert ) { + var expected, logSpy; + + expected = '<b><a title="Bold" href="/wiki/Bold">Bold</a>!</b>'; + mw.messages.set( 'integration-test', '<b>[[Bold]]!</b>' ); + + this.suppressWarnings(); + logSpy = this.sandbox.spy( mw.log, 'warn' ); + assert.equal( + window.gM( 'integration-test' ), + expected, + 'Global function gM() works correctly' + ); + assert.equal( logSpy.callCount, 1, 'mw.log.warn called' ); + this.restoreWarnings(); + + assert.equal( + mw.message( 'integration-test' ).parse(), + expected, + 'mw.message().parse() works correctly' + ); + + assert.equal( + $( '<span>' ).msg( 'integration-test' ).html(), + expected, + 'jQuery plugin $.fn.msg() works correctly' + ); + } ); + }( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js index 3328ce3f..3b5915ac 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js @@ -61,7 +61,7 @@ expected = repeat( '\n', n ) + 'some text'; $textarea = $( '<textarea>\n' + expected + '</textarea>' ); - assert.equal( $textarea.val(), expected, 'Expecting ' + n + ' newlines (HTML contained ' + (n + 1) + ')' ); + assert.equal( $textarea.val(), expected, 'Expecting ' + n + ' newlines (HTML contained ' + ( n + 1 ) + ')' ); $textarea = $( '<textarea>' ).val( expected ); assert.equal( $textarea.val(), expected, 'Expecting ' + n + ' newlines (from DOM set with ' + n + ')' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js index 670914eb..399290ca 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js @@ -11,7 +11,7 @@ }, messages: { // mw.language.listToText test - 'and': ' and', + and: ' and', 'comma-separator': ', ', 'word-separator': ' ' } @@ -51,9 +51,9 @@ for ( var i = 0; i < test.length; i++ ) { assert.equal( - mw.language.convertGrammar( test[i].word, test[i].grammarForm ), - test[i].expected, - test[i].description + mw.language.convertGrammar( test[ i ].word, test[ i ].grammarForm ), + test[ i ].expected, + test[ i ].description ); } } ); @@ -481,8 +481,8 @@ QUnit.test( 'List to text test', 4, function ( assert ) { assert.equal( mw.language.listToText( [] ), '', 'Blank list' ); - assert.equal( mw.language.listToText( ['a'] ), 'a', 'Single item' ); - assert.equal( mw.language.listToText( ['a', 'b'] ), 'a and b', 'Two items' ); - assert.equal( mw.language.listToText( ['a', 'b', 'c'] ), 'a, b and c', 'More than two items' ); + assert.equal( mw.language.listToText( [ 'a' ] ), 'a', 'Single item' ); + assert.equal( mw.language.listToText( [ 'a', 'b' ] ), 'a and b', 'Two items' ); + assert.equal( mw.language.listToText( [ 'a', 'b', 'c' ] ), 'a, b and c', 'More than two items' ); } ); }( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js index 61bab03f..288b527b 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js @@ -12,7 +12,7 @@ mw.messagePoster.factory.register( TEST_MODEL, testMessagePosterConstructor ); assert.strictEqual( - mw.messagePoster.factory.contentModelToClass[TEST_MODEL], + mw.messagePoster.factory.contentModelToClass[ TEST_MODEL ], testMessagePosterConstructor, 'Constructor is registered' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js new file mode 100644 index 00000000..6cef4a7c --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js @@ -0,0 +1,36 @@ +( function ( mw ) { + QUnit.module( 'mediawiki.storage' ); + + QUnit.test( 'set/get with localStorage', 3, function ( assert ) { + this.sandbox.stub( mw.storage, 'localStorage', { + setItem: this.sandbox.spy(), + getItem: this.sandbox.stub() + } ); + + mw.storage.set( 'foo', 'test' ); + assert.ok( mw.storage.localStorage.setItem.calledOnce ); + + mw.storage.localStorage.getItem.withArgs( 'foo' ).returns( 'test' ); + mw.storage.localStorage.getItem.returns( null ); + assert.strictEqual( mw.storage.get( 'foo' ), 'test', 'Check value gets stored.' ); + assert.strictEqual( mw.storage.get( 'bar' ), null, 'Unset values are null.' ); + } ); + + QUnit.test( 'set/get without localStorage', 3, function ( assert ) { + this.sandbox.stub( mw.storage, 'localStorage', { + getItem: this.sandbox.stub(), + removeItem: this.sandbox.stub(), + setItem: this.sandbox.stub() + } ); + + mw.storage.localStorage.getItem.throws(); + assert.strictEqual( mw.storage.get( 'foo' ), false ); + + mw.storage.localStorage.setItem.throws(); + assert.strictEqual( mw.storage.set( 'foo', 'test' ), false ); + + mw.storage.localStorage.removeItem.throws(); + assert.strictEqual( mw.storage.remove( 'foo', 'test' ), false ); + } ); + +}( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js index cf36ea82..111d85bd 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js @@ -108,21 +108,21 @@ // Multiple values at once someValues = { - 'foo': 'bar', - 'lorem': 'ipsum', - 'MediaWiki': true + foo: 'bar', + lorem: 'ipsum', + MediaWiki: true }; assert.strictEqual( conf.set( someValues ), true, 'Map.set returns boolean true if multiple values were set by passing an object' ); - assert.deepEqual( conf.get( ['foo', 'lorem'] ), { - 'foo': 'bar', - 'lorem': 'ipsum' + assert.deepEqual( conf.get( [ 'foo', 'lorem' ] ), { + foo: 'bar', + lorem: 'ipsum' }, 'Map.get returns multiple values correctly as an object' ); assert.deepEqual( conf, new mw.Map( conf.values ), 'new mw.Map maps over existing values-bearing object' ); - assert.deepEqual( conf.get( ['foo', 'notExist'] ), { - 'foo': 'bar', - 'notExist': null + assert.deepEqual( conf.get( [ 'foo', 'notExist' ] ), { + foo: 'bar', + notExist: null }, 'Map.get return includes keys that were not found as null values' ); // Interacting with globals and accessing the values object @@ -143,7 +143,7 @@ this.restoreWarnings(); // Change value via global Map - globalConf.set('anotherGlobalMapChecker', 'Again'); + globalConf.set( 'anotherGlobalMapChecker', 'Again' ); assert.equal( globalConf.get( 'anotherGlobalMapChecker' ), 'Again', 'Change in global Map reflected via get()' ); this.suppressWarnings(); assert.equal( window.anotherGlobalMapChecker, 'Again', 'Change in global Map reflected window object' ); @@ -175,8 +175,8 @@ len = formats.length; for ( i = 0; i < len; i++ ) { - format = formats[i]; - assert.equal( mw.message.apply( null, messageArguments )[format](), expectedResult, assertMessage + ' when format is ' + format ); + format = formats[ i ]; + assert.equal( mw.message.apply( null, messageArguments )[ format ](), expectedResult, assertMessage + ' when format is ' + format ); } } @@ -203,21 +203,21 @@ assert.equal( hello.format, 'escaped', 'Message.escaped correctly updated the "format" property' ); assert.ok( mw.messages.set( 'multiple-curly-brace', '"{{SITENAME}}" is the home of {{int:other-message}}' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['multiple-curly-brace'], ['text', 'parse'], '"' + siteName + '" is the home of Other Message', 'Curly brace format works correctly' ); + assertMultipleFormats( [ 'multiple-curly-brace' ], [ 'text', 'parse' ], '"' + siteName + '" is the home of Other Message', 'Curly brace format works correctly' ); assert.equal( mw.message( 'multiple-curly-brace' ).plain(), mw.messages.get( 'multiple-curly-brace' ), 'Plain format works correctly for curly brace message' ); assert.equal( mw.message( 'multiple-curly-brace' ).escaped(), mw.html.escape( '"' + siteName + '" is the home of Other Message' ), 'Escaped format works correctly for curly brace message' ); assert.ok( mw.messages.set( 'multiple-square-brackets-and-ampersand', 'Visit the [[Project:Community portal|community portal]] & [[Project:Help desk|help desk]]' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['multiple-square-brackets-and-ampersand'], ['plain', 'text'], mw.messages.get( 'multiple-square-brackets-and-ampersand' ), 'Square bracket message is not processed' ); + assertMultipleFormats( [ 'multiple-square-brackets-and-ampersand' ], [ 'plain', 'text' ], mw.messages.get( 'multiple-square-brackets-and-ampersand' ), 'Square bracket message is not processed' ); assert.equal( mw.message( 'multiple-square-brackets-and-ampersand' ).escaped(), 'Visit the [[Project:Community portal|community portal]] & [[Project:Help desk|help desk]]', 'Escaped format works correctly for square bracket message' ); assert.htmlEqual( mw.message( 'multiple-square-brackets-and-ampersand' ).parse(), 'Visit the ' + '<a title="Project:Community portal" href="/wiki/Project:Community_portal">community portal</a>' + ' & <a title="Project:Help desk" href="/wiki/Project:Help_desk">help desk</a>', 'Internal links work with parse' ); - assertMultipleFormats( ['mediawiki-test-version-entrypoints-index-php'], ['plain', 'text', 'escaped'], mw.messages.get( 'mediawiki-test-version-entrypoints-index-php' ), 'External link markup is unprocessed' ); + assertMultipleFormats( [ 'mediawiki-test-version-entrypoints-index-php' ], [ 'plain', 'text', 'escaped' ], mw.messages.get( 'mediawiki-test-version-entrypoints-index-php' ), 'External link markup is unprocessed' ); assert.htmlEqual( mw.message( 'mediawiki-test-version-entrypoints-index-php' ).parse(), '<a href="https://www.mediawiki.org/wiki/Manual:index.php">index.php</a>', 'External link works correctly in parse mode' ); - assertMultipleFormats( ['external-link-replace', 'http://example.org/?x=y&z'], ['plain', 'text'], 'Foo [http://example.org/?x=y&z bar]', 'Parameters are substituted but external link is not processed' ); + assertMultipleFormats( [ 'external-link-replace', 'http://example.org/?x=y&z' ], [ 'plain', 'text' ], 'Foo [http://example.org/?x=y&z bar]', 'Parameters are substituted but external link is not processed' ); assert.equal( mw.message( 'external-link-replace', 'http://example.org/?x=y&z' ).escaped(), 'Foo [http://example.org/?x=y&z bar]', 'In escaped mode, parameters are substituted and ampersand is escaped, but external link is not processed' ); assert.htmlEqual( mw.message( 'external-link-replace', 'http://example.org/?x=y&z' ).parse(), 'Foo <a href="http://example.org/?x=y&z">bar</a>', 'External link with replacement works in parse mode without double-escaping' ); @@ -235,26 +235,26 @@ goodbye = mw.message( 'goodbye' ); assert.strictEqual( goodbye.exists(), false, 'Message.exists returns false for nonexistent messages' ); - assertMultipleFormats( ['goodbye'], ['plain', 'text'], '<goodbye>', 'Message.toString returns <key> if key does not exist' ); + assertMultipleFormats( [ 'goodbye' ], [ 'plain', 'text' ], '<goodbye>', 'Message.toString returns <key> if key does not exist' ); // bug 30684 - assertMultipleFormats( ['goodbye'], ['parse', 'escaped'], '<goodbye>', 'Message.toString returns properly escaped <key> if key does not exist' ); + assertMultipleFormats( [ 'goodbye' ], [ 'parse', 'escaped' ], '<goodbye>', 'Message.toString returns properly escaped <key> if key does not exist' ); assert.ok( mw.messages.set( 'plural-test-msg', 'There {{PLURAL:$1|is|are}} $1 {{PLURAL:$1|result|results}}' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['plural-test-msg', 6], ['text', 'parse', 'escaped'], 'There are 6 results', 'plural get resolved' ); + assertMultipleFormats( [ 'plural-test-msg', 6 ], [ 'text', 'parse', 'escaped' ], 'There are 6 results', 'plural get resolved' ); assert.equal( mw.message( 'plural-test-msg', 6 ).plain(), 'There {{PLURAL:6|is|are}} 6 {{PLURAL:6|result|results}}', 'Parameter is substituted but plural is not resolved in plain' ); assert.ok( mw.messages.set( 'plural-test-msg-explicit', 'There {{plural:$1|is one car|are $1 cars|0=are no cars|12=are a dozen cars}}' ), 'mw.messages.set: Register message with explicit plural forms' ); - assertMultipleFormats( ['plural-test-msg-explicit', 12], ['text', 'parse', 'escaped'], 'There are a dozen cars', 'explicit plural get resolved' ); + assertMultipleFormats( [ 'plural-test-msg-explicit', 12 ], [ 'text', 'parse', 'escaped' ], 'There are a dozen cars', 'explicit plural get resolved' ); assert.ok( mw.messages.set( 'plural-test-msg-explicit-beginning', 'Basket has {{plural:$1|0=no eggs|12=a dozen eggs|6=half a dozen eggs|one egg|$1 eggs}}' ), 'mw.messages.set: Register message with explicit plural forms' ); - assertMultipleFormats( ['plural-test-msg-explicit-beginning', 1], ['text', 'parse', 'escaped'], 'Basket has one egg', 'explicit plural given at beginning get resolved for singular' ); - assertMultipleFormats( ['plural-test-msg-explicit-beginning', 4], ['text', 'parse', 'escaped'], 'Basket has 4 eggs', 'explicit plural given at beginning get resolved for plural' ); - assertMultipleFormats( ['plural-test-msg-explicit-beginning', 6], ['text', 'parse', 'escaped'], 'Basket has half a dozen eggs', 'explicit plural given at beginning get resolved for 6' ); - assertMultipleFormats( ['plural-test-msg-explicit-beginning', 0], ['text', 'parse', 'escaped'], 'Basket has no eggs', 'explicit plural given at beginning get resolved for 0' ); + assertMultipleFormats( [ 'plural-test-msg-explicit-beginning', 1 ], [ 'text', 'parse', 'escaped' ], 'Basket has one egg', 'explicit plural given at beginning get resolved for singular' ); + assertMultipleFormats( [ 'plural-test-msg-explicit-beginning', 4 ], [ 'text', 'parse', 'escaped' ], 'Basket has 4 eggs', 'explicit plural given at beginning get resolved for plural' ); + assertMultipleFormats( [ 'plural-test-msg-explicit-beginning', 6 ], [ 'text', 'parse', 'escaped' ], 'Basket has half a dozen eggs', 'explicit plural given at beginning get resolved for 6' ); + assertMultipleFormats( [ 'plural-test-msg-explicit-beginning', 0 ], [ 'text', 'parse', 'escaped' ], 'Basket has no eggs', 'explicit plural given at beginning get resolved for 0' ); - assertMultipleFormats( ['mediawiki-test-pagetriage-del-talk-page-notify-summary'], ['plain', 'text'], mw.messages.get( 'mediawiki-test-pagetriage-del-talk-page-notify-summary' ), 'Double square brackets with no parameters unchanged' ); + assertMultipleFormats( [ 'mediawiki-test-pagetriage-del-talk-page-notify-summary' ], [ 'plain', 'text' ], mw.messages.get( 'mediawiki-test-pagetriage-del-talk-page-notify-summary' ), 'Double square brackets with no parameters unchanged' ); - assertMultipleFormats( ['mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName], ['plain', 'text'], 'Notifying author of deletion nomination for [[' + specialCharactersPageName + ']]', 'Double square brackets with one parameter' ); + assertMultipleFormats( [ 'mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName ], [ 'plain', 'text' ], 'Notifying author of deletion nomination for [[' + specialCharactersPageName + ']]', 'Double square brackets with one parameter' ); assert.equal( mw.message( 'mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName ).escaped(), 'Notifying author of deletion nomination for [[' + mw.html.escape( specialCharactersPageName ) + ']]', 'Double square brackets with one parameter, when escaped' ); @@ -264,21 +264,21 @@ assert.ok( mw.messages.set( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result', '<a href=\'#\' title=\'{{#special:mypage}}\'>Username</a> (<a href=\'#\' title=\'{{#special:mytalk}}\'>talk</a>)' ), 'mw.messages.set: Register' ); assert.equal( mw.message( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result' ).plain(), mw.messages.get( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result' ), 'HTML message with curly braces is not changed in plain mode' ); - assertMultipleFormats( ['gender-plural-msg', 'male', 1], ['text', 'parse', 'escaped'], 'he is awesome', 'Gender and plural are resolved' ); + assertMultipleFormats( [ 'gender-plural-msg', 'male', 1 ], [ 'text', 'parse', 'escaped' ], 'he is awesome', 'Gender and plural are resolved' ); assert.equal( mw.message( 'gender-plural-msg', 'male', 1 ).plain(), '{{GENDER:male|he|she|they}} {{PLURAL:1|is|are}} awesome', 'Parameters are substituted, but gender and plural are not resolved in plain mode' ); assert.equal( mw.message( 'grammar-msg' ).plain(), mw.messages.get( 'grammar-msg' ), 'Grammar is not resolved in plain mode' ); - assertMultipleFormats( ['grammar-msg'], ['text', 'parse'], 'Przeszukaj ' + siteName, 'Grammar is resolved' ); + assertMultipleFormats( [ 'grammar-msg' ], [ 'text', 'parse' ], 'Przeszukaj ' + siteName, 'Grammar is resolved' ); assert.equal( mw.message( 'grammar-msg' ).escaped(), 'Przeszukaj ' + siteName, 'Grammar is resolved in escaped mode' ); - assertMultipleFormats( ['formatnum-msg', '987654321.654321'], ['text', 'parse', 'escaped'], '987,654,321.654', 'formatnum is resolved' ); + assertMultipleFormats( [ 'formatnum-msg', '987654321.654321' ], [ 'text', 'parse', 'escaped' ], '987,654,321.654', 'formatnum is resolved' ); assert.equal( mw.message( 'formatnum-msg' ).plain(), mw.messages.get( 'formatnum-msg' ), 'formatnum is not resolved in plain mode' ); - assertMultipleFormats( ['int-msg'], ['text', 'parse', 'escaped'], 'Some Other Message', 'int is resolved' ); + assertMultipleFormats( [ 'int-msg' ], [ 'text', 'parse', 'escaped' ], 'Some Other Message', 'int is resolved' ); assert.equal( mw.message( 'int-msg' ).plain(), mw.messages.get( 'int-msg' ), 'int is not resolved in plain mode' ); assert.ok( mw.messages.set( 'mediawiki-italics-msg', '<i>Very</i> important' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['mediawiki-italics-msg'], ['plain', 'text', 'parse'], mw.messages.get( 'mediawiki-italics-msg' ), 'Simple italics unchanged' ); + assertMultipleFormats( [ 'mediawiki-italics-msg' ], [ 'plain', 'text', 'parse' ], mw.messages.get( 'mediawiki-italics-msg' ), 'Simple italics unchanged' ); assert.htmlEqual( mw.message( 'mediawiki-italics-msg' ).escaped(), '<i>Very</i> important', @@ -286,7 +286,7 @@ ); assert.ok( mw.messages.set( 'mediawiki-italics-with-link', 'An <i>italicized [[link|wiki-link]]</i>' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['mediawiki-italics-with-link'], ['plain', 'text'], mw.messages.get( 'mediawiki-italics-with-link' ), 'Italics with link unchanged' ); + assertMultipleFormats( [ 'mediawiki-italics-with-link' ], [ 'plain', 'text' ], mw.messages.get( 'mediawiki-italics-with-link' ), 'Italics with link unchanged' ); assert.htmlEqual( mw.message( 'mediawiki-italics-with-link' ).escaped(), 'An <i>italicized [[link|wiki-link]]</i>', @@ -299,7 +299,7 @@ ); assert.ok( mw.messages.set( 'mediawiki-script-msg', '<script >alert( "Who put this script here?" );</script>' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['mediawiki-script-msg'], ['plain', 'text'], mw.messages.get( 'mediawiki-script-msg' ), 'Script unchanged' ); + assertMultipleFormats( [ 'mediawiki-script-msg' ], [ 'plain', 'text' ], mw.messages.get( 'mediawiki-script-msg' ), 'Script unchanged' ); assert.htmlEqual( mw.message( 'mediawiki-script-msg' ).escaped(), '<script >alert( "Who put this script here?" );</script>', @@ -340,8 +340,9 @@ * The sync style load test (for @import). This is, in a way, also an open bug for * ResourceLoader ("execute js after styles are loaded"), but browsers don't offer a * way to get a callback from when a stylesheet is loaded (that is, including any - * @import rules inside). To work around this, we'll have a little time loop to check + * `@import` rules inside). To work around this, we'll have a little time loop to check * if the styles apply. + * * Note: This test originally used new Image() and onerror to get a callback * when the url is loaded, but that is fragile since it doesn't monitor the * same request as the css @import, and Safari 4 has issues with @@ -406,7 +407,7 @@ isAwesomeDone = true; }; - mw.loader.implement( 'test.callback', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} ); + mw.loader.implement( 'test.callback', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] ); mw.loader.using( 'test.callback', function () { @@ -430,7 +431,7 @@ isAwesomeDone = true; }; - mw.loader.implement( 'hasOwnProperty', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} ); + mw.loader.implement( 'hasOwnProperty', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ], {}, {} ); mw.loader.using( 'hasOwnProperty', function () { @@ -454,7 +455,7 @@ isAwesomeDone = true; }; - mw.loader.implement( 'test.promise', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} ); + mw.loader.implement( 'test.promise', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] ); mw.loader.using( 'test.promise' ) .done( function () { @@ -491,9 +492,8 @@ QUnit.start(); }, { - 'all': '.mw-test-implement-a { float: right; }' - }, - {} + all: '.mw-test-implement-a { float: right; }' + } ); mw.loader.load( [ @@ -547,16 +547,15 @@ } ); }, { - 'url': { - 'print': [urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' )], - 'screen': [ + url: { + print: [ urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' ) ], + screen: [ // bug 40834: Make sure it actually works with more than 1 stylesheet reference urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ), urlStyleTest( '.mw-test-implement-b3', 'float', 'right' ) ] } - }, - {} + } ); mw.loader.load( [ @@ -585,9 +584,8 @@ QUnit.start(); }, { - 'all': '.mw-test-implement-c { float: right; }' - }, - {} + all: '.mw-test-implement-c { float: right; }' + } ); mw.loader.load( [ @@ -622,10 +620,9 @@ } ); }, { - 'all': [urlStyleTest( '.mw-test-implement-d', 'float', 'right' )], - 'print': [urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' )] - }, - {} + all: [ urlStyleTest( '.mw-test-implement-d', 'float', 'right' ) ], + print: [ urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' ) ] + } ); mw.loader.load( [ @@ -634,20 +631,20 @@ } ); // @import (bug 31676) - QUnit.asyncTest( 'mw.loader.implement( styles has @import)', 5, function ( assert ) { + QUnit.asyncTest( 'mw.loader.implement( styles has @import)', 7, function ( assert ) { var isJsExecuted, $element; mw.loader.implement( 'test.implement.import', function () { - assert.strictEqual( isJsExecuted, undefined, 'javascript not executed multiple times' ); + assert.strictEqual( isJsExecuted, undefined, 'script not executed multiple times' ); isJsExecuted = true; - assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state is "ready" while implement() is executing javascript' ); + assert.equal( mw.loader.getState( 'test.implement.import' ), 'executing', 'module state during implement() script execution' ); $element = $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' ); - assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'Messages are loaded before javascript execution' ); + assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'messages load before script execution' ); assertStyleAsync( assert, $element, 'float', 'right', function () { assert.equal( $element.css( 'text-align' ), 'center', @@ -658,7 +655,7 @@ } ); }, { - 'css': [ + css: [ '@import url(\'' + urlStyleTest( '.mw-test-implement-import', 'float', 'right' ) + '\');\n' @@ -670,8 +667,64 @@ } ); - mw.loader.load( 'test.implement' ); + mw.loader.using( 'test.implement.import' ).always( function () { + assert.strictEqual( isJsExecuted, true, 'script executed' ); + assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state after script execution' ); + } ); + } ); + + QUnit.asyncTest( 'mw.loader.implement( dependency with styles )', 4, function ( assert ) { + var $element = $( '<div class="mw-test-implement-e"></div>' ).appendTo( '#qunit-fixture' ), + $element2 = $( '<div class="mw-test-implement-e2"></div>' ).appendTo( '#qunit-fixture' ); + + assert.notEqual( + $element.css( 'float' ), + 'right', + 'style is clear' + ); + assert.notEqual( + $element2.css( 'float' ), + 'left', + 'style is clear' + ); + + mw.loader.register( [ + [ 'test.implement.e', '0', [ 'test.implement.e2' ] ], + [ 'test.implement.e2', '0' ] + ] ); + + mw.loader.implement( + 'test.implement.e', + function () { + assert.equal( + $element.css( 'float' ), + 'right', + 'Depending module\'s style is applied' + ); + QUnit.start(); + }, + { + all: '.mw-test-implement-e { float: right; }' + } + ); + + mw.loader.implement( + 'test.implement.e2', + function () { + assert.equal( + $element2.css( 'float' ), + 'left', + 'Dependency\'s style is applied' + ); + }, + { + all: '.mw-test-implement-e2 { float: left; }' + } + ); + mw.loader.load( [ + 'test.implement.e' + ] ); } ); QUnit.test( 'mw.loader.implement( only scripts )', 1, function ( assert ) { @@ -682,7 +735,9 @@ QUnit.asyncTest( 'mw.loader.implement( only messages )', 2, function ( assert ) { assert.assertFalse( mw.messages.exists( 'bug_29107' ), 'Verify that the test message doesn\'t exist yet' ); - mw.loader.implement( 'test.implement.msgs', [], {}, { 'bug_29107': 'loaded' } ); + // jscs: disable requireCamelCaseOrUpperCaseIdentifiers + mw.loader.implement( 'test.implement.msgs', [], {}, { bug_29107: 'loaded' } ); + // jscs: enable requireCamelCaseOrUpperCaseIdentifiers mw.loader.using( 'test.implement.msgs', function () { QUnit.start(); assert.ok( mw.messages.exists( 'bug_29107' ), 'Bug 29107: messages-only module should implement ok' ); @@ -697,9 +752,9 @@ this.sandbox.stub( mw, 'track' ); mw.loader.register( [ - ['test.module1', '0'], - ['test.module2', '0', ['test.module1']], - ['test.module3', '0', ['test.module2']] + [ 'test.module1', '0' ], + [ 'test.module2', '0', [ 'test.module1' ] ], + [ 'test.module3', '0', [ 'test.module2' ] ] ] ); mw.loader.implement( 'test.module1', function () { throw new Error( 'expected' ); @@ -713,22 +768,19 @@ QUnit.test( 'mw.loader out-of-order implementation', 9, function ( assert ) { mw.loader.register( [ - ['test.module4', '0'], - ['test.module5', '0', ['test.module4']], - ['test.module6', '0', ['test.module5']] + [ 'test.module4', '0' ], + [ 'test.module5', '0', [ 'test.module4' ] ], + [ 'test.module6', '0', [ 'test.module5' ] ] ] ); - mw.loader.implement( 'test.module4', function () { - }, {}, {} ); + mw.loader.implement( 'test.module4', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' ); assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' ); - mw.loader.implement( 'test.module6', function () { - }, {}, {} ); + mw.loader.implement( 'test.module6', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' ); assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' ); - mw.loader.implement( 'test.module5', function () { - }, {}, {} ); + mw.loader.implement( 'test.module5', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' ); assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' ); @@ -736,12 +788,11 @@ QUnit.test( 'mw.loader missing dependency', 13, function ( assert ) { mw.loader.register( [ - ['test.module7', '0'], - ['test.module8', '0', ['test.module7']], - ['test.module9', '0', ['test.module8']] + [ 'test.module7', '0' ], + [ 'test.module8', '0', [ 'test.module7' ] ], + [ 'test.module9', '0', [ 'test.module8' ] ] ] ); - mw.loader.implement( 'test.module8', function () { - }, {}, {} ); + mw.loader.implement( 'test.module8', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' ); assert.strictEqual( mw.loader.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' ); assert.strictEqual( mw.loader.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' ); @@ -749,24 +800,23 @@ assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' ); assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' ); assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' ); - mw.loader.implement( 'test.module9', function () { - }, {}, {} ); + mw.loader.implement( 'test.module9', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' ); assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' ); assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' ); mw.loader.using( - ['test.module7'], + [ 'test.module7' ], function () { assert.ok( false, 'Success fired despite missing dependency' ); assert.ok( true, 'QUnit expected() count dummy' ); }, function ( e, dependencies ) { assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' ); - assert.deepEqual( dependencies, ['test.module7'], 'Error callback called with module test.module7' ); + assert.deepEqual( dependencies, [ 'test.module7' ], 'Error callback called with module test.module7' ); } ); mw.loader.using( - ['test.module9'], + [ 'test.module9' ], function () { assert.ok( false, 'Success fired despite missing dependency' ); assert.ok( true, 'QUnit expected() count dummy' ); @@ -776,7 +826,7 @@ dependencies.sort(); assert.deepEqual( dependencies, - ['test.module7', 'test.module8', 'test.module9'], + [ 'test.module7', 'test.module8', 'test.module9' ], 'Error callback called with all three modules as dependencies' ); } @@ -786,9 +836,9 @@ QUnit.asyncTest( 'mw.loader dependency handling', 5, function ( assert ) { mw.loader.register( [ // [module, version, dependencies, group, source] - ['testMissing', '1', [], null, 'testloader'], - ['testUsesMissing', '1', ['testMissing'], null, 'testloader'], - ['testUsesNestedMissing', '1', ['testUsesMissing'], null, 'testloader'] + [ 'testMissing', '1', [], null, 'testloader' ], + [ 'testUsesMissing', '1', [ 'testMissing' ], null, 'testloader' ], + [ 'testUsesNestedMissing', '1', [ 'testUsesMissing' ], null, 'testloader' ] ] ); function verifyModuleStates() { @@ -797,7 +847,7 @@ assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' ); } - mw.loader.using( ['testUsesNestedMissing'], + mw.loader.using( [ 'testUsesNestedMissing' ], function () { assert.ok( false, 'Error handler should be invoked.' ); assert.ok( true ); // Dummy to reach QUnit expect() @@ -811,7 +861,7 @@ // As soon as server spits out state('testMissing', 'missing'); // it will bubble up and trigger the error callback. // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing. - assert.deepEqual( badmodules, ['testMissing'], 'Bad modules as expected.' ); + assert.deepEqual( badmodules, [ 'testMissing' ], 'Bad modules as expected.' ); verifyModuleStates(); @@ -823,9 +873,9 @@ QUnit.asyncTest( 'mw.loader skin-function handling', 5, function ( assert ) { mw.loader.register( [ // [module, version, dependencies, group, source, skip] - ['testSkipped', '1', [], null, 'testloader', 'return true;'], - ['testNotSkipped', '1', [], null, 'testloader', 'return false;'], - ['testUsesSkippable', '1', ['testSkipped', 'testNotSkipped'], null, 'testloader'] + [ 'testSkipped', '1', [], null, 'testloader', 'return true;' ], + [ 'testNotSkipped', '1', [], null, 'testloader', 'return false;' ], + [ 'testUsesSkippable', '1', [ 'testSkipped', 'testNotSkipped' ], null, 'testloader' ] ] ); function verifyModuleStates() { @@ -834,7 +884,7 @@ assert.equal( mw.loader.getState( 'testUsesSkippable' ), 'ready', 'Module is ready when skippable dependencies are ready' ); } - mw.loader.using( ['testUsesSkippable'], + mw.loader.using( [ 'testUsesSkippable' ], function () { assert.ok( true, 'Success handler should be invoked.' ); assert.ok( true ); // Dummy to match error handler and reach QUnit expect() @@ -858,7 +908,7 @@ // This bug was actually already fixed in 1.18 and later when discovered in 1.17. // Test is for regressions! - // Forge an URL to the test callback script + // Forge a URL to the test callback script var target = QUnit.fixurl( mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js' ); @@ -875,6 +925,45 @@ mw.loader.load( target ); } ); + QUnit.asyncTest( 'mw.loader( "/absolute-path" )', 2, function ( assert ) { + // Forge a URL to the test callback script + var target = QUnit.fixurl( + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js' + ); + + // Confirm that mw.loader.load() works with absolute-paths (relative to current hostname) + assert.equal( target.slice( 0, 1 ), '/', 'URL is relative to document root' ); + + // Async! + // The target calls QUnit.start + mw.loader.load( target ); + } ); + + QUnit.asyncTest( 'mw.loader() executing race (T112232)', 2, function ( assert ) { + var done = false; + + // The red herring schedules its CSS buffer first. In T112232, a bug in the + // state machine would cause the job for testRaceLoadMe to run with an earlier job. + mw.loader.implement( + 'testRaceRedHerring', + function () {}, + { css: [ '.mw-testRaceRedHerring {}' ] } + ); + mw.loader.implement( + 'testRaceLoadMe', + function () { + done = true; + }, + { css: [ '.mw-testRaceLoadMe { float: left; }' ] } + ); + + mw.loader.load( [ 'testRaceRedHerring', 'testRaceLoadMe' ] ); + mw.loader.using( 'testRaceLoadMe', function () { + assert.strictEqual( done, true, 'script ran' ); + assert.strictEqual( mw.loader.getState( 'testRaceLoadMe' ), 'ready', 'state' ); + } ).always( QUnit.start ); + } ); + QUnit.test( 'mw.html', 13, function ( assert ) { assert.throws( function () { mw.html.escape(); @@ -968,9 +1057,9 @@ mw.hook( 'test.hook.data' ).add( function ( data1, data2 ) { assert.equal( data1, 'example', 'Fire with data (string param)' ); - assert.deepEqual( data2, ['two'], 'Fire with data (array param)' ); + assert.deepEqual( data2, [ 'two' ], 'Fire with data (array param)' ); } ); - mw.hook( 'test.hook.data' ).fire( 'example', ['two'] ); + mw.hook( 'test.hook.data' ).fire( 'example', [ 'two' ] ); hook = mw.hook( 'test.hook.chainable' ); assert.strictEqual( hook.add(), hook, 'hook.add is chainable' ); @@ -981,7 +1070,7 @@ add = hook.add; fire = hook.fire; add( function ( x, y ) { - assert.deepEqual( [x, y], ['x', 'y'], 'Detached (contextless) with data' ); + assert.deepEqual( [ x, y ], [ 'x', 'y' ], 'Detached (contextless) with data' ); } ); fire( 'x', 'y' ); @@ -1004,7 +1093,7 @@ assert.equal( chr, 'z', 'Adding callback later invokes right away with last data' ); } ); - assert.deepEqual( chars, ['x', 'y', 'z'], 'Multiple callbacks with multiple fires' ); + assert.deepEqual( chars, [ 'x', 'y', 'z' ], 'Multiple callbacks with multiple fires' ); chars = []; callback = function ( chr ) { @@ -1033,7 +1122,7 @@ assert.deepEqual( chars, - ['x', 'x', 'x', 'x', 'y', 'z'], + [ 'x', 'x', 'x', 'x', 'y', 'z' ], '"add" and "remove" support variadic arguments. ' + '"add" does not filter unique. ' + '"remove" removes all equal by reference. ' + diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js index e43516b0..89eb45f2 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js @@ -1,7 +1,7 @@ ( function ( mw, $ ) { QUnit.module( 'mediawiki.toc', QUnit.newMwEnvironment( { setup: function () { - // Prevent live cookies like mw_hidetoc=1 from interferring with the test + // Prevent live cookies from interferring with the test this.stub( $, 'cookie' ).returns( null ); } } ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js index cdb26244..5329be6a 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js @@ -39,4 +39,22 @@ assert.assertTrue( this.timeStamp >= now, 'thisValue has sane timestamp' ); } ); } ); + + QUnit.test( 'trackUnsubscribe', 1, function ( assert ) { + var sequence = []; + function unsubber( topic, data ) { + sequence.push( [ topic, data ] ); + } + + mw.track( 'unsub', { key: 1 } ); + mw.trackSubscribe( 'unsub', unsubber ); + mw.track( 'unsub', { key: 2 } ); + mw.trackUnsubscribe( unsubber ); + mw.track( 'unsub', { key: 3 } ); + + assert.deepEqual( sequence, [ + [ 'unsub', { key: 1 } ], + [ 'unsub', { key: 2 } ] + ], 'Stop when unsubscribing' ); + } ); }( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js index 0b42af4b..c1f1484c 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js @@ -2,54 +2,54 @@ var // Based on IPTest.php > testisIPv4 IPV4_CASES = [ - [false, false, 'Boolean false is not an IP'], - [false, true, 'Boolean true is not an IP'], - [false, '', 'Empty string is not an IP'], - [false, 'abc', '"abc" is not an IP'], - [false, ':', 'Colon is not an IP'], - [false, '124.24.52', 'IPv4 not enough quads'], - [false, '24.324.52.13', 'IPv4 out of range'], - [false, '.24.52.13', 'IPv4 starts with period'], - - [true, '124.24.52.13', '124.24.52.134 is a valid IP'], - [true, '1.24.52.13', '1.24.52.13 is a valid IP'], - [false, '74.24.52.13/20', 'IPv4 ranges are not recognized as valid IPs'] + [ false, false, 'Boolean false is not an IP' ], + [ false, true, 'Boolean true is not an IP' ], + [ false, '', 'Empty string is not an IP' ], + [ false, 'abc', '"abc" is not an IP' ], + [ false, ':', 'Colon is not an IP' ], + [ false, '124.24.52', 'IPv4 not enough quads' ], + [ false, '24.324.52.13', 'IPv4 out of range' ], + [ false, '.24.52.13', 'IPv4 starts with period' ], + + [ true, '124.24.52.13', '124.24.52.134 is a valid IP' ], + [ true, '1.24.52.13', '1.24.52.13 is a valid IP' ], + [ false, '74.24.52.13/20', 'IPv4 ranges are not recognized as valid IPs' ] ], // Based on IPTest.php > testisIPv6 IPV6_CASES = [ - [false, ':fc:100::', 'IPv6 starting with lone ":"'], - [false, 'fc:100:::', 'IPv6 ending with a ":::"'], - [false, 'fc:300', 'IPv6 with only 2 words'], - [false, 'fc:100:300', 'IPv6 with only 3 words'], - - [false, 'fc:100:a:d:1:e:ac:0::', 'IPv6 with 8 words ending with "::"'], - [false, 'fc:100:a:d:1:e:ac:0:1::', 'IPv6 with 9 words ending with "::"'], - - [false, ':::'], - [false, '::0:', 'IPv6 ending in a lone ":"'], - - [true, '::', 'IPv6 zero address'], - - [false, '::fc:100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words'], - [false, '::fc:100:a:d:1:e:ac:0:1', 'IPv6 with 9 words'], - - [false, ':fc::100', 'IPv6 starting with lone ":"'], - [false, 'fc::100:', 'IPv6 ending with lone ":"'], - [false, 'fc:::100', 'IPv6 with ":::" in the middle'], - - [true, 'fc::100', 'IPv6 with "::" and 2 words'], - [true, 'fc::100:a', 'IPv6 with "::" and 3 words'], - [true, 'fc::100:a:d', 'IPv6 with "::" and 4 words'], - [true, 'fc::100:a:d:1', 'IPv6 with "::" and 5 words'], - [true, 'fc::100:a:d:1:e', 'IPv6 with "::" and 6 words'], - [true, 'fc::100:a:d:1:e:ac', 'IPv6 with "::" and 7 words'], - [true, '2001::df', 'IPv6 with "::" and 2 words'], - [true, '2001:5c0:1400:a::df', 'IPv6 with "::" and 5 words'], - [true, '2001:5c0:1400:a::df:2', 'IPv6 with "::" and 6 words'], - - [false, 'fc::100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words'], - [false, 'fc::100:a:d:1:e:ac:0:1', 'IPv6 with 9 words'] + [ false, ':fc:100::', 'IPv6 starting with lone ":"' ], + [ false, 'fc:100:::', 'IPv6 ending with a ":::"' ], + [ false, 'fc:300', 'IPv6 with only 2 words' ], + [ false, 'fc:100:300', 'IPv6 with only 3 words' ], + + [ false, 'fc:100:a:d:1:e:ac:0::', 'IPv6 with 8 words ending with "::"' ], + [ false, 'fc:100:a:d:1:e:ac:0:1::', 'IPv6 with 9 words ending with "::"' ], + + [ false, ':::' ], + [ false, '::0:', 'IPv6 ending in a lone ":"' ], + + [ true, '::', 'IPv6 zero address' ], + + [ false, '::fc:100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' ], + [ false, '::fc:100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' ], + + [ false, ':fc::100', 'IPv6 starting with lone ":"' ], + [ false, 'fc::100:', 'IPv6 ending with lone ":"' ], + [ false, 'fc:::100', 'IPv6 with ":::" in the middle' ], + + [ true, 'fc::100', 'IPv6 with "::" and 2 words' ], + [ true, 'fc::100:a', 'IPv6 with "::" and 3 words' ], + [ true, 'fc::100:a:d', 'IPv6 with "::" and 4 words' ], + [ true, 'fc::100:a:d:1', 'IPv6 with "::" and 5 words' ], + [ true, 'fc::100:a:d:1:e', 'IPv6 with "::" and 6 words' ], + [ true, 'fc::100:a:d:1:e:ac', 'IPv6 with "::" and 7 words' ], + [ true, '2001::df', 'IPv6 with "::" and 2 words' ], + [ true, '2001:5c0:1400:a::df', 'IPv6 with "::" and 5 words' ], + [ true, '2001:5c0:1400:a::df:2', 'IPv6 with "::" and 6 words' ], + + [ false, 'fc::100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' ], + [ false, 'fc::100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' ] ]; Array.prototype.push.apply( IPV6_CASES, @@ -70,7 +70,7 @@ '::fc:100:a:d:1:e:ac', 'fc:100:a:d:1:e:ac:0' ], function ( el ) { - return [[ true, el, el + ' is a valid IP' ]]; + return [ [ true, el, el + ' is a valid IP' ] ]; } ) ); @@ -83,7 +83,7 @@ }, messages: { // Used by accessKeyLabel in test for addPortletLink - 'brackets': '[$1]', + brackets: '[$1]', 'word-separator': ' ' } } ) ); @@ -92,7 +92,7 @@ assert.equal( mw.util.rawurlencode( 'Test:A & B/Here' ), 'Test%3AA%20%26%20B%2FHere' ); } ); - QUnit.test( 'wikiUrlencode', 10, function ( assert ) { + QUnit.test( 'wikiUrlencode', 11, function ( assert ) { assert.equal( mw.util.wikiUrlencode( 'Test:A & B/Here' ), 'Test:A_%26_B/Here' ); // See also wfUrlencodeTest.php#provideURLS $.each( { @@ -102,6 +102,7 @@ ':': ':', ';@$-_.!*': ';@$-_.!*', '/': '/', + '~': '~', '[]': '%5B%5D', '<>': '%3C%3E', '\'': '%27' @@ -133,10 +134,10 @@ QUnit.test( 'wikiScript', 4, function ( assert ) { mw.config.set( { - 'wgScript': '/w/i.php', // customized wgScript for bug 39103 - 'wgLoadScript': '/w/l.php', // customized wgLoadScript for bug 39103 - 'wgScriptPath': '/w', - 'wgScriptExtension': '.php' + wgScript: '/w/i.php', // customized wgScript for bug 39103 + wgLoadScript: '/w/l.php', // customized wgLoadScript for bug 39103 + wgScriptPath: '/w', + wgScriptExtension: '.php' } ); assert.equal( mw.util.wikiScript(), mw.config.get( 'wgScript' ), @@ -175,10 +176,10 @@ url = 'http://example.org/#&foo=bad'; assert.strictEqual( mw.util.getParamValue( 'foo', url ), null, 'Ignore hash if param is not in querystring but in hash (bug 27427)' ); - url = 'example.org?' + $.param( { 'TEST': 'a b+c' } ); + url = 'example.org?' + $.param( { TEST: 'a b+c' } ); assert.strictEqual( mw.util.getParamValue( 'TEST', url ), 'a b+c', 'Bug 30441: getParamValue must understand "+" encoding of space' ); - url = 'example.org?' + $.param( { 'TEST': 'a b+c d' } ); // check for sloppy code from r95332 :) + url = 'example.org?' + $.param( { TEST: 'a b+c d' } ); // check for sloppy code from r95332 :) assert.strictEqual( mw.util.getParamValue( 'TEST', url ), 'a b+c d', 'Bug 30441: getParamValue must understand "+" encoding of space (multiple spaces)' ); } ); @@ -240,7 +241,7 @@ 'ResourceLoader', 't-rl', 'More info about ResourceLoader on MediaWiki.org ', 'l' ); - assert.ok( $.isDomElement( tbRL ), 'addPortletLink returns a valid DOM Element according to $.isDomElement' ); + assert.ok( tbRL && tbRL.nodeType, 'addPortletLink returns a DOM Node' ); tbMW = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/', 'MediaWiki.org', 't-mworg', 'Go to MediaWiki.org', 'm', tbRL ); @@ -265,7 +266,7 @@ ); assert.equal( $tbMW.closest( '.portlet' ).attr( 'id' ), 'p-test-tb', 'Link was inserted within correct portlet' ); - assert.strictEqual( $tbMW.next()[0], tbRL, 'Link is in the correct position (nextnode as Node object)' ); + assert.strictEqual( $tbMW.next()[ 0 ], tbRL, 'Link is in the correct position (nextnode as Node object)' ); cuQuux = mw.util.addPortletLink( 'p-test-custom', '#', 'Quux', null, 'Example [shift-x]', 'q' ); $cuQuux = $( cuQuux ); @@ -281,7 +282,7 @@ tbRLDM = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', 'Default modules', 't-rldm', 'List of all default modules ', 'd', '#t-rl' ); - assert.strictEqual( $( tbRLDM ).next()[0], tbRL, 'Link is in the correct position (CSS selector as nextnode)' ); + assert.strictEqual( $( tbRLDM ).next()[ 0 ], tbRL, 'Link is in the correct position (CSS selector as nextnode)' ); caFoo = mw.util.addPortletLink( 'p-test-views', '#', 'Foo' ); @@ -289,19 +290,19 @@ assert.strictEqual( $( caFoo ).find( 'span' ).length, 1, 'A <span> element should be added for porlets with vectorTabs class.' ); addedAfter = mw.util.addPortletLink( 'p-test-tb', '#', 'After foo', 'post-foo', 'After foo', null, $( tbRL ) ); - assert.strictEqual( $( addedAfter ).next()[0], tbRL, 'Link is in the correct position (jQuery object as nextnode)' ); + assert.strictEqual( $( addedAfter ).next()[ 0 ], tbRL, 'Link is in the correct position (jQuery object as nextnode)' ); // test case - nonexistent id as next node tbRLDMnonexistentid = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', 'Default modules', 't-rldm-nonexistent', 'List of all default modules ', 'd', '#t-rl-nonexistent' ); - assert.equal( tbRLDMnonexistentid, $( '#p-test-tb li:last' )[0], 'Fallback to adding at the end (nextnode non-matching CSS selector)' ); + assert.equal( tbRLDMnonexistentid, $( '#p-test-tb li:last' )[ 0 ], 'Fallback to adding at the end (nextnode non-matching CSS selector)' ); // test case - empty jquery object as next node tbRLDMemptyjquery = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', 'Default modules', 't-rldm-empty-jquery', 'List of all default modules ', 'd', $( '#t-rl-nonexistent' ) ); - assert.equal( tbRLDMemptyjquery, $( '#p-test-tb li:last' )[0], 'Fallback to adding at the end (nextnode as empty jQuery object)' ); + assert.equal( tbRLDMemptyjquery, $( '#p-test-tb li:last' )[ 0 ], 'Fallback to adding at the end (nextnode as empty jQuery object)' ); } ); QUnit.test( 'validateEmail', 6, function ( assert ) { @@ -319,23 +320,23 @@ QUnit.test( 'isIPv6Address', 40, function ( assert ) { $.each( IPV6_CASES, function ( i, ipCase ) { - assert.strictEqual( mw.util.isIPv6Address( ipCase[1] ), ipCase[0], ipCase[2] ); + assert.strictEqual( mw.util.isIPv6Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] ); } ); } ); QUnit.test( 'isIPv4Address', 11, function ( assert ) { $.each( IPV4_CASES, function ( i, ipCase ) { - assert.strictEqual( mw.util.isIPv4Address( ipCase[1] ), ipCase[0], ipCase[2] ); + assert.strictEqual( mw.util.isIPv4Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] ); } ); } ); QUnit.test( 'isIPAddress', 51, function ( assert ) { $.each( IPV4_CASES, function ( i, ipCase ) { - assert.strictEqual( mw.util.isIPv4Address( ipCase[1] ), ipCase[0], ipCase[2] ); + assert.strictEqual( mw.util.isIPv4Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] ); } ); $.each( IPV6_CASES, function ( i, ipCase ) { - assert.strictEqual( mw.util.isIPv6Address( ipCase[1] ), ipCase[0], ipCase[2] ); + assert.strictEqual( mw.util.isIPv6Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] ); } ); } ); }( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/startup.test.js b/tests/qunit/suites/resources/startup.test.js index 6011961a..5ea7a816 100644 --- a/tests/qunit/suites/resources/startup.test.js +++ b/tests/qunit/suites/resources/startup.test.js @@ -86,7 +86,9 @@ 'Mozilla/5.0 (Series40; NokiaX3-02/05.60; Profile/MIDP-2.1 Configuration/CLDC-1.1) Gecko/20100401 S40OviBrowser/3.2.0.0.6', 'Mozilla/5.0 (Series40; Nokia305/05.92; Profile/MIDP-2.1 Configuration/CLDC-1.1) Gecko/20100401 S40OviBrowser/3.7.0.0.11', // Google Glass - 'Mozilla/5.0 (Linux; U; Android 4.0.4; en-us; Glass 1 Build/IMM76L; XE11) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30' + 'Mozilla/5.0 (Linux; U; Android 4.0.4; en-us; Glass 1 Build/IMM76L; XE11) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30', + // MeeGo + 'Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13' ], // No explicit support for or against these browsers, they're given a shot at Grade A. gradeX: [ |