From eaaec38d6fdba01b30d051711dc84865fbe388ad Mon Sep 17 00:00:00 2001 From: Matthew Flaschen Date: Fri, 28 Dec 2012 21:39:12 -0500 Subject: [PATCH] (bug 43498) (bug 43574) Two wikilink types and {{int:}}. * Bug 43498 - Implement two wikilink types, with tests. Also add test for external link, which was already implemented in the main code. * Bug 43574 - Support {{int:}} message includes in jqueryMsg, with tests. * Add tests for when the old mw.msg should be called, vs. getMessageFunction. Change-Id: I7a748eb99259103f491ff5a5aa0bf223fc0b76c4 --- resources/mediawiki/mediawiki.jqueryMsg.js | 108 +++++++++-- resources/mediawiki/mediawiki.jqueryMsg.peg | 1 + .../mediawiki/mediawiki.jqueryMsg.test.js | 177 +++++++++++++++++- 3 files changed, 268 insertions(+), 18 deletions(-) diff --git a/resources/mediawiki/mediawiki.jqueryMsg.js b/resources/mediawiki/mediawiki.jqueryMsg.js index 6e2d3b410b..c599f4bc40 100644 --- a/resources/mediawiki/mediawiki.jqueryMsg.js +++ b/resources/mediawiki/mediawiki.jqueryMsg.js @@ -164,7 +164,7 @@ regularLiteral, regularLiteralWithoutBar, regularLiteralWithoutSpace, backslash, anyCharacter, escapedOrLiteralWithoutSpace, escapedOrLiteralWithoutBar, escapedOrRegularLiteral, whitespace, dollar, digits, - openExtlink, closeExtlink, openLink, closeLink, templateName, pipe, colon, + openExtlink, closeExtlink, wikilinkPage, wikilinkContents, openLink, closeLink, templateName, pipe, colon, templateContents, openTemplate, closeTemplate, nonWhitespaceExpression, paramExpression, expression, result; @@ -304,6 +304,14 @@ var result = nOrMore( 1, escapedOrLiteralWithoutBar )(); return result === null ? null : result.join(''); } + + // Used for wikilink page names. Like literalWithoutBar, but + // without allowing escapes. + function unescapedLiteralWithoutBar() { + var result = nOrMore( 1, regularLiteralWithoutBar )(); + return result === null ? null : result.join(''); + } + function literal() { var result = nOrMore( 1, escapedOrRegularLiteral )(); return result === null ? null : result.join(''); @@ -357,16 +365,48 @@ } openLink = makeStringParser( '[[' ); closeLink = makeStringParser( ']]' ); + pipe = makeStringParser( '|' ); + + function template() { + var result = sequence( [ + openTemplate, + templateContents, + closeTemplate + ] ); + return result === null ? null : result[1]; + } + + wikilinkPage = choice( [ + unescapedLiteralWithoutBar, + template + ] ); + + function pipedWikilink() { + var result = sequence( [ + wikilinkPage, + pipe, + expression + ] ); + return result === null ? null : [ result[0], result[2] ]; + } + + wikilinkContents = choice( [ + pipedWikilink, + wikilinkPage // unpiped link + ] ); + function link() { - var result, parsedResult; + var result, parsedResult, parsedLinkContents; result = null; + parsedResult = sequence( [ openLink, - expression, + wikilinkContents, closeLink ] ); if ( parsedResult !== null ) { - result = [ 'WLINK', parsedResult[1] ]; + parsedLinkContents = parsedResult[1]; + result = [ 'WLINK' ].concat( parsedLinkContents ); } return result; } @@ -389,7 +429,7 @@ // use a CONCAT operator if there are multiple nodes, otherwise return the first node, raw. return expr.length > 1 ? [ 'CONCAT' ].concat( expr ) : expr[0]; } - pipe = makeStringParser( '|' ); + function templateWithReplacement() { var result = sequence( [ templateName, @@ -430,14 +470,6 @@ ] ); openTemplate = makeStringParser('{{'); closeTemplate = makeStringParser('}}'); - function template() { - var result = sequence( [ - openTemplate, - templateContents, - closeTemplate - ] ); - return result === null ? null : result[1]; - } nonWhitespaceExpression = choice( [ template, link, @@ -454,6 +486,7 @@ replacement, literalWithoutBar ] ); + expression = choice( [ template, link, @@ -462,6 +495,7 @@ replacement, literal ] ); + function start() { var result = nOrMore( 0, expression )(); if ( result === null ) { @@ -594,11 +628,41 @@ /** * Transform wiki-link - * TODO unimplemented + * + * TODO: + * It only handles basic cases, either no pipe, or a pipe with an explicit + * anchor. + * + * It does not attempt to handle features like the pipe trick. + * However, the pipe trick should usually not be present in wikitext retrieved + * from the server, since the replacement is done at save time. + * It may, though, if the wikitext appears in extension-controlled content. + * * @param nodes */ - wlink: function () { - return 'unimplemented'; + wlink: function ( nodes ) { + var page, anchor, url; + + page = nodes[0]; + url = mw.util.wikiGetlink( page ); + + // [[Some Page]] or [[Namespace:Some Page]] + if ( nodes.length === 1 ) { + anchor = page; + } + + /* + * [[Some Page|anchor text]] or + * [[Namespace:Some Page|anchor] + */ + else { + anchor = nodes[1]; + } + + return $( '' ).attr( { + title: page, + href: url + } ).text( anchor ); }, /** @@ -695,6 +759,16 @@ var form = nodes[0], word = nodes[1]; return word && form && this.language.convertGrammar( word, form ); + }, + + /** + * Tranform parsed structure into a int: (interface language) message include + * Invoked by putting {{MediaWiki:othermessage}} into a message + * @param {Array} of nodes + * @return {string} Other message + */ + int: function ( nodes ) { + return mw.jqueryMsg.getMessageFunction()( nodes[0].toLowerCase() ); } }; // Deprecated! don't rely on gM existing. @@ -711,7 +785,7 @@ // Caching is somewhat problematic, because we do need different message functions for different maps, so // we'd have to cache the parser as a member of this.map, which sounds a bit ugly. // Do not use mw.jqueryMsg unless required - if ( this.map.get( this.key ).indexOf( '{{' ) < 0 ) { + if ( !/\{\{|\[/.test(this.map.get( this.key ) ) ) { // Fall back to mw.msg's simple parser return oldParser.apply( this ); } diff --git a/resources/mediawiki/mediawiki.jqueryMsg.peg b/resources/mediawiki/mediawiki.jqueryMsg.peg index e059ed1d9e..7879d6fa22 100644 --- a/resources/mediawiki/mediawiki.jqueryMsg.peg +++ b/resources/mediawiki/mediawiki.jqueryMsg.peg @@ -37,6 +37,7 @@ templateParam templateName = tn:[A-Za-z_]+ { return tn.join('').toUpperCase() } +/* TODO: Update to reflect separate piped and unpiped handling */ link = "[[" w:expression "]]" { return [ 'WLINK', w ]; } diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js index bce7bd7106..29e94a59e9 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js @@ -1,16 +1,27 @@ ( function ( mw, $ ) { +var mwLanguageCache = {}, oldGetOuterHtml; + QUnit.module( 'mediawiki.jqueryMsg', QUnit.newMwEnvironment( { setup: function () { this.orgMwLangauge = mw.language; mw.language = $.extend( true, {}, this.orgMwLangauge ); + oldGetOuterHtml = $.fn.getOuterHtml; + $.fn.getOuterHtml = function () { + var $div = $( '
' ), html; + $div.append( $( this ).eq( 0 ).clone() ); + html = $div.html(); + $div.empty(); + $div = undefined; + return html; + }; }, teardown: function () { mw.language = this.orgMwLangauge; + $.fn.getOuterHtml = oldGetOuterHtml; } }) ); -var mwLanguageCache = {}; function getMwLanguage( langCode, cb ) { if ( mwLanguageCache[langCode] !== undefined ) { mwLanguageCache[langCode].add( cb ); @@ -154,4 +165,168 @@ QUnit.test( 'Output matches PHP parser', mw.libs.phpParserData.tests.length, fun } ); }); +QUnit.test( 'Links', 6, function ( assert ) { + var parser = mw.jqueryMsg.getMessageFunction(), + expectedListUsers, + expectedDisambiguationsText, + expectedMultipleBars, + expectedSpecialCharacters, + specialCharactersPageName; + + /* + The below three are all identical to or based on real messages. For disambiguations-text, + the bold was removed because it is not yet implemented. + */ + + mw.messages.set( 'statistics-users', '注册[[Special:ListUsers|用户]]' ); + + expectedListUsers = '注册' + $( '' ).attr( { + title: 'Special:ListUsers', + href: mw.util.wikiGetlink( 'Special:ListUsers' ) + } ).text( '用户' ).getOuterHtml(); + + assert.equal( + parser( 'statistics-users' ), + expectedListUsers, + 'Piped wikilink' + ); + + expectedDisambiguationsText = 'The following pages contain at least one link to a disambiguation page.\nThey may have to link to a more appropriate page instead.
\nA page is treated as a disambiguation page if it uses a template that is linked from ' + + $( '
' ).attr( { + title: 'MediaWiki:Disambiguationspage', + href: mw.util.wikiGetlink( 'MediaWiki:Disambiguationspage' ) + } ).text( 'MediaWiki:Disambiguationspage' ).getOuterHtml() + '.'; + mw.messages.set( 'disambiguations-text', 'The following pages contain at least one link to a disambiguation page.\nThey may have to link to a more appropriate page instead.
\nA page is treated as a disambiguation page if it uses a template that is linked from [[MediaWiki:Disambiguationspage]].' ); + assert.equal( + parser( 'disambiguations-text' ), + expectedDisambiguationsText, + 'Wikilink without pipe' + ); + + mw.messages.set( 'version-entrypoints-index-php', '[https://www.mediawiki.org/wiki/Manual:index.php index.php]' ); + assert.equal( + parser( 'version-entrypoints-index-php' ), + '
index.php', + 'External link' + ); + + // Pipe trick is not supported currently, but should not parse as text either. + mw.messages.set( 'pipe-trick', '[[Tampa, Florida|]]' ); + assert.equal( + parser( 'pipe-trick' ), + 'Error: Parse error at position 0 in input: [[Tampa, Florida|]]', + 'Pipe trick should return error string.' + ); + + expectedMultipleBars = $( '' ).attr( { + title: 'Main Page', + href: mw.util.wikiGetlink( 'Main Page' ) + } ).text( 'Main|Page' ).getOuterHtml(); + mw.messages.set( 'multiple-bars', '[[Main Page|Main|Page]]' ); + assert.equal( + parser( 'multiple-bars' ), + expectedMultipleBars, + 'Bar in anchor' + ); + + specialCharactersPageName = '"Who" wants to be a millionaire & live on \'Exotic Island\'?'; + expectedSpecialCharacters = $( '' ).attr( { + title: specialCharactersPageName, + href: mw.util.wikiGetlink( specialCharactersPageName ) + } ).text( specialCharactersPageName ).getOuterHtml(); + + mw.messages.set( 'special-characters', '[[' + specialCharactersPageName + ']]' ); + assert.equal( + parser( 'special-characters' ), + expectedSpecialCharacters, + 'Special characters' + ); +}); + +QUnit.test( 'Int magic word', 4, function ( assert ) { + var parser = mw.jqueryMsg.getMessageFunction(), + newarticletextSource = 'You have followed a link to a page that does not exist yet. To create the page, start typing in the box below (see the [[{{Int:Helppage}}|help page]] for more info). If you are here by mistake, click your browser\'s back button.', + expectedNewarticletext; + + mw.messages.set( 'helppage', 'Help:Contents' ); + + expectedNewarticletext = 'You have followed a link to a page that does not exist yet. To create the page, start typing in the box below (see the ' + + $( '' ).attr( { + title: mw.msg( 'helppage' ), + href: mw.util.wikiGetlink( mw.msg( 'helppage' ) ) + } ).text( 'help page' ).getOuterHtml() + ' for more info). If you are here by mistake, click your browser\'s back button.'; + + mw.messages.set( 'newarticletext', newarticletextSource ); + + assert.equal( + parser( 'newarticletext' ), + expectedNewarticletext, + 'Link with nested message' + ); + + mw.messages.set( 'portal-url', 'Project:Community portal' ); + mw.messages.set( 'see-portal-url', '{{Int:portal-url}} is an important community page.' ); + assert.equal( + parser( 'see-portal-url' ), + 'Project:Community portal is an important community page.', + 'Nested message' + ); + + mw.messages.set( 'newarticletext-lowercase', + newarticletextSource.replace( 'Int:Helppage', 'int:helppage' ) ); + + assert.equal( + parser( 'newarticletext-lowercase' ), + expectedNewarticletext, + 'Link with nested message, lowercase include' + ); + + mw.messages.set( 'uses-missing-int', '{{int:doesnt-exist}}' ); + + assert.equal( + parser( 'uses-missing-int' ), + '[doesnt-exist]', + 'int: where nested message does not exist' + ); +}); + +// Tests that getMessageFunction is used for messages with curly braces or square brackets, +// but not otherwise. +QUnit.test( 'Calls to mw.msg', 8, function ( assert ) { + // Should be + var map, oldGMF, outerCalled, innerCalled; + + map = new mw.Map(); + map.set( { + 'curly-brace': '{{int:message}}', + 'single-square-bracket': '[https://www.mediawiki.org/ MediaWiki]', + 'double-square-bracket': '[[Some page]]', + 'regular': 'Other message' + } ); + + oldGMF = mw.jqueryMsg.getMessageFunction; + + mw.jqueryMsg.getMessageFunction = function() { + outerCalled = true; + return function() { + innerCalled = true; + }; + }; + + function verifyGetMessageFunction( key, shouldCall ) { + outerCalled = false; + innerCalled = false; + ( new mw.Message( map, key ) ).parser(); + assert.strictEqual( outerCalled, shouldCall, 'Outer function called for ' + key ); + assert.strictEqual( innerCalled, shouldCall, 'Inner function called for ' + key ); + } + + verifyGetMessageFunction( 'curly-brace', true ); + verifyGetMessageFunction( 'single-square-bracket', true ); + verifyGetMessageFunction( 'double-square-bracket', true ); + verifyGetMessageFunction( 'regular', false ); + + mw.jqueryMsg.getMessageFunction = oldGMF; +} ); + }( mediaWiki, jQuery ) ); -- 2.20.1