diff --git a/runestone/fitb/js/fitb.js b/runestone/fitb/js/fitb.js index 4c65baeaa..66348e6a4 100644 --- a/runestone/fitb/js/fitb.js +++ b/runestone/fitb/js/fitb.js @@ -37,9 +37,9 @@ export default class FITB extends RunestoneBase { this.scriptSelector(this.origElem).html() ); this.createFITBElement(); - this.checkServer("fillb"); this.caption = "Fill in the Blank"; this.addCaption("runestone"); + this.checkServer("fillb", true); } // Find the script tag containing JSON in a given root DOM node. scriptSelector(root_node) { diff --git a/runestone/fitb/test/_sources/index.rst b/runestone/fitb/test/_sources/index.rst index 46b4dc2b5..7f46d880d 100644 --- a/runestone/fitb/test/_sources/index.rst +++ b/runestone/fitb/test/_sources/index.rst @@ -7,7 +7,7 @@ qnum testing Fill in the Blank ----------------- -.. fillintheblank:: fill1412 +.. fillintheblank:: test_fitb_string Fill in the blanks to make the following sentence: "The red car drove away." @@ -20,7 +20,7 @@ Fill in the Blank Test 2 - test a numeric range. -.. fillintheblank:: fill_2pi +.. fillintheblank:: test_fitb_number .. If this isn't treated as a comment, then it will cause a **syntax error, thus producing a test failure. @@ -55,7 +55,7 @@ Error testing Regex testing ------------- -.. fillintheblank:: fillregex +.. fillintheblank:: test_fitb_regex_1 :casei: Complete the sentence: |blank| had a |blank| lamb. One plus one is: (note that if there aren't enough blanks for the feedback given, they're added to the end of the problem. So, we don't **need** to specify a blank here.) @@ -69,7 +69,7 @@ Regex testing :2 1: Close.... (The second number is a tolerance, so this matches 1 or 3.) :x: Nope. (As earlier, this matches anything.) -.. fillintheblank:: regexescapes1 +.. fillintheblank:: test_fitb_regex_2 :casei: Windows system files are stored in: |blank|. @@ -78,37 +78,10 @@ Regex testing :program files: Third party applications are stored here, not system files. :x: Try again. -.. fillintheblank:: regexescapes2 +.. fillintheblank:: test_fitb_regex_3 :casei: Python lists are declared using: |blank|. - :\[\]: Correct. :x: Try again. - -Timed exam testing ------------------- -.. timed:: timed-exam-test - - .. fillintheblank:: timed-fitb-1 - - - Fill in the blanks to make the following sentence: "The red car drove away." - - The |blank| car drove |blank|. - - - :red: Correct. - :x: Incorrect. Try 'red'. - - :away: Correct. - :x: Incorrect. Try 'away'. - - .. fillintheblank:: timed-fitb-2 - - What is the solution to the following: - - :math:`2 * \pi =` |blank|. - - - :6.28 0.005: Good job. - :3.27 3: Try higher. - :9.29 3: Try lower. - :.*: Incorrect. Try again. diff --git a/runestone/fitb/test/test_fitb.py b/runestone/fitb/test/test_fitb.py index c3c1044f9..cc8ad94fe 100644 --- a/runestone/fitb/test/test_fitb.py +++ b/runestone/fitb/test/test_fitb.py @@ -1,213 +1,169 @@ -from unittest import TestCase -import codecs -from selenium.webdriver.common.alert import Alert -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.by import By -from runestone.unittest_base import module_fixture_maker, RunestoneTestCase - -mf, setUpModule, tearDownModule = module_fixture_maker(__file__, True) - - -class FITB_MISC_Tests(TestCase): - # Look for errors producted by invalid questions. - def test_1(self): - # Check for the following directive-level errors. - directive_level_errors = ( - # Produced my fitb id error1_no_content, - ( - 38, - 'Content block expected for the "fillintheblank" directive; none found.', - ), - (50, "Not enough feedback for the number of blanks supplied."), - ) - for error_line, error_string in directive_level_errors: - self.assertIn( - ":{}: WARNING: {}".format(error_line, error_string), - mf.build_stderr_data, - ) - - # Check for the following error inside the directive. - inside_directive_errors = ( - # error2, - ( - 42, - "the last item in a fill-in-the-blank question must be a bulleted list.", - ), - # error3, - ( - 48, - "each list item in a fill-in-the-blank problems must contain only one item, a field list.", - ), - ) - for error_line, error_string in inside_directive_errors: - self.assertIn( - ": WARNING: On line {}, {}".format(error_line, error_string), - mf.build_stderr_data, - ) - - self.assertIn( - "WARNING: while setting up extension runestone.lp: role 'docname' is already registered, it will be overridden", - mf.build_stderr_data, - ) - - # Make sure we saw all errors. - self.assertEqual( - len(directive_level_errors) + len(inside_directive_errors) + 1, - mf.build_stderr_data.count("WARNING"), - ) - - # Check that numbering works correctly. - def test_2(self): - with codecs.open("build/testfitb/index.html", encoding="utf-8") as f: - self.assertIn("Before-5-After: Fill in the blanks", f.read()) - - -class FITBtests(RunestoneTestCase): - ## Helpers - ## ======= - # Load the web page, then return the DIV containing a FITB question. - def find_fitb(self, elem_id): - self.driver.get(self.host + "/index.html") - wait = WebDriverWait(self.driver, 10) - try: - wait.until(EC.presence_of_element_located((By.ID, elem_id))) - except: - text = self.driver.page_source - print(text[:300]) - - self.fitb = self.driver.find_element_by_id(elem_id) - return self.fitb - - # Find one of the blanks, based on the given index. - def find_blank(self, index): - return self.fitb.find_elements_by_tag_name("input")[index] - - # Click the "Check me" button. - def click_checkme(self): - self.fitb.find_element_by_tag_name("button").click() - - # Find the question's feedback element. - def find_feedback(self, elem_id): - return self.fitb.find_element_by_id(elem_id + "_feedback") - - ## Tests - ## ===== - # One of two correct answers - def test_fitb(self): - """ - http://runestoneinteractive.org/build/html/directives.html#fill-in-the-blank for documentation - """ - self.find_fitb("fill1412") - self.find_blank(0).send_keys("red") - self.click_checkme() - feedback = self.find_feedback("fill1412") - self.assertIn("Correct", feedback.text) - # Get desiered response from .i18n file loaded based on language attribute in the HTML tag initially set in conf.py - msg_no_answer = self.driver.execute_script("return $.i18n('msg_no_answer')") - - self.assertIn(msg_no_answer, feedback.text) - - # No answers yet -- no answer provided feedback. - def test_fitb2(self): - self.find_fitb("fill1412") - self.click_checkme() - feedback = self.find_feedback("fill1412") - self.assertIsNotNone(feedback.text) - # Get desiered response from .i18n file loaded based on language attribute in the HTML tag initially set in conf.py - msg_no_answer = self.driver.execute_script("return $.i18n('msg_no_answer')") - - self.assertIn(msg_no_answer, feedback.text) - - # Both correct answers - def test_fitb3(self): - self.find_fitb("fill1412") - self.find_blank(0).send_keys("red") - self.find_blank(1).send_keys("away") - self.click_checkme() - feedback = self.find_feedback("fill1412") - self.assertIn("Correct", feedback.text) - - def test_fitb4(self): - self.find_fitb("fill1412") - blank0 = self.find_blank(0) - # Type an incorrect answer. - blank0.send_keys("red") - # Delete it. - blank0.clear() - # Type the correct answer. - blank0.send_keys("red") - self.find_blank(1).send_keys("away") - self.click_checkme() - feedback = self.find_feedback("fill1412") - self.assertIn("Correct", feedback.text) - - def test_fitboneblank_too_low(self): - self.find_fitb("fill_2pi") - self.find_blank(0).send_keys(" 6") - self.click_checkme() - feedback = self.find_feedback("fill_2pi") - self.assertIn("Try higher.", feedback.text) - - def test_fitboneblank_wildcard(self): - self.find_fitb("fill_2pi") - self.find_blank(0).send_keys("I give up") - self.click_checkme() - feedback = self.find_feedback("fill_2pi") - self.assertIn("Incorrect. Try again.", feedback.text) - - def test_fitbfillrange(self): - self.find_fitb("fill_2pi") - self.find_blank(0).send_keys(" 6.28 ") - self.click_checkme() - feedback = self.find_feedback("fill_2pi") - self.assertIn("Good job.", feedback.text) - - def test_fitbregex(self): - self.find_fitb("fillregex") - self.find_blank(0).send_keys(" maire ") - # self.find_blank(0).send_keys(" mARy ") - self.find_blank(1).send_keys("LITTLE") - self.find_blank(2).send_keys("2") - self.click_checkme() - feedback = self.find_feedback("fillregex") - self.assertIn("Correct.", feedback.text) - - def test_regexescapes1(self): - self.find_fitb("regexescapes1") - self.find_blank(0).send_keys(r"C:\windows\system") - self.click_checkme() - feedback = self.find_feedback("regexescapes1") - self.assertIn("Correct.", feedback.text) - - def test_regexescapes2(self): - self.find_fitb("regexescapes2") - self.find_blank(0).send_keys("[]") - self.click_checkme() - feedback = self.find_feedback("regexescapes2") - self.assertIn("Correct.", feedback.text) - - # moving the finish button seems to confuse selenium when the start button is hidden. - # def test_timed(self): - # # The question we want isn't visible yet. Load the page first. - # self.driver.get(self.host + "/index.html") - # # Start the assessment to show it. - # self.driver.find_element_by_id("start").click() - # # Now find the first fitb. - # self.fitb = self.driver.find_element_by_id("timed-fitb-1") - # # Fill it in. - # self.find_blank(0).send_keys("red") - # self.find_blank(1).send_keys("away") - # # go to the second fitb - # self.driver.find_element_by_id("next").click() - # self.fitb = self.driver.find_element_by_id("timed-fitb-2") - # # Fill it in with a wrong answer - # self.find_blank(0).send_keys("3.27") - # # Finish the exam and click OK. - # fb = self.driver.find_element_by_id("finish") - # # self.assertNotNone(fb) - # fb.click() - # Alert(self.driver).accept() - # # Check the results. Should have one correct - # timed_results = self.driver.find_element_by_id("timed-exam-testresults").text - # self.assertIn("Num Correct: 1", timed_results) +from selenium.webdriver.support import expected_conditions as EC + + +def test_1(selenium_module_fixture): + mf = selenium_module_fixture + + # Check for the following directive-level errors. + directive_level_errors = ( + # Produced my fitb id error1_no_content, + ( + 38, + 'Content block expected for the "fillintheblank" directive; none found.', + ), + (50, "Not enough feedback for the number of blanks supplied."), + ) + for error_line, error_string in directive_level_errors: + assert ":{}: WARNING: {}".format(error_line, error_string) in mf.build_stderr_data + + # Check for the following error inside the directive. + inside_directive_errors = ( + # error2, + ( + 42, + "the last item in a fill-in-the-blank question must be a bulleted list.", + ), + # error3, + ( + 48, + "each list item in a fill-in-the-blank problems must contain only one item, a field list.", + ), + ) + for error_line, error_string in inside_directive_errors: + assert ": WARNING: On line {}, {}".format(error_line, error_string) in mf.build_stderr_data + + assert "WARNING: while setting up extension runestone.lp: role 'docname' is already registered, it will be overridden" in mf.build_stderr_data + + # Make sure we saw all errors. + assert len(directive_level_errors) + len(inside_directive_errors) + 1 == mf.build_stderr_data.count("WARNING") + + +# Check that numbering works correctly. +def test_2(): + with open("build/testfitb/index.html", encoding="utf-8") as f: + assert "Before-5-After: Fill in the blanks" in f.read() + + +## Helpers +## ======= +# Load the web page, then return the DIV containing a FITB question. +def find_fitb(selenium_utils, div_id): + selenium_utils.wait_until_ready(div_id) + return selenium_utils.driver.find_element_by_id(div_id) + + +# Find one of the blanks, based on the given index. +def find_blank(fitb_element, index, clear=True): + blank = fitb_element.find_elements_by_tag_name("input")[index] + if clear: + blank.clear() + return blank + + +# Click the "Check me" button. +def click_checkme(fitb_element): + fitb_element.find_element_by_tag_name("button").click() + + +# Find the question's feedback element. +def check_feedback(selenium_utils, fitb_element, expected_text): + div_id = fitb_element.get_attribute("id") + selenium_utils.wait.until( + EC.text_to_be_present_in_element((By.ID, div_id + "_feedback"), expected_text)) + + +## Tests +## ===== +# One of two correct answers +def test_fitb1(selenium_utils_get): + """ + http://runestoneinteractive.org/build/html/directives.html#fill-in-the-blank for documentation + """ + fitb = find_fitb(selenium_utils_get, "test_fitb_string") + find_blank(fitb, 0) + find_blank(fitb, 1) + click_checkme(fitb) + # Get desired response from .i18n file loaded based on language attribute in the HTML tag initially set in conf.py + msg_no_answer = selenium_utils_get.driver.execute_script("return $.i18n('msg_no_answer')") + check_feedback(selenium_utils_get, fitb, msg_no_answer) + + +# No answers yet -- no answer provided feedback. +def test_fitb2(selenium_utils_get): + fitb = find_fitb(selenium_utils_get, "test_fitb_string") + find_blank(fitb, 0).send_keys("red") + find_blank(fitb, 1) + click_checkme(fitb) + check_feedback(selenium_utils_get, fitb, "Correct") + # Get desired response from .i18n file loaded based on language attribute in the HTML tag initially set in conf.py + msg_no_answer = selenium_utils_get.driver.execute_script("return $.i18n('msg_no_answer')") + check_feedback(selenium_utils_get, fitb, msg_no_answer) + + +# Both correct answers +def test_fitb3(selenium_utils_get): + fitb = find_fitb(selenium_utils_get, "test_fitb_string") + find_blank(fitb, 0).send_keys("red") + find_blank(fitb, 1).send_keys("away") + click_checkme(fitb) + check_feedback(selenium_utils_get, fitb, "Correct") + + +def test_fitb4(selenium_utils_get): + fitb = find_fitb(selenium_utils_get, "test_fitb_string") + blank0 = find_blank(fitb, 0) + # Type an incorrect answer. + blank0.send_keys("red") + # Delete it. + blank0.clear() + # Type the correct answer. + blank0.send_keys("red") + find_blank(fitb, 1).send_keys("away") + click_checkme(fitb) + check_feedback(selenium_utils_get, fitb, "Correct") + + +def test_fitboneblank_too_low(selenium_utils_get): + fitb = find_fitb(selenium_utils_get, "test_fitb_number") + find_blank(fitb, 0).send_keys(" 6") + click_checkme(fitb) + check_feedback(selenium_utils_get, fitb, "Try higher.") + + +def test_fitboneblank_wildcard(selenium_utils_get): + fitb = find_fitb(selenium_utils_get, "test_fitb_number") + find_blank(fitb, 0).send_keys("I give up") + click_checkme(fitb) + check_feedback(selenium_utils_get, fitb, "Incorrect. Try again.") + + +def test_fitbfillrange(selenium_utils_get): + fitb = find_fitb(selenium_utils_get, "test_fitb_number") + find_blank(fitb, 0).send_keys(" 6.28 ") + click_checkme(fitb) + check_feedback(selenium_utils_get, fitb, "Good job.") + + +def test_fitbregex(selenium_utils_get): + fitb = find_fitb(selenium_utils_get, "test_fitb_regex_1") + find_blank(fitb, 0).send_keys(" maire ") + # find_blank(fitb, 0).send_keys(" mARy ") + find_blank(fitb, 1).send_keys("LITTLE") + find_blank(fitb, 2).send_keys("2") + click_checkme(fitb) + check_feedback(selenium_utils_get, fitb, "Correct") + + +def test_regexescapes1(selenium_utils_get): + fitb = find_fitb(selenium_utils_get, "test_fitb_regex_2") + find_blank(fitb, 0).send_keys(r"C:\windows\system") + click_checkme(fitb) + check_feedback(selenium_utils_get, fitb, "Correct") + + +def test_regexescapes2(selenium_utils_get): + fitb = find_fitb(selenium_utils_get, "test_fitb_regex_3") + find_blank(fitb, 0).send_keys("[]") + click_checkme(fitb) + check_feedback(selenium_utils_get, fitb, "Correct")