From 150dd39a5b901a2adcba0e33e38ad971096f8be9 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 17 Jan 2016 12:40:30 +0100 Subject: [PATCH] ItemSessionControl->showFeedback now considered correctly. --- .../runtime/tests/AssessmentItemSession.php | 47 +++-- .../tests/AssessmentTestSessionTest.php | 104 +++++++++-- ...eedbacks_nonadaptive_individual_linear.xml | 166 +++++++++++++++++- 3 files changed, 285 insertions(+), 32 deletions(-) diff --git a/src/qtism/runtime/tests/AssessmentItemSession.php b/src/qtism/runtime/tests/AssessmentItemSession.php index 3514774d7..b36fedf11 100644 --- a/src/qtism/runtime/tests/AssessmentItemSession.php +++ b/src/qtism/runtime/tests/AssessmentItemSession.php @@ -897,24 +897,23 @@ public function suspend() $msg = "Cannot switch from state " . strtoupper(AssessmentItemSessionState::getNameByConstant($state)) . " to state SUSPENDED."; $code = AssessmentItemSessionException::STATE_VIOLATION; throw new AssessmentItemSessionException($msg, $this, $code); - } else { + } elseif ($state == AssessmentItemSessionState::MODAL_FEEDBACK) { + // Let's play the suspension ritual... + $maxAttempts = $this->getItemSessionControl()->getMaxAttempts(); - if ($state == AssessmentItemSessionState::MODAL_FEEDBACK) { - // Let's play the suspension ritual... - $maxAttempts = $this->getItemSessionControl()->getMaxAttempts(); - + if ($this->getAssessmentItem()->isAdaptive() === true && $this->getSubmissionMode() === SubmissionMode::INDIVIDUAL && $this['completionStatus']->getValue() === self::COMPLETION_STATUS_COMPLETED) { // -- Adaptive item. - if ($this->getAssessmentItem()->isAdaptive() === true && $this->getSubmissionMode() === SubmissionMode::INDIVIDUAL && $this['completionStatus']->getValue() === self::COMPLETION_STATUS_COMPLETED) { - $this->endItemSession(); - } + $this->endItemSession(); + } elseif ($this->getAssessmentItem()->isAdaptive() === false && $this['numAttempts']->getValue() >= $maxAttempts && $maxAttempts !== 0 && $this->getSubmissionMode() !== SubmissionMode::SIMULTANEOUS) { // -- Non-adaptive item + maxAttempts reached. - elseif ($this->getAssessmentItem()->isAdaptive() === false && $this['numAttempts']->getValue() >= $maxAttempts && $maxAttempts !== 0 && $this->getSubmissionMode() !== SubmissionMode::SIMULTANEOUS) { - $this->endItemSession(); - } + $this->endItemSession(); } else { $this->setState(AssessmentItemSessionState::SUSPENDED); $this->setAttempting(false); } + } else { + $this->setState(AssessmentItemSessionState::SUSPENDED); + $this->setAttempting(false); } } @@ -1285,11 +1284,31 @@ protected function createResponseProcessingEngine(ResponseProcessing $responsePr */ private function mustModalFeedback() { + // From IMS QTI 2.1: + // A value of maxAttempts greater than 1, by definition, indicates that any applicable feedback must be shown. + // This applies to both Modal Feedback and Integrated Feedback where applicable. However, once the maximum number + // of allowed attempts have been used (or for adaptive items, completionStatus has been set to completed) whether + // or not feedback is shown is controlled by the showFeedback constraint. + // + // This [showFeedback] constraint affects the visibility of feedback after the end of the last attempt. If it + // is false then feedback is not shown. This includes both Modal Feedback and Integrated Feedback even if the + // candidate has access to the review state. The default is false. + $mustModalFeedback = false; + $itemSessionControl = $this->getItemSessionControl(); + + if ($this->getRemainingAttempts() === 0 && $itemSessionControl->mustShowFeedback() === false) { + return $mustModalFeedback; + } // Feedback is never shown in SIMULTANEOUS submission mode, nor if showFeedback is disabled. - if ($this->getSubmissionMode() === SubmissionMode::INDIVIDUAL && $this->getItemSessionControl()->mustShowFeedback() === true) { - + $maxAttempts = $itemSessionControl->getMaxAttempts(); + if ($this->getAssessmentItem()->isAdaptive() === true) { + $maxAttempts = 0; + } + + if (($maxAttempts === 0 || $maxAttempts > 1) && $this->getSubmissionMode() === SubmissionMode::INDIVIDUAL) { + foreach ($this->getAssessmentItem()->getModalFeedbackRules() as $rule) { $outcomeValue = $this[$rule->getOutcomeIdentifier()]; @@ -1308,7 +1327,7 @@ private function mustModalFeedback() } } } - + return $mustModalFeedback; } diff --git a/test/qtismtest/runtime/tests/AssessmentTestSessionTest.php b/test/qtismtest/runtime/tests/AssessmentTestSessionTest.php index 24a45636a..b9cdfd54a 100644 --- a/test/qtismtest/runtime/tests/AssessmentTestSessionTest.php +++ b/test/qtismtest/runtime/tests/AssessmentTestSessionTest.php @@ -1386,39 +1386,113 @@ public function testItemModalFeedbacks() { $session = $manager->createAssessmentTestSession($doc->getDocumentComponent()); $session->beginTestSession(); - // -- Q01. + // -- Q01 nonAdaptive, maxAttempts = 1, showFeedback = true. $session->beginAttempt(); $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('true')))); $session->endAttempt($responses); - $this->assertEquals(AssessmentItemSessionState::MODAL_FEEDBACK, $session->getCurrentAssessmentItemSession()->getState()); + // The ModalFeedback must not be shown because the number of attempts is not > 1. + $this->assertEquals(AssessmentItemSessionState::CLOSED, $session->getCurrentAssessmentItemSession()->getState()); // -- Move from Q01 to Q02. $session->moveNext(); - // Check that the item session for Q01 is correctly CLOSED by moving next, - // even if it was in a modal feedback state. Why closed and not suspended? - // Because maxAttempts = 1. - $itemSessions = $session->getAssessmentItemSessions('Q01'); - $this->assertEquals(AssessmentItemSessionState::CLOSED, $itemSessions[0]->getState()); - - // -- Q02. - // Here, the maxAttempts is 0 i.e. no limit. Moreover, feedback is shown only if the answer - // is wrong. + + // -- Q02 nonAdaptive, maxAttempts = 0, showFeedback = false. + // Here, the maxAttempts is 0 i.e. no limit. Moreover, feedback is shown only if the answer is wrong. $session->beginAttempt(); $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('false')))); $session->endAttempt($responses); $this->assertEquals(AssessmentItemSessionState::MODAL_FEEDBACK, $session->getCurrentAssessmentItemSession()->getState()); - // Brutal new attempt! + // Brutal new attempt, without suspending explicitely the item session! $session->beginAttempt(); $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('true')))); $session->endAttempt($responses); $this->assertEquals(AssessmentItemSessionState::SUSPENDED, $session->getCurrentAssessmentItemSession()->getState()); - - $session->endTestSession(); + + $session->beginAttempt(); + $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('false')))); + $session->endAttempt($responses); + $this->assertEquals(AssessmentItemSessionState::MODAL_FEEDBACK, $session->getCurrentAssessmentItemSession()->getState()); + + // -- Move from Q02 to Q03. + $session->moveNext(); + + // Make sure that Q02's session get's closed by moving next. + $itemSessions = $session->getAssessmentItemSessions('Q02'); + $this->assertEquals(AssessmentItemSessionState::SUSPENDED, $itemSessions[0]->getState()); + + // -- Q03 nonAdaptive, maxAttempts = 2, showFeedback = false. + $session->beginAttempt(); + $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('false')))); + $session->endAttempt($responses); + $this->assertEquals(AssessmentItemSessionState::MODAL_FEEDBACK, $session->getCurrentAssessmentItemSession()->getState()); + + // itemSessionControl->showFeedback = false. It means that the last attempt will have no ModalFeedback shown. + $session->beginAttempt(); + $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('false')))); + $session->endAttempt($responses); + $this->assertEquals(AssessmentItemSessionState::CLOSED, $session->getCurrentAssessmentItemSession()->getState()); + + // -- Move from Q03 to Q04. + $session->moveNext(); + + // -- Q04 nonAdaptive, maxAttempts = 2, showFeedback = true + $session->beginAttempt(); + $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('false')))); + $session->endAttempt($responses); + $this->assertEquals(AssessmentItemSessionState::MODAL_FEEDBACK, $session->getCurrentAssessmentItemSession()->getState()); + + // itemSessionControl->showFeedback = true. It means that the last attempt will have a ModalFeedback shown. + $session->beginAttempt(); + $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('false')))); + $session->endAttempt($responses); + $this->assertEquals(AssessmentItemSessionState::MODAL_FEEDBACK, $session->getCurrentAssessmentItemSession()->getState()); + + // -- Move from Q04 to Q05. + $session->moveNext(); + // Check that Q04's session went to CLOSE state by moving next, because max number of attempts reached. + $itemSessions = $session->getAssessmentItemSessions('Q04'); + $this->assertEquals(AssessmentItemSessionState::CLOSED, $itemSessions[0]->getState()); + + // -- Q05 adaptive, showFeedback = true + $session->beginAttempt(); + $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('false')))); + $session->endAttempt($responses); + $this->assertEquals(AssessmentItemSessionState::MODAL_FEEDBACK, $session->getCurrentAssessmentItemSession()->getState()); + + // $itemSessionControl->showFeedback = true, so the final "correct!" feedback is shown. + $session->beginAttempt(); + $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('true')))); + $session->endAttempt($responses); + $this->assertEquals(AssessmentItemSessionState::MODAL_FEEDBACK, $session->getCurrentAssessmentItemSession()->getState()); + + // -- Move from Q05 to Q06. + $session->moveNext(); + // Check that Q05 went to CLOSE state. + $itemSessions = $session->getAssessmentItemSessions('Q05'); + $this->assertEquals(AssessmentItemSessionState::CLOSED, $itemSessions[0]->getState()); + + // -- Q06 adaptive, showFeedback = false + $session->beginAttempt(); + $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('false')))); + $session->endAttempt($responses); + $this->assertEquals(AssessmentItemSessionState::MODAL_FEEDBACK, $session->getCurrentAssessmentItemSession()->getState()); + + // $itemSessionControl->showFeedback = false, so the "correct!" feedback is not shown. + $session->beginAttempt(); + $responses = new State(array(new ResponseVariable('RESPONSE', Cardinality::SINGLE, BaseType::IDENTIFIER, new QtiIdentifier('true')))); + $session->endAttempt($responses); + $this->assertEquals(AssessmentItemSessionState::CLOSED, $session->getCurrentAssessmentItemSession()->getState()); + + // Ends the test session. + $session->moveNext(); + $itemSessions = $session->getAssessmentItemSessionStore()->getAllAssessmentItemSessions(); foreach ($itemSessions as $itemSession) { $this->assertEquals(AssessmentItemSessionState::CLOSED, $itemSession->getState()); } + + $this->assertEquals(AssessmentTestSessionState::CLOSED, $session->getState()); } public function testIsTimeout() { @@ -1473,4 +1547,4 @@ public function testIsTimeout() { $this->assertEquals(AssessmentTestSessionState::CLOSED, $session->getState()); $this->assertFalse($session->isTimeout()); } -} \ No newline at end of file +} diff --git a/test/samples/custom/runtime/item_modalfeedbacks/modalfeedbacks_nonadaptive_individual_linear.xml b/test/samples/custom/runtime/item_modalfeedbacks/modalfeedbacks_nonadaptive_individual_linear.xml index 289b03925..241431cd9 100644 --- a/test/samples/custom/runtime/item_modalfeedbacks/modalfeedbacks_nonadaptive_individual_linear.xml +++ b/test/samples/custom/runtime/item_modalfeedbacks/modalfeedbacks_nonadaptive_individual_linear.xml @@ -4,9 +4,9 @@ xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_v2p1 http://www.taotesting.com/xsd/qticompact_v1p0.xsd" identifier="modalfeedbacks_nonadaptive_individual_linear" title="Modal Feedbacks"> - + true @@ -44,7 +44,7 @@ - + true @@ -79,6 +79,166 @@ + + + + + + + true + + + + + 0 + + + + + + + + + + + + 1.0 + + + correct + + + + + incorrect + + + + + + + + + + + + + true + + + + + 0 + + + + + + + + + + + + 1.0 + + + correct + + + + + incorrect + + + + + + + + + + + + + true + + + + + 0 + + + + + + + + + + + + 1.0 + + + correct + + + completed + + + + + incorrect + + + + + + + + + + + + + + true + + + + + 0 + + + + + + + + + + + + 1.0 + + + correct + + + completed + + + + + incorrect + + + + + + + - \ No newline at end of file +