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