d8a7d6a3526f0acd00cdbfcd2bbed1afb5914371
[lhc/web/wiklou.git] / tests / qunit / suites / resources / jquery / jquery.tablesorter.test.js
1 ( function ( $, mw ) {
2 /*jshint onevar: false */
3
4 var config = {
5 wgMonthNames: ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
6 wgMonthNamesShort: ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
7 wgDefaultDateFormat: 'dmy',
8 wgSeparatorTransformTable: ['', ''],
9 wgDigitTransformTable: ['', ''],
10 wgContentLanguage: 'en'
11 };
12
13 QUnit.module( 'jquery.tablesorter', QUnit.newMwEnvironment( { config: config } ) );
14
15 /**
16 * Create an HTML table from an array of row arrays containing text strings.
17 * First row will be header row. No fancy rowspan/colspan stuff.
18 *
19 * @param {String[]} header
20 * @param {String[][]} data
21 * @return jQuery
22 */
23 function tableCreate( header, data ) {
24 var i,
25 $table = $( '<table class="sortable"><thead></thead><tbody></tbody></table>' ),
26 $thead = $table.find( 'thead' ),
27 $tbody = $table.find( 'tbody' ),
28 $tr = $( '<tr>' );
29
30 $.each( header, function ( i, str ) {
31 var $th = $( '<th>' );
32 $th.text( str ).appendTo( $tr );
33 } );
34 $tr.appendTo( $thead );
35
36 for ( i = 0; i < data.length; i++ ) {
37 /*jshint loopfunc: true */
38 $tr = $( '<tr>' );
39 $.each( data[i], function ( j, str ) {
40 var $td = $( '<td>' );
41 $td.text( str ).appendTo( $tr );
42 } );
43 $tr.appendTo( $tbody );
44 }
45 return $table;
46 }
47
48 /**
49 * Extract text from table.
50 *
51 * @param {jQuery} $table
52 * @return String[][]
53 */
54 function tableExtract( $table ) {
55 var data = [];
56
57 $table.find( 'tbody' ).find( 'tr' ).each( function ( i, tr ) {
58 var row = [];
59 $( tr ).find( 'td,th' ).each( function ( i, td ) {
60 row.push( $( td ).text() );
61 } );
62 data.push( row );
63 } );
64 return data;
65 }
66
67 /**
68 * Run a table test by building a table with the given data,
69 * running some callback on it, then checking the results.
70 *
71 * @param {String} msg text to pass on to qunit for the comparison
72 * @param {String[]} header cols to make the table
73 * @param {String[][]} data rows/cols to make the table
74 * @param {String[][]} expected rows/cols to compare against at end
75 * @param {function($table)} callback something to do with the table before we compare
76 */
77 function tableTest( msg, header, data, expected, callback ) {
78 QUnit.test( msg, 1, function ( assert ) {
79 var $table = tableCreate( header, data );
80
81 // Give caller a chance to set up sorting and manipulate the table.
82 callback( $table );
83
84 // Table sorting is done synchronously; if it ever needs to change back
85 // to asynchronous, we'll need a timeout or a callback here.
86 var extracted = tableExtract( $table );
87 assert.deepEqual( extracted, expected, msg );
88 } );
89 }
90
91 /**
92 * Run a table test by building a table with the given HTML,
93 * running some callback on it, then checking the results.
94 *
95 * @param {String} msg text to pass on to qunit for the comparison
96 * @param {String} HTML to make the table
97 * @param {String[][]} expected rows/cols to compare against at end
98 * @param {function($table)} callback something to do with the table before we compare
99 */
100 function tableTestHTML( msg, html, expected, callback ) {
101 QUnit.test( msg, 1, function ( assert ) {
102 var $table = $( html );
103
104 // Give caller a chance to set up sorting and manipulate the table.
105 if ( callback ) {
106 callback( $table );
107 } else {
108 $table.tablesorter();
109 $table.find( '#sortme' ).click();
110 }
111
112 // Table sorting is done synchronously; if it ever needs to change back
113 // to asynchronous, we'll need a timeout or a callback here.
114 var extracted = tableExtract( $table );
115 assert.deepEqual( extracted, expected, msg );
116 } );
117 }
118
119 function reversed( arr ) {
120 // Clone array
121 var arr2 = arr.slice( 0 );
122
123 arr2.reverse();
124
125 return arr2;
126 }
127
128 // Sample data set using planets named and their radius
129 var header = [ 'Planet' , 'Radius (km)'],
130 mercury = [ 'Mercury', '2439.7' ],
131 venus = [ 'Venus' , '6051.8' ],
132 earth = [ 'Earth' , '6371.0' ],
133 mars = [ 'Mars' , '3390.0' ],
134 jupiter = [ 'Jupiter', '69911' ],
135 saturn = [ 'Saturn' , '58232' ];
136
137 // Initial data set
138 var planets = [mercury, venus, earth, mars, jupiter, saturn];
139 var ascendingName = [earth, jupiter, mars, mercury, saturn, venus];
140 var ascendingRadius = [mercury, mars, venus, earth, saturn, jupiter];
141
142 tableTest(
143 'Basic planet table: sorting initially - ascending by name',
144 header,
145 planets,
146 ascendingName,
147 function ( $table ) {
148 $table.tablesorter( { sortList: [
149 { 0: 'asc' }
150 ] } );
151 }
152 );
153 tableTest(
154 'Basic planet table: sorting initially - descending by radius',
155 header,
156 planets,
157 reversed( ascendingRadius ),
158 function ( $table ) {
159 $table.tablesorter( { sortList: [
160 { 1: 'desc' }
161 ] } );
162 }
163 );
164 tableTest(
165 'Basic planet table: ascending by name',
166 header,
167 planets,
168 ascendingName,
169 function ( $table ) {
170 $table.tablesorter();
171 $table.find( '.headerSort:eq(0)' ).click();
172 }
173 );
174 tableTest(
175 'Basic planet table: ascending by name a second time',
176 header,
177 planets,
178 ascendingName,
179 function ( $table ) {
180 $table.tablesorter();
181 $table.find( '.headerSort:eq(0)' ).click();
182 }
183 );
184 tableTest(
185 'Basic planet table: ascending by name (multiple clicks)',
186 header,
187 planets,
188 ascendingName,
189 function ( $table ) {
190 $table.tablesorter();
191 $table.find( '.headerSort:eq(0)' ).click();
192 $table.find( '.headerSort:eq(1)' ).click();
193 $table.find( '.headerSort:eq(0)' ).click();
194 }
195 );
196 tableTest(
197 'Basic planet table: descending by name',
198 header,
199 planets,
200 reversed( ascendingName ),
201 function ( $table ) {
202 $table.tablesorter();
203 $table.find( '.headerSort:eq(0)' ).click().click();
204 }
205 );
206 tableTest(
207 'Basic planet table: ascending radius',
208 header,
209 planets,
210 ascendingRadius,
211 function ( $table ) {
212 $table.tablesorter();
213 $table.find( '.headerSort:eq(1)' ).click();
214 }
215 );
216 tableTest(
217 'Basic planet table: descending radius',
218 header,
219 planets,
220 reversed( ascendingRadius ),
221 function ( $table ) {
222 $table.tablesorter();
223 $table.find( '.headerSort:eq(1)' ).click().click();
224 }
225 );
226
227 // Sample data set to test multiple column sorting
228 header = [ 'column1' , 'column2'];
229 var
230 a1 = [ 'A', '1' ],
231 a2 = [ 'A', '2' ],
232 a3 = [ 'A', '3' ],
233 b1 = [ 'B', '1' ],
234 b2 = [ 'B', '2' ],
235 b3 = [ 'B', '3' ];
236 var initial = [a2, b3, a1, a3, b2, b1];
237 var asc = [a1, a2, a3, b1, b2, b3];
238 var descasc = [b1, b2, b3, a1, a2, a3];
239
240 tableTest(
241 'Sorting multiple columns by passing sort list',
242 header,
243 initial,
244 asc,
245 function ( $table ) {
246 $table.tablesorter(
247 { sortList: [
248 { 0: 'asc' },
249 { 1: 'asc' }
250 ] }
251 );
252 }
253 );
254 tableTest(
255 'Sorting multiple columns by programmatically triggering sort()',
256 header,
257 initial,
258 descasc,
259 function ( $table ) {
260 $table.tablesorter();
261 $table.data( 'tablesorter' ).sort(
262 [
263 { 0: 'desc' },
264 { 1: 'asc' }
265 ]
266 );
267 }
268 );
269 tableTest(
270 'Reset to initial sorting by triggering sort() without any parameters',
271 header,
272 initial,
273 asc,
274 function ( $table ) {
275 $table.tablesorter(
276 { sortList: [
277 { 0: 'asc' },
278 { 1: 'asc' }
279 ] }
280 );
281 $table.data( 'tablesorter' ).sort(
282 [
283 { 0: 'desc' },
284 { 1: 'asc' }
285 ]
286 );
287 $table.data( 'tablesorter' ).sort();
288 }
289 );
290 tableTest(
291 'Sort via click event after having initialized the tablesorter with initial sorting',
292 header,
293 initial,
294 descasc,
295 function ( $table ) {
296 $table.tablesorter(
297 { sortList: [ { 0: 'asc' }, { 1: 'asc' } ] }
298 );
299 $table.find( '.headerSort:eq(0)' ).click();
300 }
301 );
302 tableTest(
303 'Multi-sort via click event after having initialized the tablesorter with initial sorting',
304 header,
305 initial,
306 asc,
307 function ( $table ) {
308 $table.tablesorter(
309 { sortList: [ { 0: 'desc' }, { 1: 'desc' } ] }
310 );
311 $table.find( '.headerSort:eq(0)' ).click();
312
313 // Pretend to click while pressing the multi-sort key
314 var event = $.Event( 'click' );
315 event[$table.data( 'tablesorter' ).config.sortMultiSortKey] = true;
316 $table.find( '.headerSort:eq(1)' ).trigger( event );
317 }
318 );
319 QUnit.test( 'Reset sorting making table appear unsorted', 3, function ( assert ) {
320 var $table = tableCreate( header, initial );
321 $table.tablesorter(
322 { sortList: [
323 { 0: 'desc' },
324 { 1: 'asc' }
325 ] }
326 );
327 $table.data( 'tablesorter' ).sort( [] );
328
329 assert.equal(
330 $table.find( 'th.headerSortUp' ).length + $table.find( 'th.headerSortDown' ).length,
331 0,
332 'No sort specific sort classes addign to header cells'
333 );
334
335 assert.equal(
336 $table.find( 'th' ).first().attr( 'title' ),
337 mw.msg( 'sort-ascending' ),
338 'First header cell has default title'
339 );
340
341 assert.equal(
342 $table.find( 'th' ).first().attr( 'title' ),
343 $table.find( 'th' ).last().attr( 'title' ),
344 'Both header cells\' titles match'
345 );
346 } );
347
348 // Sorting with colspans
349 header = [ 'column1a' , 'column1b', 'column1c', 'column2' ];
350 var
351 aaa1 = [ 'A', 'A', 'A', '1' ],
352 aab5 = [ 'A', 'A', 'B', '5' ],
353 abc3 = [ 'A', 'B', 'C', '3' ],
354 bbc2 = [ 'B', 'B', 'C', '2' ],
355 caa4 = [ 'C', 'A', 'A', '4' ];
356 // initial is already declared above
357 initial = [ aab5, aaa1, abc3, bbc2, caa4 ];
358 tableTest( 'Sorting with colspanned headers: spanned column',
359 header,
360 initial,
361 [ aaa1, aab5, abc3, bbc2, caa4 ],
362 function ( $table ) {
363 // Make colspanned header for test
364 $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
365 $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
366
367 $table.tablesorter();
368 $table.find( '.headerSort:eq(0)' ).click();
369 }
370 );
371 tableTest( 'Sorting with colspanned headers: sort spanned column twice',
372 header,
373 initial,
374 [ caa4, bbc2, abc3, aab5, aaa1 ],
375 function ( $table ) {
376 // Make colspanned header for test
377 $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
378 $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
379
380 $table.tablesorter();
381 $table.find( '.headerSort:eq(0)' ).click();
382 $table.find( '.headerSort:eq(0)' ).click();
383 }
384 );
385 tableTest( 'Sorting with colspanned headers: subsequent column',
386 header,
387 initial,
388 [ aaa1, bbc2, abc3, caa4, aab5 ],
389 function ( $table ) {
390 // Make colspanned header for test
391 $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
392 $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
393
394 $table.tablesorter();
395 $table.find( '.headerSort:eq(1)' ).click();
396 }
397 );
398 tableTest( 'Sorting with colspanned headers: sort subsequent column twice',
399 header,
400 initial,
401 [ aab5, caa4, abc3, bbc2, aaa1 ],
402 function ( $table ) {
403 // Make colspanned header for test
404 $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
405 $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
406
407 $table.tablesorter();
408 $table.find( '.headerSort:eq(1)' ).click();
409 $table.find( '.headerSort:eq(1)' ).click();
410 }
411 );
412
413
414 tableTest(
415 'Basic planet table: one unsortable column',
416 header,
417 planets,
418 planets,
419 function ( $table ) {
420 $table.find( 'tr:eq(0) > th:eq(0)' ).addClass( 'unsortable' );
421
422 $table.tablesorter();
423 $table.find( 'tr:eq(0) > th:eq(0)' ).click();
424 }
425 );
426
427 // Regression tests!
428 tableTest(
429 'Bug 28775: German-style (dmy) short numeric dates',
430 ['Date'],
431 [
432 // German-style dates are day-month-year
433 ['11.11.2011'],
434 ['01.11.2011'],
435 ['02.10.2011'],
436 ['03.08.2011'],
437 ['09.11.2011']
438 ],
439 [
440 // Sorted by ascending date
441 ['03.08.2011'],
442 ['02.10.2011'],
443 ['01.11.2011'],
444 ['09.11.2011'],
445 ['11.11.2011']
446 ],
447 function ( $table ) {
448 mw.config.set( 'wgDefaultDateFormat', 'dmy' );
449 mw.config.set( 'wgContentLanguage', 'de' );
450
451 $table.tablesorter();
452 $table.find( '.headerSort:eq(0)' ).click();
453 }
454 );
455
456 tableTest(
457 'Bug 28775: American-style (mdy) short numeric dates',
458 ['Date'],
459 [
460 // American-style dates are month-day-year
461 ['11.11.2011'],
462 ['01.11.2011'],
463 ['02.10.2011'],
464 ['03.08.2011'],
465 ['09.11.2011']
466 ],
467 [
468 // Sorted by ascending date
469 ['01.11.2011'],
470 ['02.10.2011'],
471 ['03.08.2011'],
472 ['09.11.2011'],
473 ['11.11.2011']
474 ],
475 function ( $table ) {
476 mw.config.set( 'wgDefaultDateFormat', 'mdy' );
477
478 $table.tablesorter();
479 $table.find( '.headerSort:eq(0)' ).click();
480 }
481 );
482
483 var ipv4 = [
484 // Some randomly generated fake IPs
485 ['45.238.27.109'],
486 ['44.172.9.22'],
487 ['247.240.82.209'],
488 ['204.204.132.158'],
489 ['170.38.91.162'],
490 ['197.219.164.9'],
491 ['45.68.154.72'],
492 ['182.195.149.80']
493 ];
494 var ipv4Sorted = [
495 // Sort order should go octet by octet
496 ['44.172.9.22'],
497 ['45.68.154.72'],
498 ['45.238.27.109'],
499 ['170.38.91.162'],
500 ['182.195.149.80'],
501 ['197.219.164.9'],
502 ['204.204.132.158'],
503 ['247.240.82.209']
504 ];
505
506 tableTest(
507 'Bug 17141: IPv4 address sorting',
508 ['IP'],
509 ipv4,
510 ipv4Sorted,
511 function ( $table ) {
512 $table.tablesorter();
513 $table.find( '.headerSort:eq(0)' ).click();
514 }
515 );
516 tableTest(
517 'Bug 17141: IPv4 address sorting (reverse)',
518 ['IP'],
519 ipv4,
520 reversed( ipv4Sorted ),
521 function ( $table ) {
522 $table.tablesorter();
523 $table.find( '.headerSort:eq(0)' ).click().click();
524 }
525 );
526
527 var umlautWords = [
528 // Some words with Umlauts
529 ['Günther'],
530 ['Peter'],
531 ['Björn'],
532 ['Bjorn'],
533 ['Apfel'],
534 ['Äpfel'],
535 ['Strasse'],
536 ['Sträßschen']
537 ];
538
539 var umlautWordsSorted = [
540 // Some words with Umlauts
541 ['Äpfel'],
542 ['Apfel'],
543 ['Björn'],
544 ['Bjorn'],
545 ['Günther'],
546 ['Peter'],
547 ['Sträßschen'],
548 ['Strasse']
549 ];
550
551 tableTest(
552 'Accented Characters with custom collation',
553 ['Name'],
554 umlautWords,
555 umlautWordsSorted,
556 function ( $table ) {
557 mw.config.set( 'tableSorterCollation', {
558 'ä': 'ae',
559 'ö': 'oe',
560 'ß': 'ss',
561 'ü': 'ue'
562 } );
563
564 $table.tablesorter();
565 $table.find( '.headerSort:eq(0)' ).click();
566 }
567 );
568
569 QUnit.test( 'Rowspan not exploded on init', 1, function ( assert ) {
570 var $table = tableCreate( header, planets );
571
572 // Modify the table to have a multiple-row-spanning cell:
573 // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
574 $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
575 // - Set rowspan for 2nd cell of 3rd row to 3.
576 // This covers the removed cell in the 4th and 5th row.
577 $table.find( 'tr:eq(2) td:eq(1)' ).attr( 'rowspan', '3' );
578
579 $table.tablesorter();
580
581 assert.equal(
582 $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowSpan' ),
583 3,
584 'Rowspan not exploded'
585 );
586 } );
587
588 var planetsRowspan = [
589 [ 'Earth', '6051.8' ],
590 jupiter,
591 [ 'Mars', '6051.8' ],
592 mercury,
593 saturn,
594 venus
595 ];
596 var planetsRowspanII = [ jupiter, mercury, saturn, venus, [ 'Venus', '6371.0' ], [ 'Venus', '3390.0' ] ];
597
598 tableTest(
599 'Basic planet table: same value for multiple rows via rowspan',
600 header,
601 planets,
602 planetsRowspan,
603 function ( $table ) {
604 // Modify the table to have a multiple-row-spanning cell:
605 // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
606 $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
607 // - Set rowspan for 2nd cell of 3rd row to 3.
608 // This covers the removed cell in the 4th and 5th row.
609 $table.find( 'tr:eq(2) td:eq(1)' ).attr( 'rowspan', '3' );
610
611 $table.tablesorter();
612 $table.find( '.headerSort:eq(0)' ).click();
613 }
614 );
615 tableTest(
616 'Basic planet table: same value for multiple rows via rowspan (sorting initially)',
617 header,
618 planets,
619 planetsRowspan,
620 function ( $table ) {
621 // Modify the table to have a multiple-row-spanning cell:
622 // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
623 $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
624 // - Set rowspan for 2nd cell of 3rd row to 3.
625 // This covers the removed cell in the 4th and 5th row.
626 $table.find( 'tr:eq(2) td:eq(1)' ).attr( 'rowspan', '3' );
627
628 $table.tablesorter( { sortList: [
629 { 0: 'asc' }
630 ] } );
631 }
632 );
633 tableTest(
634 'Basic planet table: Same value for multiple rows via rowspan II',
635 header,
636 planets,
637 planetsRowspanII,
638 function ( $table ) {
639 // Modify the table to have a multiple-row-spanning cell:
640 // - Remove 1st cell of 4th row, and, 1st cell or 5th row.
641 $table.find( 'tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)' ).remove();
642 // - Set rowspan for 1st cell of 3rd row to 3.
643 // This covers the removed cell in the 4th and 5th row.
644 $table.find( 'tr:eq(2) td:eq(0)' ).attr( 'rowspan', '3' );
645
646 $table.tablesorter();
647 $table.find( '.headerSort:eq(0)' ).click();
648 }
649 );
650
651 var complexMDYDates = [
652 // Some words with Umlauts
653 ['January, 19 2010'],
654 ['April 21 1991'],
655 ['04 22 1991'],
656 ['5.12.1990'],
657 ['December 12 \'10']
658 ];
659
660 var complexMDYSorted = [
661 ['5.12.1990'],
662 ['April 21 1991'],
663 ['04 22 1991'],
664 ['January, 19 2010'],
665 ['December 12 \'10']
666 ];
667
668 tableTest(
669 'Complex date parsing I',
670 ['date'],
671 complexMDYDates,
672 complexMDYSorted,
673 function ( $table ) {
674 mw.config.set( 'wgDefaultDateFormat', 'mdy' );
675
676 $table.tablesorter();
677 $table.find( '.headerSort:eq(0)' ).click();
678 }
679 );
680
681 var currencyUnsorted = [
682 ['1.02 $'],
683 ['$ 3.00'],
684 ['€ 2,99'],
685 ['$ 1.00'],
686 ['$3.50'],
687 ['$ 1.50'],
688 ['€ 0.99']
689 ];
690
691 var currencySorted = [
692 ['€ 0.99'],
693 ['$ 1.00'],
694 ['1.02 $'],
695 ['$ 1.50'],
696 ['$ 3.00'],
697 ['$3.50'],
698 // Comma's sort after dots
699 // Not intentional but test to detect changes
700 ['€ 2,99']
701 ];
702
703 tableTest(
704 'Currency parsing I',
705 ['currency'],
706 currencyUnsorted,
707 currencySorted,
708 function ( $table ) {
709 $table.tablesorter();
710 $table.find( '.headerSort:eq(0)' ).click();
711 }
712 );
713
714 var ascendingNameLegacy = ascendingName.slice( 0 );
715 ascendingNameLegacy[4] = ascendingNameLegacy[5];
716 ascendingNameLegacy.pop();
717
718 tableTest(
719 'Legacy compat with .sortbottom',
720 header,
721 planets,
722 ascendingNameLegacy,
723 function ( $table ) {
724 $table.find( 'tr:last' ).addClass( 'sortbottom' );
725 $table.tablesorter();
726 $table.find( '.headerSort:eq(0)' ).click();
727 }
728 );
729
730 QUnit.test( 'Test detection routine', 1, function ( assert ) {
731 var $table;
732 $table = $(
733 '<table class="sortable">' +
734 '<caption>CAPTION</caption>' +
735 '<tr><th>THEAD</th></tr>' +
736 '<tr><td>1</td></tr>' +
737 '<tr class="sortbottom"><td>text</td></tr>' +
738 '</table>'
739 );
740 $table.tablesorter();
741 $table.find( '.headerSort:eq(0)' ).click();
742
743 assert.equal(
744 $table.data( 'tablesorter' ).config.parsers[0].id,
745 'number',
746 'Correctly detected column content skipping sortbottom'
747 );
748 } );
749
750 /** FIXME: the diff output is not very readeable. */
751 QUnit.test( 'bug 32047 - caption must be before thead', 1, function ( assert ) {
752 var $table;
753 $table = $(
754 '<table class="sortable">' +
755 '<caption>CAPTION</caption>' +
756 '<tr><th>THEAD</th></tr>' +
757 '<tr><td>A</td></tr>' +
758 '<tr><td>B</td></tr>' +
759 '<tr class="sortbottom"><td>TFOOT</td></tr>' +
760 '</table>'
761 );
762 $table.tablesorter();
763
764 assert.equal(
765 $table.children().get( 0 ).nodeName,
766 'CAPTION',
767 'First element after <thead> must be <caption> (bug 32047)'
768 );
769 } );
770
771 QUnit.test( 'data-sort-value attribute, when available, should override sorting position', 3, function ( assert ) {
772 var $table, data;
773
774 // Example 1: All cells except one cell without data-sort-value,
775 // which should be sorted at it's text content value.
776 $table = $(
777 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
778 '<tbody>' +
779 '<tr><td>Cheetah</td></tr>' +
780 '<tr><td data-sort-value="Apple">Bird</td></tr>' +
781 '<tr><td data-sort-value="Bananna">Ferret</td></tr>' +
782 '<tr><td data-sort-value="Drupe">Elephant</td></tr>' +
783 '<tr><td data-sort-value="Cherry">Dolphin</td></tr>' +
784 '</tbody></table>'
785 );
786 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
787
788 data = [];
789 $table.find( 'tbody > tr' ).each( function ( i, tr ) {
790 $( tr ).find( 'td' ).each( function ( i, td ) {
791 data.push( {
792 data: $( td ).data( 'sortValue' ),
793 text: $( td ).text()
794 } );
795 } );
796 } );
797
798 assert.deepEqual( data, [
799 {
800 data: 'Apple',
801 text: 'Bird'
802 },
803 {
804 data: 'Bananna',
805 text: 'Ferret'
806 },
807 {
808 data: undefined,
809 text: 'Cheetah'
810 },
811 {
812 data: 'Cherry',
813 text: 'Dolphin'
814 },
815 {
816 data: 'Drupe',
817 text: 'Elephant'
818 }
819 ], 'Order matches expected order (based on data-sort-value attribute values)' );
820
821 // Example 2
822 $table = $(
823 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
824 '<tbody>' +
825 '<tr><td>D</td></tr>' +
826 '<tr><td data-sort-value="E">A</td></tr>' +
827 '<tr><td>B</td></tr>' +
828 '<tr><td>G</td></tr>' +
829 '<tr><td data-sort-value="F">C</td></tr>' +
830 '</tbody></table>'
831 );
832 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
833
834 data = [];
835 $table.find( 'tbody > tr' ).each( function ( i, tr ) {
836 $( tr ).find( 'td' ).each( function ( i, td ) {
837 data.push( {
838 data: $( td ).data( 'sortValue' ),
839 text: $( td ).text()
840 } );
841 } );
842 } );
843
844 assert.deepEqual( data, [
845 {
846 data: undefined,
847 text: 'B'
848 },
849 {
850 data: undefined,
851 text: 'D'
852 },
853 {
854 data: 'E',
855 text: 'A'
856 },
857 {
858 data: 'F',
859 text: 'C'
860 },
861 {
862 data: undefined,
863 text: 'G'
864 }
865 ], 'Order matches expected order (based on data-sort-value attribute values)' );
866
867 // Example 3: Test that live changes are used from data-sort-value,
868 // even if they change after the tablesorter is constructed (bug 38152).
869 $table = $(
870 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
871 '<tbody>' +
872 '<tr><td>D</td></tr>' +
873 '<tr><td data-sort-value="1">A</td></tr>' +
874 '<tr><td>B</td></tr>' +
875 '<tr><td data-sort-value="2">G</td></tr>' +
876 '<tr><td>C</td></tr>' +
877 '</tbody></table>'
878 );
879 // initialize table sorter and sort once
880 $table
881 .tablesorter()
882 .find( '.headerSort:eq(0)' ).click();
883
884 // Change the sortValue data properties (bug 38152)
885 // - change data
886 $table.find( 'td:contains(A)' ).data( 'sortValue', 3 );
887 // - add data
888 $table.find( 'td:contains(B)' ).data( 'sortValue', 1 );
889 // - remove data, bring back attribute: 2
890 $table.find( 'td:contains(G)' ).removeData( 'sortValue' );
891
892 // Now sort again (twice, so it is back at Ascending)
893 $table.find( '.headerSort:eq(0)' ).click();
894 $table.find( '.headerSort:eq(0)' ).click();
895
896 data = [];
897 $table.find( 'tbody > tr' ).each( function ( i, tr ) {
898 $( tr ).find( 'td' ).each( function ( i, td ) {
899 data.push( {
900 data: $( td ).data( 'sortValue' ),
901 text: $( td ).text()
902 } );
903 } );
904 } );
905
906 assert.deepEqual( data, [
907 {
908 data: 1,
909 text: 'B'
910 },
911 {
912 data: 2,
913 text: 'G'
914 },
915 {
916 data: 3,
917 text: 'A'
918 },
919 {
920 data: undefined,
921 text: 'C'
922 },
923 {
924 data: undefined,
925 text: 'D'
926 }
927 ], 'Order matches expected order, using the current sortValue in $.data()' );
928
929 } );
930
931 var numbers = [
932 [ '12' ],
933 [ '7' ],
934 [ '13,000'],
935 [ '9' ],
936 [ '14' ],
937 [ '8.0' ]
938 ];
939 var numbersAsc = [
940 [ '7' ],
941 [ '8.0' ],
942 [ '9' ],
943 [ '12' ],
944 [ '14' ],
945 [ '13,000']
946 ];
947
948 tableTest( 'bug 8115: sort numbers with commas (ascending)',
949 ['Numbers'], numbers, numbersAsc,
950 function ( $table ) {
951 $table.tablesorter();
952 $table.find( '.headerSort:eq(0)' ).click();
953 }
954 );
955
956 tableTest( 'bug 8115: sort numbers with commas (descending)',
957 ['Numbers'], numbers, reversed( numbersAsc ),
958 function ( $table ) {
959 $table.tablesorter();
960 $table.find( '.headerSort:eq(0)' ).click().click();
961 }
962 );
963 // TODO add numbers sorting tests for bug 8115 with a different language
964
965 QUnit.test( 'bug 32888 - Tables inside a tableheader cell', 2, function ( assert ) {
966 var $table;
967 $table = $(
968 '<table class="sortable" id="mw-bug-32888">' +
969 '<tr><th>header<table id="mw-bug-32888-2">' +
970 '<tr><th>1</th><th>2</th></tr>' +
971 '</table></th></tr>' +
972 '<tr><td>A</td></tr>' +
973 '<tr><td>B</td></tr>' +
974 '</table>'
975 );
976 $table.tablesorter();
977
978 assert.equal(
979 $table.find( '> thead:eq(0) > tr > th.headerSort' ).length,
980 1,
981 'Child tables inside a headercell should not interfere with sortable headers (bug 32888)'
982 );
983 assert.equal(
984 $( '#mw-bug-32888-2' ).find( 'th.headerSort' ).length,
985 0,
986 'The headers of child tables inside a headercell should not be sortable themselves (bug 32888)'
987 );
988 } );
989
990
991 var correctDateSorting1 = [
992 ['01 January 2010'],
993 ['05 February 2010'],
994 ['16 January 2010']
995 ];
996
997 var correctDateSortingSorted1 = [
998 ['01 January 2010'],
999 ['16 January 2010'],
1000 ['05 February 2010']
1001 ];
1002
1003 tableTest(
1004 'Correct date sorting I',
1005 ['date'],
1006 correctDateSorting1,
1007 correctDateSortingSorted1,
1008 function ( $table ) {
1009 mw.config.set( 'wgDefaultDateFormat', 'mdy' );
1010
1011 $table.tablesorter();
1012 $table.find( '.headerSort:eq(0)' ).click();
1013 }
1014 );
1015
1016 var correctDateSorting2 = [
1017 ['January 01 2010'],
1018 ['February 05 2010'],
1019 ['January 16 2010']
1020 ];
1021
1022 var correctDateSortingSorted2 = [
1023 ['January 01 2010'],
1024 ['January 16 2010'],
1025 ['February 05 2010']
1026 ];
1027
1028 tableTest(
1029 'Correct date sorting II',
1030 ['date'],
1031 correctDateSorting2,
1032 correctDateSortingSorted2,
1033 function ( $table ) {
1034 mw.config.set( 'wgDefaultDateFormat', 'dmy' );
1035
1036 $table.tablesorter();
1037 $table.find( '.headerSort:eq(0)' ).click();
1038 }
1039 );
1040
1041 QUnit.test( 'Sorting images using alt text', 1, function ( assert ) {
1042 var $table = $(
1043 '<table class="sortable">' +
1044 '<tr><th>THEAD</th></tr>' +
1045 '<tr><td><img alt="2"/></td></tr>' +
1046 '<tr><td>1</td></tr>' +
1047 '</table>'
1048 );
1049 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
1050
1051 assert.equal(
1052 $table.find( 'td' ).first().text(),
1053 '1',
1054 'Applied correct sorting order'
1055 );
1056 } );
1057
1058 QUnit.test( 'Sorting images using alt text (complex)', 1, function ( assert ) {
1059 var $table = $(
1060 '<table class="sortable">' +
1061 '<tr><th>THEAD</th></tr>' +
1062 '<tr><td><img alt="D" />A</td></tr>' +
1063 '<tr><td>CC</td></tr>' +
1064 '<tr><td><a><img alt="A" /></a>F</tr>' +
1065 '<tr><td><img alt="A" /><strong>E</strong></tr>' +
1066 '<tr><td><strong><img alt="A" />D</strong></tr>' +
1067 '<tr><td><img alt="A" />C</tr>' +
1068 '</table>'
1069 );
1070 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
1071
1072 assert.equal(
1073 $table.find( 'td' ).text(),
1074 'CDEFCCA',
1075 'Applied correct sorting order'
1076 );
1077 } );
1078
1079 QUnit.test( 'Sorting images using alt text (with format autodetection)', 1, function ( assert ) {
1080 var $table = $(
1081 '<table class="sortable">' +
1082 '<tr><th>THEAD</th></tr>' +
1083 '<tr><td><img alt="1" />7</td></tr>' +
1084 '<tr><td>1<img alt="6" /></td></tr>' +
1085 '<tr><td>5</td></tr>' +
1086 '<tr><td>4</td></tr>' +
1087 '</table>'
1088 );
1089 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
1090
1091 assert.equal(
1092 $table.find( 'td' ).text(),
1093 '4517',
1094 'Applied correct sorting order'
1095 );
1096 } );
1097
1098 QUnit.test( 'bug 38911 - The row with the largest amount of columns should receive the sort indicators', 3, function ( assert ) {
1099 var $table = $(
1100 '<table class="sortable">' +
1101 '<thead>' +
1102 '<tr><th rowspan="2" id="A1">A1</th><th colspan="2">B2a</th></tr>' +
1103 '<tr><th id="B2b">B2b</th><th id="C2b">C2b</th></tr>' +
1104 '</thead>' +
1105 '<tr><td>A</td><td>Aa</td><td>Ab</td></tr>' +
1106 '<tr><td>B</td><td>Ba</td><td>Bb</td></tr>' +
1107 '</table>'
1108 );
1109 $table.tablesorter();
1110
1111 assert.equal(
1112 $table.find( '#A1' ).attr( 'class' ),
1113 'headerSort',
1114 'The first column of the first row should be sortable'
1115 );
1116 assert.equal(
1117 $table.find( '#B2b' ).attr( 'class' ),
1118 'headerSort',
1119 'The th element of the 2nd row of the 2nd column should be sortable'
1120 );
1121 assert.equal(
1122 $table.find( '#C2b' ).attr( 'class' ),
1123 'headerSort',
1124 'The th element of the 2nd row of the 3rd column should be sortable'
1125 );
1126 } );
1127
1128 QUnit.test( 'rowspans in table headers should prefer the last row when rows are equal in length', 2, function ( assert ) {
1129 var $table = $(
1130 '<table class="sortable">' +
1131 '<thead>' +
1132 '<tr><th rowspan="2" id="A1">A1</th><th>B2a</th></tr>' +
1133 '<tr><th id="B2b">B2b</th></tr>' +
1134 '</thead>' +
1135 '<tr><td>A</td><td>Aa</td></tr>' +
1136 '<tr><td>B</td><td>Ba</td></tr>' +
1137 '</table>'
1138 );
1139 $table.tablesorter();
1140
1141 assert.equal(
1142 $table.find( '#A1' ).attr( 'class' ),
1143 'headerSort',
1144 'The first column of the first row should be sortable'
1145 );
1146 assert.equal(
1147 $table.find( '#B2b' ).attr( 'class' ),
1148 'headerSort',
1149 'The th element of the 2nd row of the 2nd column should be sortable'
1150 );
1151 } );
1152
1153 QUnit.test( 'holes in the table headers should not throw JS errors', 2, function ( assert ) {
1154 var $table = $(
1155 '<table class="sortable">' +
1156 '<thead>' +
1157 '<tr><th id="A1">A1</th><th>B1</th><th id="C1" rowspan="2">C1</th></tr>' +
1158 '<tr><th id="A2">A2</th></tr>' +
1159 '</thead>' +
1160 '<tr><td>A</td><td>Aa</td><td>Aaa</td></tr>' +
1161 '<tr><td>B</td><td>Ba</td><td>Bbb</td></tr>' +
1162 '</table>'
1163 );
1164 $table.tablesorter();
1165 assert.equal( 0,
1166 $table.find( '#A2' ).prop( 'headerIndex' ),
1167 'A2 should be a sort header'
1168 );
1169 assert.equal( 1, // should be 2
1170 $table.find( '#C1' ).prop( 'headerIndex' ),
1171 'C1 should be a sort header, but will sort the wrong column'
1172 );
1173 } );
1174
1175 // bug 41889 - exploding rowspans in more complex cases
1176 tableTestHTML(
1177 'Rowspan exploding with row headers',
1178 '<table class="sortable">' +
1179 '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' +
1180 '<tbody>' +
1181 '<tr><td>1</td><th rowspan="2">foo</th><td rowspan="2">bar</td><td>baz</td></tr>' +
1182 '<tr><td>2</td><td>baz</td></tr>' +
1183 '</tbody></table>',
1184 [
1185 [ '1', 'foo', 'bar', 'baz' ],
1186 [ '2', 'foo', 'bar', 'baz' ]
1187 ]
1188 );
1189
1190 // bug 53211 - exploding rowspans in more complex cases
1191 QUnit.test(
1192 'Rowspan exploding with row headers and colspans', 1, function ( assert ) {
1193 var $table = $( '<table class="sortable">' +
1194 '<thead><tr><th rowspan="2">n</th><th colspan="2">foo</th><th rowspan="2">baz</th></tr>' +
1195 '<tr><th>foo</th><th>bar</th></tr></thead>' +
1196 '<tbody>' +
1197 '<tr><td>1</td><td>foo</td><td>bar</td><td>baz</td></tr>' +
1198 '<tr><td>2</td><td>foo</td><td>bar</td><td>baz</td></tr>' +
1199 '</tbody></table>' );
1200
1201 $table.tablesorter();
1202 assert.equal( 2, $table.find( 'tr:eq(1) th:eq(1)').prop('headerIndex'), 'Incorrect index of sort header' );
1203 }
1204 );
1205
1206 tableTestHTML(
1207 'Rowspan exploding with colspanned cells',
1208 '<table class="sortable">' +
1209 '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' +
1210 '<tbody>' +
1211 '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td></tr>' +
1212 '<tr><td>2</td><td colspan="2">foobar</td></tr>' +
1213 '</tbody></table>',
1214 [
1215 [ '1', 'foo', 'bar', 'baz' ],
1216 [ '2', 'foobar', 'baz' ]
1217 ]
1218 );
1219
1220 tableTestHTML(
1221 'Rowspan exploding with colspanned cells (2)',
1222 '<table class="sortable">' +
1223 '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th><th>quux</th></tr></thead>' +
1224 '<tbody>' +
1225 '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td><td>quux</td></tr>' +
1226 '<tr><td>2</td><td colspan="2">foobar</td><td>quux</td></tr>' +
1227 '</tbody></table>',
1228 [
1229 [ '1', 'foo', 'bar', 'baz', 'quux' ],
1230 [ '2', 'foobar', 'baz', 'quux' ]
1231 ]
1232 );
1233
1234 tableTestHTML(
1235 'Rowspan exploding with rightmost rows spanning most',
1236 '<table class="sortable">' +
1237 '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th></tr></thead>' +
1238 '<tbody>' +
1239 '<tr><td>1</td><td rowspan="2">foo</td><td rowspan="4">bar</td></tr>' +
1240 '<tr><td>2</td></tr>' +
1241 '<tr><td>3</td><td rowspan="2">foo</td></tr>' +
1242 '<tr><td>4</td></tr>' +
1243 '</tbody></table>',
1244 [
1245 [ '1', 'foo', 'bar' ],
1246 [ '2', 'foo', 'bar' ],
1247 [ '3', 'foo', 'bar' ],
1248 [ '4', 'foo', 'bar' ]
1249 ]
1250 );
1251
1252 tableTestHTML(
1253 'Rowspan exploding with rightmost rows spanning most (2)',
1254 '<table class="sortable">' +
1255 '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' +
1256 '<tbody>' +
1257 '<tr><td>1</td><td rowspan="2">foo</td><td rowspan="4">bar</td><td>baz</td></tr>' +
1258 '<tr><td>2</td><td>baz</td></tr>' +
1259 '<tr><td>3</td><td rowspan="2">foo</td><td>baz</td></tr>' +
1260 '<tr><td>4</td><td>baz</td></tr>' +
1261 '</tbody></table>',
1262 [
1263 [ '1', 'foo', 'bar', 'baz' ],
1264 [ '2', 'foo', 'bar', 'baz' ],
1265 [ '3', 'foo', 'bar', 'baz' ],
1266 [ '4', 'foo', 'bar', 'baz' ]
1267 ]
1268 );
1269
1270 tableTestHTML(
1271 'Rowspan exploding with row-and-colspanned cells',
1272 '<table class="sortable">' +
1273 '<thead><tr><th id="sortme">n</th><th>foo1</th><th>foo2</th><th>bar</th><th>baz</th></tr></thead>' +
1274 '<tbody>' +
1275 '<tr><td>1</td><td rowspan="2">foo1</td><td rowspan="2">foo2</td><td rowspan="4">bar</td><td>baz</td></tr>' +
1276 '<tr><td>2</td><td>baz</td></tr>' +
1277 '<tr><td>3</td><td colspan="2" rowspan="2">foo</td><td>baz</td></tr>' +
1278 '<tr><td>4</td><td>baz</td></tr>' +
1279 '</tbody></table>',
1280 [
1281 [ '1', 'foo1', 'foo2', 'bar', 'baz' ],
1282 [ '2', 'foo1', 'foo2', 'bar', 'baz' ],
1283 [ '3', 'foo', 'bar', 'baz' ],
1284 [ '4', 'foo', 'bar', 'baz' ]
1285 ]
1286 );
1287
1288 tableTestHTML(
1289 'Rowspan exploding with uneven rowspan layout',
1290 '<table class="sortable">' +
1291 '<thead><tr><th id="sortme">n</th><th>foo1</th><th>foo2</th><th>foo3</th><th>bar</th><th>baz</th></tr></thead>' +
1292 '<tbody>' +
1293 '<tr><td>1</td><td rowspan="2">foo1</td><td rowspan="2">foo2</td><td rowspan="2">foo3</td><td>bar</td><td>baz</td></tr>' +
1294 '<tr><td>2</td><td rowspan="3">bar</td><td>baz</td></tr>' +
1295 '<tr><td>3</td><td rowspan="2">foo1</td><td rowspan="2">foo2</td><td rowspan="2">foo3</td><td>baz</td></tr>' +
1296 '<tr><td>4</td><td>baz</td></tr>' +
1297 '</tbody></table>',
1298 [
1299 [ '1', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ],
1300 [ '2', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ],
1301 [ '3', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ],
1302 [ '4', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ]
1303 ]
1304 );
1305
1306 }( jQuery, mediaWiki ) );