Merge "Align image frame padding with core"
[mediawiki.git] / tests / qunit / suites / resources / mediawiki / mediawiki.jqueryMsg.test.js
blobbc4b253f977c9d7bcd76095af37431ebf2cdc26d
1 ( function ( mw, $ ) {
2         var mwLanguageCache = {}, formatText, formatParse, formatnumTests, specialCharactersPageName,
3                 expectedListUsers, expectedEntrypoints;
5         // When the expected result is the same in both modes
6         function assertBothModes( assert, parserArguments, expectedResult, assertMessage ) {
7                 assert.equal( formatText.apply( null, parserArguments ), expectedResult, assertMessage + ' when format is \'text\'' );
8                 assert.equal( formatParse.apply( null, parserArguments ), expectedResult, assertMessage + ' when format is \'parse\'' );
9         }
11         QUnit.module( 'mediawiki.jqueryMsg', QUnit.newMwEnvironment( {
12                 setup: function () {
13                         this.originalMwLanguage = mw.language;
15                         specialCharactersPageName = '"Who" wants to be a millionaire & live on \'Exotic Island\'?';
17                         expectedListUsers = '注册<a title="Special:ListUsers" href="/wiki/Special:ListUsers">用户</a>';
19                         expectedEntrypoints = '<a href="https://www.mediawiki.org/wiki/Manual:index.php">index.php</a>';
21                         formatText = mw.jqueryMsg.getMessageFunction( {
22                                 format: 'text'
23                         } );
25                         formatParse = mw.jqueryMsg.getMessageFunction( {
26                                 format: 'parse'
27                         } );
28                 },
29                 teardown: function () {
30                         mw.language = this.originalMwLanguage;
31                 },
32                 config: {
33                         wgArticlePath: '/wiki/$1'
34                 },
35                 // Messages that are reused in multiple tests
36                 messages: {
37                         // The values for gender are not significant,
38                         // what matters is which of the values is choosen by the parser
39                         'gender-msg': '$1: {{GENDER:$2|blue|pink|green}}',
40                         'gender-msg-currentuser': '{{GENDER:|blue|pink|green}}',
42                         'plural-msg': 'Found $1 {{PLURAL:$1|item|items}}',
44                         // Assume the grammar form grammar_case_foo is not valid in any language
45                         'grammar-msg': 'Przeszukaj {{GRAMMAR:grammar_case_foo|{{SITENAME}}}}',
47                         'formatnum-msg': '{{formatnum:$1}}',
49                         'portal-url': 'Project:Community portal',
50                         'see-portal-url': '{{Int:portal-url}} is an important community page.',
52                         'jquerymsg-test-statistics-users': '注册[[Special:ListUsers|用户]]',
54                         'jquerymsg-test-version-entrypoints-index-php': '[https://www.mediawiki.org/wiki/Manual:index.php index.php]',
56                         'external-link-replace': 'Foo [$1 bar]'
57                 }
58         } ) );
60         function getMwLanguage( langCode, cb ) {
61                 if ( mwLanguageCache[langCode] !== undefined ) {
62                         mwLanguageCache[langCode].add( cb );
63                         return;
64                 }
65                 mwLanguageCache[langCode] = $.Callbacks( 'once memory' );
66                 mwLanguageCache[langCode].add( cb );
67                 $.ajax( {
68                         url: mw.util.wikiScript( 'load' ),
69                         data: {
70                                 skin: mw.config.get( 'skin' ),
71                                 lang: langCode,
72                                 debug: mw.config.get( 'debug' ),
73                                 modules: [
74                                         'mediawiki.language.data',
75                                         'mediawiki.language'
76                                 ].join( '|' ),
77                                 only: 'scripts'
78                         },
79                         dataType: 'script'
80                 } ).done(function () {
81                                 mwLanguageCache[langCode].fire( mw.language );
82                         } ).fail( function () {
83                                 mwLanguageCache[langCode].fire( false );
84                         } );
85         }
87         QUnit.test( 'Replace', 9, function ( assert ) {
88                 mw.messages.set( 'simple', 'Foo $1 baz $2' );
90                 assert.equal( formatParse( 'simple' ), 'Foo $1 baz $2', 'Replacements with no substitutes' );
91                 assert.equal( formatParse( 'simple', 'bar' ), 'Foo bar baz $2', 'Replacements with less substitutes' );
92                 assert.equal( formatParse( 'simple', 'bar', 'quux' ), 'Foo bar baz quux', 'Replacements with all substitutes' );
94                 mw.messages.set( 'plain-input', '<foo foo="foo">x$1y&lt;</foo>z' );
96                 assert.equal(
97                         formatParse( 'plain-input', 'bar' ),
98                         '&lt;foo foo="foo"&gt;xbary&amp;lt;&lt;/foo&gt;z',
99                         'Input is not considered html'
100                 );
102                 mw.messages.set( 'plain-replace', 'Foo $1' );
104                 assert.equal(
105                         formatParse( 'plain-replace', '<bar bar="bar">&gt;</bar>' ),
106                         'Foo &lt;bar bar="bar"&gt;&amp;gt;&lt;/bar&gt;',
107                         'Replacement is not considered html'
108                 );
110                 mw.messages.set( 'object-replace', 'Foo $1' );
112                 assert.equal(
113                         formatParse( 'object-replace', $( '<div class="bar">&gt;</div>' ) ),
114                         'Foo <div class="bar">&gt;</div>',
115                         'jQuery objects are preserved as raw html'
116                 );
118                 assert.equal(
119                         formatParse( 'object-replace', $( '<div class="bar">&gt;</div>' ).get( 0 ) ),
120                         'Foo <div class="bar">&gt;</div>',
121                         'HTMLElement objects are preserved as raw html'
122                 );
124                 assert.equal(
125                         formatParse( 'object-replace', $( '<div class="bar">&gt;</div>' ).toArray() ),
126                         'Foo <div class="bar">&gt;</div>',
127                         'HTMLElement[] arrays are preserved as raw html'
128                 );
130                 assert.equal(
131                         formatParse( 'external-link-replace', 'http://example.org/?x=y&z' ),
132                         'Foo <a href="http://example.org/?x=y&amp;z">bar</a>',
133                         'Href is not double-escaped in wikilink function'
134                 );
135         } );
137         QUnit.test( 'Plural', 3, function ( assert ) {
138                 assert.equal( formatParse( 'plural-msg', 0 ), 'Found 0 items', 'Plural test for english with zero as count' );
139                 assert.equal( formatParse( 'plural-msg', 1 ), 'Found 1 item', 'Singular test for english' );
140                 assert.equal( formatParse( 'plural-msg', 2 ), 'Found 2 items', 'Plural test for english' );
141         } );
143         QUnit.test( 'Gender', 15, function ( assert ) {
144                 var originalGender = mw.user.options.get( 'gender' );
146                 // TODO: These tests should be for mw.msg once mw.msg integrated with mw.jqueryMsg
147                 // TODO: English may not be the best language for these tests. Use a language like Arabic or Russian
148                 mw.user.options.set( 'gender', 'male' );
149                 assert.equal(
150                         formatParse( 'gender-msg', 'Bob', 'male' ),
151                         'Bob: blue',
152                         'Masculine from string "male"'
153                 );
154                 assert.equal(
155                         formatParse( 'gender-msg', 'Bob', mw.user ),
156                         'Bob: blue',
157                         'Masculine from mw.user object'
158                 );
159                 assert.equal(
160                         formatParse( 'gender-msg-currentuser' ),
161                         'blue',
162                         'Masculine for current user'
163                 );
165                 mw.user.options.set( 'gender', 'female' );
166                 assert.equal(
167                         formatParse( 'gender-msg', 'Alice', 'female' ),
168                         'Alice: pink',
169                         'Feminine from string "female"' );
170                 assert.equal(
171                         formatParse( 'gender-msg', 'Alice', mw.user ),
172                         'Alice: pink',
173                         'Feminine from mw.user object'
174                 );
175                 assert.equal(
176                         formatParse( 'gender-msg-currentuser' ),
177                         'pink',
178                         'Feminine for current user'
179                 );
181                 mw.user.options.set( 'gender', 'unknown' );
182                 assert.equal(
183                         formatParse( 'gender-msg', 'Foo', mw.user ),
184                         'Foo: green',
185                         'Neutral from mw.user object' );
186                 assert.equal(
187                         formatParse( 'gender-msg', 'User' ),
188                         'User: green',
189                         'Neutral when no parameter given' );
190                 assert.equal(
191                         formatParse( 'gender-msg', 'User', 'unknown' ),
192                         'User: green',
193                         'Neutral from string "unknown"'
194                 );
195                 assert.equal(
196                         formatParse( 'gender-msg-currentuser' ),
197                         'green',
198                         'Neutral for current user'
199                 );
201                 mw.messages.set( 'gender-msg-one-form', '{{GENDER:$1|User}}: $2 {{PLURAL:$2|edit|edits}}' );
203                 assert.equal(
204                         formatParse( 'gender-msg-one-form', 'male', 10 ),
205                         'User: 10 edits',
206                         'Gender neutral and plural form'
207                 );
208                 assert.equal(
209                         formatParse( 'gender-msg-one-form', 'female', 1 ),
210                         'User: 1 edit',
211                         'Gender neutral and singular form'
212                 );
214                 mw.messages.set( 'gender-msg-lowercase', '{{gender:$1|he|she}} is awesome' );
215                 assert.equal(
216                         formatParse( 'gender-msg-lowercase', 'male' ),
217                         'he is awesome',
218                         'Gender masculine'
219                 );
220                 assert.equal(
221                         formatParse( 'gender-msg-lowercase', 'female' ),
222                         'she is awesome',
223                         'Gender feminine'
224                 );
226                 mw.messages.set( 'gender-msg-wrong', '{{gender}} test' );
227                 assert.equal(
228                         formatParse( 'gender-msg-wrong', 'female' ),
229                         ' test',
230                         'Invalid syntax should result in {{gender}} simply being stripped away'
231                 );
233                 mw.user.options.set( 'gender', originalGender );
234         } );
236         QUnit.test( 'Grammar', 2, function ( assert ) {
237                 assert.equal( formatParse( 'grammar-msg' ), 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'Grammar Test with sitename' );
239                 mw.messages.set( 'grammar-msg-wrong-syntax', 'Przeszukaj {{GRAMMAR:grammar_case_xyz}}' );
240                 assert.equal( formatParse( 'grammar-msg-wrong-syntax' ), 'Przeszukaj ', 'Grammar Test with wrong grammar template syntax' );
241         } );
243         QUnit.test( 'Match PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) {
244                 mw.messages.set( mw.libs.phpParserData.messages );
245                 $.each( mw.libs.phpParserData.tests, function ( i, test ) {
246                         QUnit.stop();
247                         getMwLanguage( test.lang, function ( langClass ) {
248                                 QUnit.start();
249                                 if ( !langClass ) {
250                                         assert.ok( false, 'Language "' + test.lang + '" failed to load' );
251                                         return;
252                                 }
253                                 mw.config.set( 'wgUserLanguage', test.lang );
254                                 var parser = new mw.jqueryMsg.parser( { language: langClass } );
255                                 assert.equal(
256                                         parser.parse( test.key, test.args ).html(),
257                                         test.result,
258                                         test.name
259                                 );
260                         } );
261                 } );
262         } );
264         QUnit.test( 'Links', 6, function ( assert ) {
265                 var expectedDisambiguationsText,
266                         expectedMultipleBars,
267                         expectedSpecialCharacters;
269                 // The below three are all identical to or based on real messages.  For disambiguations-text,
270                 // the bold was removed because it is not yet implemented.
272                 assert.htmlEqual(
273                         formatParse( 'jquerymsg-test-statistics-users' ),
274                         expectedListUsers,
275                         'Piped wikilink'
276                 );
278                 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 ' +
279                         '<a title="MediaWiki:Disambiguationspage" href="/wiki/MediaWiki:Disambiguationspage">MediaWiki:Disambiguationspage</a>.';
281                 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]].' );
282                 assert.htmlEqual(
283                         formatParse( 'disambiguations-text' ),
284                         expectedDisambiguationsText,
285                         'Wikilink without pipe'
286                 );
288                 assert.htmlEqual(
289                         formatParse( 'jquerymsg-test-version-entrypoints-index-php' ),
290                         expectedEntrypoints,
291                         'External link'
292                 );
294                 // Pipe trick is not supported currently, but should not parse as text either.
295                 mw.messages.set( 'pipe-trick', '[[Tampa, Florida|]]' );
296                 this.suppressWarnings();
297                 assert.equal(
298                         formatParse( 'pipe-trick' ),
299                         '[[Tampa, Florida|]]',
300                         'Pipe trick should not be parsed.'
301                 );
302                 this.restoreWarnings();
304                 expectedMultipleBars = '<a title="Main Page" href="/wiki/Main_Page">Main|Page</a>';
305                 mw.messages.set( 'multiple-bars', '[[Main Page|Main|Page]]' );
306                 assert.htmlEqual(
307                         formatParse( 'multiple-bars' ),
308                         expectedMultipleBars,
309                         'Bar in anchor'
310                 );
312                 expectedSpecialCharacters = '<a title="&quot;Who&quot; wants to be a millionaire &amp; live on &#039;Exotic Island&#039;?" href="/wiki/%22Who%22_wants_to_be_a_millionaire_%26_live_on_%27Exotic_Island%27%3F">&quot;Who&quot; wants to be a millionaire &amp; live on &#039;Exotic Island&#039;?</a>';
314                 mw.messages.set( 'special-characters', '[[' + specialCharactersPageName + ']]' );
315                 assert.htmlEqual(
316                         formatParse( 'special-characters' ),
317                         expectedSpecialCharacters,
318                         'Special characters'
319                 );
320         } );
322 // Tests that {{-transformation vs. general parsing are done as requested
323         QUnit.test( 'Curly brace transformation', 14, function ( assert ) {
324                 var oldUserLang = mw.config.get( 'wgUserLanguage' );
326                 assertBothModes( assert, ['gender-msg', 'Bob', 'male'], 'Bob: blue', 'gender is resolved' );
328                 assertBothModes( assert, ['plural-msg', 5], 'Found 5 items', 'plural is resolved' );
330                 assertBothModes( assert, ['grammar-msg'], 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'grammar is resolved' );
332                 mw.config.set( 'wgUserLanguage', 'en' );
333                 assertBothModes( assert, ['formatnum-msg', '987654321.654321'], '987,654,321.654', 'formatnum is resolved' );
335                 // Test non-{{ wikitext, where behavior differs
337                 // Wikilink
338                 assert.equal(
339                         formatText( 'jquerymsg-test-statistics-users' ),
340                         mw.messages.get( 'jquerymsg-test-statistics-users' ),
341                         'Internal link message unchanged when format is \'text\''
342                 );
343                 assert.htmlEqual(
344                         formatParse( 'jquerymsg-test-statistics-users' ),
345                         expectedListUsers,
346                         'Internal link message parsed when format is \'parse\''
347                 );
349                 // External link
350                 assert.equal(
351                         formatText( 'jquerymsg-test-version-entrypoints-index-php' ),
352                         mw.messages.get( 'jquerymsg-test-version-entrypoints-index-php' ),
353                         'External link message unchanged when format is \'text\''
354                 );
355                 assert.htmlEqual(
356                         formatParse( 'jquerymsg-test-version-entrypoints-index-php' ),
357                         expectedEntrypoints,
358                         'External link message processed when format is \'parse\''
359                 );
361                 // External link with parameter
362                 assert.equal(
363                         formatText( 'external-link-replace', 'http://example.com' ),
364                         'Foo [http://example.com bar]',
365                         'External link message only substitutes parameter when format is \'text\''
366                 );
367                 assert.htmlEqual(
368                         formatParse( 'external-link-replace', 'http://example.com' ),
369                         'Foo <a href="http://example.com">bar</a>',
370                         'External link message processed when format is \'parse\''
371                 );
373                 mw.config.set( 'wgUserLanguage', oldUserLang );
374         } );
376         QUnit.test( 'Int', 4, function ( assert ) {
377                 var 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:Foobar}}|foobar]] for more info). If you are here by mistake, click your browser\'s back button.',
378                         expectedNewarticletext,
379                         helpPageTitle = 'Help:Foobar';
381                 mw.messages.set( 'foobar', helpPageTitle );
383                 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 ' +
384                         '<a title="Help:Foobar" href="/wiki/Help:Foobar">foobar</a> for more info). If you are here by mistake, click your browser\'s back button.';
386                 mw.messages.set( 'newarticletext', newarticletextSource );
388                 assert.htmlEqual(
389                         formatParse( 'newarticletext' ),
390                         expectedNewarticletext,
391                         'Link with nested message'
392                 );
394                 assert.equal(
395                         formatParse( 'see-portal-url' ),
396                         'Project:Community portal is an important community page.',
397                         'Nested message'
398                 );
400                 mw.messages.set( 'newarticletext-lowercase',
401                         newarticletextSource.replace( 'Int:Helppage', 'int:helppage' ) );
403                 assert.htmlEqual(
404                         formatParse( 'newarticletext-lowercase' ),
405                         expectedNewarticletext,
406                         'Link with nested message, lowercase include'
407                 );
409                 mw.messages.set( 'uses-missing-int', '{{int:doesnt-exist}}' );
411                 assert.equal(
412                         formatParse( 'uses-missing-int' ),
413                         '[doesnt-exist]',
414                         'int: where nested message does not exist'
415                 );
416         } );
418 // Tests that getMessageFunction is used for non-plain messages with curly braces or
419 // square brackets, but not otherwise.
420         QUnit.test( 'mw.Message.prototype.parser monkey-patch', 22, function ( assert ) {
421                 var oldGMF, outerCalled, innerCalled;
423                 mw.messages.set( {
424                         'curly-brace': '{{int:message}}',
425                         'single-square-bracket': '[https://www.mediawiki.org/ MediaWiki]',
426                         'double-square-bracket': '[[Some page]]',
427                         'regular': 'Other message'
428                 } );
430                 oldGMF = mw.jqueryMsg.getMessageFunction;
432                 mw.jqueryMsg.getMessageFunction = function () {
433                         outerCalled = true;
434                         return function () {
435                                 innerCalled = true;
436                         };
437                 };
439                 function verifyGetMessageFunction( key, format, shouldCall ) {
440                         var message;
441                         outerCalled = false;
442                         innerCalled = false;
443                         message = mw.message( key );
444                         message[format]();
445                         assert.strictEqual( outerCalled, shouldCall, 'Outer function called for ' + key );
446                         assert.strictEqual( innerCalled, shouldCall, 'Inner function called for ' + key );
447                 }
449                 verifyGetMessageFunction( 'curly-brace', 'parse', true );
450                 verifyGetMessageFunction( 'curly-brace', 'plain', false );
452                 verifyGetMessageFunction( 'single-square-bracket', 'parse', true );
453                 verifyGetMessageFunction( 'single-square-bracket', 'plain', false );
455                 verifyGetMessageFunction( 'double-square-bracket', 'parse', true );
456                 verifyGetMessageFunction( 'double-square-bracket', 'plain', false );
458                 verifyGetMessageFunction( 'regular', 'parse', false );
459                 verifyGetMessageFunction( 'regular', 'plain', false );
461                 verifyGetMessageFunction( 'jquerymsg-test-pagetriage-del-talk-page-notify-summary', 'plain', false );
462                 verifyGetMessageFunction( 'jquerymsg-test-categorytree-collapse-bullet', 'plain', false );
463                 verifyGetMessageFunction( 'jquerymsg-test-wikieditor-toolbar-help-content-signature-result', 'plain', false );
465                 mw.jqueryMsg.getMessageFunction = oldGMF;
466         } );
468 formatnumTests = [
469         {
470                 lang: 'en',
471                 number: 987654321.654321,
472                 result: '987,654,321.654',
473                 description: 'formatnum test for English, decimal seperator'
474         },
475         {
476                 lang: 'ar',
477                 number: 987654321.654321,
478                 result: '٩٨٧٬٦٥٤٬٣٢١٫٦٥٤',
479                 description: 'formatnum test for Arabic, with decimal seperator'
480         },
481         {
482                 lang: 'ar',
483                 number: '٩٨٧٦٥٤٣٢١٫٦٥٤٣٢١',
484                 result: 987654321,
485                 integer: true,
486                 description: 'formatnum test for Arabic, with decimal seperator, reverse'
487         },
488         {
489                 lang: 'ar',
490                 number: -12.89,
491                 result: '-١٢٫٨٩',
492                 description: 'formatnum test for Arabic, negative number'
493         },
494         {
495                 lang: 'ar',
496                 number: '-١٢٫٨٩',
497                 result: -12,
498                 integer: true,
499                 description: 'formatnum test for Arabic, negative number, reverse'
500         },
501         {
502                 lang: 'nl',
503                 number: 987654321.654321,
504                 result: '987.654.321,654',
505                 description: 'formatnum test for Nederlands, decimal seperator'
506         },
507         {
508                 lang: 'nl',
509                 number: -12.89,
510                 result: '-12,89',
511                 description: 'formatnum test for Nederlands, negative number'
512         },
513         {
514                 lang: 'nl',
515                 number: '.89',
516                 result: '0,89',
517                 description: 'formatnum test for Nederlands'
518         },
519         {
520                 lang: 'nl',
521                 number: 'invalidnumber',
522                 result: 'invalidnumber',
523                 description: 'formatnum test for Nederlands, invalid number'
524         },
525         {
526                 lang: 'ml',
527                 number: '1000000000',
528                 result: '1,00,00,00,000',
529                 description: 'formatnum test for Malayalam'
530         },
531         {
532                 lang: 'ml',
533                 number: '-1000000000',
534                 result: '-1,00,00,00,000',
535                 description: 'formatnum test for Malayalam, negative number'
536         },
537         /*
538          * This will fail because of wrong pattern for ml in MW(different from CLDR)
539         {
540                 lang: 'ml',
541                 number: '1000000000.000',
542                 result: '1,00,00,00,000.000',
543                 description: 'formatnum test for Malayalam with decimal place'
544         },
545         */
546         {
547                 lang: 'hi',
548                 number: '123456789.123456789',
549                 result: '१२,३४,५६,७८९',
550                 description: 'formatnum test for Hindi'
551         },
552         {
553                 lang: 'hi',
554                 number: '१२,३४,५६,७८९',
555                 result: '१२,३४,५६,७८९',
556                 description: 'formatnum test for Hindi, Devanagari digits passed'
557         },
558         {
559                 lang: 'hi',
560                 number: '१२३४५६,७८९',
561                 result: '123456',
562                 integer: true,
563                 description: 'formatnum test for Hindi, Devanagari digits passed to get integer value'
564         }
567 QUnit.test( 'formatnum', formatnumTests.length, function ( assert ) {
568         mw.messages.set( 'formatnum-msg', '{{formatnum:$1}}' );
569         mw.messages.set( 'formatnum-msg-int', '{{formatnum:$1|R}}' );
570         $.each( formatnumTests, function ( i, test ) {
571                 QUnit.stop();
572                 getMwLanguage( test.lang, function ( langClass ) {
573                         QUnit.start();
574                         if ( !langClass ) {
575                                 assert.ok( false, 'Language "' + test.lang + '" failed to load' );
576                                 return;
577                         }
578                         mw.messages.set(test.message );
579                         mw.config.set( 'wgUserLanguage', test.lang );
580                         var parser = new mw.jqueryMsg.parser( { language: langClass } );
581                         assert.equal(
582                                 parser.parse( test.integer ? 'formatnum-msg-int' : 'formatnum-msg',
583                                         [ test.number ] ).html(),
584                                 test.result,
585                                 test.description
586                         );
587                 } );
588         } );
589 } );
591 // HTML in wikitext
592 QUnit.test( 'HTML', 26, function ( assert ) {
593         mw.messages.set( 'jquerymsg-italics-msg', '<i>Very</i> important' );
595         assertBothModes( assert, ['jquerymsg-italics-msg'], mw.messages.get( 'jquerymsg-italics-msg' ), 'Simple italics unchanged' );
597         mw.messages.set( 'jquerymsg-bold-msg', '<b>Strong</b> speaker' );
598         assertBothModes( assert, ['jquerymsg-bold-msg'], mw.messages.get( 'jquerymsg-bold-msg' ), 'Simple bold unchanged' );
600         mw.messages.set( 'jquerymsg-bold-italics-msg', 'It is <b><i>key</i></b>' );
601         assertBothModes( assert, ['jquerymsg-bold-italics-msg'], mw.messages.get( 'jquerymsg-bold-italics-msg' ), 'Bold and italics nesting order preserved' );
603         mw.messages.set( 'jquerymsg-italics-bold-msg', 'It is <i><b>vital</b></i>' );
604         assertBothModes( assert, ['jquerymsg-italics-bold-msg'], mw.messages.get( 'jquerymsg-italics-bold-msg' ), 'Italics and bold nesting order preserved' );
606         mw.messages.set( 'jquerymsg-italics-with-link', 'An <i>italicized [[link|wiki-link]]</i>' );
608         assert.htmlEqual(
609                 formatParse( 'jquerymsg-italics-with-link' ),
610                 'An <i>italicized <a title="link" href="' + mw.html.escape( mw.util.getUrl( 'link' ) ) + '">wiki-link</i>',
611                 'Italics with link inside in parse mode'
612         );
614         assert.equal(
615                 formatText( 'jquerymsg-italics-with-link' ),
616                 mw.messages.get( 'jquerymsg-italics-with-link' ),
617                 'Italics with link unchanged in text mode'
618         );
620         mw.messages.set( 'jquerymsg-italics-id-class', '<i id="foo" class="bar">Foo</i>' );
621         assert.htmlEqual(
622                 formatParse( 'jquerymsg-italics-id-class' ),
623                 mw.messages.get( 'jquerymsg-italics-id-class' ),
624                 'ID and class are allowed'
625         );
627         mw.messages.set( 'jquerymsg-italics-onclick', '<i onclick="alert(\'foo\')">Foo</i>' );
628         assert.htmlEqual(
629                 formatParse( 'jquerymsg-italics-onclick' ),
630                 '&lt;i onclick=&quot;alert(\'foo\')&quot;&gt;Foo&lt;/i&gt;',
631                 'element with onclick is escaped because it is not allowed'
632         );
634         mw.messages.set( 'jquerymsg-script-msg', '<script  >alert( "Who put this tag here?" );</script>' );
635         assert.htmlEqual(
636                 formatParse( 'jquerymsg-script-msg' ),
637                 '&lt;script  &gt;alert( &quot;Who put this tag here?&quot; );&lt;/script&gt;',
638                 'Tag outside whitelist escaped in parse mode'
639         );
641         assert.equal(
642                 formatText( 'jquerymsg-script-msg' ),
643                 mw.messages.get( 'jquerymsg-script-msg' ),
644                 'Tag outside whitelist unchanged in text mode'
645         );
647         mw.messages.set( 'jquerymsg-script-link-msg', '<script>[[Foo|bar]]</script>' );
648         assert.htmlEqual(
649                 formatParse( 'jquerymsg-script-link-msg' ),
650                 '&lt;script&gt;<a title="Foo" href="' + mw.html.escape( mw.util.getUrl( 'Foo' ) ) + '">bar</a>&lt;/script&gt;',
651                 'Script tag text is escaped because that element is not allowed, but link inside is still HTML'
652         );
654         mw.messages.set( 'jquerymsg-mismatched-html', '<i class="important">test</b>' );
655         assert.htmlEqual(
656                 formatParse( 'jquerymsg-mismatched-html' ),
657                 '&lt;i class=&quot;important&quot;&gt;test&lt;/b&gt;',
658                 'Mismatched HTML start and end tag treated as text'
659         );
661         // TODO (mattflaschen, 2013-03-18): It's not a security issue, but there's no real
662         // reason the htmlEmitter span needs to be here. It's an artifact of how emitting works.
663         mw.messages.set( 'jquerymsg-script-and-external-link', '<script>alert( "jquerymsg-script-and-external-link test" );</script> [http://example.com <i>Foo</i> bar]' );
664         assert.htmlEqual(
665                 formatParse( 'jquerymsg-script-and-external-link' ),
666                 '&lt;script&gt;alert( "jquerymsg-script-and-external-link test" );&lt;/script&gt; <a href="http://example.com"><span class="mediaWiki_htmlEmitter"><i>Foo</i> bar</span></a>',
667                 'HTML tags in external links not interfering with escaping of other tags'
668         );
670         mw.messages.set( 'jquerymsg-link-script', '[http://example.com <script>alert( "jquerymsg-link-script test" );</script>]' );
671         assert.htmlEqual(
672                 formatParse( 'jquerymsg-link-script' ),
673                 '<a href="http://example.com"><span class="mediaWiki_htmlEmitter">&lt;script&gt;alert( "jquerymsg-link-script test" );&lt;/script&gt;</span></a>',
674                 'Non-whitelisted HTML tag in external link anchor treated as text'
675         );
677         // Intentionally not using htmlEqual for the quote tests
678         mw.messages.set( 'jquerymsg-double-quotes-preserved', '<i id="double">Double</i>' );
679         assert.equal(
680                 formatParse( 'jquerymsg-double-quotes-preserved' ),
681                 mw.messages.get( 'jquerymsg-double-quotes-preserved' ),
682                 'Attributes with double quotes are preserved as such'
683         );
685         mw.messages.set( 'jquerymsg-single-quotes-normalized-to-double', '<i id=\'single\'>Single</i>' );
686         assert.equal(
687                 formatParse( 'jquerymsg-single-quotes-normalized-to-double' ),
688                 '<i id="single">Single</i>',
689                 'Attributes with single quotes are normalized to double'
690         );
692         mw.messages.set( 'jquerymsg-escaped-double-quotes-attribute', '<i style="font-family:&quot;Arial&quot;">Styled</i>' );
693         assert.htmlEqual(
694                 formatParse( 'jquerymsg-escaped-double-quotes-attribute' ),
695                 mw.messages.get( 'jquerymsg-escaped-double-quotes-attribute' ),
696                 'Escaped attributes are parsed correctly'
697         );
699         mw.messages.set( 'jquerymsg-escaped-single-quotes-attribute', '<i style=\'font-family:&#039;Arial&#039;\'>Styled</i>' );
700         assert.htmlEqual(
701                 formatParse( 'jquerymsg-escaped-single-quotes-attribute' ),
702                 mw.messages.get( 'jquerymsg-escaped-single-quotes-attribute' ),
703                 'Escaped attributes are parsed correctly'
704         );
706         mw.messages.set( 'jquerymsg-wikitext-contents-parsed', '<i>[http://example.com Example]</i>' );
707         assert.htmlEqual(
708                 formatParse( 'jquerymsg-wikitext-contents-parsed' ),
709                 '<i><a href="http://example.com">Example</a></i>',
710                 'Contents of valid tag are treated as wikitext, so external link is parsed'
711         );
713         mw.messages.set( 'jquerymsg-wikitext-contents-script', '<i><script>Script inside</script></i>' );
714         assert.htmlEqual(
715                 formatParse( 'jquerymsg-wikitext-contents-script' ),
716                 '<i><span class="mediaWiki_htmlEmitter">&lt;script&gt;Script inside&lt;/script&gt;</span></i>',
717                 'Contents of valid tag are treated as wikitext, so invalid HTML element is treated as text'
718         );
720         mw.messages.set( 'jquerymsg-unclosed-tag', 'Foo<tag>bar' );
721         assert.htmlEqual(
722                 formatParse( 'jquerymsg-unclosed-tag' ),
723                 'Foo&lt;tag&gt;bar',
724                 'Nonsupported unclosed tags are escaped'
725         );
727         mw.messages.set( 'jquerymsg-self-closing-tag', 'Foo<tag/>bar' );
728         assert.htmlEqual(
729                 formatParse( 'jquerymsg-self-closing-tag' ),
730                 'Foo&lt;tag/&gt;bar',
731                 'Self-closing tags don\'t cause a parse error'
732         );
733 } );
735         QUnit.test( 'Behavior in case of invalid wikitext', 3, function ( assert ) {
736                 mw.messages.set( 'invalid-wikitext', '<b>{{FAIL}}</b>' );
738                 this.suppressWarnings();
739                 var logSpy = this.sandbox.spy( mw.log, 'warn' );
741                 assert.equal(
742                         formatParse( 'invalid-wikitext' ),
743                         '&lt;b&gt;{{FAIL}}&lt;/b&gt;',
744                         'Invalid wikitext: \'parse\' format'
745                 );
747                 assert.equal(
748                         formatText( 'invalid-wikitext' ),
749                         '<b>{{FAIL}}</b>',
750                         'Invalid wikitext: \'text\' format'
751                 );
753                 assert.equal( logSpy.callCount, 2, 'mw.log.warn calls' );
754         } );
756 }( mediaWiki, jQuery ) );