Merge "Add ss_active_users in SiteStats::isSane"
[mediawiki.git] / tests / qunit / suites / resources / jquery / jquery.tablesorter.test.js
blob7c8503b660e201925e552ac027bf6d35c8559e7b
1 ( function ( $, mw ) {
2         /*jshint onevar: false */
4         var config = {
5                 wgMonthNames: ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
6                 wgMonthNamesShort: ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
7                 wgDefaultDateFormat: 'dmy',
8                 wgSeparatorTransformTable: ['', ''],
9                 wgDigitTransformTable: ['', ''],
10                 wgContentLanguage: 'en'
11         };
13         QUnit.module( 'jquery.tablesorter', QUnit.newMwEnvironment( { config: config } ) );
15         /**
16          * Create an HTML table from an array of row arrays containing text strings.
17          * First row will be header row. No fancy rowspan/colspan stuff.
18          *
19          * @param {String[]} header
20          * @param {String[][]} data
21          * @return jQuery
22          */
23         function tableCreate( header, data ) {
24                 var i,
25                         $table = $( '<table class="sortable"><thead></thead><tbody></tbody></table>' ),
26                         $thead = $table.find( 'thead' ),
27                         $tbody = $table.find( 'tbody' ),
28                         $tr = $( '<tr>' );
30                 $.each( header, function ( i, str ) {
31                         var $th = $( '<th>' );
32                         $th.text( str ).appendTo( $tr );
33                 } );
34                 $tr.appendTo( $thead );
36                 for ( i = 0; i < data.length; i++ ) {
37                         /*jshint loopfunc: true */
38                         $tr = $( '<tr>' );
39                         $.each( data[i], function ( j, str ) {
40                                 var $td = $( '<td>' );
41                                 $td.text( str ).appendTo( $tr );
42                         } );
43                         $tr.appendTo( $tbody );
44                 }
45                 return $table;
46         }
48         /**
49          * Extract text from table.
50          *
51          * @param {jQuery} $table
52          * @return String[][]
53          */
54         function tableExtract( $table ) {
55                 var data = [];
57                 $table.find( 'tbody' ).find( 'tr' ).each( function ( i, tr ) {
58                         var row = [];
59                         $( tr ).find( 'td,th' ).each( function ( i, td ) {
60                                 row.push( $( td ).text() );
61                         } );
62                         data.push( row );
63                 } );
64                 return data;
65         }
67         /**
68          * Run a table test by building a table with the given data,
69          * running some callback on it, then checking the results.
70          *
71          * @param {String} msg text to pass on to qunit for the comparison
72          * @param {String[]} header cols to make the table
73          * @param {String[][]} data rows/cols to make the table
74          * @param {String[][]} expected rows/cols to compare against at end
75          * @param {function($table)} callback something to do with the table before we compare
76          */
77         function tableTest( msg, header, data, expected, callback ) {
78                 QUnit.test( msg, 1, function ( assert ) {
79                         var $table = tableCreate( header, data );
81                         // Give caller a chance to set up sorting and manipulate the table.
82                         callback( $table );
84                         // Table sorting is done synchronously; if it ever needs to change back
85                         // to asynchronous, we'll need a timeout or a callback here.
86                         var extracted = tableExtract( $table );
87                         assert.deepEqual( extracted, expected, msg );
88                 } );
89         }
91         /**
92          * Run a table test by building a table with the given HTML,
93          * running some callback on it, then checking the results.
94          *
95          * @param {String} msg text to pass on to qunit for the comparison
96          * @param {String} HTML to make the table
97          * @param {String[][]} expected rows/cols to compare against at end
98          * @param {function($table)} callback something to do with the table before we compare
99          */
100         function tableTestHTML( msg, html, expected, callback ) {
101                 QUnit.test( msg, 1, function ( assert ) {
102                         var $table = $( html );
104                         // Give caller a chance to set up sorting and manipulate the table.
105                         if ( callback ) {
106                                 callback( $table );
107                         } else {
108                                 $table.tablesorter();
109                                 $table.find( '#sortme' ).click();
110                         }
112                         // Table sorting is done synchronously; if it ever needs to change back
113                         // to asynchronous, we'll need a timeout or a callback here.
114                         var extracted = tableExtract( $table );
115                         assert.deepEqual( extracted, expected, msg );
116                 } );
117         }
119         function reversed( arr ) {
120                 // Clone array
121                 var arr2 = arr.slice( 0 );
123                 arr2.reverse();
125                 return arr2;
126         }
128         // Sample data set using planets named and their radius
129         var header = [ 'Planet' , 'Radius (km)'],
130                 mercury = [ 'Mercury', '2439.7' ],
131                 venus = [ 'Venus'  , '6051.8' ],
132                 earth = [ 'Earth'  , '6371.0' ],
133                 mars = [ 'Mars'   , '3390.0' ],
134                 jupiter = [ 'Jupiter', '69911' ],
135                 saturn = [ 'Saturn' , '58232' ];
137         // Initial data set
138         var planets = [mercury, venus, earth, mars, jupiter, saturn];
139         var ascendingName = [earth, jupiter, mars, mercury, saturn, venus];
140         var ascendingRadius = [mercury, mars, venus, earth, saturn, jupiter];
142         tableTest(
143                 'Basic planet table: sorting initially - ascending by name',
144                 header,
145                 planets,
146                 ascendingName,
147                 function ( $table ) {
148                         $table.tablesorter( { sortList: [
149                                 { 0: 'asc' }
150                         ] } );
151                 }
152         );
153         tableTest(
154                 'Basic planet table: sorting initially - descending by radius',
155                 header,
156                 planets,
157                 reversed( ascendingRadius ),
158                 function ( $table ) {
159                         $table.tablesorter( { sortList: [
160                                 { 1: 'desc' }
161                         ] } );
162                 }
163         );
164         tableTest(
165                 'Basic planet table: ascending by name',
166                 header,
167                 planets,
168                 ascendingName,
169                 function ( $table ) {
170                         $table.tablesorter();
171                         $table.find( '.headerSort:eq(0)' ).click();
172                 }
173         );
174         tableTest(
175                 'Basic planet table: ascending by name a second time',
176                 header,
177                 planets,
178                 ascendingName,
179                 function ( $table ) {
180                         $table.tablesorter();
181                         $table.find( '.headerSort:eq(0)' ).click();
182                 }
183         );
184         tableTest(
185                 'Basic planet table: descending by name',
186                 header,
187                 planets,
188                 reversed( ascendingName ),
189                 function ( $table ) {
190                         $table.tablesorter();
191                         $table.find( '.headerSort:eq(0)' ).click().click();
192                 }
193         );
194         tableTest(
195                 'Basic planet table: ascending radius',
196                 header,
197                 planets,
198                 ascendingRadius,
199                 function ( $table ) {
200                         $table.tablesorter();
201                         $table.find( '.headerSort:eq(1)' ).click();
202                 }
203         );
204         tableTest(
205                 'Basic planet table: descending radius',
206                 header,
207                 planets,
208                 reversed( ascendingRadius ),
209                 function ( $table ) {
210                         $table.tablesorter();
211                         $table.find( '.headerSort:eq(1)' ).click().click();
212                 }
213         );
215         // Sample data set to test multiple column sorting
216         header = [ 'column1' , 'column2'];
217         var
218                 a1 = [ 'A', '1' ],
219                 a2 = [ 'A', '2' ],
220                 a3 = [ 'A', '3' ],
221                 b1 = [ 'B', '1' ],
222                 b2 = [ 'B', '2' ],
223                 b3 = [ 'B', '3' ];
224         var initial = [a2, b3, a1, a3, b2, b1];
225         var asc = [a1, a2, a3, b1, b2, b3];
226         var descasc = [b1, b2, b3, a1, a2, a3];
228         tableTest(
229                 'Sorting multiple columns by passing sort list',
230                 header,
231                 initial,
232                 asc,
233                 function ( $table ) {
234                         $table.tablesorter(
235                                 { sortList: [
236                                         { 0: 'asc' },
237                                         { 1: 'asc' }
238                                 ] }
239                         );
240                 }
241         );
242         tableTest(
243                 'Sorting multiple columns by programmatically triggering sort()',
244                 header,
245                 initial,
246                 descasc,
247                 function ( $table ) {
248                         $table.tablesorter();
249                         $table.data( 'tablesorter' ).sort(
250                                 [
251                                         { 0: 'desc' },
252                                         { 1: 'asc' }
253                                 ]
254                         );
255                 }
256         );
257         tableTest(
258                 'Reset to initial sorting by triggering sort() without any parameters',
259                 header,
260                 initial,
261                 asc,
262                 function ( $table ) {
263                         $table.tablesorter(
264                                 { sortList: [
265                                         { 0: 'asc' },
266                                         { 1: 'asc' }
267                                 ] }
268                         );
269                         $table.data( 'tablesorter' ).sort(
270                                 [
271                                         { 0: 'desc' },
272                                         { 1: 'asc' }
273                                 ]
274                         );
275                         $table.data( 'tablesorter' ).sort();
276                 }
277         );
278         QUnit.test( 'Reset sorting making table appear unsorted', 3, function ( assert ) {
279                 var $table = tableCreate( header, initial );
280                 $table.tablesorter(
281                         { sortList: [
282                                 { 0: 'desc' },
283                                 { 1: 'asc' }
284                         ] }
285                 );
286                 $table.data( 'tablesorter' ).sort( [] );
288                 assert.equal(
289                         $table.find( 'th.headerSortUp' ).length + $table.find( 'th.headerSortDown' ).length,
290                         0,
291                         'No sort specific sort classes addign to header cells'
292                 );
294                 assert.equal(
295                         $table.find( 'th' ).first().attr( 'title' ),
296                         mw.msg( 'sort-ascending' ),
297                         'First header cell has default title'
298                 );
300                 assert.equal(
301                         $table.find( 'th' ).first().attr( 'title' ),
302                         $table.find( 'th' ).last().attr( 'title' ),
303                         'Both header cells\' titles match'
304                 );
305         } );
307         // Sorting with colspans
308         header = [ 'column1a' , 'column1b', 'column1c', 'column2' ];
309         var
310                 aaa1 = [ 'A', 'A', 'A', '1' ],
311                 aab5 = [ 'A', 'A', 'B', '5' ],
312                 abc3 = [ 'A', 'B', 'C', '3' ],
313                 bbc2 = [ 'B', 'B', 'C', '2' ],
314                 caa4 = [ 'C', 'A', 'A', '4' ];
315         // initial is already declared above
316         initial = [ aab5, aaa1, abc3, bbc2, caa4 ];
317         tableTest( 'Sorting with colspanned headers: spanned column',
318                 header,
319                 initial,
320                 [ aaa1, aab5, abc3, bbc2, caa4 ],
321                 function ( $table ) {
322                         // Make colspanned header for test
323                         $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
324                         $table.find( 'tr:eq(0) th:eq(0)' ).prop( 'colspan', '3' );
326                         $table.tablesorter();
327                         $table.find( '.headerSort:eq(0)' ).click();
328                 }
329         );
330         tableTest( 'Sorting with colspanned headers: subsequent column',
331                 header,
332                 initial,
333                 [ aaa1, bbc2, abc3, caa4, aab5 ],
334                 function ( $table ) {
335                         // Make colspanned header for test
336                         $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
337                         $table.find( 'tr:eq(0) th:eq(0)' ).prop( 'colspan', '3' );
339                         $table.tablesorter();
340                         $table.find( '.headerSort:eq(1)' ).click();
341                 }
342         );
344         // Regression tests!
345         tableTest(
346                 'Bug 28775: German-style (dmy) short numeric dates',
347                 ['Date'],
348                 [
349                         // German-style dates are day-month-year
350                         ['11.11.2011'],
351                         ['01.11.2011'],
352                         ['02.10.2011'],
353                         ['03.08.2011'],
354                         ['09.11.2011']
355                 ],
356                 [
357                         // Sorted by ascending date
358                         ['03.08.2011'],
359                         ['02.10.2011'],
360                         ['01.11.2011'],
361                         ['09.11.2011'],
362                         ['11.11.2011']
363                 ],
364                 function ( $table ) {
365                         mw.config.set( 'wgDefaultDateFormat', 'dmy' );
366                         mw.config.set( 'wgContentLanguage', 'de' );
368                         $table.tablesorter();
369                         $table.find( '.headerSort:eq(0)' ).click();
370                 }
371         );
373         tableTest(
374                 'Bug 28775: American-style (mdy) short numeric dates',
375                 ['Date'],
376                 [
377                         // American-style dates are month-day-year
378                         ['11.11.2011'],
379                         ['01.11.2011'],
380                         ['02.10.2011'],
381                         ['03.08.2011'],
382                         ['09.11.2011']
383                 ],
384                 [
385                         // Sorted by ascending date
386                         ['01.11.2011'],
387                         ['02.10.2011'],
388                         ['03.08.2011'],
389                         ['09.11.2011'],
390                         ['11.11.2011']
391                 ],
392                 function ( $table ) {
393                         mw.config.set( 'wgDefaultDateFormat', 'mdy' );
395                         $table.tablesorter();
396                         $table.find( '.headerSort:eq(0)' ).click();
397                 }
398         );
400         var ipv4 = [
401                 // Some randomly generated fake IPs
402                 ['45.238.27.109'],
403                 ['44.172.9.22'],
404                 ['247.240.82.209'],
405                 ['204.204.132.158'],
406                 ['170.38.91.162'],
407                 ['197.219.164.9'],
408                 ['45.68.154.72'],
409                 ['182.195.149.80']
410         ];
411         var ipv4Sorted = [
412                 // Sort order should go octet by octet
413                 ['44.172.9.22'],
414                 ['45.68.154.72'],
415                 ['45.238.27.109'],
416                 ['170.38.91.162'],
417                 ['182.195.149.80'],
418                 ['197.219.164.9'],
419                 ['204.204.132.158'],
420                 ['247.240.82.209']
421         ];
423         tableTest(
424                 'Bug 17141: IPv4 address sorting',
425                 ['IP'],
426                 ipv4,
427                 ipv4Sorted,
428                 function ( $table ) {
429                         $table.tablesorter();
430                         $table.find( '.headerSort:eq(0)' ).click();
431                 }
432         );
433         tableTest(
434                 'Bug 17141: IPv4 address sorting (reverse)',
435                 ['IP'],
436                 ipv4,
437                 reversed( ipv4Sorted ),
438                 function ( $table ) {
439                         $table.tablesorter();
440                         $table.find( '.headerSort:eq(0)' ).click().click();
441                 }
442         );
444         var umlautWords = [
445                 // Some words with Umlauts
446                 ['Günther'],
447                 ['Peter'],
448                 ['Björn'],
449                 ['Bjorn'],
450                 ['Apfel'],
451                 ['Äpfel'],
452                 ['Strasse'],
453                 ['Sträßschen']
454         ];
456         var umlautWordsSorted = [
457                 // Some words with Umlauts
458                 ['Äpfel'],
459                 ['Apfel'],
460                 ['Björn'],
461                 ['Bjorn'],
462                 ['Günther'],
463                 ['Peter'],
464                 ['Sträßschen'],
465                 ['Strasse']
466         ];
468         tableTest(
469                 'Accented Characters with custom collation',
470                 ['Name'],
471                 umlautWords,
472                 umlautWordsSorted,
473                 function ( $table ) {
474                         mw.config.set( 'tableSorterCollation', {
475                                 'ä': 'ae',
476                                 'ö': 'oe',
477                                 'ß': 'ss',
478                                 'ü': 'ue'
479                         } );
481                         $table.tablesorter();
482                         $table.find( '.headerSort:eq(0)' ).click();
483                 }
484         );
486         QUnit.test( 'Rowspan not exploded on init', 1, function ( assert ) {
487                 var $table = tableCreate( header, planets );
489                 // Modify the table to have a multiple-row-spanning cell:
490                 // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
491                 $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
492                 // - Set rowspan for 2nd cell of 3rd row to 3.
493                 //   This covers the removed cell in the 4th and 5th row.
494                 $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' );
496                 $table.tablesorter();
498                 assert.equal(
499                         $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan' ),
500                         3,
501                         'Rowspan not exploded'
502                 );
503         } );
505         var planetsRowspan = [
506                 [ 'Earth', '6051.8' ],
507                 jupiter,
508                 [ 'Mars', '6051.8' ],
509                 mercury,
510                 saturn,
511                 venus
512         ];
513         var planetsRowspanII = [ jupiter, mercury, saturn, venus, [ 'Venus', '6371.0' ], [ 'Venus', '3390.0' ] ];
515         tableTest(
516                 'Basic planet table: same value for multiple rows via rowspan',
517                 header,
518                 planets,
519                 planetsRowspan,
520                 function ( $table ) {
521                         // Modify the table to have a multiple-row-spanning cell:
522                         // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
523                         $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
524                         // - Set rowspan for 2nd cell of 3rd row to 3.
525                         //   This covers the removed cell in the 4th and 5th row.
526                         $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' );
528                         $table.tablesorter();
529                         $table.find( '.headerSort:eq(0)' ).click();
530                 }
531         );
532         tableTest(
533                 'Basic planet table: same value for multiple rows via rowspan (sorting initially)',
534                 header,
535                 planets,
536                 planetsRowspan,
537                 function ( $table ) {
538                         // Modify the table to have a multiple-row-spanning cell:
539                         // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
540                         $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
541                         // - Set rowspan for 2nd cell of 3rd row to 3.
542                         //   This covers the removed cell in the 4th and 5th row.
543                         $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' );
545                         $table.tablesorter( { sortList: [
546                                 { 0: 'asc' }
547                         ] } );
548                 }
549         );
550         tableTest(
551                 'Basic planet table: Same value for multiple rows via rowspan II',
552                 header,
553                 planets,
554                 planetsRowspanII,
555                 function ( $table ) {
556                         // Modify the table to have a multiple-row-spanning cell:
557                         // - Remove 1st cell of 4th row, and, 1st cell or 5th row.
558                         $table.find( 'tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)' ).remove();
559                         // - Set rowspan for 1st cell of 3rd row to 3.
560                         //   This covers the removed cell in the 4th and 5th row.
561                         $table.find( 'tr:eq(2) td:eq(0)' ).prop( 'rowspan', '3' );
563                         $table.tablesorter();
564                         $table.find( '.headerSort:eq(0)' ).click();
565                 }
566         );
568         var complexMDYDates = [
569                 // Some words with Umlauts
570                 ['January, 19 2010'],
571                 ['April 21 1991'],
572                 ['04 22 1991'],
573                 ['5.12.1990'],
574                 ['December 12 \'10']
575         ];
577         var complexMDYSorted = [
578                 ['5.12.1990'],
579                 ['April 21 1991'],
580                 ['04 22 1991'],
581                 ['January, 19 2010'],
582                 ['December 12 \'10']
583         ];
585         tableTest(
586                 'Complex date parsing I',
587                 ['date'],
588                 complexMDYDates,
589                 complexMDYSorted,
590                 function ( $table ) {
591                         mw.config.set( 'wgDefaultDateFormat', 'mdy' );
593                         $table.tablesorter();
594                         $table.find( '.headerSort:eq(0)' ).click();
595                 }
596         );
598         var currencyUnsorted = [
599                 ['1.02 $'],
600                 ['$ 3.00'],
601                 ['€ 2,99'],
602                 ['$ 1.00'],
603                 ['$3.50'],
604                 ['$ 1.50'],
605                 ['€ 0.99']
606         ];
608         var currencySorted = [
609                 ['€ 0.99'],
610                 ['$ 1.00'],
611                 ['1.02 $'],
612                 ['$ 1.50'],
613                 ['$ 3.00'],
614                 ['$3.50'],
615                 // Comma's sort after dots
616                 // Not intentional but test to detect changes
617                 ['€ 2,99']
618         ];
620         tableTest(
621                 'Currency parsing I',
622                 ['currency'],
623                 currencyUnsorted,
624                 currencySorted,
625                 function ( $table ) {
626                         $table.tablesorter();
627                         $table.find( '.headerSort:eq(0)' ).click();
628                 }
629         );
631         var ascendingNameLegacy = ascendingName.slice( 0 );
632         ascendingNameLegacy[4] = ascendingNameLegacy[5];
633         ascendingNameLegacy.pop();
635         tableTest(
636                 'Legacy compat with .sortbottom',
637                 header,
638                 planets,
639                 ascendingNameLegacy,
640                 function ( $table ) {
641                         $table.find( 'tr:last' ).addClass( 'sortbottom' );
642                         $table.tablesorter();
643                         $table.find( '.headerSort:eq(0)' ).click();
644                 }
645         );
647         QUnit.test( 'Test detection routine', 1, function ( assert ) {
648                 var $table;
649                 $table = $(
650                         '<table class="sortable">' +
651                                 '<caption>CAPTION</caption>' +
652                                 '<tr><th>THEAD</th></tr>' +
653                                 '<tr><td>1</td></tr>' +
654                                 '<tr class="sortbottom"><td>text</td></tr>' +
655                                 '</table>'
656                 );
657                 $table.tablesorter();
658                 $table.find( '.headerSort:eq(0)' ).click();
660                 assert.equal(
661                         $table.data( 'tablesorter' ).config.parsers[0].id,
662                         'number',
663                         'Correctly detected column content skipping sortbottom'
664                 );
665         } );
667         /** FIXME: the diff output is not very readeable. */
668         QUnit.test( 'bug 32047 - caption must be before thead', 1, function ( assert ) {
669                 var $table;
670                 $table = $(
671                         '<table class="sortable">' +
672                                 '<caption>CAPTION</caption>' +
673                                 '<tr><th>THEAD</th></tr>' +
674                                 '<tr><td>A</td></tr>' +
675                                 '<tr><td>B</td></tr>' +
676                                 '<tr class="sortbottom"><td>TFOOT</td></tr>' +
677                                 '</table>'
678                 );
679                 $table.tablesorter();
681                 assert.equal(
682                         $table.children().get( 0 ).nodeName,
683                         'CAPTION',
684                         'First element after <thead> must be <caption> (bug 32047)'
685                 );
686         } );
688         QUnit.test( 'data-sort-value attribute, when available, should override sorting position', 3, function ( assert ) {
689                 var $table, data;
691                 // Example 1: All cells except one cell without data-sort-value,
692                 // which should be sorted at it's text content value.
693                 $table = $(
694                         '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
695                                 '<tbody>' +
696                                 '<tr><td>Cheetah</td></tr>' +
697                                 '<tr><td data-sort-value="Apple">Bird</td></tr>' +
698                                 '<tr><td data-sort-value="Bananna">Ferret</td></tr>' +
699                                 '<tr><td data-sort-value="Drupe">Elephant</td></tr>' +
700                                 '<tr><td data-sort-value="Cherry">Dolphin</td></tr>' +
701                                 '</tbody></table>'
702                 );
703                 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
705                 data = [];
706                 $table.find( 'tbody > tr' ).each( function ( i, tr ) {
707                         $( tr ).find( 'td' ).each( function ( i, td ) {
708                                 data.push( {
709                                         data: $( td ).data( 'sortValue' ),
710                                         text: $( td ).text()
711                                 } );
712                         } );
713                 } );
715                 assert.deepEqual( data, [
716                         {
717                                 data: 'Apple',
718                                 text: 'Bird'
719                         },
720                         {
721                                 data: 'Bananna',
722                                 text: 'Ferret'
723                         },
724                         {
725                                 data: undefined,
726                                 text: 'Cheetah'
727                         },
728                         {
729                                 data: 'Cherry',
730                                 text: 'Dolphin'
731                         },
732                         {
733                                 data: 'Drupe',
734                                 text: 'Elephant'
735                         }
736                 ], 'Order matches expected order (based on data-sort-value attribute values)' );
738                 // Example 2
739                 $table = $(
740                         '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
741                                 '<tbody>' +
742                                 '<tr><td>D</td></tr>' +
743                                 '<tr><td data-sort-value="E">A</td></tr>' +
744                                 '<tr><td>B</td></tr>' +
745                                 '<tr><td>G</td></tr>' +
746                                 '<tr><td data-sort-value="F">C</td></tr>' +
747                                 '</tbody></table>'
748                 );
749                 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
751                 data = [];
752                 $table.find( 'tbody > tr' ).each( function ( i, tr ) {
753                         $( tr ).find( 'td' ).each( function ( i, td ) {
754                                 data.push( {
755                                         data: $( td ).data( 'sortValue' ),
756                                         text: $( td ).text()
757                                 } );
758                         } );
759                 } );
761                 assert.deepEqual( data, [
762                         {
763                                 data: undefined,
764                                 text: 'B'
765                         },
766                         {
767                                 data: undefined,
768                                 text: 'D'
769                         },
770                         {
771                                 data: 'E',
772                                 text: 'A'
773                         },
774                         {
775                                 data: 'F',
776                                 text: 'C'
777                         },
778                         {
779                                 data: undefined,
780                                 text: 'G'
781                         }
782                 ], 'Order matches expected order (based on data-sort-value attribute values)' );
784                 // Example 3: Test that live changes are used from data-sort-value,
785                 // even if they change after the tablesorter is constructed (bug 38152).
786                 $table = $(
787                         '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
788                                 '<tbody>' +
789                                 '<tr><td>D</td></tr>' +
790                                 '<tr><td data-sort-value="1">A</td></tr>' +
791                                 '<tr><td>B</td></tr>' +
792                                 '<tr><td data-sort-value="2">G</td></tr>' +
793                                 '<tr><td>C</td></tr>' +
794                                 '</tbody></table>'
795                 );
796                 // initialize table sorter and sort once
797                 $table
798                         .tablesorter()
799                         .find( '.headerSort:eq(0)' ).click();
801                 // Change the sortValue data properties (bug 38152)
802                 // - change data
803                 $table.find( 'td:contains(A)' ).data( 'sortValue', 3 );
804                 // - add data
805                 $table.find( 'td:contains(B)' ).data( 'sortValue', 1 );
806                 // - remove data, bring back attribute: 2
807                 $table.find( 'td:contains(G)' ).removeData( 'sortValue' );
809                 // Now sort again (twice, so it is back at Ascending)
810                 $table.find( '.headerSort:eq(0)' ).click();
811                 $table.find( '.headerSort:eq(0)' ).click();
813                 data = [];
814                 $table.find( 'tbody > tr' ).each( function ( i, tr ) {
815                         $( tr ).find( 'td' ).each( function ( i, td ) {
816                                 data.push( {
817                                         data: $( td ).data( 'sortValue' ),
818                                         text: $( td ).text()
819                                 } );
820                         } );
821                 } );
823                 assert.deepEqual( data, [
824                         {
825                                 data: 1,
826                                 text: 'B'
827                         },
828                         {
829                                 data: 2,
830                                 text: 'G'
831                         },
832                         {
833                                 data: 3,
834                                 text: 'A'
835                         },
836                         {
837                                 data: undefined,
838                                 text: 'C'
839                         },
840                         {
841                                 data: undefined,
842                                 text: 'D'
843                         }
844                 ], 'Order matches expected order, using the current sortValue in $.data()' );
846         } );
848         var numbers = [
849                 [ '12'    ],
850                 [  '7'    ],
851                 [ '13,000'],
852                 [  '9'    ],
853                 [ '14'    ],
854                 [  '8.0'  ]
855         ];
856         var numbersAsc = [
857                 [  '7'    ],
858                 [  '8.0'  ],
859                 [  '9'    ],
860                 [ '12'    ],
861                 [ '14'    ],
862                 [ '13,000']
863         ];
865         tableTest( 'bug 8115: sort numbers with commas (ascending)',
866                 ['Numbers'], numbers, numbersAsc,
867                 function ( $table ) {
868                         $table.tablesorter();
869                         $table.find( '.headerSort:eq(0)' ).click();
870                 }
871         );
873         tableTest( 'bug 8115: sort numbers with commas (descending)',
874                 ['Numbers'], numbers, reversed( numbersAsc ),
875                 function ( $table ) {
876                         $table.tablesorter();
877                         $table.find( '.headerSort:eq(0)' ).click().click();
878                 }
879         );
880         // TODO add numbers sorting tests for bug 8115 with a different language
882         QUnit.test( 'bug 32888 - Tables inside a tableheader cell', 2, function ( assert ) {
883                 var $table;
884                 $table = $(
885                         '<table class="sortable" id="mw-bug-32888">' +
886                                 '<tr><th>header<table id="mw-bug-32888-2">' +
887                                 '<tr><th>1</th><th>2</th></tr>' +
888                                 '</table></th></tr>' +
889                                 '<tr><td>A</td></tr>' +
890                                 '<tr><td>B</td></tr>' +
891                                 '</table>'
892                 );
893                 $table.tablesorter();
895                 assert.equal(
896                         $table.find( '> thead:eq(0) > tr > th.headerSort' ).length,
897                         1,
898                         'Child tables inside a headercell should not interfere with sortable headers (bug 32888)'
899                 );
900                 assert.equal(
901                         $( '#mw-bug-32888-2' ).find( 'th.headerSort' ).length,
902                         0,
903                         'The headers of child tables inside a headercell should not be sortable themselves (bug 32888)'
904                 );
905         } );
908         var correctDateSorting1 = [
909                 ['01 January 2010'],
910                 ['05 February 2010'],
911                 ['16 January 2010']
912         ];
914         var correctDateSortingSorted1 = [
915                 ['01 January 2010'],
916                 ['16 January 2010'],
917                 ['05 February 2010']
918         ];
920         tableTest(
921                 'Correct date sorting I',
922                 ['date'],
923                 correctDateSorting1,
924                 correctDateSortingSorted1,
925                 function ( $table ) {
926                         mw.config.set( 'wgDefaultDateFormat', 'mdy' );
928                         $table.tablesorter();
929                         $table.find( '.headerSort:eq(0)' ).click();
930                 }
931         );
933         var correctDateSorting2 = [
934                 ['January 01 2010'],
935                 ['February 05 2010'],
936                 ['January 16 2010']
937         ];
939         var correctDateSortingSorted2 = [
940                 ['January 01 2010'],
941                 ['January 16 2010'],
942                 ['February 05 2010']
943         ];
945         tableTest(
946                 'Correct date sorting II',
947                 ['date'],
948                 correctDateSorting2,
949                 correctDateSortingSorted2,
950                 function ( $table ) {
951                         mw.config.set( 'wgDefaultDateFormat', 'dmy' );
953                         $table.tablesorter();
954                         $table.find( '.headerSort:eq(0)' ).click();
955                 }
956         );
958         QUnit.test( 'Sorting images using alt text', 1, function ( assert ) {
959                 var $table = $(
960                         '<table class="sortable">' +
961                                 '<tr><th>THEAD</th></tr>' +
962                                 '<tr><td><img alt="2"/></td></tr>' +
963                                 '<tr><td>1</td></tr>' +
964                                 '</table>'
965                 );
966                 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
968                 assert.equal(
969                         $table.find( 'td' ).first().text(),
970                         '1',
971                         'Applied correct sorting order'
972                 );
973         } );
975         QUnit.test( 'Sorting images using alt text (complex)', 1, function ( assert ) {
976                 var $table = $(
977                         '<table class="sortable">' +
978                                 '<tr><th>THEAD</th></tr>' +
979                                 '<tr><td><img alt="D" />A</td></tr>' +
980                                 '<tr><td>CC</td></tr>' +
981                                 '<tr><td><a><img alt="A" /></a>F</tr>' +
982                                 '<tr><td><img alt="A" /><strong>E</strong></tr>' +
983                                 '<tr><td><strong><img alt="A" />D</strong></tr>' +
984                                 '<tr><td><img alt="A" />C</tr>' +
985                                 '</table>'
986                 );
987                 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
989                 assert.equal(
990                         $table.find( 'td' ).text(),
991                         'CDEFCCA',
992                         'Applied correct sorting order'
993                 );
994         } );
996         QUnit.test( 'Sorting images using alt text (with format autodetection)', 1, function ( assert ) {
997                 var $table = $(
998                         '<table class="sortable">' +
999                                 '<tr><th>THEAD</th></tr>' +
1000                                 '<tr><td><img alt="1" />7</td></tr>' +
1001                                 '<tr><td>1<img alt="6" /></td></tr>' +
1002                                 '<tr><td>5</td></tr>' +
1003                                 '<tr><td>4</td></tr>' +
1004                                 '</table>'
1005                 );
1006                 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
1008                 assert.equal(
1009                         $table.find( 'td' ).text(),
1010                         '4517',
1011                         'Applied correct sorting order'
1012                 );
1013         } );
1015         // bug 41889 - exploding rowspans in more complex cases
1016         tableTestHTML(
1017                 'Rowspan exploding with row headers',
1018                 '<table class="sortable">' +
1019                         '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' +
1020                         '<tbody>' +
1021                         '<tr><td>1</td><th rowspan="2">foo</th><td rowspan="2">bar</td><td>baz</td></tr>' +
1022                         '<tr><td>2</td><td>baz</td></tr>' +
1023                         '</tbody></table>',
1024                 [
1025                         [ '1', 'foo', 'bar', 'baz' ],
1026                         [ '2', 'foo', 'bar', 'baz' ]
1027                 ]
1028         );
1030         tableTestHTML(
1031                 'Rowspan exploding with colspanned cells',
1032                 '<table class="sortable">' +
1033                         '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' +
1034                         '<tbody>' +
1035                         '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td></tr>' +
1036                         '<tr><td>2</td><td colspan="2">foobar</td></tr>' +
1037                         '</tbody></table>',
1038                 [
1039                         [ '1', 'foo', 'bar', 'baz' ],
1040                         [ '2', 'foobar', 'baz' ]
1041                 ]
1042         );
1044         tableTestHTML(
1045                 'Rowspan exploding with colspanned cells (2)',
1046                 '<table class="sortable">' +
1047                         '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th><th>quux</th></tr></thead>' +
1048                         '<tbody>' +
1049                         '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td><td>quux</td></tr>' +
1050                         '<tr><td>2</td><td colspan="2">foobar</td><td>quux</td></tr>' +
1051                         '</tbody></table>',
1052                 [
1053                         [ '1', 'foo', 'bar', 'baz', 'quux' ],
1054                         [ '2', 'foobar', 'baz', 'quux' ]
1055                 ]
1056         );
1058         tableTestHTML(
1059                 'Rowspan exploding with rightmost rows spanning most',
1060                 '<table class="sortable">' +
1061                         '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th></tr></thead>' +
1062                         '<tbody>' +
1063                         '<tr><td>1</td><td rowspan="2">foo</td><td rowspan="4">bar</td></tr>' +
1064                         '<tr><td>2</td></tr>' +
1065                         '<tr><td>3</td><td rowspan="2">foo</td></tr>' +
1066                         '<tr><td>4</td></tr>' +
1067                         '</tbody></table>',
1068                 [
1069                         [ '1', 'foo', 'bar' ],
1070                         [ '2', 'foo', 'bar' ],
1071                         [ '3', 'foo', 'bar' ],
1072                         [ '4', 'foo', 'bar' ]
1073                 ]
1074         );
1076         tableTestHTML(
1077                 'Rowspan exploding with rightmost rows spanning most (2)',
1078                 '<table class="sortable">' +
1079                         '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' +
1080                         '<tbody>' +
1081                         '<tr><td>1</td><td rowspan="2">foo</td><td rowspan="4">bar</td><td>baz</td></tr>' +
1082                         '<tr><td>2</td><td>baz</td></tr>' +
1083                         '<tr><td>3</td><td rowspan="2">foo</td><td>baz</td></tr>' +
1084                         '<tr><td>4</td><td>baz</td></tr>' +
1085                         '</tbody></table>',
1086                 [
1087                         [ '1', 'foo', 'bar', 'baz' ],
1088                         [ '2', 'foo', 'bar', 'baz' ],
1089                         [ '3', 'foo', 'bar', 'baz' ],
1090                         [ '4', 'foo', 'bar', 'baz' ]
1091                 ]
1092         );
1094         tableTestHTML(
1095                 'Rowspan exploding with row-and-colspanned cells',
1096                 '<table class="sortable">' +
1097                         '<thead><tr><th id="sortme">n</th><th>foo1</th><th>foo2</th><th>bar</th><th>baz</th></tr></thead>' +
1098                         '<tbody>' +
1099                         '<tr><td>1</td><td rowspan="2">foo1</td><td rowspan="2">foo2</td><td rowspan="4">bar</td><td>baz</td></tr>' +
1100                         '<tr><td>2</td><td>baz</td></tr>' +
1101                         '<tr><td>3</td><td colspan="2" rowspan="2">foo</td><td>baz</td></tr>' +
1102                         '<tr><td>4</td><td>baz</td></tr>' +
1103                         '</tbody></table>',
1104                 [
1105                         [ '1', 'foo1', 'foo2', 'bar', 'baz' ],
1106                         [ '2', 'foo1', 'foo2', 'bar', 'baz' ],
1107                         [ '3', 'foo', 'bar', 'baz' ],
1108                         [ '4', 'foo', 'bar', 'baz' ]
1109                 ]
1110         );
1112         tableTestHTML(
1113                 'Rowspan exploding with uneven rowspan layout',
1114                 '<table class="sortable">' +
1115                         '<thead><tr><th id="sortme">n</th><th>foo1</th><th>foo2</th><th>foo3</th><th>bar</th><th>baz</th></tr></thead>' +
1116                         '<tbody>' +
1117                         '<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>' +
1118                         '<tr><td>2</td><td rowspan="3">bar</td><td>baz</td></tr>' +
1119                         '<tr><td>3</td><td rowspan="2">foo1</td><td rowspan="2">foo2</td><td rowspan="2">foo3</td><td>baz</td></tr>' +
1120                         '<tr><td>4</td><td>baz</td></tr>' +
1121                         '</tbody></table>',
1122                 [
1123                         [ '1', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ],
1124                         [ '2', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ],
1125                         [ '3', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ],
1126                         [ '4', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ]
1127                 ]
1128         );
1130 }( jQuery, mediaWiki ) );