(bug 37755) Set robot meta tags for 'view source' pages
[lhc/web/wiklou.git] / tests / qunit / suites / resources / jquery / jquery.tablesorter.test.js
1 ( function ( $, mw ) {
2
3 var config = {
4 wgMonthNames: ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
5 wgMonthNamesShort: ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
6 wgDefaultDateFormat: 'dmy',
7 wgContentLanguage: 'en'
8 };
9
10 QUnit.module( 'jquery.tablesorter', QUnit.newMwEnvironment({ config: config }) );
11
12 /**
13 * Create an HTML table from an array of row arrays containing text strings.
14 * First row will be header row. No fancy rowspan/colspan stuff.
15 *
16 * @param {String[]} header
17 * @param {String[][]} data
18 * @return jQuery
19 */
20 function tableCreate( header, data ) {
21 var i,
22 $table = $( '<table class="sortable"><thead></thead><tbody></tbody></table>' ),
23 $thead = $table.find( 'thead' ),
24 $tbody = $table.find( 'tbody' ),
25 $tr = $( '<tr>' );
26
27 $.each( header, function ( i, str ) {
28 var $th = $( '<th>' );
29 $th.text( str ).appendTo( $tr );
30 });
31 $tr.appendTo( $thead );
32
33 for ( i = 0; i < data.length; i++ ) {
34 $tr = $( '<tr>' );
35 $.each( data[i], function ( j, str ) {
36 var $td = $( '<td>' );
37 $td.text( str ).appendTo( $tr );
38 });
39 $tr.appendTo( $tbody );
40 }
41 return $table;
42 }
43
44 /**
45 * Extract text from table.
46 *
47 * @param {jQuery} $table
48 * @return String[][]
49 */
50 function tableExtract( $table ) {
51 var data = [];
52
53 $table.find( 'tbody' ).find( 'tr' ).each( function( i, tr ) {
54 var row = [];
55 $( tr ).find( 'td,th' ).each( function( i, td ) {
56 row.push( $( td ).text() );
57 });
58 data.push( row );
59 });
60 return data;
61 }
62
63 /**
64 * Run a table test by building a table with the given data,
65 * running some callback on it, then checking the results.
66 *
67 * @param {String} msg text to pass on to qunit for the comparison
68 * @param {String[]} header cols to make the table
69 * @param {String[][]} data rows/cols to make the table
70 * @param {String[][]} expected rows/cols to compare against at end
71 * @param {function($table)} callback something to do with the table before we compare
72 */
73 function tableTest( msg, header, data, expected, callback ) {
74 QUnit.test( msg, 1, function ( assert ) {
75 var $table = tableCreate( header, data );
76
77 // Give caller a chance to set up sorting and manipulate the table.
78 callback( $table );
79
80 // Table sorting is done synchronously; if it ever needs to change back
81 // to asynchronous, we'll need a timeout or a callback here.
82 var extracted = tableExtract( $table );
83 assert.deepEqual( extracted, expected, msg );
84 });
85 }
86
87 function reversed(arr) {
88 // Clone array
89 var arr2 = arr.slice(0);
90
91 arr2.reverse();
92
93 return arr2;
94 }
95
96 // Sample data set using planets named and their radius
97 var header = [ 'Planet' , 'Radius (km)'],
98 mercury = [ 'Mercury', '2439.7' ],
99 venus = [ 'Venus' , '6051.8' ],
100 earth = [ 'Earth' , '6371.0' ],
101 mars = [ 'Mars' , '3390.0' ],
102 jupiter = [ 'Jupiter', '69911' ],
103 saturn = [ 'Saturn' , '58232' ];
104
105 // Initial data set
106 var planets = [mercury, venus, earth, mars, jupiter, saturn];
107 var ascendingName = [earth, jupiter, mars, mercury, saturn, venus];
108 var ascendingRadius = [mercury, mars, venus, earth, saturn, jupiter];
109
110 tableTest(
111 'Basic planet table: ascending by name',
112 header,
113 planets,
114 ascendingName,
115 function ( $table ) {
116 $table.tablesorter();
117 $table.find( '.headerSort:eq(0)' ).click();
118 }
119 );
120 tableTest(
121 'Basic planet table: ascending by name a second time',
122 header,
123 planets,
124 ascendingName,
125 function ( $table ) {
126 $table.tablesorter();
127 $table.find( '.headerSort:eq(0)' ).click();
128 }
129 );
130 tableTest(
131 'Basic planet table: descending by name',
132 header,
133 planets,
134 reversed(ascendingName),
135 function ( $table ) {
136 $table.tablesorter();
137 $table.find( '.headerSort:eq(0)' ).click().click();
138 }
139 );
140 tableTest(
141 'Basic planet table: ascending radius',
142 header,
143 planets,
144 ascendingRadius,
145 function ( $table ) {
146 $table.tablesorter();
147 $table.find( '.headerSort:eq(1)' ).click();
148 }
149 );
150 tableTest(
151 'Basic planet table: descending radius',
152 header,
153 planets,
154 reversed(ascendingRadius),
155 function ( $table ) {
156 $table.tablesorter();
157 $table.find( '.headerSort:eq(1)' ).click().click();
158 }
159 );
160
161
162 // Regression tests!
163 tableTest(
164 'Bug 28775: German-style (dmy) short numeric dates',
165 ['Date'],
166 [ // German-style dates are day-month-year
167 ['11.11.2011'],
168 ['01.11.2011'],
169 ['02.10.2011'],
170 ['03.08.2011'],
171 ['09.11.2011']
172 ],
173 [ // Sorted by ascending date
174 ['03.08.2011'],
175 ['02.10.2011'],
176 ['01.11.2011'],
177 ['09.11.2011'],
178 ['11.11.2011']
179 ],
180 function ( $table ) {
181 mw.config.set( 'wgDefaultDateFormat', 'dmy' );
182 mw.config.set( 'wgContentLanguage', 'de' );
183
184 $table.tablesorter();
185 $table.find( '.headerSort:eq(0)' ).click();
186 }
187 );
188
189 tableTest(
190 'Bug 28775: American-style (mdy) short numeric dates',
191 ['Date'],
192 [ // American-style dates are month-day-year
193 ['11.11.2011'],
194 ['01.11.2011'],
195 ['02.10.2011'],
196 ['03.08.2011'],
197 ['09.11.2011']
198 ],
199 [ // Sorted by ascending date
200 ['01.11.2011'],
201 ['02.10.2011'],
202 ['03.08.2011'],
203 ['09.11.2011'],
204 ['11.11.2011']
205 ],
206 function ( $table ) {
207 mw.config.set( 'wgDefaultDateFormat', 'mdy' );
208
209 $table.tablesorter();
210 $table.find( '.headerSort:eq(0)' ).click();
211 }
212 );
213
214 var ipv4 = [
215 // Some randomly generated fake IPs
216 ['45.238.27.109'],
217 ['44.172.9.22'],
218 ['247.240.82.209'],
219 ['204.204.132.158'],
220 ['170.38.91.162'],
221 ['197.219.164.9'],
222 ['45.68.154.72'],
223 ['182.195.149.80']
224 ];
225 var ipv4Sorted = [
226 // Sort order should go octet by octet
227 ['44.172.9.22'],
228 ['45.68.154.72'],
229 ['45.238.27.109'],
230 ['170.38.91.162'],
231 ['182.195.149.80'],
232 ['197.219.164.9'],
233 ['204.204.132.158'],
234 ['247.240.82.209']
235 ];
236
237 tableTest(
238 'Bug 17141: IPv4 address sorting',
239 ['IP'],
240 ipv4,
241 ipv4Sorted,
242 function ( $table ) {
243 $table.tablesorter();
244 $table.find( '.headerSort:eq(0)' ).click();
245 }
246 );
247 tableTest(
248 'Bug 17141: IPv4 address sorting (reverse)',
249 ['IP'],
250 ipv4,
251 reversed(ipv4Sorted),
252 function ( $table ) {
253 $table.tablesorter();
254 $table.find( '.headerSort:eq(0)' ).click().click();
255 }
256 );
257
258 var umlautWords = [
259 // Some words with Umlauts
260 ['Günther'],
261 ['Peter'],
262 ['Björn'],
263 ['Bjorn'],
264 ['Apfel'],
265 ['Äpfel'],
266 ['Strasse'],
267 ['Sträßschen']
268 ];
269
270 var umlautWordsSorted = [
271 // Some words with Umlauts
272 ['Äpfel'],
273 ['Apfel'],
274 ['Björn'],
275 ['Bjorn'],
276 ['Günther'],
277 ['Peter'],
278 ['Sträßschen'],
279 ['Strasse']
280 ];
281
282 tableTest(
283 'Accented Characters with custom collation',
284 ['Name'],
285 umlautWords,
286 umlautWordsSorted,
287 function ( $table ) {
288 mw.config.set( 'tableSorterCollation', {
289 'ä': 'ae',
290 'ö': 'oe',
291 'ß': 'ss',
292 'ü':'ue'
293 } );
294
295 $table.tablesorter();
296 $table.find( '.headerSort:eq(0)' ).click();
297 }
298 );
299
300 var planetsRowspan = [["Earth","6051.8"], jupiter, ["Mars","6051.8"], mercury, saturn, venus];
301 var planetsRowspanII = [jupiter, mercury, saturn, venus, ['Venus', '6371.0'], ['Venus', '3390.0']];
302
303 tableTest(
304 'Basic planet table: same value for multiple rows via rowspan',
305 header,
306 planets,
307 planetsRowspan,
308 function ( $table ) {
309 // Modify the table to have a multiuple-row-spanning cell:
310 // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
311 $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
312 // - Set rowspan for 2nd cell of 3rd row to 3.
313 // This covers the removed cell in the 4th and 5th row.
314 $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' );
315
316 $table.tablesorter();
317 $table.find( '.headerSort:eq(0)' ).click();
318 }
319 );
320 tableTest(
321 'Basic planet table: Same value for multiple rows via rowspan II',
322 header,
323 planets,
324 planetsRowspanII,
325 function ( $table ) {
326 // Modify the table to have a multiuple-row-spanning cell:
327 // - Remove 1st cell of 4th row, and, 1st cell or 5th row.
328 $table.find( 'tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)' ).remove();
329 // - Set rowspan for 1st cell of 3rd row to 3.
330 // This covers the removed cell in the 4th and 5th row.
331 $table.find( 'tr:eq(2) td:eq(0)' ).prop( 'rowspan', '3' );
332
333 $table.tablesorter();
334 $table.find( '.headerSort:eq(0)' ).click();
335 }
336 );
337
338 var complexMDYDates = [
339 // Some words with Umlauts
340 ['January, 19 2010'],
341 ['April 21 1991'],
342 ['04 22 1991'],
343 ['5.12.1990'],
344 ['December 12 \'10']
345 ];
346
347 var complexMDYSorted = [
348 ['5.12.1990'],
349 ['April 21 1991'],
350 ['04 22 1991'],
351 ['January, 19 2010'],
352 ['December 12 \'10']
353 ];
354
355 tableTest(
356 'Complex date parsing I',
357 ['date'],
358 complexMDYDates,
359 complexMDYSorted,
360 function ( $table ) {
361 mw.config.set( 'wgDefaultDateFormat', 'mdy' );
362
363 $table.tablesorter();
364 $table.find( '.headerSort:eq(0)' ).click();
365 }
366 );
367
368 var currencyUnsorted = [
369 ['1.02 $'],
370 ['$ 3.00'],
371 ['€ 2,99'],
372 ['$ 1.00'],
373 ['$3.50'],
374 ['$ 1.50'],
375 ['€ 0.99']
376 ];
377
378 var currencySorted = [
379 ['€ 0.99'],
380 ['$ 1.00'],
381 ['1.02 $'],
382 ['$ 1.50'],
383 ['$ 3.00'],
384 ['$3.50'],
385 // Comma's sort after dots
386 // Not intentional but test to detect changes
387 ['€ 2,99']
388 ];
389
390 tableTest(
391 'Currency parsing I',
392 ['currency'],
393 currencyUnsorted,
394 currencySorted,
395 function ( $table ) {
396 $table.tablesorter();
397 $table.find( '.headerSort:eq(0)' ).click();
398 }
399 );
400
401 var ascendingNameLegacy = ascendingName.slice(0);
402 ascendingNameLegacy[4] = ascendingNameLegacy[5];
403 ascendingNameLegacy.pop();
404
405 tableTest(
406 'Legacy compat with .sortbottom',
407 header,
408 planets,
409 ascendingNameLegacy,
410 function( $table ) {
411 $table.find( 'tr:last' ).addClass( 'sortbottom' );
412 $table.tablesorter();
413 $table.find( '.headerSort:eq(0)' ).click();
414 }
415 );
416
417
418 /** FIXME: the diff output is not very readeable. */
419 QUnit.test( 'bug 32047 - caption must be before thead', function ( assert ) {
420 var $table;
421 $table = $(
422 '<table class="sortable">' +
423 '<caption>CAPTION</caption>' +
424 '<tr><th>THEAD</th></tr>' +
425 '<tr><td>A</td></tr>' +
426 '<tr><td>B</td></tr>' +
427 '<tr class="sortbottom"><td>TFOOT</td></tr>' +
428 '</table>'
429 );
430 $table.tablesorter();
431
432 assert.equal(
433 $table.children( ).get( 0 ).nodeName,
434 'CAPTION',
435 'First element after <thead> must be <caption> (bug 32047)'
436 );
437 });
438
439 QUnit.test( 'data-sort-value attribute, when available, should override sorting position', function ( assert ) {
440 var $table, data;
441
442 // Example 1: All cells except one cell without data-sort-value,
443 // which should be sorted at it's text content value.
444 $table = $(
445 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
446 '<tbody>' +
447 '<tr><td>Cheetah</td></tr>' +
448 '<tr><td data-sort-value="Apple">Bird</td></tr>' +
449 '<tr><td data-sort-value="Bananna">Ferret</td></tr>' +
450 '<tr><td data-sort-value="Drupe">Elephant</td></tr>' +
451 '<tr><td data-sort-value="Cherry">Dolphin</td></tr>' +
452 '</tbody></table>'
453 );
454 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
455
456 data = [];
457 $table.find( 'tbody > tr' ).each( function( i, tr ) {
458 $( tr ).find( 'td' ).each( function( i, td ) {
459 data.push( {
460 data: $( td ).data( 'sortValue' ),
461 text: $( td ).text()
462 } );
463 });
464 });
465
466 assert.deepEqual( data, [
467 {
468 data: 'Apple',
469 text: 'Bird'
470 }, {
471 data: 'Bananna',
472 text: 'Ferret'
473 }, {
474 data: undefined,
475 text: 'Cheetah'
476 }, {
477 data: 'Cherry',
478 text: 'Dolphin'
479 }, {
480 data: 'Drupe',
481 text: 'Elephant'
482 }
483 ], 'Order matches expected order (based on data-sort-value attribute values)' );
484
485 // Example 2
486 $table = $(
487 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
488 '<tbody>' +
489 '<tr><td>D</td></tr>' +
490 '<tr><td data-sort-value="E">A</td></tr>' +
491 '<tr><td>B</td></tr>' +
492 '<tr><td>G</td></tr>' +
493 '<tr><td data-sort-value="F">C</td></tr>' +
494 '</tbody></table>'
495 );
496 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
497
498 data = [];
499 $table.find( 'tbody > tr' ).each( function ( i, tr ) {
500 $( tr ).find( 'td' ).each( function ( i, td ) {
501 data.push( {
502 data: $( td ).data( 'sortValue' ),
503 text: $( td ).text()
504 } );
505 });
506 });
507
508 assert.deepEqual( data, [
509 {
510 data: undefined,
511 text: 'B'
512 }, {
513 data: undefined,
514 text: 'D'
515 }, {
516 data: 'E',
517 text: 'A'
518 }, {
519 data: 'F',
520 text: 'C'
521 }, {
522 data: undefined,
523 text: 'G'
524 }
525 ], 'Order matches expected order (based on data-sort-value attribute values)' );
526
527 // Example 3: Test that live changes are used from data-sort-value,
528 // even if they change after the tablesorter is constructed (bug 38152).
529 $table = $(
530 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
531 '<tbody>' +
532 '<tr><td>D</td></tr>' +
533 '<tr><td data-sort-value="1">A</td></tr>' +
534 '<tr><td>B</td></tr>' +
535 '<tr><td data-sort-value="2">G</td></tr>' +
536 '<tr><td>C</td></tr>' +
537 '</tbody></table>'
538 );
539 // initialize table sorter and sort once
540 $table
541 .tablesorter()
542 .find( '.headerSort:eq(0)' ).click();
543
544 // Change the sortValue data properties (bug 38152)
545 // - change data
546 $table.find( 'td:contains(A)' ).data( 'sortValue', 3 );
547 // - add data
548 $table.find( 'td:contains(B)' ).data( 'sortValue', 1 );
549 // - remove data, bring back attribute: 2
550 $table.find( 'td:contains(G)' ).removeData( 'sortValue' );
551
552 // Now sort again (twice, so it is back at Ascending)
553 $table.find( '.headerSort:eq(0)' ).click();
554 $table.find( '.headerSort:eq(0)' ).click();
555
556 data = [];
557 $table.find( 'tbody > tr' ).each( function( i, tr ) {
558 $( tr ).find( 'td' ).each( function( i, td ) {
559 data.push( {
560 data: $( td ).data( 'sortValue' ),
561 text: $( td ).text()
562 } );
563 });
564 });
565
566 assert.deepEqual( data, [
567 {
568 data: 1,
569 text: "B"
570 }, {
571 data: 2,
572 text: "G"
573 }, {
574 data: 3,
575 text: "A"
576 }, {
577 data: undefined,
578 text: "C"
579 }, {
580 data: undefined,
581 text: "D"
582 }
583 ], 'Order matches expected order, using the current sortValue in $.data()' );
584
585 });
586
587 var numbers = [
588 [ '12' ],
589 [ '7' ],
590 [ '13,000'],
591 [ '9' ],
592 [ '14' ],
593 [ '8.0' ]
594 ];
595 var numbersAsc = [
596 [ '7' ],
597 [ '8.0' ],
598 [ '9' ],
599 [ '12' ],
600 [ '14' ],
601 [ '13,000']
602 ];
603
604 tableTest( 'bug 8115: sort numbers with commas (ascending)',
605 ['Numbers'], numbers, numbersAsc,
606 function( $table ) {
607 $table.tablesorter();
608 $table.find( '.headerSort:eq(0)' ).click();
609 }
610 );
611
612 tableTest( 'bug 8115: sort numbers with commas (descending)',
613 ['Numbers'], numbers, reversed(numbersAsc),
614 function( $table ) {
615 $table.tablesorter();
616 $table.find( '.headerSort:eq(0)' ).click().click();
617 }
618 );
619 // TODO add numbers sorting tests for bug 8115 with a different language
620
621 QUnit.test( 'bug 32888 - Tables inside a tableheader cell', 2, function ( assert ) {
622 var $table;
623 $table = $(
624 '<table class="sortable" id="mw-bug-32888">' +
625 '<tr><th>header<table id="mw-bug-32888-2">'+
626 '<tr><th>1</th><th>2</th></tr>' +
627 '</table></th></tr>' +
628 '<tr><td>A</td></tr>' +
629 '<tr><td>B</td></tr>' +
630 '</table>'
631 );
632 $table.tablesorter();
633
634 assert.equal(
635 $table.find('> thead:eq(0) > tr > th.headerSort').length,
636 1,
637 'Child tables inside a headercell should not interfere with sortable headers (bug 32888)'
638 );
639 assert.equal(
640 $( '#mw-bug-32888-2' ).find('th.headerSort').length,
641 0,
642 'The headers of child tables inside a headercell should not be sortable themselves (bug 32888)'
643 );
644 });
645
646
647 var correctDateSorting1 = [
648 ['01 January 2010'],
649 ['05 February 2010'],
650 ['16 January 2010']
651 ];
652
653 var correctDateSortingSorted1 = [
654 ['01 January 2010'],
655 ['16 January 2010'],
656 ['05 February 2010']
657 ];
658
659 tableTest(
660 'Correct date sorting I',
661 ['date'],
662 correctDateSorting1,
663 correctDateSortingSorted1,
664 function ( $table ) {
665 mw.config.set( 'wgDefaultDateFormat', 'mdy' );
666
667 $table.tablesorter();
668 $table.find( '.headerSort:eq(0)' ).click();
669 }
670 );
671
672 var correctDateSorting2 = [
673 ['January 01 2010'],
674 ['February 05 2010'],
675 ['January 16 2010']
676 ];
677
678 var correctDateSortingSorted2 = [
679 ['January 01 2010'],
680 ['January 16 2010'],
681 ['February 05 2010']
682 ];
683
684 tableTest(
685 'Correct date sorting II',
686 ['date'],
687 correctDateSorting2,
688 correctDateSortingSorted2,
689 function ( $table ) {
690 mw.config.set( 'wgDefaultDateFormat', 'dmy' );
691
692 $table.tablesorter();
693 $table.find( '.headerSort:eq(0)' ).click();
694 }
695 );
696
697 }( jQuery, mediaWiki ) );