diff --git a/composer.json b/composer.json
index a0b354182..47cdafea6 100644
--- a/composer.json
+++ b/composer.json
@@ -2,7 +2,7 @@
"name": "qtism/qtism",
"description": "OAT QTI Software Module Library",
"type": "library",
- "version": "0.9.12",
+ "version": "0.9.13",
"authors": [
{
"name": "Open Assessment Technologies S.A.",
diff --git a/qtism/runtime/tests/AssessmentTestSession.php b/qtism/runtime/tests/AssessmentTestSession.php
index c3b4aaf28..ae6378ef4 100644
--- a/qtism/runtime/tests/AssessmentTestSession.php
+++ b/qtism/runtime/tests/AssessmentTestSession.php
@@ -1585,9 +1585,11 @@ protected function nextRouteItem($ignoreBranchings = false, $ignorePreConditions
$this->endTestSession();
}
else if ($target === 'EXIT_TESTPART') {
+ $route->next();
$this->moveNextTestPart();
}
else if ($target === 'EXIT_SECTION') {
+ $route->next();
$this->moveNextAssessmentSection();
}
else {
@@ -1653,42 +1655,40 @@ public function moveNextTestPart() {
$route = $this->getRoute();
$from = $route->current();
- $route->next();
- while ($route->valid() === true && $route->current()->getTestPart() === $from->getTestPart()) {
- $this->nextRouteItem();
- }
-
- if ($this->isRunning() === true) {
- $this->interactWithItemSession();
- }
+ while ($route->valid() === true && $route->current()->getTestPart() === $from->getTestPart()) {
+ $this->nextRouteItem();
+ }
+
+ if ($this->isRunning() === true) {
+ $this->interactWithItemSession();
+ }
}
-
+
/**
* Set the position in the Route at the very next assessmentSection in the route sequence.
- *
+ *
* * If there is no assessmentSection left in the flow, the test session ends gracefully.
* * If there are still pending responses, they are processed.
- *
+ *
* @throws AssessmentTestSessionException If the test is not running.
*/
public function moveNextAssessmentSection() {
-
+
if ($this->isRunning() === false) {
$msg = "Cannot move to the next assessmentSection while the state of the test session is INITIAL or CLOSED.";
throw new AssessmentTestSessionException($msg, AssessmentTestSessionException::STATE_VIOLATION);
}
-
+
$route = $this->getRoute();
$from = $route->current();
- $route->next();
- while ($route->valid() === true && $route->current()->getAssessmentSection() === $from->getAssessmentSection()) {
- $this->nextRouteItem();
- }
-
- if ($this->isRunning() === true) {
- $this->interactWithItemSession();
- }
+ while ($route->valid() === true && $route->current()->getAssessmentSection() === $from->getAssessmentSection()) {
+ $this->nextRouteItem();
+ }
+
+ if ($this->isRunning() === true) {
+ $this->interactWithItemSession();
+ }
}
/**
@@ -2526,4 +2526,4 @@ protected function buildCurrentItemSessionIdentifier() {
protected function timeLimitsInForce($excludeItem = false) {
return count($this->getCurrentRouteItem()->getTimeLimits($excludeItem)) !== 0;
}
-}
\ No newline at end of file
+}
diff --git a/test/qtism/runtime/tests/AssessmentTestSessionTimingTest.php b/test/qtism/runtime/tests/AssessmentTestSessionTimingTest.php
index 679e90986..e6344cc60 100644
--- a/test/qtism/runtime/tests/AssessmentTestSessionTimingTest.php
+++ b/test/qtism/runtime/tests/AssessmentTestSessionTimingTest.php
@@ -334,4 +334,35 @@ public function testMultipleOccurences() {
$this->assertEquals(2, $session['Q01.2.duration']->getSeconds(true));
$this->assertEquals(0, $session['Q01.3.duration']->getSeconds(true));
}
- }
\ No newline at end of file
+
+ public function testLastItemTimeout() {
+ $session = self::instantiate(self::samplesDir() . 'custom/runtime/timings/last_item_timeout.xml');
+ $session->beginTestSession();
+
+ $session->beginAttempt();
+ sleep(2);
+ $session->moveNext();
+ $this->assertEquals(AssessmentTestSessionState::CLOSED, $session->getState());
+ }
+
+ public function testLastItemSectionTimeout() {
+ $session = self::instantiate(self::samplesDir() . 'custom/runtime/timings/last_item_section_timeout.xml');
+ $session->beginTestSession();
+
+ $session->beginAttempt();
+ sleep(2);
+ $session->moveNextAssessmentSection();
+ $this->assertEquals(AssessmentTestSessionState::CLOSED, $session->getState());
+ }
+
+ public function testLastItemTestPartTimeout() {
+ $session = self::instantiate(self::samplesDir() . 'custom/runtime/timings/last_item_testpart_timeout.xml');
+ $session->beginTestSession();
+
+ $session->beginAttempt();
+ sleep(2);
+ $session->moveNextTestPart();
+ $this->assertEquals(AssessmentTestSessionState::CLOSED, $session->getState());
+ }
+ }
+
diff --git a/test/samples/custom/runtime/timings/last_item_section_timeout.xml b/test/samples/custom/runtime/timings/last_item_section_timeout.xml
new file mode 100644
index 000000000..d8a89155e
--- /dev/null
+++ b/test/samples/custom/runtime/timings/last_item_section_timeout.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+ ChoiceB
+
+
+
+
+ 0.0
+
+
+
+
+
+
+
diff --git a/test/samples/custom/runtime/timings/last_item_testpart_timeout.xml b/test/samples/custom/runtime/timings/last_item_testpart_timeout.xml
new file mode 100644
index 000000000..7bf4f8ce4
--- /dev/null
+++ b/test/samples/custom/runtime/timings/last_item_testpart_timeout.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+ ChoiceB
+
+
+
+
+ 0.0
+
+
+
+
+
+
+
diff --git a/test/samples/custom/runtime/timings/last_item_timeout.xml b/test/samples/custom/runtime/timings/last_item_timeout.xml
new file mode 100644
index 000000000..a21f9021e
--- /dev/null
+++ b/test/samples/custom/runtime/timings/last_item_timeout.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+ ChoiceB
+
+
+
+
+ 0.0
+
+
+
+
+
+
+