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