From a1789ddde42033f1b05cc4929491214ee6e79383 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Thu, 17 Dec 2015 09:15:42 +0100 Subject: Update to MediaWiki 1.26.0 --- .../resources/mediawiki/mediawiki.RegExp.test.js | 38 +++ .../resources/mediawiki/mediawiki.Title.test.js | 171 ++++++++----- .../resources/mediawiki/mediawiki.Uri.test.js | 10 +- .../resources/mediawiki/mediawiki.cldr.test.js | 6 +- .../resources/mediawiki/mediawiki.cookie.test.js | 4 +- .../mediawiki/mediawiki.errorLogger.test.js | 2 +- .../mediawiki/mediawiki.experiments.test.js | 63 +++++ .../mediawiki/mediawiki.jqueryMsg.test.js | 269 +++++++++++++++++--- .../resources/mediawiki/mediawiki.jscompat.test.js | 2 +- .../resources/mediawiki/mediawiki.language.test.js | 14 +- .../mediawiki.messagePoster.factory.test.js | 2 +- .../resources/mediawiki/mediawiki.storage.test.js | 36 +++ .../suites/resources/mediawiki/mediawiki.test.js | 283 ++++++++++++++------- .../resources/mediawiki/mediawiki.toc.test.js | 2 +- .../resources/mediawiki/mediawiki.track.test.js | 18 ++ .../resources/mediawiki/mediawiki.util.test.js | 127 ++++----- 16 files changed, 763 insertions(+), 284 deletions(-) create mode 100644 tests/qunit/suites/resources/mediawiki/mediawiki.RegExp.test.js create mode 100644 tests/qunit/suites/resources/mediawiki/mediawiki.experiments.test.js create mode 100644 tests/qunit/suites/resources/mediawiki/mediawiki.storage.test.js (limited to 'tests/qunit/suites/resources/mediawiki') 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', @@ -601,20 +656,6 @@ nameText: 'Dot. dot', 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', @@ -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 = '注册用户'; + expectedListUsersSitename = '注册用户' + + mw.config.get( 'wgSiteName' ) + ''; expectedEntrypoints = 'index.php'; @@ -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 = 'Main|Page'; @@ -384,20 +412,159 @@ expectedSpecialCharacters, 'Special characters' ); + + mw.messages.set( 'leading-colon', '[[:File:Foo.jpg]]' ); + assert.htmlEqual( + formatParse( 'leading-colon' ), + 'File:Foo.jpg', + '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 Example] asd', + 'asd Example asd' + ], + [ + 'extlink-html-partial', + 'asd [http://example.org foo Example bar] asd', + 'asd foo Example bar asd' + ], + [ + 'wikilink-html-full', + 'asd [[Example|Example]] asd', + 'asd Example asd' + ], + [ + 'wikilink-html-partial', + 'asd [[Example|foo Example bar]] asd', + 'asd foo Example bar 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 Example asd' + ], + [ + 'extlink-param-href-partial', + 'asd [$1/example Example] asd', + 'asd Example asd' + ], + [ + 'extlink-param-text-full', + 'asd [http://example.org $2] asd', + 'asd Text asd' + ], + [ + 'extlink-param-text-partial', + 'asd [http://example.org Example $2] asd', + 'asd Example Text asd' + ], + [ + 'extlink-param-both-full', + 'asd [$1 $2] asd', + 'asd Text asd' + ], + [ + 'extlink-param-both-partial', + 'asd [$1/example Example $2] asd', + 'asd Example Text asd' + ], + [ + 'wikilink-param-href-full', + 'asd [[$1|Example]] asd', + 'asd Example asd' + ], + [ + 'wikilink-param-href-partial', + 'asd [[$1/Test|Example]] asd', + 'asd Example asd' + ], + [ + 'wikilink-param-text-full', + 'asd [[Example|$2]] asd', + 'asd Text asd' + ], + [ + 'wikilink-param-text-partial', + 'asd [[Example|Example $2]] asd', + 'asd Example Text asd' + ], + [ + 'wikilink-param-both-full', + 'asd [[$1|$2]] asd', + 'asd Text asd' + ], + [ + 'wikilink-param-both-partial', + 'asd [[$1/Test|Example $2]] asd', + 'asd Example Text asd' + ], + [ + 'wikilink-param-unpiped-full', + 'asd [[$1]] asd', + 'asd Example asd' + ], + [ + 'wikilink-param-unpiped-partial', + 'asd [[$1/Test]] asd', + 'asd Example/Test 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', 'Very 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', 'Strong 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 key' ); - 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 vital' ); - 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 italicized [[link|wiki-link]]' ); @@ -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', ' [http://example.com Foo bar]' ); assert.htmlEqual( formatParse( 'jquerymsg-script-and-external-link' ), - '<script>alert( "jquerymsg-script-and-external-link test" );</script> Foo bar', + '<script>alert( "jquerymsg-script-and-external-link test" );</script> Foo bar', 'HTML tags in external links not interfering with escaping of other tags' ); mw.messages.set( 'jquerymsg-link-script', '[http://example.com ]' ); assert.htmlEqual( formatParse( 'jquerymsg-link-script' ), - '<script>alert( "jquerymsg-link-script test" );</script>', + '<script>alert( "jquerymsg-link-script test" );</script>', 'Non-whitelisted HTML tag in external link anchor treated as text' ); @@ -792,7 +956,7 @@ mw.messages.set( 'jquerymsg-wikitext-contents-script', '' ); assert.htmlEqual( formatParse( 'jquerymsg-wikitext-contents-script' ), - '<script>Script inside</script>', + '<script>Script inside</script>', '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 = 'Bold!'; + mw.messages.set( 'integration-test', '[[Bold]]!' ); + + 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( + $( '' ).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 = $( '' ); - 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 = $( '