Merge branch 'wip-MDL-57649-master' of git://github.com/abgreeve/moodle
authorDan Poltawski <dan@moodle.com>
Mon, 17 Jul 2017 08:01:05 +0000 (09:01 +0100)
committerDan Poltawski <dan@moodle.com>
Mon, 17 Jul 2017 08:01:05 +0000 (09:01 +0100)
26 files changed:
blocks/comments/tests/behat/behat_block_comments.php
calendar/export_execute.php
calendar/lib.php
course/tests/behat/behat_course.php
course/yui/build/moodle-course-management/moodle-course-management-debug.js
course/yui/build/moodle-course-management/moodle-course-management-min.js
course/yui/build/moodle-course-management/moodle-course-management.js
course/yui/src/management/js/console.js
grade/report/overview/db/access.php
grade/report/overview/db/upgrade.php [new file with mode: 0644]
grade/report/overview/lib.php
grade/report/overview/version.php
group/tests/behat/behat_groups.php
lib/behat/behat_base.php
lib/behat/form_field/behat_form_checkbox.php
lib/behat/form_field/behat_form_field.php
lib/behat/form_field/behat_form_select.php
lib/bennu/iCalendar_components.php
lib/bennu/readme_moodle.txt
lib/filestorage/file_storage.php
lib/filestorage/stored_file.php
lib/tests/behat/behat_forms.php
lib/tests/behat/behat_general.php
mod/assign/locallib.php
mod/quiz/classes/structure.php
mod/quiz/tests/structure_test.php

index 587695b..26f7495 100644 (file)
@@ -66,7 +66,7 @@ class behat_block_comments extends behat_base {
             $this->find_link(get_string('savecomment'))->click();
             // Delay after clicking so that additional comments will have unique time stamps.
             // We delay 1 second which is all we need.
-            $this->getSession()->wait(1000, false);
+            $this->getSession()->wait(1000);
 
         } else {
 
@@ -104,7 +104,7 @@ class behat_block_comments extends behat_base {
         );
 
         // Wait for the animation to finish, in theory is just 1 sec, adding 4 just in case.
-        $this->getSession()->wait(4 * 1000, false);
+        $this->getSession()->wait(4 * 1000);
     }
 
 }
index 9952dee..ca162a2 100644 (file)
@@ -216,10 +216,14 @@ foreach($events as $event) {
         //dtend is better than duration, because it works in Microsoft Outlook and works better in Korganizer
         $ev->add_property('dtstart', Bennu::timestamp_to_datetime($event->timestart)); // when event starts.
         $ev->add_property('dtend', Bennu::timestamp_to_datetime($event->timestart + $event->timeduration));
+    } else if ($event->timeduration == 0) {
+        // When no duration is present, the event is instantaneous event, ex - Due date of a module.
+        // Moodle doesn't support all day events yet. See MDL-56227.
+        $ev->add_property('dtstart', Bennu::timestamp_to_datetime($event->timestart));
+        $ev->add_property('dtend', Bennu::timestamp_to_datetime($event->timestart));
     } else {
-        // When no duration is present, ie an all day event, VALUE should be date instead of time and dtend = dtstart + 1 day.
-        $ev->add_property('dtstart', Bennu::timestamp_to_date($event->timestart), array('value' => 'DATE')); // All day event.
-        $ev->add_property('dtend', Bennu::timestamp_to_date($event->timestart + DAYSECS), array('value' => 'DATE')); // All day event.
+        // This can be used to represent all day events in future.
+        throw new coding_exception("Negative duration is not supported yet.");
     }
     if ($event->courseid != 0) {
         $coursecontext = context_course::instance($event->courseid);
index f6f1e4a..94e617b 100644 (file)
@@ -2944,7 +2944,8 @@ function calendar_add_icalendar_event($event, $courseid, $subscriptionid, $timez
         // Check to see if the event started at Midnight on the imported calendar.
         date_default_timezone_set($timezone);
         if (date('H:i:s', $eventrecord->timestart) === "00:00:00") {
-            // This event should be an all day event.
+            // This event should be an all day event. This is not correct, we don't do anything differently for all day events.
+            // See MDL-56227.
             $eventrecord->timeduration = 0;
         }
         \core_date::set_default_server_timezone();
index 8cdeef6..4b20f93 100644 (file)
@@ -1830,7 +1830,7 @@ class behat_course extends behat_base {
         $node->click();
 
         // Smooth expansion.
-        $this->getSession()->wait(1000, false);
+        $this->getSession()->wait(1000);
     }
 
     /**
index abda207..0bef68f 100644 (file)
Binary files a/course/yui/build/moodle-course-management/moodle-course-management-debug.js and b/course/yui/build/moodle-course-management/moodle-course-management-debug.js differ
index 1c994ed..2e524a0 100644 (file)
Binary files a/course/yui/build/moodle-course-management/moodle-course-management-min.js and b/course/yui/build/moodle-course-management/moodle-course-management-min.js differ
index 0c71169..10220c9 100644 (file)
Binary files a/course/yui/build/moodle-course-management/moodle-course-management.js and b/course/yui/build/moodle-course-management/moodle-course-management.js differ
index 633fd47..4c7c034 100644 (file)
@@ -277,7 +277,7 @@ Console.prototype = {
         if (!this.categoriesinit) {
             this.get('categorylisting').delegate('click', this.handleCategoryDelegation, 'a[data-action]', this);
             this.get('categorylisting').delegate('click', this.handleCategoryDelegation, 'input[name="bcat[]"]', this);
-            this.get('categorylisting').delegate('click', this.handleBulkSortByaction, '#menuselectsortby', this);
+            this.get('categorylisting').delegate('change', this.handleBulkSortByaction, '#menuselectsortby', this);
             this.categoriesinit = true;
             Y.log(count + ' categories being managed', 'info', 'moodle-course-management');
         } else {
index b33412c..0676152 100644 (file)
@@ -31,8 +31,7 @@ $capabilities = array(
         'captype' => 'read',
         'contextlevel' => CONTEXT_COURSE,
         'archetypes' => array(
-            'student' => CAP_ALLOW,
-            'manager' => CAP_ALLOW
+            'user' => CAP_ALLOW
         )
     )
 
diff --git a/grade/report/overview/db/upgrade.php b/grade/report/overview/db/upgrade.php
new file mode 100644 (file)
index 0000000..a331896
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Grade overview report upgrade steps.
+ *
+ * @package    gradereport_overview
+ * @copyright  2017 Simey Lameze <simey@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Function to upgrade grade overview report.
+ *
+ * @param int $oldversion the version we are upgrading from
+ * @return bool result
+ */
+function xmldb_gradereport_overview_upgrade($oldversion) {
+
+    if ($oldversion < 2017051501) {
+        $context = context_system::instance();
+        $capability = 'gradereport/overview:view';
+
+        // Now allow authenticated user role to access that report.
+        $authenticateduserroles = get_archetype_roles('user');
+        foreach ($authenticateduserroles as $roleid => $notused) {
+            assign_capability($capability, CAP_ALLOW, $roleid, $context->id);
+        }
+
+        upgrade_plugin_savepoint(true, 2017051501, 'gradereport', 'overview');
+    }
+    return true;
+}
index 6ab529e..117e7fd 100644 (file)
@@ -468,7 +468,7 @@ function gradereport_overview_myprofile_navigation(core_user\output\myprofile\tr
     $usercontext = context_user::instance($user->id);
     $coursecontext = context_course::instance($course->id);
     if (grade_report_overview::check_access($systemcontext, $coursecontext, $usercontext, $course, $user->id)) {
-        $url = new moodle_url('/grade/report/overview/index.php', array('userid' => $user->id));
+        $url = new moodle_url('/grade/report/overview/index.php', array('userid' => $user->id, 'id' => $course->id));
         $node = new core_user\output\myprofile\node('reports', 'grades', get_string('gradesoverview', 'gradereport_overview'),
                 null, $url);
         $tree->add_node($node);
index d93b45a..630f614 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2017051500;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2017051501;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2017050500;        // Requires this Moodle version
 $plugin->component = 'gradereport_overview'; // Full name of the plugin (used for diagnostics)
index d17b225..e314e68 100644 (file)
@@ -62,8 +62,11 @@ class behat_groups extends behat_base {
         $select->selectOption($fulloption);
 
         // This is needed by some drivers to ensure relevant event is triggred and button is enabled.
-        $script = "Syn.trigger('change', {}, {{ELEMENT}})";
-        $this->getSession()->getDriver()->triggerSynScript($select->getXpath(), $script);
+        $driver = $this->getSession()->getDriver();
+        if ($driver instanceof \Moodle\BehatExtension\Driver\MoodleSelenium2Driver) {
+            $script = "Syn.trigger('change', {}, {{ELEMENT}})";
+            $driver->triggerSynScript($select->getXpath(), $script);
+        }
         $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
 
         // Here we don't need to wait for the AJAX response.
index d01213b..e7c631f 100644 (file)
@@ -28,7 +28,8 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-use Behat\Mink\Exception\ExpectationException as ExpectationException,
+use Behat\Mink\Exception\DriverException,
+    Behat\Mink\Exception\ExpectationException as ExpectationException,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
     Behat\Mink\Element\NodeElement as NodeElement;
 
@@ -335,8 +336,6 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
                 if (!$exception) {
                     $exception = $e;
                 }
-                // We wait until no exception is thrown or timeout expires.
-                continue;
             }
 
             if ($this->running_javascript()) {
@@ -727,7 +726,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
             $pending = '';
             try {
                 $jscode = '
-                    return function() {
+                    return (function() {
                         if (typeof M === "undefined") {
                             if (document.readyState === "complete") {
                                 return "";
@@ -741,7 +740,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
                         } else {
                             return "incomplete"
                         }
-                    }();';
+                    }());';
                 $pending = $this->getSession()->evaluateScript($jscode);
             } catch (NoSuchWindow $nsw) {
                 // We catch an exception here, in case we just closed the window we were interacting with.
@@ -883,6 +882,8 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
 
         } catch (NoSuchWindow $e) {
             // If we were interacting with a popup window it will not exists after closing it.
+        } catch (DriverException $e) {
+            // Same reason as above.
         }
     }
 
@@ -959,7 +960,12 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
         }
         $this->ensure_node_is_visible($node); // Ensures hidden elements can't be clicked.
         $xpath = $node->getXpath();
-        $script = "Syn.click({{ELEMENT}})";
-        $this->getSession()->getDriver()->triggerSynScript($xpath, $script);
+        $driver = $this->getSession()->getDriver();
+        if ($driver instanceof \Moodle\BehatExtension\Driver\MoodleSelenium2Driver) {
+            $script = "Syn.click({{ELEMENT}})";
+            $driver->triggerSynScript($xpath, $script);
+        } else {
+            $driver->click($xpath);
+        }
     }
 }
index dc568b0..5cd624a 100644 (file)
@@ -108,9 +108,12 @@ class behat_form_checkbox extends behat_form_field {
      * Trigger on change event.
      */
     protected function trigger_on_change() {
-        $this->session->getDriver()->triggerSynScript(
-            $this->field->getXPath(),
-            "Syn.trigger('change', {}, {{ELEMENT}})"
-        );
+        $driver = $this->session->getDriver();
+        if ($driver instanceof \Moodle\BehatExtension\Driver\MoodleSelenium2Driver) {
+            $driver->triggerSynScript(
+                $this->field->getXPath(),
+                "Syn.trigger('change', {}, {{ELEMENT}})"
+            );
+        }
     }
 }
index f7f5b9e..56d7124 100644 (file)
@@ -114,6 +114,8 @@ class behat_form_field {
             // If the JS handler attached to keydown or keypress destroys the element
             // the later events may trigger errors because form element no longer exist
             // or is not visible. Ignore such exceptions here.
+        } catch (\Behat\Mink\Exception\ElementNotFoundException $e) {
+            // Other Mink drivers can throw this for the same reason as above.
         }
     }
 
index 0b16552..6e359fd 100644 (file)
@@ -78,8 +78,11 @@ class behat_form_select extends behat_form_field {
                 if (!$node = $this->session->getDriver()->find($dialoguexpath)) {
                     $script = "Syn.trigger('change', {}, {{ELEMENT}})";
                     try {
-                        $this->session->getDriver()->triggerSynScript($this->field->getXpath(), $script);
-                        $this->session->getDriver()->click('//body//div[@class="skiplinks"]');
+                        $driver = $this->session->getDriver();
+                        if ($driver instanceof \Moodle\BehatExtension\Driver\MoodleSelenium2Driver) {
+                            $driver->triggerSynScript($this->field->getXpath(), $script);
+                        }
+                        $driver->click('//body//div[@class="skiplinks"]');
                     } catch (\Exception $e) {
                         return;
                     }
index 9b9e313..83a1e5f 100644 (file)
@@ -411,7 +411,7 @@ class iCalendar_event extends iCalendar_component {
             // DTEND must be later than DTSTART
             // The standard is not clear on how to hande different value types though
             // TODO: handle this correctly even if the value types are different
-            if($this->properties['DTEND'][0]->value <= $this->properties['DTSTART'][0]->value) {
+            if($this->properties['DTEND'][0]->value < $this->properties['DTSTART'][0]->value) {
                 return false;
             }
 
index e33a956..845664c 100644 (file)
@@ -8,3 +8,4 @@ modifications:
 5/ updated DTEND;TZID and DTSTAR;TZID values to support quotations (7 Nov 2014)
 6/ MDL-49032: fixed rfc2445_fold() to fix incorrect RFC2445_WSP definition (16 Sep 2015)
 7/ added timestamp_to_date function to support zero duration events (16 Sept 2015)
+8/ Updated \iCalendar_event::invariant_holds() to allow for same dtstart and dtend timestamps (13 July 2017)
index f42b6e5..4e878f8 100644 (file)
@@ -1020,6 +1020,27 @@ class file_storage {
         return $dir_info;
     }
 
+    /**
+     * Add new file record to database and handle callbacks.
+     *
+     * @param stdClass $newrecord
+     */
+    protected function create_file($newrecord) {
+        global $DB;
+        $newrecord->id = $DB->insert_record('files', $newrecord);
+
+        if ($newrecord->filename !== '.') {
+            // Callback for file created.
+            if ($pluginsfunction = get_plugins_with_function('after_file_created')) {
+                foreach ($pluginsfunction as $plugintype => $plugins) {
+                    foreach ($plugins as $pluginfunction) {
+                        $pluginfunction($newrecord);
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Add new local file based on existing local file.
      *
@@ -1134,7 +1155,7 @@ class file_storage {
         }
 
         try {
-            $newrecord->id = $DB->insert_record('files', $newrecord);
+            $this->create_file($newrecord);
         } catch (dml_exception $e) {
             throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid,
                                                      $newrecord->filepath, $newrecord->filename, $e->debuginfo);
@@ -1302,7 +1323,7 @@ class file_storage {
         $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
 
         try {
-            $newrecord->id = $DB->insert_record('files', $newrecord);
+            $this->create_file($newrecord);
         } catch (dml_exception $e) {
             if ($newfile) {
                 $this->move_to_trash($newrecord->contenthash);
@@ -1421,7 +1442,7 @@ class file_storage {
         $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
 
         try {
-            $newrecord->id = $DB->insert_record('files', $newrecord);
+            $this->create_file($newrecord);
         } catch (dml_exception $e) {
             if ($newfile) {
                 $this->move_to_trash($newrecord->contenthash);
index 9655d01..e1c3e73 100644 (file)
@@ -139,6 +139,7 @@ class stored_file {
         global $DB;
         $updatereferencesneeded = false;
         $keys = array_keys((array)$this->file_record);
+        $filepreupdate = clone($this->file_record);
         foreach ($dataobject as $field => $value) {
             if (in_array($field, $keys)) {
                 if ($field == 'contextid' and (!is_number($value) or $value < 1)) {
@@ -216,6 +217,17 @@ class stored_file {
             // Either filesize or contenthash of this file have changed. Update all files that reference to it.
             $this->fs->update_references_to_storedfile($this);
         }
+
+        // Callback for file update.
+        if (!$this->is_directory()) {
+            if ($pluginsfunction = get_plugins_with_function('after_file_updated')) {
+                foreach ($pluginsfunction as $plugintype => $plugins) {
+                    foreach ($plugins as $pluginfunction) {
+                        $pluginfunction($this->file_record, $filepreupdate);
+                    }
+                }
+            }
+        }
     }
 
     /**
@@ -375,6 +387,17 @@ class stored_file {
             $DB->delete_records('files', array('id'=>$this->file_record->id));
 
             $transaction->allow_commit();
+
+            if (!$this->is_directory()) {
+                // Callback for file deletion.
+                if ($pluginsfunction = get_plugins_with_function('after_file_deleted')) {
+                    foreach ($pluginsfunction as $plugintype => $plugins) {
+                        foreach ($plugins as $pluginfunction) {
+                            $pluginfunction($this->file_record);
+                        }
+                    }
+                }
+            }
         }
 
         // Move pool file to trash if content not needed any more.
@@ -815,9 +838,20 @@ class stored_file {
      * @return int
      */
     public function set_sortorder($sortorder) {
+        $oldorder = $this->file_record->sortorder;
         $filerecord = new stdClass;
         $filerecord->sortorder = $sortorder;
         $this->update($filerecord);
+        if (!$this->is_directory()) {
+            // Callback for file sort order change.
+            if ($pluginsfunction = get_plugins_with_function('after_file_sorted')) {
+                foreach ($pluginsfunction as $plugintype => $plugins) {
+                    foreach ($plugins as $pluginfunction) {
+                        $pluginfunction($this->file_record, $oldorder, $sortorder);
+                    }
+                }
+            }
+        }
     }
 
     /**
index 229b5c7..f7e09b3 100644 (file)
@@ -148,13 +148,20 @@ class behat_forms extends behat_base {
                 return;
             }
 
-            // Funny thing about this, with findAll() we specify a pattern and each element matching the pattern is added to the array
-            // with of xpaths with a [0], [1]... sufix, but when we click on an element it does not matches the specified xpath
-            // anymore (now is a "Show less..." link) so [1] becomes [0], that's why we always click on the first XPath match,
-            // will be always the next one.
-            $iterations = count($showmores);
-            for ($i = 0; $i < $iterations; $i++) {
-                $showmores[0]->click();
+            if ($this->getSession()->getDriver() instanceof \DMore\ChromeDriver\ChromeDriver) {
+                // Chrome Driver produces unique xpaths for each element.
+                foreach ($showmores as $showmore) {
+                    $showmore->click();
+                }
+            } else {
+                // Funny thing about this, with findAll() we specify a pattern and each element matching the pattern
+                // is added to the array with of xpaths with a [0], [1]... sufix, but when we click on an element it
+                // does not matches the specified xpath anymore (now is a "Show less..." link) so [1] becomes [0],
+                // that's why we always click on the first XPath match, will be always the next one.
+                $iterations = count($showmores);
+                for ($i = 0; $i < $iterations; $i++) {
+                    $showmores[0]->click();
+                }
             }
 
         } catch (ElementNotFoundException $e) {
index 0adb30f..749540d 100644 (file)
@@ -143,7 +143,7 @@ class behat_general extends behat_base {
 
         // Wait until the URL change is executed.
         if ($this->running_javascript()) {
-            $this->getSession()->wait($waittime * 1000, false);
+            $this->getSession()->wait($waittime * 1000);
 
         } else if (!empty($url)) {
             // We redirect directly as we can not wait for an automatic redirection.
@@ -253,7 +253,7 @@ class behat_general extends behat_base {
      */
     public function i_wait_seconds($seconds) {
         if ($this->running_javascript()) {
-            $this->getSession()->wait($seconds * 1000, false);
+            $this->getSession()->wait($seconds * 1000);
         } else {
             sleep($seconds);
         }
@@ -634,6 +634,9 @@ class behat_general extends behat_base {
                     } catch (WebDriver\Exception\NoSuchElement $e) {
                         // Do nothing just return, as element is no more on page.
                         return true;
+                    } catch (ElementNotFoundException $e) {
+                        // Do nothing just return, as element is no more on page.
+                        return true;
                     }
                 }
 
@@ -1610,7 +1613,12 @@ class behat_general extends behat_base {
         }
         // Gets the node based on the requested selector type and locator.
         $node = $this->get_selected_node($selectortype, $element);
-        $this->getSession()->getDriver()->post_key("\xEE\x80\x84", $node->getXpath());
+        $driver = $this->getSession()->getDriver();
+        if ($driver instanceof \Moodle\BehatExtension\Driver\MoodleSelenium2Driver) {
+            $driver->post_key("\xEE\x80\x84", $node->getXpath());
+        } else {
+            $driver->keyDown($node->getXpath(), "\t");
+        }
     }
 
     /**
index cebdddc..5a16442 100644 (file)
@@ -2329,7 +2329,7 @@ class assign {
                 $uniqueid = 0;
                 if ($submission->blindmarking && !$submission->revealidentities) {
                     if (empty($submission->recordid)) {
-                        $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id);
+                        $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $grader->id);
                     } else {
                         $uniqueid = $submission->recordid;
                     }
index ba7f3a3..aae5c24 100644 (file)
@@ -715,7 +715,8 @@ class structure {
         }
 
         $followingslotnumber = $moveafterslotnumber + 1;
-        if ($followingslotnumber == $movingslotnumber) {
+        // Prevent checking against non-existance slot when already at the last slot.
+        if ($followingslotnumber == $movingslotnumber && !$this->is_last_slot_in_quiz($followingslotnumber)) {
             $followingslotnumber += 1;
         }
 
index dd13daa..9874635 100644 (file)
@@ -315,6 +315,24 @@ class mod_quiz_structure_testcase extends advanced_testcase {
             ), $structure);
     }
 
+    public function test_move_last_slot_to_previous_page_emptying_the_last_page() {
+        $quizobj = $this->create_test_quiz(array(
+                array('TF1', 1, 'truefalse'),
+                array('TF2', 2, 'truefalse'),
+            ));
+        $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+
+        $idtomove = $structure->get_question_in_slot(2)->slotid;
+        $idmoveafter = $structure->get_question_in_slot(1)->slotid;
+        $structure->move_slot($idtomove, $idmoveafter, '1');
+
+        $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+        $this->assert_quiz_layout(array(
+                array('TF1', 1, 'truefalse'),
+                array('TF2', 1, 'truefalse'),
+            ), $structure);
+    }
+
     public function test_end_of_one_section_to_start_of_next() {
         $quizobj = $this->create_test_quiz(array(
                 array('TF1', 1, 'truefalse'),