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
: [
124 'Basic planet table: sorting initially - descending by radius',
127 reversed( ascendingRadius
),
128 function ( $table
) {
129 $table
.tablesorter( { sortList
: [
135 'Basic planet table: ascending by name',
139 function ( $table
) {
140 $table
.tablesorter();
141 $table
.find( '.headerSort:eq(0)' ).click();
145 'Basic planet table: ascending by name a second time',
149 function ( $table
) {
150 $table
.tablesorter();
151 $table
.find( '.headerSort:eq(0)' ).click();
155 'Basic planet table: descending by name',
158 reversed( ascendingName
),
159 function ( $table
) {
160 $table
.tablesorter();
161 $table
.find( '.headerSort:eq(0)' ).click().click();
165 'Basic planet table: ascending radius',
169 function ( $table
) {
170 $table
.tablesorter();
171 $table
.find( '.headerSort:eq(1)' ).click();
175 'Basic planet table: descending radius',
178 reversed( ascendingRadius
),
179 function ( $table
) {
180 $table
.tablesorter();
181 $table
.find( '.headerSort:eq(1)' ).click().click();
185 // Sample data set to test multiple column sorting
186 header
= [ 'column1' , 'column2'];
194 var initial
= [a2
, b3
, a1
, a3
, b2
, b1
];
195 var asc
= [a1
, a2
, a3
, b1
, b2
, b3
];
196 var descasc
= [b1
, b2
, b3
, a1
, a2
, a3
];
199 'Sorting multiple columns by passing sort list',
203 function ( $table
) {
213 'Sorting multiple columns by programmatically triggering sort()',
217 function ( $table
) {
218 $table
.tablesorter();
219 $table
.data( 'tablesorter' ).sort(
228 'Reset to initial sorting by triggering sort() without any parameters',
232 function ( $table
) {
239 $table
.data( 'tablesorter' ).sort(
245 $table
.data( 'tablesorter' ).sort();
248 QUnit
.test( 'Reset sorting making table appear unsorted', 3, function ( assert
) {
249 var $table
= tableCreate( header
, initial
);
256 $table
.data( 'tablesorter' ).sort( [] );
259 $table
.find( 'th.headerSortUp' ).length
+ $table
.find( 'th.headerSortDown' ).length
,
261 'No sort specific sort classes addign to header cells'
265 $table
.find( 'th' ).first().attr( 'title' ),
266 mw
.msg( 'sort-ascending' ),
267 'First header cell has default title'
271 $table
.find( 'th' ).first().attr( 'title' ),
272 $table
.find( 'th' ).last().attr( 'title' ),
273 'Both header cells\' titles match'
277 // Sorting with colspans
278 header
= [ 'column1a' , 'column1b', 'column1c', 'column2' ];
280 aaa1
= [ 'A', 'A', 'A', '1' ],
281 aab5
= [ 'A', 'A', 'B', '5' ],
282 abc3
= [ 'A', 'B', 'C', '3' ],
283 bbc2
= [ 'B', 'B', 'C', '2' ],
284 caa4
= [ 'C', 'A', 'A', '4' ];
285 // initial is already declared above
286 initial
= [ aab5
, aaa1
, abc3
, bbc2
, caa4
];
287 tableTest( 'Sorting with colspanned headers: spanned column',
290 [ aaa1
, aab5
, abc3
, bbc2
, caa4
],
291 function ( $table
) {
292 // Make colspanned header for test
293 $table
.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
294 $table
.find( 'tr:eq(0) th:eq(0)' ).prop( 'colspan', '3' );
296 $table
.tablesorter();
297 $table
.find( '.headerSort:eq(0)' ).click();
300 tableTest( 'Sorting with colspanned headers: subsequent column',
303 [ aaa1
, bbc2
, abc3
, caa4
, aab5
],
304 function ( $table
) {
305 // Make colspanned header for test
306 $table
.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
307 $table
.find( 'tr:eq(0) th:eq(0)' ).prop( 'colspan', '3' );
309 $table
.tablesorter();
310 $table
.find( '.headerSort:eq(1)' ).click();
316 'Bug 28775: German-style (dmy) short numeric dates',
319 // German-style dates are day-month-year
327 // Sorted by ascending date
334 function ( $table
) {
335 mw
.config
.set( 'wgDefaultDateFormat', 'dmy' );
336 mw
.config
.set( 'wgContentLanguage', 'de' );
338 $table
.tablesorter();
339 $table
.find( '.headerSort:eq(0)' ).click();
344 'Bug 28775: American-style (mdy) short numeric dates',
347 // American-style dates are month-day-year
355 // Sorted by ascending date
362 function ( $table
) {
363 mw
.config
.set( 'wgDefaultDateFormat', 'mdy' );
365 $table
.tablesorter();
366 $table
.find( '.headerSort:eq(0)' ).click();
371 // Some randomly generated fake IPs
382 // Sort order should go octet by octet
394 'Bug 17141: IPv4 address sorting',
398 function ( $table
) {
399 $table
.tablesorter();
400 $table
.find( '.headerSort:eq(0)' ).click();
404 'Bug 17141: IPv4 address sorting (reverse)',
407 reversed( ipv4Sorted
),
408 function ( $table
) {
409 $table
.tablesorter();
410 $table
.find( '.headerSort:eq(0)' ).click().click();
415 // Some words with Umlauts
426 var umlautWordsSorted
= [
427 // Some words with Umlauts
439 'Accented Characters with custom collation',
443 function ( $table
) {
444 mw
.config
.set( 'tableSorterCollation', {
451 $table
.tablesorter();
452 $table
.find( '.headerSort:eq(0)' ).click();
456 QUnit
.test( 'Rowspan not exploded on init', 1, function ( assert
) {
457 var $table
= tableCreate( header
, planets
);
459 // Modify the table to have a multiple-row-spanning cell:
460 // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
461 $table
.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
462 // - Set rowspan for 2nd cell of 3rd row to 3.
463 // This covers the removed cell in the 4th and 5th row.
464 $table
.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' );
466 $table
.tablesorter();
469 $table
.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan' ),
471 'Rowspan not exploded'
475 var planetsRowspan
= [
476 [ 'Earth', '6051.8' ],
478 [ 'Mars', '6051.8' ],
483 var planetsRowspanII
= [ jupiter
, mercury
, saturn
, venus
, [ 'Venus', '6371.0' ], [ 'Venus', '3390.0' ] ];
486 'Basic planet table: same value for multiple rows via rowspan',
490 function ( $table
) {
491 // Modify the table to have a multiple-row-spanning cell:
492 // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
493 $table
.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
494 // - Set rowspan for 2nd cell of 3rd row to 3.
495 // This covers the removed cell in the 4th and 5th row.
496 $table
.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' );
498 $table
.tablesorter();
499 $table
.find( '.headerSort:eq(0)' ).click();
503 'Basic planet table: same value for multiple rows via rowspan (sorting initially)',
507 function ( $table
) {
508 // Modify the table to have a multiple-row-spanning cell:
509 // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
510 $table
.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
511 // - Set rowspan for 2nd cell of 3rd row to 3.
512 // This covers the removed cell in the 4th and 5th row.
513 $table
.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' );
515 $table
.tablesorter( { sortList
: [
521 'Basic planet table: Same value for multiple rows via rowspan II',
525 function ( $table
) {
526 // Modify the table to have a multiple-row-spanning cell:
527 // - Remove 1st cell of 4th row, and, 1st cell or 5th row.
528 $table
.find( 'tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)' ).remove();
529 // - Set rowspan for 1st cell of 3rd row to 3.
530 // This covers the removed cell in the 4th and 5th row.
531 $table
.find( 'tr:eq(2) td:eq(0)' ).prop( 'rowspan', '3' );
533 $table
.tablesorter();
534 $table
.find( '.headerSort:eq(0)' ).click();
538 var complexMDYDates
= [
539 // Some words with Umlauts
540 ['January, 19 2010'],
547 var complexMDYSorted
= [
551 ['January, 19 2010'],
556 'Complex date parsing I',
560 function ( $table
) {
561 mw
.config
.set( 'wgDefaultDateFormat', 'mdy' );
563 $table
.tablesorter();
564 $table
.find( '.headerSort:eq(0)' ).click();
568 var currencyUnsorted
= [
578 var currencySorted
= [
585 // Comma's sort after dots
586 // Not intentional but test to detect changes
591 'Currency parsing I',
595 function ( $table
) {
596 $table
.tablesorter();
597 $table
.find( '.headerSort:eq(0)' ).click();
601 var ascendingNameLegacy
= ascendingName
.slice( 0 );
602 ascendingNameLegacy
[4] = ascendingNameLegacy
[5];
603 ascendingNameLegacy
.pop();
606 'Legacy compat with .sortbottom',
610 function ( $table
) {
611 $table
.find( 'tr:last' ).addClass( 'sortbottom' );
612 $table
.tablesorter();
613 $table
.find( '.headerSort:eq(0)' ).click();
617 QUnit
.test( 'Test detection routine', function ( assert
) {
620 '<table class="sortable">' +
621 '<caption>CAPTION</caption>' +
622 '<tr><th>THEAD</th></tr>' +
623 '<tr><td>1</td></tr>' +
624 '<tr class="sortbottom"><td>text</td></tr>' +
627 $table
.tablesorter();
628 $table
.find( '.headerSort:eq(0)' ).click();
631 $table
.data( 'tablesorter' ).config
.parsers
[0].id
,
633 'Correctly detected column content skipping sortbottom'
637 /** FIXME: the diff output is not very readeable. */
638 QUnit
.test( 'bug 32047 - caption must be before thead', function ( assert
) {
641 '<table class="sortable">' +
642 '<caption>CAPTION</caption>' +
643 '<tr><th>THEAD</th></tr>' +
644 '<tr><td>A</td></tr>' +
645 '<tr><td>B</td></tr>' +
646 '<tr class="sortbottom"><td>TFOOT</td></tr>' +
649 $table
.tablesorter();
652 $table
.children().get( 0 ).nodeName
,
654 'First element after <thead> must be <caption> (bug 32047)'
658 QUnit
.test( 'data-sort-value attribute, when available, should override sorting position', function ( assert
) {
661 // Example 1: All cells except one cell without data-sort-value,
662 // which should be sorted at it's text content value.
664 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
666 '<tr><td>Cheetah</td></tr>' +
667 '<tr><td data-sort-value="Apple">Bird</td></tr>' +
668 '<tr><td data-sort-value="Bananna">Ferret</td></tr>' +
669 '<tr><td data-sort-value="Drupe">Elephant</td></tr>' +
670 '<tr><td data-sort-value="Cherry">Dolphin</td></tr>' +
673 $table
.tablesorter().find( '.headerSort:eq(0)' ).click();
676 $table
.find( 'tbody > tr' ).each( function ( i
, tr
) {
677 $( tr
).find( 'td' ).each( function ( i
, td
) {
679 data
: $( td
).data( 'sortValue' ),
685 assert
.deepEqual( data
, [
706 ], 'Order matches expected order (based on data-sort-value attribute values)' );
710 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
712 '<tr><td>D</td></tr>' +
713 '<tr><td data-sort-value="E">A</td></tr>' +
714 '<tr><td>B</td></tr>' +
715 '<tr><td>G</td></tr>' +
716 '<tr><td data-sort-value="F">C</td></tr>' +
719 $table
.tablesorter().find( '.headerSort:eq(0)' ).click();
722 $table
.find( 'tbody > tr' ).each( function ( i
, tr
) {
723 $( tr
).find( 'td' ).each( function ( i
, td
) {
725 data
: $( td
).data( 'sortValue' ),
731 assert
.deepEqual( data
, [
752 ], 'Order matches expected order (based on data-sort-value attribute values)' );
754 // Example 3: Test that live changes are used from data-sort-value,
755 // even if they change after the tablesorter is constructed (bug 38152).
757 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
759 '<tr><td>D</td></tr>' +
760 '<tr><td data-sort-value="1">A</td></tr>' +
761 '<tr><td>B</td></tr>' +
762 '<tr><td data-sort-value="2">G</td></tr>' +
763 '<tr><td>C</td></tr>' +
766 // initialize table sorter and sort once
769 .find( '.headerSort:eq(0)' ).click();
771 // Change the sortValue data properties (bug 38152)
773 $table
.find( 'td:contains(A)' ).data( 'sortValue', 3 );
775 $table
.find( 'td:contains(B)' ).data( 'sortValue', 1 );
776 // - remove data, bring back attribute: 2
777 $table
.find( 'td:contains(G)' ).removeData( 'sortValue' );
779 // Now sort again (twice, so it is back at Ascending)
780 $table
.find( '.headerSort:eq(0)' ).click();
781 $table
.find( '.headerSort:eq(0)' ).click();
784 $table
.find( 'tbody > tr' ).each( function ( i
, tr
) {
785 $( tr
).find( 'td' ).each( function ( i
, td
) {
787 data
: $( td
).data( 'sortValue' ),
793 assert
.deepEqual( data
, [
814 ], 'Order matches expected order, using the current sortValue in $.data()' );
835 tableTest( 'bug 8115: sort numbers with commas (ascending)',
836 ['Numbers'], numbers
, numbersAsc
,
837 function ( $table
) {
838 $table
.tablesorter();
839 $table
.find( '.headerSort:eq(0)' ).click();
843 tableTest( 'bug 8115: sort numbers with commas (descending)',
844 ['Numbers'], numbers
, reversed( numbersAsc
),
845 function ( $table
) {
846 $table
.tablesorter();
847 $table
.find( '.headerSort:eq(0)' ).click().click();
850 // TODO add numbers sorting tests for bug 8115 with a different language
852 QUnit
.test( 'bug 32888 - Tables inside a tableheader cell', 2, function ( assert
) {
855 '<table class="sortable" id="mw-bug-32888">' +
856 '<tr><th>header<table id="mw-bug-32888-2">' +
857 '<tr><th>1</th><th>2</th></tr>' +
858 '</table></th></tr>' +
859 '<tr><td>A</td></tr>' +
860 '<tr><td>B</td></tr>' +
863 $table
.tablesorter();
866 $table
.find( '> thead:eq(0) > tr > th.headerSort' ).length
,
868 'Child tables inside a headercell should not interfere with sortable headers (bug 32888)'
871 $( '#mw-bug-32888-2' ).find( 'th.headerSort' ).length
,
873 'The headers of child tables inside a headercell should not be sortable themselves (bug 32888)'
878 var correctDateSorting1
= [
880 ['05 February 2010'],
884 var correctDateSortingSorted1
= [
891 'Correct date sorting I',
894 correctDateSortingSorted1
,
895 function ( $table
) {
896 mw
.config
.set( 'wgDefaultDateFormat', 'mdy' );
898 $table
.tablesorter();
899 $table
.find( '.headerSort:eq(0)' ).click();
903 var correctDateSorting2
= [
905 ['February 05 2010'],
909 var correctDateSortingSorted2
= [
916 'Correct date sorting II',
919 correctDateSortingSorted2
,
920 function ( $table
) {
921 mw
.config
.set( 'wgDefaultDateFormat', 'dmy' );
923 $table
.tablesorter();
924 $table
.find( '.headerSort:eq(0)' ).click();
928 QUnit
.test( 'Sorting images using alt text', function ( assert
) {
930 '<table class="sortable">' +
931 '<tr><th>THEAD</th></tr>' +
932 '<tr><td><img alt="2"/></td></tr>' +
933 '<tr><td>1</td></tr>' +
936 $table
.tablesorter().find( '.headerSort:eq(0)' ).click();
939 $table
.find( 'td' ).first().text(),
941 'Applied correct sorting order'
945 QUnit
.test( 'Sorting images using alt text (complex)', function ( assert
) {
947 '<table class="sortable">' +
948 '<tr><th>THEAD</th></tr>' +
949 '<tr><td><img alt="D" />A</td></tr>' +
950 '<tr><td>CC</td></tr>' +
951 '<tr><td><a><img alt="A" /></a>F</tr>' +
952 '<tr><td><img alt="A" /><strong>E</strong></tr>' +
953 '<tr><td><strong><img alt="A" />D</strong></tr>' +
954 '<tr><td><img alt="A" />C</tr>' +
957 $table
.tablesorter().find( '.headerSort:eq(0)' ).click();
960 $table
.find( 'td' ).text(),
962 'Applied correct sorting order'
966 QUnit
.test( 'Sorting images using alt text (with format autodetection)', function ( assert
) {
968 '<table class="sortable">' +
969 '<tr><th>THEAD</th></tr>' +
970 '<tr><td><img alt="1" />7</td></tr>' +
971 '<tr><td>1<img alt="6" /></td></tr>' +
972 '<tr><td>5</td></tr>' +
973 '<tr><td>4</td></tr>' +
976 $table
.tablesorter().find( '.headerSort:eq(0)' ).click();
979 $table
.find( 'td' ).text(),
981 'Applied correct sorting order'
984 }( jQuery
, mediaWiki
) );