diff options
Diffstat (limited to 'tests/qunit/suites/resources/mediawiki')
16 files changed, 763 insertions, 284 deletions
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js new file mode 100644 index 00000000..2388497a --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js @@ -0,0 +1,38 @@ +( function ( mw, $ ) { + QUnit.module( 'mediawiki.RegExp' ); + + QUnit.test( 'escape', 16, function ( assert ) { + var specials, normal; + + specials = [ + '\\', + '{', + '}', + '(', + ')', + '[', + ']', + '|', + '.', + '?', + '*', + '+', + '-', + '^', + '$' + ]; + + normal = [ + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + 'abcdefghijklmnopqrstuvwxyz', + '0123456789' + ].join( '' ); + + $.each( specials, function ( i, str ) { + assert.propEqual( str.match( new RegExp( mw.RegExp.escape( str ) ) ), [ str ], 'Match ' + str ); + } ); + + assert.equal( mw.RegExp.escape( normal ), normal, 'Alphanumerals are left alone' ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js index c0afe07c..641a5a5d 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js @@ -100,33 +100,35 @@ // testing custom / localized namespace 100: 'Penguins' }, + // jscs: disable requireCamelCaseOrUpperCaseIdentifiers wgNamespaceIds: { - 'media': -2, - 'special': -1, + media: -2, + special: -1, '': 0, - 'talk': 1, - 'user': 2, - 'user_talk': 3, - 'wikipedia': 4, - 'wikipedia_talk': 5, - 'file': 6, - 'file_talk': 7, - 'mediawiki': 8, - 'mediawiki_talk': 9, - 'template': 10, - 'template_talk': 11, - 'help': 12, - 'help_talk': 13, - 'category': 14, - 'category_talk': 15, - 'image': 6, - 'image_talk': 7, - 'project': 4, - 'project_talk': 5, + talk: 1, + user: 2, + user_talk: 3, + wikipedia: 4, + wikipedia_talk: 5, + file: 6, + file_talk: 7, + mediawiki: 8, + mediawiki_talk: 9, + template: 10, + template_talk: 11, + help: 12, + help_talk: 13, + category: 14, + category_talk: 15, + image: 6, + image_talk: 7, + project: 4, + project_talk: 5, // Testing custom namespaces and aliases - 'penguins': 100, - 'antarctic_waterfowl': 100 + penguins: 100, + antarctic_waterfowl: 100 }, + // jscs: enable requireCamelCaseOrUpperCaseIdentifiers wgCaseSensitiveNamespaces: [] } } ) ); @@ -134,14 +136,14 @@ QUnit.test( 'constructor', cases.invalid.length, function ( assert ) { var i, title; for ( i = 0; i < cases.valid.length; i++ ) { - title = new mw.Title( cases.valid[i] ); + title = new mw.Title( cases.valid[ i ] ); } for ( i = 0; i < cases.invalid.length; i++ ) { /*jshint loopfunc:true */ - title = cases.invalid[i]; + title = cases.invalid[ i ]; assert.throws( function () { return new mw.Title( title ); - }, cases.invalid[i] ); + }, cases.invalid[ i ] ); } } ); @@ -149,21 +151,21 @@ var i; for ( i = 0; i < cases.valid.length; i++ ) { assert.equal( - $.type( mw.Title.newFromText( cases.valid[i] ) ), + $.type( mw.Title.newFromText( cases.valid[ i ] ) ), 'object', - cases.valid[i] + cases.valid[ i ] ); } for ( i = 0; i < cases.invalid.length; i++ ) { assert.equal( - $.type( mw.Title.newFromText( cases.invalid[i] ) ), + $.type( mw.Title.newFromText( cases.invalid[ i ] ) ), 'null', - cases.invalid[i] + cases.invalid[ i ] ); } } ); - QUnit.test( 'Basic parsing', 12, function ( assert ) { + QUnit.test( 'Basic parsing', 21, function ( assert ) { var title; title = new mw.Title( 'File:Foo_bar.JPG' ); @@ -181,6 +183,17 @@ title = new mw.Title( 'Foo#bar' ); assert.equal( title.getPrefixedText(), 'Foo' ); assert.equal( title.getFragment(), 'bar' ); + + title = new mw.Title( '.foo' ); + assert.equal( title.getPrefixedText(), '.foo' ); + assert.equal( title.getName(), '' ); + assert.equal( title.getNameText(), '' ); + assert.equal( title.getExtension(), 'foo' ); + assert.equal( title.getDotExtension(), '.foo' ); + assert.equal( title.getMain(), '.foo' ); + assert.equal( title.getMainText(), '.foo' ); + assert.equal( title.getPrefixedDb(), '.foo' ); + assert.equal( title.getPrefixedText(), '.foo' ); } ); QUnit.test( 'Transformation', 11, function ( assert ) { @@ -266,7 +279,7 @@ assert.equal( title.toString(), 'Article', 'Default config: No sensitive namespaces by default. First-letter becomes uppercase' ); // $wgCapitalLinks = false; - mw.config.set( 'wgCaseSensitiveNamespaces', [0, -2, 1, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15] ); + mw.config.set( 'wgCaseSensitiveNamespaces', [ 0, -2, 1, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15 ] ); title = new mw.Title( 'article' ); assert.equal( title.toString(), 'article', '$wgCapitalLinks=false: Article namespace is sensitive, first-letter case stays lowercase' ); @@ -309,8 +322,8 @@ assert.strictEqual( title.exists(), null, 'Return null with empty existance registry' ); // Basic registry, checks default to boolean - mw.Title.exist.set( ['Does_exist', 'User_talk:NeilK', 'Wikipedia:Sandbox_rules'], true ); - mw.Title.exist.set( ['Does_not_exist', 'User:John', 'Foobar'], false ); + mw.Title.exist.set( [ 'Does_exist', 'User_talk:NeilK', 'Wikipedia:Sandbox_rules' ], true ); + mw.Title.exist.set( [ 'Does_not_exist', 'User:John', 'Foobar' ], false ); title = new mw.Title( 'Project:Sandbox rules' ); assert.assertTrue( title.exists(), 'Return true for page titles marked as existing' ); @@ -327,7 +340,7 @@ title = new mw.Title( 'Foobar' ); assert.equal( title.getUrl(), '/wiki/Foobar', 'Basic functionality, getUrl uses mw.util.getUrl' ); - assert.equal( title.getUrl({ action: 'edit' }), '/wiki/Foobar?action=edit', 'Basic functionality, \'params\' parameter' ); + assert.equal( title.getUrl( { action: 'edit' } ), '/wiki/Foobar?action=edit', 'Basic functionality, \'params\' parameter' ); title = new mw.Title( 'John Doe', 3 ); assert.equal( title.getUrl(), '/wiki/User_talk:John_Doe', 'Escaping in title and namespace for urls' ); @@ -420,7 +433,7 @@ ]; for ( i = 0; i < cases.length; i++ ) { - thisCase = cases[i]; + thisCase = cases[ i ]; title = mw.Title.newFromImg( { src: thisCase.url } ); if ( thisCase.nameText !== undefined ) { @@ -467,28 +480,62 @@ ]; for ( i = 0; i < cases.length; i++ ) { - thisCase = cases[i]; + thisCase = cases[ i ]; title = mw.Title.newFromText( thisCase.text ); assert.equal( title.getRelativeText( thisCase.relativeTo ), thisCase.expectedResult ); } } ); - QUnit.test( 'newFromUserInput', 8, function ( assert ) { + QUnit.test( 'normalizeExtension', 5, function ( assert ) { + var extension, i, thisCase, prefix, + cases = [ + { + extension: 'png', + expected: 'png', + description: 'Extension already in canonical form' + }, + { + extension: 'PNG', + expected: 'png', + description: 'Extension lowercased in canonical form' + }, + { + extension: 'jpeg', + expected: 'jpg', + description: 'Extension changed in canonical form' + }, + { + extension: 'JPEG', + expected: 'jpg', + description: 'Extension lowercased and changed in canonical form' + }, + { + extension: '~~~', + expected: '', + description: 'Extension invalid and discarded' + } + ]; + + for ( i = 0; i < cases.length; i++ ) { + thisCase = cases[ i ]; + extension = mw.Title.normalizeExtension( thisCase.extension ); + + prefix = '[' + thisCase.description + '] '; + assert.equal( extension, thisCase.expected, prefix + 'Extension as expected' ); + } + } ); + + QUnit.test( 'newFromUserInput', 12, function ( assert ) { var title, i, thisCase, prefix, cases = [ { title: 'DCS0001557854455.JPG', - defaultNamespace: 0, - options: { - fileExtension: 'PNG' - }, expected: 'DCS0001557854455.JPG', description: 'Title in normal namespace without anything invalid but with "file extension"' }, { title: 'MediaWiki:Msg-awesome', - defaultNamespace: undefined, expected: 'MediaWiki:Msg-awesome', description: 'Full title (page in MediaWiki namespace) supplied as string' }, @@ -506,11 +553,21 @@ }, expected: 'File:The/Mw/Sound.kml', description: 'Page in File-namespace without explicit options' + }, + { + title: 'File:Foo.JPEG', + expected: 'File:Foo.JPEG', + description: 'Page in File-namespace with non-canonical extension' + }, + { + title: 'File:Foo.JPEG ', + expected: 'File:Foo.JPEG', + description: 'Page in File-namespace with trailing whitespace' } ]; for ( i = 0; i < cases.length; i++ ) { - thisCase = cases[i]; + thisCase = cases[ i ]; title = mw.Title.newFromUserInput( thisCase.title, thisCase.defaultNamespace, thisCase.options ); if ( thisCase.expected !== undefined ) { @@ -524,15 +581,14 @@ } } ); - QUnit.test( 'newFromFileName', 62, function ( assert ) { + QUnit.test( 'newFromFileName', 54, function ( assert ) { var title, i, thisCase, prefix, cases = [ { fileName: 'DCS0001557854455.JPG', typeOfName: 'Standard camera output', nameText: 'DCS0001557854455', - prefixedText: 'File:DCS0001557854455.JPG', - extensionDesired: 'jpg' + prefixedText: 'File:DCS0001557854455.JPG' }, { fileName: 'File:Sample.png', @@ -544,8 +600,7 @@ fileName: 'Treppe 2222 Test upload.jpg', typeOfName: 'File name with spaces in it and lower case file extension', nameText: 'Treppe 2222 Test upload', - prefixedText: 'File:Treppe 2222 Test upload.jpg', - extensionDesired: 'JPG' + prefixedText: 'File:Treppe 2222 Test upload.jpg' }, { fileName: 'I contain a \ttab.jpg', @@ -602,20 +657,6 @@ prefixedText: 'File:Dot. dot. dot' }, { - fileName: 'dot. dot ._dot', - typeOfName: 'File name with different file extension desired', - nameText: 'Dot. dot . dot', - prefixedText: 'File:Dot. dot . dot.png', - extensionDesired: 'png' - }, - { - fileName: 'fileWOExt', - typeOfName: 'File W/O extension with extension desired', - nameText: 'FileWOExt', - prefixedText: 'File:FileWOExt.png', - extensionDesired: 'png' - }, - { fileName: '𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢𠻗𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵𢫕𢭃𢯊𢱑𢱕𢳂𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵𢫕𢭃𢯊𢱑𢱕𢳂.png', typeOfName: 'File name longer than 240 bytes', nameText: '𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢𠻗𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵𢫕𢭃𢯊𢱑𢱕𢳂𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵', @@ -632,8 +673,8 @@ ]; for ( i = 0; i < cases.length; i++ ) { - thisCase = cases[i]; - title = mw.Title.newFromFileName( thisCase.fileName, thisCase.extensionDesired ); + thisCase = cases[ i ]; + title = mw.Title.newFromFileName( thisCase.fileName ); if ( thisCase.nameText !== undefined ) { prefix = '[' + thisCase.typeOfName + '] '; diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js index ba366553..51374bd8 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js @@ -11,7 +11,7 @@ } } ) ); - $.each( [true, false], function ( i, strictMode ) { + $.each( [ true, false ], function ( i, strictMode ) { QUnit.test( 'Basic construction and properties (' + ( strictMode ? '' : 'non-' ) + 'strict mode)', 2, function ( assert ) { var uriString, uri; uriString = 'http://www.ietf.org/rfc/rfc2396.txt'; @@ -76,8 +76,8 @@ } ); assert.strictEqual( uri.query.n, '1', 'Simple parameter with overrideKeys:false' ); - assert.strictEqual( uri.query.m[0], 'foo', 'Order of multi-value parameters with overrideKeys:true' ); - assert.strictEqual( uri.query.m[1], 'bar', 'Order of multi-value parameters with overrideKeys:true' ); + assert.strictEqual( uri.query.m[ 0 ], 'foo', 'Order of multi-value parameters with overrideKeys:true' ); + assert.strictEqual( uri.query.m[ 1 ], 'bar', 'Order of multi-value parameters with overrideKeys:true' ); assert.strictEqual( uri.query.m.length, 2, 'Number of mult-value field is correct' ); uri = new mw.Uri( 'ftp://usr:pwd@192.0.2.16/' ); @@ -270,7 +270,7 @@ assert.deepEqual( original.query, - { 'one': '1', 'two': '2' }, + { one: '1', two: '2' }, 'Properties is deep cloned (bug 37708)' ); } ); @@ -367,7 +367,7 @@ host: 'example.com', port: undefined, path: '/wiki/Foo', - query: { 'v': '2' }, + query: { v: '2' }, fragment: undefined }, 'basic object properties' diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js index 779a0ed4..399db914 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js @@ -65,9 +65,9 @@ QUnit.test( 'Plural Test for ' + langCode, tests.length, function ( assert ) { for ( var i = 0; i < tests.length; i++ ) { assert.equal( - mw.language.convertPlural( tests[i][0], tests[i][1] ), - tests[i][2], - tests[i][3] + mw.language.convertPlural( tests[ i ][ 0 ], tests[ i ][ 1 ] ), + tests[ i ][ 2 ], + tests[ i ][ 3 ] ); } } ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js index f5f199ea..7a13f0f7 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.cookie.test.js @@ -102,7 +102,7 @@ } ); call = $.cookie.lastCall.args; - assert.strictEqual( call[0], 'myPrefixfoo' ); + assert.strictEqual( call[ 0 ], 'myPrefixfoo' ); assert.deepEqual( call[ 2 ], { expires: expiryDate, domain: 'myDomain', @@ -122,7 +122,7 @@ } ); call = $.cookie.lastCall.args; - assert.strictEqual( call[0], 'myPrefixfoo' ); + assert.strictEqual( call[ 0 ], 'myPrefixfoo' ); assert.deepEqual( call[ 2 ], { expires: date, domain: 'myDomain', diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js index 7c3f1ecb..587c8931 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js @@ -7,7 +7,7 @@ errorUrl = 'http://example.com', errorLine = '123', errorColumn = '45', - errorObject = new Error( 'Foo'), + errorObject = new Error( 'Foo' ), oldHandler = this.sandbox.stub(); this.sandbox.stub( mw, 'track' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.experiments.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.experiments.test.js new file mode 100644 index 00000000..774b2053 --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.experiments.test.js @@ -0,0 +1,63 @@ +( function ( mw ) { + + var getBucket = mw.experiments.getBucket; + + function createExperiment() { + return { + name: 'experiment', + enabled: true, + buckets: { + control: 0.25, + A: 0.25, + B: 0.25, + C: 0.25 + } + }; + } + + QUnit.module( 'mediawiki.experiments' ); + + QUnit.test( 'getBucket( experiment, token )', 4, function ( assert ) { + var experiment = createExperiment(), + token = '123457890'; + + assert.equal( + getBucket( experiment, token ), + getBucket( experiment, token ), + 'It returns the same bucket for the same experiment-token pair.' + ); + + // -------- + experiment = createExperiment(); + experiment.buckets = { + A: 0.314159265359 + }; + + assert.equal( + 'A', + getBucket( experiment, token ), + 'It returns the bucket if only one is defined.' + ); + + // -------- + experiment = createExperiment(); + experiment.enabled = false; + + assert.equal( + 'control', + getBucket( experiment, token ), + 'It returns "control" if the experiment is disabled.' + ); + + // -------- + experiment = createExperiment(); + experiment.buckets = {}; + + assert.equal( + 'control', + getBucket( experiment, token ), + 'It returns "control" if the experiment doesn\'t have any buckets.' + ); + } ); + +}( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js index 7e23e2ff..c088554a 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js @@ -1,5 +1,6 @@ ( function ( mw, $ ) { - var formatText, formatParse, formatnumTests, specialCharactersPageName, expectedListUsers, expectedEntrypoints, + var formatText, formatParse, formatnumTests, specialCharactersPageName, expectedListUsers, + expectedListUsersSitename, expectedEntrypoints, mwLanguageCache = {}, hasOwn = Object.hasOwnProperty; @@ -16,6 +17,8 @@ specialCharactersPageName = '"Who" wants to be a millionaire & live on \'Exotic Island\'?'; expectedListUsers = '注册<a title="Special:ListUsers" href="/wiki/Special:ListUsers">用户</a>'; + expectedListUsersSitename = '注册<a title="Special:ListUsers" href="/wiki/Special:ListUsers">用户' + + mw.config.get( 'wgSiteName' ) + '</a>'; expectedEntrypoints = '<a href="https://www.mediawiki.org/wiki/Manual:index.php">index.php</a>'; @@ -52,6 +55,7 @@ 'see-portal-url': '{{Int:portal-url}} is an important community page.', 'jquerymsg-test-statistics-users': '注册[[Special:ListUsers|用户]]', + 'jquerymsg-test-statistics-users-sitename': '注册[[Special:ListUsers|用户{{SITENAME}}]]', 'jquerymsg-test-version-entrypoints-index-php': '[https://www.mediawiki.org/wiki/Manual:index.php index.php]', @@ -70,7 +74,7 @@ */ function getMwLanguage( langCode ) { if ( !hasOwn.call( mwLanguageCache, langCode ) ) { - mwLanguageCache[langCode] = $.ajax( { + mwLanguageCache[ langCode ] = $.ajax( { url: mw.util.wikiScript( 'load' ), data: { skin: mw.config.get( 'skin' ), @@ -88,25 +92,37 @@ return mw.language; } ); } - return mwLanguageCache[langCode]; + return mwLanguageCache[ langCode ]; } /** * @param {Function[]} tasks List of functions that perform tasks * that may be asynchronous. Invoke the callback parameter when done. - * @param {Function} done When all tasks are done. - * @return + * @param {Function} complete Called when all tasks are done, or when the sequence is aborted. */ - function process( tasks, done ) { - function run() { + function process( tasks, complete ) { + /*jshint latedef:false */ + function abort() { + tasks.splice( 0, tasks.length ); + next(); + } + function next() { + if ( !tasks ) { + // This happens if after the process is completed, one of our callbacks is + // invoked. This can happen if a test timed out but the process was still + // running. In that case, ignore it. Don't invoke complete() a second time. + return; + } var task = tasks.shift(); if ( task ) { - task( run ); + task( next, abort ); } else { - done(); + // Remove tasks list to indicate the process is final. + tasks = null; + complete(); } } - run(); + next(); } QUnit.test( 'Replace', 16, function ( assert ) { @@ -306,9 +322,9 @@ QUnit.test( 'Match PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) { mw.messages.set( mw.libs.phpParserData.messages ); var tasks = $.map( mw.libs.phpParserData.tests, function ( test ) { - return function ( next ) { + return function ( next, abort ) { getMwLanguage( test.lang ) - .done( function ( langClass ) { + .then( function ( langClass ) { mw.config.set( 'wgUserLanguage', test.lang ); var parser = new mw.jqueryMsg.parser( { language: langClass } ); assert.equal( @@ -316,11 +332,10 @@ test.result, test.name ); - } ) - .fail( function () { + }, function () { assert.ok( false, 'Language "' + test.lang + '" failed to load.' ); } ) - .always( next ); + .then( next, abort ); }; } ); @@ -328,8 +343,9 @@ process( tasks, QUnit.start ); } ); - QUnit.test( 'Links', 6, function ( assert ) { - var expectedDisambiguationsText, + QUnit.test( 'Links', 14, function ( assert ) { + var testCases, + expectedDisambiguationsText, expectedMultipleBars, expectedSpecialCharacters; @@ -360,12 +376,24 @@ // Pipe trick is not supported currently, but should not parse as text either. mw.messages.set( 'pipe-trick', '[[Tampa, Florida|]]' ); + mw.messages.set( 'reverse-pipe-trick', '[[|Tampa, Florida]]' ); + mw.messages.set( 'empty-link', '[[]]' ); this.suppressWarnings(); assert.equal( formatParse( 'pipe-trick' ), '[[Tampa, Florida|]]', 'Pipe trick should not be parsed.' ); + assert.equal( + formatParse( 'reverse-pipe-trick' ), + '[[|Tampa, Florida]]', + 'Reverse pipe trick should not be parsed.' + ); + assert.equal( + formatParse( 'empty-link' ), + '[[]]', + 'Empty link should not be parsed.' + ); this.restoreWarnings(); expectedMultipleBars = '<a title="Main Page" href="/wiki/Main_Page">Main|Page</a>'; @@ -384,20 +412,159 @@ expectedSpecialCharacters, 'Special characters' ); + + mw.messages.set( 'leading-colon', '[[:File:Foo.jpg]]' ); + assert.htmlEqual( + formatParse( 'leading-colon' ), + '<a title="File:Foo.jpg" href="/wiki/File:Foo.jpg">File:Foo.jpg</a>', + 'Leading colon in links is stripped' + ); + + assert.htmlEqual( + formatParse( 'jquerymsg-test-statistics-users-sitename' ), + expectedListUsersSitename, + 'Piped wikilink with parser function in the text' + ); + + testCases = [ + [ + 'extlink-html-full', + 'asd [http://example.org <strong>Example</strong>] asd', + 'asd <a href="http://example.org"><strong>Example</strong></a> asd' + ], + [ + 'extlink-html-partial', + 'asd [http://example.org foo <strong>Example</strong> bar] asd', + 'asd <a href="http://example.org">foo <strong>Example</strong> bar</a> asd' + ], + [ + 'wikilink-html-full', + 'asd [[Example|<strong>Example</strong>]] asd', + 'asd <a title="Example" href="/wiki/Example"><strong>Example</strong></a> asd' + ], + [ + 'wikilink-html-partial', + 'asd [[Example|foo <strong>Example</strong> bar]] asd', + 'asd <a title="Example" href="/wiki/Example">foo <strong>Example</strong> bar</a> asd' + ] + ]; + + $.each( testCases, function () { + var + key = this[ 0 ], + input = this[ 1 ], + output = this[ 2 ]; + mw.messages.set( key, input ); + assert.htmlEqual( + formatParse( key ), + output, + 'HTML in links: ' + key + ); + } ); + } ); + + QUnit.test( 'Replacements in links', 14, function ( assert ) { + var testCases = [ + [ + 'extlink-param-href-full', + 'asd [$1 Example] asd', + 'asd <a href="http://example.com">Example</a> asd' + ], + [ + 'extlink-param-href-partial', + 'asd [$1/example Example] asd', + 'asd <a href="http://example.com/example">Example</a> asd' + ], + [ + 'extlink-param-text-full', + 'asd [http://example.org $2] asd', + 'asd <a href="http://example.org">Text</a> asd' + ], + [ + 'extlink-param-text-partial', + 'asd [http://example.org Example $2] asd', + 'asd <a href="http://example.org">Example Text</a> asd' + ], + [ + 'extlink-param-both-full', + 'asd [$1 $2] asd', + 'asd <a href="http://example.com">Text</a> asd' + ], + [ + 'extlink-param-both-partial', + 'asd [$1/example Example $2] asd', + 'asd <a href="http://example.com/example">Example Text</a> asd' + ], + [ + 'wikilink-param-href-full', + 'asd [[$1|Example]] asd', + 'asd <a title="Example" href="/wiki/Example">Example</a> asd' + ], + [ + 'wikilink-param-href-partial', + 'asd [[$1/Test|Example]] asd', + 'asd <a title="Example/Test" href="/wiki/Example/Test">Example</a> asd' + ], + [ + 'wikilink-param-text-full', + 'asd [[Example|$2]] asd', + 'asd <a title="Example" href="/wiki/Example">Text</a> asd' + ], + [ + 'wikilink-param-text-partial', + 'asd [[Example|Example $2]] asd', + 'asd <a title="Example" href="/wiki/Example">Example Text</a> asd' + ], + [ + 'wikilink-param-both-full', + 'asd [[$1|$2]] asd', + 'asd <a title="Example" href="/wiki/Example">Text</a> asd' + ], + [ + 'wikilink-param-both-partial', + 'asd [[$1/Test|Example $2]] asd', + 'asd <a title="Example/Test" href="/wiki/Example/Test">Example Text</a> asd' + ], + [ + 'wikilink-param-unpiped-full', + 'asd [[$1]] asd', + 'asd <a title="Example" href="/wiki/Example">Example</a> asd' + ], + [ + 'wikilink-param-unpiped-partial', + 'asd [[$1/Test]] asd', + 'asd <a title="Example/Test" href="/wiki/Example/Test">Example/Test</a> asd' + ] + ]; + + $.each( testCases, function () { + var + key = this[ 0 ], + input = this[ 1 ], + output = this[ 2 ], + paramHref = key.slice( 0, 8 ) === 'wikilink' ? 'Example' : 'http://example.com', + paramText = 'Text'; + mw.messages.set( key, input ); + assert.htmlEqual( + formatParse( key, paramHref, paramText ), + output, + 'Replacements in links: ' + key + ); + } ); } ); -// Tests that {{-transformation vs. general parsing are done as requested + // Tests that {{-transformation vs. general parsing are done as requested QUnit.test( 'Curly brace transformation', 16, function ( assert ) { var oldUserLang = mw.config.get( 'wgUserLanguage' ); - assertBothModes( assert, ['gender-msg', 'Bob', 'male'], 'Bob: blue', 'gender is resolved' ); + assertBothModes( assert, [ 'gender-msg', 'Bob', 'male' ], 'Bob: blue', 'gender is resolved' ); - assertBothModes( assert, ['plural-msg', 5], 'Found 5 items', 'plural is resolved' ); + assertBothModes( assert, [ 'plural-msg', 5 ], 'Found 5 items', 'plural is resolved' ); - assertBothModes( assert, ['grammar-msg'], 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'grammar is resolved' ); + assertBothModes( assert, [ 'grammar-msg' ], 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'grammar is resolved' ); mw.config.set( 'wgUserLanguage', 'en' ); - assertBothModes( assert, ['formatnum-msg', '987654321.654321'], '987,654,321.654', 'formatnum is resolved' ); + assertBothModes( assert, [ 'formatnum-msg', '987654321.654321' ], '987,654,321.654', 'formatnum is resolved' ); // Test non-{{ wikitext, where behavior differs @@ -501,7 +668,7 @@ 'curly-brace': '{{int:message}}', 'single-square-bracket': '[https://www.mediawiki.org/ MediaWiki]', 'double-square-bracket': '[[Some page]]', - 'regular': 'Other message' + regular: 'Other message' } ); oldGMF = mw.jqueryMsg.getMessageFunction; @@ -518,7 +685,7 @@ outerCalled = false; innerCalled = false; message = mw.message( key ); - message[format](); + message[ format ](); assert.strictEqual( outerCalled, shouldCall, 'Outer function called for ' + key ); assert.strictEqual( innerCalled, shouldCall, 'Inner function called for ' + key ); } @@ -645,9 +812,9 @@ mw.messages.set( 'formatnum-msg', '{{formatnum:$1}}' ); mw.messages.set( 'formatnum-msg-int', '{{formatnum:$1|R}}' ); var queue = $.map( formatnumTests, function ( test ) { - return function ( next ) { + return function ( next, abort ) { getMwLanguage( test.lang ) - .done( function ( langClass ) { + .then( function ( langClass ) { mw.config.set( 'wgUserLanguage', test.lang ); var parser = new mw.jqueryMsg.parser( { language: langClass } ); assert.equal( @@ -656,11 +823,10 @@ test.result, test.description ); - } ) - .fail( function () { + }, function () { assert.ok( false, 'Language "' + test.lang + '" failed to load' ); } ) - .always( next ); + .then( next, abort ); }; } ); QUnit.stop(); @@ -671,16 +837,16 @@ QUnit.test( 'HTML', 26, function ( assert ) { mw.messages.set( 'jquerymsg-italics-msg', '<i>Very</i> important' ); - assertBothModes( assert, ['jquerymsg-italics-msg'], mw.messages.get( 'jquerymsg-italics-msg' ), 'Simple italics unchanged' ); + assertBothModes( assert, [ 'jquerymsg-italics-msg' ], mw.messages.get( 'jquerymsg-italics-msg' ), 'Simple italics unchanged' ); mw.messages.set( 'jquerymsg-bold-msg', '<b>Strong</b> speaker' ); - assertBothModes( assert, ['jquerymsg-bold-msg'], mw.messages.get( 'jquerymsg-bold-msg' ), 'Simple bold unchanged' ); + assertBothModes( assert, [ 'jquerymsg-bold-msg' ], mw.messages.get( 'jquerymsg-bold-msg' ), 'Simple bold unchanged' ); mw.messages.set( 'jquerymsg-bold-italics-msg', 'It is <b><i>key</i></b>' ); - assertBothModes( assert, ['jquerymsg-bold-italics-msg'], mw.messages.get( 'jquerymsg-bold-italics-msg' ), 'Bold and italics nesting order preserved' ); + assertBothModes( assert, [ 'jquerymsg-bold-italics-msg' ], mw.messages.get( 'jquerymsg-bold-italics-msg' ), 'Bold and italics nesting order preserved' ); mw.messages.set( 'jquerymsg-italics-bold-msg', 'It is <i><b>vital</b></i>' ); - assertBothModes( assert, ['jquerymsg-italics-bold-msg'], mw.messages.get( 'jquerymsg-italics-bold-msg' ), 'Italics and bold nesting order preserved' ); + assertBothModes( assert, [ 'jquerymsg-italics-bold-msg' ], mw.messages.get( 'jquerymsg-italics-bold-msg' ), 'Italics and bold nesting order preserved' ); mw.messages.set( 'jquerymsg-italics-with-link', 'An <i>italicized [[link|wiki-link]]</i>' ); @@ -737,19 +903,17 @@ 'Mismatched HTML start and end tag treated as text' ); - // TODO (mattflaschen, 2013-03-18): It's not a security issue, but there's no real - // reason the htmlEmitter span needs to be here. It's an artifact of how emitting works. mw.messages.set( 'jquerymsg-script-and-external-link', '<script>alert( "jquerymsg-script-and-external-link test" );</script> [http://example.com <i>Foo</i> bar]' ); assert.htmlEqual( formatParse( 'jquerymsg-script-and-external-link' ), - '<script>alert( "jquerymsg-script-and-external-link test" );</script> <a href="http://example.com"><span class="mediaWiki_htmlEmitter"><i>Foo</i> bar</span></a>', + '<script>alert( "jquerymsg-script-and-external-link test" );</script> <a href="http://example.com"><i>Foo</i> bar</a>', 'HTML tags in external links not interfering with escaping of other tags' ); mw.messages.set( 'jquerymsg-link-script', '[http://example.com <script>alert( "jquerymsg-link-script test" );</script>]' ); assert.htmlEqual( formatParse( 'jquerymsg-link-script' ), - '<a href="http://example.com"><span class="mediaWiki_htmlEmitter"><script>alert( "jquerymsg-link-script test" );</script></span></a>', + '<a href="http://example.com"><script>alert( "jquerymsg-link-script test" );</script></a>', 'Non-whitelisted HTML tag in external link anchor treated as text' ); @@ -792,7 +956,7 @@ mw.messages.set( 'jquerymsg-wikitext-contents-script', '<i><script>Script inside</script></i>' ); assert.htmlEqual( formatParse( 'jquerymsg-wikitext-contents-script' ), - '<i><span class="mediaWiki_htmlEmitter"><script>Script inside</script></span></i>', + '<i><script>Script inside</script></i>', 'Contents of valid tag are treated as wikitext, so invalid HTML element is treated as text' ); @@ -832,4 +996,33 @@ assert.equal( logSpy.callCount, 2, 'mw.log.warn calls' ); } ); + QUnit.test( 'Integration', 4, function ( assert ) { + var expected, logSpy; + + expected = '<b><a title="Bold" href="/wiki/Bold">Bold</a>!</b>'; + mw.messages.set( 'integration-test', '<b>[[Bold]]!</b>' ); + + this.suppressWarnings(); + logSpy = this.sandbox.spy( mw.log, 'warn' ); + assert.equal( + window.gM( 'integration-test' ), + expected, + 'Global function gM() works correctly' + ); + assert.equal( logSpy.callCount, 1, 'mw.log.warn called' ); + this.restoreWarnings(); + + assert.equal( + mw.message( 'integration-test' ).parse(), + expected, + 'mw.message().parse() works correctly' + ); + + assert.equal( + $( '<span>' ).msg( 'integration-test' ).html(), + expected, + 'jQuery plugin $.fn.msg() works correctly' + ); + } ); + }( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js index 3328ce3f..3b5915ac 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js @@ -61,7 +61,7 @@ expected = repeat( '\n', n ) + 'some text'; $textarea = $( '<textarea>\n' + expected + '</textarea>' ); - assert.equal( $textarea.val(), expected, 'Expecting ' + n + ' newlines (HTML contained ' + (n + 1) + ')' ); + assert.equal( $textarea.val(), expected, 'Expecting ' + n + ' newlines (HTML contained ' + ( n + 1 ) + ')' ); $textarea = $( '<textarea>' ).val( expected ); assert.equal( $textarea.val(), expected, 'Expecting ' + n + ' newlines (from DOM set with ' + n + ')' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js index 670914eb..399290ca 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js @@ -11,7 +11,7 @@ }, messages: { // mw.language.listToText test - 'and': ' and', + and: ' and', 'comma-separator': ', ', 'word-separator': ' ' } @@ -51,9 +51,9 @@ for ( var i = 0; i < test.length; i++ ) { assert.equal( - mw.language.convertGrammar( test[i].word, test[i].grammarForm ), - test[i].expected, - test[i].description + mw.language.convertGrammar( test[ i ].word, test[ i ].grammarForm ), + test[ i ].expected, + test[ i ].description ); } } ); @@ -481,8 +481,8 @@ QUnit.test( 'List to text test', 4, function ( assert ) { assert.equal( mw.language.listToText( [] ), '', 'Blank list' ); - assert.equal( mw.language.listToText( ['a'] ), 'a', 'Single item' ); - assert.equal( mw.language.listToText( ['a', 'b'] ), 'a and b', 'Two items' ); - assert.equal( mw.language.listToText( ['a', 'b', 'c'] ), 'a, b and c', 'More than two items' ); + assert.equal( mw.language.listToText( [ 'a' ] ), 'a', 'Single item' ); + assert.equal( mw.language.listToText( [ 'a', 'b' ] ), 'a and b', 'Two items' ); + assert.equal( mw.language.listToText( [ 'a', 'b', 'c' ] ), 'a, b and c', 'More than two items' ); } ); }( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js index 61bab03f..288b527b 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.messagePoster.factory.test.js @@ -12,7 +12,7 @@ mw.messagePoster.factory.register( TEST_MODEL, testMessagePosterConstructor ); assert.strictEqual( - mw.messagePoster.factory.contentModelToClass[TEST_MODEL], + mw.messagePoster.factory.contentModelToClass[ TEST_MODEL ], testMessagePosterConstructor, 'Constructor is registered' ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js new file mode 100644 index 00000000..6cef4a7c --- /dev/null +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js @@ -0,0 +1,36 @@ +( function ( mw ) { + QUnit.module( 'mediawiki.storage' ); + + QUnit.test( 'set/get with localStorage', 3, function ( assert ) { + this.sandbox.stub( mw.storage, 'localStorage', { + setItem: this.sandbox.spy(), + getItem: this.sandbox.stub() + } ); + + mw.storage.set( 'foo', 'test' ); + assert.ok( mw.storage.localStorage.setItem.calledOnce ); + + mw.storage.localStorage.getItem.withArgs( 'foo' ).returns( 'test' ); + mw.storage.localStorage.getItem.returns( null ); + assert.strictEqual( mw.storage.get( 'foo' ), 'test', 'Check value gets stored.' ); + assert.strictEqual( mw.storage.get( 'bar' ), null, 'Unset values are null.' ); + } ); + + QUnit.test( 'set/get without localStorage', 3, function ( assert ) { + this.sandbox.stub( mw.storage, 'localStorage', { + getItem: this.sandbox.stub(), + removeItem: this.sandbox.stub(), + setItem: this.sandbox.stub() + } ); + + mw.storage.localStorage.getItem.throws(); + assert.strictEqual( mw.storage.get( 'foo' ), false ); + + mw.storage.localStorage.setItem.throws(); + assert.strictEqual( mw.storage.set( 'foo', 'test' ), false ); + + mw.storage.localStorage.removeItem.throws(); + assert.strictEqual( mw.storage.remove( 'foo', 'test' ), false ); + } ); + +}( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js index cf36ea82..111d85bd 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js @@ -108,21 +108,21 @@ // Multiple values at once someValues = { - 'foo': 'bar', - 'lorem': 'ipsum', - 'MediaWiki': true + foo: 'bar', + lorem: 'ipsum', + MediaWiki: true }; assert.strictEqual( conf.set( someValues ), true, 'Map.set returns boolean true if multiple values were set by passing an object' ); - assert.deepEqual( conf.get( ['foo', 'lorem'] ), { - 'foo': 'bar', - 'lorem': 'ipsum' + assert.deepEqual( conf.get( [ 'foo', 'lorem' ] ), { + foo: 'bar', + lorem: 'ipsum' }, 'Map.get returns multiple values correctly as an object' ); assert.deepEqual( conf, new mw.Map( conf.values ), 'new mw.Map maps over existing values-bearing object' ); - assert.deepEqual( conf.get( ['foo', 'notExist'] ), { - 'foo': 'bar', - 'notExist': null + assert.deepEqual( conf.get( [ 'foo', 'notExist' ] ), { + foo: 'bar', + notExist: null }, 'Map.get return includes keys that were not found as null values' ); // Interacting with globals and accessing the values object @@ -143,7 +143,7 @@ this.restoreWarnings(); // Change value via global Map - globalConf.set('anotherGlobalMapChecker', 'Again'); + globalConf.set( 'anotherGlobalMapChecker', 'Again' ); assert.equal( globalConf.get( 'anotherGlobalMapChecker' ), 'Again', 'Change in global Map reflected via get()' ); this.suppressWarnings(); assert.equal( window.anotherGlobalMapChecker, 'Again', 'Change in global Map reflected window object' ); @@ -175,8 +175,8 @@ len = formats.length; for ( i = 0; i < len; i++ ) { - format = formats[i]; - assert.equal( mw.message.apply( null, messageArguments )[format](), expectedResult, assertMessage + ' when format is ' + format ); + format = formats[ i ]; + assert.equal( mw.message.apply( null, messageArguments )[ format ](), expectedResult, assertMessage + ' when format is ' + format ); } } @@ -203,21 +203,21 @@ assert.equal( hello.format, 'escaped', 'Message.escaped correctly updated the "format" property' ); assert.ok( mw.messages.set( 'multiple-curly-brace', '"{{SITENAME}}" is the home of {{int:other-message}}' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['multiple-curly-brace'], ['text', 'parse'], '"' + siteName + '" is the home of Other Message', 'Curly brace format works correctly' ); + assertMultipleFormats( [ 'multiple-curly-brace' ], [ 'text', 'parse' ], '"' + siteName + '" is the home of Other Message', 'Curly brace format works correctly' ); assert.equal( mw.message( 'multiple-curly-brace' ).plain(), mw.messages.get( 'multiple-curly-brace' ), 'Plain format works correctly for curly brace message' ); assert.equal( mw.message( 'multiple-curly-brace' ).escaped(), mw.html.escape( '"' + siteName + '" is the home of Other Message' ), 'Escaped format works correctly for curly brace message' ); assert.ok( mw.messages.set( 'multiple-square-brackets-and-ampersand', 'Visit the [[Project:Community portal|community portal]] & [[Project:Help desk|help desk]]' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['multiple-square-brackets-and-ampersand'], ['plain', 'text'], mw.messages.get( 'multiple-square-brackets-and-ampersand' ), 'Square bracket message is not processed' ); + assertMultipleFormats( [ 'multiple-square-brackets-and-ampersand' ], [ 'plain', 'text' ], mw.messages.get( 'multiple-square-brackets-and-ampersand' ), 'Square bracket message is not processed' ); assert.equal( mw.message( 'multiple-square-brackets-and-ampersand' ).escaped(), 'Visit the [[Project:Community portal|community portal]] & [[Project:Help desk|help desk]]', 'Escaped format works correctly for square bracket message' ); assert.htmlEqual( mw.message( 'multiple-square-brackets-and-ampersand' ).parse(), 'Visit the ' + '<a title="Project:Community portal" href="/wiki/Project:Community_portal">community portal</a>' + ' & <a title="Project:Help desk" href="/wiki/Project:Help_desk">help desk</a>', 'Internal links work with parse' ); - assertMultipleFormats( ['mediawiki-test-version-entrypoints-index-php'], ['plain', 'text', 'escaped'], mw.messages.get( 'mediawiki-test-version-entrypoints-index-php' ), 'External link markup is unprocessed' ); + assertMultipleFormats( [ 'mediawiki-test-version-entrypoints-index-php' ], [ 'plain', 'text', 'escaped' ], mw.messages.get( 'mediawiki-test-version-entrypoints-index-php' ), 'External link markup is unprocessed' ); assert.htmlEqual( mw.message( 'mediawiki-test-version-entrypoints-index-php' ).parse(), '<a href="https://www.mediawiki.org/wiki/Manual:index.php">index.php</a>', 'External link works correctly in parse mode' ); - assertMultipleFormats( ['external-link-replace', 'http://example.org/?x=y&z'], ['plain', 'text'], 'Foo [http://example.org/?x=y&z bar]', 'Parameters are substituted but external link is not processed' ); + assertMultipleFormats( [ 'external-link-replace', 'http://example.org/?x=y&z' ], [ 'plain', 'text' ], 'Foo [http://example.org/?x=y&z bar]', 'Parameters are substituted but external link is not processed' ); assert.equal( mw.message( 'external-link-replace', 'http://example.org/?x=y&z' ).escaped(), 'Foo [http://example.org/?x=y&z bar]', 'In escaped mode, parameters are substituted and ampersand is escaped, but external link is not processed' ); assert.htmlEqual( mw.message( 'external-link-replace', 'http://example.org/?x=y&z' ).parse(), 'Foo <a href="http://example.org/?x=y&z">bar</a>', 'External link with replacement works in parse mode without double-escaping' ); @@ -235,26 +235,26 @@ goodbye = mw.message( 'goodbye' ); assert.strictEqual( goodbye.exists(), false, 'Message.exists returns false for nonexistent messages' ); - assertMultipleFormats( ['goodbye'], ['plain', 'text'], '<goodbye>', 'Message.toString returns <key> if key does not exist' ); + assertMultipleFormats( [ 'goodbye' ], [ 'plain', 'text' ], '<goodbye>', 'Message.toString returns <key> if key does not exist' ); // bug 30684 - assertMultipleFormats( ['goodbye'], ['parse', 'escaped'], '<goodbye>', 'Message.toString returns properly escaped <key> if key does not exist' ); + assertMultipleFormats( [ 'goodbye' ], [ 'parse', 'escaped' ], '<goodbye>', 'Message.toString returns properly escaped <key> if key does not exist' ); assert.ok( mw.messages.set( 'plural-test-msg', 'There {{PLURAL:$1|is|are}} $1 {{PLURAL:$1|result|results}}' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['plural-test-msg', 6], ['text', 'parse', 'escaped'], 'There are 6 results', 'plural get resolved' ); + assertMultipleFormats( [ 'plural-test-msg', 6 ], [ 'text', 'parse', 'escaped' ], 'There are 6 results', 'plural get resolved' ); assert.equal( mw.message( 'plural-test-msg', 6 ).plain(), 'There {{PLURAL:6|is|are}} 6 {{PLURAL:6|result|results}}', 'Parameter is substituted but plural is not resolved in plain' ); assert.ok( mw.messages.set( 'plural-test-msg-explicit', 'There {{plural:$1|is one car|are $1 cars|0=are no cars|12=are a dozen cars}}' ), 'mw.messages.set: Register message with explicit plural forms' ); - assertMultipleFormats( ['plural-test-msg-explicit', 12], ['text', 'parse', 'escaped'], 'There are a dozen cars', 'explicit plural get resolved' ); + assertMultipleFormats( [ 'plural-test-msg-explicit', 12 ], [ 'text', 'parse', 'escaped' ], 'There are a dozen cars', 'explicit plural get resolved' ); assert.ok( mw.messages.set( 'plural-test-msg-explicit-beginning', 'Basket has {{plural:$1|0=no eggs|12=a dozen eggs|6=half a dozen eggs|one egg|$1 eggs}}' ), 'mw.messages.set: Register message with explicit plural forms' ); - assertMultipleFormats( ['plural-test-msg-explicit-beginning', 1], ['text', 'parse', 'escaped'], 'Basket has one egg', 'explicit plural given at beginning get resolved for singular' ); - assertMultipleFormats( ['plural-test-msg-explicit-beginning', 4], ['text', 'parse', 'escaped'], 'Basket has 4 eggs', 'explicit plural given at beginning get resolved for plural' ); - assertMultipleFormats( ['plural-test-msg-explicit-beginning', 6], ['text', 'parse', 'escaped'], 'Basket has half a dozen eggs', 'explicit plural given at beginning get resolved for 6' ); - assertMultipleFormats( ['plural-test-msg-explicit-beginning', 0], ['text', 'parse', 'escaped'], 'Basket has no eggs', 'explicit plural given at beginning get resolved for 0' ); + assertMultipleFormats( [ 'plural-test-msg-explicit-beginning', 1 ], [ 'text', 'parse', 'escaped' ], 'Basket has one egg', 'explicit plural given at beginning get resolved for singular' ); + assertMultipleFormats( [ 'plural-test-msg-explicit-beginning', 4 ], [ 'text', 'parse', 'escaped' ], 'Basket has 4 eggs', 'explicit plural given at beginning get resolved for plural' ); + assertMultipleFormats( [ 'plural-test-msg-explicit-beginning', 6 ], [ 'text', 'parse', 'escaped' ], 'Basket has half a dozen eggs', 'explicit plural given at beginning get resolved for 6' ); + assertMultipleFormats( [ 'plural-test-msg-explicit-beginning', 0 ], [ 'text', 'parse', 'escaped' ], 'Basket has no eggs', 'explicit plural given at beginning get resolved for 0' ); - assertMultipleFormats( ['mediawiki-test-pagetriage-del-talk-page-notify-summary'], ['plain', 'text'], mw.messages.get( 'mediawiki-test-pagetriage-del-talk-page-notify-summary' ), 'Double square brackets with no parameters unchanged' ); + assertMultipleFormats( [ 'mediawiki-test-pagetriage-del-talk-page-notify-summary' ], [ 'plain', 'text' ], mw.messages.get( 'mediawiki-test-pagetriage-del-talk-page-notify-summary' ), 'Double square brackets with no parameters unchanged' ); - assertMultipleFormats( ['mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName], ['plain', 'text'], 'Notifying author of deletion nomination for [[' + specialCharactersPageName + ']]', 'Double square brackets with one parameter' ); + assertMultipleFormats( [ 'mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName ], [ 'plain', 'text' ], 'Notifying author of deletion nomination for [[' + specialCharactersPageName + ']]', 'Double square brackets with one parameter' ); assert.equal( mw.message( 'mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName ).escaped(), 'Notifying author of deletion nomination for [[' + mw.html.escape( specialCharactersPageName ) + ']]', 'Double square brackets with one parameter, when escaped' ); @@ -264,21 +264,21 @@ assert.ok( mw.messages.set( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result', '<a href=\'#\' title=\'{{#special:mypage}}\'>Username</a> (<a href=\'#\' title=\'{{#special:mytalk}}\'>talk</a>)' ), 'mw.messages.set: Register' ); assert.equal( mw.message( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result' ).plain(), mw.messages.get( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result' ), 'HTML message with curly braces is not changed in plain mode' ); - assertMultipleFormats( ['gender-plural-msg', 'male', 1], ['text', 'parse', 'escaped'], 'he is awesome', 'Gender and plural are resolved' ); + assertMultipleFormats( [ 'gender-plural-msg', 'male', 1 ], [ 'text', 'parse', 'escaped' ], 'he is awesome', 'Gender and plural are resolved' ); assert.equal( mw.message( 'gender-plural-msg', 'male', 1 ).plain(), '{{GENDER:male|he|she|they}} {{PLURAL:1|is|are}} awesome', 'Parameters are substituted, but gender and plural are not resolved in plain mode' ); assert.equal( mw.message( 'grammar-msg' ).plain(), mw.messages.get( 'grammar-msg' ), 'Grammar is not resolved in plain mode' ); - assertMultipleFormats( ['grammar-msg'], ['text', 'parse'], 'Przeszukaj ' + siteName, 'Grammar is resolved' ); + assertMultipleFormats( [ 'grammar-msg' ], [ 'text', 'parse' ], 'Przeszukaj ' + siteName, 'Grammar is resolved' ); assert.equal( mw.message( 'grammar-msg' ).escaped(), 'Przeszukaj ' + siteName, 'Grammar is resolved in escaped mode' ); - assertMultipleFormats( ['formatnum-msg', '987654321.654321'], ['text', 'parse', 'escaped'], '987,654,321.654', 'formatnum is resolved' ); + assertMultipleFormats( [ 'formatnum-msg', '987654321.654321' ], [ 'text', 'parse', 'escaped' ], '987,654,321.654', 'formatnum is resolved' ); assert.equal( mw.message( 'formatnum-msg' ).plain(), mw.messages.get( 'formatnum-msg' ), 'formatnum is not resolved in plain mode' ); - assertMultipleFormats( ['int-msg'], ['text', 'parse', 'escaped'], 'Some Other Message', 'int is resolved' ); + assertMultipleFormats( [ 'int-msg' ], [ 'text', 'parse', 'escaped' ], 'Some Other Message', 'int is resolved' ); assert.equal( mw.message( 'int-msg' ).plain(), mw.messages.get( 'int-msg' ), 'int is not resolved in plain mode' ); assert.ok( mw.messages.set( 'mediawiki-italics-msg', '<i>Very</i> important' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['mediawiki-italics-msg'], ['plain', 'text', 'parse'], mw.messages.get( 'mediawiki-italics-msg' ), 'Simple italics unchanged' ); + assertMultipleFormats( [ 'mediawiki-italics-msg' ], [ 'plain', 'text', 'parse' ], mw.messages.get( 'mediawiki-italics-msg' ), 'Simple italics unchanged' ); assert.htmlEqual( mw.message( 'mediawiki-italics-msg' ).escaped(), '<i>Very</i> important', @@ -286,7 +286,7 @@ ); assert.ok( mw.messages.set( 'mediawiki-italics-with-link', 'An <i>italicized [[link|wiki-link]]</i>' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['mediawiki-italics-with-link'], ['plain', 'text'], mw.messages.get( 'mediawiki-italics-with-link' ), 'Italics with link unchanged' ); + assertMultipleFormats( [ 'mediawiki-italics-with-link' ], [ 'plain', 'text' ], mw.messages.get( 'mediawiki-italics-with-link' ), 'Italics with link unchanged' ); assert.htmlEqual( mw.message( 'mediawiki-italics-with-link' ).escaped(), 'An <i>italicized [[link|wiki-link]]</i>', @@ -299,7 +299,7 @@ ); assert.ok( mw.messages.set( 'mediawiki-script-msg', '<script >alert( "Who put this script here?" );</script>' ), 'mw.messages.set: Register' ); - assertMultipleFormats( ['mediawiki-script-msg'], ['plain', 'text'], mw.messages.get( 'mediawiki-script-msg' ), 'Script unchanged' ); + assertMultipleFormats( [ 'mediawiki-script-msg' ], [ 'plain', 'text' ], mw.messages.get( 'mediawiki-script-msg' ), 'Script unchanged' ); assert.htmlEqual( mw.message( 'mediawiki-script-msg' ).escaped(), '<script >alert( "Who put this script here?" );</script>', @@ -340,8 +340,9 @@ * The sync style load test (for @import). This is, in a way, also an open bug for * ResourceLoader ("execute js after styles are loaded"), but browsers don't offer a * way to get a callback from when a stylesheet is loaded (that is, including any - * @import rules inside). To work around this, we'll have a little time loop to check + * `@import` rules inside). To work around this, we'll have a little time loop to check * if the styles apply. + * * Note: This test originally used new Image() and onerror to get a callback * when the url is loaded, but that is fragile since it doesn't monitor the * same request as the css @import, and Safari 4 has issues with @@ -406,7 +407,7 @@ isAwesomeDone = true; }; - mw.loader.implement( 'test.callback', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} ); + mw.loader.implement( 'test.callback', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] ); mw.loader.using( 'test.callback', function () { @@ -430,7 +431,7 @@ isAwesomeDone = true; }; - mw.loader.implement( 'hasOwnProperty', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} ); + mw.loader.implement( 'hasOwnProperty', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ], {}, {} ); mw.loader.using( 'hasOwnProperty', function () { @@ -454,7 +455,7 @@ isAwesomeDone = true; }; - mw.loader.implement( 'test.promise', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} ); + mw.loader.implement( 'test.promise', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] ); mw.loader.using( 'test.promise' ) .done( function () { @@ -491,9 +492,8 @@ QUnit.start(); }, { - 'all': '.mw-test-implement-a { float: right; }' - }, - {} + all: '.mw-test-implement-a { float: right; }' + } ); mw.loader.load( [ @@ -547,16 +547,15 @@ } ); }, { - 'url': { - 'print': [urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' )], - 'screen': [ + url: { + print: [ urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' ) ], + screen: [ // bug 40834: Make sure it actually works with more than 1 stylesheet reference urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ), urlStyleTest( '.mw-test-implement-b3', 'float', 'right' ) ] } - }, - {} + } ); mw.loader.load( [ @@ -585,9 +584,8 @@ QUnit.start(); }, { - 'all': '.mw-test-implement-c { float: right; }' - }, - {} + all: '.mw-test-implement-c { float: right; }' + } ); mw.loader.load( [ @@ -622,10 +620,9 @@ } ); }, { - 'all': [urlStyleTest( '.mw-test-implement-d', 'float', 'right' )], - 'print': [urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' )] - }, - {} + all: [ urlStyleTest( '.mw-test-implement-d', 'float', 'right' ) ], + print: [ urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' ) ] + } ); mw.loader.load( [ @@ -634,20 +631,20 @@ } ); // @import (bug 31676) - QUnit.asyncTest( 'mw.loader.implement( styles has @import)', 5, function ( assert ) { + QUnit.asyncTest( 'mw.loader.implement( styles has @import)', 7, function ( assert ) { var isJsExecuted, $element; mw.loader.implement( 'test.implement.import', function () { - assert.strictEqual( isJsExecuted, undefined, 'javascript not executed multiple times' ); + assert.strictEqual( isJsExecuted, undefined, 'script not executed multiple times' ); isJsExecuted = true; - assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state is "ready" while implement() is executing javascript' ); + assert.equal( mw.loader.getState( 'test.implement.import' ), 'executing', 'module state during implement() script execution' ); $element = $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' ); - assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'Messages are loaded before javascript execution' ); + assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'messages load before script execution' ); assertStyleAsync( assert, $element, 'float', 'right', function () { assert.equal( $element.css( 'text-align' ), 'center', @@ -658,7 +655,7 @@ } ); }, { - 'css': [ + css: [ '@import url(\'' + urlStyleTest( '.mw-test-implement-import', 'float', 'right' ) + '\');\n' @@ -670,8 +667,64 @@ } ); - mw.loader.load( 'test.implement' ); + mw.loader.using( 'test.implement.import' ).always( function () { + assert.strictEqual( isJsExecuted, true, 'script executed' ); + assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state after script execution' ); + } ); + } ); + + QUnit.asyncTest( 'mw.loader.implement( dependency with styles )', 4, function ( assert ) { + var $element = $( '<div class="mw-test-implement-e"></div>' ).appendTo( '#qunit-fixture' ), + $element2 = $( '<div class="mw-test-implement-e2"></div>' ).appendTo( '#qunit-fixture' ); + + assert.notEqual( + $element.css( 'float' ), + 'right', + 'style is clear' + ); + assert.notEqual( + $element2.css( 'float' ), + 'left', + 'style is clear' + ); + + mw.loader.register( [ + [ 'test.implement.e', '0', [ 'test.implement.e2' ] ], + [ 'test.implement.e2', '0' ] + ] ); + + mw.loader.implement( + 'test.implement.e', + function () { + assert.equal( + $element.css( 'float' ), + 'right', + 'Depending module\'s style is applied' + ); + QUnit.start(); + }, + { + all: '.mw-test-implement-e { float: right; }' + } + ); + + mw.loader.implement( + 'test.implement.e2', + function () { + assert.equal( + $element2.css( 'float' ), + 'left', + 'Dependency\'s style is applied' + ); + }, + { + all: '.mw-test-implement-e2 { float: left; }' + } + ); + mw.loader.load( [ + 'test.implement.e' + ] ); } ); QUnit.test( 'mw.loader.implement( only scripts )', 1, function ( assert ) { @@ -682,7 +735,9 @@ QUnit.asyncTest( 'mw.loader.implement( only messages )', 2, function ( assert ) { assert.assertFalse( mw.messages.exists( 'bug_29107' ), 'Verify that the test message doesn\'t exist yet' ); - mw.loader.implement( 'test.implement.msgs', [], {}, { 'bug_29107': 'loaded' } ); + // jscs: disable requireCamelCaseOrUpperCaseIdentifiers + mw.loader.implement( 'test.implement.msgs', [], {}, { bug_29107: 'loaded' } ); + // jscs: enable requireCamelCaseOrUpperCaseIdentifiers mw.loader.using( 'test.implement.msgs', function () { QUnit.start(); assert.ok( mw.messages.exists( 'bug_29107' ), 'Bug 29107: messages-only module should implement ok' ); @@ -697,9 +752,9 @@ this.sandbox.stub( mw, 'track' ); mw.loader.register( [ - ['test.module1', '0'], - ['test.module2', '0', ['test.module1']], - ['test.module3', '0', ['test.module2']] + [ 'test.module1', '0' ], + [ 'test.module2', '0', [ 'test.module1' ] ], + [ 'test.module3', '0', [ 'test.module2' ] ] ] ); mw.loader.implement( 'test.module1', function () { throw new Error( 'expected' ); @@ -713,22 +768,19 @@ QUnit.test( 'mw.loader out-of-order implementation', 9, function ( assert ) { mw.loader.register( [ - ['test.module4', '0'], - ['test.module5', '0', ['test.module4']], - ['test.module6', '0', ['test.module5']] + [ 'test.module4', '0' ], + [ 'test.module5', '0', [ 'test.module4' ] ], + [ 'test.module6', '0', [ 'test.module5' ] ] ] ); - mw.loader.implement( 'test.module4', function () { - }, {}, {} ); + mw.loader.implement( 'test.module4', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' ); assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' ); - mw.loader.implement( 'test.module6', function () { - }, {}, {} ); + mw.loader.implement( 'test.module6', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' ); assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' ); - mw.loader.implement( 'test.module5', function () { - }, {}, {} ); + mw.loader.implement( 'test.module5', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' ); assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' ); @@ -736,12 +788,11 @@ QUnit.test( 'mw.loader missing dependency', 13, function ( assert ) { mw.loader.register( [ - ['test.module7', '0'], - ['test.module8', '0', ['test.module7']], - ['test.module9', '0', ['test.module8']] + [ 'test.module7', '0' ], + [ 'test.module8', '0', [ 'test.module7' ] ], + [ 'test.module9', '0', [ 'test.module8' ] ] ] ); - mw.loader.implement( 'test.module8', function () { - }, {}, {} ); + mw.loader.implement( 'test.module8', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' ); assert.strictEqual( mw.loader.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' ); assert.strictEqual( mw.loader.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' ); @@ -749,24 +800,23 @@ assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' ); assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' ); assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' ); - mw.loader.implement( 'test.module9', function () { - }, {}, {} ); + mw.loader.implement( 'test.module9', function () {} ); assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' ); assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' ); assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' ); mw.loader.using( - ['test.module7'], + [ 'test.module7' ], function () { assert.ok( false, 'Success fired despite missing dependency' ); assert.ok( true, 'QUnit expected() count dummy' ); }, function ( e, dependencies ) { assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' ); - assert.deepEqual( dependencies, ['test.module7'], 'Error callback called with module test.module7' ); + assert.deepEqual( dependencies, [ 'test.module7' ], 'Error callback called with module test.module7' ); } ); mw.loader.using( - ['test.module9'], + [ 'test.module9' ], function () { assert.ok( false, 'Success fired despite missing dependency' ); assert.ok( true, 'QUnit expected() count dummy' ); @@ -776,7 +826,7 @@ dependencies.sort(); assert.deepEqual( dependencies, - ['test.module7', 'test.module8', 'test.module9'], + [ 'test.module7', 'test.module8', 'test.module9' ], 'Error callback called with all three modules as dependencies' ); } @@ -786,9 +836,9 @@ QUnit.asyncTest( 'mw.loader dependency handling', 5, function ( assert ) { mw.loader.register( [ // [module, version, dependencies, group, source] - ['testMissing', '1', [], null, 'testloader'], - ['testUsesMissing', '1', ['testMissing'], null, 'testloader'], - ['testUsesNestedMissing', '1', ['testUsesMissing'], null, 'testloader'] + [ 'testMissing', '1', [], null, 'testloader' ], + [ 'testUsesMissing', '1', [ 'testMissing' ], null, 'testloader' ], + [ 'testUsesNestedMissing', '1', [ 'testUsesMissing' ], null, 'testloader' ] ] ); function verifyModuleStates() { @@ -797,7 +847,7 @@ assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' ); } - mw.loader.using( ['testUsesNestedMissing'], + mw.loader.using( [ 'testUsesNestedMissing' ], function () { assert.ok( false, 'Error handler should be invoked.' ); assert.ok( true ); // Dummy to reach QUnit expect() @@ -811,7 +861,7 @@ // As soon as server spits out state('testMissing', 'missing'); // it will bubble up and trigger the error callback. // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing. - assert.deepEqual( badmodules, ['testMissing'], 'Bad modules as expected.' ); + assert.deepEqual( badmodules, [ 'testMissing' ], 'Bad modules as expected.' ); verifyModuleStates(); @@ -823,9 +873,9 @@ QUnit.asyncTest( 'mw.loader skin-function handling', 5, function ( assert ) { mw.loader.register( [ // [module, version, dependencies, group, source, skip] - ['testSkipped', '1', [], null, 'testloader', 'return true;'], - ['testNotSkipped', '1', [], null, 'testloader', 'return false;'], - ['testUsesSkippable', '1', ['testSkipped', 'testNotSkipped'], null, 'testloader'] + [ 'testSkipped', '1', [], null, 'testloader', 'return true;' ], + [ 'testNotSkipped', '1', [], null, 'testloader', 'return false;' ], + [ 'testUsesSkippable', '1', [ 'testSkipped', 'testNotSkipped' ], null, 'testloader' ] ] ); function verifyModuleStates() { @@ -834,7 +884,7 @@ assert.equal( mw.loader.getState( 'testUsesSkippable' ), 'ready', 'Module is ready when skippable dependencies are ready' ); } - mw.loader.using( ['testUsesSkippable'], + mw.loader.using( [ 'testUsesSkippable' ], function () { assert.ok( true, 'Success handler should be invoked.' ); assert.ok( true ); // Dummy to match error handler and reach QUnit expect() @@ -858,7 +908,7 @@ // This bug was actually already fixed in 1.18 and later when discovered in 1.17. // Test is for regressions! - // Forge an URL to the test callback script + // Forge a URL to the test callback script var target = QUnit.fixurl( mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js' ); @@ -875,6 +925,45 @@ mw.loader.load( target ); } ); + QUnit.asyncTest( 'mw.loader( "/absolute-path" )', 2, function ( assert ) { + // Forge a URL to the test callback script + var target = QUnit.fixurl( + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js' + ); + + // Confirm that mw.loader.load() works with absolute-paths (relative to current hostname) + assert.equal( target.slice( 0, 1 ), '/', 'URL is relative to document root' ); + + // Async! + // The target calls QUnit.start + mw.loader.load( target ); + } ); + + QUnit.asyncTest( 'mw.loader() executing race (T112232)', 2, function ( assert ) { + var done = false; + + // The red herring schedules its CSS buffer first. In T112232, a bug in the + // state machine would cause the job for testRaceLoadMe to run with an earlier job. + mw.loader.implement( + 'testRaceRedHerring', + function () {}, + { css: [ '.mw-testRaceRedHerring {}' ] } + ); + mw.loader.implement( + 'testRaceLoadMe', + function () { + done = true; + }, + { css: [ '.mw-testRaceLoadMe { float: left; }' ] } + ); + + mw.loader.load( [ 'testRaceRedHerring', 'testRaceLoadMe' ] ); + mw.loader.using( 'testRaceLoadMe', function () { + assert.strictEqual( done, true, 'script ran' ); + assert.strictEqual( mw.loader.getState( 'testRaceLoadMe' ), 'ready', 'state' ); + } ).always( QUnit.start ); + } ); + QUnit.test( 'mw.html', 13, function ( assert ) { assert.throws( function () { mw.html.escape(); @@ -968,9 +1057,9 @@ mw.hook( 'test.hook.data' ).add( function ( data1, data2 ) { assert.equal( data1, 'example', 'Fire with data (string param)' ); - assert.deepEqual( data2, ['two'], 'Fire with data (array param)' ); + assert.deepEqual( data2, [ 'two' ], 'Fire with data (array param)' ); } ); - mw.hook( 'test.hook.data' ).fire( 'example', ['two'] ); + mw.hook( 'test.hook.data' ).fire( 'example', [ 'two' ] ); hook = mw.hook( 'test.hook.chainable' ); assert.strictEqual( hook.add(), hook, 'hook.add is chainable' ); @@ -981,7 +1070,7 @@ add = hook.add; fire = hook.fire; add( function ( x, y ) { - assert.deepEqual( [x, y], ['x', 'y'], 'Detached (contextless) with data' ); + assert.deepEqual( [ x, y ], [ 'x', 'y' ], 'Detached (contextless) with data' ); } ); fire( 'x', 'y' ); @@ -1004,7 +1093,7 @@ assert.equal( chr, 'z', 'Adding callback later invokes right away with last data' ); } ); - assert.deepEqual( chars, ['x', 'y', 'z'], 'Multiple callbacks with multiple fires' ); + assert.deepEqual( chars, [ 'x', 'y', 'z' ], 'Multiple callbacks with multiple fires' ); chars = []; callback = function ( chr ) { @@ -1033,7 +1122,7 @@ assert.deepEqual( chars, - ['x', 'x', 'x', 'x', 'y', 'z'], + [ 'x', 'x', 'x', 'x', 'y', 'z' ], '"add" and "remove" support variadic arguments. ' + '"add" does not filter unique. ' + '"remove" removes all equal by reference. ' + diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js index e43516b0..89eb45f2 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js @@ -1,7 +1,7 @@ ( function ( mw, $ ) { QUnit.module( 'mediawiki.toc', QUnit.newMwEnvironment( { setup: function () { - // Prevent live cookies like mw_hidetoc=1 from interferring with the test + // Prevent live cookies from interferring with the test this.stub( $, 'cookie' ).returns( null ); } } ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js index cdb26244..5329be6a 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.track.test.js @@ -39,4 +39,22 @@ assert.assertTrue( this.timeStamp >= now, 'thisValue has sane timestamp' ); } ); } ); + + QUnit.test( 'trackUnsubscribe', 1, function ( assert ) { + var sequence = []; + function unsubber( topic, data ) { + sequence.push( [ topic, data ] ); + } + + mw.track( 'unsub', { key: 1 } ); + mw.trackSubscribe( 'unsub', unsubber ); + mw.track( 'unsub', { key: 2 } ); + mw.trackUnsubscribe( unsubber ); + mw.track( 'unsub', { key: 3 } ); + + assert.deepEqual( sequence, [ + [ 'unsub', { key: 1 } ], + [ 'unsub', { key: 2 } ] + ], 'Stop when unsubscribing' ); + } ); }( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js index 0b42af4b..c1f1484c 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js @@ -2,54 +2,54 @@ var // Based on IPTest.php > testisIPv4 IPV4_CASES = [ - [false, false, 'Boolean false is not an IP'], - [false, true, 'Boolean true is not an IP'], - [false, '', 'Empty string is not an IP'], - [false, 'abc', '"abc" is not an IP'], - [false, ':', 'Colon is not an IP'], - [false, '124.24.52', 'IPv4 not enough quads'], - [false, '24.324.52.13', 'IPv4 out of range'], - [false, '.24.52.13', 'IPv4 starts with period'], - - [true, '124.24.52.13', '124.24.52.134 is a valid IP'], - [true, '1.24.52.13', '1.24.52.13 is a valid IP'], - [false, '74.24.52.13/20', 'IPv4 ranges are not recognized as valid IPs'] + [ false, false, 'Boolean false is not an IP' ], + [ false, true, 'Boolean true is not an IP' ], + [ false, '', 'Empty string is not an IP' ], + [ false, 'abc', '"abc" is not an IP' ], + [ false, ':', 'Colon is not an IP' ], + [ false, '124.24.52', 'IPv4 not enough quads' ], + [ false, '24.324.52.13', 'IPv4 out of range' ], + [ false, '.24.52.13', 'IPv4 starts with period' ], + + [ true, '124.24.52.13', '124.24.52.134 is a valid IP' ], + [ true, '1.24.52.13', '1.24.52.13 is a valid IP' ], + [ false, '74.24.52.13/20', 'IPv4 ranges are not recognized as valid IPs' ] ], // Based on IPTest.php > testisIPv6 IPV6_CASES = [ - [false, ':fc:100::', 'IPv6 starting with lone ":"'], - [false, 'fc:100:::', 'IPv6 ending with a ":::"'], - [false, 'fc:300', 'IPv6 with only 2 words'], - [false, 'fc:100:300', 'IPv6 with only 3 words'], - - [false, 'fc:100:a:d:1:e:ac:0::', 'IPv6 with 8 words ending with "::"'], - [false, 'fc:100:a:d:1:e:ac:0:1::', 'IPv6 with 9 words ending with "::"'], - - [false, ':::'], - [false, '::0:', 'IPv6 ending in a lone ":"'], - - [true, '::', 'IPv6 zero address'], - - [false, '::fc:100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words'], - [false, '::fc:100:a:d:1:e:ac:0:1', 'IPv6 with 9 words'], - - [false, ':fc::100', 'IPv6 starting with lone ":"'], - [false, 'fc::100:', 'IPv6 ending with lone ":"'], - [false, 'fc:::100', 'IPv6 with ":::" in the middle'], - - [true, 'fc::100', 'IPv6 with "::" and 2 words'], - [true, 'fc::100:a', 'IPv6 with "::" and 3 words'], - [true, 'fc::100:a:d', 'IPv6 with "::" and 4 words'], - [true, 'fc::100:a:d:1', 'IPv6 with "::" and 5 words'], - [true, 'fc::100:a:d:1:e', 'IPv6 with "::" and 6 words'], - [true, 'fc::100:a:d:1:e:ac', 'IPv6 with "::" and 7 words'], - [true, '2001::df', 'IPv6 with "::" and 2 words'], - [true, '2001:5c0:1400:a::df', 'IPv6 with "::" and 5 words'], - [true, '2001:5c0:1400:a::df:2', 'IPv6 with "::" and 6 words'], - - [false, 'fc::100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words'], - [false, 'fc::100:a:d:1:e:ac:0:1', 'IPv6 with 9 words'] + [ false, ':fc:100::', 'IPv6 starting with lone ":"' ], + [ false, 'fc:100:::', 'IPv6 ending with a ":::"' ], + [ false, 'fc:300', 'IPv6 with only 2 words' ], + [ false, 'fc:100:300', 'IPv6 with only 3 words' ], + + [ false, 'fc:100:a:d:1:e:ac:0::', 'IPv6 with 8 words ending with "::"' ], + [ false, 'fc:100:a:d:1:e:ac:0:1::', 'IPv6 with 9 words ending with "::"' ], + + [ false, ':::' ], + [ false, '::0:', 'IPv6 ending in a lone ":"' ], + + [ true, '::', 'IPv6 zero address' ], + + [ false, '::fc:100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' ], + [ false, '::fc:100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' ], + + [ false, ':fc::100', 'IPv6 starting with lone ":"' ], + [ false, 'fc::100:', 'IPv6 ending with lone ":"' ], + [ false, 'fc:::100', 'IPv6 with ":::" in the middle' ], + + [ true, 'fc::100', 'IPv6 with "::" and 2 words' ], + [ true, 'fc::100:a', 'IPv6 with "::" and 3 words' ], + [ true, 'fc::100:a:d', 'IPv6 with "::" and 4 words' ], + [ true, 'fc::100:a:d:1', 'IPv6 with "::" and 5 words' ], + [ true, 'fc::100:a:d:1:e', 'IPv6 with "::" and 6 words' ], + [ true, 'fc::100:a:d:1:e:ac', 'IPv6 with "::" and 7 words' ], + [ true, '2001::df', 'IPv6 with "::" and 2 words' ], + [ true, '2001:5c0:1400:a::df', 'IPv6 with "::" and 5 words' ], + [ true, '2001:5c0:1400:a::df:2', 'IPv6 with "::" and 6 words' ], + + [ false, 'fc::100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' ], + [ false, 'fc::100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' ] ]; Array.prototype.push.apply( IPV6_CASES, @@ -70,7 +70,7 @@ '::fc:100:a:d:1:e:ac', 'fc:100:a:d:1:e:ac:0' ], function ( el ) { - return [[ true, el, el + ' is a valid IP' ]]; + return [ [ true, el, el + ' is a valid IP' ] ]; } ) ); @@ -83,7 +83,7 @@ }, messages: { // Used by accessKeyLabel in test for addPortletLink - 'brackets': '[$1]', + brackets: '[$1]', 'word-separator': ' ' } } ) ); @@ -92,7 +92,7 @@ assert.equal( mw.util.rawurlencode( 'Test:A & B/Here' ), 'Test%3AA%20%26%20B%2FHere' ); } ); - QUnit.test( 'wikiUrlencode', 10, function ( assert ) { + QUnit.test( 'wikiUrlencode', 11, function ( assert ) { assert.equal( mw.util.wikiUrlencode( 'Test:A & B/Here' ), 'Test:A_%26_B/Here' ); // See also wfUrlencodeTest.php#provideURLS $.each( { @@ -102,6 +102,7 @@ ':': ':', ';@$-_.!*': ';@$-_.!*', '/': '/', + '~': '~', '[]': '%5B%5D', '<>': '%3C%3E', '\'': '%27' @@ -133,10 +134,10 @@ QUnit.test( 'wikiScript', 4, function ( assert ) { mw.config.set( { - 'wgScript': '/w/i.php', // customized wgScript for bug 39103 - 'wgLoadScript': '/w/l.php', // customized wgLoadScript for bug 39103 - 'wgScriptPath': '/w', - 'wgScriptExtension': '.php' + wgScript: '/w/i.php', // customized wgScript for bug 39103 + wgLoadScript: '/w/l.php', // customized wgLoadScript for bug 39103 + wgScriptPath: '/w', + wgScriptExtension: '.php' } ); assert.equal( mw.util.wikiScript(), mw.config.get( 'wgScript' ), @@ -175,10 +176,10 @@ url = 'http://example.org/#&foo=bad'; assert.strictEqual( mw.util.getParamValue( 'foo', url ), null, 'Ignore hash if param is not in querystring but in hash (bug 27427)' ); - url = 'example.org?' + $.param( { 'TEST': 'a b+c' } ); + url = 'example.org?' + $.param( { TEST: 'a b+c' } ); assert.strictEqual( mw.util.getParamValue( 'TEST', url ), 'a b+c', 'Bug 30441: getParamValue must understand "+" encoding of space' ); - url = 'example.org?' + $.param( { 'TEST': 'a b+c d' } ); // check for sloppy code from r95332 :) + url = 'example.org?' + $.param( { TEST: 'a b+c d' } ); // check for sloppy code from r95332 :) assert.strictEqual( mw.util.getParamValue( 'TEST', url ), 'a b+c d', 'Bug 30441: getParamValue must understand "+" encoding of space (multiple spaces)' ); } ); @@ -240,7 +241,7 @@ 'ResourceLoader', 't-rl', 'More info about ResourceLoader on MediaWiki.org ', 'l' ); - assert.ok( $.isDomElement( tbRL ), 'addPortletLink returns a valid DOM Element according to $.isDomElement' ); + assert.ok( tbRL && tbRL.nodeType, 'addPortletLink returns a DOM Node' ); tbMW = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/', 'MediaWiki.org', 't-mworg', 'Go to MediaWiki.org', 'm', tbRL ); @@ -265,7 +266,7 @@ ); assert.equal( $tbMW.closest( '.portlet' ).attr( 'id' ), 'p-test-tb', 'Link was inserted within correct portlet' ); - assert.strictEqual( $tbMW.next()[0], tbRL, 'Link is in the correct position (nextnode as Node object)' ); + assert.strictEqual( $tbMW.next()[ 0 ], tbRL, 'Link is in the correct position (nextnode as Node object)' ); cuQuux = mw.util.addPortletLink( 'p-test-custom', '#', 'Quux', null, 'Example [shift-x]', 'q' ); $cuQuux = $( cuQuux ); @@ -281,7 +282,7 @@ tbRLDM = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', 'Default modules', 't-rldm', 'List of all default modules ', 'd', '#t-rl' ); - assert.strictEqual( $( tbRLDM ).next()[0], tbRL, 'Link is in the correct position (CSS selector as nextnode)' ); + assert.strictEqual( $( tbRLDM ).next()[ 0 ], tbRL, 'Link is in the correct position (CSS selector as nextnode)' ); caFoo = mw.util.addPortletLink( 'p-test-views', '#', 'Foo' ); @@ -289,19 +290,19 @@ assert.strictEqual( $( caFoo ).find( 'span' ).length, 1, 'A <span> element should be added for porlets with vectorTabs class.' ); addedAfter = mw.util.addPortletLink( 'p-test-tb', '#', 'After foo', 'post-foo', 'After foo', null, $( tbRL ) ); - assert.strictEqual( $( addedAfter ).next()[0], tbRL, 'Link is in the correct position (jQuery object as nextnode)' ); + assert.strictEqual( $( addedAfter ).next()[ 0 ], tbRL, 'Link is in the correct position (jQuery object as nextnode)' ); // test case - nonexistent id as next node tbRLDMnonexistentid = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', 'Default modules', 't-rldm-nonexistent', 'List of all default modules ', 'd', '#t-rl-nonexistent' ); - assert.equal( tbRLDMnonexistentid, $( '#p-test-tb li:last' )[0], 'Fallback to adding at the end (nextnode non-matching CSS selector)' ); + assert.equal( tbRLDMnonexistentid, $( '#p-test-tb li:last' )[ 0 ], 'Fallback to adding at the end (nextnode non-matching CSS selector)' ); // test case - empty jquery object as next node tbRLDMemptyjquery = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', 'Default modules', 't-rldm-empty-jquery', 'List of all default modules ', 'd', $( '#t-rl-nonexistent' ) ); - assert.equal( tbRLDMemptyjquery, $( '#p-test-tb li:last' )[0], 'Fallback to adding at the end (nextnode as empty jQuery object)' ); + assert.equal( tbRLDMemptyjquery, $( '#p-test-tb li:last' )[ 0 ], 'Fallback to adding at the end (nextnode as empty jQuery object)' ); } ); QUnit.test( 'validateEmail', 6, function ( assert ) { @@ -319,23 +320,23 @@ QUnit.test( 'isIPv6Address', 40, function ( assert ) { $.each( IPV6_CASES, function ( i, ipCase ) { - assert.strictEqual( mw.util.isIPv6Address( ipCase[1] ), ipCase[0], ipCase[2] ); + assert.strictEqual( mw.util.isIPv6Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] ); } ); } ); QUnit.test( 'isIPv4Address', 11, function ( assert ) { $.each( IPV4_CASES, function ( i, ipCase ) { - assert.strictEqual( mw.util.isIPv4Address( ipCase[1] ), ipCase[0], ipCase[2] ); + assert.strictEqual( mw.util.isIPv4Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] ); } ); } ); QUnit.test( 'isIPAddress', 51, function ( assert ) { $.each( IPV4_CASES, function ( i, ipCase ) { - assert.strictEqual( mw.util.isIPv4Address( ipCase[1] ), ipCase[0], ipCase[2] ); + assert.strictEqual( mw.util.isIPv4Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] ); } ); $.each( IPV6_CASES, function ( i, ipCase ) { - assert.strictEqual( mw.util.isIPv6Address( ipCase[1] ), ipCase[0], ipCase[2] ); + assert.strictEqual( mw.util.isIPv6Address( ipCase[ 1 ] ), ipCase[ 0 ], ipCase[ 2 ] ); } ); } ); }( mediaWiki, jQuery ) ); |