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/>.
16
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 */
25
26declare(strict_types = 1);
27
28namespace tests\core_grades {
29
30    use advanced_testcase;
31    use core_grades\component_gradeitems;
32    use coding_exception;
33
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 {
43
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        }
53
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        }
61
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        }
73
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        }
95
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        }
109
110
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        }
117
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        }
124
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        }
131
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        }
138
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        }
146
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        }
154
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        }
162
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        }
170
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        }
192
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        }
206
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        }
236
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        }
249
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';
255
256            $this->expectException(coding_exception::class);
257            component_gradeitems::get_field_name_for_itemnumber($component, 100, 'gradecat');
258        }
259
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';
265
266            $this->assertEquals('gradecat', component_gradeitems::get_field_name_for_itemnumber($component, 0, 'gradecat'));
267        }
268
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';
274
275            $this->expectException(coding_exception::class);
276            component_gradeitems::get_field_name_for_itemnumber($component, 100, 'gradecat');
277        }
278
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';
284
285            $this->expectException(coding_exception::class);
286            component_gradeitems::get_field_name_for_itemnumber($component, 100, 'gradecat');
287        }
288
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        }
328
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        }
341
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';
347
348            $this->expectException(coding_exception::class);
349            component_gradeitems::get_field_name_for_itemname($component, 'typo', 'gradecat');
350        }
351
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';
358
359            $this->assertEquals('gradecat', component_gradeitems::get_field_name_for_itemname($component, '', 'gradecat'));
360        }
361
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';
367
368            $this->expectException(coding_exception::class);
369            component_gradeitems::get_field_name_for_itemname($component, 'example', 'gradecat');
370        }
371
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';
377
378            $this->assertEquals('gradecat', component_gradeitems::get_field_name_for_itemname($component, '', 'gradecat'));
379        }
380
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';
386
387            $this->expectException(coding_exception::class);
388            component_gradeitems::get_field_name_for_itemname($component, 'example', 'gradecat');
389        }
390
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        }
408
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        }
420
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';
426
427            $this->assertEquals('', component_gradeitems::get_itemname_from_itemnumber($component, 1000));
428        }
429
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';
435
436            $this->expectException(coding_exception::class);
437            component_gradeitems::get_itemname_from_itemnumber($component, 100);
438        }
439
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';
445
446            $this->assertEquals('', component_gradeitems::get_itemname_from_itemnumber($component, 0));
447        }
448
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';
454
455            $this->expectException(coding_exception::class);
456            component_gradeitems::get_itemname_from_itemnumber($component, 100);
457        }
458
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';
464
465            $this->expectException(coding_exception::class);
466            component_gradeitems::get_itemname_from_itemnumber($component, 100);
467        }
468
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        }
490
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        }
502
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';
508
509            $this->expectException(coding_exception::class);
510            component_gradeitems::get_itemnumber_from_itemname($component, 'typo');
511        }
512
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';
519
520            $this->assertEquals(0, component_gradeitems::get_itemnumber_from_itemname($component, ''));
521        }
522
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';
528
529            $this->expectException(coding_exception::class);
530            component_gradeitems::get_itemnumber_from_itemname($component, 'example');
531        }
532
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';
538
539            $this->assertEquals(0, component_gradeitems::get_itemnumber_from_itemname($component, ''));
540        }
541
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';
547
548            $this->expectException(coding_exception::class);
549            component_gradeitems::get_itemnumber_from_itemname($component, 'example');
550        }
551    }
552}
553
554namespace tests\core_grades\component_gradeitems\valid\grades {
555    use core_grades\local\gradeitem\itemnumber_mapping;
556
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    }
578}
579
580namespace 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;
583
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        }
604
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    }
616}
617
618namespace tests\core_grades\component_gradeitems\invalid\grades {
619    use core_grades\local\gradeitem\itemnumber_mapping;
620
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        }
641
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    }
653}
654