2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
20 use testable_flexible_table
;
22 defined('MOODLE_INTERNAL') ||
die();
25 require_once($CFG->libdir
. '/tablelib.php');
26 require_once($CFG->libdir
. '/tests/fixtures/testable_flexible_table.php');
29 * Test some of tablelib.
33 * @copyright 2013 Damyon Wiese <damyon@moodle.com>
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 class tablelib_test
extends \advanced_testcase
{
37 protected function generate_columns($cols) {
39 foreach (range(0, $cols - 1) as $j) {
40 array_push($columns, 'column' . $j);
45 protected function generate_headers($cols) {
47 foreach (range(0, $cols - 1) as $j) {
48 array_push($columns, 'Column ' . $j);
53 protected function generate_data($rows, $cols) {
56 foreach (range(0, $rows - 1) as $i) {
58 foreach (range(0, $cols - 1) as $j) {
59 $val = 'row ' . $i . ' col ' . $j;
60 $row['column' . $j] = $val;
62 array_push($data, $row);
68 * Create a table with properties as passed in params, add data and output html.
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
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);
85 $table->finish_output();
89 * Create a table with properties as passed in params.
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
99 protected function create_and_setup_table($columns, $headers, $sortable, $collapsible, $suppress, $nosorting) {
100 $table = new flexible_table('tablelib_test');
102 $table->define_columns($columns);
103 $table->define_headers($headers);
104 $table->define_baseurl('/invalid.php');
106 $table->sortable($sortable);
107 $table->collapsible($collapsible);
108 foreach ($suppress as $column) {
109 $table->column_suppress($column);
112 foreach ($nosorting as $column) {
113 $table->no_sorting($column);
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.
126 false, // Collapsible.
127 array(), // Suppress columns.
128 array(), // No sorting.
134 public function test_has_next_pagination() {
136 $data = $this->generate_data(11, 2);
137 $columns = $this->generate_columns(2);
138 $headers = $this->generate_headers(2);
140 // Search for pagination controls containing 'page-link"\saria-label="Next"'.
141 $this->expectOutputRegex('/Next page/');
143 $this->run_table_test(
155 public function test_has_hide() {
157 $data = $this->generate_data(11, 2);
158 $columns = $this->generate_columns(2);
159 $headers = $this->generate_headers(2);
161 // Search for 'hide' links in the column headers.
162 $this->expectOutputRegex('/' . get_string('hide') . '/');
164 $this->run_table_test(
176 public function test_has_not_hide() {
178 $data = $this->generate_data(11, 2);
179 $columns = $this->generate_columns(2);
180 $headers = $this->generate_headers(2);
182 // Make sure there are no 'hide' links in the headers.
185 $this->run_table_test(
195 $output = ob_get_contents();
197 $this->assertStringNotContainsString(get_string('hide'), $output);
200 public function test_has_sort() {
202 $data = $this->generate_data(11, 2);
203 $columns = $this->generate_columns(2);
204 $headers = $this->generate_headers(2);
206 // Search for pagination controls containing '1.*2</a>.*Next</a>'.
207 $this->expectOutputRegex('/' . get_string('sortby') . '/');
209 $this->run_table_test(
221 public function test_has_not_sort() {
223 $data = $this->generate_data(11, 2);
224 $columns = $this->generate_columns(2);
225 $headers = $this->generate_headers(2);
227 // Make sure there are no 'Sort by' links in the headers.
230 $this->run_table_test(
240 $output = ob_get_contents();
242 $this->assertStringNotContainsString(get_string('sortby'), $output);
245 public function test_has_not_next_pagination() {
247 $data = $this->generate_data(10, 2);
248 $columns = $this->generate_columns(2);
249 $headers = $this->generate_headers(2);
251 // Make sure there are no 'Next' links in the pagination.
254 $this->run_table_test(
265 $output = ob_get_contents();
267 $this->assertStringNotContainsString(get_string('next'), $output);
270 public function test_1_col() {
272 $data = $this->generate_data(100, 1);
273 $columns = $this->generate_columns(1);
274 $headers = $this->generate_headers(1);
276 $this->expectOutputRegex('/row 0 col 0/');
278 $this->run_table_test(
290 public function test_empty_rows() {
292 $data = $this->generate_data(1, 5);
293 $columns = $this->generate_columns(5);
294 $headers = $this->generate_headers(5);
296 // Test that we have at least 5 columns generated for each empty row.
297 $this->expectOutputRegex('/emptyrow.*r9_c4/');
299 $this->run_table_test(
311 public function test_5_cols() {
313 $data = $this->generate_data(100, 5);
314 $columns = $this->generate_columns(5);
315 $headers = $this->generate_headers(5);
317 $this->expectOutputRegex('/row 0 col 0/');
319 $this->run_table_test(
331 public function test_50_cols() {
333 $data = $this->generate_data(100, 50);
334 $columns = $this->generate_columns(50);
335 $headers = $this->generate_headers(50);
337 $this->expectOutputRegex('/row 0 col 0/');
339 $this->run_table_test(
352 * Data provider for test_fullname_column
356 public function fullname_column_provider() {
359 ['alternatename lastname'],
360 ['firstname lastnamephonetic'],
365 * Test fullname column observes configured alternate fullname format configuration
367 * @param string $format
370 * @dataProvider fullname_column_provider
372 public function test_fullname_column(string $format) {
373 $this->resetAfterTest();
374 $this->setAdminUser();
376 set_config('alternativefullnameformat', $format);
378 $user = $this->getDataGenerator()->create_user();
380 $table = $this->create_and_setup_table(['fullname'], [], true, false, [], []);
381 $this->assertStringContainsString(fullname($user, true), $table->format_row($user)['fullname']);
385 * Test fullname column ignores fullname format configuration for a user with viewfullnames capability prohibited
387 * @param string $format
390 * @dataProvider fullname_column_provider
392 public function test_fullname_column_prohibit_viewfullnames(string $format) {
395 $this->resetAfterTest();
397 set_config('alternativefullnameformat', $format);
399 $currentuser = $this->getDataGenerator()->create_user();
400 $this->setUser($currentuser);
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
);
406 $user = $this->getDataGenerator()->create_user();
408 $table = $this->create_and_setup_table(['fullname'], [], true, false, [], []);
409 $this->assertStringContainsString(fullname($user, false), $table->format_row($user)['fullname']);
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]));
418 $table = new flexible_table('tablelib_test');
419 $table->define_columns($columns);
420 $table->define_headers($headers);
421 $table->define_baseurl('/invalid.php');
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);
429 public function test_persistent_table() {
432 $data = $this->generate_data(5, 5);
433 $columns = $this->generate_columns(5);
434 $headers = $this->generate_headers(5);
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');
442 $table1->sortable(true);
443 $table1->collapsible(true);
445 $table1->is_persistent(false);
446 $_GET['thide'] = 'column0';
447 $_GET['tsort'] = 'column1';
448 $_GET['tifirst'] = 'A';
449 $_GET['tilast'] = 'Z';
451 foreach ($data as $row) {
452 $table1->add_data_keyed($row);
456 // Clear session data between each new table.
457 unset($SESSION->flextable
);
459 $table2 = new flexible_table('tablelib_test');
460 $table2->define_columns($columns);
461 $table2->define_headers($headers);
462 $table2->define_baseurl('/invalid.php');
464 $table2->sortable(true);
465 $table2->collapsible(true);
467 $table2->is_persistent(false);
470 foreach ($data as $row) {
471 $table2->add_data_keyed($row);
475 $this->assertNotEquals($table1, $table2);
477 unset($SESSION->flextable
);
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');
485 $table3->sortable(true);
486 $table3->collapsible(true);
488 $table3->is_persistent(true);
489 $_GET['thide'] = 'column0';
490 $_GET['tsort'] = 'column1';
491 $_GET['tifirst'] = 'A';
492 $_GET['tilast'] = 'Z';
494 foreach ($data as $row) {
495 $table3->add_data_keyed($row);
499 unset($SESSION->flextable
);
501 $table4 = new flexible_table('tablelib_test');
502 $table4->define_columns($columns);
503 $table4->define_headers($headers);
504 $table4->define_baseurl('/invalid.php');
506 $table4->sortable(true);
507 $table4->collapsible(true);
509 $table4->is_persistent(true);
512 foreach ($data as $row) {
513 $table4->add_data_keyed($row);
517 $this->assertEquals($table3, $table4);
519 unset($SESSION->flextable
);
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');
527 $table5->sortable(true);
528 $table5->collapsible(true);
530 $table5->is_persistent(true);
531 $_GET['thide'] = 'column0';
532 $_GET['tsort'] = 'column1';
533 $_GET['tifirst'] = 'A';
534 $_GET['tilast'] = 'Z';
536 foreach ($data as $row) {
537 $table5->add_data_keyed($row);
541 $table6 = new flexible_table('tablelib_test');
542 $table6->define_columns($columns);
543 $table6->define_headers($headers);
544 $table6->define_baseurl('/invalid.php');
546 $table6->sortable(true);
547 $table6->collapsible(true);
549 $table6->is_persistent(true);
552 foreach ($data as $row) {
553 $table6->add_data_keyed($row);
557 $this->assertEquals($table5, $table6);
561 * Helper method for preparing tables instances in {@link self::test_can_be_reset()}.
563 * @param string $tableid
564 * @return testable_flexible_table
566 protected function prepare_table_for_reset_test($tableid) {
569 unset($SESSION->flextable
[$tableid]);
571 $data = $this->generate_data(25, 3);
572 $columns = array('column0', 'column1', 'column2');
573 $headers = $this->generate_headers(3);
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);
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_'));
589 $this->assertFalse($table->can_be_reset());
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
);
595 $this->assertFalse($table->can_be_reset());
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
;
603 unset($_GET['tsort']);
604 unset($_GET['tdir']);
605 $this->assertFalse($table->can_be_reset());
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
;
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());
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';
623 unset($_GET['tsort']);
624 $this->assertTrue($table->can_be_reset());
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
;
632 unset($_GET['tsort']);
633 unset($_GET['tdir']);
634 $this->assertTrue($table->can_be_reset());
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';
642 $_GET['tsort'] = 'column1';
644 unset($_GET['tsort']);
645 $this->assertTrue($table->can_be_reset());
647 // Table having some column collapsed, can be reset.
648 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
649 $_GET['thide'] = 'column2';
651 unset($_GET['thide']);
652 $this->assertTrue($table->can_be_reset());
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';
658 unset($_GET['tshow']);
659 $this->assertFalse($table->can_be_reset());
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';
665 $_GET['tshow'] = 'column0';
667 unset($_GET['thide']);
668 unset($_GET['tshow']);
669 $this->assertFalse($table->can_be_reset());
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';
675 unset($_GET['tifirst']);
676 $this->assertTrue($table->can_be_reset());
680 * Test export in CSV format
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']);
689 $table->is_downloadable(true);
690 $table->is_downloading('csv');
693 $table->add_data(['column0' => 'a', 'column1' => 'b', 'column2' => 'c']);
694 $output = ob_get_contents();
697 $this->assertEquals("Col1,Col2,Col3\na,b,c\n", substr($output, 3));
701 * Test the initials functionality.
703 * @dataProvider initials_provider
704 * @param string|null $getvalue
705 * @param string|null $setvalue
706 * @param string|null $finalvalue
708 public function test_initials_first_set(?
string $getvalue, ?
string $setvalue, ?
string $finalvalue): void
{
711 $this->resetAfterTest(true);
713 $table = new flexible_table('tablelib_test');
715 $user = $this->getDataGenerator()->create_user();
717 $table->define_columns(['fullname']);
718 $table->define_headers(['Fullname']);
719 $table->define_baseurl('/invalid.php');
720 $table->initialbars(true);
722 if ($getvalue !== null) {
723 $_GET['tifirst'] = $getvalue;
726 if ($setvalue !== null) {
727 $table->set_first_initial($setvalue);
732 $this->assertEquals($finalvalue, $table->get_initial_first());
736 * Test the initials functionality.
738 * @dataProvider initials_provider
739 * @param string|null $getvalue
740 * @param string|null $setvalue
741 * @param string|null $finalvalue
743 public function test_initials_last_set(?
string $getvalue, ?
string $setvalue, ?
string $finalvalue): void
{
746 $this->resetAfterTest(true);
748 $table = new flexible_table('tablelib_test');
750 $user = $this->getDataGenerator()->create_user();
752 $table->define_columns(['fullname']);
753 $table->define_headers(['Fullname']);
754 $table->define_baseurl('/invalid.php');
755 $table->initialbars(true);
757 if ($getvalue !== null) {
758 $_GET['tilast'] = $getvalue;
761 if ($setvalue !== null) {
762 $table->set_last_initial($setvalue);
767 $this->assertEquals($finalvalue, $table->get_initial_last());
771 * Data for testing initials providers.
775 public function initials_provider(): array {