profiler: log slow methods that worsen DB locks in transactions
[mediawiki.git] / tests / qunit / suites / resources / jquery / jquery.tablesorter.test.js
blob494abd878d50961bb73409fccd4a3e87fbfc0d52
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: ascending by name (multiple clicks)',
186                 header,
187                 planets,
188                 ascendingName,
189                 function ( $table ) {
190                         $table.tablesorter();
191                         $table.find( '.headerSort:eq(0)' ).click();
192                         $table.find( '.headerSort:eq(1)' ).click();
193                         $table.find( '.headerSort:eq(0)' ).click();
194                 }
195         );
196         tableTest(
197                 'Basic planet table: descending by name',
198                 header,
199                 planets,
200                 reversed( ascendingName ),
201                 function ( $table ) {
202                         $table.tablesorter();
203                         $table.find( '.headerSort:eq(0)' ).click().click();
204                 }
205         );
206         tableTest(
207                 'Basic planet table: ascending radius',
208                 header,
209                 planets,
210                 ascendingRadius,
211                 function ( $table ) {
212                         $table.tablesorter();
213                         $table.find( '.headerSort:eq(1)' ).click();
214                 }
215         );
216         tableTest(
217                 'Basic planet table: descending radius',
218                 header,
219                 planets,
220                 reversed( ascendingRadius ),
221                 function ( $table ) {
222                         $table.tablesorter();
223                         $table.find( '.headerSort:eq(1)' ).click().click();
224                 }
225         );
227         // Sample data set to test multiple column sorting
228         header = [ 'column1' , 'column2'];
229         var
230                 a1 = [ 'A', '1' ],
231                 a2 = [ 'A', '2' ],
232                 a3 = [ 'A', '3' ],
233                 b1 = [ 'B', '1' ],
234                 b2 = [ 'B', '2' ],
235                 b3 = [ 'B', '3' ];
236         var initial = [a2, b3, a1, a3, b2, b1];
237         var asc = [a1, a2, a3, b1, b2, b3];
238         var descasc = [b1, b2, b3, a1, a2, a3];
240         tableTest(
241                 'Sorting multiple columns by passing sort list',
242                 header,
243                 initial,
244                 asc,
245                 function ( $table ) {
246                         $table.tablesorter(
247                                 { sortList: [
248                                         { 0: 'asc' },
249                                         { 1: 'asc' }
250                                 ] }
251                         );
252                 }
253         );
254         tableTest(
255                 'Sorting multiple columns by programmatically triggering sort()',
256                 header,
257                 initial,
258                 descasc,
259                 function ( $table ) {
260                         $table.tablesorter();
261                         $table.data( 'tablesorter' ).sort(
262                                 [
263                                         { 0: 'desc' },
264                                         { 1: 'asc' }
265                                 ]
266                         );
267                 }
268         );
269         tableTest(
270                 'Reset to initial sorting by triggering sort() without any parameters',
271                 header,
272                 initial,
273                 asc,
274                 function ( $table ) {
275                         $table.tablesorter(
276                                 { sortList: [
277                                         { 0: 'asc' },
278                                         { 1: 'asc' }
279                                 ] }
280                         );
281                         $table.data( 'tablesorter' ).sort(
282                                 [
283                                         { 0: 'desc' },
284                                         { 1: 'asc' }
285                                 ]
286                         );
287                         $table.data( 'tablesorter' ).sort();
288                 }
289         );
290         tableTest(
291                 'Sort via click event after having initialized the tablesorter with initial sorting',
292                 header,
293                 initial,
294                 descasc,
295                 function ( $table ) {
296                         $table.tablesorter(
297                                 { sortList: [ { 0: 'asc' }, { 1: 'asc' } ] }
298                         );
299                         $table.find( '.headerSort:eq(0)' ).click();
300                 }
301         );
302         tableTest(
303                 'Multi-sort via click event after having initialized the tablesorter with initial sorting',
304                 header,
305                 initial,
306                 asc,
307                 function ( $table ) {
308                         $table.tablesorter(
309                                 { sortList: [ { 0: 'desc' }, { 1: 'desc' } ] }
310                         );
311                         $table.find( '.headerSort:eq(0)' ).click();
313                         // Pretend to click while pressing the multi-sort key
314                         var event = $.Event( 'click' );
315                         event[$table.data( 'tablesorter' ).config.sortMultiSortKey] = true;
316                         $table.find( '.headerSort:eq(1)' ).trigger( event );
317                 }
318         );
319         QUnit.test( 'Reset sorting making table appear unsorted', 3, function ( assert ) {
320                 var $table = tableCreate( header, initial );
321                 $table.tablesorter(
322                         { sortList: [
323                                 { 0: 'desc' },
324                                 { 1: 'asc' }
325                         ] }
326                 );
327                 $table.data( 'tablesorter' ).sort( [] );
329                 assert.equal(
330                         $table.find( 'th.headerSortUp' ).length + $table.find( 'th.headerSortDown' ).length,
331                         0,
332                         'No sort specific sort classes addign to header cells'
333                 );
335                 assert.equal(
336                         $table.find( 'th' ).first().attr( 'title' ),
337                         mw.msg( 'sort-ascending' ),
338                         'First header cell has default title'
339                 );
341                 assert.equal(
342                         $table.find( 'th' ).first().attr( 'title' ),
343                         $table.find( 'th' ).last().attr( 'title' ),
344                         'Both header cells\' titles match'
345                 );
346         } );
348         // Sorting with colspans
349         header = [ 'column1a' , 'column1b', 'column1c', 'column2' ];
350         var
351                 aaa1 = [ 'A', 'A', 'A', '1' ],
352                 aab5 = [ 'A', 'A', 'B', '5' ],
353                 abc3 = [ 'A', 'B', 'C', '3' ],
354                 bbc2 = [ 'B', 'B', 'C', '2' ],
355                 caa4 = [ 'C', 'A', 'A', '4' ];
356         // initial is already declared above
357         initial = [ aab5, aaa1, abc3, bbc2, caa4 ];
358         tableTest( 'Sorting with colspanned headers: spanned column',
359                 header,
360                 initial,
361                 [ aaa1, aab5, abc3, bbc2, caa4 ],
362                 function ( $table ) {
363                         // Make colspanned header for test
364                         $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
365                         $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
367                         $table.tablesorter();
368                         $table.find( '.headerSort:eq(0)' ).click();
369                 }
370         );
371         tableTest( 'Sorting with colspanned headers: sort spanned column twice',
372                 header,
373                 initial,
374                 [ caa4, bbc2, abc3, aab5, aaa1 ],
375                 function ( $table ) {
376                         // Make colspanned header for test
377                         $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
378                         $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
380                         $table.tablesorter();
381                         $table.find( '.headerSort:eq(0)' ).click();
382                         $table.find( '.headerSort:eq(0)' ).click();
383                 }
384         );
385         tableTest( 'Sorting with colspanned headers: subsequent column',
386                 header,
387                 initial,
388                 [ aaa1, bbc2, abc3, caa4, aab5 ],
389                 function ( $table ) {
390                         // Make colspanned header for test
391                         $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
392                         $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
394                         $table.tablesorter();
395                         $table.find( '.headerSort:eq(1)' ).click();
396                 }
397         );
398         tableTest( 'Sorting with colspanned headers: sort subsequent column twice',
399                 header,
400                 initial,
401                 [ aab5, caa4, abc3, bbc2, aaa1 ],
402                 function ( $table ) {
403                         // Make colspanned header for test
404                         $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove();
405                         $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
407                         $table.tablesorter();
408                         $table.find( '.headerSort:eq(1)' ).click();
409                         $table.find( '.headerSort:eq(1)' ).click();
410                 }
411         );
414         // Regression tests!
415         tableTest(
416                 'Bug 28775: German-style (dmy) short numeric dates',
417                 ['Date'],
418                 [
419                         // German-style dates are day-month-year
420                         ['11.11.2011'],
421                         ['01.11.2011'],
422                         ['02.10.2011'],
423                         ['03.08.2011'],
424                         ['09.11.2011']
425                 ],
426                 [
427                         // Sorted by ascending date
428                         ['03.08.2011'],
429                         ['02.10.2011'],
430                         ['01.11.2011'],
431                         ['09.11.2011'],
432                         ['11.11.2011']
433                 ],
434                 function ( $table ) {
435                         mw.config.set( 'wgDefaultDateFormat', 'dmy' );
436                         mw.config.set( 'wgContentLanguage', 'de' );
438                         $table.tablesorter();
439                         $table.find( '.headerSort:eq(0)' ).click();
440                 }
441         );
443         tableTest(
444                 'Bug 28775: American-style (mdy) short numeric dates',
445                 ['Date'],
446                 [
447                         // American-style dates are month-day-year
448                         ['11.11.2011'],
449                         ['01.11.2011'],
450                         ['02.10.2011'],
451                         ['03.08.2011'],
452                         ['09.11.2011']
453                 ],
454                 [
455                         // Sorted by ascending date
456                         ['01.11.2011'],
457                         ['02.10.2011'],
458                         ['03.08.2011'],
459                         ['09.11.2011'],
460                         ['11.11.2011']
461                 ],
462                 function ( $table ) {
463                         mw.config.set( 'wgDefaultDateFormat', 'mdy' );
465                         $table.tablesorter();
466                         $table.find( '.headerSort:eq(0)' ).click();
467                 }
468         );
470         var ipv4 = [
471                 // Some randomly generated fake IPs
472                 ['45.238.27.109'],
473                 ['44.172.9.22'],
474                 ['247.240.82.209'],
475                 ['204.204.132.158'],
476                 ['170.38.91.162'],
477                 ['197.219.164.9'],
478                 ['45.68.154.72'],
479                 ['182.195.149.80']
480         ];
481         var ipv4Sorted = [
482                 // Sort order should go octet by octet
483                 ['44.172.9.22'],
484                 ['45.68.154.72'],
485                 ['45.238.27.109'],
486                 ['170.38.91.162'],
487                 ['182.195.149.80'],
488                 ['197.219.164.9'],
489                 ['204.204.132.158'],
490                 ['247.240.82.209']
491         ];
493         tableTest(
494                 'Bug 17141: IPv4 address sorting',
495                 ['IP'],
496                 ipv4,
497                 ipv4Sorted,
498                 function ( $table ) {
499                         $table.tablesorter();
500                         $table.find( '.headerSort:eq(0)' ).click();
501                 }
502         );
503         tableTest(
504                 'Bug 17141: IPv4 address sorting (reverse)',
505                 ['IP'],
506                 ipv4,
507                 reversed( ipv4Sorted ),
508                 function ( $table ) {
509                         $table.tablesorter();
510                         $table.find( '.headerSort:eq(0)' ).click().click();
511                 }
512         );
514         var umlautWords = [
515                 // Some words with Umlauts
516                 ['Günther'],
517                 ['Peter'],
518                 ['Björn'],
519                 ['Bjorn'],
520                 ['Apfel'],
521                 ['Äpfel'],
522                 ['Strasse'],
523                 ['Sträßschen']
524         ];
526         var umlautWordsSorted = [
527                 // Some words with Umlauts
528                 ['Äpfel'],
529                 ['Apfel'],
530                 ['Björn'],
531                 ['Bjorn'],
532                 ['Günther'],
533                 ['Peter'],
534                 ['Sträßschen'],
535                 ['Strasse']
536         ];
538         tableTest(
539                 'Accented Characters with custom collation',
540                 ['Name'],
541                 umlautWords,
542                 umlautWordsSorted,
543                 function ( $table ) {
544                         mw.config.set( 'tableSorterCollation', {
545                                 'ä': 'ae',
546                                 'ö': 'oe',
547                                 'ß': 'ss',
548                                 'ü': 'ue'
549                         } );
551                         $table.tablesorter();
552                         $table.find( '.headerSort:eq(0)' ).click();
553                 }
554         );
556         QUnit.test( 'Rowspan not exploded on init', 1, function ( assert ) {
557                 var $table = tableCreate( header, planets );
559                 // Modify the table to have a multiple-row-spanning cell:
560                 // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
561                 $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
562                 // - Set rowspan for 2nd cell of 3rd row to 3.
563                 //   This covers the removed cell in the 4th and 5th row.
564                 $table.find( 'tr:eq(2) td:eq(1)' ).attr( 'rowspan', '3' );
566                 $table.tablesorter();
568                 assert.equal(
569                         $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowSpan' ),
570                         3,
571                         'Rowspan not exploded'
572                 );
573         } );
575         var planetsRowspan = [
576                 [ 'Earth', '6051.8' ],
577                 jupiter,
578                 [ 'Mars', '6051.8' ],
579                 mercury,
580                 saturn,
581                 venus
582         ];
583         var planetsRowspanII = [ jupiter, mercury, saturn, venus, [ 'Venus', '6371.0' ], [ 'Venus', '3390.0' ] ];
585         tableTest(
586                 'Basic planet table: same value for multiple rows via rowspan',
587                 header,
588                 planets,
589                 planetsRowspan,
590                 function ( $table ) {
591                         // Modify the table to have a multiple-row-spanning cell:
592                         // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
593                         $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
594                         // - Set rowspan for 2nd cell of 3rd row to 3.
595                         //   This covers the removed cell in the 4th and 5th row.
596                         $table.find( 'tr:eq(2) td:eq(1)' ).attr( 'rowspan', '3' );
598                         $table.tablesorter();
599                         $table.find( '.headerSort:eq(0)' ).click();
600                 }
601         );
602         tableTest(
603                 'Basic planet table: same value for multiple rows via rowspan (sorting initially)',
604                 header,
605                 planets,
606                 planetsRowspan,
607                 function ( $table ) {
608                         // Modify the table to have a multiple-row-spanning cell:
609                         // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row.
610                         $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove();
611                         // - Set rowspan for 2nd cell of 3rd row to 3.
612                         //   This covers the removed cell in the 4th and 5th row.
613                         $table.find( 'tr:eq(2) td:eq(1)' ).attr( 'rowspan', '3' );
615                         $table.tablesorter( { sortList: [
616                                 { 0: 'asc' }
617                         ] } );
618                 }
619         );
620         tableTest(
621                 'Basic planet table: Same value for multiple rows via rowspan II',
622                 header,
623                 planets,
624                 planetsRowspanII,
625                 function ( $table ) {
626                         // Modify the table to have a multiple-row-spanning cell:
627                         // - Remove 1st cell of 4th row, and, 1st cell or 5th row.
628                         $table.find( 'tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)' ).remove();
629                         // - Set rowspan for 1st cell of 3rd row to 3.
630                         //   This covers the removed cell in the 4th and 5th row.
631                         $table.find( 'tr:eq(2) td:eq(0)' ).attr( 'rowspan', '3' );
633                         $table.tablesorter();
634                         $table.find( '.headerSort:eq(0)' ).click();
635                 }
636         );
638         var complexMDYDates = [
639                 // Some words with Umlauts
640                 ['January, 19 2010'],
641                 ['April 21 1991'],
642                 ['04 22 1991'],
643                 ['5.12.1990'],
644                 ['December 12 \'10']
645         ];
647         var complexMDYSorted = [
648                 ['5.12.1990'],
649                 ['April 21 1991'],
650                 ['04 22 1991'],
651                 ['January, 19 2010'],
652                 ['December 12 \'10']
653         ];
655         tableTest(
656                 'Complex date parsing I',
657                 ['date'],
658                 complexMDYDates,
659                 complexMDYSorted,
660                 function ( $table ) {
661                         mw.config.set( 'wgDefaultDateFormat', 'mdy' );
663                         $table.tablesorter();
664                         $table.find( '.headerSort:eq(0)' ).click();
665                 }
666         );
668         var currencyUnsorted = [
669                 ['1.02 $'],
670                 ['$ 3.00'],
671                 ['€ 2,99'],
672                 ['$ 1.00'],
673                 ['$3.50'],
674                 ['$ 1.50'],
675                 ['€ 0.99']
676         ];
678         var currencySorted = [
679                 ['€ 0.99'],
680                 ['$ 1.00'],
681                 ['1.02 $'],
682                 ['$ 1.50'],
683                 ['$ 3.00'],
684                 ['$3.50'],
685                 // Comma's sort after dots
686                 // Not intentional but test to detect changes
687                 ['€ 2,99']
688         ];
690         tableTest(
691                 'Currency parsing I',
692                 ['currency'],
693                 currencyUnsorted,
694                 currencySorted,
695                 function ( $table ) {
696                         $table.tablesorter();
697                         $table.find( '.headerSort:eq(0)' ).click();
698                 }
699         );
701         var ascendingNameLegacy = ascendingName.slice( 0 );
702         ascendingNameLegacy[4] = ascendingNameLegacy[5];
703         ascendingNameLegacy.pop();
705         tableTest(
706                 'Legacy compat with .sortbottom',
707                 header,
708                 planets,
709                 ascendingNameLegacy,
710                 function ( $table ) {
711                         $table.find( 'tr:last' ).addClass( 'sortbottom' );
712                         $table.tablesorter();
713                         $table.find( '.headerSort:eq(0)' ).click();
714                 }
715         );
717         QUnit.test( 'Test detection routine', 1, function ( assert ) {
718                 var $table;
719                 $table = $(
720                         '<table class="sortable">' +
721                                 '<caption>CAPTION</caption>' +
722                                 '<tr><th>THEAD</th></tr>' +
723                                 '<tr><td>1</td></tr>' +
724                                 '<tr class="sortbottom"><td>text</td></tr>' +
725                                 '</table>'
726                 );
727                 $table.tablesorter();
728                 $table.find( '.headerSort:eq(0)' ).click();
730                 assert.equal(
731                         $table.data( 'tablesorter' ).config.parsers[0].id,
732                         'number',
733                         'Correctly detected column content skipping sortbottom'
734                 );
735         } );
737         /** FIXME: the diff output is not very readeable. */
738         QUnit.test( 'bug 32047 - caption must be before thead', 1, function ( assert ) {
739                 var $table;
740                 $table = $(
741                         '<table class="sortable">' +
742                                 '<caption>CAPTION</caption>' +
743                                 '<tr><th>THEAD</th></tr>' +
744                                 '<tr><td>A</td></tr>' +
745                                 '<tr><td>B</td></tr>' +
746                                 '<tr class="sortbottom"><td>TFOOT</td></tr>' +
747                                 '</table>'
748                 );
749                 $table.tablesorter();
751                 assert.equal(
752                         $table.children().get( 0 ).nodeName,
753                         'CAPTION',
754                         'First element after <thead> must be <caption> (bug 32047)'
755                 );
756         } );
758         QUnit.test( 'data-sort-value attribute, when available, should override sorting position', 3, function ( assert ) {
759                 var $table, data;
761                 // Example 1: All cells except one cell without data-sort-value,
762                 // which should be sorted at it's text content value.
763                 $table = $(
764                         '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
765                                 '<tbody>' +
766                                 '<tr><td>Cheetah</td></tr>' +
767                                 '<tr><td data-sort-value="Apple">Bird</td></tr>' +
768                                 '<tr><td data-sort-value="Bananna">Ferret</td></tr>' +
769                                 '<tr><td data-sort-value="Drupe">Elephant</td></tr>' +
770                                 '<tr><td data-sort-value="Cherry">Dolphin</td></tr>' +
771                                 '</tbody></table>'
772                 );
773                 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
775                 data = [];
776                 $table.find( 'tbody > tr' ).each( function ( i, tr ) {
777                         $( tr ).find( 'td' ).each( function ( i, td ) {
778                                 data.push( {
779                                         data: $( td ).data( 'sortValue' ),
780                                         text: $( td ).text()
781                                 } );
782                         } );
783                 } );
785                 assert.deepEqual( data, [
786                         {
787                                 data: 'Apple',
788                                 text: 'Bird'
789                         },
790                         {
791                                 data: 'Bananna',
792                                 text: 'Ferret'
793                         },
794                         {
795                                 data: undefined,
796                                 text: 'Cheetah'
797                         },
798                         {
799                                 data: 'Cherry',
800                                 text: 'Dolphin'
801                         },
802                         {
803                                 data: 'Drupe',
804                                 text: 'Elephant'
805                         }
806                 ], 'Order matches expected order (based on data-sort-value attribute values)' );
808                 // Example 2
809                 $table = $(
810                         '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
811                                 '<tbody>' +
812                                 '<tr><td>D</td></tr>' +
813                                 '<tr><td data-sort-value="E">A</td></tr>' +
814                                 '<tr><td>B</td></tr>' +
815                                 '<tr><td>G</td></tr>' +
816                                 '<tr><td data-sort-value="F">C</td></tr>' +
817                                 '</tbody></table>'
818                 );
819                 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
821                 data = [];
822                 $table.find( 'tbody > tr' ).each( function ( i, tr ) {
823                         $( tr ).find( 'td' ).each( function ( i, td ) {
824                                 data.push( {
825                                         data: $( td ).data( 'sortValue' ),
826                                         text: $( td ).text()
827                                 } );
828                         } );
829                 } );
831                 assert.deepEqual( data, [
832                         {
833                                 data: undefined,
834                                 text: 'B'
835                         },
836                         {
837                                 data: undefined,
838                                 text: 'D'
839                         },
840                         {
841                                 data: 'E',
842                                 text: 'A'
843                         },
844                         {
845                                 data: 'F',
846                                 text: 'C'
847                         },
848                         {
849                                 data: undefined,
850                                 text: 'G'
851                         }
852                 ], 'Order matches expected order (based on data-sort-value attribute values)' );
854                 // Example 3: Test that live changes are used from data-sort-value,
855                 // even if they change after the tablesorter is constructed (bug 38152).
856                 $table = $(
857                         '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
858                                 '<tbody>' +
859                                 '<tr><td>D</td></tr>' +
860                                 '<tr><td data-sort-value="1">A</td></tr>' +
861                                 '<tr><td>B</td></tr>' +
862                                 '<tr><td data-sort-value="2">G</td></tr>' +
863                                 '<tr><td>C</td></tr>' +
864                                 '</tbody></table>'
865                 );
866                 // initialize table sorter and sort once
867                 $table
868                         .tablesorter()
869                         .find( '.headerSort:eq(0)' ).click();
871                 // Change the sortValue data properties (bug 38152)
872                 // - change data
873                 $table.find( 'td:contains(A)' ).data( 'sortValue', 3 );
874                 // - add data
875                 $table.find( 'td:contains(B)' ).data( 'sortValue', 1 );
876                 // - remove data, bring back attribute: 2
877                 $table.find( 'td:contains(G)' ).removeData( 'sortValue' );
879                 // Now sort again (twice, so it is back at Ascending)
880                 $table.find( '.headerSort:eq(0)' ).click();
881                 $table.find( '.headerSort:eq(0)' ).click();
883                 data = [];
884                 $table.find( 'tbody > tr' ).each( function ( i, tr ) {
885                         $( tr ).find( 'td' ).each( function ( i, td ) {
886                                 data.push( {
887                                         data: $( td ).data( 'sortValue' ),
888                                         text: $( td ).text()
889                                 } );
890                         } );
891                 } );
893                 assert.deepEqual( data, [
894                         {
895                                 data: 1,
896                                 text: 'B'
897                         },
898                         {
899                                 data: 2,
900                                 text: 'G'
901                         },
902                         {
903                                 data: 3,
904                                 text: 'A'
905                         },
906                         {
907                                 data: undefined,
908                                 text: 'C'
909                         },
910                         {
911                                 data: undefined,
912                                 text: 'D'
913                         }
914                 ], 'Order matches expected order, using the current sortValue in $.data()' );
916         } );
918         var numbers = [
919                 [ '12'    ],
920                 [  '7'    ],
921                 [ '13,000'],
922                 [  '9'    ],
923                 [ '14'    ],
924                 [  '8.0'  ]
925         ];
926         var numbersAsc = [
927                 [  '7'    ],
928                 [  '8.0'  ],
929                 [  '9'    ],
930                 [ '12'    ],
931                 [ '14'    ],
932                 [ '13,000']
933         ];
935         tableTest( 'bug 8115: sort numbers with commas (ascending)',
936                 ['Numbers'], numbers, numbersAsc,
937                 function ( $table ) {
938                         $table.tablesorter();
939                         $table.find( '.headerSort:eq(0)' ).click();
940                 }
941         );
943         tableTest( 'bug 8115: sort numbers with commas (descending)',
944                 ['Numbers'], numbers, reversed( numbersAsc ),
945                 function ( $table ) {
946                         $table.tablesorter();
947                         $table.find( '.headerSort:eq(0)' ).click().click();
948                 }
949         );
950         // TODO add numbers sorting tests for bug 8115 with a different language
952         QUnit.test( 'bug 32888 - Tables inside a tableheader cell', 2, function ( assert ) {
953                 var $table;
954                 $table = $(
955                         '<table class="sortable" id="mw-bug-32888">' +
956                                 '<tr><th>header<table id="mw-bug-32888-2">' +
957                                 '<tr><th>1</th><th>2</th></tr>' +
958                                 '</table></th></tr>' +
959                                 '<tr><td>A</td></tr>' +
960                                 '<tr><td>B</td></tr>' +
961                                 '</table>'
962                 );
963                 $table.tablesorter();
965                 assert.equal(
966                         $table.find( '> thead:eq(0) > tr > th.headerSort' ).length,
967                         1,
968                         'Child tables inside a headercell should not interfere with sortable headers (bug 32888)'
969                 );
970                 assert.equal(
971                         $( '#mw-bug-32888-2' ).find( 'th.headerSort' ).length,
972                         0,
973                         'The headers of child tables inside a headercell should not be sortable themselves (bug 32888)'
974                 );
975         } );
978         var correctDateSorting1 = [
979                 ['01 January 2010'],
980                 ['05 February 2010'],
981                 ['16 January 2010']
982         ];
984         var correctDateSortingSorted1 = [
985                 ['01 January 2010'],
986                 ['16 January 2010'],
987                 ['05 February 2010']
988         ];
990         tableTest(
991                 'Correct date sorting I',
992                 ['date'],
993                 correctDateSorting1,
994                 correctDateSortingSorted1,
995                 function ( $table ) {
996                         mw.config.set( 'wgDefaultDateFormat', 'mdy' );
998                         $table.tablesorter();
999                         $table.find( '.headerSort:eq(0)' ).click();
1000                 }
1001         );
1003         var correctDateSorting2 = [
1004                 ['January 01 2010'],
1005                 ['February 05 2010'],
1006                 ['January 16 2010']
1007         ];
1009         var correctDateSortingSorted2 = [
1010                 ['January 01 2010'],
1011                 ['January 16 2010'],
1012                 ['February 05 2010']
1013         ];
1015         tableTest(
1016                 'Correct date sorting II',
1017                 ['date'],
1018                 correctDateSorting2,
1019                 correctDateSortingSorted2,
1020                 function ( $table ) {
1021                         mw.config.set( 'wgDefaultDateFormat', 'dmy' );
1023                         $table.tablesorter();
1024                         $table.find( '.headerSort:eq(0)' ).click();
1025                 }
1026         );
1028         QUnit.test( 'Sorting images using alt text', 1, function ( assert ) {
1029                 var $table = $(
1030                         '<table class="sortable">' +
1031                                 '<tr><th>THEAD</th></tr>' +
1032                                 '<tr><td><img alt="2"/></td></tr>' +
1033                                 '<tr><td>1</td></tr>' +
1034                                 '</table>'
1035                 );
1036                 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
1038                 assert.equal(
1039                         $table.find( 'td' ).first().text(),
1040                         '1',
1041                         'Applied correct sorting order'
1042                 );
1043         } );
1045         QUnit.test( 'Sorting images using alt text (complex)', 1, function ( assert ) {
1046                 var $table = $(
1047                         '<table class="sortable">' +
1048                                 '<tr><th>THEAD</th></tr>' +
1049                                 '<tr><td><img alt="D" />A</td></tr>' +
1050                                 '<tr><td>CC</td></tr>' +
1051                                 '<tr><td><a><img alt="A" /></a>F</tr>' +
1052                                 '<tr><td><img alt="A" /><strong>E</strong></tr>' +
1053                                 '<tr><td><strong><img alt="A" />D</strong></tr>' +
1054                                 '<tr><td><img alt="A" />C</tr>' +
1055                                 '</table>'
1056                 );
1057                 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
1059                 assert.equal(
1060                         $table.find( 'td' ).text(),
1061                         'CDEFCCA',
1062                         'Applied correct sorting order'
1063                 );
1064         } );
1066         QUnit.test( 'Sorting images using alt text (with format autodetection)', 1, function ( assert ) {
1067                 var $table = $(
1068                         '<table class="sortable">' +
1069                                 '<tr><th>THEAD</th></tr>' +
1070                                 '<tr><td><img alt="1" />7</td></tr>' +
1071                                 '<tr><td>1<img alt="6" /></td></tr>' +
1072                                 '<tr><td>5</td></tr>' +
1073                                 '<tr><td>4</td></tr>' +
1074                                 '</table>'
1075                 );
1076                 $table.tablesorter().find( '.headerSort:eq(0)' ).click();
1078                 assert.equal(
1079                         $table.find( 'td' ).text(),
1080                         '4517',
1081                         'Applied correct sorting order'
1082                 );
1083         } );
1085         QUnit.test( 'bug 38911 - The row with the largest amount of columns should receive the sort indicators', 3, function ( assert ) {
1086                 var $table = $(
1087                         '<table class="sortable">' +
1088                                 '<thead>' +
1089                                 '<tr><th rowspan="2" id="A1">A1</th><th colspan="2">B2a</th></tr>' +
1090                                 '<tr><th id="B2b">B2b</th><th id="C2b">C2b</th></tr>' +
1091                                 '</thead>' +
1092                                 '<tr><td>A</td><td>Aa</td><td>Ab</td></tr>' +
1093                                 '<tr><td>B</td><td>Ba</td><td>Bb</td></tr>' +
1094                                 '</table>'
1095                 );
1096                 $table.tablesorter();
1098                 assert.equal(
1099                         $table.find( '#A1' ).attr( 'class' ),
1100                         'headerSort',
1101                         'The first column of the first row should be sortable'
1102                 );
1103                 assert.equal(
1104                         $table.find( '#B2b' ).attr( 'class' ),
1105                         'headerSort',
1106                         'The th element of the 2nd row of the 2nd column should be sortable'
1107                 );
1108                 assert.equal(
1109                         $table.find( '#C2b' ).attr( 'class' ),
1110                         'headerSort',
1111                         'The th element of the 2nd row of the 3rd column should be sortable'
1112                 );
1113         } );
1115         QUnit.test( 'rowspans in table headers should prefer the last row when rows are equal in length', 2, function ( assert ) {
1116                 var $table = $(
1117                         '<table class="sortable">' +
1118                                 '<thead>' +
1119                                 '<tr><th rowspan="2" id="A1">A1</th><th>B2a</th></tr>' +
1120                                 '<tr><th id="B2b">B2b</th></tr>' +
1121                                 '</thead>' +
1122                                 '<tr><td>A</td><td>Aa</td></tr>' +
1123                                 '<tr><td>B</td><td>Ba</td></tr>' +
1124                                 '</table>'
1125                 );
1126                 $table.tablesorter();
1128                 assert.equal(
1129                         $table.find( '#A1' ).attr( 'class' ),
1130                         'headerSort',
1131                         'The first column of the first row should be sortable'
1132                 );
1133                 assert.equal(
1134                         $table.find( '#B2b' ).attr( 'class' ),
1135                         'headerSort',
1136                         'The th element of the 2nd row of the 2nd column should be sortable'
1137                 );
1138         } );
1140         // bug 41889 - exploding rowspans in more complex cases
1141         tableTestHTML(
1142                 'Rowspan exploding with row headers',
1143                 '<table class="sortable">' +
1144                         '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' +
1145                         '<tbody>' +
1146                         '<tr><td>1</td><th rowspan="2">foo</th><td rowspan="2">bar</td><td>baz</td></tr>' +
1147                         '<tr><td>2</td><td>baz</td></tr>' +
1148                         '</tbody></table>',
1149                 [
1150                         [ '1', 'foo', 'bar', 'baz' ],
1151                         [ '2', 'foo', 'bar', 'baz' ]
1152                 ]
1153         );
1155         tableTestHTML(
1156                 'Rowspan exploding with colspanned cells',
1157                 '<table class="sortable">' +
1158                         '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' +
1159                         '<tbody>' +
1160                         '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td></tr>' +
1161                         '<tr><td>2</td><td colspan="2">foobar</td></tr>' +
1162                         '</tbody></table>',
1163                 [
1164                         [ '1', 'foo', 'bar', 'baz' ],
1165                         [ '2', 'foobar', 'baz' ]
1166                 ]
1167         );
1169         tableTestHTML(
1170                 'Rowspan exploding with colspanned cells (2)',
1171                 '<table class="sortable">' +
1172                         '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th><th>quux</th></tr></thead>' +
1173                         '<tbody>' +
1174                         '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td><td>quux</td></tr>' +
1175                         '<tr><td>2</td><td colspan="2">foobar</td><td>quux</td></tr>' +
1176                         '</tbody></table>',
1177                 [
1178                         [ '1', 'foo', 'bar', 'baz', 'quux' ],
1179                         [ '2', 'foobar', 'baz', 'quux' ]
1180                 ]
1181         );
1183         tableTestHTML(
1184                 'Rowspan exploding with rightmost rows spanning most',
1185                 '<table class="sortable">' +
1186                         '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th></tr></thead>' +
1187                         '<tbody>' +
1188                         '<tr><td>1</td><td rowspan="2">foo</td><td rowspan="4">bar</td></tr>' +
1189                         '<tr><td>2</td></tr>' +
1190                         '<tr><td>3</td><td rowspan="2">foo</td></tr>' +
1191                         '<tr><td>4</td></tr>' +
1192                         '</tbody></table>',
1193                 [
1194                         [ '1', 'foo', 'bar' ],
1195                         [ '2', 'foo', 'bar' ],
1196                         [ '3', 'foo', 'bar' ],
1197                         [ '4', 'foo', 'bar' ]
1198                 ]
1199         );
1201         tableTestHTML(
1202                 'Rowspan exploding with rightmost rows spanning most (2)',
1203                 '<table class="sortable">' +
1204                         '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' +
1205                         '<tbody>' +
1206                         '<tr><td>1</td><td rowspan="2">foo</td><td rowspan="4">bar</td><td>baz</td></tr>' +
1207                         '<tr><td>2</td><td>baz</td></tr>' +
1208                         '<tr><td>3</td><td rowspan="2">foo</td><td>baz</td></tr>' +
1209                         '<tr><td>4</td><td>baz</td></tr>' +
1210                         '</tbody></table>',
1211                 [
1212                         [ '1', 'foo', 'bar', 'baz' ],
1213                         [ '2', 'foo', 'bar', 'baz' ],
1214                         [ '3', 'foo', 'bar', 'baz' ],
1215                         [ '4', 'foo', 'bar', 'baz' ]
1216                 ]
1217         );
1219         tableTestHTML(
1220                 'Rowspan exploding with row-and-colspanned cells',
1221                 '<table class="sortable">' +
1222                         '<thead><tr><th id="sortme">n</th><th>foo1</th><th>foo2</th><th>bar</th><th>baz</th></tr></thead>' +
1223                         '<tbody>' +
1224                         '<tr><td>1</td><td rowspan="2">foo1</td><td rowspan="2">foo2</td><td rowspan="4">bar</td><td>baz</td></tr>' +
1225                         '<tr><td>2</td><td>baz</td></tr>' +
1226                         '<tr><td>3</td><td colspan="2" rowspan="2">foo</td><td>baz</td></tr>' +
1227                         '<tr><td>4</td><td>baz</td></tr>' +
1228                         '</tbody></table>',
1229                 [
1230                         [ '1', 'foo1', 'foo2', 'bar', 'baz' ],
1231                         [ '2', 'foo1', 'foo2', 'bar', 'baz' ],
1232                         [ '3', 'foo', 'bar', 'baz' ],
1233                         [ '4', 'foo', 'bar', 'baz' ]
1234                 ]
1235         );
1237         tableTestHTML(
1238                 'Rowspan exploding with uneven rowspan layout',
1239                 '<table class="sortable">' +
1240                         '<thead><tr><th id="sortme">n</th><th>foo1</th><th>foo2</th><th>foo3</th><th>bar</th><th>baz</th></tr></thead>' +
1241                         '<tbody>' +
1242                         '<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>' +
1243                         '<tr><td>2</td><td rowspan="3">bar</td><td>baz</td></tr>' +
1244                         '<tr><td>3</td><td rowspan="2">foo1</td><td rowspan="2">foo2</td><td rowspan="2">foo3</td><td>baz</td></tr>' +
1245                         '<tr><td>4</td><td>baz</td></tr>' +
1246                         '</tbody></table>',
1247                 [
1248                         [ '1', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ],
1249                         [ '2', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ],
1250                         [ '3', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ],
1251                         [ '4', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ]
1252                 ]
1253         );
1255 }( jQuery, mediaWiki ) );