MDL-79059 core: Use full name as alt text for user picture links
[moodle.git] / lib / tests / tablelib_test.php
blobb7328a56a4395d05316671980536e89c13c1c1e8
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.
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/>.
17 namespace core;
19 use flexible_table;
20 use testable_flexible_table;
22 defined('MOODLE_INTERNAL') || die();
24 global $CFG;
25 require_once($CFG->libdir . '/tablelib.php');
26 require_once($CFG->libdir . '/tests/fixtures/testable_flexible_table.php');
28 /**
29 * Test some of tablelib.
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
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);
42 return $columns;
45 protected function generate_headers($cols) {
46 $columns = array();
47 foreach (range(0, $cols - 1) as $j) {
48 array_push($columns, 'Column ' . $j);
50 return $columns;
53 protected function generate_data($rows, $cols) {
54 $data = array();
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;
62 array_push($data, $row);
64 return $data;
67 /**
68 * Create a table with properties as passed in params, add data and output html.
70 * @param string[] $columns The columns of the table.
71 * @param string[] $headers The header of the table.
72 * @param bool $sortable Sorting of the table.
73 * @param bool $collapsible Is table collapsible.
74 * @param string[] $suppress Suppress columns.
75 * @param string[] $nosorting No sorting.
76 * @param (array|object)[] $data The data of the table.
77 * @param int $pagesize Page size of the table
78 * @param string $caption Caption of the table.
79 * @param array $captionattribute The attribute of the caption.
81 protected function run_table_test($columns, $headers, $sortable, $collapsible, $suppress, $nosorting, $data,
82 $pagesize, $caption = '', $captionattribute = []) {
83 $table = $this->create_and_setup_table($columns, $headers, $sortable, $collapsible, $suppress, $nosorting,
84 $caption, $captionattribute);
85 $table->pagesize($pagesize, count($data));
86 foreach ($data as $row) {
87 $table->add_data_keyed($row);
89 $table->finish_output();
92 /**
93 * Create a table with properties as passed in params.
95 * @param string[] $columns The columns of the table.
96 * @param string[] $headers The header of the table.
97 * @param bool $sortable Sorting of the table.
98 * @param bool $collapsible Is table collapsible.
99 * @param string[] $suppress Suppress columns.
100 * @param string[] $nosorting No sorting.
101 * @param string $caption Caption of the table.
102 * @param array $captionattribute The attribute of the caption.
103 * @return flexible_table
105 protected function create_and_setup_table($columns, $headers, $sortable, $collapsible, $suppress, $nosorting,
106 $caption = '', $captionattribute = '') {
107 $table = new flexible_table('tablelib_test');
109 $table->define_columns($columns);
110 $table->define_headers($headers);
111 $table->define_baseurl('/invalid.php');
113 $table->sortable($sortable);
114 $table->collapsible($collapsible);
115 foreach ($suppress as $column) {
116 $table->column_suppress($column);
119 foreach ($nosorting as $column) {
120 $table->no_sorting($column);
122 if ($caption) {
123 $table->set_caption($caption, $captionattribute);
126 $table->setup();
127 return $table;
130 public function test_empty_table() {
131 $this->expectOutputRegex('/' . get_string('nothingtodisplay') . '/');
132 $this->run_table_test(
133 array('column1', 'column2'), // Columns.
134 array('Column 1', 'Column 2'), // Headers.
135 true, // Sortable.
136 false, // Collapsible.
137 array(), // Suppress columns.
138 array(), // No sorting.
139 array(), // Data.
140 10 // Page size.
144 public function test_has_next_pagination() {
146 $data = $this->generate_data(11, 2);
147 $columns = $this->generate_columns(2);
148 $headers = $this->generate_headers(2);
150 // Search for pagination controls containing 'page-link"\saria-label="Next"'.
151 $this->expectOutputRegex('/Next page/');
153 $this->run_table_test(
154 $columns,
155 $headers,
156 true,
157 false,
158 array(),
159 array(),
160 $data,
165 public function test_has_hide() {
167 $data = $this->generate_data(11, 2);
168 $columns = $this->generate_columns(2);
169 $headers = $this->generate_headers(2);
171 // Search for 'hide' links in the column headers.
172 $this->expectOutputRegex('/' . get_string('hide') . '/');
174 $this->run_table_test(
175 $columns,
176 $headers,
177 true,
178 true,
179 array(),
180 array(),
181 $data,
186 public function test_has_not_hide() {
188 $data = $this->generate_data(11, 2);
189 $columns = $this->generate_columns(2);
190 $headers = $this->generate_headers(2);
192 // Make sure there are no 'hide' links in the headers.
194 ob_start();
195 $this->run_table_test(
196 $columns,
197 $headers,
198 true,
199 false,
200 array(),
201 array(),
202 $data,
205 $output = ob_get_contents();
206 ob_end_clean();
207 $this->assertStringNotContainsString(get_string('hide'), $output);
210 public function test_has_sort() {
212 $data = $this->generate_data(11, 2);
213 $columns = $this->generate_columns(2);
214 $headers = $this->generate_headers(2);
216 // Search for pagination controls containing '1.*2</a>.*Next</a>'.
217 $this->expectOutputRegex('/' . get_string('sortby') . '/');
219 $this->run_table_test(
220 $columns,
221 $headers,
222 true,
223 false,
224 array(),
225 array(),
226 $data,
231 public function test_has_not_sort() {
233 $data = $this->generate_data(11, 2);
234 $columns = $this->generate_columns(2);
235 $headers = $this->generate_headers(2);
237 // Make sure there are no 'Sort by' links in the headers.
239 ob_start();
240 $this->run_table_test(
241 $columns,
242 $headers,
243 false,
244 false,
245 array(),
246 array(),
247 $data,
250 $output = ob_get_contents();
251 ob_end_clean();
252 $this->assertStringNotContainsString(get_string('sortby'), $output);
255 public function test_has_not_next_pagination() {
257 $data = $this->generate_data(10, 2);
258 $columns = $this->generate_columns(2);
259 $headers = $this->generate_headers(2);
261 // Make sure there are no 'Next' links in the pagination.
263 ob_start();
264 $this->run_table_test(
265 $columns,
266 $headers,
267 true,
268 false,
269 array(),
270 array(),
271 $data,
275 $output = ob_get_contents();
276 ob_end_clean();
277 $this->assertStringNotContainsString(get_string('next'), $output);
280 public function test_1_col() {
282 $data = $this->generate_data(100, 1);
283 $columns = $this->generate_columns(1);
284 $headers = $this->generate_headers(1);
286 $this->expectOutputRegex('/row 0 col 0/');
288 $this->run_table_test(
289 $columns,
290 $headers,
291 true,
292 false,
293 array(),
294 array(),
295 $data,
300 public function test_empty_rows() {
302 $data = $this->generate_data(1, 5);
303 $columns = $this->generate_columns(5);
304 $headers = $this->generate_headers(5);
306 // Test that we have at least 5 columns generated for each empty row.
307 $this->expectOutputRegex('/emptyrow.*r9_c4/');
309 $this->run_table_test(
310 $columns,
311 $headers,
312 true,
313 false,
314 array(),
315 array(),
316 $data,
321 public function test_5_cols() {
323 $data = $this->generate_data(100, 5);
324 $columns = $this->generate_columns(5);
325 $headers = $this->generate_headers(5);
327 $this->expectOutputRegex('/row 0 col 0/');
329 $this->run_table_test(
330 $columns,
331 $headers,
332 true,
333 false,
334 array(),
335 array(),
336 $data,
341 public function test_50_cols() {
343 $data = $this->generate_data(100, 50);
344 $columns = $this->generate_columns(50);
345 $headers = $this->generate_headers(50);
347 $this->expectOutputRegex('/row 0 col 0/');
349 $this->run_table_test(
350 $columns,
351 $headers,
352 true,
353 false,
354 array(),
355 array(),
356 $data,
362 * Data provider for test_fullname_column
364 * @return array
366 public function fullname_column_provider() {
367 return [
368 ['language'],
369 ['alternatename lastname'],
370 ['firstname lastnamephonetic'],
375 * Test fullname column observes configured alternate fullname format configuration
377 * @param string $format
378 * @return void
380 * @dataProvider fullname_column_provider
382 public function test_fullname_column(string $format) {
383 $this->resetAfterTest();
384 $this->setAdminUser();
386 set_config('alternativefullnameformat', $format);
388 $user = $this->getDataGenerator()->create_user();
390 $table = $this->create_and_setup_table(['fullname'], [], true, false, [], []);
391 $this->assertStringContainsString(fullname($user, true), $table->format_row($user)['fullname']);
395 * Test fullname column ignores fullname format configuration for a user with viewfullnames capability prohibited
397 * @param string $format
398 * @return void
400 * @dataProvider fullname_column_provider
402 public function test_fullname_column_prohibit_viewfullnames(string $format) {
403 global $DB, $CFG;
405 $this->resetAfterTest();
407 set_config('alternativefullnameformat', $format);
409 $currentuser = $this->getDataGenerator()->create_user();
410 $this->setUser($currentuser);
412 // Prohibit the viewfullnames from the default user role.
413 $userrole = $DB->get_record('role', ['id' => $CFG->defaultuserroleid]);
414 role_change_permission($userrole->id, \context_system::instance(), 'moodle/site:viewfullnames', CAP_PROHIBIT);
416 $user = $this->getDataGenerator()->create_user();
418 $table = $this->create_and_setup_table(['fullname'], [], true, false, [], []);
419 $this->assertStringContainsString(fullname($user, false), $table->format_row($user)['fullname']);
422 public function test_get_row_html() {
423 $data = $this->generate_data(1, 5);
424 $columns = $this->generate_columns(5);
425 $headers = $this->generate_headers(5);
426 $data = array_keys(array_flip($data[0]));
428 $table = new flexible_table('tablelib_test');
429 $table->define_columns($columns);
430 $table->define_headers($headers);
431 $table->define_baseurl('/invalid.php');
433 $row = $table->get_row_html($data);
434 $this->assertMatchesRegularExpression('/row 0 col 0/', $row);
435 $this->assertMatchesRegularExpression('/<tr class=""/', $row);
436 $this->assertMatchesRegularExpression('/<td class="cell c0"/', $row);
439 public function test_persistent_table() {
440 global $SESSION;
442 $data = $this->generate_data(5, 5);
443 $columns = $this->generate_columns(5);
444 $headers = $this->generate_headers(5);
446 // Testing without persistence first to verify that the results are different.
447 $table1 = new flexible_table('tablelib_test');
448 $table1->define_columns($columns);
449 $table1->define_headers($headers);
450 $table1->define_baseurl('/invalid.php');
452 $table1->sortable(true);
453 $table1->collapsible(true);
455 $table1->is_persistent(false);
456 $_GET['thide'] = 'column0';
457 $_GET['tsort'] = 'column1';
458 $_GET['tifirst'] = 'A';
459 $_GET['tilast'] = 'Z';
461 foreach ($data as $row) {
462 $table1->add_data_keyed($row);
464 $table1->setup();
466 // Clear session data between each new table.
467 unset($SESSION->flextable);
469 $table2 = new flexible_table('tablelib_test');
470 $table2->define_columns($columns);
471 $table2->define_headers($headers);
472 $table2->define_baseurl('/invalid.php');
474 $table2->sortable(true);
475 $table2->collapsible(true);
477 $table2->is_persistent(false);
478 unset($_GET);
480 foreach ($data as $row) {
481 $table2->add_data_keyed($row);
483 $table2->setup();
485 $this->assertNotEquals($table1, $table2);
487 unset($SESSION->flextable);
489 // Now testing with persistence to check that the tables are the same.
490 $table3 = new flexible_table('tablelib_test');
491 $table3->define_columns($columns);
492 $table3->define_headers($headers);
493 $table3->define_baseurl('/invalid.php');
495 $table3->sortable(true);
496 $table3->collapsible(true);
498 $table3->is_persistent(true);
499 $_GET['thide'] = 'column0';
500 $_GET['tsort'] = 'column1';
501 $_GET['tifirst'] = 'A';
502 $_GET['tilast'] = 'Z';
504 foreach ($data as $row) {
505 $table3->add_data_keyed($row);
507 $table3->setup();
509 unset($SESSION->flextable);
511 $table4 = new flexible_table('tablelib_test');
512 $table4->define_columns($columns);
513 $table4->define_headers($headers);
514 $table4->define_baseurl('/invalid.php');
516 $table4->sortable(true);
517 $table4->collapsible(true);
519 $table4->is_persistent(true);
520 unset($_GET);
522 foreach ($data as $row) {
523 $table4->add_data_keyed($row);
525 $table4->setup();
527 $this->assertEquals($table3, $table4);
529 unset($SESSION->flextable);
531 // Finally, another test with no persistence, but without clearing the session data.
532 $table5 = new flexible_table('tablelib_test');
533 $table5->define_columns($columns);
534 $table5->define_headers($headers);
535 $table5->define_baseurl('/invalid.php');
537 $table5->sortable(true);
538 $table5->collapsible(true);
540 $table5->is_persistent(true);
541 $_GET['thide'] = 'column0';
542 $_GET['tsort'] = 'column1';
543 $_GET['tifirst'] = 'A';
544 $_GET['tilast'] = 'Z';
546 foreach ($data as $row) {
547 $table5->add_data_keyed($row);
549 $table5->setup();
551 $table6 = new flexible_table('tablelib_test');
552 $table6->define_columns($columns);
553 $table6->define_headers($headers);
554 $table6->define_baseurl('/invalid.php');
556 $table6->sortable(true);
557 $table6->collapsible(true);
559 $table6->is_persistent(true);
560 unset($_GET);
562 foreach ($data as $row) {
563 $table6->add_data_keyed($row);
565 $table6->setup();
567 $this->assertEquals($table5, $table6);
571 * Helper method for preparing tables instances in {@link self::test_can_be_reset()}.
573 * @param string $tableid
574 * @return testable_flexible_table
576 protected function prepare_table_for_reset_test($tableid) {
577 global $SESSION;
579 unset($SESSION->flextable[$tableid]);
581 $data = $this->generate_data(25, 3);
582 $columns = array('column0', 'column1', 'column2');
583 $headers = $this->generate_headers(3);
585 $table = new testable_flexible_table($tableid);
586 $table->define_baseurl('/invalid.php');
587 $table->define_columns($columns);
588 $table->define_headers($headers);
589 $table->collapsible(true);
590 $table->is_persistent(false);
592 return $table;
595 public function test_can_be_reset() {
596 // Table in its default state (as if seen for the first time), nothing to reset.
597 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
598 $table->setup();
599 $this->assertFalse($table->can_be_reset());
601 // Table in its default state with default sorting defined, nothing to reset.
602 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
603 $table->sortable(true, 'column1', SORT_DESC);
604 $table->setup();
605 $this->assertFalse($table->can_be_reset());
607 // Table explicitly sorted 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 unset($_GET['tsort']);
614 unset($_GET['tdir']);
615 $this->assertFalse($table->can_be_reset());
617 // Table explicitly sorted twice by the default column & direction, nothing to reset.
618 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
619 $table->sortable(true, 'column1', SORT_DESC);
620 $_GET['tsort'] = 'column1';
621 $_GET['tdir'] = SORT_DESC;
622 $table->setup();
623 $table->setup(); // Set up again to simulate the second page request.
624 unset($_GET['tsort']);
625 unset($_GET['tdir']);
626 $this->assertFalse($table->can_be_reset());
628 // Table sorted by other than default column, can be reset.
629 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
630 $table->sortable(true, 'column1', SORT_DESC);
631 $_GET['tsort'] = 'column2';
632 $table->setup();
633 unset($_GET['tsort']);
634 $this->assertTrue($table->can_be_reset());
636 // Table sorted by other than default direction, can be reset.
637 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
638 $table->sortable(true, 'column1', SORT_DESC);
639 $_GET['tsort'] = 'column1';
640 $_GET['tdir'] = SORT_ASC;
641 $table->setup();
642 unset($_GET['tsort']);
643 unset($_GET['tdir']);
644 $this->assertTrue($table->can_be_reset());
646 // Table sorted by the default column after another sorting previously selected.
647 // This leads to different ORDER BY than just having a single sort defined, can be reset.
648 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
649 $table->sortable(true, 'column1', SORT_DESC);
650 $_GET['tsort'] = 'column0';
651 $table->setup();
652 $_GET['tsort'] = 'column1';
653 $table->setup();
654 unset($_GET['tsort']);
655 $this->assertTrue($table->can_be_reset());
657 // Table having some column collapsed, can be reset.
658 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
659 $_GET['thide'] = 'column2';
660 $table->setup();
661 unset($_GET['thide']);
662 $this->assertTrue($table->can_be_reset());
664 // Table having some column explicitly expanded, nothing to reset.
665 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
666 $_GET['tshow'] = 'column2';
667 $table->setup();
668 unset($_GET['tshow']);
669 $this->assertFalse($table->can_be_reset());
671 // Table after expanding a collapsed column, nothing to reset.
672 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
673 $_GET['thide'] = 'column0';
674 $table->setup();
675 $_GET['tshow'] = 'column0';
676 $table->setup();
677 unset($_GET['thide']);
678 unset($_GET['tshow']);
679 $this->assertFalse($table->can_be_reset());
681 // Table with some name filtering enabled, can be reset.
682 $table = $this->prepare_table_for_reset_test(uniqid('tablelib_test_'));
683 $_GET['tifirst'] = 'A';
684 $table->setup();
685 unset($_GET['tifirst']);
686 $this->assertTrue($table->can_be_reset());
690 * Test export in CSV format
692 public function test_table_export() {
693 $table = new flexible_table('tablelib_test_export');
694 $table->define_baseurl('/invalid.php');
695 $table->define_columns(['c1', 'c2', 'c3']);
696 $table->define_headers(['Col1', 'Col2', 'Col3']);
698 ob_start();
699 $table->is_downloadable(true);
700 $table->is_downloading('csv');
702 $table->setup();
703 $table->add_data(['column0' => 'a', 'column1' => 'b', 'column2' => 'c']);
704 $output = ob_get_contents();
705 ob_end_clean();
707 $this->assertEquals("Col1,Col2,Col3\na,b,c\n", substr($output, 3));
711 * Test the initials functionality.
713 * @dataProvider initials_provider
714 * @param string|null $getvalue
715 * @param string|null $setvalue
716 * @param string|null $finalvalue
718 public function test_initials_first_set(?string $getvalue, ?string $setvalue, ?string $finalvalue): void {
719 global $_GET;
721 $this->resetAfterTest(true);
723 $table = new flexible_table('tablelib_test');
725 $user = $this->getDataGenerator()->create_user();
727 $table->define_columns(['fullname']);
728 $table->define_headers(['Fullname']);
729 $table->define_baseurl('/invalid.php');
730 $table->initialbars(true);
732 if ($getvalue !== null) {
733 $_GET['tifirst'] = $getvalue;
736 if ($setvalue !== null) {
737 $table->set_first_initial($setvalue);
740 $table->setup();
742 $this->assertEquals($finalvalue, $table->get_initial_first());
746 * Test the initials functionality.
748 * @dataProvider initials_provider
749 * @param string|null $getvalue
750 * @param string|null $setvalue
751 * @param string|null $finalvalue
753 public function test_initials_last_set(?string $getvalue, ?string $setvalue, ?string $finalvalue): void {
754 global $_GET;
756 $this->resetAfterTest(true);
758 $table = new flexible_table('tablelib_test');
760 $user = $this->getDataGenerator()->create_user();
762 $table->define_columns(['fullname']);
763 $table->define_headers(['Fullname']);
764 $table->define_baseurl('/invalid.php');
765 $table->initialbars(true);
767 if ($getvalue !== null) {
768 $_GET['tilast'] = $getvalue;
771 if ($setvalue !== null) {
772 $table->set_last_initial($setvalue);
775 $table->setup();
777 $this->assertEquals($finalvalue, $table->get_initial_last());
781 * Data for testing initials providers.
783 * @return array
785 public function initials_provider(): array {
786 return [
787 [null, null, null],
788 ['A', null, 'A'],
789 ['Z', null, 'Z'],
790 [null, 'A', 'A'],
791 [null, 'Z', 'Z'],
792 ['A', 'Z', 'Z'],
793 ['Z', 'A', 'A'],
798 * Data test for set and render caption for table.
800 * @covers ::set_caption_for_table
801 * @covers ::render_caption_for_table
803 public function test_set_and_render_caption_for_table(): void {
804 $data = $this->generate_data(10, 2);
805 $columns = $this->generate_columns(2);
806 $headers = $this->generate_headers(2);
807 $caption = 'Caption for table';
808 $captionattribute = ['class' => 'inline'];
809 $this->run_table_test(
810 $columns,
811 $headers,
812 // Sortable.
813 true,
814 // Collapsible.
815 false,
816 // Suppress columns.
818 // No sorting.
820 // Data.
821 $data,
822 // Page size.
824 // Caption for table.
825 $caption,
826 // Caption attribute.
827 $captionattribute,
829 $this->expectOutputRegex('/' . '<caption class="inline">' . $caption . '<\/caption>' . '/');