MDL-66079 core_grades: Add support for multiple grade items in an activity
[moodle.git] / grade / tests / component_gradeitems_test.php
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/>.
17 /**
18  * Unit tests for core_grades\component_gradeitems;
19  *
20  * @package   core_grades
21  * @category  test
22  * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU Public License
24  */
26 declare(strict_types = 1);
28 namespace tests\core_grades {
30     use advanced_testcase;
31     use core_grades\component_gradeitems;
32     use coding_exception;
34     /**
35      * Unit tests for core_grades\component_gradeitems;
36      *
37      * @package   core_grades
38      * @category  test
39      * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
40      * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41      */
42     class component_gradeitems_test extends advanced_testcase {
44         /**
45          * Ensure that a component which does not implement the mapping class excepts.
46          */
47         public function test_get_itemname_mapping_for_component_does_not_exist(): void {
48             $mappings = component_gradeitems::get_itemname_mapping_for_component('invalid_component');
49             $this->assertIsArray($mappings);
50             $this->assertCount(1, $mappings);
51             $this->assertArrayHasKey(0, $mappings);
52         }
54         /**
55          * Ensure that a component which does not implement the mapping class correctly excepts.
56          */
57         public function test_get_itemname_mapping_for_valid_component_invalid_mapping(): void {
58             $this->expectException(coding_exception::class);
59             component_gradeitems::get_itemname_mapping_for_component('tests\core_grades\component_gradeitems\invalid');
60         }
62         /**
63          * Ensure that a component which implements the mapping class correctly eets the correct set of mappings.
64          */
65         public function test_get_itemname_mapping_for_valid_component_valid_mapping(): void {
66             $mapping = component_gradeitems::get_itemname_mapping_for_component('tests\core_grades\component_gradeitems\valid');
67             $this->assertIsArray($mapping);
68             $this->assertEquals([
69                 0 => 'rating',
70                 1 => 'someother',
71             ], $mapping);
72         }
74         /**
75          * Data provider for is_valid_itemname tests.
76          *
77          * @return array
78          */
79         public function is_valid_itemname_provider(): array {
80             return [
81                 'valid' => [
82                     'someother',
83                     true,
84                 ],
85                 'validnotadvanced' => [
86                     'rating',
87                     true,
88                 ],
89                 'invalid' => [
90                     'doesnotexist',
91                     false,
92                 ],
93             ];
94         }
96         /**
97          * Ensure that a component implementing advanced grading returns the correct areas.
98          *
99          * @dataProvider is_valid_itemname_provider
100          * @param string $itemname
101          * @param bool $isadvanced
102          */
103         public function test_is_valid_itemname(string $itemname, bool $isadvanced): void {
104             $this->assertEquals(
105                 $isadvanced,
106                 component_gradeitems::is_valid_itemname('tests\core_grades\component_gradeitems\valid_and_advanced', $itemname)
107             );
108         }
111         /**
112          * Ensure that a component which does not implement the advancedgrading interface returns this.
113          */
114         public function test_defines_advancedgrading_itemnames_for_component_does_not_exist(): void {
115             $this->assertFalse(component_gradeitems::defines_advancedgrading_itemnames_for_component('invalid_component'));
116         }
118         /**
119          * Ensure that a component which does not implement the advancedgrading interface returns this.
120          */
121         public function test_defines_advancedgrading_itemnames_for_component_no_interfaces(): void {
122             $this->assertFalse(component_gradeitems::defines_advancedgrading_itemnames_for_component('tests\core_grades\component_gradeitems\invalid'));
123         }
125         /**
126          * Ensure that a component which implements the item mapping but not implement the advancedgrading interface returns this.
127          */
128         public function test_defines_advancedgrading_itemnames_for_component_grading_no_interface(): void {
129             $this->assertFalse(component_gradeitems::defines_advancedgrading_itemnames_for_component('tests\core_grades\component_gradeitems\valid'));
130         }
132         /**
133          * Ensure that a component which implements the item mapping but not implement the advancedgrading interface returns this.
134          */
135         public function test_defines_advancedgrading_itemnames_for_component_grading_has_interface(): void {
136             $this->assertTrue(component_gradeitems::defines_advancedgrading_itemnames_for_component('tests\core_grades\component_gradeitems\valid_and_advanced'));
137         }
139         /**
140          * Ensure that a component which does not implement the advancedgrading interface returns this.
141          */
142         public function test_get_advancedgrading_itemnames_for_component_does_not_exist(): void {
143             $this->expectException(coding_exception::class);
144             component_gradeitems::get_advancedgrading_itemnames_for_component('invalid_component');
145         }
147         /**
148          * Ensure that a component which does not implement the advancedgrading interface returns this.
149          */
150         public function test_get_advancedgrading_itemnames_for_component_no_interfaces(): void {
151             $this->expectException(coding_exception::class);
152             component_gradeitems::get_advancedgrading_itemnames_for_component('tests\core_grades\component_gradeitems\invalid');
153         }
155         /**
156          * Ensure that a component which implements the item mapping but not implement the advancedgrading interface returns this.
157          */
158         public function test_get_advancedgrading_itemnames_for_component_grading_no_interface(): void {
159             $this->expectException(coding_exception::class);
160             component_gradeitems::get_advancedgrading_itemnames_for_component('tests\core_grades\component_gradeitems\valid');
161         }
163         /**
164          * Ensure that a component implementing advanced grading returns the correct areas.
165          */
166         public function test_get_advancedgrading_itemnames_for_component(): void {
167             $areas = component_gradeitems::get_advancedgrading_itemnames_for_component('tests\core_grades\component_gradeitems\valid_and_advanced');
168             $this->assertEquals(['someother'], $areas);
169         }
171         /**
172          * Data provider for is_advancedgrading_itemname tests.
173          *
174          * @return array
175          */
176         public function is_advancedgrading_itemname_provider(): array {
177             return [
178                 'valid' => [
179                     'someother',
180                     true,
181                 ],
182                 'validnotadvanced' => [
183                     'rating',
184                     false,
185                 ],
186                 'invalid' => [
187                     'doesnotexist',
188                     false,
189                 ],
190             ];
191         }
193         /**
194          * Ensure that a component implementing advanced grading returns the correct areas.
195          *
196          * @dataProvider is_advancedgrading_itemname_provider
197          * @param string $itemname
198          * @param bool $isadvanced
199          */
200         public function test_is_advancedgrading_itemname(string $itemname, bool $isadvanced): void {
201             $this->assertEquals(
202                 $isadvanced,
203                 component_gradeitems::is_advancedgrading_itemname('tests\core_grades\component_gradeitems\valid_and_advanced', $itemname)
204             );
205         }
207         /**
208          * Data provider for get_field_name_for_itemnumber.
209          *
210          * @return array
211          */
212         public function get_field_name_for_itemnumber_provider(): array {
213             return [
214                 'Valid itemnumber 0 case 1' => [
215                     0,
216                     'gradecat',
217                     'gradecat',
218                 ],
219                 'Valid itemnumber 0 case 2' => [
220                     0,
221                     'melon',
222                     'melon',
223                 ],
224                 'Valid itemnumber 1 case 1' => [
225                     1,
226                     'gradecat',
227                     'gradecat_someother',
228                 ],
229                 'Valid itemnumber 1 case 2' => [
230                     1,
231                     'melon',
232                     'melon_someother',
233                 ],
234             ];
235         }
237         /**
238          * Ensure that valid field names are correctly mapped for a valid component.
239          *
240          * @dataProvider get_field_name_for_itemnumber_provider
241          * @param int $itemnumber The item itemnumber to test
242          * @param string $fieldname The field name being translated
243          * @param string $expected The expected value
244          */
245         public function test_get_field_name_for_itemnumber(int $itemnumber, string $fieldname, string $expected): void {
246             $component = 'tests\core_grades\component_gradeitems\valid';
247             $this->assertEquals($expected, component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, $fieldname));
248         }
250         /**
251          * Ensure that an invalid itemnumber does not provide any field name.
252          */
253         public function test_get_field_name_for_itemnumber_invalid_itemnumber(): void {
254             $component = 'tests\core_grades\component_gradeitems\valid';
256             $this->expectException(coding_exception::class);
257             component_gradeitems::get_field_name_for_itemnumber($component, 100, 'gradecat');
258         }
260         /**
261          * Ensure that a component which does not define a mapping can still get a mapping for itemnumber 0.
262          */
263         public function test_get_field_name_for_itemnumber_component_not_defining_mapping_itemnumber_zero(): void {
264             $component = 'tests\core_grades\othervalid';
266             $this->assertEquals('gradecat', component_gradeitems::get_field_name_for_itemnumber($component, 0, 'gradecat'));
267         }
269         /**
270          * Ensure that a component which does not define a mapping cannot get a mapping for itemnumber 1+.
271          */
272         public function test_get_field_name_for_itemnumber_component_not_defining_mapping_itemnumber_nonzero(): void {
273             $component = 'tests\core_grades\othervalid';
275             $this->expectException(coding_exception::class);
276             component_gradeitems::get_field_name_for_itemnumber($component, 100, 'gradecat');
277         }
279         /**
280          * Ensure that a component which incorrectly defines a mapping cannot get a mapping for itemnumber 1+.
281          */
282         public function test_get_field_name_for_itemnumber_component_invalid_mapping_itemnumber_nonzero(): void {
283             $component = 'tests\core_grades\component_gradeitems\invalid';
285             $this->expectException(coding_exception::class);
286             component_gradeitems::get_field_name_for_itemnumber($component, 100, 'gradecat');
287         }
289         /**
290          * Data provider for get_field_name_for_itemname.
291          *
292          * @return array
293          */
294         public function get_field_name_for_itemname_provider(): array {
295             return [
296                 'Empty itemname empty case 1' => [
297                     '',
298                     'gradecat',
299                     'gradecat',
300                 ],
301                 'Empty itemname empty case 2' => [
302                     '',
303                     'melon',
304                     'melon',
305                 ],
306                 'First itemname empty case 1' => [
307                     'rating',
308                     'gradecat',
309                     'gradecat',
310                 ],
311                 'First itemname empty case 2' => [
312                     'rating',
313                     'melon',
314                     'melon',
315                 ],
316                 'Other itemname empty case 1' => [
317                     'someother',
318                     'gradecat',
319                     'gradecat_someother',
320                 ],
321                 'Other itemname empty case 2' => [
322                     'someother',
323                     'melon',
324                     'melon_someother',
325                 ],
326             ];
327         }
329         /**
330          * Ensure that valid field names are correctly mapped for a valid component.
331          *
332          * @dataProvider get_field_name_for_itemname_provider
333          * @param string $itemname The item itemname to test
334          * @param string $fieldname The field name being translated
335          * @param string $expected The expected value
336          */
337         public function test_get_field_name_for_itemname(string $itemname, string $fieldname, string $expected): void {
338             $component = 'tests\core_grades\component_gradeitems\valid';
339             $this->assertEquals($expected, component_gradeitems::get_field_name_for_itemname($component, $itemname, $fieldname));
340         }
342         /**
343          * Ensure that an invalid itemname does not provide any field name.
344          */
345         public function test_get_field_name_for_itemname_invalid_itemname(): void {
346             $component = 'tests\core_grades\component_gradeitems\valid';
348             $this->expectException(coding_exception::class);
349             component_gradeitems::get_field_name_for_itemname($component, 'typo', 'gradecat');
350         }
352         /**
353          * Ensure that an empty itemname provides a matching fieldname regardless of whether the component exists or
354          * not.
355          */
356         public function test_get_field_name_for_itemname_not_defining_mapping_empty_name(): void {
357             $component = 'tests\core_grades\othervalid';
359             $this->assertEquals('gradecat', component_gradeitems::get_field_name_for_itemname($component, '', 'gradecat'));
360         }
362         /**
363          * Ensure that an valid component with some itemname excepts.
364          */
365         public function test_get_field_name_for_itemname_not_defining_mapping_with_name(): void {
366             $component = 'tests\core_grades\othervalid';
368             $this->expectException(coding_exception::class);
369             component_gradeitems::get_field_name_for_itemname($component, 'example', 'gradecat');
370         }
372         /**
373          * Ensure that an empty itemname provides a matching fieldname even if the mapping is invalid.
374          */
375         public function test_get_field_name_for_itemname_invalid_mapping_empty_name(): void {
376             $component = 'tests\core_grades\component_gradeitems\invalid';
378             $this->assertEquals('gradecat', component_gradeitems::get_field_name_for_itemname($component, '', 'gradecat'));
379         }
381         /**
382          * Ensure that an invalid mapping with some itemname excepts.
383          */
384         public function test_get_field_name_for_itemname_invalid_mapping_with_name(): void {
385             $component = 'tests\core_grades\component_gradeitems\invalid';
387             $this->expectException(coding_exception::class);
388             component_gradeitems::get_field_name_for_itemname($component, 'example', 'gradecat');
389         }
391         /**
392          * Data provider for get_itemname_from_itemnumber.
393          *
394          * @return array
395          */
396         public function get_itemname_from_itemnumber_provider(): array {
397             return [
398                 'Valid itemnumber 0' => [
399                     0,
400                     '',
401                 ],
402                 'Valid itemnumber 1' => [
403                     1,
404                     'someother',
405                 ],
406             ];
407         }
409         /**
410          * Ensure that item names are correctly mapped for a valid component.
411          *
412          * @dataProvider get_itemname_from_itemnumber_provider
413          * @param int $itemnumber The item itemnumber to test
414          * @param string $expected The expected value
415          */
416         public function test_get_itemname_from_itemnumber(int $itemnumber, string $expected): void {
417             $component = 'tests\core_grades\component_gradeitems\valid';
418             $this->assertEquals($expected, component_gradeitems::get_itemname_from_itemnumber($component, $itemnumber));
419         }
421         /**
422          * Ensure that an itemnumber over 1000 is treated as itemnumber 0 for the purpose of outcomes.
423          */
424         public function test_get_itemname_from_itemnumber_outcome_itemnumber(): void {
425             $component = 'tests\core_grades\component_gradeitems\valid';
427             $this->assertEquals('', component_gradeitems::get_itemname_from_itemnumber($component, 1000));
428         }
430         /**
431          * Ensure that an invalid itemnumber does not provide any field name.
432          */
433         public function test_get_itemname_from_itemnumber_invalid_itemnumber(): void {
434             $component = 'tests\core_grades\component_gradeitems\valid';
436             $this->expectException(coding_exception::class);
437             component_gradeitems::get_itemname_from_itemnumber($component, 100);
438         }
440         /**
441          * Ensure that a component which does not define a mapping can still get a mapping for itemnumber 0.
442          */
443         public function test_get_itemname_from_itemnumber_component_not_defining_mapping_itemnumber_zero(): void {
444             $component = 'tests\core_grades\othervalid';
446             $this->assertEquals('', component_gradeitems::get_itemname_from_itemnumber($component, 0));
447         }
449         /**
450          * Ensure that a component which does not define a mapping cannot get a mapping for itemnumber 1+.
451          */
452         public function test_get_itemname_from_itemnumber_component_not_defining_mapping_itemnumber_nonzero(): void {
453             $component = 'tests\core_grades\othervalid';
455             $this->expectException(coding_exception::class);
456             component_gradeitems::get_itemname_from_itemnumber($component, 100);
457         }
459         /**
460          * Ensure that a component which incorrectly defines a mapping cannot get a mapping for itemnumber 1+.
461          */
462         public function test_get_itemname_from_itemnumber_component_invalid_mapping_itemnumber_nonzero(): void {
463             $component = 'tests\core_grades\component_gradeitems\invalid';
465             $this->expectException(coding_exception::class);
466             component_gradeitems::get_itemname_from_itemnumber($component, 100);
467         }
469         /**
470          * Data provider for get_itemname_from_itemnumber.
471          *
472          * @return array
473          */
474         public function get_itemnumber_from_itemname_provider(): array {
475             return [
476                 'Empty itemname empty' => [
477                     '',
478                     0,
479                 ],
480                 'First itemname empty' => [
481                     'rating',
482                     0,
483                 ],
484                 'Other itemname empty' => [
485                     'someother',
486                     1,
487                 ],
488             ];
489         }
491         /**
492          * Ensure that valid item names are correctly mapped for a valid component.
493          *
494          * @dataProvider get_itemnumber_from_itemname_provider
495          * @param string $itemname The item itemname to test
496          * @param int $expected The expected value
497          */
498         public function test_get_itemnumber_from_itemname(string $itemname, int $expected): void {
499             $component = 'tests\core_grades\component_gradeitems\valid';
500             $this->assertEquals($expected, component_gradeitems::get_itemnumber_from_itemname($component, $itemname));
501         }
503         /**
504          * Ensure that an invalid itemname excepts.
505          */
506         public function test_get_itemnumber_from_itemname_invalid_itemname(): void {
507             $component = 'tests\core_grades\component_gradeitems\valid';
509             $this->expectException(coding_exception::class);
510             component_gradeitems::get_itemnumber_from_itemname($component, 'typo');
511         }
513         /**
514          * Ensure that an empty itemname provides a correct itemnumber regardless of whether the component exists or
515          * not.
516          */
517         public function test_get_itemnumber_from_itemname_not_defining_mapping_empty_name(): void {
518             $component = 'tests\core_grades\component_gradeitems\othervalid';
520             $this->assertEquals(0, component_gradeitems::get_itemnumber_from_itemname($component, ''));
521         }
523         /**
524          * Ensure that an valid component with some itemname excepts.
525          */
526         public function test_get_itemnumber_from_itemname_not_defining_mapping_with_name(): void {
527             $component = 'tests\core_grades\component_gradeitems\othervalid';
529             $this->expectException(coding_exception::class);
530             component_gradeitems::get_itemnumber_from_itemname($component, 'example');
531         }
533         /**
534          * Ensure that an empty itemname provides a matching fieldname even if the mapping is invalid.
535          */
536         public function test_get_itemnumber_from_itemname_invalid_mapping_empty_name(): void {
537             $component = 'tests\core_grades\component_gradeitems\invalid';
539             $this->assertEquals(0, component_gradeitems::get_itemnumber_from_itemname($component, ''));
540         }
542         /**
543          * Ensure that an invalid mapping with some itemname excepts.
544          */
545         public function test_get_itemnumber_from_itemname_invalid_mapping_with_name(): void {
546             $component = 'tests\core_grades\component_gradeitems\invalid';
548             $this->expectException(coding_exception::class);
549             component_gradeitems::get_itemnumber_from_itemname($component, 'example');
550         }
551     }
554 namespace tests\core_grades\component_gradeitems\valid\grades {
555     use core_grades\local\gradeitem\itemnumber_mapping;
557     /**
558      * Valid class for testing mappings.
559      *
560      * @package   core_grades
561      * @category  test
562      * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
563      * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
564      */
565     class gradeitems implements itemnumber_mapping {
566         /**
567          * Get the grade item mapping of item number to item name.
568          *
569          * @return array
570          */
571         public static function get_itemname_mapping_for_component(): array {
572             return [
573                 0 => 'rating',
574                 1 => 'someother',
575             ];
576         }
577     }
580 namespace tests\core_grades\component_gradeitems\valid_and_advanced\grades {
581     use core_grades\local\gradeitem\itemnumber_mapping;
582     use core_grades\local\gradeitem\advancedgrading_mapping;
584     /**
585      * Valid class for testing mappings.
586      *
587      * @package   core_grades
588      * @category  test
589      * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
590      * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
591      */
592     class gradeitems implements itemnumber_mapping, advancedgrading_mapping {
593         /**
594          * Get the grade item mapping of item number to item name.
595          *
596          * @return array
597          */
598         public static function get_itemname_mapping_for_component(): array {
599             return [
600                 0 => 'rating',
601                 1 => 'someother',
602             ];
603         }
605         /**
606          * Get the list of items which define advanced grading.
607          *
608          * @return array
609          */
610         public static function get_advancedgrading_itemnames(): array {
611             return [
612                 'someother',
613             ];
614         }
615     }
618 namespace tests\core_grades\component_gradeitems\invalid\grades {
619     use core_grades\local\gradeitem\itemnumber_mapping;
621     /**
622      * Invalid class for testing mappings.
623      *
624      * @package   core_grades
625      * @category  test
626      * @copyright 2019 Andrew Nicols <andrew@nicols.co.uk>
627      * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
628      */
629     class gradeitems {
630         /**
631          * Get the grade item mapping of item number to item name.
632          *
633          * @return array
634          */
635         public static function get_itemname_mapping_for_component(): array {
636             return [
637                 0 => 'rating',
638                 1 => 'someother',
639             ];
640         }
642         /**
643          * Get the list of items which define advanced grading.
644          *
645          * @return array
646          */
647         public static function get_advancedgrading_itemnames(): array {
648             return [
649                 1 => 'someother',
650             ];
651         }
652     }