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 wgSeparatorTransformTable: ['', ''],
9 wgDigitTransformTable: ['', ''],
10 wgContentLanguage: 'en'
13 QUnit.module( 'jquery.tablesorter', QUnit.newMwEnvironment( { config: config } ) );
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.
19 * @param {String[]} header
20 * @param {String[][]} data
23 function tableCreate( header, data ) {
25 $table = $( '<table class="sortable"><thead></thead><tbody></tbody></table>' ),
26 $thead = $table.find( 'thead' ),
27 $tbody = $table.find( 'tbody' ),
30 $.each( header, function ( i, str ) {
31 var $th = $( '<th>' );
32 $th.text( str ).appendTo( $tr );
34 $tr.appendTo( $thead );
36 for ( i = 0; i < data.length; i++ ) {
37 /*jshint loopfunc: true */
39 $.each( data[i], function ( j, str ) {
40 var $td = $( '<td>' );
41 $td.text( str ).appendTo( $tr );
43 $tr.appendTo( $tbody );
49 * Extract text from table.
51 * @param {jQuery} $table
54 function tableExtract( $table ) {
57 $table.find( 'tbody' ).find( 'tr' ).each( function ( i, tr ) {
59 $( tr ).find( 'td,th' ).each( function ( i, td ) {
60 row.push( $( td ).text() );
68 * Run a table test by building a table with the given data,
69 * running some callback on it, then checking the results.
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
77 function tableTest( msg, header, data, expected, callback ) {
78 QUnit.test( msg, 1, function ( assert ) {
79 var $table = tableCreate( header, data );
81 // Give caller a chance to set up sorting and manipulate the table.
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 );
92 * Run a table test by building a table with the given HTML,
93 * running some callback on it, then checking the results.
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
100 function tableTestHTML( msg, html, expected, callback ) {
101 QUnit.test( msg, 1, function ( assert ) {
102 var $table = $( html );
104 // Give caller a chance to set up sorting and manipulate the table.
108 $table.tablesorter();
109 $table.find( '#sortme' ).click();
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 );
119 function reversed( arr ) {
121 var arr2 = arr.slice( 0 );
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' ];
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];
143 'Basic planet table: sorting initially - ascending by name',
147 function ( $table ) {
148 $table.tablesorter( { sortList: [
154 'Basic planet table: sorting initially - descending by radius',
157 reversed( ascendingRadius ),
158 function ( $table ) {
159 $table.tablesorter( { sortList: [
165 'Basic planet table: ascending by name',
169 function ( $table ) {
170 $table.tablesorter();
171 $table.find( '.headerSort:eq(0)' ).click();
175 'Basic planet table: ascending by name a second time',
179 function ( $table ) {
180 $table.tablesorter();
181 $table.find( '.headerSort:eq(0)' ).click();
185 'Basic planet table: ascending by name (multiple clicks)',
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();
197 'Basic planet table: descending by name',
200 reversed( ascendingName ),
201 function ( $table ) {
202 $table.tablesorter();
203 $table.find( '.headerSort:eq(0)' ).click().click();
207 'Basic planet table: ascending radius',
211 function ( $table ) {
212 $table.tablesorter();
213 $table.find( '.headerSort:eq(1)' ).click();
217 'Basic planet table: descending radius',
220 reversed( ascendingRadius ),
221 function ( $table ) {
222 $table.tablesorter();
223 $table.find( '.headerSort:eq(1)' ).click().click();
227 // Sample data set to test multiple column sorting
228 header = [ 'column1' , 'column2'];
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];
241 'Sorting multiple columns by passing sort list',
245 function ( $table ) {
255 'Sorting multiple columns by programmatically triggering sort()',
259 function ( $table ) {
260 $table.tablesorter();
261 $table.data( 'tablesorter' ).sort(
270 'Reset to initial sorting by triggering sort() without any parameters',
274 function ( $table ) {
281 $table.data( 'tablesorter' ).sort(
287 $table.data( 'tablesorter' ).sort();
291 'Sort via click event after having initialized the tablesorter with initial sorting',
295 function ( $table ) {
297 { sortList: [ { 0: 'asc' }, { 1: 'asc' } ] }
299 $table.find( '.headerSort:eq(0)' ).click();
303 'Multi-sort via click event after having initialized the tablesorter with initial sorting',
307 function ( $table ) {
309 { sortList: [ { 0: 'desc' }, { 1: 'desc' } ] }
311 $table.find( '.headerSort:eq(0)' ).click();
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 );
319 QUnit.test( 'Reset sorting making table appear unsorted', 3, function ( assert ) {
320 var $table = tableCreate( header, initial );
327 $table.data( 'tablesorter' ).sort( [] );
330 $table.find( 'th.headerSortUp' ).length + $table.find( 'th.headerSortDown' ).length,
332 'No sort specific sort classes addign to header cells'
336 $table.find( 'th' ).first().attr( 'title' ),
337 mw.msg( 'sort-ascending' ),
338 'First header cell has default title'
342 $table.find( 'th' ).first().attr( 'title' ),
343 $table.find( 'th' ).last().attr( 'title' ),
344 'Both header cells\' titles match'
348 // Sorting with colspans
349 header = [ 'column1a' , 'column1b', 'column1c', 'column2' ];
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',
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' );
367 $table.tablesorter();
368 $table.find( '.headerSort:eq(0)' ).click();
371 tableTest( 'Sorting with colspanned headers: sort spanned column twice',
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' );
380 $table.tablesorter();
381 $table.find( '.headerSort:eq(0)' ).click();
382 $table.find( '.headerSort:eq(0)' ).click();
385 tableTest( 'Sorting with colspanned headers: subsequent column',
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' );
394 $table.tablesorter();
395 $table.find( '.headerSort:eq(1)' ).click();
398 tableTest( 'Sorting with colspanned headers: sort subsequent column twice',
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' );
407 $table.tablesorter();
408 $table.find( '.headerSort:eq(1)' ).click();
409 $table.find( '.headerSort:eq(1)' ).click();
415 'Basic planet table: one unsortable column',
419 function ( $table ) {
420 $table.find( 'tr:eq(0) > th:eq(0)' ).addClass( 'unsortable' );
422 $table.tablesorter();
423 $table.find( 'tr:eq(0) > th:eq(0)' ).click();
429 'Bug 28775: German-style (dmy) short numeric dates',
432 // German-style dates are day-month-year
440 // Sorted by ascending date
447 function ( $table ) {
448 mw.config.set( 'wgDefaultDateFormat', 'dmy' );
449 mw.config.set( 'wgContentLanguage', 'de' );
451 $table.tablesorter();
452 $table.find( '.headerSort:eq(0)' ).click();
457 'Bug 28775: American-style (mdy) short numeric dates',
460 // American-style dates are month-day-year
468 // Sorted by ascending date
475 function ( $table ) {
476 mw.config.set( 'wgDefaultDateFormat', 'mdy' );
478 $table.tablesorter();
479 $table.find( '.headerSort:eq(0)' ).click();
484 // Some randomly generated fake IPs
495 // Sort order should go octet by octet
507 'Bug 17141: IPv4 address sorting',
511 function ( $table ) {
512 $table.tablesorter();
513 $table.find( '.headerSort:eq(0)' ).click();
517 'Bug 17141: IPv4 address sorting (reverse)',
520 reversed( ipv4Sorted ),
521 function ( $table ) {
522 $table.tablesorter();
523 $table.find( '.headerSort:eq(0)' ).click().click();
528 // Some words with Umlauts
539 var umlautWordsSorted = [
540 // Some words with Umlauts
552 'Accented Characters with custom collation',
556 function ( $table ) {
557 mw.config.set( 'tableSorterCollation', {
564 $table.tablesorter();
565 $table.find( '.headerSort:eq(0)' ).click();
569 QUnit.test( 'Rowspan not exploded on init', 1, function ( assert ) {
570 var $table = tableCreate( header, planets );
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' );
579 $table.tablesorter();
582 $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowSpan' ),
584 'Rowspan not exploded'
588 var planetsRowspan = [
589 [ 'Earth', '6051.8' ],
591 [ 'Mars', '6051.8' ],
596 var planetsRowspanII = [ jupiter, mercury, saturn, venus, [ 'Venus', '6371.0' ], [ 'Venus', '3390.0' ] ];
599 'Basic planet table: same value for multiple rows via rowspan',
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' );
611 $table.tablesorter();
612 $table.find( '.headerSort:eq(0)' ).click();
616 'Basic planet table: same value for multiple rows via rowspan (sorting initially)',
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' );
628 $table.tablesorter( { sortList: [
634 'Basic planet table: Same value for multiple rows via rowspan II',
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' );
646 $table.tablesorter();
647 $table.find( '.headerSort:eq(0)' ).click();
651 var complexMDYDates = [
652 // Some words with Umlauts
653 ['January, 19 2010'],
660 var complexMDYSorted = [
664 ['January, 19 2010'],
669 'Complex date parsing I',
673 function ( $table ) {
674 mw.config.set( 'wgDefaultDateFormat', 'mdy' );
676 $table.tablesorter();
677 $table.find( '.headerSort:eq(0)' ).click();
681 var currencyUnsorted = [
691 var currencySorted = [
698 // Comma's sort after dots
699 // Not intentional but test to detect changes
704 'Currency parsing I',
708 function ( $table ) {
709 $table.tablesorter();
710 $table.find( '.headerSort:eq(0)' ).click();
714 var ascendingNameLegacy = ascendingName.slice( 0 );
715 ascendingNameLegacy[4] = ascendingNameLegacy[5];
716 ascendingNameLegacy.pop();
719 'Legacy compat with .sortbottom',
723 function ( $table ) {
724 $table.find( 'tr:last' ).addClass( 'sortbottom' );
725 $table.tablesorter();
726 $table.find( '.headerSort:eq(0)' ).click();
730 QUnit.test( 'Test detection routine', 1, function ( assert ) {
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>' +
740 $table.tablesorter();
741 $table.find( '.headerSort:eq(0)' ).click();
744 $table.data( 'tablesorter' ).config.parsers[0].id,
746 'Correctly detected column content skipping sortbottom'
750 /** FIXME: the diff output is not very readeable. */
751 QUnit.test( 'bug 32047 - caption must be before thead', 1, function ( assert ) {
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>' +
762 $table.tablesorter();
765 $table.children().get( 0 ).nodeName,
767 'First element after <thead> must be <caption> (bug 32047)'
771 QUnit.test( 'data-sort-value attribute, when available, should override sorting position', 3, function ( assert ) {
774 // Example 1: All cells except one cell without data-sort-value,
775 // which should be sorted at it's text content value.
777 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
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>' +
786 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
789 $table.find( 'tbody > tr' ).each( function ( i, tr ) {
790 $( tr ).find( 'td' ).each( function ( i, td ) {
792 data: $( td ).data( 'sortValue' ),
798 assert.deepEqual( data, [
819 ], 'Order matches expected order (based on data-sort-value attribute values)' );
823 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
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>' +
832 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
835 $table.find( 'tbody > tr' ).each( function ( i, tr ) {
836 $( tr ).find( 'td' ).each( function ( i, td ) {
838 data: $( td ).data( 'sortValue' ),
844 assert.deepEqual( data, [
865 ], 'Order matches expected order (based on data-sort-value attribute values)' );
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).
870 '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
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>' +
879 // initialize table sorter and sort once
882 .find( '.headerSort:eq(0)' ).click();
884 // Change the sortValue data properties (bug 38152)
886 $table.find( 'td:contains(A)' ).data( 'sortValue', 3 );
888 $table.find( 'td:contains(B)' ).data( 'sortValue', 1 );
889 // - remove data, bring back attribute: 2
890 $table.find( 'td:contains(G)' ).removeData( 'sortValue' );
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();
897 $table.find( 'tbody > tr' ).each( function ( i, tr ) {
898 $( tr ).find( 'td' ).each( function ( i, td ) {
900 data: $( td ).data( 'sortValue' ),
906 assert.deepEqual( data, [
927 ], 'Order matches expected order, using the current sortValue in $.data()' );
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();
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();
963 // TODO add numbers sorting tests for bug 8115 with a different language
965 QUnit.test( 'bug 32888 - Tables inside a tableheader cell', 2, function ( assert ) {
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>' +
976 $table.tablesorter();
979 $table.find( '> thead:eq(0) > tr > th.headerSort' ).length,
981 'Child tables inside a headercell should not interfere with sortable headers (bug 32888)'
984 $( '#mw-bug-32888-2' ).find( 'th.headerSort' ).length,
986 'The headers of child tables inside a headercell should not be sortable themselves (bug 32888)'
991 var correctDateSorting1 = [
993 ['05 February 2010'],
997 var correctDateSortingSorted1 = [
1000 ['05 February 2010']
1004 'Correct date sorting I',
1006 correctDateSorting1,
1007 correctDateSortingSorted1,
1008 function ( $table ) {
1009 mw.config.set( 'wgDefaultDateFormat', 'mdy' );
1011 $table.tablesorter();
1012 $table.find( '.headerSort:eq(0)' ).click();
1016 var correctDateSorting2 = [
1017 ['January 01 2010'],
1018 ['February 05 2010'],
1022 var correctDateSortingSorted2 = [
1023 ['January 01 2010'],
1024 ['January 16 2010'],
1025 ['February 05 2010']
1029 'Correct date sorting II',
1031 correctDateSorting2,
1032 correctDateSortingSorted2,
1033 function ( $table ) {
1034 mw.config.set( 'wgDefaultDateFormat', 'dmy' );
1036 $table.tablesorter();
1037 $table.find( '.headerSort:eq(0)' ).click();
1041 QUnit.test( 'Sorting images using alt text', 1, function ( assert ) {
1043 '<table class="sortable">' +
1044 '<tr><th>THEAD</th></tr>' +
1045 '<tr><td><img alt="2"/></td></tr>' +
1046 '<tr><td>1</td></tr>' +
1049 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
1052 $table.find( 'td' ).first().text(),
1054 'Applied correct sorting order'
1058 QUnit.test( 'Sorting images using alt text (complex)', 1, function ( assert ) {
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>' +
1070 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
1073 $table.find( 'td' ).text(),
1075 'Applied correct sorting order'
1079 QUnit.test( 'Sorting images using alt text (with format autodetection)', 1, function ( assert ) {
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>' +
1089 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
1092 $table.find( 'td' ).text(),
1094 'Applied correct sorting order'
1098 QUnit.test( 'bug 38911 - The row with the largest amount of columns should receive the sort indicators', 3, function ( assert ) {
1100 '<table class="sortable">' +
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>' +
1105 '<tr><td>A</td><td>Aa</td><td>Ab</td></tr>' +
1106 '<tr><td>B</td><td>Ba</td><td>Bb</td></tr>' +
1109 $table.tablesorter();
1112 $table.find( '#A1' ).attr( 'class' ),
1114 'The first column of the first row should be sortable'
1117 $table.find( '#B2b' ).attr( 'class' ),
1119 'The th element of the 2nd row of the 2nd column should be sortable'
1122 $table.find( '#C2b' ).attr( 'class' ),
1124 'The th element of the 2nd row of the 3rd column should be sortable'
1128 QUnit.test( 'rowspans in table headers should prefer the last row when rows are equal in length', 2, function ( assert ) {
1130 '<table class="sortable">' +
1132 '<tr><th rowspan="2" id="A1">A1</th><th>B2a</th></tr>' +
1133 '<tr><th id="B2b">B2b</th></tr>' +
1135 '<tr><td>A</td><td>Aa</td></tr>' +
1136 '<tr><td>B</td><td>Ba</td></tr>' +
1139 $table.tablesorter();
1142 $table.find( '#A1' ).attr( 'class' ),
1144 'The first column of the first row should be sortable'
1147 $table.find( '#B2b' ).attr( 'class' ),
1149 'The th element of the 2nd row of the 2nd column should be sortable'
1153 // bug 41889 - exploding rowspans in more complex cases
1155 'Rowspan exploding with row headers',
1156 '<table class="sortable">' +
1157 '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' +
1159 '<tr><td>1</td><th rowspan="2">foo</th><td rowspan="2">bar</td><td>baz</td></tr>' +
1160 '<tr><td>2</td><td>baz</td></tr>' +
1163 [ '1', 'foo', 'bar', 'baz' ],
1164 [ '2', 'foo', 'bar', 'baz' ]
1169 'Rowspan exploding with colspanned cells',
1170 '<table class="sortable">' +
1171 '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' +
1173 '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td></tr>' +
1174 '<tr><td>2</td><td colspan="2">foobar</td></tr>' +
1177 [ '1', 'foo', 'bar', 'baz' ],
1178 [ '2', 'foobar', 'baz' ]
1183 'Rowspan exploding with colspanned cells (2)',
1184 '<table class="sortable">' +
1185 '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th><th>quux</th></tr></thead>' +
1187 '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td><td>quux</td></tr>' +
1188 '<tr><td>2</td><td colspan="2">foobar</td><td>quux</td></tr>' +
1191 [ '1', 'foo', 'bar', 'baz', 'quux' ],
1192 [ '2', 'foobar', 'baz', 'quux' ]
1197 'Rowspan exploding with rightmost rows spanning most',
1198 '<table class="sortable">' +
1199 '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th></tr></thead>' +
1201 '<tr><td>1</td><td rowspan="2">foo</td><td rowspan="4">bar</td></tr>' +
1202 '<tr><td>2</td></tr>' +
1203 '<tr><td>3</td><td rowspan="2">foo</td></tr>' +
1204 '<tr><td>4</td></tr>' +
1207 [ '1', 'foo', 'bar' ],
1208 [ '2', 'foo', 'bar' ],
1209 [ '3', 'foo', 'bar' ],
1210 [ '4', 'foo', 'bar' ]
1215 'Rowspan exploding with rightmost rows spanning most (2)',
1216 '<table class="sortable">' +
1217 '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' +
1219 '<tr><td>1</td><td rowspan="2">foo</td><td rowspan="4">bar</td><td>baz</td></tr>' +
1220 '<tr><td>2</td><td>baz</td></tr>' +
1221 '<tr><td>3</td><td rowspan="2">foo</td><td>baz</td></tr>' +
1222 '<tr><td>4</td><td>baz</td></tr>' +
1225 [ '1', 'foo', 'bar', 'baz' ],
1226 [ '2', 'foo', 'bar', 'baz' ],
1227 [ '3', 'foo', 'bar', 'baz' ],
1228 [ '4', 'foo', 'bar', 'baz' ]
1233 'Rowspan exploding with row-and-colspanned cells',
1234 '<table class="sortable">' +
1235 '<thead><tr><th id="sortme">n</th><th>foo1</th><th>foo2</th><th>bar</th><th>baz</th></tr></thead>' +
1237 '<tr><td>1</td><td rowspan="2">foo1</td><td rowspan="2">foo2</td><td rowspan="4">bar</td><td>baz</td></tr>' +
1238 '<tr><td>2</td><td>baz</td></tr>' +
1239 '<tr><td>3</td><td colspan="2" rowspan="2">foo</td><td>baz</td></tr>' +
1240 '<tr><td>4</td><td>baz</td></tr>' +
1243 [ '1', 'foo1', 'foo2', 'bar', 'baz' ],
1244 [ '2', 'foo1', 'foo2', 'bar', 'baz' ],
1245 [ '3', 'foo', 'bar', 'baz' ],
1246 [ '4', 'foo', 'bar', 'baz' ]
1251 'Rowspan exploding with uneven rowspan layout',
1252 '<table class="sortable">' +
1253 '<thead><tr><th id="sortme">n</th><th>foo1</th><th>foo2</th><th>foo3</th><th>bar</th><th>baz</th></tr></thead>' +
1255 '<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>' +
1256 '<tr><td>2</td><td rowspan="3">bar</td><td>baz</td></tr>' +
1257 '<tr><td>3</td><td rowspan="2">foo1</td><td rowspan="2">foo2</td><td rowspan="2">foo3</td><td>baz</td></tr>' +
1258 '<tr><td>4</td><td>baz</td></tr>' +
1261 [ '1', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ],
1262 [ '2', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ],
1263 [ '3', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ],
1264 [ '4', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ]
1268 }( jQuery, mediaWiki ) );