Differences Between: [Versions 310 and 311] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace core; 18 19 use flexible_table; 20 use testable_flexible_table; 21 22 defined('MOODLE_INTERNAL') || die(); 23 24 global $CFG; 25 require_once($CFG->libdir . '/tablelib.php'); 26 require_once($CFG->libdir . '/tests/fixtures/testable_flexible_table.php'); 27 28 /** 29 * Test some of tablelib. 30 * 31 * @package core 32 * @category test 33 * @copyright 2013 Damyon Wiese <damyon@moodle.com> 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 */ 36 class tablelib_test extends \advanced_testcase { 37 protected function generate_columns($cols) { 38 $columns = array(); 39 foreach (range(0, $cols - 1) as $j) { 40 array_push($columns, 'column' . $j); 41 } 42 return $columns; 43 } 44 45 protected function generate_headers($cols) { 46 $columns = array(); 47 foreach (range(0, $cols - 1) as $j) { 48 array_push($columns, 'Column ' . $j); 49 } 50 return $columns; 51 } 52 53 protected function generate_data($rows, $cols) { 54 $data = array(); 55 56 foreach (range(0, $rows - 1) as $i) { 57 $row = array(); 58 foreach (range(0, $cols - 1) as $j) { 59 $val = 'row ' . $i . ' col ' . $j; 60 $row['column' . $j] = $val; 61 } 62 array_push($data, $row); 63 } 64 return $data; 65 } 66 67 /** 68 * Create a table with properties as passed in params, add data and output html. 69 * 70 * @param string[] $columns 71 * @param string[] $headers 72 * @param bool $sortable 73 * @param bool $collapsible 74 * @param string[] $suppress 75 * @param string[] $nosorting 76 * @param (array|object)[] $data 77 * @param int $pagesize 78 */ 79 protected function run_table_test($columns, $headers, $sortable, $collapsible, $suppress, $nosorting, $data, $pagesize) { 80 $table = $this->create_and_setup_table($columns, $headers, $sortable, $collapsible, $suppress, $nosorting); 81 $table->pagesize($pagesize, count($data)); 82 foreach ($data as $row) { 83 $table->add_data_keyed($row); 84 } 85 $table->finish_output(); 86 } 87 88 /** 89 * Create a table with properties as passed in params. 90 * 91 * @param string[] $columns 92 * @param string[] $headers 93 * @param bool $sortable 94 * @param bool $collapsible 95 * @param string[] $suppress 96 * @param string[] $nosorting 97 * @return flexible_table 98 */ 99 protected function create_and_setup_table($columns, $headers, $sortable, $collapsible, $suppress, $nosorting) { 100 $table = new flexible_table('tablelib_test'); 101 102 $table->define_columns($columns); 103 $table->define_headers($headers); 104 $table->define_baseurl('/invalid.php'); 105 106 $table->sortable($sortable); 107 $table->collapsible($collapsible); 108 foreach ($suppress as $column) { 109 $table->column_suppress($column); 110 } 111 112 foreach ($nosorting as $column) { 113 $table->no_sorting($column); 114 } 115 116 $table->setup(); 117 return $table; 118 } 119 120 public function test_empty_table() { 121 $this->expectOutputRegex('/' . get_string('nothingtodisplay') . '/'); 122 $this->run_table_test( 123 array('column1', 'column2'), // Columns. 124 array('Column 1', 'Column 2'), // Headers. 125 true, // Sortable. 126 false, // Collapsible. 127 array(), // Suppress columns. 128 array(), // No sorting. 129 array(), // Data. 130 10 // Page size. 131 ); 132 } 133 134 public function test_has_next_pagination() { 135 136 $data = $this->generate_data(11, 2); 137 $columns = $this->generate_columns(2); 138 $headers = $this->generate_headers(2); 139 140 // Search for pagination controls containing 'page-link"\saria-label="Next"'. 141 $this->expectOutputRegex('/Next page/'); 142 143 $this->run_table_test( 144 $columns, 145 $headers, 146 true, 147 false, 148 array(), 149 array(), 150 $data, 151 10 152 ); 153 } 154 155 public function test_has_hide() { 156 157 $data = $this->generate_data(11, 2); 158 $columns = $this->generate_columns(2); 159 $headers = $this->generate_headers(2); 160 161 // Search for 'hide' links in the column headers. 162 $this->expectOutputRegex('/' . get_string('hide') . '/'); 163 164 $this->run_table_test( 165 $columns, 166 $headers, 167 true, 168 true, 169 array(), 170 array(), 171 $data, 172 10 173 ); 174 } 175 176 public function test_has_not_hide() { 177 178 $data = $this->generate_data(11, 2); 179 $columns = $this->generate_columns(2); 180 $headers = $this->generate_headers(2); 181 182 // Make sure there are no 'hide' links in the headers. 183 184 ob_start(); 185 $this->run_table_test( 186 $columns, 187 $headers, 188 true, 189 false, 190 array(), 191 array(), 192 $data, 193 10 194 ); 195 $output = ob_get_contents(); 196 ob_end_clean(); 197 $this->assertStringNotContainsString(get_string('hide'), $output); 198 } 199 200 public function test_has_sort() { 201 202 $data = $this->generate_data(11, 2); 203 $columns = $this->generate_columns(2); 204 $headers = $this->generate_headers(2); 205 206 // Search for pagination controls containing '1.*2</a>.*Next</a>'. 207 $this->expectOutputRegex('/' . get_string('sortby') . '/'); 208 209 $this->run_table_test( 210 $columns, 211 $headers, 212 true, 213 false, 214 array(), 215 array(), 216 $data, 217 10 218 ); 219 } 220 221 public function test_has_not_sort() { 222 223 $data = $this->generate_data(11, 2); 224 $columns = $this->generate_columns(2); 225 $headers = $this->generate_headers(2); 226 227 // Make sure there are no 'Sort by' links in the headers. 228 229 ob_start(); 230 $this->run_table_test( 231 $columns, 232 $headers, 233 false, 234 false, 235 array(), 236 array(), 237 $data, 238 10 239 ); 240 $output = ob_get_contents(); 241 ob_end_clean(); 242 $this->assertStringNotContainsString(get_string('sortby'), $output); 243 } 244 245 public function test_has_not_next_pagination() { 246 247 $data = $this->generate_data(10, 2); 248 $columns = $this->generate_columns(2); 249 $headers = $this->generate_headers(2); 250 251 // Make sure there are no 'Next' links in the pagination. 252 253 ob_start(); 254 $this->run_table_test( 255 $columns, 256 $headers, 257 true, 258 false, 259 array(), 260 array(), 261 $data, 262 10 263 ); 264 265 $output = ob_get_contents(); 266 ob_end_clean(); 267 $this->assertStringNotContainsString(get_string('next'), $output); 268 } 269 270 public function test_1_col() { 271 272 $data = $this->generate_data(100, 1); 273 $columns = $this->generate_columns(1); 274 $headers = $this->generate_headers(1); 275 276 $this->expectOutputRegex('/row 0 col 0/'); 277 278 $this->run_table_test( 279 $columns, 280 $headers, 281 true, 282 false, 283 array(), 284 array(), 285 $data, 286 10 287 ); 288 } 289 290 public function test_empty_rows() { 291 292 $data = $this->generate_data(1, 5); 293 $columns = $this->generate_columns(5); 294 $headers = $this->generate_headers(5); 295 296 // Test that we have at least 5 columns generated for each empty row. 297 $this->expectOutputRegex('/emptyrow.*r9_c4/'); 298 299 $this->run_table_test( 300 $columns, 301 $headers, 302 true, 303 false, 304 array(), 305 array(), 306 $data, 307 10 308 ); 309 } 310 311 public function test_5_cols() { 312 313 $data = $this->generate_data(100, 5); 314 $columns = $this->generate_columns(5); 315 $headers = $this->generate_headers(5); 316 317 $this->expectOutputRegex('/row 0 col 0/'); 318 319 $this->run_table_test( 320 $columns, 321 $headers, 322 true, 323 false, 324 array(), 325 array(), 326 $data, 327 10 328 ); 329 } 330 331 public function test_50_cols() { 332 333 $data = $this->generate_data(100, 50); 334 $columns = $this->generate_columns(50); 335 $headers = $this->generate_headers(50); 336 337 $this->expectOutputRegex('/row 0 col 0/'); 338 339 $this->run_table_test( 340 $columns, 341 $headers, 342 true, 343 false, 344 array(), 345 array(), 346 $data, 347 10 348 ); 349 } 350 351 /** 352 * Data provider for test_fullname_column 353 * 354 * @return array 355 */ 356 public function fullname_column_provider() { 357 return [ 358 ['language'], 359 ['alternatename lastname'], 360 ['firstname lastnamephonetic'], 361 ]; 362 } 363 364 /** 365 * Test fullname column observes configured alternate fullname format configuration 366 * 367 * @param string $format 368 * @return void 369 * 370 * @dataProvider fullname_column_provider 371 */ 372 public function test_fullname_column(string $format) { 373 $this->resetAfterTest(); 374 $this->setAdminUser(); 375 376 set_config('alternativefullnameformat', $format); 377 378 $user = $this->getDataGenerator()->create_user(); 379 380 $table = $this->create_and_setup_table(['fullname'], [], true, false, [], []); 381 $this->assertStringContainsString(fullname($user, true), $table->format_row($user)['fullname']); 382 } 383 384 /** 385 * Test fullname column ignores fullname format configuration for a user with viewfullnames capability prohibited 386 * 387 * @param string $format 388 * @return void 389 * 390 * @dataProvider fullname_column_provider 391 */ 392 public function test_fullname_column_prohibit_viewfullnames(string $format) { 393 global $DB, $CFG; 394 395 $this->resetAfterTest(); 396 397 set_config('alternativefullnameformat', $format); 398 399 $currentuser = $this->getDataGenerator()->create_user(); 400 $this->setUser($currentuser); 401 402 // Prohibit the viewfullnames from the default user role. 403 $userrole = $DB->get_record('role', ['id' => $CFG->defaultuserroleid]); 404 role_change_permission($userrole->id, \context_system::instance(), 'moodle/site:viewfullnames', CAP_PROHIBIT); 405 406 $user = $this->getDataGenerator()->create_user(); 407 408 $table = $this->create_and_setup_table(['fullname'], [], true, false, [], []); 409 $this->assertStringContainsString(fullname($user, false), $table->format_row($user)['fullname']); 410 } 411 412 public function test_get_row_html() { 413 $data = $this->generate_data(1, 5); 414 $columns = $this->generate_columns(5); 415 $headers = $this->generate_headers(5); 416 $data = array_keys(array_flip($data[0])); 417 418 $table = new flexible_table('tablelib_test'); 419 $table->define_columns($columns); 420 $table->define_headers($headers); 421 $table->define_baseurl('/invalid.php'); 422 423 $row = $table->get_row_html($data); 424 $this->assertMatchesRegularExpression('/row 0 col 0/', $row); 425 $this->assertMatchesRegularExpression('/<tr class=""/', $row); 426 $this->assertMatchesRegularExpression('/<td class="cell c0"/', $row); 427 } 428 429 public function test_persistent_table() { 430 global $SESSION; 431 432 $data = $this->generate_data(5, 5); 433 $columns = $this->generate_columns(5); 434 $headers = $this->generate_headers(5); 435 436 // Testing without persistence first to verify that the results are different. 437 $table1 = new flexible_table('tablelib_test'); 438 $table1->define_columns($columns); 439 $table1->define_headers($headers); 440 $table1->define_baseurl('/invalid.php'); 441 442 $table1->sortable(true); 443 $table1->collapsible(true); 444 445 $table1->is_persistent(false); 446 $_GET['thide'] = 'column0'; 447 $_GET['tsort'] = 'column1'; 448 $_GET['tifirst'] = 'A'; 449 $_GET['tilast'] = 'Z'; 450 451 foreach ($data as $row) { 452 $table1->add_data_keyed($row); 453 } 454 $table1->setup(); 455 456 // Clear session data between each new table. 457 unset($SESSION->flextable); 458 459 $table2 = new flexible_table('tablelib_test'); 460 $table2->define_columns($columns); 461 $table2->define_headers($headers); 462 $table2->define_baseurl('/invalid.php'); 463 464 $table2->sortable(true); 465 $table2->collapsible(true); 466 467 $table2->is_persistent(false); 468 unset($_GET); 469 470 foreach ($data as $row) { 471 $table2->add_data_keyed($row); 472 } 473 $table2->setup(); 474 475 $this->assertNotEquals($table1, $table2); 476 477 unset($SESSION->flextable); 478 479 // Now testing with persistence to check that the tables are the same. 480 $table3 = new flexible_table('tablelib_test'); 481 $table3->define_columns($columns); 482 $table3->define_headers($headers); 483 $table3->define_baseurl('/invalid.php'); 484 485 $table3->sortable(true); 486 $table3->collapsible(true); 487 488 $table3->is_persistent(true); 489 $_GET['thide'] = 'column0'; 490 $_GET['tsort'] = 'column1'; 491 $_GET['tifirst'] = 'A'; 492 $_GET['tilast'] = 'Z'; 493 494 foreach ($data as $row) { 495 $table3->add_data_keyed($row); 496 } 497 $table3->setup(); 498 499 unset($SESSION->flextable); 500 501 $table4 = new flexible_table('tablelib_test'); 502 $table4->define_columns($columns); 503 $table4->define_headers($headers); 504 $table4->define_baseurl('/invalid.php'); 505 506 $table4->sortable(true); 507 $table4->collapsible(true); 508 509 $table4->is_persistent(true); 510 unset($_GET); 511 512 foreach ($data as $row) { 513 $table4->add_data_keyed($row); 514 } 515 $table4->setup(); 516 517 $this->assertEquals($table3, $table4); 518 519 unset($SESSION->flextable); 520 521 // Finally, another test with no persistence, but without clearing the session data. 522 $table5 = new flexible_table('tablelib_test'); 523 $table5->define_columns($columns); 524 $table5->define_headers($headers); 525 $table5->define_baseurl('/invalid.php'); 526 527 $table5->sortable(true); 528 $table5->collapsible(true); 529 530 $table5->is_persistent(true); 531 $_GET['thide'] = 'column0'; 532 $_GET['tsort'] = 'column1'; 533 $_GET['tifirst'] = 'A'; 534 $_GET['tilast'] = 'Z'; 535 536 foreach ($data as $row) { 537 $table5->add_data_keyed($row); 538 } 539 $table5->setup(); 540 541 $table6 = new flexible_table('tablelib_test'); 542 $table6->define_columns($columns); 543 $table6->define_headers($headers); 544 $table6->define_baseurl('/invalid.php'); 545 546 $table6->sortable(true); 547 $table6->collapsible(true); 548 549 $table6->is_persistent(true); 550 unset($_GET); 551 552 foreach ($data as $row) { 553 $table6->add_data_keyed($row); 554 } 555 $table6->setup(); 556 557 $this->assertEquals($table5, $table6); 558 } 559 560 /** 561 * Helper method for preparing tables instances in {@link self::test_can_be_reset()}. 562 * 563 * @param string $tableid 564 * @return testable_flexible_table 565 */ 566 protected function prepare_table_for_reset_test($tableid) { 567 global $SESSION; 568 569 unset($SESSION->flextable[$tableid]); 570 571 $data = $this->generate_data(25, 3); 572 $columns = array('column0', 'column1', 'column2'); 573 $headers = $this->generate_headers(3); 574 575 $table = new testable_flexible_table($tableid); 576 $table->define_baseurl('/invalid.php'); 577 $table->define_columns($columns); 578 $table->define_headers($headers); 579 $table->collapsible(true); 580 $table->is_persistent(false); 581 582 return $table; 583 } 584 585 public function test_can_be_reset() { 586 // Table in its default state (as if seen for the first time), nothing to reset. 587 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_')); 588 $table->setup(); 589 $this->assertFalse($table->can_be_reset()); 590 591 // Table in its default state with default sorting defined, nothing to reset. 592 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_')); 593 $table->sortable(true, 'column1', SORT_DESC); 594 $table->setup(); 595 $this->assertFalse($table->can_be_reset()); 596 597 // Table explicitly sorted by the default column & direction, nothing to reset. 598 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_')); 599 $table->sortable(true, 'column1', SORT_DESC); 600 $_GET['tsort'] = 'column1'; 601 $_GET['tdir'] = SORT_DESC; 602 $table->setup(); 603 unset($_GET['tsort']); 604 unset($_GET['tdir']); 605 $this->assertFalse($table->can_be_reset()); 606 607 // Table explicitly sorted twice by the default column & direction, nothing to reset. 608 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_')); 609 $table->sortable(true, 'column1', SORT_DESC); 610 $_GET['tsort'] = 'column1'; 611 $_GET['tdir'] = SORT_DESC; 612 $table->setup(); 613 $table->setup(); // Set up again to simulate the second page request. 614 unset($_GET['tsort']); 615 unset($_GET['tdir']); 616 $this->assertFalse($table->can_be_reset()); 617 618 // Table sorted by other than default column, can be reset. 619 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_')); 620 $table->sortable(true, 'column1', SORT_DESC); 621 $_GET['tsort'] = 'column2'; 622 $table->setup(); 623 unset($_GET['tsort']); 624 $this->assertTrue($table->can_be_reset()); 625 626 // Table sorted by other than default direction, can be reset. 627 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_')); 628 $table->sortable(true, 'column1', SORT_DESC); 629 $_GET['tsort'] = 'column1'; 630 $_GET['tdir'] = SORT_ASC; 631 $table->setup(); 632 unset($_GET['tsort']); 633 unset($_GET['tdir']); 634 $this->assertTrue($table->can_be_reset()); 635 636 // Table sorted by the default column after another sorting previously selected. 637 // This leads to different ORDER BY than just having a single sort defined, can be reset. 638 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_')); 639 $table->sortable(true, 'column1', SORT_DESC); 640 $_GET['tsort'] = 'column0'; 641 $table->setup(); 642 $_GET['tsort'] = 'column1'; 643 $table->setup(); 644 unset($_GET['tsort']); 645 $this->assertTrue($table->can_be_reset()); 646 647 // Table having some column collapsed, can be reset. 648 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_')); 649 $_GET['thide'] = 'column2'; 650 $table->setup(); 651 unset($_GET['thide']); 652 $this->assertTrue($table->can_be_reset()); 653 654 // Table having some column explicitly expanded, nothing to reset. 655 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_')); 656 $_GET['tshow'] = 'column2'; 657 $table->setup(); 658 unset($_GET['tshow']); 659 $this->assertFalse($table->can_be_reset()); 660 661 // Table after expanding a collapsed column, nothing to reset. 662 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_')); 663 $_GET['thide'] = 'column0'; 664 $table->setup(); 665 $_GET['tshow'] = 'column0'; 666 $table->setup(); 667 unset($_GET['thide']); 668 unset($_GET['tshow']); 669 $this->assertFalse($table->can_be_reset()); 670 671 // Table with some name filtering enabled, can be reset. 672 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_')); 673 $_GET['tifirst'] = 'A'; 674 $table->setup(); 675 unset($_GET['tifirst']); 676 $this->assertTrue($table->can_be_reset()); 677 } 678 679 /** 680 * Test export in CSV format 681 */ 682 public function test_table_export() { 683 $table = new flexible_table('tablelib_test_export'); 684 $table->define_baseurl('/invalid.php'); 685 $table->define_columns(['c1', 'c2', 'c3']); 686 $table->define_headers(['Col1', 'Col2', 'Col3']); 687 688 ob_start(); 689 $table->is_downloadable(true); 690 $table->is_downloading('csv'); 691 692 $table->setup(); 693 $table->add_data(['column0' => 'a', 'column1' => 'b', 'column2' => 'c']); 694 $output = ob_get_contents(); 695 ob_end_clean(); 696 697 $this->assertEquals("Col1,Col2,Col3\na,b,c\n", substr($output, 3)); 698 } 699 700 /** 701 * Test the initials functionality. 702 * 703 * @dataProvider initials_provider 704 * @param string|null $getvalue 705 * @param string|null $setvalue 706 * @param string|null $finalvalue 707 */ 708 public function test_initials_first_set(?string $getvalue, ?string $setvalue, ?string $finalvalue): void { 709 global $_GET; 710 711 $this->resetAfterTest(true); 712 713 $table = new flexible_table('tablelib_test'); 714 715 $user = $this->getDataGenerator()->create_user(); 716 717 $table->define_columns(['fullname']); 718 $table->define_headers(['Fullname']); 719 $table->define_baseurl('/invalid.php'); 720 $table->initialbars(true); 721 722 if ($getvalue !== null) { 723 $_GET['tifirst'] = $getvalue; 724 } 725 726 if ($setvalue !== null) { 727 $table->set_first_initial($setvalue); 728 } 729 730 $table->setup(); 731 732 $this->assertEquals($finalvalue, $table->get_initial_first()); 733 } 734 735 /** 736 * Test the initials functionality. 737 * 738 * @dataProvider initials_provider 739 * @param string|null $getvalue 740 * @param string|null $setvalue 741 * @param string|null $finalvalue 742 */ 743 public function test_initials_last_set(?string $getvalue, ?string $setvalue, ?string $finalvalue): void { 744 global $_GET; 745 746 $this->resetAfterTest(true); 747 748 $table = new flexible_table('tablelib_test'); 749 750 $user = $this->getDataGenerator()->create_user(); 751 752 $table->define_columns(['fullname']); 753 $table->define_headers(['Fullname']); 754 $table->define_baseurl('/invalid.php'); 755 $table->initialbars(true); 756 757 if ($getvalue !== null) { 758 $_GET['tilast'] = $getvalue; 759 } 760 761 if ($setvalue !== null) { 762 $table->set_last_initial($setvalue); 763 } 764 765 $table->setup(); 766 767 $this->assertEquals($finalvalue, $table->get_initial_last()); 768 } 769 770 /** 771 * Data for testing initials providers. 772 * 773 * @return array 774 */ 775 public function initials_provider(): array { 776 return [ 777 [null, null, null], 778 ['A', null, 'A'], 779 ['Z', null, 'Z'], 780 [null, 'A', 'A'], 781 [null, 'Z', 'Z'], 782 ['A', 'Z', 'Z'], 783 ['Z', 'A', 'A'], 784 ]; 785 } 786 787 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body