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