diff options
Diffstat (limited to 'tests/qunit/suites')
22 files changed, 1204 insertions, 1129 deletions
diff --git a/tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js b/tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js index f6ea1b48..4484467d 100644 --- a/tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js @@ -7,7 +7,7 @@ } ) ); var getAccessKeyPrefixTestData = [ - //ua string, platform string, expected prefix + // 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-'], @@ -27,11 +27,11 @@ ['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 + // strings appended to title to make sure updateTooltipAccessKeys handles them correctly updateTooltipAccessKeysTestData = [ '', ' [a]', ' [test-a]', ' [alt-b]' ]; function makeInput( title, accessKey ) { - //The properties aren't escaped, so make sure you don't call this function with values that need to be escaped! + // The properties aren't escaped, so make sure you don't call this function with values that need to be escaped! return '<input title="' + title + '" ' + ( accessKey ? 'accessKey="' + accessKey + '" ' : '' ) + ' />'; } @@ -46,10 +46,10 @@ } ); QUnit.test( 'updateTooltipAccessKeys - current browser', 2, function ( assert ) { - var title = $( makeInput ( 'Title', 'a' ) ).updateTooltipAccessKeys().prop( 'title' ), - //The new title should be something like "Title [alt-a]", but the exact label will depend on the browser. - //The "a" could be capitalized, and the prefix could be anything, e.g. a simple "^" for ctrl- - //(no browser is known using such a short prefix, though) or "Alt+Umschalt+" in German Firefox. + var title = $( makeInput( 'Title', 'a' ) ).updateTooltipAccessKeys().prop( 'title' ), + // The new title should be something like "Title [alt-a]", but the exact label will depend on the browser. + // The "a" could be capitalized, and the prefix could be anything, e.g. a simple "^" for ctrl- + // (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.' ); diff --git a/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js b/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js index 22d2af19..0cb9cc81 100644 --- a/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js @@ -60,7 +60,7 @@ ); QUnit.start(); - }, 10 ); + } ); } ); } diff --git a/tests/qunit/suites/resources/jquery/jquery.client.test.js b/tests/qunit/suites/resources/jquery/jquery.client.test.js deleted file mode 100644 index c6dd91c4..00000000 --- a/tests/qunit/suites/resources/jquery/jquery.client.test.js +++ /dev/null @@ -1,638 +0,0 @@ -( function ( $ ) { - - QUnit.module( 'jquery.client', QUnit.newMwEnvironment() ); - - var uacount = 0, - // Object keyed by userAgent. Value is an array (human-readable name, client-profile object, navigator.platform value) - uas = { - // Internet Explorer 6 - // Internet Explorer 7 - 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)': { - title: 'Internet Explorer 7', - platform: 'Win32', - profile: { - name: 'msie', - layout: 'trident', - layoutVersion: 'unknown', - platform: 'win', - version: '7.0', - versionBase: '7', - versionNumber: 7 - }, - wikiEditor: { - ltr: true, - rtl: false - } - }, - // Internet Explorer 8 - // Internet Explorer 9 - // Internet Explorer 10 - 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)': { - title: 'Internet Explorer 10', - platform: 'Win32', - profile: { - name: 'msie', - layout: 'trident', - layoutVersion: 6, - platform: 'win', - version: '10.0', - versionBase: '10', - versionNumber: 10 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Internet Explorer 11 - 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv 11.0) like Gecko': { - title: 'Internet Explorer 11', - platform: 'Win32', - profile: { - name: 'msie', - layout: 'trident', - layoutVersion: 7, - platform: 'win', - version: '11.0', - versionBase: '11', - versionNumber: 11 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Internet Explorer 11 - Windows 8.1 x64 Modern UI - 'Mozilla/5.0 (Windows NT 6.3; Win64; x64; Trident/7.0; rv:11.0) like Gecko': { - title: 'Internet Explorer 11', - platform: 'Win64', - profile: { - name: 'msie', - layout: 'trident', - layoutVersion: 7, - platform: 'win', - version: '11.0', - versionBase: '11', - versionNumber: 11 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Internet Explorer 11 - Windows 8.1 x64 desktop UI - 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko': { - title: 'Internet Explorer 11', - platform: 'WOW64', - profile: { - name: 'msie', - layout: 'trident', - layoutVersion: 7, - platform: 'win', - version: '11.0', - versionBase: '11', - versionNumber: 11 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Firefox 2 - // Firefox 3.5 - 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.19) Gecko/20110420 Firefox/3.5.19': { - title: 'Firefox 3.5', - platform: 'MacIntel', - profile: { - name: 'firefox', - layout: 'gecko', - layoutVersion: 20110420, - platform: 'mac', - version: '3.5.19', - versionBase: '3', - versionNumber: 3.5 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Firefox 3.6 - 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.17) Gecko/20110422 Ubuntu/10.10 (maverick) Firefox/3.6.17': { - title: 'Firefox 3.6', - platform: 'Linux i686', - profile: { - name: 'firefox', - layout: 'gecko', - layoutVersion: 20110422, - platform: 'linux', - version: '3.6.17', - versionBase: '3', - versionNumber: 3.6 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Firefox 4 - 'Mozilla/5.0 (Windows NT 6.0; rv:2.0.1) Gecko/20100101 Firefox/4.0.1': { - title: 'Firefox 4', - platform: 'Win32', - profile: { - name: 'firefox', - layout: 'gecko', - layoutVersion: 20100101, - platform: 'win', - version: '4.0.1', - versionBase: '4', - versionNumber: 4 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Firefox 10 nightly build - 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0a1) Gecko/20111103 Firefox/10.0a1': { - title: 'Firefox 10 nightly', - platform: 'Linux', - profile: { - name: 'firefox', - layout: 'gecko', - layoutVersion: 20111103, - platform: 'linux', - version: '10.0a1', - versionBase: '10', - versionNumber: 10 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Iceweasel 10.0.6 - 'Mozilla/5.0 (X11; Linux i686; rv:10.0.6) Gecko/20100101 Iceweasel/10.0.6': { - title: 'Iceweasel 10.0.6', - platform: 'Linux', - profile: { - name: 'iceweasel', - layout: 'gecko', - layoutVersion: 20100101, - platform: 'linux', - version: '10.0.6', - versionBase: '10', - versionNumber: 10 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Iceweasel 15.0.1 - 'Mozilla/5.0 (X11; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1 Iceweasel/15.0.1': { - title: 'Iceweasel 15.0.1', - platform: 'Linux', - profile: { - name: 'iceweasel', - layout: 'gecko', - layoutVersion: 20100101, - platform: 'linux', - version: '15.0.1', - versionBase: '15', - versionNumber: 15 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Firefox 5 - // Safari 3 - // Safari 4 - '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': { - title: 'Safari 4', - platform: 'MacIntel', - profile: { - name: 'safari', - layout: 'webkit', - layoutVersion: 531, - platform: 'mac', - version: '4.0.5', - versionBase: '4', - versionNumber: 4 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - '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': { - title: 'Safari 4', - platform: 'Win32', - profile: { - name: 'safari', - layout: 'webkit', - layoutVersion: 533, - platform: 'win', - version: '4.0.5', - versionBase: '4', - versionNumber: 4 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Safari 5 - // Safari 6 - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/536.29.13 (KHTML, like Gecko) Version/6.0.4 Safari/536.29.13': { - title: 'Safari 6', - platform: 'MacIntel', - profile: { - name: 'safari', - layout: 'webkit', - layoutVersion: 536, - platform: 'mac', - version: '6.0.4', - versionBase: '6', - versionNumber: 6 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Safari 6.0.5+ (doesn't have the comma in "KHTML, like Gecko") - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/536.30.1 (KHTML like Gecko) Version/6.0.5 Safari/536.30.1': { - title: 'Safari 6', - platform: 'MacIntel', - profile: { - name: 'safari', - layout: 'webkit', - layoutVersion: 536, - platform: 'mac', - version: '6.0.5', - versionBase: '6', - versionNumber: 6 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Opera 10+ - 'Opera/9.80 (Windows NT 5.1)': { - title: 'Opera 10+ (exact version unspecified)', - platform: 'Win32', - profile: { - name: 'opera', - layout: 'presto', - layoutVersion: 'unknown', - platform: 'win', - version: '10', - versionBase: '10', - versionNumber: 10 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Opera 12 - 'Opera/9.80 (Windows NT 5.1) Presto/2.12.388 Version/12.11': { - title: 'Opera 12', - platform: 'Win32', - profile: { - name: 'opera', - layout: 'presto', - layoutVersion: 'unknown', - platform: 'win', - version: '12.11', - versionBase: '12', - versionNumber: 12.11 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Opera 15 (WebKit-based) - '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': { - title: 'Opera 15', - platform: 'Win32', - profile: { - name: 'opera', - layout: 'webkit', - layoutVersion: 537, - platform: 'win', - version: '15.0.1147.130', - versionBase: '15', - versionNumber: 15 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Chrome 5 - // Chrome 6 - // Chrome 7 - // Chrome 8 - // Chrome 9 - // Chrome 10 - // Chrome 11 - // Chrome 12 - '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': { - title: 'Chrome 12', - platform: 'MacIntel', - profile: { - name: 'chrome', - layout: 'webkit', - layoutVersion: 534, - platform: 'mac', - version: '12.0.742.112', - versionBase: '12', - versionNumber: 12 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.68 Safari/534.30': { - title: 'Chrome 12', - platform: 'Linux i686', - profile: { - name: 'chrome', - layout: 'webkit', - layoutVersion: 534, - platform: 'linux', - version: '12.0.742.68', - versionBase: '12', - versionNumber: 12 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Android WebKit Browser 2.3 - 'Mozilla/5.0 (Linux; U; Android 2.3.5; en-us; HTC Vision Build/GRI40) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1': { - title: 'Android WebKit Browser 2.3', - platform: 'Linux armv7l', - profile: { - name: 'android', - layout: 'webkit', - layoutVersion: 533, - platform: 'linux', - version: '2.3.5', - versionBase: '2', - versionNumber: 2.3 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Rekonq (bug 34924) - 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.34 (KHTML, like Gecko) rekonq Safari/534.34': { - title: 'Rekonq', - platform: 'Linux i686', - profile: { - name: 'rekonq', - layout: 'webkit', - layoutVersion: 534, - platform: 'linux', - version: '534.34', - versionBase: '534', - versionNumber: 534.34 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Konqueror - 'Mozilla/5.0 (X11; Linux i686) KHTML/4.9.1 (like Gecko) Konqueror/4.9': { - title: 'Konqueror', - platform: 'Linux i686', - profile: { - name: 'konqueror', - layout: 'khtml', - layoutVersion: 'unknown', - platform: 'linux', - version: '4.9.1', - versionBase: '4', - versionNumber: 4.9 - }, - wikiEditor: { - // '4.9' is less than '4.11'. - ltr: false, - rtl: false - }, - wikiEditorLegacy: { - // The check is missing in legacyTestMap - ltr: true, - rtl: true - } - }, - // Amazon Silk - 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us; Silk/1.0.13.81_10003810) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16 Silk-Accelerated=true': { - title: 'Silk', - platform: 'Desktop', - profile: { - name: 'silk', - layout: 'webkit', - layoutVersion: 533, - platform: 'unknown', - version: '1.0.13.81_10003810', - versionBase: '1', - versionNumber: 1 - }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - 'Mozilla/5.0 (Linux; U; Android 4.0.3; en-us; KFTT Build/IML74K) AppleWebKit/535.19 (KHTML, like Gecko) Silk/2.1 Mobile Safari/535.19 Silk-Accelerated=true': { - title: 'Silk', - platform: 'Mobile', - profile: { - name: 'silk', - layout: 'webkit', - layoutVersion: 535, - platform: 'unknown', - version: '2.1', - versionBase: '2', - versionNumber: 2.1 - }, - wikiEditor: { - ltr: true, - rtl: true - } - } - }, - testMap = { - // Example from WikiEditor, modified to provide version identifiers as strings and with - // Konqueror 4.11 check added. - 'ltr': { - 'msie': [['>=', '7.0']], - 'firefox': [['>=', '2']], - 'opera': [['>=', '9.6']], - 'safari': [['>=', '3']], - 'chrome': [['>=', '3']], - 'netscape': [['>=', '9']], - 'konqueror': [['>=', '4.11']], - 'blackberry': false, - 'ipod': false, - 'iphone': false - }, - 'rtl': { - 'msie': [['>=', '8']], - 'firefox': [['>=', '2']], - 'opera': [['>=', '9.6']], - 'safari': [['>=', '3']], - 'chrome': [['>=', '3']], - 'netscape': [['>=', '9']], - 'konqueror': [['>=', '4.11']], - 'blackberry': false, - 'ipod': false, - 'iphone': false - } - }, - legacyTestMap = { - // Original example from WikiEditor. - // This is using the old, but still supported way of providing version identifiers as numbers - // instead of strings; with this method, 4.9 would be considered larger than 4.11. - 'ltr': { - 'msie': [['>=', 7.0]], - 'firefox': [['>=', 2]], - 'opera': [['>=', 9.6]], - 'safari': [['>=', 3]], - 'chrome': [['>=', 3]], - 'netscape': [['>=', 9]], - 'blackberry': false, - 'ipod': false, - 'iphone': false - }, - 'rtl': { - 'msie': [['>=', 8]], - 'firefox': [['>=', 2]], - 'opera': [['>=', 9.6]], - 'safari': [['>=', 3]], - 'chrome': [['>=', 3]], - 'netscape': [['>=', 9]], - 'blackberry': false, - 'ipod': false, - 'iphone': false - } - } - ; - - // Count test cases - $.each( uas, function () { - uacount++; - } ); - - QUnit.test( 'profile( navObject )', 7, function ( assert ) { - var p = $.client.profile(); - - function unknownOrType( val, type, summary ) { - assert.ok( typeof val === type || val === 'unknown', summary ); - } - - assert.equal( typeof p, 'object', 'profile returns an object' ); - unknownOrType( p.layout, 'string', 'p.layout is a string (or "unknown")' ); - unknownOrType( p.layoutVersion, 'number', 'p.layoutVersion is a number (or "unknown")' ); - unknownOrType( p.platform, 'string', 'p.platform is a string (or "unknown")' ); - unknownOrType( p.version, 'string', 'p.version is a string (or "unknown")' ); - unknownOrType( p.versionBase, 'string', 'p.versionBase is a string (or "unknown")' ); - assert.equal( typeof p.versionNumber, 'number', 'p.versionNumber is a number' ); - } ); - - QUnit.test( 'profile( navObject ) - samples', uacount, function ( assert ) { - // Loop through and run tests - $.each( uas, function ( rawUserAgent, data ) { - // Generate a client profile object and compare recursively - var ret = $.client.profile( { - userAgent: rawUserAgent, - platform: data.platform - } ); - assert.deepEqual( ret, data.profile, 'Client profile support check for ' + data.title + ' (' + data.platform + '): ' + rawUserAgent ); - } ); - } ); - - QUnit.test( 'test( testMap )', 4, function ( assert ) { - // .test() uses eval, make sure no exceptions are thrown - // then do a basic return value type check - var testMatch = $.client.test( testMap ), - ie7Profile = $.client.profile( { - 'userAgent': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)', - 'platform': '' - } ); - - assert.equal( typeof testMatch, 'boolean', 'map with ltr/rtl split returns a boolean value' ); - - testMatch = $.client.test( testMap.ltr ); - - assert.equal( typeof testMatch, 'boolean', 'simple map (without ltr/rtl split) returns a boolean value' ); - - assert.equal( $.client.test( { - 'msie': null - }, ie7Profile ), true, 'returns true if any version of a browser are allowed (null)' ); - - assert.equal( $.client.test( { - 'msie': false - }, ie7Profile ), false, 'returns false if all versions of a browser are not allowed (false)' ); - } ); - - QUnit.test( 'test( testMap, exactMatchOnly )', 2, function ( assert ) { - var ie7Profile = $.client.profile( { - 'userAgent': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)', - 'platform': '' - } ); - - assert.equal( $.client.test( { - 'firefox': [['>=', 2]] - }, ie7Profile, false ), true, 'returns true if browser not found and exactMatchOnly not set' ); - - assert.equal( $.client.test( { - 'firefox': [['>=', 2]] - }, ie7Profile, true ), false, 'returns false if browser not found and exactMatchOnly is set' ); - } ); - - QUnit.test( 'test( testMap ), test( legacyTestMap ) - WikiEditor sample', uacount * 2 * 2, function ( assert ) { - var $body = $( 'body' ), - bodyClasses = $body.attr( 'class' ); - - // Loop through and run tests - $.each( uas, function ( agent, data ) { - $.each( ['ltr', 'rtl'], function ( i, dir ) { - var profile, testMatch, legacyTestMatch; - $body.removeClass( 'ltr rtl' ).addClass( dir ); - profile = $.client.profile( { - userAgent: agent, - platform: data.platform - } ); - testMatch = $.client.test( testMap, profile ); - legacyTestMatch = $.client.test( legacyTestMap, profile ); - $body.removeClass( dir ); - - assert.equal( - testMatch, - data.wikiEditor[dir], - 'testing comparison based on ' + dir + ', ' + agent - ); - assert.equal( - legacyTestMatch, - data.wikiEditorLegacy ? data.wikiEditorLegacy[dir] : data.wikiEditor[dir], - 'testing comparison based on ' + dir + ', ' + agent + ' (legacyTestMap)' - ); - } ); - } ); - - // Restore body classes - $body.attr( 'class', bodyClasses ); - } ); -}( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js b/tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js index 0b7e87ee..ca3f418c 100644 --- a/tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js @@ -1,13 +1,14 @@ ( function ( $ ) { QUnit.module( 'jquery.getAttrs', QUnit.newMwEnvironment() ); - QUnit.test( 'Check', 1, function ( assert ) { + QUnit.test( 'getAttrs()', 1, function ( assert ) { var attrs = { foo: 'bar', - 'class': 'lorem' + 'class': 'lorem', + 'data-foo': 'data value' }, $el = $( '<div>' ).attr( attrs ); - assert.deepEqual( $el.getAttrs(), attrs, 'getAttrs() return object should match the attributes set, no more, no less' ); + assert.propEqual( $el.getAttrs(), attrs, 'keys and values match' ); } ); }( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js b/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js index bbea8297..78c185f1 100644 --- a/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.placeholder.test.js @@ -1,10 +1,10 @@ -(function ($) { +( function ($) { 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'); + 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) { diff --git a/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js b/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js new file mode 100644 index 00000000..97a3ae12 --- /dev/null +++ b/tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js @@ -0,0 +1,223 @@ +( function ( $, mw ) { + /** + * This module tests the input/output capabilities of the parsers of tablesorter. + * It does not test actual sorting. + */ + + var text, ipv4, + simpleMDYDatesInMDY, simpleMDYDatesInDMY, oldMDYDates, complexMDYDates, clobberedDates, MYDates, YDates, + currencyData, transformedCurrencyData; + + QUnit.module( 'jquery.tablesorter.parsers', 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'] + }, + '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', + /* default date format of the content language */ + wgDefaultDateFormat: 'dmy', + /* These two are important for numeric interpretations */ + wgSeparatorTransformTable: ['', ''], + wgDigitTransformTable: ['', ''] + } + } ) ); + + /** + * For a value, check if the parser recognizes it and how it transforms it + * + * @param {String} msg text to pass on to qunit describing the test case + * @param {String[]} parserId of the parser that will be tested + * @param {String[][]} data Array of testcases. Each testcase, array of + * inputValue: The string value that we want to test the parser for + * recognized: If we expect that this value's type is detectable by the parser + * outputValue: The value the parser has converted the input to + * msg: describing the testcase + * @param {function($table)} callback something to do before we start the testcase + */ + function parserTest( msg, parserId, data, callback ) { + QUnit.test( msg, data.length * 2, function ( assert ) { + var extractedR, extractedF, parser; + + if (callback !== undefined ) { + callback(); + } + + parser = $.tablesorter.getParser( parserId ); + $.each( data, function ( index, testcase ) { + 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] ); + } ); + + } ); + } + + text = [ + [ 'Mars', true, 'mars', 'Simple text' ], + [ 'Mẘas', true, 'mẘas', 'Non ascii character' ], + [ 'A sentence', true, 'a sentence', 'A sentence with space chars' ] + ]; + parserTest( 'Textual keys', 'text', text ); + + 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' ] + ]; + 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'] + ]; + 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'] + ]; + parserTest( 'MDY Dates using dmy content language', 'date', simpleMDYDatesInDMY, function () { + mw.config.set( { + 'wgDefaultDateFormat': 'dmy', + 'wgContentLanguage': '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'] + ]; + 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, ''] + ]; + 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'] + ]; + 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'] + ]; + 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'] + ]; + 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, ''] + ]; + 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, ''] + ]; + 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: ['', ''] + } ); + } ); + + // TODO add numbers sorting tests for bug 8115 with a different language + +}( jQuery, mediaWiki ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js index 92dad9ff..f63aa27a 100644 --- a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js @@ -156,9 +156,29 @@ ]; 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'] + }, + '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: { - wgMonthNames: ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], - wgMonthNamesShort: ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], wgDefaultDateFormat: 'dmy', wgSeparatorTransformTable: ['', ''], wgDigitTransformTable: ['', ''], @@ -1160,11 +1180,11 @@ '</table>' ); $table.tablesorter(); - assert.equal( $table.find( '#A2' ).prop( 'headerIndex' ), + assert.equal( $table.find( '#A2' ).data( 'headerIndex' ), undefined, 'A2 should not be a sort header' ); - assert.equal( $table.find( '#C1' ).prop( 'headerIndex' ), + assert.equal( $table.find( '#C1' ).data( 'headerIndex' ), 2, 'C1 should be a sort header' ); @@ -1181,11 +1201,11 @@ '</table>' ); $table.tablesorter(); - assert.equal( $table.find( '#C2' ).prop( 'headerIndex' ), + assert.equal( $table.find( '#C2' ).data( 'headerIndex' ), 2, 'C2 should be a sort header' ); - assert.equal( $table.find( '#C1' ).prop( 'headerIndex' ), + assert.equal( $table.find( '#C1' ).data( 'headerIndex' ), undefined, 'C1 should not be a sort header' ); @@ -1209,18 +1229,19 @@ // bug 53211 - exploding rowspans in more complex cases QUnit.test( 'Rowspan exploding with row headers and colspans', 1, function ( assert ) { - var $table = $( '<table class="sortable">' + - '<thead><tr><th rowspan="2">n</th><th colspan="2">foo</th><th rowspan="2">baz</th></tr>' + - '<tr><th>foo</th><th>bar</th></tr></thead>' + - '<tbody>' + - '<tr><td>1</td><td>foo</td><td>bar</td><td>baz</td></tr>' + - '<tr><td>2</td><td>foo</td><td>bar</td><td>baz</td></tr>' + - '</tbody></table>' ); + var $table = $( '<table class="sortable">' + + '<thead><tr><th rowspan="2">n</th><th colspan="2">foo</th><th rowspan="2">baz</th></tr>' + + '<tr><th>foo</th><th>bar</th></tr></thead>' + + '<tbody>' + + '<tr><td>1</td><td>foo</td><td>bar</td><td>baz</td></tr>' + + '<tr><td>2</td><td>foo</td><td>bar</td><td>baz</td></tr>' + + '</tbody></table>' ); $table.tablesorter(); - assert.equal( $table.find( 'tr:eq(1) th:eq(1)').prop('headerIndex'), + assert.equal( $table.find( 'tr:eq(1) th:eq(1)').data('headerIndex'), 2, - 'Incorrect index of sort header' ); + 'Incorrect index of sort header' + ); } ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js new file mode 100644 index 00000000..c0a6585f --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js @@ -0,0 +1,78 @@ +( function ( mw ) { + QUnit.module( 'mediawiki.api.options', QUnit.newMwEnvironment( { + setup: function () { + this.server = this.sandbox.useFakeServer(); + } + } ) ); + + QUnit.test( 'saveOption', function ( assert ) { + QUnit.expect( 2 ); + + var + api = new mw.Api(), + stub = this.sandbox.stub( mw.Api.prototype, 'saveOptions' ); + + api.saveOption( 'foo', 'bar' ); + + assert.ok( stub.calledOnce, '#saveOptions called once' ); + assert.deepEqual( stub.getCall( 0 ).args, [ { foo: 'bar' } ], '#saveOptions called correctly' ); + } ); + + QUnit.test( 'saveOptions', function ( assert ) { + QUnit.expect( 13 ); + + var api = new mw.Api(); + + // We need to respond to the request for token first, otherwise the other requests won't be sent + // until after the server.respond call, which confuses sinon terribly. This sucks a lot. + api.getToken( 'options' ); + this.server.respond( + /action=tokens.*&type=options/, + [ 200, { 'Content-Type': 'application/json' }, + '{ "tokens": { "optionstoken": "+\\\\" } }' ] + ); + + api.saveOptions( {} ).done( function () { + assert.ok( true, 'Request completed: empty case' ); + } ); + api.saveOptions( { foo: 'bar' } ).done( function () { + assert.ok( true, 'Request completed: simple' ); + } ); + api.saveOptions( { foo: 'bar', baz: 'quux' } ).done( function () { + assert.ok( true, 'Request completed: two options' ); + } ); + api.saveOptions( { foo: 'bar|quux', bar: 'a|b|c', baz: 'quux' } ).done( function () { + assert.ok( true, 'Request completed: not bundleable' ); + } ); + api.saveOptions( { foo: null } ).done( function () { + assert.ok( true, 'Request completed: reset an option' ); + } ); + api.saveOptions( { 'foo|bar=quux': null } ).done( function () { + assert.ok( true, 'Request completed: reset an option, not bundleable' ); + } ); + + // Requests are POST, match requestBody instead of url + this.server.respond( function ( request ) { + switch ( request.requestBody ) { + // simple + case 'action=options&format=json&change=foo%3Dbar&token=%2B%5C': + // two options + case 'action=options&format=json&change=foo%3Dbar%7Cbaz%3Dquux&token=%2B%5C': + // not bundleable + case 'action=options&format=json&optionname=foo&optionvalue=bar%7Cquux&token=%2B%5C': + case 'action=options&format=json&optionname=bar&optionvalue=a%7Cb%7Cc&token=%2B%5C': + case 'action=options&format=json&change=baz%3Dquux&token=%2B%5C': + // reset an option + case 'action=options&format=json&change=foo&token=%2B%5C': + // reset an option, not bundleable + case 'action=options&format=json&optionname=foo%7Cbar%3Dquux&token=%2B%5C': + assert.ok( true, 'Repond to ' + request.requestBody ); + request.respond( 200, { 'Content-Type': 'application/json' }, + '{ "options": "success" }' ); + break; + default: + assert.ok( false, 'Unexpected request:' + request.requestBody ); + } + } ); + } ); +}( 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 f156c728..b89526fb 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js @@ -51,52 +51,26 @@ this.server.respond( function ( request ) { if ( window.FormData ) { - assert.ok( !request.url.match( /action=/), 'Request has no query string' ); + assert.ok( !request.url.match( /action=/ ), 'Request has no query string' ); assert.ok( request.requestBody instanceof FormData, 'Request uses FormData body' ); } else { - assert.ok( !request.url.match( /action=test/), 'Request has no query string' ); + assert.ok( !request.url.match( /action=test/ ), 'Request has no query string' ); assert.equal( request.requestBody, 'action=test&format=json', 'Request uses query string body' ); } request.respond( 200, { 'Content-Type': 'application/json' }, '[]' ); } ); } ); - QUnit.test( 'Deprecated callback methods', function ( assert ) { - QUnit.expect( 3 ); + QUnit.test( 'Converting arrays to pipe-separated', function ( assert ) { + QUnit.expect( 1 ); var api = new mw.Api(); + api.get( { test: [ 'foo', 'bar', 'baz' ] } ); - this.suppressWarnings(); - - api.get( {}, function () { - assert.ok( true, 'Function argument treated as success callback.' ); - } ); - - api.get( {}, { - ok: function () { - assert.ok( true, '"ok" property treated as success callback.' ); - } - } ); - - api.get( { action: 'doesntexist' }, { - err: function () { - assert.ok( true, '"err" property treated as error callback.' ); - } - } ); - - this.restoreWarnings(); - - this.server.respondWith( /action=query/, function ( request ) { + 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' }, '[]' ); } ); - - this.server.respondWith( /action=doesntexist/, function ( request ) { - request.respond( 200, { 'Content-Type': 'application/json' }, - '{ "error": { "code": "unknown_action" } }' - ); - } ); - - this.server.respond(); } ); QUnit.test( 'getToken( pre-populated )', function ( assert ) { @@ -196,6 +170,28 @@ ); } ); + 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' } ) + .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(); + } ); + QUnit.test( 'postWithToken( tokenType, params, ajaxOptions )', function ( assert ) { QUnit.expect( 3 ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js index 7ab309aa..c0afe07c 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js @@ -46,8 +46,8 @@ // Note: The ones with # are commented out as those are interpreted as fragment and // as such end up being valid. 'A é B', - //'A é B', - //'A é B', + // 'A é B', + // 'A é B', // Subject of NS_TALK does not roundtrip to NS_MAIN 'Talk:File:Example.svg', // Directory navigation @@ -437,33 +437,34 @@ } ); QUnit.test( 'getRelativeText', 5, function ( assert ) { - var cases = [ - { - text: 'asd', - relativeTo: 123, - expectedResult: ':Asd' - }, - { - text: 'dfg', - relativeTo: 0, - expectedResult: 'Dfg' - }, - { - text: 'Template:Ghj', - relativeTo: 0, - expectedResult: 'Template:Ghj' - }, - { - text: 'Template:1', - relativeTo: 10, - expectedResult: '1' - }, - { - text: 'User:Hi', - relativeTo: 10, - expectedResult: 'User:Hi' - } - ], i, thisCase, title; + var i, thisCase, title, + cases = [ + { + text: 'asd', + relativeTo: 123, + expectedResult: ':Asd' + }, + { + text: 'dfg', + relativeTo: 0, + expectedResult: 'Dfg' + }, + { + text: 'Template:Ghj', + relativeTo: 0, + expectedResult: 'Template:Ghj' + }, + { + text: 'Template:1', + relativeTo: 10, + expectedResult: '1' + }, + { + text: 'User:Hi', + relativeTo: 10, + expectedResult: 'User:Hi' + } + ]; for ( i = 0; i < cases.length; i++ ) { thisCase = cases[i]; diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js index 7a58d38d..ba366553 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js @@ -314,6 +314,66 @@ assert.equal( uri.toString(), 'http://www.example.com/dir/', 'empty array value is ommitted' ); } ); + QUnit.test( 'Variable defaultUri', 2, function ( assert ) { + var uri, + href = 'http://example.org/w/index.php#here', + UriClass = mw.UriRelative( function () { + return href; + } ); + + uri = new UriClass(); + assert.deepEqual( + { + protocol: uri.protocol, + user: uri.user, + password: uri.password, + host: uri.host, + port: uri.port, + path: uri.path, + query: uri.query, + fragment: uri.fragment + }, + { + protocol: 'http', + user: undefined, + password: undefined, + host: 'example.org', + port: undefined, + path: '/w/index.php', + query: {}, + fragment: 'here' + }, + 'basic object properties' + ); + + // Default URI may change, e.g. via history.replaceState, pushState or location.hash (T74334) + href = 'https://example.com/wiki/Foo?v=2'; + uri = new UriClass(); + assert.deepEqual( + { + protocol: uri.protocol, + user: uri.user, + password: uri.password, + host: uri.host, + port: uri.port, + path: uri.path, + query: uri.query, + fragment: uri.fragment + }, + { + protocol: 'https', + user: undefined, + password: undefined, + host: 'example.com', + port: undefined, + path: '/wiki/Foo', + query: { 'v': '2' }, + fragment: undefined + }, + 'basic object properties' + ); + } ); + QUnit.test( 'Advanced URL', 11, function ( assert ) { var uri, queryString, relativePath; @@ -429,6 +489,5 @@ uri = new UriClass( testPath ); href = uri.toString(); assert.equal( href, testProtocol + testServer + ':' + testPort + testPath, 'Root-relative URL gets host, protocol, and port supplied' ); - } ); }( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js index c9653dab..f5f199ea 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js @@ -53,7 +53,7 @@ assert.strictEqual( call[ 1 ], '0', '0 is value' ); } ); - QUnit.test( 'set( key, value, expires )', 5, function ( assert ) { + QUnit.test( 'set( key, value, expires )', 6, function ( assert ) { var date, options; date = new Date(); @@ -61,15 +61,22 @@ mw.cookie.set( 'foo', 'bar' ); options = $.cookie.lastCall.args[ 2 ]; - assert.deepEqual( options.expires, expiryDate, 'Default cookie expiration is used' ); + assert.deepEqual( options.expires, expiryDate, 'default expiration' ); mw.cookie.set( 'foo', 'bar', date ); options = $.cookie.lastCall.args[ 2 ]; - assert.strictEqual( options.expires, date, 'Custom expiration date' ); + assert.strictEqual( options.expires, date, 'custom expiration as Date' ); + + date = new Date(); + date.setDate( date.getDate() + 1 ); + + mw.cookie.set( 'foo', 'bar', 86400 ); + options = $.cookie.lastCall.args[ 2 ]; + assert.deepEqual( options.expires, date, 'custom expiration as lifetime in seconds' ); mw.cookie.set( 'foo', 'bar', null ); options = $.cookie.lastCall.args[ 2 ]; - assert.strictEqual( options.expires, undefined, 'Expiry null forces session cookie' ); + assert.strictEqual( options.expires, undefined, 'null forces session cookie' ); // Per DefaultSettings.php, when wgCookieExpiration is 0, the default should // be session cookies @@ -81,7 +88,7 @@ mw.cookie.set( 'foo', 'bar', date ); options = $.cookie.lastCall.args[ 2 ]; - assert.strictEqual( options.expires, date, 'Custom expiration when default is session cookies' ); + assert.strictEqual( options.expires, date, 'custom expiration (with wgCookieExpiration=0)' ); } ); QUnit.test( 'set( key, value, options )', 4, function ( assert ) { @@ -169,4 +176,4 @@ assert.strictEqual( key, 'barfoo' ); } ); -} ( mediaWiki, jQuery ) ); +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js new file mode 100644 index 00000000..7c3f1ecb --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js @@ -0,0 +1,42 @@ +( function ( $, mw ) { + QUnit.module( 'mediawiki.errorLogger', QUnit.newMwEnvironment() ); + + QUnit.test( 'installGlobalHandler', 7, function ( assert ) { + var w = {}, + errorMessage = 'Foo', + errorUrl = 'http://example.com', + errorLine = '123', + errorColumn = '45', + errorObject = new Error( 'Foo'), + oldHandler = this.sandbox.stub(); + + this.sandbox.stub( mw, 'track' ); + + mw.errorLogger.installGlobalHandler( w ); + + assert.ok( w.onerror, 'Global handler has been installed' ); + assert.strictEqual( w.onerror( errorMessage, errorUrl, errorLine ), false, + 'Global handler returns false when there is no previous handler' ); + sinon.assert.calledWithExactly( mw.track, 'global.error', + sinon.match( { errorMessage: errorMessage, url: errorUrl, lineNumber: errorLine } ) ); + + mw.track.reset(); + w.onerror( errorMessage, errorUrl, errorLine, errorColumn, errorObject ); + sinon.assert.calledWithExactly( mw.track, 'global.error', + sinon.match( { errorMessage: errorMessage, url: errorUrl, lineNumber: errorLine, + columnNumber: errorColumn, errorObject: errorObject } ) ); + + w = { onerror: oldHandler }; + + mw.errorLogger.installGlobalHandler( w ); + w.onerror( errorMessage, errorUrl, errorLine ); + sinon.assert.calledWithExactly( oldHandler, errorMessage, errorUrl, errorLine ); + + oldHandler.returns( false ); + assert.strictEqual( w.onerror( errorMessage, errorUrl, errorLine ), false, + 'Global handler preserves false return from previous handler' ); + oldHandler.returns( true ); + assert.strictEqual( w.onerror( errorMessage, errorUrl, errorLine ), true, + 'Global handler preserves true return from previous handler' ); + } ); +}( jQuery, mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js index 6b3be43b..7e23e2ff 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js @@ -55,7 +55,9 @@ 'jquerymsg-test-version-entrypoints-index-php': '[https://www.mediawiki.org/wiki/Manual:index.php index.php]', - 'external-link-replace': 'Foo [$1 bar]' + 'external-link-replace': 'Foo [$1 bar]', + 'external-link-plural': 'Foo {{PLURAL:$1|is [$2 one]|are [$2 some]|2=[$2 two]|3=three|4=a=b|5=}} things.', + 'plural-only-explicit-forms': 'It is a {{PLURAL:$1|1=single|2=double}} room.' } } ) ); @@ -107,7 +109,7 @@ run(); } - QUnit.test( 'Replace', 9, function ( assert ) { + QUnit.test( 'Replace', 16, function ( assert ) { mw.messages.set( 'simple', 'Foo $1 baz $2' ); assert.equal( formatParse( 'simple' ), 'Foo $1 baz $2', 'Replacements with no substitutes' ); @@ -155,6 +157,41 @@ 'Foo <a href="http://example.org/?x=y&z">bar</a>', 'Href is not double-escaped in wikilink function' ); + assert.equal( + formatParse( 'external-link-plural', 1, 'http://example.org' ), + 'Foo is <a href="http://example.org">one</a> things.', + 'Link is expanded inside plural and is not escaped html' + ); + assert.equal( + formatParse( 'external-link-plural', 2, 'http://example.org' ), + 'Foo <a href=\"http://example.org\">two</a> things.', + 'Link is expanded inside an explicit plural form and is not escaped html' + ); + assert.equal( + formatParse( 'external-link-plural', 3 ), + 'Foo three things.', + 'A simple explicit plural form co-existing with complex explicit plural forms' + ); + assert.equal( + formatParse( 'external-link-plural', 4, 'http://example.org' ), + 'Foo a=b things.', + 'Only first equal sign is used as delimiter for explicit plural form. Repeated equal signs does not create issue' + ); + assert.equal( + formatParse( 'external-link-plural', 5, 'http://example.org' ), + 'Foo are <a href="http://example.org">some</a> things.', + 'Invalid explicit plural form. Plural fallback to the "other" plural form' + ); + assert.equal( + formatParse( 'external-link-plural', 6, 'http://example.org' ), + 'Foo are <a href="http://example.org">some</a> things.', + 'Plural fallback to the "other" plural form' + ); + assert.equal( + formatParse( 'plural-only-explicit-forms', 2 ), + 'It is a double room.', + 'Plural with explicit forms alone.' + ); } ); QUnit.test( 'Plural', 6, function ( assert ) { @@ -505,274 +542,274 @@ mw.jqueryMsg.getMessageFunction = oldGMF; } ); -formatnumTests = [ - { - lang: 'en', - number: 987654321.654321, - result: '987,654,321.654', - description: 'formatnum test for English, decimal seperator' - }, - { - lang: 'ar', - number: 987654321.654321, - result: '٩٨٧٬٦٥٤٬٣٢١٫٦٥٤', - description: 'formatnum test for Arabic, with decimal seperator' - }, - { - lang: 'ar', - number: '٩٨٧٦٥٤٣٢١٫٦٥٤٣٢١', - result: 987654321, - integer: true, - description: 'formatnum test for Arabic, with decimal seperator, reverse' - }, - { - lang: 'ar', - number: -12.89, - result: '-١٢٫٨٩', - description: 'formatnum test for Arabic, negative number' - }, - { - lang: 'ar', - number: '-١٢٫٨٩', - result: -12, - integer: true, - description: 'formatnum test for Arabic, negative number, reverse' - }, - { - lang: 'nl', - number: 987654321.654321, - result: '987.654.321,654', - description: 'formatnum test for Nederlands, decimal seperator' - }, - { - lang: 'nl', - number: -12.89, - result: '-12,89', - description: 'formatnum test for Nederlands, negative number' - }, - { - lang: 'nl', - number: '.89', - result: '0,89', - description: 'formatnum test for Nederlands' - }, - { - lang: 'nl', - number: 'invalidnumber', - result: 'invalidnumber', - description: 'formatnum test for Nederlands, invalid number' - }, - { - lang: 'ml', - number: '1000000000', - result: '1,00,00,00,000', - description: 'formatnum test for Malayalam' - }, - { - lang: 'ml', - number: '-1000000000', - result: '-1,00,00,00,000', - description: 'formatnum test for Malayalam, negative number' - }, - /* - * This will fail because of wrong pattern for ml in MW(different from CLDR) - { - lang: 'ml', - number: '1000000000.000', - result: '1,00,00,00,000.000', - description: 'formatnum test for Malayalam with decimal place' - }, - */ - { - lang: 'hi', - number: '123456789.123456789', - result: '१२,३४,५६,७८९', - description: 'formatnum test for Hindi' - }, - { - lang: 'hi', - number: '१२,३४,५६,७८९', - result: '१२,३४,५६,७८९', - description: 'formatnum test for Hindi, Devanagari digits passed' - }, - { - lang: 'hi', - number: '१२३४५६,७८९', - result: '123456', - integer: true, - description: 'formatnum test for Hindi, Devanagari digits passed to get integer value' - } -]; - -QUnit.test( 'formatnum', formatnumTests.length, function ( assert ) { - 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 ) { - getMwLanguage( test.lang ) - .done( function ( langClass ) { - mw.config.set( 'wgUserLanguage', test.lang ); - var parser = new mw.jqueryMsg.parser( { language: langClass } ); - assert.equal( - parser.parse( test.integer ? 'formatnum-msg-int' : 'formatnum-msg', - [ test.number ] ).html(), - test.result, - test.description - ); - } ) - .fail( function () { - assert.ok( false, 'Language "' + test.lang + '" failed to load' ); - } ) - .always( next ); - }; + formatnumTests = [ + { + lang: 'en', + number: 987654321.654321, + result: '987,654,321.654', + description: 'formatnum test for English, decimal separator' + }, + { + lang: 'ar', + number: 987654321.654321, + result: '٩٨٧٬٦٥٤٬٣٢١٫٦٥٤', + description: 'formatnum test for Arabic, with decimal separator' + }, + { + lang: 'ar', + number: '٩٨٧٦٥٤٣٢١٫٦٥٤٣٢١', + result: 987654321, + integer: true, + description: 'formatnum test for Arabic, with decimal separator, reverse' + }, + { + lang: 'ar', + number: -12.89, + result: '-١٢٫٨٩', + description: 'formatnum test for Arabic, negative number' + }, + { + lang: 'ar', + number: '-١٢٫٨٩', + result: -12, + integer: true, + description: 'formatnum test for Arabic, negative number, reverse' + }, + { + lang: 'nl', + number: 987654321.654321, + result: '987.654.321,654', + description: 'formatnum test for Nederlands, decimal separator' + }, + { + lang: 'nl', + number: -12.89, + result: '-12,89', + description: 'formatnum test for Nederlands, negative number' + }, + { + lang: 'nl', + number: '.89', + result: '0,89', + description: 'formatnum test for Nederlands' + }, + { + lang: 'nl', + number: 'invalidnumber', + result: 'invalidnumber', + description: 'formatnum test for Nederlands, invalid number' + }, + { + lang: 'ml', + number: '1000000000', + result: '1,00,00,00,000', + description: 'formatnum test for Malayalam' + }, + { + lang: 'ml', + number: '-1000000000', + result: '-1,00,00,00,000', + description: 'formatnum test for Malayalam, negative number' + }, + /* + * This will fail because of wrong pattern for ml in MW(different from CLDR) + { + lang: 'ml', + number: '1000000000.000', + result: '1,00,00,00,000.000', + description: 'formatnum test for Malayalam with decimal place' + }, + */ + { + lang: 'hi', + number: '123456789.123456789', + result: '१२,३४,५६,७८९', + description: 'formatnum test for Hindi' + }, + { + lang: 'hi', + number: '१२,३४,५६,७८९', + result: '१२,३४,५६,७८९', + description: 'formatnum test for Hindi, Devanagari digits passed' + }, + { + lang: 'hi', + number: '१२३४५६,७८९', + result: '123456', + integer: true, + description: 'formatnum test for Hindi, Devanagari digits passed to get integer value' + } + ]; + + QUnit.test( 'formatnum', formatnumTests.length, function ( assert ) { + 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 ) { + getMwLanguage( test.lang ) + .done( function ( langClass ) { + mw.config.set( 'wgUserLanguage', test.lang ); + var parser = new mw.jqueryMsg.parser( { language: langClass } ); + assert.equal( + parser.parse( test.integer ? 'formatnum-msg-int' : 'formatnum-msg', + [ test.number ] ).html(), + test.result, + test.description + ); + } ) + .fail( function () { + assert.ok( false, 'Language "' + test.lang + '" failed to load' ); + } ) + .always( next ); + }; + } ); + QUnit.stop(); + process( queue, QUnit.start ); + } ); + + // HTML in wikitext + 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' ); + + mw.messages.set( 'jquerymsg-bold-msg', '<b>Strong</b> speaker' ); + 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' ); + + 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' ); + + mw.messages.set( 'jquerymsg-italics-with-link', 'An <i>italicized [[link|wiki-link]]</i>' ); + + assert.htmlEqual( + formatParse( 'jquerymsg-italics-with-link' ), + 'An <i>italicized <a title="link" href="' + mw.html.escape( mw.util.getUrl( 'link' ) ) + '">wiki-link</i>', + 'Italics with link inside in parse mode' + ); + + assert.equal( + formatText( 'jquerymsg-italics-with-link' ), + mw.messages.get( 'jquerymsg-italics-with-link' ), + 'Italics with link unchanged in text mode' + ); + + mw.messages.set( 'jquerymsg-italics-id-class', '<i id="foo" class="bar">Foo</i>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-italics-id-class' ), + mw.messages.get( 'jquerymsg-italics-id-class' ), + 'ID and class are allowed' + ); + + mw.messages.set( 'jquerymsg-italics-onclick', '<i onclick="alert(\'foo\')">Foo</i>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-italics-onclick' ), + '<i onclick="alert(\'foo\')">Foo</i>', + 'element with onclick is escaped because it is not allowed' + ); + + mw.messages.set( 'jquerymsg-script-msg', '<script >alert( "Who put this tag here?" );</script>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-script-msg' ), + '<script >alert( "Who put this tag here?" );</script>', + 'Tag outside whitelist escaped in parse mode' + ); + + assert.equal( + formatText( 'jquerymsg-script-msg' ), + mw.messages.get( 'jquerymsg-script-msg' ), + 'Tag outside whitelist unchanged in text mode' + ); + + mw.messages.set( 'jquerymsg-script-link-msg', '<script>[[Foo|bar]]</script>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-script-link-msg' ), + '<script><a title="Foo" href="' + mw.html.escape( mw.util.getUrl( 'Foo' ) ) + '">bar</a></script>', + 'Script tag text is escaped because that element is not allowed, but link inside is still HTML' + ); + + mw.messages.set( 'jquerymsg-mismatched-html', '<i class="important">test</b>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-mismatched-html' ), + '<i class="important">test</b>', + '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>', + '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>', + 'Non-whitelisted HTML tag in external link anchor treated as text' + ); + + // Intentionally not using htmlEqual for the quote tests + mw.messages.set( 'jquerymsg-double-quotes-preserved', '<i id="double">Double</i>' ); + assert.equal( + formatParse( 'jquerymsg-double-quotes-preserved' ), + mw.messages.get( 'jquerymsg-double-quotes-preserved' ), + 'Attributes with double quotes are preserved as such' + ); + + mw.messages.set( 'jquerymsg-single-quotes-normalized-to-double', '<i id=\'single\'>Single</i>' ); + assert.equal( + formatParse( 'jquerymsg-single-quotes-normalized-to-double' ), + '<i id="single">Single</i>', + 'Attributes with single quotes are normalized to double' + ); + + mw.messages.set( 'jquerymsg-escaped-double-quotes-attribute', '<i style="font-family:"Arial"">Styled</i>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-escaped-double-quotes-attribute' ), + mw.messages.get( 'jquerymsg-escaped-double-quotes-attribute' ), + 'Escaped attributes are parsed correctly' + ); + + mw.messages.set( 'jquerymsg-escaped-single-quotes-attribute', '<i style=\'font-family:'Arial'\'>Styled</i>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-escaped-single-quotes-attribute' ), + mw.messages.get( 'jquerymsg-escaped-single-quotes-attribute' ), + 'Escaped attributes are parsed correctly' + ); + + mw.messages.set( 'jquerymsg-wikitext-contents-parsed', '<i>[http://example.com Example]</i>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-wikitext-contents-parsed' ), + '<i><a href="http://example.com">Example</a></i>', + 'Contents of valid tag are treated as wikitext, so external link is parsed' + ); + + 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>', + 'Contents of valid tag are treated as wikitext, so invalid HTML element is treated as text' + ); + + mw.messages.set( 'jquerymsg-unclosed-tag', 'Foo<tag>bar' ); + assert.htmlEqual( + formatParse( 'jquerymsg-unclosed-tag' ), + 'Foo<tag>bar', + 'Nonsupported unclosed tags are escaped' + ); + + mw.messages.set( 'jquerymsg-self-closing-tag', 'Foo<tag/>bar' ); + assert.htmlEqual( + formatParse( 'jquerymsg-self-closing-tag' ), + 'Foo<tag/>bar', + 'Self-closing tags don\'t cause a parse error' + ); } ); - QUnit.stop(); - process( queue, QUnit.start ); -} ); - -// HTML in wikitext -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' ); - - mw.messages.set( 'jquerymsg-bold-msg', '<b>Strong</b> speaker' ); - 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' ); - - 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' ); - - mw.messages.set( 'jquerymsg-italics-with-link', 'An <i>italicized [[link|wiki-link]]</i>' ); - - assert.htmlEqual( - formatParse( 'jquerymsg-italics-with-link' ), - 'An <i>italicized <a title="link" href="' + mw.html.escape( mw.util.getUrl( 'link' ) ) + '">wiki-link</i>', - 'Italics with link inside in parse mode' - ); - - assert.equal( - formatText( 'jquerymsg-italics-with-link' ), - mw.messages.get( 'jquerymsg-italics-with-link' ), - 'Italics with link unchanged in text mode' - ); - - mw.messages.set( 'jquerymsg-italics-id-class', '<i id="foo" class="bar">Foo</i>' ); - assert.htmlEqual( - formatParse( 'jquerymsg-italics-id-class' ), - mw.messages.get( 'jquerymsg-italics-id-class' ), - 'ID and class are allowed' - ); - - mw.messages.set( 'jquerymsg-italics-onclick', '<i onclick="alert(\'foo\')">Foo</i>' ); - assert.htmlEqual( - formatParse( 'jquerymsg-italics-onclick' ), - '<i onclick="alert(\'foo\')">Foo</i>', - 'element with onclick is escaped because it is not allowed' - ); - - mw.messages.set( 'jquerymsg-script-msg', '<script >alert( "Who put this tag here?" );</script>' ); - assert.htmlEqual( - formatParse( 'jquerymsg-script-msg' ), - '<script >alert( "Who put this tag here?" );</script>', - 'Tag outside whitelist escaped in parse mode' - ); - - assert.equal( - formatText( 'jquerymsg-script-msg' ), - mw.messages.get( 'jquerymsg-script-msg' ), - 'Tag outside whitelist unchanged in text mode' - ); - - mw.messages.set( 'jquerymsg-script-link-msg', '<script>[[Foo|bar]]</script>' ); - assert.htmlEqual( - formatParse( 'jquerymsg-script-link-msg' ), - '<script><a title="Foo" href="' + mw.html.escape( mw.util.getUrl( 'Foo' ) ) + '">bar</a></script>', - 'Script tag text is escaped because that element is not allowed, but link inside is still HTML' - ); - - mw.messages.set( 'jquerymsg-mismatched-html', '<i class="important">test</b>' ); - assert.htmlEqual( - formatParse( 'jquerymsg-mismatched-html' ), - '<i class="important">test</b>', - '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>', - '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>', - 'Non-whitelisted HTML tag in external link anchor treated as text' - ); - - // Intentionally not using htmlEqual for the quote tests - mw.messages.set( 'jquerymsg-double-quotes-preserved', '<i id="double">Double</i>' ); - assert.equal( - formatParse( 'jquerymsg-double-quotes-preserved' ), - mw.messages.get( 'jquerymsg-double-quotes-preserved' ), - 'Attributes with double quotes are preserved as such' - ); - - mw.messages.set( 'jquerymsg-single-quotes-normalized-to-double', '<i id=\'single\'>Single</i>' ); - assert.equal( - formatParse( 'jquerymsg-single-quotes-normalized-to-double' ), - '<i id="single">Single</i>', - 'Attributes with single quotes are normalized to double' - ); - - mw.messages.set( 'jquerymsg-escaped-double-quotes-attribute', '<i style="font-family:"Arial"">Styled</i>' ); - assert.htmlEqual( - formatParse( 'jquerymsg-escaped-double-quotes-attribute' ), - mw.messages.get( 'jquerymsg-escaped-double-quotes-attribute' ), - 'Escaped attributes are parsed correctly' - ); - - mw.messages.set( 'jquerymsg-escaped-single-quotes-attribute', '<i style=\'font-family:'Arial'\'>Styled</i>' ); - assert.htmlEqual( - formatParse( 'jquerymsg-escaped-single-quotes-attribute' ), - mw.messages.get( 'jquerymsg-escaped-single-quotes-attribute' ), - 'Escaped attributes are parsed correctly' - ); - - mw.messages.set( 'jquerymsg-wikitext-contents-parsed', '<i>[http://example.com Example]</i>' ); - assert.htmlEqual( - formatParse( 'jquerymsg-wikitext-contents-parsed' ), - '<i><a href="http://example.com">Example</a></i>', - 'Contents of valid tag are treated as wikitext, so external link is parsed' - ); - - 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>', - 'Contents of valid tag are treated as wikitext, so invalid HTML element is treated as text' - ); - - mw.messages.set( 'jquerymsg-unclosed-tag', 'Foo<tag>bar' ); - assert.htmlEqual( - formatParse( 'jquerymsg-unclosed-tag' ), - 'Foo<tag>bar', - 'Nonsupported unclosed tags are escaped' - ); - - mw.messages.set( 'jquerymsg-self-closing-tag', 'Foo<tag/>bar' ); - assert.htmlEqual( - formatParse( 'jquerymsg-self-closing-tag' ), - 'Foo<tag/>bar', - 'Self-closing tags don\'t cause a parse error' - ); -} ); QUnit.test( 'Behavior in case of invalid wikitext', 3, function ( assert ) { mw.messages.set( 'invalid-wikitext', '<b>{{FAIL}}</b>' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js index 16f90df8..670914eb 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js @@ -8,19 +8,32 @@ }, teardown: function () { mw.language.data.values = this.liveLangData; + }, + messages: { + // mw.language.listToText test + 'and': ' and', + 'comma-separator': ', ', + 'word-separator': ' ' } } ) ); - QUnit.test( 'mw.language getData and setData', 2, function ( assert ) { + QUnit.test( 'mw.language getData and setData', 3, function ( assert ) { mw.language.setData( 'en', 'testkey', 'testvalue' ); assert.equal( mw.language.getData( 'en', 'testkey' ), 'testvalue', 'Getter setter test for mw.language' ); assert.equal( mw.language.getData( 'en', 'invalidkey' ), undefined, 'Getter setter test for mw.language with invalid key' ); + mw.language.setData( 'en-us', 'testkey', 'testvalue' ); + assert.equal( mw.language.getData( 'en-US', 'testkey' ), 'testvalue', 'Case insensitive test for mw.language' ); } ); QUnit.test( 'mw.language.commafy test', 9, function ( assert ) { + mw.language.setData( 'en', 'digitGroupingPattern', null ); + mw.language.setData( 'en', 'digitTransformTable', null ); + mw.language.setData( 'en', 'separatorTransformTable', null ); + + mw.config.set( 'wgUserLanguage', 'en' ); // Number grouping patterns are as per http://cldr.unicode.org/translation/number-patterns assert.equal( mw.language.commafy( 1234.567, '###0.#####' ), '1234.567', 'Pattern with no digit grouping separator defined' ); - assert.equal( mw.language.commafy( 123456789.567, '###0.#####' ), '123456789.567', 'Pattern with no digit grouping seperator defined, bigger decimal part' ); + assert.equal( mw.language.commafy( 123456789.567, '###0.#####' ), '123456789.567', 'Pattern with no digit grouping separator defined, bigger decimal part' ); assert.equal( mw.language.commafy( 0.567, '###0.#####' ), '0.567', 'Decimal part 0' ); assert.equal( mw.language.commafy( '.567', '###0.#####' ), '0.567', 'Decimal part missing. replace with zero' ); assert.equal( mw.language.commafy( 1234, '##,#0.#####' ), '12,34', 'Pattern with no fractional part' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js new file mode 100644 index 00000000..61bab03f --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js @@ -0,0 +1,28 @@ +( function ( mw ) { + var TEST_MODEL = 'test-content-model'; + + QUnit.module( 'mediawiki.messagePoster', QUnit.newMwEnvironment( { + teardown: function () { + mw.messagePoster.factory.unregister( TEST_MODEL ); + } + } ) ); + + QUnit.test( 'register', 2, function ( assert ) { + var testMessagePosterConstructor = function () {}; + + mw.messagePoster.factory.register( TEST_MODEL, testMessagePosterConstructor ); + assert.strictEqual( + mw.messagePoster.factory.contentModelToClass[TEST_MODEL], + testMessagePosterConstructor, + 'Constructor is registered' + ); + + assert.throws( + function () { + mw.messagePoster.factory.register( TEST_MODEL, testMessagePosterConstructor ); + }, + new RegExp( 'The content model \'' + TEST_MODEL + '\' is already registered.' ), + 'Throws exception is same model is registered a second time' + ); + } ); +}( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js new file mode 100644 index 00000000..86fd828a --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js @@ -0,0 +1,63 @@ +( function ( mw ) { + + QUnit.module( 'mediawiki.template', { + setup: function () { + var abcCompiler = { + compile: function () { + return 'abc default compiler'; + } + }; + + // Register some template compiler languages + mw.template.registerCompiler( 'abc', abcCompiler ); + mw.template.registerCompiler( 'xyz', { + compile: function () { + return 'xyz compiler'; + } + } ); + + // Stub register some templates + this.sandbox.stub( mw.templates, 'get' ).returns( { + 'test_templates_foo.xyz': 'goodbye', + 'test_templates_foo.abc': 'thankyou' + } ); + } + } ); + + QUnit.test( 'add', 1, function ( assert ) { + assert.throws( + function () { + mw.template.add( 'module', 'test_templates_foo', 'hello' ); + }, + 'When no prefix throw exception' + ); + } ); + + QUnit.test( 'compile', 1, function ( assert ) { + assert.throws( + function () { + mw.template.compile( '{{foo}}', 'rainbow' ); + }, + 'Unknown compiler names throw exceptions' + ); + } ); + + QUnit.test( 'get', 4, function ( assert ) { + assert.strictEqual( mw.template.get( 'test.mediawiki.template', 'test_templates_foo.xyz' ), 'xyz compiler' ); + assert.strictEqual( mw.template.get( 'test.mediawiki.template', 'test_templates_foo.abc' ), 'abc default compiler' ); + assert.throws( + function () { + mw.template.get( 'this.should.not.exist', 'hello' ); + }, + 'When bad module name given throw error.' + ); + + assert.throws( + function () { + mw.template.get( 'mediawiki.template', 'hello' ); + }, + 'The template hello should not exist in the mediawiki.templates module and should throw an exception.' + ); + } ); + +}( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js index 7e0ee917..cf36ea82 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js @@ -1,6 +1,8 @@ /*jshint -W024 */ ( function ( mw, $ ) { - var specialCharactersPageName; + var specialCharactersPageName, + // Can't mock SITENAME since jqueryMsg caches it at load + siteName = mw.config.get( 'wgSiteName' ); // Since QUnitTestResources.php loads both mediawiki and mediawiki.jqueryMsg as // dependencies, this only tests the monkey-patched behavior with the two of them combined. @@ -55,7 +57,7 @@ this.restoreWarnings(); } ); - QUnit.test( 'mw.Map', 28, function ( assert ) { + QUnit.test( 'mw.Map', 35, function ( assert ) { var arry, conf, funky, globalConf, nummy, someValues; conf = new mw.Map(); @@ -86,8 +88,10 @@ assert.strictEqual( conf.set( 'constructor', 42 ), true, 'Map.set for key "constructor"' ); assert.strictEqual( conf.get( 'constructor' ), 42, 'Map.get for key "constructor"' ); - assert.strictEqual( conf.set( 'ImUndefined', undefined ), true, 'Map.set allows setting value to `undefined`' ); - assert.equal( conf.get( 'ImUndefined', 'fallback' ), undefined, 'Map.get supports retreiving value of `undefined`' ); + assert.strictEqual( conf.set( 'undef' ), false, 'Map.set requires explicit value (no undefined default)' ); + + assert.strictEqual( conf.set( 'undef', undefined ), true, 'Map.set allows setting value to `undefined`' ); + assert.equal( conf.get( 'undef', 'fallback' ), undefined, 'Map.get supports retreiving value of `undefined`' ); assert.strictEqual( conf.set( funky, 'Funky' ), false, 'Map.set returns boolean false if key was invalid (Function)' ); assert.strictEqual( conf.set( arry, 'Arry' ), false, 'Map.set returns boolean false if key was invalid (Array)' ); @@ -99,7 +103,7 @@ conf.set( String( nummy ), 'I used to be a number' ); assert.strictEqual( conf.exists( 'doesNotExist' ), false, 'Map.exists where property does not exist' ); - assert.strictEqual( conf.exists( 'ImUndefined' ), true, 'Map.exists where value is `undefined`' ); + assert.strictEqual( conf.exists( 'undef' ), true, 'Map.exists where value is `undefined`' ); assert.strictEqual( conf.exists( nummy ), false, 'Map.exists where key is invalid but looks like an existing key' ); // Multiple values at once @@ -126,12 +130,31 @@ conf.set( 'globalMapChecker', 'Hi' ); - assert.ok( 'globalMapChecker' in window === false, 'new mw.Map did not store its values in the global window object by default' ); + assert.ok( ( 'globalMapChecker' in window ) === false, 'Map does not its store values in the window object by default' ); globalConf = new mw.Map( true ); globalConf.set( 'anotherGlobalMapChecker', 'Hello' ); - assert.ok( 'anotherGlobalMapChecker' in window, 'new mw.Map( true ) did store its values in the global window object' ); + assert.ok( 'anotherGlobalMapChecker' in window, 'global Map stores its values in the window object' ); + + assert.equal( globalConf.get( 'anotherGlobalMapChecker' ), 'Hello', 'get value from global Map via get()' ); + this.suppressWarnings(); + assert.equal( window.anotherGlobalMapChecker, 'Hello', 'get value from global Map via window object' ); + this.restoreWarnings(); + + // Change value via global Map + 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' ); + this.restoreWarnings(); + + // Change value via window object + this.suppressWarnings(); + window.anotherGlobalMapChecker = 'World'; + assert.equal( window.anotherGlobalMapChecker, 'World', 'Change in window object works' ); + this.restoreWarnings(); + assert.equal( globalConf.get( 'anotherGlobalMapChecker' ), 'Again', 'Change in window object not reflected in global Map' ); // Whitelist this global variable for QUnit's 'noglobal' mode if ( QUnit.config.noglobals ) { @@ -148,7 +171,9 @@ // Convenience method for asserting the same result for multiple formats function assertMultipleFormats( messageArguments, formats, expectedResult, assertMessage ) { - var len = formats.length, format, i; + var format, i, + 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 ); @@ -178,9 +203,9 @@ 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'], '"' + mw.config.get( 'wgSiteName') + '" 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( '"' + mw.config.get( 'wgSiteName') + '" is the home of Other Message' ), 'Escaped 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' ); @@ -243,8 +268,8 @@ 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 ' + mw.config.get( 'wgSiteName' ), 'Grammar is resolved' ); - assert.equal( mw.message( 'grammar-msg' ).escaped(), 'Przeszukaj ' + mw.html.escape( mw.config.get( 'wgSiteName' ) ), 'Grammar is resolved in escaped mode' ); + 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' ); assert.equal( mw.message( 'formatnum-msg' ).plain(), mw.messages.get( 'formatnum-msg' ), 'formatnum is not resolved in plain mode' ); @@ -304,7 +329,7 @@ assert.equal( mw.msg( 'gender-plural-msg', 'female', '1' ), 'she is awesome', 'Gender test for female, plural count 1' ); assert.equal( mw.msg( 'gender-plural-msg', 'unknown', 10 ), 'they are awesome', 'Gender test for neutral, plural count 10' ); - assert.equal( mw.msg( 'grammar-msg' ), 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'Grammar is resolved' ); + assert.equal( mw.msg( 'grammar-msg' ), 'Przeszukaj ' + siteName, 'Grammar is resolved' ); assert.equal( mw.msg( 'formatnum-msg', '987654321.654321' ), '987,654,321.654', 'formatnum is resolved' ); @@ -352,7 +377,7 @@ return; } // Otherwise, keep polling - setTimeout( styleTestLoop, 150 ); + setTimeout( styleTestLoop ); } // Start the loop @@ -396,6 +421,30 @@ } ); } ); + QUnit.asyncTest( 'mw.loader with Object method as module name', 2, function ( assert ) { + var isAwesomeDone; + + mw.loader.testCallback = function () { + QUnit.start(); + assert.strictEqual( isAwesomeDone, undefined, 'Implementing module hasOwnProperty: isAwesomeDone should still be undefined' ); + isAwesomeDone = true; + }; + + mw.loader.implement( 'hasOwnProperty', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} ); + + mw.loader.using( 'hasOwnProperty', function () { + + // /sample/awesome.js declares the "mw.loader.testCallback" function + // which contains a call to start() and ok() + assert.strictEqual( isAwesomeDone, true, 'hasOwnProperty module should\'ve caused isAwesomeDone to be true' ); + delete mw.loader.testCallback; + + }, function () { + QUnit.start(); + assert.ok( false, 'Error callback fired while loader.using "hasOwnProperty" module' ); + } ); + } ); + QUnit.asyncTest( 'mw.loader.using( .. ).promise', 2, function ( assert ) { var isAwesomeDone; @@ -625,6 +674,11 @@ } ); + QUnit.test( 'mw.loader.implement( only scripts )', 1, function ( assert ) { + mw.loader.implement( 'test.onlyscripts', function () {} ); + assert.strictEqual( mw.loader.getState( 'test.onlyscripts' ), 'ready' ); + } ); + 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' ); @@ -638,7 +692,10 @@ } ); } ); - QUnit.test( 'mw.loader erroneous indirect dependency', 3, function ( assert ) { + QUnit.test( 'mw.loader erroneous indirect dependency', 4, function ( assert ) { + // don't emit an error event + this.sandbox.stub( mw, 'track' ); + mw.loader.register( [ ['test.module1', '0'], ['test.module2', '0', ['test.module1']], @@ -650,6 +707,8 @@ assert.strictEqual( mw.loader.getState( 'test.module1' ), 'error', 'Expected "error" state for test.module1' ); assert.strictEqual( mw.loader.getState( 'test.module2' ), 'error', 'Expected "error" state for test.module2' ); assert.strictEqual( mw.loader.getState( 'test.module3' ), 'error', 'Expected "error" state for test.module3' ); + + assert.strictEqual( mw.track.callCount, 1 ); } ); QUnit.test( 'mw.loader out-of-order implementation', 9, function ( assert ) { diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js new file mode 100644 index 00000000..cdb26244 --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js @@ -0,0 +1,42 @@ +( function ( mw ) { + QUnit.module( 'mediawiki.track' ); + + QUnit.test( 'track', 1, function ( assert ) { + var sequence = []; + mw.trackSubscribe( 'simple', function ( topic, data ) { + sequence.push( [ topic, data ] ); + } ); + mw.track( 'simple', { key: 1 } ); + mw.track( 'simple', { key: 2 } ); + + assert.deepEqual( sequence, [ + [ 'simple', { key: 1 } ], + [ 'simple', { key: 2 } ] + ], 'Events after subscribing' ); + } ); + + QUnit.test( 'trackSubscribe', 4, function ( assert ) { + var now, + sequence = []; + mw.track( 'before', { key: 1 } ); + mw.track( 'before', { key: 2 } ); + mw.trackSubscribe( 'before', function ( topic, data ) { + sequence.push( [ topic, data ] ); + } ); + mw.track( 'before', { key: 3 } ); + + assert.deepEqual( sequence, [ + [ 'before', { key: 1 } ], + [ 'before', { key: 2 } ], + [ 'before', { key: 3 } ] + ], 'Replay events from before subscribing' ); + + now = mw.now(); + mw.track( 'context', { key: 0 } ); + mw.trackSubscribe( 'context', function ( topic, data ) { + assert.strictEqual( this.topic, topic, 'thisValue has topic' ); + assert.strictEqual( this.data, data, 'thisValue has data' ); + assert.assertTrue( this.timeStamp >= now, 'thisValue has sane timestamp' ); + } ); + } ); +}( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js index 91321a2f..04e002dd 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js @@ -1,7 +1,17 @@ -( function ( mw ) { +( function ( mw, $ ) { QUnit.module( 'mediawiki.user', QUnit.newMwEnvironment( { setup: function () { this.server = this.sandbox.useFakeServer(); + this.crypto = window.crypto; + this.msCrypto = window.msCrypto; + }, + teardown: function () { + if ( this.crypto ) { + window.crypto = this.crypto; + } + if ( this.msCrypto ) { + window.msCrypto = this.msCrypto; + } } } ) ); @@ -51,4 +61,38 @@ this.server.respond(); } ); -}( mediaWiki ) ); + + QUnit.test( 'generateRandomSessionId', 4, function ( assert ) { + var result, result2; + + result = mw.user.generateRandomSessionId(); + assert.equal( typeof result, 'string', 'type' ); + assert.equal( $.trim( result ), result, 'no whitespace at beginning or end' ); + assert.equal( result.length, 16, 'size' ); + + result2 = mw.user.generateRandomSessionId(); + assert.notEqual( result, result2, 'different when called multiple times' ); + + } ); + + QUnit.test( 'generateRandomSessionId (fallback)', 4, function ( assert ) { + var result, result2; + + // Pretend crypto API is not there to test the Math.random fallback + if ( window.crypto ) { + window.crypto = undefined; + } + if ( window.msCrypto ) { + window.msCrypto = undefined; + } + + result = mw.user.generateRandomSessionId(); + assert.equal( typeof result, 'string', 'type' ); + assert.equal( $.trim( result ), result, 'no whitespace at beginning or end' ); + assert.equal( result.length, 16, 'size' ); + + result2 = mw.user.generateRandomSessionId(); + assert.notEqual( result, result2, 'different when called multiple times' ); + + } ); +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js index 4401eadb..0b42af4b 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js @@ -1,4 +1,79 @@ ( function ( mw, $ ) { + 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'] + ], + + // 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'] + ]; + + Array.prototype.push.apply( IPV6_CASES, + $.map( [ + 'fc:100::', + 'fc:100:a::', + 'fc:100:a:d::', + 'fc:100:a:d:1::', + 'fc:100:a:d:1:e::', + 'fc:100:a:d:1:e:ac::', + '::0', + '::fc', + '::fc:100', + '::fc:100:a', + '::fc:100:a:d', + '::fc:100:a:d:1', + '::fc:100:a:d:1:e', + '::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' ]]; + } ) + ); + QUnit.module( 'mediawiki.util', QUnit.newMwEnvironment( { setup: function () { $.fn.updateTooltipAccessKeys.setTestMode( true ); @@ -35,24 +110,25 @@ } ); } ); - QUnit.test( 'getUrl', 4, function ( assert ) { + QUnit.test( 'getUrl', 5, function ( assert ) { // Not part of startUp module mw.config.set( 'wgArticlePath', '/wiki/$1' ); mw.config.set( 'wgPageName', 'Foobar' ); var href = mw.util.getUrl( 'Sandbox' ); - assert.equal( href, '/wiki/Sandbox', 'Simple title; Get link for "Sandbox"' ); + assert.equal( href, '/wiki/Sandbox', 'simple title' ); - href = mw.util.getUrl( 'Foo:Sandbox ? 5+5=10 ! (test)/subpage' ); - assert.equal( href, '/wiki/Foo:Sandbox_%3F_5%2B5%3D10_!_(test)/subpage', - 'Advanced title; Get link for "Foo:Sandbox ? 5+5=10 ! (test)/subpage"' ); + href = mw.util.getUrl( 'Foo:Sandbox? 5+5=10! (test)/sub ' ); + assert.equal( href, '/wiki/Foo:Sandbox%3F_5%2B5%3D10!_(test)/sub_', 'advanced title' ); href = mw.util.getUrl(); - assert.equal( href, '/wiki/Foobar', 'Default title; Get link for current page ("Foobar")' ); + assert.equal( href, '/wiki/Foobar', 'default title' ); + + href = mw.util.getUrl( null, { action: 'edit' } ); + assert.equal( href, '/wiki/Foobar?action=edit', 'default title with query string' ); href = mw.util.getUrl( 'Sandbox', { action: 'edit' } ); - assert.equal( href, '/wiki/Sandbox?action=edit', - 'Simple title with query string; Get link for "Sandbox" with action=edit' ); + assert.equal( href, '/wiki/Sandbox?action=edit', 'simple title with query string' ); } ); QUnit.test( 'wikiScript', 4, function ( assert ) { @@ -189,7 +265,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 (by passing nextnode)' ); + 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 ); @@ -205,7 +281,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.equal( $( tbRLDM ).next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing CSS selector)' ); + assert.strictEqual( $( tbRLDM ).next()[0], tbRL, 'Link is in the correct position (CSS selector as nextnode)' ); caFoo = mw.util.addPortletLink( 'p-test-views', '#', 'Foo' ); @@ -213,26 +289,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 (by passing a 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], 'Nonexistent id as nextnode adds the portlet at end' ); + 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], 'Empty jquery as nextnode adds the portlet at end' ); - } ); - - QUnit.test( 'jsMessage', 1, function ( assert ) { - this.suppressWarnings(); - var a = mw.util.jsMessage( 'MediaWiki is <b>Awesome</b>.' ); - this.restoreWarnings(); - assert.ok( a, 'Basic checking of return value' ); + 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 ) { @@ -249,95 +318,24 @@ } ); QUnit.test( 'isIPv6Address', 40, function ( assert ) { - // Shortcuts - function assertFalseIPv6( addy, summary ) { - return assert.strictEqual( mw.util.isIPv6Address( addy ), false, summary ); - } - - function assertTrueIPv6( addy, summary ) { - return assert.strictEqual( mw.util.isIPv6Address( addy ), true, summary ); - } - - // Based on IPTest.php > testisIPv6 - assertFalseIPv6( ':fc:100::', 'IPv6 starting with lone ":"' ); - assertFalseIPv6( 'fc:100:::', 'IPv6 ending with a ":::"' ); - assertFalseIPv6( 'fc:300', 'IPv6 with only 2 words' ); - assertFalseIPv6( 'fc:100:300', 'IPv6 with only 3 words' ); - - $.each( - ['fc:100::', - 'fc:100:a::', - 'fc:100:a:d::', - 'fc:100:a:d:1::', - 'fc:100:a:d:1:e::', - 'fc:100:a:d:1:e:ac::'], function ( i, addy ) { - assertTrueIPv6( addy, addy + ' is a valid IP' ); - } ); - - assertFalseIPv6( 'fc:100:a:d:1:e:ac:0::', 'IPv6 with 8 words ending with "::"' ); - assertFalseIPv6( 'fc:100:a:d:1:e:ac:0:1::', 'IPv6 with 9 words ending with "::"' ); - - assertFalseIPv6( ':::' ); - assertFalseIPv6( '::0:', 'IPv6 ending in a lone ":"' ); - - assertTrueIPv6( '::', 'IPv6 zero address' ); - $.each( - ['::0', - '::fc', - '::fc:100', - '::fc:100:a', - '::fc:100:a:d', - '::fc:100:a:d:1', - '::fc:100:a:d:1:e', - '::fc:100:a:d:1:e:ac', - - 'fc:100:a:d:1:e:ac:0'], function ( i, addy ) { - assertTrueIPv6( addy, addy + ' is a valid IP' ); - } ); - - assertFalseIPv6( '::fc:100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' ); - assertFalseIPv6( '::fc:100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' ); - - assertFalseIPv6( ':fc::100', 'IPv6 starting with lone ":"' ); - assertFalseIPv6( 'fc::100:', 'IPv6 ending with lone ":"' ); - assertFalseIPv6( 'fc:::100', 'IPv6 with ":::" in the middle' ); - - assertTrueIPv6( 'fc::100', 'IPv6 with "::" and 2 words' ); - assertTrueIPv6( 'fc::100:a', 'IPv6 with "::" and 3 words' ); - assertTrueIPv6( 'fc::100:a:d', 'IPv6 with "::" and 4 words' ); - assertTrueIPv6( 'fc::100:a:d:1', 'IPv6 with "::" and 5 words' ); - assertTrueIPv6( 'fc::100:a:d:1:e', 'IPv6 with "::" and 6 words' ); - assertTrueIPv6( 'fc::100:a:d:1:e:ac', 'IPv6 with "::" and 7 words' ); - assertTrueIPv6( '2001::df', 'IPv6 with "::" and 2 words' ); - assertTrueIPv6( '2001:5c0:1400:a::df', 'IPv6 with "::" and 5 words' ); - assertTrueIPv6( '2001:5c0:1400:a::df:2', 'IPv6 with "::" and 6 words' ); - - assertFalseIPv6( 'fc::100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' ); - assertFalseIPv6( 'fc::100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' ); + $.each( IPV6_CASES, function ( i, ipCase ) { + assert.strictEqual( mw.util.isIPv6Address( ipCase[1] ), ipCase[0], ipCase[2] ); + } ); } ); QUnit.test( 'isIPv4Address', 11, function ( assert ) { - // Shortcuts - function assertFalseIPv4( addy, summary ) { - assert.strictEqual( mw.util.isIPv4Address( addy ), false, summary ); - } + $.each( IPV4_CASES, function ( i, ipCase ) { + assert.strictEqual( mw.util.isIPv4Address( ipCase[1] ), ipCase[0], ipCase[2] ); + } ); + } ); - function assertTrueIPv4( addy, summary ) { - assert.strictEqual( mw.util.isIPv4Address( addy ), true, summary ); - } + QUnit.test( 'isIPAddress', 51, function ( assert ) { + $.each( IPV4_CASES, function ( i, ipCase ) { + assert.strictEqual( mw.util.isIPv4Address( ipCase[1] ), ipCase[0], ipCase[2] ); + } ); - // Based on IPTest.php > testisIPv4 - assertFalseIPv4( false, 'Boolean false is not an IP' ); - assertFalseIPv4( true, 'Boolean true is not an IP' ); - assertFalseIPv4( '', 'Empty string is not an IP' ); - assertFalseIPv4( 'abc', '"abc" is not an IP' ); - assertFalseIPv4( ':', 'Colon is not an IP' ); - assertFalseIPv4( '124.24.52', 'IPv4 not enough quads' ); - assertFalseIPv4( '24.324.52.13', 'IPv4 out of range' ); - assertFalseIPv4( '.24.52.13', 'IPv4 starts with period' ); - - assertTrueIPv4( '124.24.52.13', '124.24.52.134 is a valid IP' ); - assertTrueIPv4( '1.24.52.13', '1.24.52.13 is a valid IP' ); - assertFalseIPv4( '74.24.52.13/20', 'IPv4 ranges are not recogzized as valid IPs' ); + $.each( IPV6_CASES, function ( i, ipCase ) { + 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 ed03418a..6011961a 100644 --- a/tests/qunit/suites/resources/startup.test.js +++ b/tests/qunit/suites/resources/startup.test.js @@ -96,6 +96,7 @@ 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.7) Gecko/20060928 (Debian|Debian-1.8.0.7-1) Epiphany/2.14', 'Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.8.1.6) Gecko/20070817 IceWeasel/2.0.0.6-g2', // KHTML + 'Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.4 (like Gecko)', 'Mozilla/5.0 (compatible; Konqueror/4.3; Linux) KHTML/4.3.5 (like Gecko)', // Text browsers 'Links (2.1pre33; Darwin 8.11.0 Power Macintosh; x)', |