Commit b5632bef by Xavier Antoviaque

Rename `<quizz>` into `<mcq>` for consistency with upcoming `<mrq>`

Also preserve `<quizz>` binding for compatibility. Split quizz/MCQ
blocks into individual python files
parent 85b2708d
...@@ -6,7 +6,7 @@ This XBlock allows to automate the workflow of real-life mentoring, within an ed ...@@ -6,7 +6,7 @@ This XBlock allows to automate the workflow of real-life mentoring, within an ed
It supports: It supports:
* **Free-form answers** (textarea) which can be shared accross different XBlock instances (for example, to remind a student of an answer he gave before). Editable or read-only. * **Free-form answers** (textarea) which can be shared accross different XBlock instances (for example, to remind a student of an answer he gave before). Editable or read-only.
* **Self-assessment quizzes** (multiple choice), to display predetermined feedback to a student based on his choices in the self-assessment. Supports rating scales and arbitrary answers. * **Self-assessment MCQs** (multiple choice), to display predetermined feedback to a student based on his choices in the self-assessment. Supports rating scales and arbitrary answers.
* **Progression tracking**, allowing to check that the student has completed the previous steps before allowing to complete a given XBlock instance. Provides a link to the next step to the student. * **Progression tracking**, allowing to check that the student has completed the previous steps before allowing to complete a given XBlock instance. Provides a link to the next step to the student.
* **Tables**, which allow to present answers from the student to free-form answers in a concise way. Supports custom headers. * **Tables**, which allow to present answers from the student to free-form answers in a concise way. Supports custom headers.
* **Data export**, to allow course authors to download a CSV file containing the free-form answers entered by the students * **Data export**, to allow course authors to download a CSV file containing the free-form answers entered by the students
...@@ -44,12 +44,12 @@ Second XBlock instance: ...@@ -44,12 +44,12 @@ Second XBlock instance:
</mentoring> </mentoring>
``` ```
### Self-assessment quizzes ### Self-assessment MCQs
```xml ```xml
<mentoring url_name="quizz_1" enforce_dependency="false"> <mentoring url_name="mcq_1" enforce_dependency="false">
<quizz name="quizz_1_1" type="choices"> <mcq name="mcq_1_1" type="choices">
<question>Do you like this quizz?</question> <question>Do you like this MCQ?</question>
<choice value="yes">Yes</choice> <choice value="yes">Yes</choice>
<choice value="maybenot">Maybe not</choice> <choice value="maybenot">Maybe not</choice>
<choice value="understand">I don't understand</choice> <choice value="understand">I don't understand</choice>
...@@ -57,16 +57,16 @@ Second XBlock instance: ...@@ -57,16 +57,16 @@ Second XBlock instance:
<tip display="yes">Great!</tip> <tip display="yes">Great!</tip>
<tip reject="maybenot">Ah, damn.</tip> <tip reject="maybenot">Ah, damn.</tip>
<tip reject="understand"><html><div id="test-custom-html">Really?</div></html></tip> <tip reject="understand"><html><div id="test-custom-html">Really?</div></html></tip>
</quizz> </mcq>
<quizz name="quizz_1_2" type="rating" low="Not good at all" high="Extremely good"> <mcq name="mcq_1_2" type="rating" low="Not good at all" high="Extremely good">
<question>How much do you rate this quizz?</question> <question>How much do you rate this MCQ?</question>
<choice value="notwant">I don't want to rate it</choice> <choice value="notwant">I don't want to rate it</choice>
<tip display="4,5">I love good grades.</tip> <tip display="4,5">I love good grades.</tip>
<tip reject="1,2,3">Will do better next time...</tip> <tip reject="1,2,3">Will do better next time...</tip>
<tip reject="notwant">Your loss!</tip> <tip reject="notwant">Your loss!</tip>
</quizz> </MCQ>
<message type="completed"> <message type="completed">
All is good now... All is good now...
......
from .answer import AnswerBlock from .answer import AnswerBlock
from .choice import ChoiceBlock
from .dataexport import MentoringDataExportBlock from .dataexport import MentoringDataExportBlock
from .html import HTMLBlock from .html import HTMLBlock
from .quizz import QuizzBlock, QuizzChoiceBlock, QuizzTipBlock from .mcq import MCQBlock
from .mentoring import MentoringBlock from .mentoring import MentoringBlock
from .message import MentoringMessageBlock from .message import MentoringMessageBlock
from .table import MentoringTableBlock, MentoringTableColumnBlock, MentoringTableColumnHeaderBlock from .table import MentoringTableBlock, MentoringTableColumnBlock, MentoringTableColumnHeaderBlock
from .tip import TipBlock
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 Harvard
#
# Authors:
# Xavier Antoviaque <xavier@antoviaque.org>
#
# This software's license gives you freedom; you can copy, convey,
# propagate, redistribute and/or modify this program under the terms of
# the GNU Affero General Public License (AGPL) as published by the Free
# Software Foundation (FSF), either version 3 of the License, or (at your
# option) any later version of the AGPL published by the FSF.
#
# This program 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 Affero
# General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
#
# Imports ###########################################################
import logging
from .light_children import LightChild, Scope, String
# Globals ###########################################################
log = logging.getLogger(__name__)
# Classes ###########################################################
class ChoiceBlock(LightChild):
"""
Custom choice of an answer for a MCQ/MRQ
"""
value = String(help="Value of the choice when selected", scope=Scope.content, default="")
content = String(help="Human-readable version of the choice value", scope=Scope.content, default="")
...@@ -27,6 +27,8 @@ import logging ...@@ -27,6 +27,8 @@ import logging
from xblock.fragment import Fragment from xblock.fragment import Fragment
from .choice import ChoiceBlock
from .tip import TipBlock
from .light_children import LightChild, Scope, String from .light_children import LightChild, Scope, String
from .utils import render_template from .utils import render_template
...@@ -36,32 +38,18 @@ from .utils import render_template ...@@ -36,32 +38,18 @@ from .utils import render_template
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# Functions #########################################################
def commas_to_list(commas_str):
"""
Converts a comma-separated string to a list
"""
if commas_str is None:
return None # Means default value (which can be non-empty)
elif commas_str == '':
return [] # Means empty list
else:
return commas_str.split(',')
# Classes ########################################################### # Classes ###########################################################
class QuizzBlock(LightChild): class MCQBlock(LightChild):
""" """
An XBlock used to ask multiple-choice questions An XBlock used to ask multiple-choice questions
Must be a child of a MentoringBlock. Allow to display a tip/advice depending on the Must be a child of a MentoringBlock. Allow to display a tip/advice depending on the
values entered by the student, and supports multiple types of multiple-choice values entered by the student, and supports multiple types of multiple-choice
set, with preset choices and author-defined values. set, with preset choices and author-defined values.
""" """
question = String(help="Question to ask the student", scope=Scope.content, default="") question = String(help="Question to ask the student", scope=Scope.content, default="")
type = String(help="Type of quizz", scope=Scope.content, default="choices") type = String(help="Type of MCQ", scope=Scope.content, default="choices")
student_choice = String(help="Last input submitted by the student", default="", scope=Scope.user_state) student_choice = String(help="Last input submitted by the student", default="", scope=Scope.user_state)
low = String(help="Label for low ratings", scope=Scope.content, default="Less") low = String(help="Label for low ratings", scope=Scope.content, default="Less")
high = String(help="Label for high ratings", scope=Scope.content, default="More") high = String(help="Label for high ratings", scope=Scope.content, default="More")
...@@ -82,9 +70,9 @@ class QuizzBlock(LightChild): ...@@ -82,9 +70,9 @@ class QuizzBlock(LightChild):
def mentoring_view(self, context=None): def mentoring_view(self, context=None):
if str(self.type) not in ('rating', 'choices'): if str(self.type) not in ('rating', 'choices'):
raise ValueError, u'Invalid value for QuizzBlock.type: `{}`'.format(self.type) raise ValueError, u'Invalid value for MCQBlock.type: `{}`'.format(self.type)
template_path = 'templates/html/quizz_{}.html'.format(self.type) template_path = 'templates/html/mcq_{}.html'.format(self.type)
html = render_template(template_path, { html = render_template(template_path, {
'self': self, 'self': self,
'custom_choices': self.custom_choices, 'custom_choices': self.custom_choices,
...@@ -92,26 +80,27 @@ class QuizzBlock(LightChild): ...@@ -92,26 +80,27 @@ class QuizzBlock(LightChild):
fragment = Fragment(html) fragment = Fragment(html)
fragment.add_css_url(self.runtime.local_resource_url(self.xblock_container, fragment.add_css_url(self.runtime.local_resource_url(self.xblock_container,
'public/css/quizz.css')) 'public/css/mcq.css'))
fragment.add_javascript_url(self.runtime.local_resource_url(self.xblock_container, fragment.add_javascript_url(self.runtime.local_resource_url(self.xblock_container,
'public/js/quizz.js')) 'public/js/mcq.js'))
fragment.initialize_js('QuizzBlock') fragment.initialize_js('MCQBlock')
return fragment return fragment
@property @property
def custom_choices(self): def custom_choices(self):
custom_choices = [] custom_choices = []
for child in self.get_children_objects(): for child in self.get_children_objects():
if isinstance(child, QuizzChoiceBlock): if isinstance(child, ChoiceBlock):
custom_choices.append(child) custom_choices.append(child)
return custom_choices return custom_choices
def submit(self, submission): def submit(self, submission):
log.debug(u'Received quizz submission: "%s"', submission) log.debug(u'Received MCQ submission: "%s"', submission)
completed = True completed = True
tips_fragments = [] tips_fragments = []
for tip in self.get_tips(): for tip in self.get_tips():
log.warn(tip)
completed = completed and tip.is_completed(submission) completed = completed and tip.is_completed(submission)
if tip.is_tip_displayed(submission): if tip.is_tip_displayed(submission):
tips_fragments.append(tip.render(submission)) tips_fragments.append(tip.render(submission))
...@@ -129,7 +118,7 @@ class QuizzBlock(LightChild): ...@@ -129,7 +118,7 @@ class QuizzBlock(LightChild):
'completed': completed, 'completed': completed,
'tips': formatted_tips, 'tips': formatted_tips,
} }
log.debug(u'Quizz submission result: %s', result) log.debug(u'MCQ submission result: %s', result)
return result return result
def get_submission_display(self, submission): def get_submission_display(self, submission):
...@@ -147,56 +136,6 @@ class QuizzBlock(LightChild): ...@@ -147,56 +136,6 @@ class QuizzBlock(LightChild):
""" """
tips = [] tips = []
for child in self.get_children_objects(): for child in self.get_children_objects():
if isinstance(child, QuizzTipBlock): if isinstance(child, TipBlock):
tips.append(child) tips.append(child)
return tips return tips
class QuizzTipBlock(LightChild):
"""
Each quizz
"""
content = String(help="Text of the tip to provide if needed", scope=Scope.content, default="")
display = String(help="List of choices to display the tip for", scope=Scope.content, default=None)
reject = String(help="List of choices to reject", scope=Scope.content, default=None)
def render(self, submission):
"""
Returns a fragment containing the formatted tip
"""
fragment, named_children = self.get_children_fragment({})
fragment.add_content(render_template('templates/html/tip.html', {
'self': self,
'named_children': named_children,
}))
return self.xblock_container.fragment_text_rewriting(fragment)
def is_completed(self, submission):
return submission and submission not in self.reject_with_defaults
def is_tip_displayed(self, submission):
return submission in self.display_with_defaults
@property
def display_with_defaults(self):
display = commas_to_list(self.display)
if display is None:
display = self.reject_with_defaults
else:
display += [choice for choice in self.reject_with_defaults
if choice not in display]
return display
@property
def reject_with_defaults(self):
reject = commas_to_list(self.reject)
log.debug(reject)
return reject or []
class QuizzChoiceBlock(LightChild):
"""
Custom choice of an answer for a quizz
"""
value = String(help="Value of the choice when selected", scope=Scope.content, default="")
content = String(help="Human-readable version of the choice value", scope=Scope.content, default="")
...@@ -49,8 +49,8 @@ class MentoringBlock(XBlockWithLightChildren): ...@@ -49,8 +49,8 @@ class MentoringBlock(XBlockWithLightChildren):
""" """
An XBlock providing mentoring capabilities An XBlock providing mentoring capabilities
Composed of text, answers input fields, and a set of multiple choice quizzes with advices. Composed of text, answers input fields, and a set of MRQ/MCQ with advices.
A set of conditions on the provided answers and quizzes choices will determine if the A set of conditions on the provided answers and MCQ/MRQ choices will determine if the
student is a) provided mentoring advices and asked to alter his answer, or b) is given the student is a) provided mentoring advices and asked to alter his answer, or b) is given the
ok to continue. ok to continue.
""" """
......
function QuizzBlock(runtime, element) { function MCQBlock(runtime, element) {
return { return {
submit: function() { submit: function() {
var checkedRadio = $('input[type=radio]:checked', element); var checkedRadio = $('input[type=radio]:checked', element);
......
<div class="quizz-tip"> <div class="mcq-tip">
<strong> <strong>
To the question <span class="italic">"{{ self.question }}"</span>, To the question <span class="italic">"{{ self.question }}"</span>,
{% if submission %} {% if submission %}
......
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
<answer name="goal" /> <answer name="goal" />
<quizz name="quizz_1_1" type="choices"> <mcq name="mcq_1_1" type="choices">
<question>Do you like this quizz?</question> <question>Do you like this MCQ?</question>
<choice value="yes">Yes</choice> <choice value="yes">Yes</choice>
<choice value="maybenot">Maybe not</choice> <choice value="maybenot">Maybe not</choice>
<choice value="understand">I don't understand</choice> <choice value="understand">I don't understand</choice>
...@@ -14,16 +14,16 @@ ...@@ -14,16 +14,16 @@
<tip display="yes">Great!</tip> <tip display="yes">Great!</tip>
<tip reject="maybenot">Ah, damn.</tip> <tip reject="maybenot">Ah, damn.</tip>
<tip reject="understand"><html><div id="test-custom-html">Really?</div></html></tip> <tip reject="understand"><html><div id="test-custom-html">Really?</div></html></tip>
</quizz> </mcq>
<quizz name="quizz_1_2" type="rating" low="Not good at all" high="Extremely good"> <mcq name="mcq_1_2" type="rating" low="Not good at all" high="Extremely good">
<question>How much do you rate this quizz?</question> <question>How much do you rate this MCQ?</question>
<choice value="notwant">I don't want to rate it</choice> <choice value="notwant">I don't want to rate it</choice>
<tip display="4,5">I love good grades.</tip> <tip display="4,5">I love good grades.</tip>
<tip reject="1,2,3">Will do better next time...</tip> <tip reject="1,2,3">Will do better next time...</tip>
<tip reject="notwant">Your loss!</tip> <tip reject="notwant">Your loss!</tip>
</quizz> </mcq>
<message type="completed"> <message type="completed">
<html><p>Congratulations!</p></html> <html><p>Congratulations!</p></html>
......
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 Harvard
#
# Authors:
# Xavier Antoviaque <xavier@antoviaque.org>
#
# This software's license gives you freedom; you can copy, convey,
# propagate, redistribute and/or modify this program under the terms of
# the GNU Affero General Public License (AGPL) as published by the Free
# Software Foundation (FSF), either version 3 of the License, or (at your
# option) any later version of the AGPL published by the FSF.
#
# This program 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 Affero
# General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
#
# Imports ###########################################################
import logging
from .light_children import LightChild, Scope, String
from .utils import render_template
# Globals ###########################################################
log = logging.getLogger(__name__)
# Functions #########################################################
def commas_to_list(commas_str):
"""
Converts a comma-separated string to a list
"""
if commas_str is None:
return None # Means default value (which can be non-empty)
elif commas_str == '':
return [] # Means empty list
else:
return commas_str.split(',')
# Classes ###########################################################
class TipBlock(LightChild):
"""
Each choice can define a tip depending on selection
"""
content = String(help="Text of the tip to provide if needed", scope=Scope.content, default="")
display = String(help="List of choices to display the tip for", scope=Scope.content, default=None)
reject = String(help="List of choices to reject", scope=Scope.content, default=None)
def render(self, submission):
"""
Returns a fragment containing the formatted tip
"""
fragment, named_children = self.get_children_fragment({})
fragment.add_content(render_template('templates/html/tip.html', {
'self': self,
'named_children': named_children,
}))
return self.xblock_container.fragment_text_rewriting(fragment)
def is_completed(self, submission):
return submission and submission not in self.reject_with_defaults
def is_tip_displayed(self, submission):
return submission in self.display_with_defaults
@property
def display_with_defaults(self):
display = commas_to_list(self.display)
if display is None:
display = self.reject_with_defaults
else:
display += [choice for choice in self.reject_with_defaults
if choice not in display]
return display
@property
def reject_with_defaults(self):
reject = commas_to_list(self.reject)
log.debug(reject)
return reject or []
...@@ -52,10 +52,11 @@ BLOCKS_CHILDREN = [ ...@@ -52,10 +52,11 @@ BLOCKS_CHILDREN = [
'column = mentoring:MentoringTableColumnBlock', 'column = mentoring:MentoringTableColumnBlock',
'header = mentoring:MentoringTableColumnHeaderBlock', 'header = mentoring:MentoringTableColumnHeaderBlock',
'answer = mentoring:AnswerBlock', 'answer = mentoring:AnswerBlock',
'quizz = mentoring:QuizzBlock', 'quizz = mentoring:MCQBlock',
'mcq = mentoring:MCQBlock',
'message = mentoring:MentoringMessageBlock', 'message = mentoring:MentoringMessageBlock',
'tip = mentoring:QuizzTipBlock', 'tip = mentoring:TipBlock',
'choice = mentoring:QuizzChoiceBlock', 'choice = mentoring:ChoiceBlock',
'html = mentoring:HTMLBlock', 'html = mentoring:HTMLBlock',
] ]
......
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 Harvard
#
# Authors:
# Xavier Antoviaque <xavier@antoviaque.org>
#
# This software's license gives you freedom; you can copy, convey,
# propagate, redistribute and/or modify this program under the terms of
# the GNU Affero General Public License (AGPL) as published by the Free
# Software Foundation (FSF), either version 3 of the License, or (at your
# option) any later version of the AGPL published by the FSF.
#
# This program 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 Affero
# General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
#
# Imports ###########################################################
import time
from mentoring.test_base import MentoringBaseTest
# Classes ###########################################################
class MCQBlockTest(MentoringBaseTest):
def test_mcq_choices_rating(self):
"""
Mentoring MCQ should display tips according to user choice
"""
# Initial MCQ status
mentoring = self.go_to_page('MCQ 1')
mcq1 = mentoring.find_element_by_css_selector('fieldset.choices')
mcq2 = mentoring.find_element_by_css_selector('fieldset.rating')
messages = mentoring.find_element_by_css_selector('.messages')
progress = mentoring.find_element_by_css_selector('.progress > .indicator')
self.assertEqual(messages.text, '')
self.assertFalse(messages.find_elements_by_xpath('./*'))
self.assertEqual(progress.text, '(Not completed)')
self.assertFalse(progress.find_elements_by_xpath('./*'))
mcq1_legend = mcq1.find_element_by_css_selector('legend')
mcq2_legend = mcq2.find_element_by_css_selector('legend')
self.assertEqual(mcq1_legend.text, 'Do you like this MCQ?')
self.assertEqual(mcq2_legend.text, 'How much do you rate this MCQ?')
mcq1_choices = mcq1.find_elements_by_css_selector('.choices .choice label')
mcq2_choices = mcq2.find_elements_by_css_selector('.choices .choice label')
self.assertEqual(len(mcq1_choices), 3)
self.assertEqual(len(mcq2_choices), 6)
self.assertEqual(mcq1_choices[0].text, 'Yes')
self.assertEqual(mcq1_choices[1].text, 'Maybe not')
self.assertEqual(mcq1_choices[2].text, "I don't understand")
self.assertEqual(mcq2_choices[0].text, '1')
self.assertEqual(mcq2_choices[1].text, '2')
self.assertEqual(mcq2_choices[2].text, '3')
self.assertEqual(mcq2_choices[3].text, '4')
self.assertEqual(mcq2_choices[4].text, '5')
self.assertEqual(mcq2_choices[5].text, "I don't want to rate it")
mcq1_choices_input = [
mcq1_choices[0].find_element_by_css_selector('input'),
mcq1_choices[1].find_element_by_css_selector('input'),
mcq1_choices[2].find_element_by_css_selector('input'),
]
mcq2_choices_input = [
mcq2_choices[0].find_element_by_css_selector('input'),
mcq2_choices[1].find_element_by_css_selector('input'),
mcq2_choices[2].find_element_by_css_selector('input'),
mcq2_choices[3].find_element_by_css_selector('input'),
mcq2_choices[4].find_element_by_css_selector('input'),
mcq2_choices[5].find_element_by_css_selector('input'),
]
self.assertEqual(mcq1_choices_input[0].get_attribute('value'), 'yes')
self.assertEqual(mcq1_choices_input[1].get_attribute('value'), 'maybenot')
self.assertEqual(mcq1_choices_input[2].get_attribute('value'), 'understand')
self.assertEqual(mcq2_choices_input[0].get_attribute('value'), '1')
self.assertEqual(mcq2_choices_input[1].get_attribute('value'), '2')
self.assertEqual(mcq2_choices_input[2].get_attribute('value'), '3')
self.assertEqual(mcq2_choices_input[3].get_attribute('value'), '4')
self.assertEqual(mcq2_choices_input[4].get_attribute('value'), '5')
self.assertEqual(mcq2_choices_input[5].get_attribute('value'), 'notwant')
# Submit without selecting anything
submit = mentoring.find_element_by_css_selector('input.submit')
submit.click()
tips = messages.find_elements_by_xpath('./*')
self.assertEqual(len(tips), 2)
self.assertEqual(tips[0].text, 'To the question "Do you like this MCQ?", you have not provided an answer.')
self.assertEqual(tips[1].text, 'To the question "How much do you rate this MCQ?", you have not provided an answer.')
self.assertEqual(progress.text, '(Not completed)')
self.assertFalse(progress.find_elements_by_xpath('./*'))
# Select only one option
mcq1_choices_input[1].click()
submit.click()
time.sleep(1)
tips = messages.find_elements_by_xpath('./*')
self.assertEqual(len(tips), 2)
self.assertEqual(tips[0].text, 'To the question "Do you like this MCQ?", you answered "Maybe not".\nAh, damn.')
self.assertEqual(tips[1].text, 'To the question "How much do you rate this MCQ?", you have not provided an answer.')
self.assertEqual(progress.text, '(Not completed)')
self.assertFalse(progress.find_elements_by_xpath('./*'))
# One with only display tip, one with reject tip - should not complete
mcq1_choices_input[0].click()
mcq2_choices_input[2].click()
submit.click()
time.sleep(1)
tips = messages.find_elements_by_xpath('./*')
self.assertEqual(len(tips), 2)
self.assertEqual(tips[0].text, 'To the question "Do you like this MCQ?", you answered "Yes".\nGreat!')
self.assertEqual(tips[1].text, 'To the question "How much do you rate this MCQ?", you answered "3".\nWill do better next time...')
self.assertEqual(progress.text, '(Not completed)')
self.assertFalse(progress.find_elements_by_xpath('./*'))
# Only display tips, to allow to complete
mcq1_choices_input[0].click()
mcq2_choices_input[3].click()
submit.click()
time.sleep(1)
tips = messages.find_elements_by_xpath('./*')
self.assertEqual(len(tips), 3)
self.assertEqual(tips[0].text, 'To the question "Do you like this MCQ?", you answered "Yes".\nGreat!')
self.assertEqual(tips[1].text, 'To the question "How much do you rate this MCQ?", you answered "4".\nI love good grades.')
self.assertEqual(tips[2].text, 'Congratulations!\nAll is good now...') # Includes child <html>
self.assertEqual(progress.text, '')
self.assertTrue(progress.find_elements_by_css_selector('img'))
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 Harvard
#
# Authors:
# Xavier Antoviaque <xavier@antoviaque.org>
#
# This software's license gives you freedom; you can copy, convey,
# propagate, redistribute and/or modify this program under the terms of
# the GNU Affero General Public License (AGPL) as published by the Free
# Software Foundation (FSF), either version 3 of the License, or (at your
# option) any later version of the AGPL published by the FSF.
#
# This program 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 Affero
# General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
#
# Imports ###########################################################
import time
from mentoring.test_base import MentoringBaseTest
# Classes ###########################################################
class QuizzBlockTest(MentoringBaseTest):
def test_quizz_choices_rating(self):
"""
Mentoring quizz should display tips according to user choice
"""
# Initial quizzes status
mentoring = self.go_to_page('Quizz 1')
quizz1 = mentoring.find_element_by_css_selector('fieldset.choices')
quizz2 = mentoring.find_element_by_css_selector('fieldset.rating')
messages = mentoring.find_element_by_css_selector('.messages')
progress = mentoring.find_element_by_css_selector('.progress > .indicator')
self.assertEqual(messages.text, '')
self.assertFalse(messages.find_elements_by_xpath('./*'))
self.assertEqual(progress.text, '(Not completed)')
self.assertFalse(progress.find_elements_by_xpath('./*'))
quizz1_legend = quizz1.find_element_by_css_selector('legend')
quizz2_legend = quizz2.find_element_by_css_selector('legend')
self.assertEqual(quizz1_legend.text, 'Do you like this quizz?')
self.assertEqual(quizz2_legend.text, 'How much do you rate this quizz?')
quizz1_choices = quizz1.find_elements_by_css_selector('.choices .choice label')
quizz2_choices = quizz2.find_elements_by_css_selector('.choices .choice label')
self.assertEqual(len(quizz1_choices), 3)
self.assertEqual(len(quizz2_choices), 6)
self.assertEqual(quizz1_choices[0].text, 'Yes')
self.assertEqual(quizz1_choices[1].text, 'Maybe not')
self.assertEqual(quizz1_choices[2].text, "I don't understand")
self.assertEqual(quizz2_choices[0].text, '1')
self.assertEqual(quizz2_choices[1].text, '2')
self.assertEqual(quizz2_choices[2].text, '3')
self.assertEqual(quizz2_choices[3].text, '4')
self.assertEqual(quizz2_choices[4].text, '5')
self.assertEqual(quizz2_choices[5].text, "I don't want to rate it")
quizz1_choices_input = [
quizz1_choices[0].find_element_by_css_selector('input'),
quizz1_choices[1].find_element_by_css_selector('input'),
quizz1_choices[2].find_element_by_css_selector('input'),
]
quizz2_choices_input = [
quizz2_choices[0].find_element_by_css_selector('input'),
quizz2_choices[1].find_element_by_css_selector('input'),
quizz2_choices[2].find_element_by_css_selector('input'),
quizz2_choices[3].find_element_by_css_selector('input'),
quizz2_choices[4].find_element_by_css_selector('input'),
quizz2_choices[5].find_element_by_css_selector('input'),
]
self.assertEqual(quizz1_choices_input[0].get_attribute('value'), 'yes')
self.assertEqual(quizz1_choices_input[1].get_attribute('value'), 'maybenot')
self.assertEqual(quizz1_choices_input[2].get_attribute('value'), 'understand')
self.assertEqual(quizz2_choices_input[0].get_attribute('value'), '1')
self.assertEqual(quizz2_choices_input[1].get_attribute('value'), '2')
self.assertEqual(quizz2_choices_input[2].get_attribute('value'), '3')
self.assertEqual(quizz2_choices_input[3].get_attribute('value'), '4')
self.assertEqual(quizz2_choices_input[4].get_attribute('value'), '5')
self.assertEqual(quizz2_choices_input[5].get_attribute('value'), 'notwant')
# Submit without selecting anything
submit = mentoring.find_element_by_css_selector('input.submit')
submit.click()
tips = messages.find_elements_by_xpath('./*')
self.assertEqual(len(tips), 2)
self.assertEqual(tips[0].text, 'To the question "Do you like this quizz?", you have not provided an answer.')
self.assertEqual(tips[1].text, 'To the question "How much do you rate this quizz?", you have not provided an answer.')
self.assertEqual(progress.text, '(Not completed)')
self.assertFalse(progress.find_elements_by_xpath('./*'))
# Select only one option
quizz1_choices_input[1].click()
submit.click()
time.sleep(1)
tips = messages.find_elements_by_xpath('./*')
self.assertEqual(len(tips), 2)
self.assertEqual(tips[0].text, 'To the question "Do you like this quizz?", you answered "Maybe not".\nAh, damn.')
self.assertEqual(tips[1].text, 'To the question "How much do you rate this quizz?", you have not provided an answer.')
self.assertEqual(progress.text, '(Not completed)')
self.assertFalse(progress.find_elements_by_xpath('./*'))
# One with only display tip, one with reject tip - should not complete
quizz1_choices_input[0].click()
quizz2_choices_input[2].click()
submit.click()
time.sleep(1)
tips = messages.find_elements_by_xpath('./*')
self.assertEqual(len(tips), 2)
self.assertEqual(tips[0].text, 'To the question "Do you like this quizz?", you answered "Yes".\nGreat!')
self.assertEqual(tips[1].text, 'To the question "How much do you rate this quizz?", you answered "3".\nWill do better next time...')
self.assertEqual(progress.text, '(Not completed)')
self.assertFalse(progress.find_elements_by_xpath('./*'))
# Only display tips, to allow to complete
quizz1_choices_input[0].click()
quizz2_choices_input[3].click()
submit.click()
time.sleep(1)
tips = messages.find_elements_by_xpath('./*')
self.assertEqual(len(tips), 3)
self.assertEqual(tips[0].text, 'To the question "Do you like this quizz?", you answered "Yes".\nGreat!')
self.assertEqual(tips[1].text, 'To the question "How much do you rate this quizz?", you answered "4".\nI love good grades.')
self.assertEqual(tips[2].text, 'Congratulations!\nAll is good now...') # Includes child <html>
self.assertEqual(progress.text, '')
self.assertTrue(progress.find_elements_by_css_selector('img'))
<vertical> <vertical>
<mentoring url_name="quizz_1" enforce_dependency="false"> <mentoring url_name="mcq_1" enforce_dependency="false">
<quizz name="quizz_1_1" type="choices"> <mcq name="mcq_1_1" type="choices">
<question>Do you like this quizz?</question> <question>Do you like this MCQ?</question>
<choice value="yes">Yes</choice> <choice value="yes">Yes</choice>
<choice value="maybenot">Maybe not</choice> <choice value="maybenot">Maybe not</choice>
<choice value="understand">I don't understand</choice> <choice value="understand">I don't understand</choice>
...@@ -9,16 +9,16 @@ ...@@ -9,16 +9,16 @@
<tip display="yes">Great!</tip> <tip display="yes">Great!</tip>
<tip reject="maybenot">Ah, damn.</tip> <tip reject="maybenot">Ah, damn.</tip>
<tip reject="understand"><html><div id="test-custom-html">Really?</div></html></tip> <tip reject="understand"><html><div id="test-custom-html">Really?</div></html></tip>
</quizz> </mcq>
<quizz name="quizz_1_2" type="rating" low="Not good at all" high="Extremely good"> <mcq name="mcq_1_2" type="rating" low="Not good at all" high="Extremely good">
<question>How much do you rate this quizz?</question> <question>How much do you rate this MCQ?</question>
<choice value="notwant">I don't want to rate it</choice> <choice value="notwant">I don't want to rate it</choice>
<tip display="4,5">I love good grades.</tip> <tip display="4,5">I love good grades.</tip>
<tip reject="1,2,3">Will do better next time...</tip> <tip reject="1,2,3">Will do better next time...</tip>
<tip reject="notwant">Your loss!</tip> <tip reject="notwant">Your loss!</tip>
</quizz> </mcq>
<message type="completed"> <message type="completed">
All is good now... All is good now...
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment