1 /* spec for language & message behaviour in MediaWiki */
5 "en_simple": "Simple message",
6 "en_replace": "Simple $1 replacement",
7 "en_replace2": "Simple $1 $2 replacements",
8 "en_link": "Simple [http://example.com link to example].",
9 "en_link_replace": "Complex [$1 $2] behaviour.",
10 "en_simple_magic": "Simple {{ALOHOMORA}} message",
11 "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
12 "en_undelete_empty_param": "Undelete{{PLURAL:$1|| multiple edits}}",
13 "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
14 "en_escape0": "Escape \\to fantasy island",
15 "en_escape1": "I had \\$2.50 in my pocket",
16 "en_escape2": "I had {{PLURAL:$1|the absolute \\|$1\\| which came out to \\$3.00 in my C:\\\\drive| some stuff}}",
17 "en_fail": "This should fail to {{parse",
18 "en_fail_magic": "There is no such magic word as {{SIETNAME}}",
19 "en_evil": "This has <script type='text/javascript'>window.en_evil = true;</script> tags"
25 ( function( mw, $, undefined ) {
27 describe( "mediaWiki.jqueryMsg", function() {
29 describe( "basic message functionality", function() {
31 it( "should return identity for empty string", function() {
32 var parser = new mw.jqueryMsg.parser();
33 expect( parser.parse( 'en_empty' ).html() ).toEqual( '' );
37 it( "should return identity for simple string", function() {
38 var parser = new mw.jqueryMsg.parser();
39 expect( parser.parse( 'en_simple' ).html() ).toEqual( 'Simple message' );
44 describe( "escaping", function() {
46 it ( "should handle simple escaping", function() {
47 var parser = new mw.jqueryMsg.parser();
48 expect( parser.parse( 'en_escape0' ).html() ).toEqual( 'Escape to fantasy island' );
51 it ( "should escape dollar signs found in ordinary text when backslashed", function() {
52 var parser = new mw.jqueryMsg.parser();
53 expect( parser.parse( 'en_escape1' ).html() ).toEqual( 'I had $2.50 in my pocket' );
56 it ( "should handle a complicated escaping case, including escaped pipe chars in template args", function() {
57 var parser = new mw.jqueryMsg.parser();
58 expect( parser.parse( 'en_escape2', [ 1 ] ).html() ).toEqual( 'I had the absolute |1| which came out to $3.00 in my C:\\drive' );
63 describe( "replacing", function() {
65 it ( "should handle simple replacing", function() {
66 var parser = new mw.jqueryMsg.parser();
67 expect( parser.parse( 'en_replace', [ 'foo' ] ).html() ).toEqual( 'Simple foo replacement' );
70 it ( "should return $n if replacement not there", function() {
71 var parser = new mw.jqueryMsg.parser();
72 expect( parser.parse( 'en_replace', [] ).html() ).toEqual( 'Simple $1 replacement' );
73 expect( parser.parse( 'en_replace2', [ 'bar' ] ).html() ).toEqual( 'Simple bar $2 replacements' );
78 describe( "linking", function() {
80 it ( "should handle a simple link", function() {
81 var parser = new mw.jqueryMsg.parser();
82 var parsed = parser.parse( 'en_link' );
83 var contents = parsed.contents();
84 expect( contents.length ).toEqual( 3 );
85 expect( contents[0].nodeName ).toEqual( '#text' );
86 expect( contents[0].nodeValue ).toEqual( 'Simple ' );
87 expect( contents[1].nodeName ).toEqual( 'A' );
88 expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com' );
89 expect( contents[1].childNodes[0].nodeValue ).toEqual( 'link to example' );
90 expect( contents[2].nodeName ).toEqual( '#text' );
91 expect( contents[2].nodeValue ).toEqual( '.' );
94 it ( "should replace a URL into a link", function() {
95 var parser = new mw.jqueryMsg.parser();
96 var parsed = parser.parse( 'en_link_replace', [ 'http://example.com/foo', 'linking' ] );
97 var contents = parsed.contents();
98 expect( contents.length ).toEqual( 3 );
99 expect( contents[0].nodeName ).toEqual( '#text' );
100 expect( contents[0].nodeValue ).toEqual( 'Complex ' );
101 expect( contents[1].nodeName ).toEqual( 'A' );
102 expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com/foo' );
103 expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' );
104 expect( contents[2].nodeName ).toEqual( '#text' );
105 expect( contents[2].nodeValue ).toEqual( ' behaviour.' );
108 it ( "should bind a click handler into a link", function() {
109 var parser = new mw.jqueryMsg.parser();
111 var click = function() { clicked = true; };
112 var parsed = parser.parse( 'en_link_replace', [ click, 'linking' ] );
113 var contents = parsed.contents();
114 expect( contents.length ).toEqual( 3 );
115 expect( contents[0].nodeName ).toEqual( '#text' );
116 expect( contents[0].nodeValue ).toEqual( 'Complex ' );
117 expect( contents[1].nodeName ).toEqual( 'A' );
118 expect( contents[1].getAttribute( 'href' ) ).toEqual( '#' );
119 expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' );
120 expect( contents[2].nodeName ).toEqual( '#text' );
121 expect( contents[2].nodeValue ).toEqual( ' behaviour.' );
122 // determining bindings is hard in IE
123 var anchor = parsed.find( 'a' );
124 if ( ( $.browser.mozilla || $.browser.webkit ) && anchor.click ) {
125 expect( clicked ).toEqual( false );
127 expect( clicked ).toEqual( true );
131 it ( "should wrap a jquery arg around link contents -- even another element", function() {
132 var parser = new mw.jqueryMsg.parser();
134 var click = function() { clicked = true; };
135 var button = $( '<button>' ).click( click );
136 var parsed = parser.parse( 'en_link_replace', [ button, 'buttoning' ] );
137 var contents = parsed.contents();
138 expect( contents.length ).toEqual( 3 );
139 expect( contents[0].nodeName ).toEqual( '#text' );
140 expect( contents[0].nodeValue ).toEqual( 'Complex ' );
141 expect( contents[1].nodeName ).toEqual( 'BUTTON' );
142 expect( contents[1].childNodes[0].nodeValue ).toEqual( 'buttoning' );
143 expect( contents[2].nodeName ).toEqual( '#text' );
144 expect( contents[2].nodeValue ).toEqual( ' behaviour.' );
145 // determining bindings is hard in IE
146 if ( ( $.browser.mozilla || $.browser.webkit ) && button.click ) {
147 expect( clicked ).toEqual( false );
148 parsed.find( 'button' ).click();
149 expect( clicked ).toEqual( true );
157 describe( "magic keywords", function() {
158 it( "should substitute magic keywords", function() {
164 var parser = new mw.jqueryMsg.parser( options );
165 expect( parser.parse( 'en_simple_magic' ).html() ).toEqual( 'Simple open message' );
169 describe( "error conditions", function() {
170 it( "should return non-existent key in square brackets", function() {
171 var parser = new mw.jqueryMsg.parser();
172 expect( parser.parse( 'en_does_not_exist' ).html() ).toEqual( '[en_does_not_exist]' );
176 it( "should fail to parse", function() {
177 var parser = new mw.jqueryMsg.parser();
178 expect( function() { parser.parse( 'en_fail' ); } ).toThrow(
179 'Parse error at position 20 in input: This should fail to {{parse'
184 describe( "empty parameters", function() {
185 it( "should deal with empty parameters", function() {
186 var parser = new mw.jqueryMsg.parser();
187 var ast = parser.getAst( 'en_undelete_empty_param' );
188 expect( parser.parse( 'en_undelete_empty_param', [ 1 ] ).html() ).toEqual( 'Undelete' );
189 expect( parser.parse( 'en_undelete_empty_param', [ 3 ] ).html() ).toEqual( 'Undelete multiple edits' );
194 describe( "easy message interface functions", function() {
195 it( "should allow a global that returns strings", function() {
196 var gM = mw.jqueryMsg.getMessageFunction();
197 // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
198 // a surrounding <SPAN> is needed for html() to work right
199 var expectedHtml = $( '<span>Complex <a href="http://example.com/foo">linking</a> behaviour.</span>' ).html();
200 var result = gM( 'en_link_replace', 'http://example.com/foo', 'linking' );
201 expect( typeof result ).toEqual( 'string' );
202 expect( result ).toEqual( expectedHtml );
205 it( "should allow a jQuery plugin that appends to nodes", function() {
206 $.fn.msg = mw.jqueryMsg.getPlugin();
207 var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) );
209 var $button = $( '<button>' ).click( function() { clicked = true; } );
210 $div.find( '.foo' ).msg( 'en_link_replace', $button, 'buttoning' );
211 // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
212 // a surrounding <SPAN> is needed for html() to work right
213 var expectedHtml = $( '<span>Complex <button>buttoning</button> behaviour.</span>' ).html();
214 var createdHtml = $div.find( '.foo' ).html();
215 // it is hard to test for clicks with IE; also it inserts or removes spaces around nodes when creating HTML tags, depending on their type.
216 // so need to check the strings stripped of spaces.
217 if ( ( $.browser.mozilla || $.browser.webkit ) && $button.click ) {
218 expect( createdHtml ).toEqual( expectedHtml );
219 $div.find( 'button ').click();
220 expect( clicked ).toEqual( true );
221 } else if ( $.browser.ie ) {
222 expect( createdHtml.replace( /\s/, '' ) ).toEqual( expectedHtml.replace( /\s/, '' ) );
227 it( "jQuery plugin should escape incoming string arguments", function() {
228 $.fn.msg = mw.jqueryMsg.getPlugin();
229 var $div = $( '<div>' ).addClass( 'foo' );
230 $div.msg( 'en_replace', '<p>x</p>' ); // looks like HTML, but as a string, should be escaped.
231 // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
232 var expectedHtml = $( '<div class="foo">Simple <p>x</p> replacement</div>' ).html();
233 var createdHtml = $div.html();
234 expect( expectedHtml ).toEqual( createdHtml );
239 it( "jQuery plugin should never execute scripts", function() {
240 window.en_evil = false;
241 $.fn.msg = mw.jqueryMsg.getPlugin();
242 var $div = $( '<div>' );
243 $div.msg( 'en_evil' );
244 expect( window.en_evil ).toEqual( false );
249 // n.b. this passes because jQuery already seems to strip scripts away; however, it still executes them if they are appended to any element.
250 it( "jQuery plugin should never emit scripts", function() {
251 $.fn.msg = mw.jqueryMsg.getPlugin();
252 var $div = $( '<div>' );
253 $div.msg( 'en_evil' );
254 // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
255 var expectedHtml = $( '<div>This has tags</div>' ).html();
256 var createdHtml = $div.html();
257 expect( expectedHtml ).toEqual( createdHtml );
258 console.log( 'expected: ' + expectedHtml );
259 console.log( 'created: ' + createdHtml );
267 // The parser functions can throw errors, but let's not actually blow up for the user -- instead dump the error into the interface so we have
268 // a chance at fixing this
269 describe( "easy message interface functions with graceful failures", function() {
270 it( "should allow a global that returns strings, with graceful failure", function() {
271 var gM = mw.jqueryMsg.getMessageFunction();
272 // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
273 // a surrounding <SPAN> is needed for html() to work right
274 var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html();
275 var result = gM( 'en_fail' );
276 expect( typeof result ).toEqual( 'string' );
277 expect( result ).toEqual( expectedHtml );
280 it( "should allow a global that returns strings, with graceful failure on missing magic words", function() {
281 var gM = mw.jqueryMsg.getMessageFunction();
282 // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
283 // a surrounding <SPAN> is needed for html() to work right
284 var expectedHtml = $( '<span>en_fail_magic: unknown operation "sietname"</span>' ).html();
285 var result = gM( 'en_fail_magic' );
286 expect( typeof result ).toEqual( 'string' );
287 expect( result ).toEqual( expectedHtml );
291 it( "should allow a jQuery plugin, with graceful failure", function() {
292 $.fn.msg = mw.jqueryMsg.getPlugin();
293 var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) );
294 $div.find( '.foo' ).msg( 'en_fail' );
295 // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names.
296 // a surrounding <SPAN> is needed for html() to work right
297 var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html();
298 var createdHtml = $div.find( '.foo' ).html();
299 expect( createdHtml ).toEqual( expectedHtml );
308 describe( "test plurals and other language-specific functions", function() {
309 /* copying some language definitions in here -- it's hard to make this test fast and reliable
310 otherwise, and we don't want to have to know the mediawiki URL from this kind of test either.
311 We also can't preload the langs for the test since they clobber the same namespace.
312 In principle Roan said it was okay to change how languages worked so that didn't happen... maybe
313 someday. We'd have to the same kind of importing of the default rules for most rules, or maybe
314 come up with some kind of subclassing scheme for languages */
315 var languageClasses = {
318 * Arabic (العربية) language functions
321 convertPlural: function( count, forms ) {
322 forms = mw.language.preConvertPlural( forms, 6 );
332 if ( count % 100 >= 3 && count % 100 <= 10 ) {
335 if ( count % 100 >= 11 && count % 100 <= 99 ) {
341 digitTransformTable: {
342 '0': '٠', // ٠
343 '1': '١', // ١
344 '2': '٢', // ٢
345 '3': '٣', // ٣
346 '4': '٤', // ٤
347 '5': '٥', // ٥
348 '6': '٦', // ٦
349 '7': '٧', // ٧
350 '8': '٨', // ٨
351 '9': '٩', // ٩
352 '.': '٫', // ٫ wrong table ?
359 convertPlural: function( count, forms ) {
360 forms = mw.language.preConvertPlural( forms, 2 );
361 return ( count <= 1 ) ? forms[0] : forms[1];
368 /* simulate how the language classes override, or don't, the standard functions in mw.language */
369 $.each( languageClasses, function( langCode, rules ) {
370 $.each( [ 'convertPlural', 'convertNumber' ], function( i, propertyName ) {
371 if ( typeof rules[ propertyName ] === 'undefined' ) {
372 rules[ propertyName ] = mw.language[ propertyName ];
377 $.each( jasmineMsgSpec, function( i, test ) {
378 it( "should parse " + test.name, function() {
379 // using language override so we don't have to muck with global namespace
380 var parser = new mw.jqueryMsg.parser( { language: languageClasses[ test.lang ] } );
381 var parsedHtml = parser.parse( test.key, test.args ).html();
382 expect( parsedHtml ).toEqual( test.result );
389 } )( window.mediaWiki, jQuery );