From 493e7526f9c09fde822dfeb86a09e821e30d48b8 Mon Sep 17 00:00:00 2001 From: Ruslan Kabalin Date: Fri, 11 Jan 2019 11:44:38 +0100 Subject: [PATCH] MDL-57898 core_customfield: Custom fields API This commit is part of work on Custom fields API, to minimize commit history in moodle core the work of a team of developers was split into several commits with different authors but the authorship of individual lines of code may be different from the commit author. --- customfield/tests/field_controller_test.php | 246 ++++++++++++++++++++ customfield/tests/generator/lib.php | 164 +++++++++++++ customfield/tests/generator_test.php | 114 +++++++++ lib/tests/component_test.php | 2 +- 4 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 customfield/tests/field_controller_test.php create mode 100644 customfield/tests/generator/lib.php create mode 100644 customfield/tests/generator_test.php diff --git a/customfield/tests/field_controller_test.php b/customfield/tests/field_controller_test.php new file mode 100644 index 00000000000..2209e9d7366 --- /dev/null +++ b/customfield/tests/field_controller_test.php @@ -0,0 +1,246 @@ +. + +/** + * Tests for class \core_customfield\field_controller. + * + * @package core_customfield + * @category test + * @copyright 2018 Ruslan Kabalin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +use \core_customfield\category_controller; +use \core_customfield\field_controller; + +/** + * Functional test for class \core_customfield\field_controller. + * + * @package core_customfield + * @category test + * @copyright 2018 Ruslan Kabalin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_customfield_field_controller_testcase extends advanced_testcase { + + /** + * Tests set up. + */ + public function setUp() { + $this->resetAfterTest(); + } + + /** + * Get generator + * @return core_customfield_generator + */ + protected function get_generator(): core_customfield_generator { + return $this->getDataGenerator()->get_plugin_generator('core_customfield'); + } + + /** + * Test for function \core_customfield\field_controller::create() + */ + public function test_constructor() { + global $DB; + // Create the category. + $category0 = $this->get_generator()->create_category(); + + // Initiate objects without id, try with the category object or with category id or with both. + $field0 = field_controller::create(0, (object)['type' => 'checkbox'], $category0); + $this->assertInstanceOf(customfield_checkbox\field_controller::class, $field0); + $field1 = field_controller::create(0, (object)['type' => 'date', 'categoryid' => $category0->get('id')]); + $this->assertInstanceOf(customfield_date\field_controller::class, $field1); + $field2 = field_controller::create(0, (object)['type' => 'select', 'categoryid' => $category0->get('id')], $category0); + $this->assertInstanceOf(customfield_select\field_controller::class, $field2); + $field3 = field_controller::create(0, (object)['type' => 'text'], $category0); + $this->assertInstanceOf(customfield_text\field_controller::class, $field3); + $field4 = field_controller::create(0, (object)['type' => 'textarea'], $category0); + $this->assertInstanceOf(customfield_textarea\field_controller::class, $field4); + + // Save fields to the db so we have ids. + \core_customfield\api::save_field_configuration($field0, (object)['name' => 'a', 'shortname' => 'a']); + \core_customfield\api::save_field_configuration($field1, (object)['name' => 'b', 'shortname' => 'b']); + \core_customfield\api::save_field_configuration($field2, (object)['name' => 'c', 'shortname' => 'c']); + \core_customfield\api::save_field_configuration($field3, (object)['name' => 'd', 'shortname' => 'd']); + \core_customfield\api::save_field_configuration($field4, (object)['name' => 'e', 'shortname' => 'e']); + + // Retrieve fields by id. + $this->assertInstanceOf(customfield_checkbox\field_controller::class, field_controller::create($field0->get('id'))); + $this->assertInstanceOf(customfield_date\field_controller::class, field_controller::create($field1->get('id'))); + + // Retrieve field by id and category. + $this->assertInstanceOf(customfield_select\field_controller::class, + field_controller::create($field2->get('id'), null, $category0)); + + // Retrieve fields by record without category. + $fieldrecord = $DB->get_record(\core_customfield\field::TABLE, ['id' => $field3->get('id')], '*', MUST_EXIST); + $this->assertInstanceOf(customfield_text\field_controller::class, field_controller::create(0, $fieldrecord)); + + // Retrieve fields by record with category. + $fieldrecord = $DB->get_record(\core_customfield\field::TABLE, ['id' => $field4->get('id')], '*', MUST_EXIST); + $this->assertInstanceOf(customfield_textarea\field_controller::class, + field_controller::create(0, $fieldrecord, $category0)); + } + + /** + * Test for function \core_customfield\field_controller::create() in case of wrong parameters + */ + public function test_constructor_errors() { + global $DB; + // Create a category and a field. + $category = $this->get_generator()->create_category(); + $field = $this->get_generator()->create_field(['categoryid' => $category->get('id')]); + + $fieldrecord = $DB->get_record(\core_customfield\field::TABLE, ['id' => $field->get('id')], '*', MUST_EXIST); + + // Both id and record give warning. + $field = field_controller::create($fieldrecord->id, $fieldrecord); + $debugging = $this->getDebuggingMessages(); + $this->assertEquals(1, count($debugging)); + $this->assertEquals('Too many parameters, either id need to be specified or a record, but not both.', + $debugging[0]->message); + $this->resetDebugging(); + $this->assertInstanceOf(customfield_text\field_controller::class, $field); + + // Retrieve non-existing field. + try { + field_controller::create($fieldrecord->id + 1); + $this->fail('Expected exception'); + } catch (moodle_exception $e) { + $this->assertEquals('Field not found', $e->getMessage()); + $this->assertEquals(moodle_exception::class, get_class($e)); + } + + // Retrieve without id and without type. + try { + field_controller::create(0, (object)['name' => 'a'], $category); + $this->fail('Expected exception'); + } catch (coding_exception $e) { + $this->assertEquals('Coding error detected, it must be fixed by a programmer: Not enough parameters to ' . + 'initialise field_controller - unknown field type', $e->getMessage()); + $this->assertEquals(coding_exception::class, get_class($e)); + } + + // Missing category id. + try { + field_controller::create(0, (object)['type' => 'text']); + $this->fail('Expected exception'); + } catch (coding_exception $e) { + $this->assertEquals('Coding error detected, it must be fixed by a programmer: Not enough parameters ' . + 'to initialise field_controller - unknown category', $e->getMessage()); + $this->assertEquals(coding_exception::class, get_class($e)); + } + + // Mismatching category id. + try { + field_controller::create(0, (object)['type' => 'text', 'categoryid' => $category->get('id') + 1], $category); + $this->fail('Expected exception'); + } catch (coding_exception $e) { + $this->assertEquals('Coding error detected, it must be fixed by a programmer: Category of the field ' . + 'does not match category from the parameter', $e->getMessage()); + $this->assertEquals(coding_exception::class, get_class($e)); + } + + // Non-existing type. + try { + field_controller::create(0, (object)['type' => 'nonexisting'], $category); + $this->fail('Expected exception'); + } catch (moodle_exception $e) { + $this->assertEquals('Field type nonexisting not found', $e->getMessage()); + $this->assertEquals(moodle_exception::class, get_class($e)); + } + } + + /** + * Tests for behaviour of: + * \core_customfield\field_controller::save() + * \core_customfield\field_controller::get() + * \core_customfield\field_controller::get_category() + */ + public function test_create_field() { + global $DB; + $lpg = $this->get_generator(); + $category = $lpg->create_category(); + $fields = $DB->get_records(\core_customfield\field::TABLE, ['categoryid' => $category->get('id')]); + $this->assertCount(0, $fields); + + // Create field. + $fielddata = new stdClass(); + $fielddata->name = 'Field'; + $fielddata->shortname = 'field'; + $fielddata->type = 'text'; + $fielddata->categoryid = $category->get('id'); + $field = field_controller::create(0, $fielddata); + $field->save(); + + $fields = $DB->get_records(\core_customfield\field::TABLE, ['categoryid' => $category->get('id')]); + $this->assertCount(1, $fields); + $this->assertTrue(\core_customfield\field::record_exists($field->get('id'))); + $this->assertInstanceOf(\customfield_text\field_controller::class, $field); + $this->assertSame($field->get('name'), $fielddata->name); + $this->assertSame($field->get('type'), $fielddata->type); + $this->assertEquals($field->get_category()->get('id'), $category->get('id')); + } + + /** + * Tests for \core_customfield\field_controller::delete() behaviour. + */ + public function test_delete_field() { + global $DB; + $lpg = $this->get_generator(); + $category = $lpg->create_category(); + $fields = $DB->get_records(\core_customfield\field::TABLE, ['categoryid' => $category->get('id')]); + $this->assertCount(0, $fields); + + // Create field using generator. + $field1 = $lpg->create_field(array('categoryid' => $category->get('id'))); + $field2 = $lpg->create_field(array('categoryid' => $category->get('id'))); + $fields = $DB->get_records(\core_customfield\field::TABLE, ['categoryid' => $category->get('id')]); + $this->assertCount(2, $fields); + + // Delete fields. + $this->assertTrue($field1->delete()); + $this->assertTrue($field2->delete()); + + // Check that the fields have been deleted. + $fields = $DB->get_records(\core_customfield\field::TABLE, ['categoryid' => $category->get('id')]); + $this->assertCount(0, $fields); + $this->assertFalse(\core_customfield\field::record_exists($field1->get('id'))); + $this->assertFalse(\core_customfield\field::record_exists($field2->get('id'))); + } + + /** + * Tests for \core_customfield\field_controller::get_configdata_property() behaviour. + */ + public function test_get_configdata_property() { + $lpg = $this->get_generator(); + $category = $lpg->create_category(); + $configdata = ['a' => 'b', 'c' => ['d', 'e']]; + $field = field_controller::create(0, (object)['type' => 'text', + 'configdata' => json_encode($configdata), 'shortname' => 'a', 'name' => 'a'], $category); + $field->save(); + + // Retrieve field and check configdata. + $field = field_controller::create($field->get('id')); + $this->assertEquals($configdata, $field->get('configdata')); + $this->assertEquals('b', $field->get_configdata_property('a')); + $this->assertEquals(['d', 'e'], $field->get_configdata_property('c')); + $this->assertEquals(null, $field->get_configdata_property('x')); + } +} \ No newline at end of file diff --git a/customfield/tests/generator/lib.php b/customfield/tests/generator/lib.php new file mode 100644 index 00000000000..1f994623663 --- /dev/null +++ b/customfield/tests/generator/lib.php @@ -0,0 +1,164 @@ +. + +/** + * Customfield data generator. + * + * @package core_customfield + * @category test + * @copyright 2018 Ruslan Kabalin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +use \core_customfield\category_controller; +use \core_customfield\field_controller; +use \core_customfield\api; + +/** + * Customfield data generator class. + * + * @package core_customfield + * @category test + * @copyright 2018 Ruslan Kabalin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_customfield_generator extends component_generator_base { + + /** @var int Number of created categories. */ + protected $categorycount = 0; + + /** @var int Number of created fields. */ + protected $fieldcount = 0; + + /** + * Create a new category. + * + * @param array|stdClass $record + * @return category_controller + */ + public function create_category($record = null) { + $this->categorycount++; + $i = $this->categorycount; + $record = (object) $record; + + if (!isset($record->name)) { + $record->name = "Category $i"; + } + if (!isset($record->component)) { + $record->component = 'core_course'; + } + if (!isset($record->area)) { + $record->area = 'course'; + } + if (!isset($record->itemid)) { + $record->itemid = 0; + } + + $handler = \core_customfield\handler::get_handler($record->component, $record->area, $record->itemid); + $categoryid = $handler->create_category($record->name); + return $handler->get_categories_with_fields()[$categoryid]; + } + + /** + * Create a new field. + * + * @param array|stdClass $record + * @return field_controller + */ + public function create_field($record): field_controller { + $this->fieldcount++; + $i = $this->fieldcount; + $record = (object) $record; + + if (empty($record->categoryid)) { + throw new coding_exception('The categoryid value is required.'); + } + $category = category_controller::create($record->categoryid); + $handler = $category->get_handler(); + + if (!isset($record->name)) { + $record->name = "Field $i"; + } + if (!isset($record->shortname)) { + $record->shortname = "fld$i"; + } + if (!isset($record->description)) { + $record->description = "Field $i description"; + } + if (!isset($record->descriptionformat)) { + $record->descriptionformat = FORMAT_HTML; + } + if (!isset($record->type)) { + $record->type = 'text'; + } + if (!isset($record->sortorder)) { + $record->sortorder = 0; + } + + if (empty($record->configdata)) { + $configdata = []; + } else if (is_array($record->configdata)) { + $configdata = $record->configdata; + } else { + $configdata = @json_decode($record->configdata, true); + $configdata = $configdata ?? []; + } + $configdata += [ + 'required' => 0, + 'uniquevalues' => 0, + 'locked' => 0, + 'visibility' => 2, + 'defaultvalue' => '', + 'displaysize' => 0, + 'maxlength' => 0, + 'ispassword' => 0, + 'link' => '', + 'linktarget' => '', + 'checkbydefault' => 0, + 'startyear' => 2000, + 'endyear' => 3000, + 'includetime' => 1, + ]; + $record->configdata = json_encode($configdata); + + $field = field_controller::create(0, (object)['type' => $record->type], $category); + $handler->save_field_configuration($field, $record); + return $handler->get_categories_with_fields()[$field->get('categoryid')]->get_fields()[$field->get('id')]; + } + + /** + * Adds instance data for one field + * + * @param field_controller $field + * @param int $instanceid + * @param mixed $value + * @return \core_customfield\data_controller + */ + public function add_instance_data(field_controller $field, int $instanceid, $value): \core_customfield\data_controller { + $data = \core_customfield\data_controller::create(0, (object)['instanceid' => $instanceid], $field); + $data->set('contextid', $data->get_context()->id); + + $rc = new ReflectionClass(get_class($data)); + $rcm = $rc->getMethod('get_form_element_name'); + $rcm->setAccessible(true); + $formelementname = $rcm->invokeArgs($data, []); + $record = (object)[$formelementname => $value]; + $data->instance_form_save($record); + return $data; + } +} diff --git a/customfield/tests/generator_test.php b/customfield/tests/generator_test.php new file mode 100644 index 00000000000..c5335c37d9f --- /dev/null +++ b/customfield/tests/generator_test.php @@ -0,0 +1,114 @@ +. + +/** + * core_customfield test data generator test. + * + * @package core_customfield + * @category test + * @copyright 2018 Ruslan Kabalin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * core_customfield test data generator testcase. + * + * @package core_customfield + * @category test + * @copyright 2018 Ruslan Kabalin + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_customfield_generator_testcase extends advanced_testcase { + + /** + * Get generator + * @return core_customfield_generator + */ + protected function get_generator(): core_customfield_generator { + return $this->getDataGenerator()->get_plugin_generator('core_customfield'); + } + + /** + * Test creating category + */ + public function test_create_category() { + $this->resetAfterTest(true); + + $lpg = $this->get_generator(); + $category = $lpg->create_category(); + + $this->assertInstanceOf('\core_customfield\category_controller', $category); + $this->assertTrue(\core_customfield\category::record_exists($category->get('id'))); + } + + /** + * Test creating field + */ + public function test_create_field() { + $this->resetAfterTest(true); + + $lpg = $this->get_generator(); + $category = $lpg->create_category(); + $field = $lpg->create_field(['categoryid' => $category->get('id')]); + + $this->assertInstanceOf('\core_customfield\field_controller', $field); + $this->assertTrue(\core_customfield\field::record_exists($field->get('id'))); + + $category = core_customfield\category_controller::create($category->get('id')); + $category = \core_customfield\api::get_categories_with_fields($category->get('component'), + $category->get('area'), $category->get('itemid'))[$category->get('id')]; + $this->assertCount(1, $category->get_fields()); + } + + /** + * Test for function add_instance_data() + */ + public function test_add_instance_data() { + $this->resetAfterTest(true); + + $lpg = $this->get_generator(); + $c1 = $lpg->create_category(); + $course1 = $this->getDataGenerator()->create_course(); + + $f11 = $this->get_generator()->create_field(['categoryid' => $c1->get('id'), 'type' => 'checkbox']); + $f12 = $this->get_generator()->create_field(['categoryid' => $c1->get('id'), 'type' => 'date']); + $f13 = $this->get_generator()->create_field(['categoryid' => $c1->get('id'), + 'type' => 'select', 'configdata' => ['options' => "a\nb\nc"]]); + $f14 = $this->get_generator()->create_field(['categoryid' => $c1->get('id'), 'type' => 'text']); + $f15 = $this->get_generator()->create_field(['categoryid' => $c1->get('id'), 'type' => 'textarea']); + + $this->get_generator()->add_instance_data($f11, $course1->id, 1); + $this->get_generator()->add_instance_data($f12, $course1->id, 1546300800); + $this->get_generator()->add_instance_data($f13, $course1->id, 2); + $this->get_generator()->add_instance_data($f14, $course1->id, 'Hello'); + $this->get_generator()->add_instance_data($f15, $course1->id, ['text' => '

Hi there

', 'format' => FORMAT_HTML]); + + $handler = $c1->get_handler(); + list($data1, $data2, $data3, $data4, $data5) = array_values($handler->get_instance_data($course1->id)); + $this->assertNotEmpty($data1->get('id')); + $this->assertEquals(1, $data1->get_value()); + $this->assertNotEmpty($data2->get('id')); + $this->assertEquals(1546300800, $data2->get_value()); + $this->assertNotEmpty($data3->get('id')); + $this->assertEquals(2, $data3->get_value()); + $this->assertNotEmpty($data4->get('id')); + $this->assertEquals('Hello', $data4->get_value()); + $this->assertNotEmpty($data5->get('id')); + $this->assertEquals('

Hi there

', $data5->get_value()); + } +} diff --git a/lib/tests/component_test.php b/lib/tests/component_test.php index 2b79591082b..b0b995a6b61 100644 --- a/lib/tests/component_test.php +++ b/lib/tests/component_test.php @@ -36,7 +36,7 @@ class core_component_testcase extends advanced_testcase { * this is defined here to annoy devs that try to add more without any thinking, * always verify that it does not collide with any existing add-on modules and subplugins!!! */ - const SUBSYSTEMCOUNT = 67; + const SUBSYSTEMCOUNT = 68; public function setUp() { $psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');