Files
moodle/reportbuilder/tests/local/report/column_test.php
T
Paul Holden 907ff8555f MDL-70794 reportbuilder: implement report column and action classes.
The column class encapsulates all data related to a report column. It
allows developers to define the type of data contained within it, in
addition to how that data is retrieved and formatted for users.

Report actions are used to define action elements that are added to
each report row. They define an icon and link, and can be used to
direct users to related pages according to the current row data.
2021-07-28 16:40:50 +01:00

424 lines
13 KiB
PHP

<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
declare(strict_types=1);
namespace core_reportbuilder\local\report;
use advanced_testcase;
use coding_exception;
use lang_string;
use stdClass;
use core_reportbuilder\local\helpers\database;
/**
* Unit tests for a report column
*
* @package core_reportbuilder
* @covers \core_reportbuilder\local\report\column
* @copyright 2020 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class column_testcase extends advanced_testcase {
/**
* Test column name getter/setter
*/
public function test_name(): void {
$column = $this->create_column('test');
$this->assertEquals('test', $column->get_name());
$this->assertEquals('another', $column
->set_name('another')
->get_name()
);
}
/**
* Test column title getter/setter
*/
public function test_title(): void {
$column = $this->create_column('test', new lang_string('show'));
$this->assertEquals('Show', $column->get_title());
$this->assertEquals('Hide', $column
->set_title(new lang_string('hide'))
->get_title()
);
// Column titles can also be empty.
$this->assertEmpty($column
->set_title(null)
->get_title());
}
/**
* Test entity name getter
*/
public function test_get_entity_name(): void {
$column = $this->create_column('test', null, 'entityname');
$this->assertEquals('entityname', $column->get_entity_name());
}
/**
* Test getting unique identifier
*/
public function test_get_unique_identifier(): void {
$column = $this->create_column('test', null, 'entityname');
$this->assertEquals('entityname:test', $column->get_unique_identifier());
}
/**
* Test column type getter/setter
*/
public function test_type(): void {
$column = $this->create_column('test');
$this->assertEquals(column::TYPE_TEXT, $column
->set_type(column::TYPE_TEXT)
->get_type());
}
/**
* Test column type with invalid value
*/
public function test_type_invalid(): void {
$column = $this->create_column('test');
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Invalid column type');
$column->set_type(-1);
}
/**
* Test adding single join
*/
public function test_add_join(): void {
$column = $this->create_column('test');
$this->assertEquals([], $column->get_joins());
$column->add_join('JOIN {user} u ON u.id = table.userid');
$this->assertEquals(['JOIN {user} u ON u.id = table.userid'], $column->get_joins());
}
/**
* Test adding multiple joins
*/
public function test_add_joins(): void {
$tablejoins = [
"JOIN {course} c2 ON c2.id = c1.id",
"JOIN {course} c3 ON c3.id = c1.id",
];
$column = $this->create_column('test')
->add_joins($tablejoins);
$this->assertEquals($tablejoins, $column->get_joins());
}
/**
* Data provider for {@see test_add_field}
*
* @return array
*/
public function add_field_provider(): array {
return [
['foo', '', ['foo AS c1_foo']],
['foo', 'bar', ['foo AS c1_bar']],
['t.foo', '', ['t.foo AS c1_foo']],
['t.foo', 'bar', ['t.foo AS c1_bar']],
];
}
/**
* Test adding single field, and retrieving it
*
* @param string $sql
* @param string $alias
* @param array $expectedselect
*
* @dataProvider add_field_provider
*/
public function test_add_field(string $sql, string $alias, array $expectedselect): void {
$column = $this->create_column('test')
->set_index(1)
->add_field($sql, $alias);
$this->assertEquals($expectedselect, $column->get_fields());
}
/**
* Test adding params to field, and retrieving them
*/
public function test_add_field_with_params(): void {
$param = database::generate_param_name();
$column = $this->create_column('test')
->set_index(1)
->add_field(":{$param}", 'foo', [$param => 'bar']);
// Select will look like the following: "p<index>_rbparam<counter>", where index is the column index and counter is
// a static value of the report helper class.
$select = $column->get_fields();
preg_match('/:(?<paramname>p1_rbparam[\d]+) AS c1_foo/', $select[0], $matches);
$this->assertArrayHasKey('paramname', $matches);
$this->assertEquals([$matches['paramname'] => 'bar'], $column->get_params());
}
/**
* Test adding field with alias as part of SQL throws an exception
*/
public function test_add_field_alias_in_sql(): void {
$column = $this->create_column('test')
->set_index(1);
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Column alias must be passed as a separate argument');
$column->add_field('foo AS bar');
}
/**
* Test adding field with complex SQL without an alias throws an exception
*/
public function test_add_field_complex_without_alias(): void {
global $DB;
$column = $this->create_column('test')
->set_index(1);
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Complex columns must have an alias');
$column->add_field($DB->sql_concat('foo', 'bar'));
}
/**
* Data provider for {@see test_add_fields}
*
* @return array
*/
public function add_fields_provider(): array {
return [
['t.foo', ['t.foo AS c1_foo']],
['t.foo bar', ['t.foo AS c1_bar']],
['t.foo AS bar', ['t.foo AS c1_bar']],
['t.foo1, t.foo2 bar, t.foo3 AS baz', ['t.foo1 AS c1_foo1', 't.foo2 AS c1_bar', 't.foo3 AS c1_baz']],
];
}
/**
* Test adding fields to a column, and retrieving them
*
* @param string $sql
* @param array $expectedselect
*
* @dataProvider add_fields_provider
*/
public function test_add_fields(string $sql, array $expectedselect): void {
$column = $this->create_column('test')
->set_index(1)
->add_fields($sql);
$this->assertEquals($expectedselect, $column->get_fields());
}
/**
* Test column alias
*/
public function test_column_alias(): void {
$column = $this->create_column('test')
->set_index(1)
->add_fields('t.foo, t.bar');
$this->assertEquals('c1_foo', $column->get_column_alias());
}
/**
* Test alias of column without any fields throws exception
*/
public function test_column_alias_no_fields(): void {
$column = $this->create_column('test');
$this->expectException(coding_exception::class);
$this->expectExceptionMessage('Column ' . $column->get_unique_identifier() . ' contains no fields');
$column->add_field($column->get_column_alias());
}
/**
* Data provider for {@see test_format_value}
*
* @return array[]
*/
public function format_value_provider(): array {
return [
[column::TYPE_INTEGER, 42],
[column::TYPE_TEXT, 'Hello'],
[column::TYPE_TIMESTAMP, HOURSECS],
[column::TYPE_BOOLEAN, 1, true],
[column::TYPE_FLOAT, 1.23],
[column::TYPE_LONGTEXT, 'Amigos'],
];
}
/**
* Test that column value is returned as correctly (value plus type)
*
* @param int $columntype
* @param mixed $value
* @param mixed|null $expected Expected value, or null to indicate it should be identical to value
*
* @dataProvider format_value_provider
*/
public function test_format_value(int $columntype, $value, $expected = null): void {
$column = $this->create_column('test')
->set_index(1)
->set_type($columntype)
->add_field('t.foo');
$this->assertSame($expected ?? $value, $column->format_value([
'c1_foo' => $value,
]));
}
/**
* Test that column value with callback is returned
*/
public function test_format_value_callback(): void {
$column = $this->create_column('test')
->set_index(1)
->add_field('t.foo')
->set_type(column::TYPE_INTEGER)
->add_callback(static function(int $value, stdClass $values) {
return $value * 2;
});
$this->assertEquals(84, $column->format_value([
'c1_bar' => 10,
'c1_foo' => 42,
]));
}
/**
* Test that column value with callback (using all fields) is returned
*/
public function test_format_value_callback_fields(): void {
$column = $this->create_column('test')
->set_index(1)
->add_fields('t.foo, t.baz')
->set_type(column::TYPE_INTEGER)
->add_callback(static function(int $value, stdClass $values) {
return $values->foo + $values->baz;
});
$this->assertEquals(60, $column->format_value([
'c1_bar' => 10,
'c1_foo' => 42,
'c1_baz' => 18,
]));
}
/**
* Test that column value with callback (using arguments) is returned
*/
public function test_format_value_callback_arguments(): void {
$column = $this->create_column('test')
->set_index(1)
->add_field('t.foo')
->set_type(column::TYPE_INTEGER)
->add_callback(static function(int $value, stdClass $values, int $argument) {
return $value - $argument;
}, 10);
$this->assertEquals(32, $column->format_value([
'c1_bar' => 10,
'c1_foo' => 42,
]));
}
/**
* Test adding multiple callbacks to a column
*/
public function test_add_multiple_callback(): void {
$column = $this->create_column('test')
->set_index(1)
->add_field('t.foo')
->set_type(column::TYPE_TEXT)
->add_callback(static function(string $value): string {
return strrev($value);
})
->add_callback(static function(string $value): string {
return strtoupper($value);
});
$this->assertEquals('LIONEL', $column->format_value([
'c1_foo' => 'lenoil',
]));
}
/**
* Test that setting column callback overwrites previous callbacks
*/
public function test_set_callback(): void {
$column = $this->create_column('test')
->set_index(1)
->add_field('t.foo')
->set_type(column::TYPE_TEXT)
->add_callback(static function(string $value): string {
return strrev($value);
})
->set_callback(static function(string $value): string {
return strtoupper($value);
});
$this->assertEquals('LENOIL', $column->format_value([
'c1_foo' => 'lenoil',
]));
}
/**
* Test is sortable
*/
public function test_is_sortable(): void {
$column = $this->create_column('test');
$this->assertFalse($column->get_is_sortable());
$column->set_is_sortable(true);
$this->assertTrue($column->get_is_sortable());
}
/**
* Test is available
*/
public function test_is_available(): void {
$column = $this->create_column('test');
$this->assertTrue($column->get_is_available());
$column->set_is_available(true);
$this->assertTrue($column->get_is_available());
}
/**
* Helper method to create a column instance
*
* @param string $name
* @param lang_string|null $title
* @param string $entityname
* @return column
*/
private function create_column(string $name, ?lang_string $title = null, string $entityname = 'column_testcase'): column {
return new column($name, $title, $entityname);
}
}