diff --git a/course/format/amd/build/local/courseindex/courseindex.min.js b/course/format/amd/build/local/courseindex/courseindex.min.js index 27b49461a36..2343e6c3fa0 100644 --- a/course/format/amd/build/local/courseindex/courseindex.min.js +++ b/course/format/amd/build/local/courseindex/courseindex.min.js @@ -1,2 +1,2 @@ -define ("core_courseformat/local/courseindex/courseindex",["exports","core/reactive","core_courseformat/courseeditor"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function d(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){d=function(a){return typeof a}}else{d=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return d(a)}function e(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function f(a){return function(){var b=this,c=arguments;return new Promise(function(d,f){var i=a.apply(b,c);function g(a){e(i,d,f,g,h,"next",a)}function h(a){e(i,d,f,g,h,"throw",a)}g(void 0)})}}function g(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function h(a,b){for(var c=0,d;cb.length){a.removeChild(a.lastChild)}}},{key:"_deleteCm",value:function _deleteCm(a){var b=a.element;delete this.cms[b.id]}}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=q;return a.default}); +define ("core_courseformat/local/courseindex/courseindex",["exports","core/reactive","core_courseformat/courseeditor"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function d(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){d=function(a){return typeof a}}else{d=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return d(a)}function e(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function f(a){return function(){var b=this,c=arguments;return new Promise(function(d,f){var i=a.apply(b,c);function g(a){e(i,d,f,g,h,"next",a)}function h(a){e(i,d,f,g,h,"throw",a)}g(void 0)})}}function g(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function h(a,b){for(var c=0,d;cb.length){a.removeChild(a.lastChild)}}},{key:"_deleteCm",value:function _deleteCm(a){var b=a.element;delete this.cms[b.id]}}],[{key:"init",value:function init(a,d){return new b({element:document.getElementById(a),reactive:(0,c.getCurrentCourseEditor)(),selectors:d})}}]);return b}(b.BaseComponent);a.default=q;return a.default}); //# sourceMappingURL=courseindex.min.js.map diff --git a/course/format/amd/build/local/courseindex/courseindex.min.js.map b/course/format/amd/build/local/courseindex/courseindex.min.js.map index 9394481ed78..3e11ef4827f 100644 --- a/course/format/amd/build/local/courseindex/courseindex.min.js.map +++ b/course/format/amd/build/local/courseindex/courseindex.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/courseindex/courseindex.js"],"names":["Component","name","selectors","SECTION","SECTION_ITEM","SECTION_TITLE","SECTION_CMLIST","CM","CM_NAME","TOGGLER","COLLAPSE","classes","SECTIONHIDDEN","CMHIDDEN","SECTIONCURRENT","sections","cms","addEventListener","element","_setupSectionTogglers","getElements","forEach","section","dataset","id","cm","watch","handler","_refreshSection","_refreshCm","_createCm","_deleteCm","_refreshCourseSectionlist","_refreshSectionCmlist","event","sectionlink","target","closest","preventDefault","parentNode","querySelector","click","Error","sectionitem","classList","toggle","visible","current","innerHTML","title","state","fakeelement","document","createElement","add","get","sectionid","exporter","reactive","getExporter","data","renderComponent","newcomponent","newelement","getElement","replaceChild","cmlist","listparent","_fixOrder","sectionlist","container","neworder","allitems","length","remove","itemid","index","item","currentitem","children","append","insertBefore","removeChild","lastChild","getElementById","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/courseindex/courseindex\n * @class core_courseformat/local/courseindex/courseindex\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_ITEM: `[data-for='section_item']`,\n SECTION_TITLE: `[data-for='section_title']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n CM: `[data-for='cm']`,\n CM_NAME: `[data-for='cm_name']`,\n TOGGLER: `[data-action=\"togglecourseindexsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n CMHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n };\n // Arrays to keep cms and sections elements.\n this.sections = {};\n this.cms = {};\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._setupSectionTogglers);\n\n // Get cms and sections elements.\n const sections = this.getElements(this.selectors.SECTION);\n sections.forEach((section) => {\n this.sections[section.dataset.id] = section;\n });\n const cms = this.getElements(this.selectors.CM);\n cms.forEach((cm) => {\n this.cms[cm.dataset.id] = cm;\n });\n }\n\n getWatchers() {\n return [\n {watch: `section:updated`, handler: this._refreshSection},\n {watch: `cm:updated`, handler: this._refreshCm},\n {watch: `cm:created`, handler: this._createCm},\n {watch: `cm:deleted`, handler: this._deleteCm},\n // Sections and cm sorting.\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n ];\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course index element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _setupSectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n if (sectionlink) {\n event.preventDefault();\n sectionlink.parentNode.querySelector(this.selectors.COLLAPSE).click();\n }\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {Object} details the update details.\n */\n _refreshSection({element}) {\n // Find the element.\n const target = this.sections[element.id];\n if (!target) {\n throw new Error(`Unkown section with ID ${element.id}`);\n }\n // Update classes.\n const sectionitem = target.querySelector(this.selectors.SECTION_ITEM);\n sectionitem.classList.toggle(this.classes.SECTIONHIDDEN, !element.visible);\n target.classList.toggle(this.classes.SECTIONCURRENT, element.current);\n // Update title.\n target.querySelector(this.selectors.SECTION_TITLE).innerHTML = element.title;\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {Object} details the update details.\n */\n _refreshCm({element}) {\n // Find the element.\n const target = this.cms[element.id];\n if (!target) {\n throw new Error(`Unkown course module with ID ${element.id}`);\n }\n // Update classes.\n target.classList.toggle(this.classes.CMHIDDEN, !element.visible);\n target.querySelector(this.selectors.CM_NAME).innerHTML = element.name;\n }\n\n /**\n * Create a newcm instance.\n *\n * @param {Object} details the update details.\n */\n async _createCm({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('li');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.cms[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshSectionCmlist({\n state,\n element: state.section.get(element.sectionid),\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.cm(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/cm', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.cms[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {Object} details the update details.\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const listparent = this.getElement(this.selectors.SECTION_CMLIST, element.id);\n this._fixOrder(listparent, cmlist, this.cms);\n }\n\n /**\n * Refresh the section list.\n *\n * @param {Object} details the update details.\n */\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n this._fixOrder(this.element, sectionlist, this.sections);\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {Array} allitems the list of html elements that can be placed in the container\n */\n _fixOrder(container, neworder, allitems) {\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n const item = allitems[itemid];\n // Get the current element at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n container.removeChild(container.lastChild);\n }\n }\n\n /**\n * Remove a cm from the list.\n *\n * The actual DOM element removal is delegated to the cm component.\n *\n * @param {Object} details the update details.\n */\n _deleteCm({element}) {\n delete this.cms[element.id];\n }\n}\n"],"file":"courseindex.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/courseindex/courseindex.js"],"names":["Component","name","selectors","SECTION","SECTION_ITEM","SECTION_TITLE","SECTION_CMLIST","CM","CM_NAME","TOGGLER","COLLAPSE","classes","SECTIONHIDDEN","CMHIDDEN","SECTIONCURRENT","COLLAPSED","sections","cms","addEventListener","element","_setupSectionTogglers","getElements","forEach","section","dataset","id","cm","watch","handler","_refreshSection","_refreshCm","_createCm","_deleteCm","_refreshCourseSectionlist","_refreshSectionCmlist","event","sectionlink","target","closest","toggler","parentNode","querySelector","classList","contains","click","Error","sectionitem","toggle","visible","current","innerHTML","title","state","fakeelement","document","createElement","add","get","sectionid","exporter","reactive","getExporter","data","renderComponent","newcomponent","newelement","getElement","replaceChild","cmlist","listparent","_fixOrder","sectionlist","container","neworder","allitems","length","remove","itemid","index","item","currentitem","children","append","insertBefore","removeChild","lastChild","getElementById","BaseComponent"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Course index main component.\n *\n * @module core_courseformat/local/courseindex/courseindex\n * @class core_courseformat/local/courseindex/courseindex\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent} from 'core/reactive';\nimport {getCurrentCourseEditor} from 'core_courseformat/courseeditor';\n\nexport default class Component extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'courseindex';\n // Default query selectors.\n this.selectors = {\n SECTION: `[data-for='section']`,\n SECTION_ITEM: `[data-for='section_item']`,\n SECTION_TITLE: `[data-for='section_title']`,\n SECTION_CMLIST: `[data-for='cmlist']`,\n CM: `[data-for='cm']`,\n CM_NAME: `[data-for='cm_name']`,\n TOGGLER: `[data-action=\"togglecourseindexsection\"]`,\n COLLAPSE: `[data-toggle=\"collapse\"]`,\n };\n // Default classes to toggle on refresh.\n this.classes = {\n SECTIONHIDDEN: 'dimmed',\n CMHIDDEN: 'dimmed',\n SECTIONCURRENT: 'current',\n COLLAPSED: `collapsed`,\n };\n // Arrays to keep cms and sections elements.\n this.sections = {};\n this.cms = {};\n }\n\n /**\n * Static method to create a component instance form the mustache template.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n * @return {Component}\n */\n static init(target, selectors) {\n return new Component({\n element: document.getElementById(target),\n reactive: getCurrentCourseEditor(),\n selectors,\n });\n }\n\n /**\n * Initial state ready method.\n */\n stateReady() {\n // Activate section togglers.\n this.addEventListener(this.element, 'click', this._setupSectionTogglers);\n\n // Get cms and sections elements.\n const sections = this.getElements(this.selectors.SECTION);\n sections.forEach((section) => {\n this.sections[section.dataset.id] = section;\n });\n const cms = this.getElements(this.selectors.CM);\n cms.forEach((cm) => {\n this.cms[cm.dataset.id] = cm;\n });\n }\n\n getWatchers() {\n return [\n {watch: `section:updated`, handler: this._refreshSection},\n {watch: `cm:updated`, handler: this._refreshCm},\n {watch: `cm:created`, handler: this._createCm},\n {watch: `cm:deleted`, handler: this._deleteCm},\n // Sections and cm sorting.\n {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},\n {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},\n ];\n }\n\n /**\n * Setup sections toggler.\n *\n * Toggler click is delegated to the main course index element because new sections can\n * appear at any moment and this way we prevent accidental double bindings.\n *\n * @param {Event} event the triggered event\n */\n _setupSectionTogglers(event) {\n const sectionlink = event.target.closest(this.selectors.TOGGLER);\n if (sectionlink) {\n const toggler = sectionlink.parentNode.querySelector(this.selectors.COLLAPSE);\n if (toggler?.classList.contains(this.classes.COLLAPSED)) {\n toggler.click();\n }\n }\n }\n\n /**\n * Update a course index section using the state information.\n *\n * @param {Object} details the update details.\n */\n _refreshSection({element}) {\n // Find the element.\n const target = this.sections[element.id];\n if (!target) {\n throw new Error(`Unkown section with ID ${element.id}`);\n }\n // Update classes.\n const sectionitem = target.querySelector(this.selectors.SECTION_ITEM);\n sectionitem.classList.toggle(this.classes.SECTIONHIDDEN, !element.visible);\n target.classList.toggle(this.classes.SECTIONCURRENT, element.current);\n // Update title.\n target.querySelector(this.selectors.SECTION_TITLE).innerHTML = element.title;\n }\n\n /**\n * Update a course index cm using the state information.\n *\n * @param {Object} details the update details.\n */\n _refreshCm({element}) {\n // Find the element.\n const target = this.cms[element.id];\n if (!target) {\n throw new Error(`Unkown course module with ID ${element.id}`);\n }\n // Update classes.\n target.classList.toggle(this.classes.CMHIDDEN, !element.visible);\n target.querySelector(this.selectors.CM_NAME).innerHTML = element.name;\n }\n\n /**\n * Create a newcm instance.\n *\n * @param {Object} details the update details.\n */\n async _createCm({state, element}) {\n // Create a fake node while the component is loading.\n const fakeelement = document.createElement('li');\n fakeelement.classList.add('bg-pulse-grey', 'w-100');\n fakeelement.innerHTML = ' ';\n this.cms[element.id] = fakeelement;\n // Place the fake node on the correct position.\n this._refreshSectionCmlist({\n state,\n element: state.section.get(element.sectionid),\n });\n // Collect render data.\n const exporter = this.reactive.getExporter();\n const data = exporter.cm(state, element);\n // Create the new content.\n const newcomponent = await this.renderComponent(fakeelement, 'core_courseformat/local/courseindex/cm', data);\n // Replace the fake node with the real content.\n const newelement = newcomponent.getElement();\n this.cms[element.id] = newelement;\n fakeelement.parentNode.replaceChild(newelement, fakeelement);\n }\n\n /**\n * Refresh a section cm list.\n *\n * @param {Object} details the update details.\n */\n _refreshSectionCmlist({element}) {\n const cmlist = element.cmlist ?? [];\n const listparent = this.getElement(this.selectors.SECTION_CMLIST, element.id);\n this._fixOrder(listparent, cmlist, this.cms);\n }\n\n /**\n * Refresh the section list.\n *\n * @param {Object} details the update details.\n */\n _refreshCourseSectionlist({element}) {\n const sectionlist = element.sectionlist ?? [];\n this._fixOrder(this.element, sectionlist, this.sections);\n }\n\n /**\n * Fix/reorder the section or cms order.\n *\n * @param {Element} container the HTML element to reorder.\n * @param {Array} neworder an array with the ids order\n * @param {Array} allitems the list of html elements that can be placed in the container\n */\n _fixOrder(container, neworder, allitems) {\n\n // Empty lists should not be visible.\n if (!neworder.length) {\n container.classList.add('hidden');\n container.innerHTML = '';\n return;\n }\n\n // Grant the list is visible (in case it was empty).\n container.classList.remove('hidden');\n\n // Move the elements in order at the beginning of the list.\n neworder.forEach((itemid, index) => {\n const item = allitems[itemid];\n // Get the current element at that position.\n const currentitem = container.children[index];\n if (currentitem === undefined) {\n container.append(item);\n return;\n }\n if (currentitem !== item) {\n container.insertBefore(item, currentitem);\n }\n });\n // Remove the remaining elements.\n while (container.children.length > neworder.length) {\n container.removeChild(container.lastChild);\n }\n }\n\n /**\n * Remove a cm from the list.\n *\n * The actual DOM element removal is delegated to the cm component.\n *\n * @param {Object} details the update details.\n */\n _deleteCm({element}) {\n delete this.cms[element.id];\n }\n}\n"],"file":"courseindex.min.js"} \ No newline at end of file diff --git a/course/format/amd/src/local/courseindex/courseindex.js b/course/format/amd/src/local/courseindex/courseindex.js index 464f7f0878d..28dfd18387f 100644 --- a/course/format/amd/src/local/courseindex/courseindex.js +++ b/course/format/amd/src/local/courseindex/courseindex.js @@ -49,6 +49,7 @@ export default class Component extends BaseComponent { SECTIONHIDDEN: 'dimmed', CMHIDDEN: 'dimmed', SECTIONCURRENT: 'current', + COLLAPSED: `collapsed`, }; // Arrays to keep cms and sections elements. this.sections = {}; @@ -111,8 +112,10 @@ export default class Component extends BaseComponent { _setupSectionTogglers(event) { const sectionlink = event.target.closest(this.selectors.TOGGLER); if (sectionlink) { - event.preventDefault(); - sectionlink.parentNode.querySelector(this.selectors.COLLAPSE).click(); + const toggler = sectionlink.parentNode.querySelector(this.selectors.COLLAPSE); + if (toggler?.classList.contains(this.classes.COLLAPSED)) { + toggler.click(); + } } } diff --git a/course/format/tests/behat/course_courseindex.feature b/course/format/tests/behat/course_courseindex.feature index 1a6162f3231..6f3943cb1b8 100644 --- a/course/format/tests/behat/course_courseindex.feature +++ b/course/format/tests/behat/course_courseindex.feature @@ -112,20 +112,47 @@ Feature: Course index depending on role And I am on "Course 1" course homepage And I click on "Side panel" "button" When I click on "Open course index drawer" "button" + # Sections should be opened by default. Then I should see "Topic 1" in the "courseindex-content" "region" And I should see "Activity sample 1" in the "courseindex-content" "region" And I should see "Second activity in section 1" in the "courseindex-content" "region" - And I click on "Topic 1" "link" in the "courseindex-content" "region" + And I should see "Topic 2" in the "courseindex-content" "region" + And I should see "Activity sample 2" in the "courseindex-content" "region" + And I should see "Topic 3" in the "courseindex-content" "region" + And I should see "Activity sample 3" in the "courseindex-content" "region" + # Collapse a section 1 via chevron. + And I click on "Collapse" "link" in the ".courseindex-section[data-number='1']" "css_element" + And I should see "Topic 1" in the "courseindex-content" "region" And I should not see "Activity sample 1" in the "courseindex-content" "region" And I should not see "Second activity in section 1" in the "courseindex-content" "region" And I should see "Topic 2" in the "courseindex-content" "region" And I should see "Activity sample 2" in the "courseindex-content" "region" - And I click on "Topic 2" "link" in the "courseindex-content" "region" + And I should see "Topic 3" in the "courseindex-content" "region" + And I should see "Activity sample 3" in the "courseindex-content" "region" + # Uncollapse section 1 via Topic name. + And I click on "Topic 1" "link" in the "courseindex-content" "region" + And I should see "Topic 1" in the "courseindex-content" "region" + And I should see "Activity sample 1" in the "courseindex-content" "region" + And I should see "Second activity in section 1" in the "courseindex-content" "region" + And I should see "Topic 2" in the "courseindex-content" "region" + And I should see "Activity sample 2" in the "courseindex-content" "region" + And I should see "Topic 3" in the "courseindex-content" "region" + And I should see "Activity sample 3" in the "courseindex-content" "region" + # Collapse a section 2 via chevron. + And I click on "Collapse" "link" in the ".courseindex-section[data-number='2']" "css_element" + And I should see "Topic 1" in the "courseindex-content" "region" + And I should see "Activity sample 1" in the "courseindex-content" "region" + And I should see "Second activity in section 1" in the "courseindex-content" "region" + And I should see "Topic 2" in the "courseindex-content" "region" And I should not see "Activity sample 2" in the "courseindex-content" "region" And I should see "Topic 3" in the "courseindex-content" "region" And I should see "Activity sample 3" in the "courseindex-content" "region" - And I click on "Topic 3" "link" in the "courseindex-content" "region" - And I should not see "Activity sample 3" in the "courseindex-content" "region" - And I should not see "Activity sample 1" in the "courseindex-content" "region" - And I should not see "Second activity in section 1" in the "courseindex-content" "region" - And I should not see "Activity sample 2" in the "courseindex-content" "region" + # Uncollapse section 2 via chevron. + And I click on "Expand" "link" in the ".courseindex-section[data-number='2']" "css_element" + And I should see "Topic 1" in the "courseindex-content" "region" + And I should see "Activity sample 1" in the "courseindex-content" "region" + And I should see "Second activity in section 1" in the "courseindex-content" "region" + And I should see "Topic 2" in the "courseindex-content" "region" + And I should see "Activity sample 2" in the "courseindex-content" "region" + And I should see "Topic 3" in the "courseindex-content" "region" + And I should see "Activity sample 3" in the "courseindex-content" "region"