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