Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 * Assignment activity-related unit tests for Completion Progress block.
19
 *
20
 * @package    block_completion_progress
21
 * @copyright  2020 Jonathon Fowler <fowlerj@usq.edu.au>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace block_completion_progress;
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
global $CFG;
30
require_once($CFG->dirroot.'/mod/assign/locallib.php');
31
require_once($CFG->dirroot.'/mod/assign/tests/fixtures/testable_assign.php');
32
 
33
use block_completion_progress\completion_progress;
34
use block_completion_progress\defaults;
35
 
36
/**
37
 * Assignment activity-related unit tests for Completion Progress block.
38
 *
39
 * @package    block_completion_progress
40
 * @copyright  2020 Jonathon Fowler <fowlerj@usq.edu.au>
41
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42
 */
43
class assign_completion_test extends \block_completion_progress\tests\completion_testcase {
44
    /**
45
     * Test assignment completion state changes.
46
     * @covers \block_completion_progress\completion_progress
47
     */
48
    public function test_assign_get_completion_state() {
49
        global $DB, $PAGE;
50
 
51
        $output = $PAGE->get_renderer('block_completion_progress');
52
        $generator = $this->getDataGenerator();
53
 
54
        $instance = $generator->create_module('assign', [
55
            'course' => $this->course->id,
56
            'submissiondrafts' => 0,
57
            'assignsubmission_onlinetext_enabled' => 1,
58
            'assignsubmission_file_enabled' => 0,
59
            'completionsubmit' => 1,
60
            'completionusegrade' => 1,
61
            'completionpassgrade' => 0,
62
            'completion' => COMPLETION_TRACKING_AUTOMATIC
63
        ]);
64
        $cm = get_coursemodule_from_instance('assign', $instance->id);
65
        $context = \context_module::instance($cm->id);
66
        $assign = new \assign($context, $cm, $this->course);
67
        $completion = new \completion_info($this->course);
68
 
69
        $student1 = $generator->create_and_enrol($this->course, 'student');
70
 
71
        $this->setUser($student1);
72
 
73
        $result = $completion->get_data($assign->get_course_module(), false, $student1->id);
74
        $this->assertSame(COMPLETION_INCOMPLETE, $result->customcompletion['completionsubmit'], 'no submission');
75
        $this->assertSame(COMPLETION_INCOMPLETE, $result->completionstate, 'no submission');
76
 
77
        $progress = (new completion_progress($this->course))
78
                    ->for_user($student1)
79
                    ->for_block_instance($this->blockinstance);
80
        $text = $output->render($progress);
81
 
82
        $this->assertStringContainsStringIgnoringCase('assign', $text, 'no submission');
83
        $this->assertStringNotContainsStringIgnoringCase('quiz', $text, 'no submission');
84
 
85
        // Not yet submitted, nor completed.
86
        $this->assertStringContainsString('futureNotCompleted', $text, 'no submission');
87
 
88
        $submission = $assign->get_user_submission($student1->id, true);
89
        $data = (object)[
90
            'userid' => $student1->id,
91
            'onlinetext_editor' => ['text' => 'Text', 'format' => FORMAT_PLAIN],
92
        ];
93
        $notices = [];
94
        $this->assertTrue($assign->save_submission($data, $notices), 'submitted awaiting grade');
95
        $this->assertEmpty($notices, 'submitted awaiting grade');
96
 
97
        $result = $completion->get_data($assign->get_course_module(), false, $student1->id);
98
        $this->assertSame(COMPLETION_COMPLETE, $result->customcompletion['completionsubmit'], 'submitted awaiting grade');
99
        $this->assertSame(COMPLETION_INCOMPLETE, $result->completiongrade, 'submitted awaiting grade');
100
        $this->assertSame(COMPLETION_INCOMPLETE, $result->completionstate, 'submitted awaiting grade');
101
 
102
        $progress = (new completion_progress($this->course))
103
                    ->for_user($student1)
104
                    ->for_block_instance($this->blockinstance);
105
        $text = $output->render($progress);
106
 
107
        // Submitted and awaiting a grade, so not completed.
108
        $this->assertStringContainsString('submittedNotComplete', $text, 'submitted awaiting grade');
109
    }
110
 
111
    /**
112
     * Test completion determination in an Assignment activity with pass/fail enabled.
113
     * @covers \block_completion_progress\completion_progress
114
     */
115
    public function test_assign_passfail() {
116
        $generator = $this->getDataGenerator();
117
 
118
        $instance = $generator->create_module('assign', [
119
            'course' => $this->course->id,
120
            'grade' => 100,
121
            'maxattempts' => -1,
122
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
123
            'completion' => COMPLETION_TRACKING_AUTOMATIC,
124
            'completionusegrade' => 1,      // The student must receive a grade to complete.
125
            'completionexpected' => time() - DAYSECS,
126
            'teamsubmission' => 0,
127
        ]);
128
        $cm = get_coursemodule_from_id('assign', $instance->cmid);
129
 
130
        // Set the passing grade.
131
        $item = \grade_item::fetch(['courseid' => $this->course->id, 'itemtype' => 'mod',
132
            'itemmodule' => 'assign', 'iteminstance' => $instance->id, 'outcomeid' => null]);
133
        $item->gradepass = 50;
134
        $item->update();
135
 
136
        $assign = new \mod_assign_testable_assign(
137
            \context_module::instance($cm->id), $cm, $this->course);
138
 
139
        $teacher = $generator->create_and_enrol($this->course, 'editingteacher');
140
 
141
        // Student 1 submits to the activity and gets graded correct.
142
        $student1 = $generator->create_and_enrol($this->course, 'student');
143
        $this->assert_progress_completion($student1, $cm, COMPLETION_INCOMPLETE);
144
        $this->submit_for_student($student1, $assign);
145
        $this->assert_progress_completion($student1, $cm, 'submitted');
146
        $this->grade_student($student1, $assign, $teacher, 75, 0);      // Pass.
147
        $this->assert_progress_completion($student1, $cm, COMPLETION_COMPLETE_PASS);
148
 
149
        // Student 2 submits to the activity and gets graded incorrect.
150
        $student2 = $generator->create_and_enrol($this->course, 'student');
151
        $this->assert_progress_completion($student2, $cm, COMPLETION_INCOMPLETE);
152
        $this->submit_for_student($student2, $assign);
153
        $this->assert_progress_completion($student2, $cm, 'submitted');
154
        $this->grade_student($student2, $assign, $teacher, 25, 0);      // Fail.
155
        $this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE_FAIL);
156
 
157
        // Student 2 then submits again.
158
        $this->submit_for_student($student2, $assign);
159
        $this->assert_progress_completion($student2, $cm, 'submitted');
160
    }
161
 
162
    /**
163
     * Test completion determination in an Assignment activity with basic completion.
164
     * @covers \block_completion_progress\completion_progress
165
     */
166
    public function test_assign_basic() {
167
        $generator = $this->getDataGenerator();
168
 
169
        $instance = $generator->create_module('assign', [
170
            'course' => $this->course->id,
171
            'maxattempts' => -1,
172
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
173
            'completion' => COMPLETION_TRACKING_AUTOMATIC,
174
            'completionsubmit' => 1,        // Submission alone is enough to trigger completion.
175
            'completionexpected' => time() - DAYSECS,
176
            'teamsubmission' => 0,
177
        ]);
178
        $cm = get_coursemodule_from_id('assign', $instance->cmid);
179
 
180
        $assign = new \mod_assign_testable_assign(
181
            \context_module::instance($cm->id), $cm, $this->course);
182
 
183
        $teacher = $generator->create_and_enrol($this->course, 'editingteacher');
184
 
185
        // Student 1 submits to the activity and gets graded correctly.
186
        $student1 = $generator->create_and_enrol($this->course, 'student');
187
        $this->assert_progress_completion($student1, $cm, COMPLETION_INCOMPLETE);
188
        $this->submit_for_student($student1, $assign);
189
        $this->assert_progress_completion($student1, $cm, COMPLETION_COMPLETE);
190
        $this->grade_student($student1, $assign, $teacher, 75, 0);      // Pass.
191
        $this->assert_progress_completion($student1, $cm, COMPLETION_COMPLETE);
192
 
193
        // Student 2 submits to the activity and gets graded incorrectly.
194
        $student2 = $generator->create_and_enrol($this->course, 'student');
195
        $this->assert_progress_completion($student2, $cm, COMPLETION_INCOMPLETE);
196
        $this->submit_for_student($student2, $assign);
197
        $this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE);
198
        $this->grade_student($student2, $assign, $teacher, 25, 0);      // Fail.
199
        $this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE);
200
 
201
        // Student 2 then submits again.
202
        $this->submit_for_student($student2, $assign);
203
        $this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE);
204
    }
205
 
206
    /**
207
     * A data provider supplying each of the possible quiz grade methods.
208
     * @return array
209
     */
210
    public function teamsubmission_provider(): array {
211
        return [
212
            'one-per-group' => [ 0, ],
213
            'per-member'    => [ 1, ],
214
        ];
215
    }
216
 
217
    /**
218
     * Test completion determination in an Assignment activity requiring team submissions,
219
     * one submission per group.
220
     *
221
     * @param integer $requireallteammemberssubmit
222
     *
223
     * @covers \block_completion_progress\completion_progress
224
     * @dataProvider teamsubmission_provider
225
     */
226
    public function test_teamsubmission($requireallteammemberssubmit) {
227
        $generator = $this->getDataGenerator();
228
 
229
        $grouping1 = $generator->create_grouping(['courseid' => $this->course->id]);
230
        $group1 = $generator->create_group(['courseid' => $this->course->id]);
231
        $group2 = $generator->create_group(['courseid' => $this->course->id]);
232
        $generator->create_grouping_group(['groupingid' => $grouping1->id, 'groupid' => $group1->id]);
233
        $generator->create_grouping_group(['groupingid' => $grouping1->id, 'groupid' => $group2->id]);
234
 
235
        $instance = $generator->create_module('assign', [
236
            'course' => $this->course->id,
237
            'maxattempts' => -1,
238
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_NONE,
239
            'completion' => COMPLETION_TRACKING_AUTOMATIC,
240
            'completionusegrade' => 1,      // The student must receive a grade to complete.
241
            'completionexpected' => time() - DAYSECS,
242
            'teamsubmission' => 1,
243
            'teamsubmissiongroupingid' => $grouping1->id,
244
            'requireallteammemberssubmit' => $requireallteammemberssubmit,
245
            'preventsubmissionnotingroup' => 0,
246
        ]);
247
        $cm = get_coursemodule_from_id('assign', $instance->cmid);
248
 
249
        $teacher = $generator->create_and_enrol($this->course, 'editingteacher');
250
 
251
        // Students 1 and 2 are grouped together.
252
        $student1 = $generator->create_and_enrol($this->course, 'student');
253
        $generator->create_group_member(['groupid' => $group1->id, 'userid' => $student1->id]);
254
        $student2 = $generator->create_and_enrol($this->course, 'student');
255
        $generator->create_group_member(['groupid' => $group1->id, 'userid' => $student2->id]);
256
 
257
        // Student 3 is not a group member.
258
        $student3 = $generator->create_and_enrol($this->course, 'student');
259
 
260
        $assign = new \mod_assign_testable_assign(
261
            \context_module::instance($cm->id), $cm, $this->course);
262
 
263
        if ($requireallteammemberssubmit == 0) {    // One-per-group.
264
            // Student 1 submits for Group 1.
265
            $this->assert_progress_completion($student1, $cm, COMPLETION_INCOMPLETE);
266
            $this->assert_progress_completion($student2, $cm, COMPLETION_INCOMPLETE);
267
            $this->submit_for_student($student1, $assign);
268
            $this->assert_progress_completion($student1, $cm, 'submitted');
269
            $this->assert_progress_completion($student2, $cm, 'submitted');
270
            $this->grade_student($student1, $assign, $teacher, 75, 0);      // Pass.
271
            $this->grade_student($student2, $assign, $teacher, 25, 0);      // Fail.
272
            $this->assert_progress_completion($student1, $cm, COMPLETION_COMPLETE);
273
            $this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE);
274
 
275
            // Student 2 submits for themself ungrouped.
276
            $this->assert_progress_completion($student3, $cm, COMPLETION_INCOMPLETE);
277
            $this->submit_for_student($student3, $assign);
278
            $this->assert_progress_completion($student3, $cm, 'submitted');
279
            $this->grade_student($student3, $assign, $teacher, 75, 0);      // Pass.
280
            $this->assert_progress_completion($student3, $cm, COMPLETION_COMPLETE);
281
 
282
        } else {
283
            // Set the passing grade.
284
            $item = \grade_item::fetch(['courseid' => $this->course->id, 'itemtype' => 'mod',
285
                'itemmodule' => 'assign', 'iteminstance' => $instance->id, 'outcomeid' => null]);
286
            $item->gradepass = 50;
287
            $item->update();
288
 
289
            // Students 1 and 2 submit individually for Group 1.
290
            $this->assert_progress_completion($student1, $cm, COMPLETION_INCOMPLETE);
291
            $this->assert_progress_completion($student2, $cm, COMPLETION_INCOMPLETE);
292
            $this->submit_for_student($student1, $assign);
293
            $this->assert_progress_completion($student1, $cm, 'submitted');
294
            $this->assert_progress_completion($student2, $cm, COMPLETION_INCOMPLETE);
295
            $this->submit_for_student($student2, $assign);
296
            $this->assert_progress_completion($student2, $cm, 'submitted');
297
            $this->grade_student($student1, $assign, $teacher, 75, 0);      // Pass.
298
            $this->grade_student($student2, $assign, $teacher, 25, 0);      // Fail.
299
            $this->assert_progress_completion($student1, $cm, COMPLETION_COMPLETE_PASS);
300
            $this->assert_progress_completion($student2, $cm, COMPLETION_COMPLETE_FAIL);
301
        }
302
    }
303
 
304
    /**
305
     * Submit an assignment.
306
     * Pinched from mod/assign/tests/generator.php and modified.
307
     *
308
     * @param object $student
309
     * @param assign $assign
310
     *
311
     * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
312
     */
313
    private function submit_for_student($student, $assign) {
314
        $this->setUser($student);
315
 
316
        $sink = $this->redirectMessages();
317
 
318
        $assign->save_submission((object) [
319
            'userid' => $student->id,
320
            'onlinetext_editor' => [
321
                'itemid' => file_get_unused_draft_itemid(),
322
                'text' => 'Text',
323
                'format' => FORMAT_HTML,
324
            ]
325
        ], $notices);
326
 
327
        $assign->submit_for_grading((object) [
328
            'userid' => $student->id,
329
        ], []);
330
 
331
        $sink->close();
332
    }
333
 
334
    /**
335
     * Award a grade to a submission.
336
     * Pinched from mod/assign/tests/generator.php and modified.
337
     *
338
     * @param object $student
339
     * @param assign $assign
340
     * @param object $teacher
341
     * @param integer $grade
342
     * @param integer $attempt
343
     *
344
     * @copyright  2018 Andrew Nicols <andrew@nicols.co.uk>
345
     */
346
    private function grade_student($student, $assign, $teacher, $grade, $attempt) {
347
        global $DB;
348
 
349
        $this->setUser($teacher);
350
 
351
        // Bump all timecreated and timemodified for this user back.
352
        $DB->execute('UPDATE {assign_submission} ' .
353
            'SET timecreated = timecreated - 1, timemodified = timemodified - 1 ' .
354
            'WHERE userid = :userid',
355
            ['userid' => $student->id]);
356
 
357
        $assign->testable_apply_grade_to_user((object) [ 'grade' => $grade ],
358
            $student->id, $attempt);
359
    }
360
}