2 /*jshint onevar: false */
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 wgContentLanguage
: 'en'
11 QUnit
.module( 'jquery.tablesorter', QUnit
.newMwEnvironment({ config
: config
}) );
14 * Create an HTML table from an array of row arrays containing text strings.
15 * First row will be header row. No fancy rowspan/colspan stuff.
17 * @param {String[]} header
18 * @param {String[][]} data
21 function tableCreate( header
, data
) {
23 $table
= $( '<table class="sortable"><thead></thead><tbody></tbody></table>' ),
24 $thead
= $table
.find( 'thead' ),
25 $tbody
= $table
.find( 'tbody' ),
28 $.each( header
, function ( i
, str
) {
29 var $th
= $( '<th>' );
30 $th
.text( str
).appendTo( $tr
);
32 $tr
.appendTo( $thead
);
34 for ( i
= 0; i
< data
.length
; i
++ ) {
35 /*jshint loopfunc: true */
37 $.each( data
[i
], function ( j
, str
) {
38 var $td
= $( '<td>' );
39 $td
.text( str
).appendTo( $tr
);
41 $tr
.appendTo( $tbody
);
47 * Extract text from table.
49 * @param {jQuery} $table
52 function tableExtract( $table
) {
55 $table
.find( 'tbody' ).find( 'tr' ).each( function( i
, tr
) {
57 $( tr
).find( 'td,th' ).each( function( i
, td
) {
58 row
.push( $( td
).text() );
66 * Run a table test by building a table with the given data,
67 * running some callback on it, then checking the results.
69 * @param {String} msg text to pass on to qunit for the comparison
70 * @param {String[]} header cols to make the table
71 * @param {String[][]} data rows/cols to make the table
72 * @param {String[][]} expected rows/cols to compare against at end
73 * @param {function($table)} callback something to do with the table before we compare
75 function tableTest( msg
, header
, data
, expected
, callback
) {
76 QUnit
.test( msg
, 1, function ( assert
) {
77 var $table
= tableCreate( header
, data
);
79 // Give caller a chance to set up sorting and manipulate the table.
82 // Table sorting is done synchronously; if it ever needs to change back
83 // to asynchronous, we'll need a timeout or a callback here.
84 var extracted
= tableExtract( $table
);
85 assert
.deepEqual( extracted
, expected
, msg
);
89 function reversed(arr
) {
91 var arr2
= arr
.slice(0);
98 // Sample data set using planets named and their radius
99 var header
= [ 'Planet' , 'Radius (km)'],
100 mercury
= [ 'Mercury', '2439.7' ],
101 venus
= [ 'Venus' , '6051.8' ],
102 earth
= [ 'Earth' , '6371.0' ],
103 mars
= [ 'Mars' , '3390.0' ],
104 jupiter
= [ 'Jupiter', '69911' ],
105 saturn
= [ 'Saturn' , '58232' ];
108 var planets
= [mercury
, venus
, earth
, mars
, jupiter
, saturn
];
109 var ascendingName
= [earth
, jupiter
, mars
, mercury
, saturn
, venus
];
110 var ascendingRadius
= [mercury
, mars
, venus
, earth
, saturn
, jupiter
];
113 'Basic planet table: sorting initially - ascending by name',
117 function ( $table
) {
118 $table
.tablesorter( { sortList
: [ { 0: 'asc' } ] } );
122 'Basic planet table: sorting initially - descending by radius',
125 reversed(ascendingRadius
),
126 function ( $table
) {
127 $table
.tablesorter( { sortList
: [ { 1: 'desc' } ] } );
131 'Basic planet table: ascending by name',
135 function ( $table
) {
136 $table
.tablesorter();
137 $table
.find( '.headerSort:eq(0)' ).click();
141 'Basic planet table: ascending by name a second time',
145 function ( $table
) {
146 $table
.tablesorter();
147 $table
.find( '.headerSort:eq(0)' ).click();
151 'Basic planet table: descending by name',
154 reversed(ascendingName
),
155 function ( $table
) {
156 $table
.tablesorter();
157 $table
.find( '.headerSort:eq(0)' ).click().click();
161 'Basic planet table: ascending radius',
165 function ( $table
) {
166 $table
.tablesorter();
167 $table
.find( '.headerSort:eq(1)' ).click();
171 'Basic planet table: descending radius',
174 reversed(ascendingRadius
),
175 function ( $table
) {
176 $table
.tablesorter();
177 $table
.find( '.headerSort:eq(1)' ).click().click();
181 // Sample data set to test multiple column sorting
182 header
= [ 'column1' , 'column2'];
190 var initial
= [a2
, b3
, a1
, a3
, b2
, b1
];
191 var asc
= [a1
, a2
, a3
, b1
, b2
, b3
];
192 var descasc
= [b1
, b2
, b3
, a1
, a2
, a3
];
195 'Sorting multiple columns by passing sort list',
199 function ( $table
) {
201 { sortList
: [ { 0: 'asc' }, { 1: 'asc' } ] }
206 'Sorting multiple columns by programmatically triggering sort()',
210 function ( $table
) {
211 $table
.tablesorter();
212 $table
.data( 'tablesorter' ).sort(
213 [ { 0: 'desc' }, { 1: 'asc' } ]
218 'Reset to initial sorting by triggering sort() without any parameters',
222 function ( $table
) {
224 { sortList
: [ { 0: 'asc' }, { 1: 'asc' } ] }
226 $table
.data( 'tablesorter' ).sort(
227 [ { 0: 'desc' }, { 1: 'asc' } ]
229 $table
.data( 'tablesorter' ).sort();
232 QUnit
.test( 'Reset sorting making table appear unsorted', 3, function ( assert
) {
233 var $table
= tableCreate( header
, initial
);
235 { sortList
: [ { 0: 'desc' }, { 1: 'asc' } ] }
237 $table
.data( 'tablesorter' ).sort( [] );
240 $table
.find( 'th.headerSortUp' ).length
+ $table
.find( 'th.headerSortDown' ).length
,
242 'No sort specific sort classes addign to header cells'
246 $table
.find( 'th' ).first().attr( 'title' ),
247 mw
.msg( 'sort-ascending' ),
248 'First header cell has default title'
252 $table
.find( 'th' ).first().attr( 'title' ),
253 $table
.find( 'th' ).last().attr( 'title' ),
254 'Both header cells\' titles match'
258 // Sorting with colspans
259 header
= [ 'column1a' , 'column1b', 'column1c', 'column2' ];
261 aaa1
= [ 'A', 'A', 'A', '1' ],
262 aab5
= [ 'A', 'A', 'B', '5' ],
263 abc3
= [ 'A', 'B', 'C', '3' ],
264 bbc2
= [ 'B', 'B', 'C', '2' ],
265 caa4
= [ 'C', 'A', 'A', '4' ];
266 // initial is already declared above
267 initial
= [ aab5
, aaa1
, abc3
, bbc2
, caa4
];
268 tableTest( 'Sorting with colspanned headers: spanned column',
271 [ aaa1
, aab5
, abc3
, bbc2
, caa4
],
272 function ( $table
) {
273 // Make colspanned header for test
274 $table
.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
275 $table
.find( 'tr:eq(0) th:eq(0)' ).prop( 'colspan', '3' );
277 $table
.tablesorter();
278 $table
.find( '.headerSort:eq(0)' ).click();
281 tableTest( 'Sorting with colspanned headers: subsequent column',
284 [ aaa1
, bbc2
, abc3
, caa4
, aab5
],
285 function ( $table
) {
286 // Make colspanned header for test
287 $table
.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
288 $table
.find( 'tr:eq(0) th:eq(0)' ).prop( 'colspan', '3' );
290 $table
.tablesorter();
291 $table
.find( '.headerSort:eq(1)' ).click();
297 'Bug 28775: German-style (dmy) short numeric dates',
299 [ // German-style dates are day-month-year
306 [ // Sorted by ascending date
313 function ( $table
) {
314 mw
.config
.set( 'wgDefaultDateFormat', 'dmy' );
315 mw
.config
.set( 'wgContentLanguage', 'de' );
317 $table
.tablesorter();
318 $table
.find( '.headerSort:eq(0)' ).click();
323 'Bug 28775: American-style (mdy) short numeric dates',
325 [ // American-style dates are month-day-year
332 [ // Sorted by ascending date
339 function ( $table
) {
340 mw
.config
.set( 'wgDefaultDateFormat', 'mdy' );
342 $table
.tablesorter();
343 $table
.find( '.headerSort:eq(0)' ).click();
348 // Some randomly generated fake IPs
359 // Sort order should go octet by octet
371 'Bug 17141: IPv4 address sorting',
375 function ( $table
) {
376 $table
.tablesorter();
377 $table
.find( '.headerSort:eq(0)' ).click();
381 'Bug 17141: IPv4 address sorting (reverse)',
384 reversed(ipv4Sorted
),
385 function ( $table
) {
386 $table
.tablesorter();
387 $table
.find( '.headerSort:eq(0)' ).click().click();
392 // Some words with Umlauts
403 var umlautWordsSorted
= [
404 // Some words with Umlauts
416 'Accented Characters with custom collation',
420 function ( $table
) {
421 mw
.config
.set( 'tableSorterCollation', {
428 $table
.tablesorter();
429 $table
.find( '.headerSort:eq(0)' ).click();
433 QUnit
.test( 'Rowspan not exploded on init', 1, function ( assert
) {
434 var $table
= tableCreate( header
, planets
);
436 // Modify the table to have a multiple-row-spanning cell:
437 // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
438 $table
.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
439 // - Set rowspan for 2nd cell of 3rd row to 3.
440 // This covers the removed cell in the 4th and 5th row.
441 $table
.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' );
443 $table
.tablesorter();
446 $table
.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan' ),
448 'Rowspan not exploded'
452 var planetsRowspan
= [ [ 'Earth', '6051.8' ], jupiter
, [ 'Mars', '6051.8' ], mercury
, saturn
, venus
];
453 var planetsRowspanII
= [ jupiter
, mercury
, saturn
, venus
, [ 'Venus', '6371.0' ], [ 'Venus', '3390.0' ] ];
456 'Basic planet table: same value for multiple rows via rowspan',
460 function ( $table
) {
461 // Modify the table to have a multiple-row-spanning cell:
462 // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
463 $table
.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
464 // - Set rowspan for 2nd cell of 3rd row to 3.
465 // This covers the removed cell in the 4th and 5th row.
466 $table
.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' );
468 $table
.tablesorter();
469 $table
.find( '.headerSort:eq(0)' ).click();
473 'Basic planet table: same value for multiple rows via rowspan (sorting initially)',
477 function ( $table
) {
478 // Modify the table to have a multiple-row-spanning cell:
479 // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
480 $table
.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
481 // - Set rowspan for 2nd cell of 3rd row to 3.
482 // This covers the removed cell in the 4th and 5th row.
483 $table
.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' );
485 $table
.tablesorter( { sortList
: [ { 0: 'asc' } ] } );
489 'Basic planet table: Same value for multiple rows via rowspan II',
493 function ( $table
) {
494 // Modify the table to have a multiple-row-spanning cell:
495 // - Remove 1st cell of 4th row, and, 1st cell or 5th row.
496 $table
.find( 'tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)' ).remove();
497 // - Set rowspan for 1st cell of 3rd row to 3.
498 // This covers the removed cell in the 4th and 5th row.
499 $table
.find( 'tr:eq(2) td:eq(0)' ).prop( 'rowspan', '3' );
501 $table
.tablesorter();
502 $table
.find( '.headerSort:eq(0)' ).click();
506 var complexMDYDates
= [
507 // Some words with Umlauts
508 ['January, 19 2010'],
515 var complexMDYSorted
= [
519 ['January, 19 2010'],
524 'Complex date parsing I',
528 function ( $table
) {
529 mw
.config
.set( 'wgDefaultDateFormat', 'mdy' );
531 $table
.tablesorter();
532 $table
.find( '.headerSort:eq(0)' ).click();
536 var currencyUnsorted
= [
546 var currencySorted
= [
553 // Comma's sort after dots
554 // Not intentional but test to detect changes
559 'Currency parsing I',
563 function ( $table
) {
564 $table
.tablesorter();
565 $table
.find( '.headerSort:eq(0)' ).click();
569 var ascendingNameLegacy
= ascendingName
.slice(0);
570 ascendingNameLegacy
[4] = ascendingNameLegacy
[5];
571 ascendingNameLegacy
.pop();
574 'Legacy compat with .sortbottom',
579 $table
.find( 'tr:last' ).addClass( 'sortbottom' );
580 $table
.tablesorter();
581 $table
.find( '.headerSort:eq(0)' ).click();
585 QUnit
.test( 'Test detection routine', function ( assert
) {
588 '<table class="sortable">' +
589 '<caption>CAPTION</caption>' +
590 '<tr><th>THEAD</th></tr>' +
591 '<tr><td>1</td></tr>' +
592 '<tr class="sortbottom"><td>text</td></tr>' +
595 $table
.tablesorter();
596 $table
.find( '.headerSort:eq(0)' ).click();
599 $table
.data( 'tablesorter' ).config
.parsers
[0].id
,
601 'Correctly detected column content skipping sortbottom'
605 /** FIXME: the diff output is not very readeable. */
606 QUnit
.test( 'bug 32047 - caption must be before thead', function ( assert
) {
609 '<table class="sortable">' +
610 '<caption>CAPTION</caption>' +
611 '<tr><th>THEAD</th></tr>' +
612 '<tr><td>A</td></tr>' +
613 '<tr><td>B</td></tr>' +
614 '<tr class="sortbottom"><td>TFOOT</td></tr>' +
617 $table
.tablesorter();
620 $table
.children( ).get( 0 ).nodeName
,
622 'First element after <thead> must be <caption> (bug 32047)'
626 QUnit
.test( 'data-sort-value attribute, when available, should override sorting position', function ( assert
) {
629 // Example 1: All cells except one cell without data-sort-value,
630 // which should be sorted at it's text content value.
632 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
634 '<tr><td>Cheetah</td></tr>' +
635 '<tr><td data-sort-value="Apple">Bird</td></tr>' +
636 '<tr><td data-sort-value="Bananna">Ferret</td></tr>' +
637 '<tr><td data-sort-value="Drupe">Elephant</td></tr>' +
638 '<tr><td data-sort-value="Cherry">Dolphin</td></tr>' +
641 $table
.tablesorter().find( '.headerSort:eq(0)' ).click();
644 $table
.find( 'tbody > tr' ).each( function( i
, tr
) {
645 $( tr
).find( 'td' ).each( function( i
, td
) {
647 data
: $( td
).data( 'sortValue' ),
653 assert
.deepEqual( data
, [
670 ], 'Order matches expected order (based on data-sort-value attribute values)' );
674 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
676 '<tr><td>D</td></tr>' +
677 '<tr><td data-sort-value="E">A</td></tr>' +
678 '<tr><td>B</td></tr>' +
679 '<tr><td>G</td></tr>' +
680 '<tr><td data-sort-value="F">C</td></tr>' +
683 $table
.tablesorter().find( '.headerSort:eq(0)' ).click();
686 $table
.find( 'tbody > tr' ).each( function ( i
, tr
) {
687 $( tr
).find( 'td' ).each( function ( i
, td
) {
689 data
: $( td
).data( 'sortValue' ),
695 assert
.deepEqual( data
, [
712 ], 'Order matches expected order (based on data-sort-value attribute values)' );
714 // Example 3: Test that live changes are used from data-sort-value,
715 // even if they change after the tablesorter is constructed (bug 38152).
717 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
719 '<tr><td>D</td></tr>' +
720 '<tr><td data-sort-value="1">A</td></tr>' +
721 '<tr><td>B</td></tr>' +
722 '<tr><td data-sort-value="2">G</td></tr>' +
723 '<tr><td>C</td></tr>' +
726 // initialize table sorter and sort once
729 .find( '.headerSort:eq(0)' ).click();
731 // Change the sortValue data properties (bug 38152)
733 $table
.find( 'td:contains(A)' ).data( 'sortValue', 3 );
735 $table
.find( 'td:contains(B)' ).data( 'sortValue', 1 );
736 // - remove data, bring back attribute: 2
737 $table
.find( 'td:contains(G)' ).removeData( 'sortValue' );
739 // Now sort again (twice, so it is back at Ascending)
740 $table
.find( '.headerSort:eq(0)' ).click();
741 $table
.find( '.headerSort:eq(0)' ).click();
744 $table
.find( 'tbody > tr' ).each( function( i
, tr
) {
745 $( tr
).find( 'td' ).each( function( i
, td
) {
747 data
: $( td
).data( 'sortValue' ),
753 assert
.deepEqual( data
, [
770 ], 'Order matches expected order, using the current sortValue in $.data()' );
791 tableTest( 'bug 8115: sort numbers with commas (ascending)',
792 ['Numbers'], numbers
, numbersAsc
,
794 $table
.tablesorter();
795 $table
.find( '.headerSort:eq(0)' ).click();
799 tableTest( 'bug 8115: sort numbers with commas (descending)',
800 ['Numbers'], numbers
, reversed(numbersAsc
),
802 $table
.tablesorter();
803 $table
.find( '.headerSort:eq(0)' ).click().click();
806 // TODO add numbers sorting tests for bug 8115 with a different language
808 QUnit
.test( 'bug 32888 - Tables inside a tableheader cell', 2, function ( assert
) {
811 '<table class="sortable" id="mw-bug-32888">' +
812 '<tr><th>header<table id="mw-bug-32888-2">'+
813 '<tr><th>1</th><th>2</th></tr>' +
814 '</table></th></tr>' +
815 '<tr><td>A</td></tr>' +
816 '<tr><td>B</td></tr>' +
819 $table
.tablesorter();
822 $table
.find('> thead:eq(0) > tr > th.headerSort').length
,
824 'Child tables inside a headercell should not interfere with sortable headers (bug 32888)'
827 $( '#mw-bug-32888-2' ).find('th.headerSort').length
,
829 'The headers of child tables inside a headercell should not be sortable themselves (bug 32888)'
834 var correctDateSorting1
= [
836 ['05 February 2010'],
840 var correctDateSortingSorted1
= [
847 'Correct date sorting I',
850 correctDateSortingSorted1
,
851 function ( $table
) {
852 mw
.config
.set( 'wgDefaultDateFormat', 'mdy' );
854 $table
.tablesorter();
855 $table
.find( '.headerSort:eq(0)' ).click();
859 var correctDateSorting2
= [
861 ['February 05 2010'],
865 var correctDateSortingSorted2
= [
872 'Correct date sorting II',
875 correctDateSortingSorted2
,
876 function ( $table
) {
877 mw
.config
.set( 'wgDefaultDateFormat', 'dmy' );
879 $table
.tablesorter();
880 $table
.find( '.headerSort:eq(0)' ).click();
884 QUnit
.test( 'Sorting images using alt text', function ( assert
) {
886 '<table class="sortable">' +
887 '<tr><th>THEAD</th></tr>' +
888 '<tr><td><img alt="2"/></td></tr>' +
889 '<tr><td>1</td></tr>' +
892 $table
.tablesorter().find( '.headerSort:eq(0)' ).click();
895 $table
.find( 'td' ).first().text(),
897 'Applied correct sorting order'
901 QUnit
.test( 'Sorting images using alt text (complex)', function ( assert
) {
903 '<table class="sortable">' +
904 '<tr><th>THEAD</th></tr>' +
905 '<tr><td><img alt="D" />A</td></tr>' +
906 '<tr><td>CC</td></tr>' +
907 '<tr><td><a><img alt="A" /></a>F</tr>' +
908 '<tr><td><img alt="A" /><strong>E</strong></tr>' +
909 '<tr><td><strong><img alt="A" />D</strong></tr>' +
910 '<tr><td><img alt="A" />C</tr>' +
913 $table
.tablesorter().find( '.headerSort:eq(0)' ).click();
916 $table
.find( 'td' ).text(),
918 'Applied correct sorting order'
922 QUnit
.test( 'Sorting images using alt text (with format autodetection)', function ( assert
) {
924 '<table class="sortable">' +
925 '<tr><th>THEAD</th></tr>' +
926 '<tr><td><img alt="1" />7</td></tr>' +
927 '<tr><td>1<img alt="6" /></td></tr>' +
928 '<tr><td>5</td></tr>' +
929 '<tr><td>4</td></tr>' +
932 $table
.tablesorter().find( '.headerSort:eq(0)' ).click();
935 $table
.find( 'td' ).text(),
937 'Applied correct sorting order'
941 }( jQuery
, mediaWiki
) );