chore: remove legacy problem studio view (#37795)

* fix: remove legacy problem studio view and resource templates
This commit is contained in:
Irtaza Akram
2026-01-08 13:07:15 +05:00
committed by GitHub
parent 0b628353ef
commit 474dc71f0d
41 changed files with 12 additions and 4715 deletions

View File

@@ -1450,8 +1450,6 @@ class ContentStoreTest(ContentStoreTestCase):
problem_loc = UsageKey.from_string(payload['locator'])
problem = self.store.get_item(problem_loc)
self.assertIsInstance(problem, ProblemBlock, "New problem is not a ProblemBlock")
context = problem.get_context()
self.assertIn('markdown', context, "markdown is missing from context")
self.assertNotIn('markdown', problem.editable_metadata_fields, "Markdown slipped into the editable metadata fields") # lint-amnesty, pylint: disable=line-too-long
def test_cms_imported_course_walkthrough(self):

View File

@@ -20,23 +20,9 @@ class TemplateTests(ModuleStoreTestCase):
self.assertIsNotNone(found.get('course'))
self.assertIsNotNone(found.get('about'))
self.assertIsNotNone(found.get('html'))
self.assertIsNotNone(found.get('problem'))
self.assertEqual(len(found.get('course')), 0)
self.assertEqual(len(found.get('about')), 1)
self.assertGreaterEqual(len(found.get('html')), 2)
self.assertGreaterEqual(len(found.get('problem')), 10)
dropdown = None
for template in found['problem']:
self.assertIn('metadata', template)
self.assertIn('display_name', template['metadata'])
if template['metadata']['display_name'] == 'Dropdown':
dropdown = template
break
self.assertIsNotNone(dropdown)
self.assertIn('markdown', dropdown['metadata'])
self.assertIn('data', dropdown)
self.assertRegex(dropdown['metadata']['markdown'], r'.*dropdown problems.*')
self.assertRegex(dropdown['data'], r'<problem>\s*<optionresponse>\s*<p>.*dropdown problems.*')
def test_get_some_templates(self):
course = CourseFactory.create()

View File

@@ -123,25 +123,6 @@ def use_video_gallery_flow():
return ENABLE_VIDEO_GALLERY_FLOW_FLAG.is_enabled()
# .. toggle_name: legacy_studio.problem_editor
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Temporarily fall back to the old Problem component (a.k.a. CAPA/problem block) editor.
# .. toggle_use_cases: temporary
# .. toggle_creation_date: 2025-03-14
# .. toggle_target_removal_date: 2025-09-14
# .. toggle_tickets: https://github.com/openedx/edx-platform/issues/36275
# .. toggle_warning: In Ulmo, this toggle will be removed. Only the new (React-based) experience will be available.
LEGACY_STUDIO_PROBLEM_EDITOR = CourseWaffleFlag('legacy_studio.problem_editor', __name__)
def use_new_problem_editor(course_key):
"""
Returns a boolean if new problem editor is enabled
"""
return not LEGACY_STUDIO_PROBLEM_EDITOR.is_enabled(course_key)
# .. toggle_name: contentstore.individualize_anonymous_user_id
# .. toggle_implementation: CourseWaffleFlag
# .. toggle_default: False

View File

@@ -33,7 +33,6 @@ from cms.djangoapps.contentstore.helpers import (
from cms.djangoapps.contentstore.toggles import (
libraries_v1_enabled,
libraries_v2_enabled,
use_new_problem_editor,
use_new_unit_page,
)
from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import load_services_for_studio
@@ -368,16 +367,14 @@ def get_component_templates(courselike, library=False): # lint-amnesty, pylint:
)
)
#If using new problem editor, we select problem type inside the editor
# because of this, we only show one problem.
if category == 'problem' and use_new_problem_editor(courselike.context_key):
if category == 'problem':
templates_for_category = [
template for template in templates_for_category if template['boilerplate_name'] == 'blank_common.yaml'
]
# Add any advanced problem types. Note that these are different xblocks being stored as Advanced Problems,
# currently not supported in libraries .
if category == 'problem' and not library and not use_new_problem_editor(courselike.context_key):
if category == 'problem' and not library:
disabled_block_names = [block.name for block in disabled_xblocks()]
advanced_problem_types = [advanced_problem_type for advanced_problem_type in ADVANCED_PROBLEM_TYPES
if advanced_problem_type['component'] not in disabled_block_names]

View File

@@ -13,7 +13,6 @@ from django.test import TestCase
from django.test.client import RequestFactory
from django.urls import reverse
from django.test.utils import override_settings
from edx_toggles.toggles.testutils import override_waffle_flag
from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE
from openedx_events.content_authoring.data import DuplicatedXBlockData
from openedx_events.content_authoring.signals import XBLOCK_DUPLICATED
@@ -23,7 +22,6 @@ from opaque_keys import InvalidKeyError
from opaque_keys.edx.asides import AsideUsageKeyV2
from opaque_keys.edx.keys import CourseKey, UsageKey
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from pyquery import PyQuery
from pytz import UTC
from bs4 import BeautifulSoup
from web_fragments.fragment import Fragment
@@ -57,7 +55,6 @@ from xmodule.partitions.partitions import (
from xmodule.partitions.tests.test_partitions import MockPartitionService
from xmodule.x_module import STUDENT_VIEW, STUDIO_VIEW
from cms.djangoapps.contentstore import toggles
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
from cms.djangoapps.contentstore.utils import (
reverse_course_url,
@@ -229,7 +226,6 @@ class GetItemTest(ItemTest):
resp = self.create_xblock(
parent_usage_key=child_vertical_usage_key,
category="problem",
boilerplate="multiplechoice.yaml",
)
self.assertEqual(resp.status_code, 200)
@@ -258,7 +254,6 @@ class GetItemTest(ItemTest):
resp = self.create_xblock(
parent_usage_key=wrapper_usage_key,
category="problem",
boilerplate="multiplechoice.yaml",
)
self.assertEqual(resp.status_code, 200)
@@ -286,7 +281,6 @@ class GetItemTest(ItemTest):
resp = self.create_xblock(
parent_usage_key=child_vertical_usage_key,
category="problem",
boilerplate="multiplechoice.yaml",
)
self.assertEqual(resp.status_code, 200)
usage_key = self.response_usage_key(resp)
@@ -311,18 +305,13 @@ class GetItemTest(ItemTest):
resp = self.create_xblock(
parent_usage_key=split_test_usage_key,
category="html",
boilerplate="announcement.yaml",
)
self.assertEqual(resp.status_code, 200)
resp = self.create_xblock(
parent_usage_key=split_test_usage_key,
category="html",
boilerplate="latex_html.yaml",
)
self.assertEqual(resp.status_code, 200)
html, __ = self._get_container_preview(split_test_usage_key)
self.assertIn("Announcement", html)
self.assertIn("LaTeX", html)
def test_split_test_edited(self):
"""
@@ -608,33 +597,12 @@ class TestCreateItem(ItemTest):
course = self.get_item_from_modulestore(self.usage_key)
self.assertIn(chap_usage_key, course.children)
# use default display name
resp = self.create_xblock(parent_usage_key=chap_usage_key, category="vertical")
vert_usage_key = self.response_usage_key(resp)
# create problem w/ boilerplate
template_id = "multiplechoice.yaml"
resp = self.create_xblock(
parent_usage_key=vert_usage_key, category="problem", boilerplate=template_id
)
prob_usage_key = self.response_usage_key(resp)
problem = self.get_item_from_modulestore(prob_usage_key)
# check against the template
course = CourseFactory.create()
problem_block = BlockFactory.create(category="problem", parent_location=course.location)
template = problem_block.get_template(template_id)
self.assertEqual(problem.data, template["data"])
self.assertEqual(problem.display_name, template["metadata"]["display_name"])
self.assertEqual(problem.markdown, template["metadata"]["markdown"])
def test_create_block_negative(self):
"""
Negative tests for create_item
"""
# non-existent boilerplate: creates a default
resp = self.create_xblock(
category="problem", boilerplate="nosuchboilerplate.yaml"
)
resp = self.create_xblock(category="problem")
self.assertEqual(resp.status_code, 200)
def test_create_with_future_date(self):
@@ -836,7 +804,6 @@ class TestDuplicateItem(ItemTest, DuplicateHelper, OpenEdxEventsTestMixin):
resp = self.create_xblock(
parent_usage_key=self.vert_usage_key,
category="problem",
boilerplate="multiplechoice.yaml",
)
self.problem_usage_key = self.response_usage_key(resp)
@@ -936,19 +903,6 @@ class TestDuplicateItem(ItemTest, DuplicateHelper, OpenEdxEventsTestMixin):
self.assertEqual(duplicated_item.display_name, expected_name)
return usage_key
# Display name comes from template.
dupe_usage_key = verify_name(
self.problem_usage_key,
self.vert_usage_key,
"Duplicate of 'Multiple Choice'",
)
# Test dupe of dupe.
verify_name(
dupe_usage_key,
self.vert_usage_key,
"Duplicate of 'Duplicate of 'Multiple Choice''",
)
# Uses default display_name of 'Text' from HTML component.
verify_name(self.html_usage_key, self.vert_usage_key, "Duplicate of 'Text'")
@@ -1847,7 +1801,6 @@ class TestDuplicateItemWithAsides(ItemTest, DuplicateHelper):
resp = self.create_xblock(
parent_usage_key=self.seq_usage_key,
category="problem",
boilerplate="multiplechoice.yaml",
)
self.problem_usage_key = self.response_usage_key(resp)
@@ -1930,11 +1883,9 @@ class TestEditItemSetup(ItemTest):
self.seq2_update_url = reverse_usage_url("xblock_handler", self.seq2_usage_key)
# create problem w/ boilerplate
template_id = "multiplechoice.yaml"
resp = self.create_xblock(
parent_usage_key=self.seq_usage_key,
category="problem",
boilerplate=template_id,
)
self.problem_usage_key = self.response_usage_key(resp)
self.problem_update_url = reverse_usage_url(
@@ -1965,19 +1916,6 @@ class TestEditItem(TestEditItemSetup):
problem = self.get_item_from_modulestore(self.problem_usage_key)
self.assertEqual(problem.rerandomize, 'never')
def test_null_field(self):
"""
Sending null in for a field 'deletes' it
"""
problem = self.get_item_from_modulestore(self.problem_usage_key)
self.assertIsNotNone(problem.markdown)
self.client.ajax_post(
self.problem_update_url,
data={'nullout': ['markdown']}
)
problem = self.get_item_from_modulestore(self.problem_usage_key)
self.assertIsNone(problem.markdown)
def test_date_fields(self):
"""
Test setting due & start dates on sequential
@@ -2427,28 +2365,6 @@ class TestEditItem(TestEditItemSetup):
) # See xmodule/fields.py
class TestEditItemSplitMongo(TestEditItemSetup):
"""
Tests for EditItem running on top of the SplitMongoModuleStore.
"""
def test_editing_view_wrappers(self):
"""
Verify that the editing view only generates a single wrapper, no matter how many times it's loaded
Exposes: PLAT-417
"""
view_url = reverse_usage_url(
"xblock_view_handler", self.problem_usage_key, {"view_name": STUDIO_VIEW}
)
for __ in range(3):
resp = self.client.get(view_url, HTTP_ACCEPT="application/json")
self.assertEqual(resp.status_code, 200)
content = json.loads(resp.content.decode("utf-8"))
self.assertEqual(len(PyQuery(content["html"])(f".xblock-{STUDIO_VIEW}")), 1)
class TestEditSplitModule(ItemTest):
"""
Tests around editing instances of the split_test block.
@@ -2864,7 +2780,6 @@ class TestComponentHandler(TestCase):
assert mocked_get_aside_from_xblock.called is is_get_aside_called
@override_waffle_flag(toggles.LEGACY_STUDIO_PROBLEM_EDITOR, True)
class TestComponentTemplates(CourseTestCase):
"""
Unit tests for the generation of the component templates for a course.
@@ -3012,12 +2927,6 @@ class TestComponentTemplates(CourseTestCase):
self.course.allow_unsupported_xblocks = True
self.templates = get_component_templates(self.course)
self._verify_basic_component("video", "Video", "us")
problem_templates = self.get_templates_of_type("problem")
problem_no_boilerplate = self.get_template(
problem_templates, "Blank Problem"
)
self.assertIsNotNone(problem_no_boilerplate)
self.assertEqual("us", problem_no_boilerplate["support_level"])
# Now fully disable video through XBlockConfiguration
XBlockConfiguration.objects.create(name="video", enabled=False)
@@ -3061,20 +2970,6 @@ class TestComponentTemplates(CourseTestCase):
self.templates = get_component_templates(self.course)
self.assertTrue((not any(item.get("category") == "done" for item in self.get_templates_of_type("advanced"))))
def test_advanced_problems(self):
"""
Test the handling of advanced problem templates.
"""
problem_templates = self.get_templates_of_type("problem")
circuit_template = self.get_template(
problem_templates, "Circuit Schematic Builder"
)
self.assertIsNotNone(circuit_template)
self.assertEqual(circuit_template.get("category"), "problem")
self.assertEqual(
circuit_template.get("boilerplate_name"), "circuitschematic.yaml"
)
def test_deprecated_no_advance_component_button(self):
"""
Test that there will be no `Advanced` button on unit page if xblocks have disabled

View File

@@ -4,41 +4,9 @@ define(['js/models/component_template'],
var mockTemplateJSON = {
templates: [
{
category: 'problem',
boilerplate_name: 'formularesponse.yaml',
display_name: 'Math Expression Input'
}, {
category: 'problem',
boilerplate_name: null,
display_name: 'Blank Advanced Problem'
}, {
category: 'problem',
boilerplate_name: 'checkboxes.yaml',
display_name: 'Checkboxes'
}, {
category: 'problem',
boilerplate_name: 'multiple_choice.yaml',
display_name: 'Multiple Choice'
}, {
category: 'problem',
boilerplate_name: 'drag_and_drop.yaml',
display_name: 'Drag and Drop'
}, {
category: 'problem',
boilerplate_name: 'problem_with_hint.yaml',
display_name: 'Problem with Adaptive Hint'
}, {
category: 'problem',
boilerplate_name: 'imageresponse.yaml',
display_name: 'Image Mapped Input'
}, {
category: 'openassessment',
boilerplate_name: null,
display_name: 'Peer Assessment'
}, {
category: 'problem',
boilerplate_name: 'an_easy_problem.yaml',
display_name: 'An Easy Problem'
}, {
category: 'word_cloud',
boilerplate_name: null,
@@ -69,7 +37,7 @@ define(['js/models/component_template'],
} else {
// If the first template is blank, make sure that it has the correct category
if (!template.boilerplate_name) {
expect(template.category).toBe('problem');
expect(template.category).toBe('openassessment');
}
lastTemplate = template;
}

View File

@@ -507,12 +507,11 @@ function($, _, Backbone, gettext, BasePage,
const primaryHeader = $(event.target).closest('.xblock-header-primary, .nav-actions');
var useNewVideoEditor = primaryHeader.attr('use-new-editor-video'),
useNewProblemEditor = primaryHeader.attr('use-new-editor-problem'),
blockType = primaryHeader.attr('data-block-type');
if((blockType === 'html')
|| (useNewVideoEditor === 'True' && blockType === 'video')
|| (useNewProblemEditor === 'True' && blockType === 'problem')
|| (blockType === 'problem')
) {
var destinationUrl = primaryHeader.attr('authoring_MFE_base_url')
+ '/' + blockType
@@ -1172,8 +1171,7 @@ function($, _, Backbone, gettext, BasePage,
onNewXBlock: function(xblockElement, scrollOffset, is_duplicate, data) {
var useNewVideoEditor = this.$('.xblock-header-primary').attr('use-new-editor-video'),
useVideoGalleryFlow = this.$('.xblock-header-primary').attr("use-video-gallery-flow"),
useNewProblemEditor = this.$('.xblock-header-primary').attr('use-new-editor-problem');
useVideoGalleryFlow = this.$('.xblock-header-primary').attr("use-video-gallery-flow");
// find the block type in the locator if availible
if(data.hasOwnProperty('locator')) {
@@ -1183,7 +1181,7 @@ function($, _, Backbone, gettext, BasePage,
// open mfe editors for new blocks only and not for content imported from libraries
if(!data.hasOwnProperty('upstreamRef') && (blockType.includes('html')
|| (useNewVideoEditor === 'True' && blockType.includes('video'))
|| (useNewProblemEditor === 'True' && blockType.includes('problem')))
|| (blockType.includes('problem')))
){
if (this.options.isIframeEmbed && (this.isSplitTestContentPage || this.isVerticalContentPage)) {
return this.postMessageToParent({

View File

@@ -13,7 +13,7 @@ from django.urls import reverse
from django.utils.translation import gettext as _
from cms.djangoapps.contentstore.helpers import xblock_studio_url, xblock_type_display_name
from cms.djangoapps.contentstore.toggles import use_new_problem_editor, use_new_video_editor, use_video_gallery_flow
from cms.djangoapps.contentstore.toggles import use_new_video_editor, use_video_gallery_flow
from cms.djangoapps.contentstore.utils import get_editor_page_base_url
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
@@ -112,7 +112,6 @@ from openedx.core.djangolib.markup import HTML, Text
<%
use_new_editor_video = use_new_video_editor(xblock_locator.course_key)
use_new_editor_problem = use_new_problem_editor(xblock_locator.course_key)
use_new_video_gallery_flow = use_video_gallery_flow()
%>
@@ -168,7 +167,6 @@ use_new_video_gallery_flow = use_video_gallery_flow()
<nav class="nav-actions" aria-label="${_('Page Actions')}"
use-new-editor-video = ${use_new_editor_video}
use-new-editor-problem = ${use_new_editor_problem}
use-video-gallery-flow = ${use_new_video_gallery_flow}
authoring_MFE_base_url = ${get_editor_page_base_url(xblock_locator.course_key)}
data-block-type = ${xblock.scope_ids.block_type}

View File

@@ -8,13 +8,12 @@ from lms.lib.utils import is_unit
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
from cms.djangoapps.contentstore.toggles import use_new_problem_editor, use_new_video_editor, use_video_gallery_flow
from cms.djangoapps.contentstore.toggles import use_new_video_editor, use_video_gallery_flow
from cms.lib.xblock.upstream_sync import UpstreamLink
from openedx.core.djangoapps.content_tagging.toggles import is_tagging_feature_disabled
%>
<%
use_new_editor_video = use_new_video_editor(xblock.context_key)
use_new_editor_problem = use_new_problem_editor(xblock.context_key)
use_new_video_gallery_flow = use_video_gallery_flow()
use_tagging = not is_tagging_feature_disabled()
xblock_url = xblock_studio_url(xblock)
@@ -84,7 +83,6 @@ can_unlink = upstream_info.upstream_ref and not upstream_info.has_top_level_pare
% endif
"
use-new-editor-video = ${use_new_editor_video}
use-new-editor-problem = ${use_new_editor_problem}
use-video-gallery-flow = ${use_new_video_gallery_flow}
authoring_MFE_base_url = ${get_editor_page_base_url(xblock.location.course_key)}
data-block-type = ${xblock.scope_ids.block_type}

View File

@@ -1,174 +0,0 @@
<%! from django.utils.translation import gettext as _ %>
<%page expression_filter="h"/>
<%namespace name='static' file='../static_content.html'/>
<% isLaTexProblem='source_code' in editable_metadata_fields and editable_metadata_fields['source_code']['explicitly_set'] and enable_latex_compiler %>
% if isLaTexProblem:
<div class="wrapper-comp-editor latex-problem" id="editor-tab">
% else:
<div class="wrapper-comp-editor" id="editor-tab">
% endif
<section class="problem-editor editor">
<div class="row">
%if enable_markdown:
<div class="editor-bar">
<ul class="format-buttons">
<li>
<button type="button" class="header-button" data-tooltip="${_("Heading")}">
<span class="problem-editor-icon heading3">
<img class="icon" src="${static.url('images/cms-editor_heading.png')}" alt="${_("Insert a heading")}">
</span>
${_("Heading")}
</button>
</li>
<li>
<button type="button" class="multiple-choice-button" data-tooltip="${_("Multiple Choice")}">
<span class="problem-editor-icon multiple-choice">
<img class="icon" src="${static.url('images/cms-editor_radio.png')}" alt="${_("Add a multiple choice question")}">
</span>
${_("Multiple Choice")}
</button>
</li>
<li>
<button type="button" class="checks-button" data-tooltip="${_("Checkboxes")}">
<span class="problem-editor-icon checks">
<img class="icon" src="${static.url('images/cms-editor_checkbox.png')}" alt="${_("Add a question with checkboxes")}">
</span>
${_("Checkboxes")}
</button>
</li>
<li>
<button type="button" class="string-button" data-tooltip="${_("Text Input")}">
<span class="problem-editor-icon string">
<img class="icon" src="${static.url('images/cms-editor_text.png')}" alt="${_("Insert a text response")}">
</span>
${_("Text Input")}
</button>
</li>
<li>
<button type="button" class="number-button" data-tooltip="${_("Numerical Input")}">
<span class="problem-editor-icon number">
<img class="icon" src="${static.url('images/cms-editor_number.png')}" alt="${_("Insert a numerical response")}">
</span>
${_("Numerical Input")}
</button>
</li>
<li>
<button type="button" class="dropdown-button" data-tooltip="${_("Dropdown")}">
<span class="problem-editor-icon dropdown">
<img class="icon" src="${static.url('images/cms-editor_dropdown.png')}" alt="${_("Insert a dropdown response")}">
</span>
${_("Dropdown")}
</button>
</li>
<li>
<button type="button" class="explanation-button" data-tooltip="${_("Explanation")}">
<span class="problem-editor-icon explanation">
<img class="icon" src="${static.url('images/cms-editor_explanation.png')}" alt="${_("Add an explanation for this question")}">
</span>
${_("Explanation")}
</button>
</li>
</ul>
<ul class="editor-tabs">
<li><button type="button" class="xml-tab advanced-toggle" data-tab="xml">${_("Advanced Editor")}</button></li>
</ul>
</div>
<textarea class="markdown-box">${markdown}</textarea>
<article class="simple-editor-cheatsheet shown">
<div class="cheatsheet-wrapper">
<div class="row">
<h5>${_("Markdown Help")}</h5>
</div>
<div class="row">
<div class="col sample heading-1">
<img class="icon" src="${static.url('images/cms-editor_heading.png')}" alt="${_("Insert a heading")}">
<h6>${_("Heading")}</h6>
</div>
<div class="col">
<pre><code>H3
=====
</code></pre>
</div>
</div>
<div class="row">
<div class="col sample multiple-choice">
<img class="icon" src="${static.url('images/cms-editor_radio.png')}" alt="${_("Add a multiple choice question")}">
<h6>${_("Multiple Choice")}</h6>
</div>
<div class="col">
<pre><code>( ) red
( ) green
(x) blue</code></pre>
</div>
</div>
<div class="row">
<div class="col sample check-multiple">
<img class="icon" src="${static.url('images/cms-editor_checkbox.png')}" alt="${_("Add a question with checkboxes")}">
<h6>${_("Checkboxes")}</h6>
</div>
<div class="col">
<pre><code>[x] earth
[ ] wind
[x] water</code></pre>
</div>
</div>
<div class="row">
<div class="col sample string-response">
<img class="icon" src="${static.url('images/cms-editor_text.png')}" alt="${_("Insert a text response")}">
<h6>${_("Text Input")}</h6>
</div>
<div class="col">
<pre><code>= dog
or= cat
or= mouse</code></pre>
</div>
</div>
<div class="row">
<div class="col sample numerical-response">
<img class="icon" src="${static.url('images/cms-editor_number.png')}" alt="${_("Insert a numerical response")}">
<h6>${_("Numerical Input")}</h6>
</div>
<div class="col">
<pre><code>= 3.14 +- 2%</code></pre>
<pre><code>= [3.14, 3.15)</code></pre>
</div>
</div>
<div class="row">
<div class="col sample option-reponse">
<img class="icon" src="${static.url('images/cms-editor_dropdown.png')}" alt="${_("Insert a dropdown response")}">
<h6>${_("Dropdown")}</h6>
</div>
<div class="col">
<pre><code>[[wrong, (right)]]</code></pre>
</div>
</div>
<div class="row">
<h6>${_("Label")}</h6>
<div class="col">
<pre><code>&gt;&gt;What is the capital of Argentina?&lt;&lt;</code></pre>
</div>
</div>
<div class="row">
<div class="col sample explanation">
<img class="icon" src="${static.url('images/cms-editor_explanation.png')}" alt="${_("Add an explanation for this question")}">
<h6>${_("Explanation")}</h6>
</div>
<div class="col">
<pre><code>[explanation] A short explanation of the answer. [explanation]</code></pre>
</div>
</div>
</div>
</article>
</div>
%endif
</div>
<textarea class="xml-box" rows="8" cols="40">${data}</textarea>
</section>
<script type="text/template" id="simple-editor-cheatsheet">
</script>
</div>
<%include file="metadata-edit.html" />

View File

@@ -924,7 +924,6 @@ class TestPythonGradedResponse(TestSubmittingProblems):
BlockFactory.create(
parent_location=self.section.location,
category='problem',
boilerplate='circuitschematic.yaml',
display_name=name,
data=xmldata
)
@@ -948,7 +947,6 @@ class TestPythonGradedResponse(TestSubmittingProblems):
BlockFactory.create(
parent_location=self.section.location,
category='problem',
boilerplate='customgrader.yaml',
data=cfn_problem_xml,
display_name=name
)
@@ -972,7 +970,6 @@ class TestPythonGradedResponse(TestSubmittingProblems):
BlockFactory.create(
parent_location=self.section.location,
category='problem',
boilerplate='customgrader.yaml',
data=computed_xml,
display_name=name
)

View File

@@ -481,11 +481,6 @@ class ContentLibrariesTestCase(ContentLibrariesRestApiTest):
"last_draft_created": update_date.isoformat().replace('+00:00', 'Z')
})
# Now view the XBlock's studio view (including draft changes):
fragment = self._render_block_view(block_id, "studio_view")
assert 'resources' in fragment
assert 'Hello world!' in fragment['content']
@patch(
"openedx.core.djangoapps.content_libraries.rest_api.libraries.LibraryBlocksView.pagination_class.page_size",
new=2,

View File

@@ -65,10 +65,6 @@ module.exports = {
'./xmodule/js/src/capa/imageinput.js',
'./xmodule/js/src/capa/schematic.js'
],
ProblemBlockEditor: [
'./xmodule/js/src/xmodule.js',
'./xmodule/js/src/problem/edit.js'
],
SequenceBlockDisplay: [
'./xmodule/js/src/xmodule.js',
'./xmodule/js/src/sequence/display.js'

View File

@@ -48,7 +48,7 @@ from xblocks_contrib.problem import ProblemBlock as _ExtractedProblemBlock
from common.djangoapps.xblock_django.constants import (
ATTR_KEY_DEPRECATED_ANONYMOUS_USER_ID,
ATTR_KEY_USER_ID,
ATTR_KEY_USER_IS_STAFF,
ATTR_KEY_USER_IS_STAFF
)
from openedx.core.djangolib.markup import HTML, Text
from xmodule.capa import responsetypes
@@ -57,11 +57,10 @@ from xmodule.capa.inputtypes import Status
from xmodule.capa.responsetypes import LoncapaProblemError, ResponseError, StudentInputError
from xmodule.capa.util import convert_files_to_filenames, get_inner_html_from_xpath
from xmodule.contentstore.django import contentstore
from xmodule.editing_block import EditingMixin
from xmodule.raw_block import RawMixin
from xmodule.util.builtin_assets import add_css_to_fragment, add_webpack_js_to_fragment
from xmodule.util.sandboxing import SandboxService
from xmodule.x_module import ResourceTemplates, XModuleMixin, XModuleToXBlockMixin, shim_xmodule_js
from xmodule.x_module import XModuleMixin, XModuleToXBlockMixin, shim_xmodule_js
from xmodule.xml_block import XmlMixin
from .capa.xqueue_interface import XQueueService
@@ -145,7 +144,6 @@ class Randomization(String): # pylint: disable=too-few-public-methods
@XBlock.needs("user")
@XBlock.needs("i18n")
@XBlock.needs("mako")
@XBlock.needs("cache")
@XBlock.needs("sandbox")
@XBlock.needs("replace_urls")
@@ -154,9 +152,7 @@ class _BuiltInProblemBlock( # pylint: disable=too-many-public-methods,too-many-
ScorableXBlockMixin,
RawMixin,
XmlMixin,
EditingMixin,
XModuleToXBlockMixin,
ResourceTemplates,
XModuleMixin,
):
"""
@@ -180,12 +176,9 @@ class _BuiltInProblemBlock( # pylint: disable=too-many-public-methods,too-many-
is_extracted = False
resources_dir = None
has_score = True
show_in_read_only_mode = True
template_dir_name = "problem"
mako_template = "widgets/problem-edit.html"
has_author_view = True
icon_class = "problem"
@@ -411,20 +404,7 @@ class _BuiltInProblemBlock( # pylint: disable=too-many-public-methods,too-many-
"""
return self.student_view(context, show_detailed_errors=True)
def studio_view(self, _context):
"""
Return the studio view.
"""
# Not converting this to django template since this method is deprecated.
fragment = Fragment(
self.runtime.service(self, "mako").render_cms_template(self.mako_template, self.get_context())
)
add_css_to_fragment(fragment, "ProblemBlockEditor.css")
add_webpack_js_to_fragment(fragment, "ProblemBlockEditor")
shim_xmodule_js(fragment, "MarkdownEditingDescriptor")
return fragment
def handle_ajax(self, dispatch, data): # pylint: disable=too-many-locals
def handle_ajax(self, dispatch, data):
"""
This is called by courseware.block_render, to handle an AJAX call.
@@ -562,17 +542,6 @@ class _BuiltInProblemBlock( # pylint: disable=too-many-public-methods,too-many-
"""
return "latex" not in template["template_id"] or course.use_latex_compiler
def get_context(self):
_context = EditingMixin.get_context(self)
_context.update(
{
"markdown": self.markdown,
"enable_markdown": self.markdown is not None,
"enable_latex_compiler": self.use_latex_compiler,
}
)
return _context
# VS[compat]
# TODO (cpennington): Delete this method once all fall 2012 course are being
# edited in the cms

View File

@@ -1,6 +0,0 @@
<section class="problem-editor editor">
<div class="row">
<textarea class="markdown-box">markdown</textarea>
<textarea class="xml-box" rows="8" cols="40">xml</textarea>
</div>
</section>

View File

@@ -1,5 +0,0 @@
<section class="problem-editor editor">
<div class="row">
<textarea class="xml-box" rows="8" cols="40">xml only</textarea>
</div>
</section>

View File

@@ -1,1080 +0,0 @@
describe('MarkdownEditingDescriptor', function() {
describe('save stores the correct data', function() {
it('saves markdown from markdown editor', function() {
loadFixtures('problem-with-markdown.html');
this.descriptor = new MarkdownEditingDescriptor($('.problem-editor'));
const saveResult = this.descriptor.save();
expect(saveResult.metadata.markdown).toEqual('markdown');
expect(saveResult.data).toXMLEqual('<problem>\n <p>markdown</p>\n</problem>');
});
it('clears markdown when xml editor is selected', function() {
loadFixtures('problem-with-markdown.html');
this.descriptor = new MarkdownEditingDescriptor($('.problem-editor'));
this.descriptor.createXMLEditor('replace with markdown');
const saveResult = this.descriptor.save();
expect(saveResult.nullout).toEqual(['markdown']);
expect(saveResult.data).toEqual('replace with markdown');
});
it('saves xml from the xml editor', function() {
loadFixtures('problem-without-markdown.html');
this.descriptor = new MarkdownEditingDescriptor($('.problem-editor'));
const saveResult = this.descriptor.save();
expect(saveResult.nullout).toEqual(['markdown']);
expect(saveResult.data).toEqual('xml only');
});
});
describe('advanced editor opens correctly', () =>
it('click on advanced editor should work', function() {
loadFixtures('problem-with-markdown.html');
this.descriptor = new MarkdownEditingDescriptor($('.problem-editor'));
spyOn(this.descriptor, 'confirmConversionToXml').and.returnValue(true);
expect(this.descriptor.confirmConversionToXml).not.toHaveBeenCalled();
const e = jasmine.createSpyObj('e', [ 'preventDefault' ]);
this.descriptor.onShowXMLButton(e);
expect(e.preventDefault).toHaveBeenCalled();
expect(this.descriptor.confirmConversionToXml).toHaveBeenCalled();
expect($('.editor-bar').length).toEqual(0);
})
);
describe('insertMultipleChoice', function() {
it('inserts the template if selection is empty', function() {
const revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('');
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.multipleChoiceTemplate);
});
it('wraps existing text', function() {
const revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('foo\nbar');
expect(revisedSelection).toEqual('( ) foo\n( ) bar\n');
});
it('recognizes x as a selection if there is non-whitespace after x', function() {
const revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('a\nx b\nc\nx \nd\n x e');
expect(revisedSelection).toEqual('( ) a\n(x) b\n( ) c\n( ) x \n( ) d\n(x) e\n');
});
it('recognizes x as a selection if it is first non whitespace and has whitespace with other non-whitespace', function() {
const revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice(' x correct\n x \nex post facto\nb x c\nx c\nxxp');
expect(revisedSelection).toEqual('(x) correct\n( ) x \n( ) ex post facto\n( ) b x c\n(x) c\n( ) xxp\n');
});
it('removes multiple newlines but not last one', function() {
const revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice('a\nx b\n\n\nc\n');
expect(revisedSelection).toEqual('( ) a\n(x) b\n( ) c\n');
});
});
describe('insertCheckboxChoice', function() {
// Note, shares code with insertMultipleChoice. Therefore only doing smoke test.
it('inserts the template if selection is empty', function() {
const revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice('');
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.checkboxChoiceTemplate);
});
it('wraps existing text', function() {
const revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice('foo\nbar');
expect(revisedSelection).toEqual('[ ] foo\n[ ] bar\n');
});
});
describe('insertStringInput', function() {
it('inserts the template if selection is empty', function() {
const revisedSelection = MarkdownEditingDescriptor.insertStringInput('');
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.stringInputTemplate);
});
it('wraps existing text', function() {
const revisedSelection = MarkdownEditingDescriptor.insertStringInput('my text');
expect(revisedSelection).toEqual('= my text');
});
});
describe('insertNumberInput', function() {
it('inserts the template if selection is empty', function() {
const revisedSelection = MarkdownEditingDescriptor.insertNumberInput('');
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.numberInputTemplate);
});
it('wraps existing text', function() {
const revisedSelection = MarkdownEditingDescriptor.insertNumberInput('my text');
expect(revisedSelection).toEqual('= my text');
});
});
describe('insertSelect', function() {
it('inserts the template if selection is empty', function() {
const revisedSelection = MarkdownEditingDescriptor.insertSelect('');
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.selectTemplate);
});
it('wraps existing text', function() {
const revisedSelection = MarkdownEditingDescriptor.insertSelect('my text');
expect(revisedSelection).toEqual('[[my text]]');
});
});
describe('insertHeader', function() {
it('inserts the template if selection is empty', function() {
const revisedSelection = MarkdownEditingDescriptor.insertHeader('');
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.headerTemplate);
});
it('wraps existing text', function() {
const revisedSelection = MarkdownEditingDescriptor.insertHeader('my text');
expect(revisedSelection).toEqual('my text\n====\n');
});
});
describe('insertExplanation', function() {
it('inserts the template if selection is empty', function() {
const revisedSelection = MarkdownEditingDescriptor.insertExplanation('');
expect(revisedSelection).toEqual(MarkdownEditingDescriptor.explanationTemplate);
});
it('wraps existing text', function() {
const revisedSelection = MarkdownEditingDescriptor.insertExplanation('my text');
expect(revisedSelection).toEqual('[explanation]\nmy text\n[explanation]');
});
});
describe('markdownToXml', function() {
it('converts raw text to paragraph', function() {
const data = MarkdownEditingDescriptor.markdownToXml('foo');
expect(data).toXMLEqual('<problem>\n <p>foo</p>\n</problem>');
});
// test default templates
it('converts numerical response to xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`A numerical response problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value.
The answer is correct if it is within a specified numerical tolerance of the expected answer.
Enter the numerical value of Pi:
= 3.14159 +- .02
Enter the approximate value of 502*9:
= 502*9 +- 15%
Enter the approximate number of atoms in a mol:
= 6.022e23 +- 10%
Enter the number of fingers on a human hand:
= 5
Range tolerance case
= [6, 7]
= (1, 2)
If first and last symbols are not brackets, or they are not closed, stringresponse will appear.
= (7), 7
= (1+2
Just input 100 test. Stringresponse will appear:
= 100 test
[Explanation]
Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.
Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.
If you look at your hand, you can count that you have five fingers.
[Explanation]\
`);
expect(data).toXMLEqual(`<problem>
<p>A numerical response problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value.</p>
<p>The answer is correct if it is within a specified numerical tolerance of the expected answer.</p>
<p>Enter the numerical value of Pi:</p>
<numericalresponse answer="3.14159">
<responseparam type="tolerance" default=".02" />
<formulaequationinput />
</numericalresponse>
<p>Enter the approximate value of 502*9:</p>
<numericalresponse answer="502*9">
<responseparam type="tolerance" default="15%" />
<formulaequationinput />
</numericalresponse>
<p>Enter the approximate number of atoms in a mol:</p>
<numericalresponse answer="6.022e23">
<responseparam type="tolerance" default="10%" />
<formulaequationinput />
</numericalresponse>
<p>Enter the number of fingers on a human hand:</p>
<numericalresponse answer="5">
<formulaequationinput />
</numericalresponse>
<p>Range tolerance case</p>
<numericalresponse answer="[6, 7]">
<formulaequationinput />
</numericalresponse>
<numericalresponse answer="(1, 2)">
<formulaequationinput />
</numericalresponse>
<p>If first and last symbols are not brackets, or they are not closed, stringresponse will appear.</p>
<stringresponse answer="(7), 7" type="ci" >
<textline size="20"/>
</stringresponse>
<stringresponse answer="(1+2" type="ci" >
<textline size="20"/>
</stringresponse>
<p>Just input 100 test. Stringresponse will appear:</p>
<stringresponse answer="100 test" type="ci" >
<textline size="20"/>
</stringresponse>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.</p>
<p>Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.</p>
<p>If you look at your hand, you can count that you have five fingers.</p>
</div>
</solution>
</problem>`);
});
it('will convert 0 as a numerical response (instead of string response)', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
Enter 0 with a tolerance:
= 0 +- .02\
`);
expect(data).toXMLEqual(`<problem>
<numericalresponse answer="0">
<p>Enter 0 with a tolerance:</p>
<responseparam type="tolerance" default=".02"/>
<formulaequationinput/>
</numericalresponse>
</problem>`);
});
it('markup with additional answer does not break numerical response', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
Enter 1 with a tolerance:
= 1 +- .02
or= 2\
`);
expect(data).toXMLEqual(`<problem>
<numericalresponse answer="1">
<p>Enter 1 with a tolerance:</p>
<responseparam type="tolerance" default=".02"/>
<additional_answer answer="2"/>
<formulaequationinput/>
</numericalresponse>
</problem>`
);
});
it('markup for numerical with multiple additional answers renders correctly', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
Enter 1 with a tolerance:
= 1 +- .02
or= 2
or= 3\
`);
expect(data).toXMLEqual(`<problem>
<numericalresponse answer="1">
<p>Enter 1 with a tolerance:</p>
<responseparam type="tolerance" default=".02"/>
<additional_answer answer="2"/>
<additional_answer answer="3"/>
<formulaequationinput/>
</numericalresponse>
</problem>`
);
});
it('Do not render ranged/tolerance/alphabetical additional answers for numerical response', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
Enter 1 with a tolerance:
= 1 +- .02
or= 2
or= 3 +- 0.1
or= [4,6]
or= ABC
or= 7\
`);
expect(data).toXMLEqual(`<problem>
<numericalresponse answer="1">
<p>Enter 1 with a tolerance:</p>
<responseparam type="tolerance" default=".02"/>
<additional_answer answer="2"/>
<additional_answer answer="7"/>
<formulaequationinput/>
</numericalresponse>
</problem>`
);
});
it('markup with feedback renders correctly in additional answer for numerical response', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
Enter 1 with a tolerance:
= 100 +- .02 {{ main feedback }}
or= 10 {{ additional feedback }}\
`);
expect(data).toXMLEqual(`<problem>
<numericalresponse answer="100">
<p>Enter 1 with a tolerance:</p>
<responseparam type="tolerance" default=".02"/>
<additional_answer answer="10">
<correcthint>additional feedback</correcthint>
</additional_answer>
<formulaequationinput/>
<correcthint>main feedback</correcthint>
</numericalresponse>
</problem>`
);
});
it('converts multiple choice to xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.
One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.
>>What Apple device competed with the portable CD player?<<
( ) The iPad
( ) Napster
(x) The iPod
( ) The vegetable peeler
( ) Android
( ) The Beatles
[Explanation]
The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.
[Explanation]\
`);
expect(data).toXMLEqual(`<problem>
<multiplechoiceresponse>
<p>A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.</p>
<p>One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.</p>
<label>What Apple device competed with the portable CD player?</label>
<choicegroup type="MultipleChoice">
<choice correct="false">The iPad</choice>
<choice correct="false">Napster</choice>
<choice correct="true">The iPod</choice>
<choice correct="false">The vegetable peeler</choice>
<choice correct="false">Android</choice>
<choice correct="false">The Beatles</choice>
</choicegroup>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.</p>
</div>
</solution>
</multiplechoiceresponse>
</problem>`);
});
it('converts multiple choice shuffle to xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.
One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.
What Apple device competed with the portable CD player?
(!x@) The iPad
(@) Napster
() The iPod
( ) The vegetable peeler
( ) Android
(@) The Beatles
[Explanation]
The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.
[Explanation]\
`);
expect(data).toXMLEqual(`\
<problem>
<multiplechoiceresponse>
<p>A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.</p>
<p>One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.</p>
<p>What Apple device competed with the portable CD player?</p>
<choicegroup type="MultipleChoice" shuffle="true">
<choice correct="true" fixed="true">The iPad</choice>
<choice correct="false" fixed="true">Napster</choice>
<choice correct="false">The iPod</choice>
<choice correct="false">The vegetable peeler</choice>
<choice correct="false">Android</choice>
<choice correct="false" fixed="true">The Beatles</choice>
</choicegroup>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.</p>
</div>
</solution>
</multiplechoiceresponse>
</problem>`);
});
it('converts a series of multiplechoice to xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`bleh
(!x) a
() b
() c
yatta
( ) x
( ) y
(x) z
testa
(!) i
( ) ii
(x) iii
[Explanation]
When the student is ready, the explanation appears.
[Explanation]\
`);
expect(data).toXMLEqual(`\
<problem>
<p>bleh</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice" shuffle="true">
<choice correct="true">a</choice>
<choice correct="false">b</choice>
<choice correct="false">c</choice>
</choicegroup>
</multiplechoiceresponse>
<p>yatta</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false">x</choice>
<choice correct="false">y</choice>
<choice correct="true">z</choice>
</choicegroup>
</multiplechoiceresponse>
<p>testa</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice" shuffle="true">
<choice correct="false">i</choice>
<choice correct="false">ii</choice>
<choice correct="true">iii</choice>
</choicegroup>
</multiplechoiceresponse>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>When the student is ready, the explanation appears.</p>
</div>
</solution>
</problem>`);
});
it('converts OptionResponse to xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`OptionResponse gives a limited set of options for students to respond with, and presents those options in a format that encourages them to search for a specific answer rather than being immediately presented with options from which to recognize the correct answer.
The answer options and the identification of the correct answer is defined in the <b>optioninput</b> tag.
Translation between Option Response and __________ is extremely straightforward:
[[(Multiple Choice), String Response, Numerical Response, External Response, Image Response]]
[Explanation]
Multiple Choice also allows students to select from a variety of pre-written responses, although the format makes it easier for students to read very long response options. Optionresponse also differs slightly because students are more likely to think of an answer and then search for it rather than relying purely on recognition to answer the question.
[Explanation]\
`);
expect(data).toXMLEqual(`\
<problem>
<optionresponse>
<p>OptionResponse gives a limited set of options for students to respond with, and presents those options in a format that encourages them to search for a specific answer rather than being immediately presented with options from which to recognize the correct answer.</p>
<p>The answer options and the identification of the correct answer is defined in the <b>optioninput</b> tag.</p>
<p>Translation between Option Response and __________ is extremely straightforward:</p>
<optioninput options="('Multiple Choice','String Response','Numerical Response','External Response','Image Response')" correct="Multiple Choice"/>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Multiple Choice also allows students to select from a variety of pre-written responses, although the format makes it easier for students to read very long response options. Optionresponse also differs slightly because students are more likely to think of an answer and then search for it rather than relying purely on recognition to answer the question.</p>
</div>
</solution>
</optionresponse>
</problem>`);
});
it('converts StringResponse to xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box.
The answer is correct if it matches every character of the expected answer. This can be a problem with international spelling, dates, or anything where the format of the answer is not clear.
Which US state has Lansing as its capital?
= Michigan
[Explanation]
Lansing is the capital of Michigan, although it is not Michgan's largest city, or even the seat of the county in which it resides.
[Explanation]\
`);
expect(data).toXMLEqual(`\
<problem>
<stringresponse answer="Michigan" type="ci">
<p>A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box.</p>
<p>The answer is correct if it matches every character of the expected answer. This can be a problem with international spelling, dates, or anything where the format of the answer is not clear.</p>
<p>Which US state has Lansing as its capital?</p>
<textline size="20"/>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Lansing is the capital of Michigan, although it is not Michgan's largest city, or even the seat of the county in which it resides.</p>
</div>
</solution>
</stringresponse>
</problem>`);
});
it('converts StringResponse with regular expression to xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`Who lead the civil right movement in the United States of America?
= | \w*\.?\s*Luther King\s*.*
[Explanation]
Test Explanation.
[Explanation]\
`);
expect(data).toXMLEqual(`\
<problem>
<stringresponse answer="w*.?s*Luther Kings*.*" type="ci regexp">
<p>Who lead the civil right movement in the United States of America?</p>
<textline size="20"/>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Test Explanation.</p>
</div>
</solution>
</stringresponse>
</problem>`);
});
it('converts StringResponse with multiple answers to xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`Who lead the civil right movement in the United States of America?
= Dr. Martin Luther King Jr.
or= Doctor Martin Luther King Junior
or= Martin Luther King
or= Martin Luther King Junior
[Explanation]
Test Explanation.
[Explanation]\
`);
expect(data).toXMLEqual(`\
<problem>
<stringresponse answer="Dr. Martin Luther King Jr." type="ci">
<p>Who lead the civil right movement in the United States of America?</p>
<additional_answer answer="Doctor Martin Luther King Junior"/>
<additional_answer answer="Martin Luther King"/>
<additional_answer answer="Martin Luther King Junior"/>
<textline size="20"/>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Test Explanation.</p>
</div>
</solution>
</stringresponse>
</problem>`);
});
it('converts StringResponse with multiple answers and regular expressions to xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`Write a number from 1 to 4.
=| ^One$
or= two
or= ^thre+
or= ^4|Four$
[Explanation]
Test Explanation.
[Explanation]\
`);
expect(data).toXMLEqual(`\
<problem>
<stringresponse answer="^One$" type="ci regexp">
<p>Write a number from 1 to 4.</p>
<additional_answer answer="two"/>
<additional_answer answer="^thre+"/>
<additional_answer answer="^4|Four$"/>
<textline size="20"/>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Test Explanation.</p>
</div>
</solution>
</stringresponse>
</problem>`);
});
// test labels
it('converts markdown labels to label attributes', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`>>Who lead the civil right movement in the United States of America?<<
= | \w*\.?\s*Luther King\s*.*
[Explanation]
Test Explanation.
[Explanation]\
`);
expect(data).toXMLEqual(`\
<problem>
<stringresponse answer="w*.?s*Luther Kings*.*" type="ci regexp">
<label>Who lead the civil right movement in the United States of America?</label>
<textline size="20"/>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Test Explanation.</p>
</div>
</solution>
</stringresponse>
</problem>`);
});
it('handles multiple questions with labels', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
France is a country in Europe.
>>What is the capital of France?<<
= Paris
Germany is a country in Europe, too.
>>What is the capital of Germany?<<
( ) Bonn
( ) Hamburg
(x) Berlin
( ) Donut\
`);
expect(data).toXMLEqual(`\
<problem>
<p>France is a country in Europe.</p>
<label>What is the capital of France?</label>
<stringresponse answer="Paris" type="ci" >
<textline size="20"/>
</stringresponse>
<p>Germany is a country in Europe, too.</p>
<label>What is the capital of Germany?</label>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false">Bonn</choice>
<choice correct="false">Hamburg</choice>
<choice correct="true">Berlin</choice>
<choice correct="false">Donut</choice>
</choicegroup>
</multiplechoiceresponse>
</problem>`);
});
it('tests multiple questions with only one label', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
France is a country in Europe.
>>What is the capital of France?<<
= Paris
Germany is a country in Europe, too.
What is the capital of Germany?
( ) Bonn
( ) Hamburg
(x) Berlin
( ) Donut\
`);
expect(data).toXMLEqual(`\
<problem>
<p>France is a country in Europe.</p>
<label>What is the capital of France?</label>
<stringresponse answer="Paris" type="ci" >
<textline size="20"/>
</stringresponse>
<p>Germany is a country in Europe, too.</p>
<p>What is the capital of Germany?</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false">Bonn</choice>
<choice correct="false">Hamburg</choice>
<choice correct="true">Berlin</choice>
<choice correct="false">Donut</choice>
</choicegroup>
</multiplechoiceresponse>
</problem>`);
});
it('adds labels to formulae', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>Enter the numerical value of Pi:<<
= 3.14159 +- .02\
`);
expect(data).toXMLEqual(`<problem>
<numericalresponse answer="3.14159">
<label>Enter the numerical value of Pi:</label>
<responseparam type="tolerance" default=".02"/>
<formulaequationinput/>
</numericalresponse>
</problem>`);
});
// test oddities
it('converts headers and oddities to xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`Not a header
A header
==============
Multiple choice w/ parentheticals
( ) option (with parens)
( ) xd option (x)
()) parentheses inside
() no space b4 close paren
Choice checks
[ ] option1 [x]
[x] correct
[x] redundant
[(] distractor
[] no space
Option with multiple correct ones
[[one option, (correct one), (should not be correct)]]
Option with embedded parens
[[My (heart), another, (correct)]]
What happens w/ empty correct options?
[[()]]
[Explanation]see[/expLanation]
[explanation]
orphaned start
No p tags in the below
<script type='javascript'>
var two = 2;
console.log(two * 2);
</script>
But in this there should be
<div>
Great ideas require offsetting.
bad tests require drivel
</div>
[code]
Code should be nicely monospaced.
[/code]\
`);
expect(data).toXMLEqual(`\
<problem>
<p>Not a header</p>
<h3 class="hd hd-2 problem-header">A header</h3>
<p>Multiple choice w/ parentheticals</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false">option (with parens)</choice>
<choice correct="false">xd option (x)</choice>
<choice correct="false">parentheses inside</choice>
<choice correct="false">no space b4 close paren</choice>
</choicegroup>
</multiplechoiceresponse>
<p>Choice checks</p>
<choiceresponse>
<checkboxgroup>
<choice correct="false">option1 [x]</choice>
<choice correct="true">correct</choice>
<choice correct="true">redundant</choice>
<choice correct="false">distractor</choice>
<choice correct="false">no space</choice>
</checkboxgroup>
</choiceresponse>
<p>Option with multiple correct ones</p>
<optionresponse>
<optioninput options="('one option','correct one','should not be correct')" correct="correct one"></optioninput>
</optionresponse>
<p>Option with embedded parens</p>
<optionresponse>
<optioninput options="('My (heart)','another','correct')" correct="correct"></optioninput>
</optionresponse>
<p>What happens w/ empty correct options?</p>
<optionresponse>
<optioninput options="('')" correct=""></optioninput>
</optionresponse>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>see</p>
</div>
</solution>
<p>[explanation]</p>
<p>orphaned start</p>
<p>No p tags in the below</p>
<script type='javascript'>
var two = 2;
console.log(two * 2);
</script>
<p>But in this there should be</p>
<div>
<p>Great ideas require offsetting.</p>
<p>bad tests require drivel</p>
</div>
<pre>
<code>Code should be nicely monospaced.
</code>
</pre>
</problem>`);
});
it('can separate responsetypes based on ---', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
Multiple choice problems allow learners to select only one option. Learners can see all the options along with the problem text.
>>Which of the following countries has the largest population?<<
( ) Brazil {{ timely feedback -- explain why an almost correct answer is wrong }}
( ) Germany
(x) Indonesia
( ) Russia
[explanation]
According to September 2014 estimates:
The population of Indonesia is approximately 250 million.
The population of Brazil is approximately 200 million.
The population of Russia is approximately 146 million.
The population of Germany is approximately 81 million.
[explanation]
---
Checkbox problems allow learners to select multiple options. Learners can see all the options along with the problem text.
>>The following languages are in the Indo-European family:<<
[x] Urdu
[ ] Finnish
[x] Marathi
[x] French
[ ] Hungarian
Note: Make sure you select all of the correct options—there may be more than one!
[explanation]
Urdu, Marathi, and French are all Indo-European languages, while Finnish and Hungarian are in the Uralic family.
[explanation]
\
`);
expect(data).toXMLEqual(`\
<problem>
<multiplechoiceresponse>
<p>Multiple choice problems allow learners to select only one option. Learners can see all the options along with the problem text.</p>
<label>Which of the following countries has the largest population?</label>
<choicegroup type="MultipleChoice">
<choice correct="false">Brazil <choicehint>timely feedback -- explain why an almost correct answer is wrong</choicehint>
</choice>
<choice correct="false">Germany</choice>
<choice correct="true">Indonesia</choice>
<choice correct="false">Russia</choice>
</choicegroup>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>According to September 2014 estimates:</p>
<p>The population of Indonesia is approximately 250 million.</p>
<p>The population of Brazil is approximately 200 million.</p>
<p>The population of Russia is approximately 146 million.</p>
<p>The population of Germany is approximately 81 million.</p>
</div>
</solution>
</multiplechoiceresponse>
<choiceresponse>
<p>Checkbox problems allow learners to select multiple options. Learners can see all the options along with the problem text.</p>
<label>The following languages are in the Indo-European family:</label>
<checkboxgroup>
<choice correct="true">Urdu</choice>
<choice correct="false">Finnish</choice>
<choice correct="true">Marathi</choice>
<choice correct="true">French</choice>
<choice correct="false">Hungarian</choice>
</checkboxgroup>
<p>Note: Make sure you select all of the correct options—there may be more than one!</p>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Urdu, Marathi, and French are all Indo-European languages, while Finnish and Hungarian are in the Uralic family.</p>
</div>
</solution>
</choiceresponse>
</problem>\
`);
});
it('can separate other things based on ---', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
Multiple choice problems allow learners to select only one option. Learners can see all the options along with the problem text.
---
>>Which of the following countries has the largest population?<<
( ) Brazil {{ timely feedback -- explain why an almost correct answer is wrong }}
( ) Germany
(x) Indonesia
( ) Russia
[explanation]
According to September 2014 estimates:
The population of Indonesia is approximately 250 million.
The population of Brazil is approximately 200 million.
The population of Russia is approximately 146 million.
The population of Germany is approximately 81 million.
[explanation]\
`);
expect(data).toXMLEqual(`\
<problem>
<p>Multiple choice problems allow learners to select only one option. Learners can see all the options along with the problem text.</p>
<multiplechoiceresponse>
<label>Which of the following countries has the largest population?</label>
<choicegroup type="MultipleChoice">
<choice correct="false">Brazil <choicehint>timely feedback -- explain why an almost correct answer is wrong</choicehint>
</choice>
<choice correct="false">Germany</choice>
<choice correct="true">Indonesia</choice>
<choice correct="false">Russia</choice>
</choicegroup>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>According to September 2014 estimates:</p>
<p>The population of Indonesia is approximately 250 million.</p>
<p>The population of Brazil is approximately 200 million.</p>
<p>The population of Russia is approximately 146 million.</p>
<p>The population of Germany is approximately 81 million.</p>
</div>
</solution>
</multiplechoiceresponse>
</problem>\
`);
});
it('can do separation if spaces are present around ---', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>The following languages are in the Indo-European family:||There are three correct choices.<<
[x] Urdu
[ ] Finnish
[x] Marathi
[x] French
[ ] Hungarian
---
>>Which of the following countries has the largest population?||You have only choice.<<
( ) Brazil {{ timely feedback -- explain why an almost correct answer is wrong }}
( ) Germany
(x) Indonesia
( ) Russia\
`);
expect(data).toXMLEqual(`\
<problem>
<choiceresponse>
<label>The following languages are in the Indo-European family:</label>
<description>There are three correct choices.</description>
<checkboxgroup>
<choice correct="true">Urdu</choice>
<choice correct="false">Finnish</choice>
<choice correct="true">Marathi</choice>
<choice correct="true">French</choice>
<choice correct="false">Hungarian</choice>
</checkboxgroup>
</choiceresponse>
<multiplechoiceresponse>
<label>Which of the following countries has the largest population?</label>
<description>You have only choice.</description>
<choicegroup type="MultipleChoice">
<choice correct="false">Brazil
<choicehint>timely feedback -- explain why an almost correct answer is wrong</choicehint>
</choice>
<choice correct="false">Germany</choice>
<choice correct="true">Indonesia</choice>
<choice correct="false">Russia</choice>
</choicegroup>
</multiplechoiceresponse>
</problem>\
`);
});
it('can extract question description', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>The following languages are in the Indo-European family:||Choose wisely.<<
[x] Urdu
[ ] Finnish
[x] Marathi
[x] French
[ ] Hungarian\
`);
expect(data).toXMLEqual(`\
<problem>
<choiceresponse>
<label>The following languages are in the Indo-European family:</label>
<description>Choose wisely.</description>
<checkboxgroup>
<choice correct="true">Urdu</choice>
<choice correct="false">Finnish</choice>
<choice correct="true">Marathi</choice>
<choice correct="true">French</choice>
<choice correct="false">Hungarian</choice>
</checkboxgroup>
</choiceresponse>
</problem>\
`);
});
it('can handle question and description spanned across multiple lines', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>The following languages
are in the
Indo-European family:
||
first second
third
<<
[x] Urdu
[ ] Finnish
[x] Marathi\
`);
expect(data).toXMLEqual(`\
<problem>
<choiceresponse>
<label>The following languages are in the Indo-European family:</label>
<description>first second third</description>
<checkboxgroup>
<choice correct="true">Urdu</choice>
<choice correct="false">Finnish</choice>
<choice correct="true">Marathi</choice>
</checkboxgroup>
</choiceresponse>
</problem>\
`);
});
it('will not add empty description', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>The following languages are in the Indo-European family:||<<
[x] Urdu
[ ] Finnish\
`);
expect(data).toXMLEqual(`\
<problem>
<choiceresponse>
<label>The following languages are in the Indo-European family:</label>
<checkboxgroup>
<choice correct="true">Urdu</choice>
<choice correct="false">Finnish</choice>
</checkboxgroup>
</choiceresponse>
</problem>\
`);
});
it('should throw error if an option does not have any text associated with it', function() {
let problemContent = `\
>>The following languages are in the Indo-European family:||<<
[ ]
[x] Urdu
[ ] Finnish\
`
expect(function(){ MarkdownEditingDescriptor.markdownToXml(problemContent); }).toThrow(
new Error(gettext("An answer option has been left blank. Please review and edit the component."))
);
problemContent = `\
>>The following languages are in the Indo-European family:||<<
( )
(x) Urdu
( ) Finnish\
`
expect(function(){ MarkdownEditingDescriptor.markdownToXml(problemContent); }).toThrow(
new Error(gettext("An answer option has been left blank. Please review and edit the component."))
);
});
});
});

View File

@@ -1,1029 +0,0 @@
// This file tests the parsing of extended-hints, double bracket sections {{ .. }}
// for all sorts of markdown.
describe('Markdown to xml extended hint dropdown', function() {
it('produces xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
Translation between Dropdown and ________ is straightforward.
[[
(Multiple Choice) {{ Good Job::Yes, multiple choice is the right answer. }}
Text Input {{ No, text input problems don't present options. }}
Numerical Input {{ No, numerical input problems don't present options. }}
]]
Clowns have funny _________ to make people laugh.
[[
dogs {{ NOPE::Not dogs, not cats, not toads }}
(FACES) {{ With lots of makeup, doncha know?}}
money {{ Clowns don't have any money, of course }}
donkeys {{don't be an ass.}}
-no hint-
]]
\
`);
expect(data).toXMLEqual(`\
<problem>
<p>Translation between Dropdown and ________ is straightforward.</p>
<optionresponse>
<optioninput>
<option correct="True">Multiple Choice
<optionhint label="Good Job">Yes, multiple choice is the right answer.</optionhint>
</option>
<option correct="False">Text Input
<optionhint>No, text input problems don't present options.</optionhint>
</option>
<option correct="False">Numerical Input
<optionhint>No, numerical input problems don't present options.</optionhint>
</option>
</optioninput>
</optionresponse>
<p>Clowns have funny _________ to make people laugh.</p>
<optionresponse>
<optioninput>
<option correct="False">dogs
<optionhint label="NOPE">Not dogs, not cats, not toads</optionhint>
</option>
<option correct="True">FACES
<optionhint>With lots of makeup, doncha know?</optionhint>
</option>
<option correct="False">money
<optionhint>Clowns don't have any money, of course</optionhint>
</option>
<option correct="False">donkeys
<optionhint>don't be an ass.</optionhint>
</option>
<option correct="False">-no hint-</option>
</optioninput>
</optionresponse>
</problem>\
`);
});
it('produces xml with demand hint', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
Translation between Dropdown and ________ is straightforward.
[[
(Right) {{ Good Job::yes }}
Wrong 1 {{no}}
Wrong 2 {{ Label::no }}
]]
|| 0) zero ||
|| 1) one ||
|| 2) two ||\
`);
expect(data).toXMLEqual(`\
<problem>
<optionresponse>
<p>Translation between Dropdown and ________ is straightforward.</p>
<optioninput>
<option correct="True">Right <optionhint label="Good Job">yes</optionhint>
</option>
<option correct="False">Wrong 1 <optionhint>no</optionhint>
</option>
<option correct="False">Wrong 2 <optionhint label="Label">no</optionhint>
</option>
</optioninput>
</optionresponse>
<demandhint>
<hint>0) zero</hint>
<hint>1) one</hint>
<hint>2) two</hint>
</demandhint>
</problem>\
`);
});
it('produces xml with single-line markdown syntax', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
A Question ________ is answered.
[[(Right), Wrong 1, Wrong 2]]
|| 0) zero ||
|| 1) one ||\
`);
expect(data).toXMLEqual(`\
<problem>
<optionresponse>
<p>A Question ________ is answered.</p>
<optioninput options="('Right','Wrong 1','Wrong 2')" correct="Right"/>
</optionresponse>
<demandhint>
<hint>0) zero</hint>
<hint>1) one</hint>
</demandhint>
</problem>\
`);
});
it('produces xml with fewer newlines', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>q1<<
[[ (aa) {{ hint1 }}
bb
cc {{ hint2 }} ]]\
`);
expect(data).toXMLEqual(`\
<problem>
<optionresponse>
<label>q1</label>
<optioninput>
<option correct="True">aa <optionhint>hint1</optionhint>
</option>
<option correct="False">bb</option>
<option correct="False">cc <optionhint>hint2</optionhint>
</option>
</optioninput>
</optionresponse>
</problem>\
`);
});
it('produces xml even with lots of whitespace', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>q1<<
[[
aa {{ hint1 }}
bb {{ hint2 }}
(cc)
]]\
`);
expect(data).toXMLEqual(`\
<problem>
<optionresponse>
<label>q1</label>
<optioninput>
<option correct="False">aa <optionhint>hint1</optionhint>
</option>
<option correct="False">bb <optionhint>hint2</optionhint>
</option>
<option correct="True">cc</option>
</optioninput>
</optionresponse>
</problem>\
`);
});
});
describe('Markdown to xml extended hint checkbox', function() {
it('produces xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>Select all the fruits from the list<<
[x] Apple {{ selected: You're right that apple is a fruit. }, {unselected: Remember that apple is also a fruit.}}
[ ] Mushroom {{U: You're right that mushrooms aren't fruit}, { selected: Mushroom is a fungus, not a fruit.}}
[x] Grape {{ selected: You're right that grape is a fruit }, {unselected: Remember that grape is also a fruit.}}
[ ] Mustang
[ ] Camero {{S:I don't know what a Camero is but it isn't a fruit.},{U:What is a camero anyway?}}
{{ ((A*B)) You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
{{ ((B*C)) You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit. }}
>>Select all the vegetables from the list<<
[ ] Banana {{ selected: No, sorry, a banana is a fruit. }, {unselected: poor banana.}}
[ ] Ice Cream
[ ] Mushroom {{U: You're right that mushrooms aren't vegetables.}, { selected: Mushroom is a fungus, not a vegetable.}}
[x] Brussel Sprout {{S: Brussel sprouts are vegetables.}, {u: Brussel sprout is the only vegetable in this list.}}
{{ ((A*B)) Making a banana split? }}
{{ ((B*D)) That will make a horrible dessert: a brussel sprout split? }}\
`);
expect(data).toXMLEqual(`\
<problem>
<label>Select all the fruits from the list</label>
<choiceresponse>
<checkboxgroup>
<choice correct="true">Apple
<choicehint selected="true">You're right that apple is a fruit.</choicehint>
<choicehint selected="false">Remember that apple is also a fruit.</choicehint>
</choice>
<choice correct="false">Mushroom
<choicehint selected="true">Mushroom is a fungus, not a fruit.</choicehint>
<choicehint selected="false">You're right that mushrooms aren't fruit</choicehint>
</choice>
<choice correct="true">Grape
<choicehint selected="true">You're right that grape is a fruit</choicehint>
<choicehint selected="false">Remember that grape is also a fruit.</choicehint>
</choice>
<choice correct="false">Mustang</choice>
<choice correct="false">Camero
<choicehint selected="true">I don't know what a Camero is but it isn't a fruit.</choicehint>
<choicehint selected="false">What is a camero anyway?</choicehint>
</choice>
<compoundhint value="A*B">You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
<compoundhint value="B*C">You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
</checkboxgroup>
</choiceresponse>
<label>Select all the vegetables from the list</label>
<choiceresponse>
<checkboxgroup>
<choice correct="false">Banana
<choicehint selected="true">No, sorry, a banana is a fruit.</choicehint>
<choicehint selected="false">poor banana.</choicehint>
</choice>
<choice correct="false">Ice Cream</choice>
<choice correct="false">Mushroom
<choicehint selected="true">Mushroom is a fungus, not a vegetable.</choicehint>
<choicehint selected="false">You're right that mushrooms aren't vegetables.</choicehint>
</choice>
<choice correct="true">Brussel Sprout
<choicehint selected="true">Brussel sprouts are vegetables.</choicehint>
<choicehint selected="false">Brussel sprout is the only vegetable in this list.</choicehint>
</choice>
<compoundhint value="A*B">Making a banana split?</compoundhint>
<compoundhint value="B*D">That will make a horrible dessert: a brussel sprout split?</compoundhint>
</checkboxgroup>
</choiceresponse>
</problem>\
`);
});
it('produces xml also with demand hints', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>Select all the fruits from the list<<
[x] Apple {{ selected: You're right that apple is a fruit. }, {unselected: Remember that apple is also a fruit.}}
[ ] Mushroom {{U: You're right that mushrooms aren't fruit}, { selected: Mushroom is a fungus, not a fruit.}}
[x] Grape {{ selected: You're right that grape is a fruit }, {unselected: Remember that grape is also a fruit.}}
[ ] Mustang
[ ] Camero {{S:I don't know what a Camero is but it isn't a fruit.},{U:What is a camero anyway?}}
{{ ((A*B)) You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
{{ ((B*C)) You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
>>Select all the vegetables from the list<<
[ ] Banana {{ selected: No, sorry, a banana is a fruit. }, {unselected: poor banana.}}
[ ] Ice Cream
[ ] Mushroom {{U: You're right that mushrooms aren't vegatbles}, { selected: Mushroom is a fungus, not a vegetable.}}
[x] Brussel Sprout {{S: Brussel sprouts are vegetables.}, {u: Brussel sprout is the only vegetable in this list.}}
{{ ((A*B)) Making a banana split? }}
{{ ((B*D)) That will make a horrible dessert: a brussel sprout split? }}
|| Hint one.||
|| Hint two. ||
|| Hint three. ||\
`);
expect(data).toXMLEqual(`\
<problem>
<label>Select all the fruits from the list</label>
<choiceresponse>
<checkboxgroup>
<choice correct="true">Apple
<choicehint selected="true">You're right that apple is a fruit.</choicehint>
<choicehint selected="false">Remember that apple is also a fruit.</choicehint>
</choice>
<choice correct="false">Mushroom
<choicehint selected="true">Mushroom is a fungus, not a fruit.</choicehint>
<choicehint selected="false">You're right that mushrooms aren't fruit</choicehint>
</choice>
<choice correct="true">Grape
<choicehint selected="true">You're right that grape is a fruit</choicehint>
<choicehint selected="false">Remember that grape is also a fruit.</choicehint>
</choice>
<choice correct="false">Mustang</choice>
<choice correct="false">Camero
<choicehint selected="true">I don't know what a Camero is but it isn't a fruit.</choicehint>
<choicehint selected="false">What is a camero anyway?</choicehint>
</choice>
<compoundhint value="A*B">You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
<compoundhint value="B*C">You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
</checkboxgroup>
</choiceresponse>
<label>Select all the vegetables from the list</label>
<choiceresponse>
<checkboxgroup>
<choice correct="false">Banana
<choicehint selected="true">No, sorry, a banana is a fruit.</choicehint>
<choicehint selected="false">poor banana.</choicehint>
</choice>
<choice correct="false">Ice Cream</choice>
<choice correct="false">Mushroom
<choicehint selected="true">Mushroom is a fungus, not a vegetable.</choicehint>
<choicehint selected="false">You're right that mushrooms aren't vegatbles</choicehint>
</choice>
<choice correct="true">Brussel Sprout
<choicehint selected="true">Brussel sprouts are vegetables.</choicehint>
<choicehint selected="false">Brussel sprout is the only vegetable in this list.</choicehint>
</choice>
<compoundhint value="A*B">Making a banana split?</compoundhint>
<compoundhint value="B*D">That will make a horrible dessert: a brussel sprout split?</compoundhint>
</checkboxgroup>
</choiceresponse>
<demandhint>
<hint>Hint one.</hint>
<hint>Hint two.</hint>
<hint>Hint three.</hint>
</demandhint>
</problem>\
`);
});
});
describe('Markdown to xml extended hint multiple choice', function() {
it('produces xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>Select the fruit from the list<<
() Mushroom {{ Mushroom is a fungus, not a fruit.}}
() Potato
(x) Apple {{ OUTSTANDING::Apple is indeed a fruit.}}
>>Select the vegetables from the list<<
() Mushroom {{ Mushroom is a fungus, not a vegetable.}}
(x) Potato {{ Potato is a root vegetable. }}
() Apple {{ OOPS::Apple is a fruit.}}\
`);
expect(data).toXMLEqual(`\
<problem>
<label>Select the fruit from the list</label>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false">Mushroom
<choicehint>Mushroom is a fungus, not a fruit.</choicehint>
</choice>
<choice correct="false">Potato</choice>
<choice correct="true">Apple
<choicehint label="OUTSTANDING">Apple is indeed a fruit.</choicehint>
</choice>
</choicegroup>
</multiplechoiceresponse>
<label>Select the vegetables from the list</label>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false">Mushroom
<choicehint>Mushroom is a fungus, not a vegetable.</choicehint>
</choice>
<choice correct="true">Potato
<choicehint>Potato is a root vegetable.</choicehint>
</choice>
<choice correct="false">Apple
<choicehint label="OOPS">Apple is a fruit.</choicehint>
</choice>
</choicegroup>
</multiplechoiceresponse>
</problem>\
`);
});
it('produces xml with demand hints', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>Select the fruit from the list<<
() Mushroom {{ Mushroom is a fungus, not a fruit.}}
() Potato
(x) Apple {{ OUTSTANDING::Apple is indeed a fruit.}}
|| 0) spaces on previous line. ||
|| 1) roses are red. ||
>>Select the vegetables from the list<<
() Mushroom {{ Mushroom is a fungus, not a vegetable.}}
(x) Potato {{ Potato is a root vegetable. }}
() Apple {{ OOPS::Apple is a fruit.}}
|| 2) where are the lions? ||
\
`);
expect(data).toXMLEqual(`\
<problem>
<label>Select the fruit from the list</label>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false">Mushroom
<choicehint>Mushroom is a fungus, not a fruit.</choicehint>
</choice>
<choice correct="false">Potato</choice>
<choice correct="true">Apple
<choicehint label="OUTSTANDING">Apple is indeed a fruit.</choicehint>
</choice>
</choicegroup>
</multiplechoiceresponse>
<label>Select the vegetables from the list</label>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false">Mushroom
<choicehint>Mushroom is a fungus, not a vegetable.</choicehint>
</choice>
<choice correct="true">Potato
<choicehint>Potato is a root vegetable.</choicehint>
</choice>
<choice correct="false">Apple
<choicehint label="OOPS">Apple is a fruit.</choicehint>
</choice>
</choicegroup>
</multiplechoiceresponse>
<demandhint>
<hint>0) spaces on previous line.</hint>
<hint>1) roses are red.</hint>
<hint>2) where are the lions?</hint>
</demandhint>
</problem>\
`);
});
});
describe('Markdown to xml extended hint text input', function() {
it('produces xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`>>In which country would you find the city of Paris?<<
= France {{ BRAVO::Viva la France! }}
\
`);
expect(data).toXMLEqual(`\
<problem>
<stringresponse answer="France" type="ci">
<label>In which country would you find the city of Paris?</label>
<correcthint label="BRAVO">Viva la France!</correcthint>
<textline size="20"/>
</stringresponse>
</problem>\
`);
});
it('produces xml with or=', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`>>Where Paris?<<
= France {{ BRAVO::hint1}}
or= USA {{ meh::hint2 }}
\
`);
expect(data).toXMLEqual(`\
<problem>
<stringresponse answer="France" type="ci">
<label>Where Paris?</label>
<correcthint label="BRAVO">hint1</correcthint>
<additional_answer answer="USA"><correcthint label="meh">hint2</correcthint>
</additional_answer>
<textline size="20"/>
</stringresponse>
</problem>\
`);
});
it('produces xml with not=', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`>>Revenge is a dish best served<<
= cold {{khaaaaaan!}}
not= warm {{feedback2}}
\
`);
expect(data).toXMLEqual(`\
<problem>
<stringresponse answer="cold" type="ci">
<label>Revenge is a dish best served</label>
<correcthint>khaaaaaan!</correcthint>
<stringequalhint answer="warm">feedback2</stringequalhint>
<textline size="20"/>
</stringresponse>
</problem>\
`);
});
it('produces xml with s=', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`>>q<<
s= 2 {{feedback1}}
\
`);
expect(data).toXMLEqual(`\
<problem>
<stringresponse answer="2" type="ci">
<label>q</label>
<correcthint>feedback1</correcthint>
<textline size="20"/>
</stringresponse>
</problem>\
`);
});
it('produces xml with = and or= and not=', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`>>q<<
= aaa
or= bbb {{feedback1}}
not= no {{feedback2}}
or= ccc
\
`);
expect(data).toXMLEqual(`\
<problem>
<stringresponse answer="aaa" type="ci">
<label>q</label>
<additional_answer answer="bbb"><correcthint>feedback1</correcthint>
</additional_answer>
<stringequalhint answer="no">feedback2</stringequalhint>
<additional_answer answer="ccc"/>
<textline size="20"/>
</stringresponse>
</problem>\
`);
});
it('produces xml with s= and or=', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`>>q<<
s= 2 {{feedback1}}
or= bbb {{feedback2}}
or= ccc
\
`);
expect(data).toXMLEqual(`\
<problem>
<stringresponse answer="2" type="ci">
<label>q</label>
<correcthint>feedback1</correcthint>
<additional_answer answer="bbb"><correcthint>feedback2</correcthint>
</additional_answer>
<additional_answer answer="ccc"/>
<textline size="20"/>
</stringresponse>
</problem>\
`);
});
it('produces xml with each = making a new question', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>q<<
= aaa
or= bbb
s= ccc\
`);
expect(data).toXMLEqual(`\
<problem>
<label>q</label>
<stringresponse answer="aaa" type="ci">
<additional_answer answer="bbb"></additional_answer>
<textline size="20"/>
</stringresponse>
<stringresponse answer="ccc" type="ci">
<textline size="20"/>
</stringresponse>
</problem>\
`);
});
it('produces xml with each = making a new question amid blank lines and paragraphs', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
paragraph
>>q<<
= aaa
or= bbb
s= ccc
paragraph 2
\
`);
expect(data).toXMLEqual(`\
<problem>
<p>paragraph</p>
<label>q</label>
<stringresponse answer="aaa" type="ci">
<additional_answer answer="bbb"></additional_answer>
<textline size="20"/>
</stringresponse>
<stringresponse answer="ccc" type="ci">
<textline size="20"/>
</stringresponse>
<p>paragraph 2</p>
</problem>\
`);
});
it('produces xml without a question when or= is just hung out there by itself', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
paragraph
>>q<<
or= aaa
paragraph 2
\
`);
expect(data).toXMLEqual(`\
<problem>
<p>paragraph</p>
<label>q</label>
<p>or= aaa</p>
<p>paragraph 2</p>
</problem>\
`);
});
it('produces xml with each = with feedback making a new question', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>q<<
s= aaa
or= bbb {{feedback1}}
= ccc {{feedback2}}
\
`);
expect(data).toXMLEqual(`\
<problem>
<label>q</label>
<stringresponse answer="aaa" type="ci">
<additional_answer answer="bbb">
<correcthint>feedback1</correcthint>
</additional_answer>
<textline size="20"/>
</stringresponse>
<stringresponse answer="ccc" type="ci">
<correcthint>feedback2</correcthint>
<textline size="20"/>
</stringresponse>
</problem>\
`);
});
it('produces xml with demand hints', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`>>Where Paris?<<
= France {{ BRAVO::hint1 }}
|| There are actually two countries with cities named Paris. ||
|| Paris is the capital of one of those countries. ||
\
`);
expect(data).toXMLEqual(`\
<problem>
<stringresponse answer="France" type="ci">
<label>Where Paris?</label>
<correcthint label="BRAVO">hint1</correcthint>
<textline size="20"/>
</stringresponse>
<demandhint>
<hint>There are actually two countries with cities named Paris.</hint>
<hint>Paris is the capital of one of those countries.</hint>
</demandhint>
</problem>`);
});
});
describe('Markdown to xml extended hint numeric input', function() {
it('produces xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>Enter the numerical value of Pi:<<
= 3.14159 +- .02 {{ Pie for everyone! }}
>>Enter the approximate value of 502*9:<<
= 4518 +- 15% {{PIE:: No pie for you!}}
>>Enter the number of fingers on a human hand<<
= 5
\
`);
expect(data).toXMLEqual(`\
<problem>
<label>Enter the numerical value of Pi:</label>
<numericalresponse answer="3.14159">
<responseparam type="tolerance" default=".02"/>
<formulaequationinput/>
<correcthint>Pie for everyone!</correcthint>
</numericalresponse>
<label>Enter the approximate value of 502*9:</label>
<numericalresponse answer="4518">
<responseparam type="tolerance" default="15%"/>
<formulaequationinput/>
<correcthint label="PIE">No pie for you!</correcthint>
</numericalresponse>
<label>Enter the number of fingers on a human hand</label>
<numericalresponse answer="5">
<formulaequationinput/>
</numericalresponse>
</problem>\
`);
});
// The output xml here shows some of the quirks of how historical markdown parsing does or does not put
// in blank lines.
it('numeric input with hints and demand hints', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>text1<<
= 1 {{ hint1 }}
|| hintA ||
>>text2<<
= 2 {{ hint2 }}
|| hintB ||
\
`);
expect(data).toXMLEqual(`\
<problem>
<label>text1</label>
<numericalresponse answer="1">
<formulaequationinput/>
<correcthint>hint1</correcthint>
</numericalresponse>
<label>text2</label>
<numericalresponse answer="2">
<formulaequationinput/>
<correcthint>hint2</correcthint>
</numericalresponse>
<demandhint>
<hint>hintA</hint>
<hint>hintB</hint>
</demandhint>
</problem>\
`);
});
});
describe('Markdown to xml extended hint with multiline hints', () =>
it('produces xml', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>Checkboxes<<
[x] A {{
selected: aaa },
{unselected:bbb}}
[ ] B {{U: c}, {
selected: d.}}
{{ ((A*B)) A*B hint}}
>>What is 1 + 1?<<
= 2 {{ part one, and
part two
}}
>>hello?<<
= hello {{
hello
hint
}}
>>multiple choice<<
(x) AA{{hint1}}
() BB {{
hint2
}}
( ) CC {{ hint3
}}
>>dropdown<<
[[
W1 {{
no }}
W2 {{
nope}}
(C1) {{ yes
}}
]]
|| aaa ||
||bbb||
|| ccc ||
\
`);
expect(data).toXMLEqual(`\
<problem>
<label>Checkboxes</label>
<choiceresponse>
<checkboxgroup>
<choice correct="true">A
<choicehint selected="true">aaa</choicehint>
<choicehint selected="false">bbb</choicehint>
</choice>
<choice correct="false">B
<choicehint selected="true">d.</choicehint>
<choicehint selected="false">c</choicehint>
</choice>
<compoundhint value="A*B">A*B hint</compoundhint>
</checkboxgroup>
</choiceresponse>
<label>What is 1 + 1?</label>
<numericalresponse answer="2">
<formulaequationinput/>
<correcthint>part one, and part two</correcthint>
</numericalresponse>
<label>hello?</label>
<stringresponse answer="hello" type="ci">
<correcthint>hello hint</correcthint>
<textline size="20"/>
</stringresponse>
<label>multiple choice</label>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="true">AA
<choicehint>hint1</choicehint>
</choice>
<choice correct="false">BB
<choicehint>hint2</choicehint>
</choice>
<choice correct="false">CC
<choicehint>hint3</choicehint>
</choice>
</choicegroup>
</multiplechoiceresponse>
<label>dropdown</label>
<optionresponse>
<optioninput>
<option correct="False">W1
<optionhint>no</optionhint>
</option>
<option correct="False">W2
<optionhint>nope</optionhint>
</option>
<option correct="True">C1
<optionhint>yes</optionhint>
</option>
</optioninput>
</optionresponse>
<demandhint>
<hint>aaa</hint>
<hint>bbb</hint>
<hint>ccc</hint>
</demandhint>
</problem>\
`);
})
);
describe('Markdown to xml extended hint with tricky syntax cases', function() {
it('produces xml with unicode', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>á and Ø<<
(x) Ø{{Ø}}
() BB
|| Ø ||
\
`);
expect(data).toXMLEqual(`\
<problem>
<multiplechoiceresponse>
<label>á and Ø</label>
<choicegroup type="MultipleChoice">
<choice correct="true">Ø
<choicehint>Ø</choicehint>
</choice>
<choice correct="false">BB</choice>
</choicegroup>
</multiplechoiceresponse>
<demandhint>
<hint>Ø</hint>
</demandhint>
</problem>\
`);
});
it('produces xml with quote-type characters', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>"quotes" aren't \`fun\`<<
() "hello" {{ isn't }}
(x) "isn't" {{ "hello" }}
\
`);
expect(data).toXMLEqual(`\
<problem>
<multiplechoiceresponse>
<label>"quotes" aren't \`fun\`</label>
<choicegroup type="MultipleChoice">
<choice correct="false">"hello"
<choicehint>isn't</choicehint>
</choice>
<choice correct="true">"isn't"
<choicehint>"hello"</choicehint>
</choice>
</choicegroup>
</multiplechoiceresponse>
</problem>\
`);
});
it('produces xml with almost but not quite multiple choice syntax', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>q1<<
this (x)
() a {{ (hint) }}
(x) b
that (y)\
`);
expect(data).toXMLEqual(`\
<problem>
<multiplechoiceresponse>
<label>q1</label>
<p>this (x)</p>
<choicegroup type="MultipleChoice">
<choice correct="false">a <choicehint>(hint)</choicehint>
</choice>
<choice correct="true">b</choice>
</choicegroup>
<p>that (y)</p>
</multiplechoiceresponse>
</problem>\
`);
});
// An incomplete checkbox hint passes through to cue the author
it('produce xml with almost but not quite checkboxgroup syntax', function() {
const data = MarkdownEditingDescriptor.markdownToXml(`\
>>q1<<
this [x]
[ ] a [square]
[x] b {{ this hint passes through }}
that []\
`);
expect(data).toXMLEqual(`\
<problem>
<choiceresponse>
<label>q1</label>
<p>this [x]</p>
<checkboxgroup>
<choice correct="false">a [square]</choice>
<choice correct="true">b {{ this hint passes through }}</choice>
</checkboxgroup>
<p>that []</p>
</choiceresponse>
</problem>\
`);
});
// It's sort of a pain to edit DOS line endings without some editor or other "fixing" them
// for you. Therefore, we construct DOS line endings on the fly just for the test.
it('produces xml with DOS \r\n line endings', function() {
let markdown = `\
>>q22<<
[[
(x) {{ hintx
these
span
}}
yy {{ meh::hinty }}
zzz {{ hintz }}
]]\
`;
markdown = markdown.replace(/\n/g, '\r\n'); // make DOS line endings
const data = MarkdownEditingDescriptor.markdownToXml(markdown);
expect(data).toXMLEqual(`\
<problem>
<optionresponse>
<label>q22</label>
<optioninput>
<option correct="True">x <optionhint>hintx these span</optionhint>
</option>
<option correct="False">yy <optionhint label="meh">hinty</optionhint>
</option>
<option correct="False">zzz <optionhint>hintz</optionhint>
</option>
</optioninput>
</optionresponse>
</problem>\
`);
});
});

View File

@@ -1 +0,0 @@
!edit.js

View File

@@ -1,905 +0,0 @@
/* global CodeMirror, _, XModule */
// no-useless-escape disabled because of warnings in regexp expressions within the
// "toXML" code. When the "useless escapes" were removed, some of the unit tests
// failed, but only in Jenkins, indicating browser-specific behavior.
/* eslint no-useless-escape: 0 */
(function () {
"use strict";
var hasPropsHelper = {}.hasOwnProperty,
extendsHelper = function (child, parent) {
// This helper method was generated by CoffeeScript. Suppressing eslint warnings.
var key;
for (key in parent) {
// eslint-disable-line no-restricted-syntax
if (hasPropsHelper.call(parent, key)) {
child[key] = parent[key]; // eslint-disable-line no-param-reassign
}
}
function ctor() {
this.constructor = child;
}
ctor.prototype = parent.prototype;
child.prototype = new ctor(); // eslint-disable-line no-param-reassign
child.__super__ = parent.prototype; // eslint-disable-line no-param-reassign, no-underscore-dangle
return child;
};
this.MarkdownEditingDescriptor = (function (_super) {
// The style of these declarations come from CoffeeScript. Rather than rewriting them,
// the eslint warnings are being suppressed.
extendsHelper(MarkdownEditingDescriptor, _super); // eslint-disable-line no-use-before-define
MarkdownEditingDescriptor.multipleChoiceTemplate =
"( ) " + // eslint-disable-line no-use-before-define
gettext("incorrect") +
"\n( ) " +
gettext("incorrect") +
"\n(x) " +
gettext("correct") +
"\n";
MarkdownEditingDescriptor.checkboxChoiceTemplate =
"[x] " + // eslint-disable-line no-use-before-define
gettext("correct") +
"\n[ ] incorrect\n[x] correct\n";
MarkdownEditingDescriptor.stringInputTemplate =
"= " + // eslint-disable-line no-use-before-define
gettext("answer") +
"\n";
MarkdownEditingDescriptor.numberInputTemplate =
"= " + // eslint-disable-line no-use-before-define
gettext("answer") +
" +- 0.001%\n";
MarkdownEditingDescriptor.selectTemplate =
"[[" + // eslint-disable-line no-use-before-define
gettext("incorrect") +
", (" +
gettext("correct") +
"), " +
gettext("incorrect") +
"]]\n";
MarkdownEditingDescriptor.headerTemplate =
"" + // eslint-disable-line no-use-before-define
gettext("Header") +
"\n=====\n";
MarkdownEditingDescriptor.explanationTemplate =
"[explanation]\n" + // eslint-disable-line no-use-before-define
gettext("Short explanation") +
"\n[explanation]\n";
function MarkdownEditingDescriptor(element) {
var that = this;
this.onToolbarButton = function () {
return MarkdownEditingDescriptor.prototype.onToolbarButton.apply(that, arguments);
};
this.onShowXMLButton = function () {
return MarkdownEditingDescriptor.prototype.onShowXMLButton.apply(that, arguments);
};
this.element = element;
if ($(".markdown-box", this.element).length !== 0) {
this.markdown_editor = CodeMirror.fromTextArea($(".markdown-box", element)[0], {
lineWrapping: true,
mode: null,
});
this.setCurrentEditor(this.markdown_editor);
// Add listeners for toolbar buttons (only present for markdown editor)
this.element.on("click", ".xml-tab", this.onShowXMLButton);
this.element.on("click", ".format-buttons button", this.onToolbarButton);
// Hide the XML text area
$(this.element.find(".xml-box")).hide();
} else {
this.createXMLEditor();
}
}
/*
Creates the XML Editor and sets it as the current editor. If text is passed in,
it will replace the text present in the HTML template.
text: optional argument to override the text passed in via the HTML template
*/
MarkdownEditingDescriptor.prototype.createXMLEditor = function (text) {
this.xml_editor = CodeMirror.fromTextArea($(".xml-box", this.element)[0], {
mode: "xml",
lineNumbers: true,
lineWrapping: true,
});
if (text) {
this.xml_editor.setValue(text);
}
this.setCurrentEditor(this.xml_editor);
$(this.xml_editor.getWrapperElement()).toggleClass("CodeMirror-advanced");
// Need to refresh to get line numbers to display properly.
this.xml_editor.refresh();
};
/*
User has clicked to show the XML editor. Before XML editor is swapped in,
the user will need to confirm the one-way conversion.
*/
MarkdownEditingDescriptor.prototype.onShowXMLButton = function (e) {
e.preventDefault();
if (this.confirmConversionToXml()) {
this.createXMLEditor(MarkdownEditingDescriptor.markdownToXml(this.markdown_editor.getValue()));
this.xml_editor.setCursor(0);
// Hide markdown-specific toolbar buttons
$(this.element.find(".editor-bar")).hide();
}
};
/*
Have the user confirm the one-way conversion to XML.
Returns true if the user clicked OK, else false.
*/
MarkdownEditingDescriptor.prototype.confirmConversionToXml = function () {
return confirm(
gettext(
"If you use the Advanced Editor, this problem will be converted to XML and you will not be able to return to the Simple Editor Interface.\n\nProceed to the Advanced Editor and convert this problem to XML?",
),
); // eslint-disable-line max-len, no-alert
};
/*
Event listener for toolbar buttons (only possible when markdown editor is visible).
*/
MarkdownEditingDescriptor.prototype.onToolbarButton = function (e) {
var revisedSelection, selection;
e.preventDefault();
selection = this.markdown_editor.getSelection();
revisedSelection = null;
switch ($(e.currentTarget).attr("class")) {
case "multiple-choice-button":
revisedSelection = MarkdownEditingDescriptor.insertMultipleChoice(selection);
break;
case "string-button":
revisedSelection = MarkdownEditingDescriptor.insertStringInput(selection);
break;
case "number-button":
revisedSelection = MarkdownEditingDescriptor.insertNumberInput(selection);
break;
case "checks-button":
revisedSelection = MarkdownEditingDescriptor.insertCheckboxChoice(selection);
break;
case "dropdown-button":
revisedSelection = MarkdownEditingDescriptor.insertSelect(selection);
break;
case "header-button":
revisedSelection = MarkdownEditingDescriptor.insertHeader(selection);
break;
case "explanation-button":
revisedSelection = MarkdownEditingDescriptor.insertExplanation(selection);
break;
default:
break;
}
if (revisedSelection !== null) {
this.markdown_editor.replaceSelection(revisedSelection);
this.markdown_editor.focus();
}
};
/*
Stores the current editor and hides the one that is not displayed.
*/
MarkdownEditingDescriptor.prototype.setCurrentEditor = function (editor) {
if (this.current_editor) {
$(this.current_editor.getWrapperElement()).hide();
}
this.current_editor = editor;
$(this.current_editor.getWrapperElement()).show();
return $(this.current_editor).focus();
};
/*
Called when save is called. Listeners are unregistered because editing the block again will
result in a new instance of the descriptor. Note that this is NOT the case for cancel--
when cancel is called the instance of the descriptor is reused if edit is selected again.
*/
MarkdownEditingDescriptor.prototype.save = function () {
this.element.off("click", ".xml-tab", this.changeEditor);
this.element.off("click", ".format-buttons button", this.onToolbarButton);
if (this.current_editor === this.markdown_editor) {
return {
data: MarkdownEditingDescriptor.markdownToXml(this.markdown_editor.getValue()),
metadata: {
markdown: this.markdown_editor.getValue(),
},
};
} else {
return {
data: this.xml_editor.getValue(),
nullout: ["markdown"],
};
}
};
MarkdownEditingDescriptor.insertMultipleChoice = function (selectedText) {
return MarkdownEditingDescriptor.insertGenericChoice(
selectedText,
"(",
")",
MarkdownEditingDescriptor.multipleChoiceTemplate,
);
};
MarkdownEditingDescriptor.insertCheckboxChoice = function (selectedText) {
return MarkdownEditingDescriptor.insertGenericChoice(
selectedText,
"[",
"]",
MarkdownEditingDescriptor.checkboxChoiceTemplate,
);
};
MarkdownEditingDescriptor.insertGenericChoice = function (selectedText, choiceStart, choiceEnd, template) {
var cleanSelectedText, line, lines, revisedLines, i, len;
if (selectedText.length > 0) {
// Replace adjacent newlines with a single newline, strip any trailing newline
cleanSelectedText = selectedText.replace(/\n+/g, "\n").replace(/\n$/, "");
lines = cleanSelectedText.split("\n");
revisedLines = "";
for (i = 0, len = lines.length; i < len; i++) {
line = lines[i];
revisedLines += choiceStart;
// a stand alone x before other text implies that this option is "correct"
if (/^\s*x\s+(\S)/i.test(line)) {
// Remove the x and any initial whitespace as long as there's more text on the line
line = line.replace(/^\s*x\s+(\S)/i, "$1");
revisedLines += "x";
} else {
revisedLines += " ";
}
revisedLines += choiceEnd + " " + line + "\n";
}
return revisedLines;
} else {
return template;
}
};
MarkdownEditingDescriptor.insertStringInput = function (selectedText) {
return MarkdownEditingDescriptor.insertGenericInput(
selectedText,
"= ",
"",
MarkdownEditingDescriptor.stringInputTemplate,
);
};
MarkdownEditingDescriptor.insertNumberInput = function (selectedText) {
return MarkdownEditingDescriptor.insertGenericInput(
selectedText,
"= ",
"",
MarkdownEditingDescriptor.numberInputTemplate,
);
};
MarkdownEditingDescriptor.insertSelect = function (selectedText) {
return MarkdownEditingDescriptor.insertGenericInput(
selectedText,
"[[",
"]]",
MarkdownEditingDescriptor.selectTemplate,
);
};
MarkdownEditingDescriptor.insertHeader = function (selectedText) {
return MarkdownEditingDescriptor.insertGenericInput(
selectedText,
"",
"\n====\n",
MarkdownEditingDescriptor.headerTemplate,
);
};
MarkdownEditingDescriptor.insertExplanation = function (selectedText) {
return MarkdownEditingDescriptor.insertGenericInput(
selectedText,
"[explanation]\n",
"\n[explanation]",
MarkdownEditingDescriptor.explanationTemplate,
);
};
MarkdownEditingDescriptor.insertGenericInput = function (selectedText, lineStart, lineEnd, template) {
if (selectedText.length > 0) {
return lineStart + selectedText + lineEnd;
} else {
return template;
}
};
MarkdownEditingDescriptor.markdownToXml = function (markdown) {
var demandHintTags = [],
finalDemandHints,
finalXml,
responseTypesMarkdown,
responseTypesXML,
toXml;
toXml = function (partialMarkdown) {
var xml = partialMarkdown,
i,
splits,
makeParagraph,
serializer,
responseType,
$xml,
responseTypesSelector,
inputtype,
beforeInputtype,
extractHint,
demandhints;
var responseTypes = [
"optionresponse",
"multiplechoiceresponse",
"stringresponse",
"numericalresponse",
"choiceresponse",
];
// fix DOS \r\n line endings to look like \n
xml = xml.replace(/\r\n/g, "\n");
// replace headers
xml = xml.replace(/(^.*?$)(?=\n\=\=+$)/gm, '<h3 class="hd hd-2 problem-header">$1</h3>');
xml = xml.replace(/\n^\=\=+$/gm, "");
// extract question and description(optional)
// >>question||description<< converts to
// <label>question</label> <description>description</description>
xml = xml.replace(/>>([^]+?)<</gm, function (match, questionText) {
var result = questionText.split("||"),
label = "<label>" + result[0] + "</label>\n"; // xss-lint: disable=javascript-concat-html
// don't add empty <description> tag
if (result.length === 1 || !result[1]) {
return label;
}
return label + "<description>" + result[1] + "</description>\n"; // xss-lint: disable=javascript-concat-html
});
// Pull out demand hints, || a hint ||
demandhints = "";
xml = xml.replace(/(^\s*\|\|.*?\|\|\s*$\n?)+/gm, function (match) {
// $\n
var inner,
options = match.split("\n");
for (i = 0; i < options.length; i += 1) {
inner = /\s*\|\|(.*?)\|\|/.exec(options[i]);
if (inner) {
demandhints += " <hint>" + inner[1].trim() + "</hint>\n"; // xss-lint: disable=javascript-concat-html
}
}
return "";
});
// replace \n+whitespace within extended hint {{ .. }}, by a space, so the whole
// hint sits on one line.
// This is the one instance of {{ ... }} matching that permits \n
xml = xml.replace(/{{(.|\n)*?}}/gm, function (match) {
return match.replace(/\r?\n( |\t)*/g, " ");
});
// Function used in many places to extract {{ label:: a hint }}.
// Returns a little hash with various parts of the hint:
// hint: the hint or empty, nothint: the rest
// labelassign: javascript assignment of label attribute, or empty
extractHint = function (inputText, detectParens) {
var text = inputText,
curly = /\s*{{(.*?)}}/.exec(text),
hint = "",
label = "",
parens = false,
labelassign = "",
labelmatch;
if (curly) {
text = text.replace(curly[0], "");
hint = curly[1].trim();
labelmatch = /^(.*?)::/.exec(hint);
if (labelmatch) {
hint = hint.replace(labelmatch[0], "").trim();
label = labelmatch[1].trim();
labelassign = ' label="' + label + '"';
}
}
if (detectParens) {
if (text.length >= 2 && text[0] === "(" && text[text.length - 1] === ")") {
text = text.substring(1, text.length - 1);
parens = true;
}
}
return {
nothint: text,
hint: hint,
label: label,
parens: parens,
labelassign: labelassign,
};
};
// replace selects
// [[ a, b, (c) ]]
// [[
// a
// b
// (c)
// ]]
// <optionresponse>
// <optioninput>
// <option correct="True">AAA<optionhint label="Good Job">
// Yes, multiple choice is the right answer.
// </optionhint>
// Note: part of the option-response syntax looks like multiple-choice, so it must be processed first.
xml = xml.replace(/\[\[((.|\n)+?)\]\]/g, function (match, group1) {
var textHint, options, optiontag, correct, lines, optionlines, line, correctstr, hintstr, label;
// decide if this is old style or new style
if (match.indexOf("\n") === -1) {
// OLD style, [[ .... ]] on one line
options = group1.split(/\,\s*/g);
optiontag = ' <optioninput options="(';
for (i = 0; i < options.length; i += 1) {
optiontag +=
"'" +
options[i].replace(/(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g, "$1") +
"'" +
(i < options.length - 1 ? "," : "");
}
optiontag += ')" correct="';
correct = /(?:^|,)\s*\((.*?)\)\s*(?:$|,)/g.exec(group1);
if (correct) {
optiontag += correct[1];
}
optiontag += '">';
return "\n<optionresponse>\n" + optiontag + "</optioninput>\n</optionresponse>\n\n"; // xss-lint: disable=javascript-concat-html
}
// new style [[ many-lines ]]
lines = group1.split("\n");
optionlines = "";
for (i = 0; i < lines.length; i++) {
line = lines[i].trim();
if (line.length > 0) {
textHint = extractHint(line, true);
if (!textHint.nothint) {
throw new Error(gettext("An answer option has been left blank. Please review and edit the component."));
}
correctstr = ' correct="' + (textHint.parens ? "True" : "False") + '"';
hintstr = "";
if (textHint.hint) {
label = textHint.label;
if (label) {
label = ' label="' + label + '"';
}
hintstr = " <optionhint" + label + ">" + textHint.hint + "</optionhint>"; // xss-lint: disable=javascript-concat-html
}
optionlines += " <option" + correctstr + ">" + textHint.nothint + hintstr + "</option>\n"; // xss-lint: disable=javascript-concat-html
}
}
return "\n<optionresponse>\n <optioninput>\n" + optionlines + " </optioninput>\n</optionresponse>\n\n"; // xss-lint: disable=javascript-concat-html
});
// multiple choice questions
//
xml = xml.replace(/(^\s*\(.{0,3}\).*?$\n*)+/gm, function (match) {
var choices = "",
shuffle = false,
options = match.split("\n"),
value,
inparens,
correct,
fixed,
hint,
result;
for (i = 0; i < options.length; i++) {
options[i] = options[i].trim(); // trim off leading/trailing whitespace
if (options[i].length > 0) {
value = options[i].split(/^\s*\(.{0,3}\)\s*/)[1];
if (!value) {
throw new Error(gettext("An answer option has been left blank. Please review and edit the component."));
}
inparens = /^\s*\((.{0,3})\)\s*/.exec(options[i])[1];
correct = /x/i.test(inparens);
fixed = "";
if (/@/.test(inparens)) {
fixed = ' fixed="true"';
}
if (/!/.test(inparens)) {
shuffle = true;
}
hint = extractHint(value);
if (hint.hint) {
value = hint.nothint;
value = value + " <choicehint" + hint.labelassign + ">" + hint.hint + "</choicehint>"; // xss-lint: disable=javascript-concat-html
}
choices += ' <choice correct="' + correct + '"' + fixed + ">" + value + "</choice>\n"; // xss-lint: disable=javascript-concat-html
}
}
result = "<multiplechoiceresponse>\n";
if (shuffle) {
result += ' <choicegroup type="MultipleChoice" shuffle="true">\n';
} else {
result += ' <choicegroup type="MultipleChoice">\n';
}
result += choices;
result += " </choicegroup>\n";
result += "</multiplechoiceresponse>\n\n";
return result;
});
// group check answers
// [.] with {{...}} lines mixed in
xml = xml.replace(/(^\s*((\[.?\])|({{.*?}})).*?$\n*)+/gm, function (match) {
var groupString = "<choiceresponse>\n",
options = match.split("\n"),
value,
correct,
abhint,
endHints,
hintbody,
hint,
inner,
select,
hints;
groupString += " <checkboxgroup>\n";
endHints = ""; // save these up to emit at the end
for (i = 0; i < options.length; i += 1) {
if (options[i].trim().length > 0) {
// detect the {{ ((A*B)) ...}} case first
// emits: <compoundhint value="A*B">AB hint</compoundhint>
abhint = /^\s*{{\s*\(\((.*?)\)\)(.*?)}}/.exec(options[i]);
if (abhint) {
// lone case of hint text processing outside of extractHint, since syntax here is unique
hintbody = abhint[2];
hintbody = hintbody.replace("&lf;", "\n").trim();
endHints += ' <compoundhint value="' + abhint[1].trim() + '">' + hintbody + "</compoundhint>\n"; // xss-lint: disable=javascript-concat-html
// eslint-disable-next-line no-continue
continue; // bail
}
value = options[i].split(/^\s*\[.?\]\s*/)[1];
if (!value) {
throw new Error(gettext("An answer option has been left blank. Please review and edit the component."));
}
correct = /^\s*\[x\]/i.test(options[i]);
hints = "";
// {{ selected: Youre right that apple is a fruit. },
// {unselected: Remember that apple is also a fruit.}}
hint = extractHint(value);
if (hint.hint) {
inner = "{" + hint.hint + "}"; // parsing is easier if we put outer { } back
// include \n since we are downstream of extractHint()
select = /{\s*(s|selected):((.|\n)*?)}/i.exec(inner);
// checkbox choicehints get their own line, since there can be two of them
// <choicehint selected="true">Youre right that apple is a fruit.</choicehint>
if (select) {
hints += '\n <choicehint selected="true">' + select[2].trim() + "</choicehint>"; // xss-lint: disable=javascript-concat-html
}
select = /{\s*(u|unselected):((.|\n)*?)}/i.exec(inner);
if (select) {
hints += '\n <choicehint selected="false">' + select[2].trim() + "</choicehint>"; // xss-lint: disable=javascript-concat-html
}
// Blank out the original text only if the specific "selected" syntax is found
// That way, if the user types it wrong, at least they can see it's not processed.
if (hints) {
value = hint.nothint;
}
}
groupString += ' <choice correct="' + correct + '">' + value + hints + "</choice>\n"; // xss-lint: disable=javascript-concat-html
}
}
groupString += endHints;
groupString += " </checkboxgroup>\n";
groupString += "</choiceresponse>\n\n";
return groupString;
});
// replace string and numerical, numericalresponse, stringresponse
// A fine example of the function-composition programming style.
xml = xml.replace(/(^s?\=\s*(.*?$)(\n*(or|not)\=\s*(.*?$))*)+/gm, function (match, p) {
// Line split here, trim off leading xxx= in each function
var answersList = p.split("\n"),
isRangeToleranceCase = function (answer) {
return _.contains(["[", "("], answer[0]) && _.contains(["]", ")"], answer[answer.length - 1]);
},
checkIsNumeric = function (stringValue) {
// remove OLX feedback
if (stringValue.indexOf("{{") !== -1 && stringValue.indexOf("}}") !== -1) {
stringValue = stringValue.replace(/{{[\s\S]*?}}/g, "").trim();
}
// allow for "e" for scientific notation, otherwise, exclude letters
if (stringValue.match(/[a-df-z]/i)) {
return false;
}
return !isNaN(parseFloat(stringValue));
},
getAnswerData = function (answerValue) {
var answerData = {},
answerParams = /(.*?)\+\-\s*(.*?$)/.exec(answerValue);
if (answerParams) {
answerData.answer = answerParams[1].replace(/\s+/g, ""); // inputs like 5*2 +- 10
answerData.default = answerParams[2];
} else {
answerData.answer = answerValue.replace(/\s+/g, ""); // inputs like 5*2
}
return answerData;
},
processNumericalResponse = function (answerValues) {
var firstAnswer,
answerData,
numericalResponseString,
additionalAnswerString,
textHint,
hintLine,
additionalTextHint,
additionalHintLine,
orMatch,
hasTolerance;
// First string case is s?= [e.g. = 100]
firstAnswer = answerValues[0].replace(/^\=\s*/, "");
// If answer is not numerical
if (!checkIsNumeric(firstAnswer) && !isRangeToleranceCase(firstAnswer)) {
return false;
}
textHint = extractHint(firstAnswer);
hintLine = "";
if (textHint.hint) {
firstAnswer = textHint.nothint;
hintLine = " <correcthint" + textHint.labelassign + ">" + textHint.hint + "</correcthint>\n"; // xss-lint: disable=javascript-concat-html
}
// Range case
if (isRangeToleranceCase(firstAnswer)) {
// [5, 7) or (5, 7), or (1.2345 * (2+3), 7*4 ] - range tolerance case
// = (5*2)*3 should not be used as range tolerance
numericalResponseString = '<numericalresponse answer="' + firstAnswer + '">\n'; // xss-lint: disable=javascript-concat-html
} else {
answerData = getAnswerData(firstAnswer);
numericalResponseString = '<numericalresponse answer="' + answerData.answer + '">\n'; // xss-lint: disable=javascript-concat-html
if (answerData.default) {
numericalResponseString +=
' <responseparam type="tolerance" default="' + answerData.default + '" />\n'; // xss-lint: disable=javascript-concat-html
}
}
// Additional answer case or= [e.g. or= 10]
// Since answerValues[0] is firstAnswer, so we will not include this in additional answers.
additionalAnswerString = "";
for (i = 1; i < answerValues.length; i++) {
additionalHintLine = "";
additionalTextHint = extractHint(answerValues[i]);
orMatch = /^or\=\s*(.*)/.exec(additionalTextHint.nothint);
if (orMatch) {
hasTolerance = /(.*?)\+\-\s*(.*?$)/.exec(orMatch[1]);
// Do not add additional_answer if additional answer is not numerical (eg. or= ABC)
// or contains range tolerance case (eg. or= (5,7)
// or has tolerance (eg. or= 10 +- 0.02)
if (isNaN(parseFloat(orMatch[1])) || isRangeToleranceCase(orMatch[1]) || hasTolerance) {
// eslint-disable-next-line no-continue
continue;
}
if (additionalTextHint.hint) {
additionalHintLine =
"<correcthint" + // xss-lint: disable=javascript-concat-html
additionalTextHint.labelassign + // xss-lint: disable=javascript-concat-html
">" + // xss-lint: disable=javascript-concat-html
additionalTextHint.hint + // xss-lint: disable=javascript-concat-html
"</correcthint>"; // xss-lint: disable=javascript-concat-html
}
additionalAnswerString += ' <additional_answer answer="' + orMatch[1] + '">'; // xss-lint: disable=javascript-concat-html
additionalAnswerString += additionalHintLine;
additionalAnswerString += "</additional_answer>\n";
}
}
// Add additional answers string to numerical problem string.
if (additionalAnswerString) {
numericalResponseString += additionalAnswerString;
}
numericalResponseString += " <formulaequationinput />\n";
numericalResponseString += hintLine;
numericalResponseString += "</numericalresponse>\n\n";
return numericalResponseString;
},
processStringResponse = function (values) {
var firstAnswer, textHint, typ, string, orMatch, notMatch;
// First string case is s?=
firstAnswer = values.shift();
firstAnswer = firstAnswer.replace(/^s?\=\s*/, "");
textHint = extractHint(firstAnswer);
firstAnswer = textHint.nothint;
typ = ' type="ci"';
if (firstAnswer[0] === "|") {
// this is regexp case
typ = ' type="ci regexp"';
firstAnswer = firstAnswer.slice(1).trim();
}
string = '<stringresponse answer="' + firstAnswer + '"' + typ + " >\n"; // xss-lint: disable=javascript-concat-html
if (textHint.hint) {
string += " <correcthint" + textHint.labelassign + ">" + textHint.hint + "</correcthint>\n"; // xss-lint: disable=javascript-concat-html
}
// Subsequent cases are not= or or=
for (i = 0; i < values.length; i += 1) {
textHint = extractHint(values[i]);
notMatch = /^not\=\s*(.*)/.exec(textHint.nothint);
if (notMatch) {
string +=
' <stringequalhint answer="' + // xss-lint: disable=javascript-concat-html
notMatch[1] + // xss-lint: disable=javascript-concat-html
'"' + // xss-lint: disable=javascript-concat-html
textHint.labelassign + // xss-lint: disable=javascript-concat-html
">" + // xss-lint: disable=javascript-concat-html
textHint.hint + // xss-lint: disable=javascript-concat-html
"</stringequalhint>\n"; // xss-lint: disable=javascript-concat-html
// eslint-disable-next-line no-continue
continue;
}
orMatch = /^or\=\s*(.*)/.exec(textHint.nothint);
if (orMatch) {
// additional_answer with answer= attribute
string += ' <additional_answer answer="' + orMatch[1] + '">'; // xss-lint: disable=javascript-concat-html
if (textHint.hint) {
string += "<correcthint" + textHint.labelassign + ">" + textHint.hint + "</correcthint>"; // xss-lint: disable=javascript-concat-html
}
string += "</additional_answer>\n";
}
}
string += ' <textline size="20"/>\n</stringresponse>\n\n';
return string;
};
return processNumericalResponse(answersList) || processStringResponse(answersList);
});
// replace explanations
xml = xml.replace(/\[explanation\]\n?([^\]]*)\[\/?explanation\]/gim, function (match, p1) {
return (
'<solution>\n<div class="detailed-solution">\n' + // xss-lint: disable=javascript-concat-html
gettext("Explanation") +
"\n\n" + // xss-lint: disable=javascript-concat-html
p1 + // xss-lint: disable=javascript-concat-html
"\n</div>\n</solution>"
);
});
// replace code blocks
xml = xml.replace(/\[code\]\n?([^\]]*)\[\/?code\]/gim, function (match, p1) {
return "<pre><code>" + p1 + "</code></pre>"; // xss-lint: disable=javascript-concat-html
});
// split scripts and preformatted sections, and wrap paragraphs
splits = xml.split(/(<\/?(?:script|pre|label|description)[^>]*>)/gi);
// Wrap a string by <p> tag when line is not already wrapped by another tag
// true when line is not already wrapped by another tag false otherwise
makeParagraph = true;
for (i = 0; i < splits.length; i += 1) {
if (/\<(script|pre|label|description)/.test(splits[i])) {
makeParagraph = false;
}
if (makeParagraph) {
splits[i] = splits[i].replace(/(^(?!\s*\<|$).*$)/gm, "<p>$1</p>");
}
if (/\<\/(script|pre|label|description)/.test(splits[i])) {
makeParagraph = true;
}
}
xml = splits.join("");
// rid white space
xml = xml.replace(/\n\n\n/g, "\n");
// if we've come across demand hints, wrap in <demandhint> at the end
if (demandhints) {
demandHintTags.push(demandhints);
}
// make selector to search responsetypes in xml
responseTypesSelector = responseTypes.join(", ");
// make temporary xml
$xml = $($.parseXML("<prob>" + xml + "</prob>")); // xss-lint: disable=javascript-concat-html
responseType = $xml.find(responseTypesSelector);
// convert if there is only one responsetype
if (responseType.length === 1) {
inputtype = responseType[0].firstElementChild;
// used to decide whether an element should be placed before or after an inputtype
beforeInputtype = true;
_.each($xml.find("prob").children(), function (child) {
// we don't want to add the responsetype again into new xml
if (responseType[0].nodeName === child.nodeName) {
beforeInputtype = false;
return;
}
if (beforeInputtype) {
// xss-lint: disable=javascript-jquery-insert-into-target
responseType[0].insertBefore(child, inputtype);
} else {
responseType[0].appendChild(child);
}
});
serializer = new XMLSerializer();
xml = serializer.serializeToString(responseType[0]);
// remove xmlns attribute added by the serializer
xml = xml.replace(/\sxmlns=['"].*?['"]/gi, "");
// XMLSerializer messes the indentation of XML so add newline
// at the end of each ending tag to make the xml looks better
xml = xml.replace(/(\<\/.*?\>)(\<.*?\>)/gi, "$1\n$2");
}
// remove class attribute added on <p> tag for question title
xml = xml.replace(/\sclass=\'qtitle\'/gi, "");
return xml;
};
responseTypesXML = [];
responseTypesMarkdown = markdown.split(/\n\s*---\s*\n/g);
_.each(responseTypesMarkdown, function (responseTypeMarkdown) {
if (responseTypeMarkdown.trim().length > 0) {
responseTypesXML.push(toXml(responseTypeMarkdown));
}
});
finalDemandHints = "";
if (demandHintTags.length) {
finalDemandHints = "\n<demandhint>\n" + demandHintTags.join("") + "</demandhint>"; // xss-lint: disable=javascript-concat-html
}
// make all responsetypes descendants of a single problem element
finalXml = "<problem>\n" + responseTypesXML.join("\n\n") + finalDemandHints + "\n</problem>"; // xss-lint: disable=javascript-concat-html
return finalXml;
};
return MarkdownEditingDescriptor;
})(XModule.Descriptor);
}).call(this);

View File

@@ -1,221 +0,0 @@
@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700");
.xmodule_edit.xmodule_ProblemBlock .ui-col-wide {
width: 74.46809%;
margin-right: 2.12766%;
float: left;
}
.xmodule_edit.xmodule_ProblemBlock .ui-col-narrow {
width: 23.40426%;
float: left;
}
.xmodule_edit.xmodule_ProblemBlock .ui-loading {
box-shadow: inset 0 1px 2px 1px rgba(0, 0, 0, 0.2);
padding: 15px 20px;
}
.xmodule_edit.xmodule_ProblemBlock .ui-loading {
animation: fadeIn 0.25s linear 1;
opacity: 0.6;
background-color: #fff;
padding: 30px 20px;
text-align: center;
}
.xmodule_edit.xmodule_ProblemBlock .ui-loading .spin {
display: inline-block;
}
.xmodule_edit.xmodule_ProblemBlock .ui-loading .copy {
padding-left: 5px;
}
.xmodule_edit.xmodule_ProblemBlock .is-hidden {
display: none;
}
.xmodule_edit.xmodule_ProblemBlock .editor {
position: relative;
}
.xmodule_edit.xmodule_ProblemBlock .editor .row {
position: relative;
}
.xmodule_edit.xmodule_ProblemBlock .editor .editor-bar {
background-color: #d4dee8;
background-image: -webkit-linear-gradient(top, #d4dee8, #c9d5e2);
background-image: linear-gradient(to bottom, #d4dee8, #c9d5e2);
position: relative;
padding: calc(var(--baseline, 20px) / 4);
border-bottom-color: #a5aaaf;
}
.xmodule_edit.xmodule_ProblemBlock .editor .editor-bar:after {
content: "";
display: table;
clear: both;
}
.xmodule_edit.xmodule_ProblemBlock .editor .editor-bar button {
display: inline-block;
float: left;
padding: 3px calc(var(--baseline, 20px) / 2) 5px;
margin-left: 7px;
border: 0;
border-radius: 2px;
background: transparent;
}
.xmodule_edit.xmodule_ProblemBlock .editor .editor-bar button .icon {
height: 21px;
}
.xmodule_edit.xmodule_ProblemBlock .editor .editor-bar button:hover,
.xmodule_edit.xmodule_ProblemBlock .editor .editor-bar button:focus {
background: rgba(255, 255, 255, 0.5);
}
.xmodule_edit.xmodule_ProblemBlock .editor .editor-tabs {
position: absolute;
top: 10px;
right: 10px;
text-align: left;
direction: ltr;
}
.xmodule_edit.xmodule_ProblemBlock .editor .editor-tabs li {
float: left;
margin-right: calc(var(--baseline, 20px) / 4);
}
.xmodule_edit.xmodule_ProblemBlock .editor .editor-tabs li:last-child {
margin-right: 0;
}
.xmodule_edit.xmodule_ProblemBlock .editor .editor-tabs .tab {
display: block;
height: 24px;
padding: 7px 20px 3px;
border: 1px solid #a5aaaf;
border-radius: 3px 3px 0 0;
background-color: var(--transparent, transparent);
background-image: -webkit-linear-gradient(top, var(--transparent, transparent) 87%, rgba(0, 0, 0, 0.06));
background-image: linear-gradient(to bottom, var(--transparent, transparent) 87%, rgba(0, 0, 0, 0.06));
background-color: #e5ecf3;
font-size: 13px;
color: #3c3c3c;
box-shadow: 1px -1px 1px rgba(0, 0, 0, 0.05);
}
.xmodule_edit.xmodule_ProblemBlock .editor .editor-tabs .tab.current {
background: var(--white, #fff);
border-bottom-color: var(--white, #fff);
}
.xmodule_edit.xmodule_ProblemBlock .editor-bar .editor-tabs .advanced-toggle {
height: auto;
margin-top: -4px;
padding: 3px 9px;
font-size: 12px;
color: var(--link-color, #1b6d99);
}
.xmodule_edit.xmodule_ProblemBlock .editor-bar .editor-tabs .advanced-toggle.current {
border: 1px solid var(--lightGrey, #edf1f5) !important;
border-radius: 3px !important;
background: var(--lightGrey, #edf1f5) !important;
color: var(--darkGrey, #8891a1) !important;
pointer-events: none;
cursor: none;
}
.xmodule_edit.xmodule_ProblemBlock .editor-bar .editor-tabs .advanced-toggle.current:hover,
.xmodule_edit.xmodule_ProblemBlock .editor-bar .editor-tabs .advanced-toggle.current:focus {
box-shadow: 0 0 0 0 !important;
background-color: var(--white, #fff);
}
.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet {
position: absolute;
top: 41px;
left: 70%;
width: 0;
border-left: 1px solid var(--gray-l2, #adadad);
background-color: var(--lightGrey, #edf1f5);
overflow: hidden;
}
.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet.shown {
width: 30%;
height: 92%;
overflow-y: scroll;
}
.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .cheatsheet-wrapper {
padding: 5%;
}
.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet h6 {
margin-top: 4px;
margin-bottom: 7px;
margin-left: 4px;
font-size: 15px;
font-weight: 700;
display: inline-block;
vertical-align: top;
}
.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .row {
padding-bottom: 5px !important;
margin-bottom: 10px !important;
border-bottom: 1px solid #ddd !important;
}
.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .row:after {
content: "";
display: table;
clear: both;
}
.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .row:last-child {
border-bottom: none !important;
margin-bottom: 0 !important;
}
.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .col {
display: block;
}
.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .col.sample {
margin-right: 30px;
}
.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet .col.sample .icon {
height: calc(var(--baseline, 20px) * 1.5);
}
.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet pre {
font-size: 12px;
line-height: 18px;
}
.xmodule_edit.xmodule_ProblemBlock .simple-editor-cheatsheet code {
padding: 0;
background: none;
}
.xmodule_edit.xmodule_ProblemBlock .problem-editor .markdown-box + .CodeMirror {
padding: 10px;
width: 69%;
}
.xmodule_edit.xmodule_ProblemBlock .problem-editor-icon {
display: inline-block;
width: 26px;
height: 21px;
vertical-align: middle;
color: var(--body-color, #313131);
}

View File

@@ -1,5 +0,0 @@
---
metadata:
display_name: Blank Common Problem
markdown: ""
data: "<problem></problem>"

View File

@@ -1,27 +0,0 @@
---
metadata:
display_name: Checkboxes
markdown: |
You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes problems. Edit this component to replace this template with your own assessment.
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
[x] a correct answer
[ ] an incorrect answer
[ ] an incorrect answer
[x] a correct answer
data: |
<problem>
<choiceresponse>
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes problems. Edit this component to replace this template with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required.</label>
<description>You can add an optional tip or note related to the prompt like this.</description>
<checkboxgroup>
<choice correct="true">a correct answer</choice>
<choice correct="false">an incorrect answer</choice>
<choice correct="false">an incorrect answer</choice>
<choice correct="true">a correct answer</choice>
</checkboxgroup>
</choiceresponse>
</problem>

View File

@@ -1,51 +0,0 @@
---
metadata:
display_name: Checkboxes with Hints and Feedback
markdown: |
You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this.<<
[x] a correct answer {{ selected: You can specify optional feedback that appears after the learner selects and submits this answer. }, { unselected: You can specify optional feedback that appears after the learner clears and submits this answer.}}
[ ] an incorrect answer
[ ] an incorrect answer {{ selected: You can specify optional feedback for none, all, or a subset of the answers. }, { unselected: You can specify optional feedback for selected answers, cleared answers, or both.}}
[x] a correct answer
{{ ((A B D)) You can specify optional feedback for a combination of answers which appears after the specified set of answers is submitted. }}
{{ ((A B C D)) You can specify optional feedback for one, several, or all answer combinations. }}
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
hinted: true
data: |
<problem>
<choiceresponse>
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required.</label>
<description>You can add an optional tip or note related to the prompt like this.</description>
<checkboxgroup>
<choice correct="true">a correct answer
<choicehint selected="true">You can specify optional feedback that appears after the learner selects and submits this answer.</choicehint>
<choicehint selected="false">You can specify optional feedback that appears after the learner clears and submits this answer.</choicehint>
</choice>
<choice correct="false">an incorrect answer
</choice>
<choice correct="false">an incorrect answer
<choicehint selected="true">You can specify optional feedback for none, all, or a subset of the answers.</choicehint>
<choicehint selected="false">You can specify optional feedback for selected answers, cleared answers, or both.</choicehint>
</choice>
<choice correct="true">a correct answer
</choice>
<compoundhint value="A B D">You can specify optional feedback for a combination of answers which appears after the specified set of answers is submitted.</compoundhint>
<compoundhint value="A B C D">You can specify optional feedback for one, several, or all answer combinations.</compoundhint>
</checkboxgroup>
</choiceresponse>
<demandhint>
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
</demandhint>
</problem>

View File

@@ -1,89 +0,0 @@
---
metadata:
display_name: Circuit Schematic Builder
markdown: !!null
data: |
<problem>
<p>
Circuit schematic problems allow students to create virtual circuits by
arranging elements such as voltage sources, capacitors, resistors, and
MOSFETs on an interactive grid. The system evaluates a DC, AC, or
transient analysis of the circuit.
</p>
<p>
For more information, see
<a href="https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/add_circuit_schematic_builder.html" target="_blank">
Circuit Schematic Builder Problem</a> in <i>Building and Running an Open edX Course</i>.
</p>
<p>
When you add the problem, be sure to select <strong>Settings</strong>
to specify a <strong>Display Name</strong> and other values that apply.
</p>
<p>You can use the following example problems as models.</p>
<schematicresponse>
<p>Make a voltage divider that splits the provided voltage evenly.</p>
<center>
<schematic height="500" width="600" parts="g,r" analyses="dc"
initial_value="[[&quot;v&quot;,[168,144,0],{&quot;value&quot;:&quot;dc(1)&quot;,&quot;_json_&quot;:0},[&quot;1&quot;,&quot;0&quot;]],[&quot;r&quot;,[296,120,0],{&quot;r&quot;:&quot;1&quot;,&quot;_json_&quot;:1},[&quot;1&quot;,&quot;output&quot;]],[&quot;L&quot;,[296,168,3],{&quot;label&quot;:&quot;output&quot;,&quot;_json_&quot;:2},[&quot;output&quot;]],[&quot;w&quot;,[296,216,168,216]],[&quot;w&quot;,[168,216,168,192]],[&quot;w&quot;,[168,144,168,120]],[&quot;w&quot;,[168,120,296,120]],[&quot;g&quot;,[168,216,0],{&quot;_json_&quot;:7},[&quot;0&quot;]],[&quot;view&quot;,-67.49999999999994,-78.49999999999994,1.6000000000000003,&quot;50&quot;,&quot;10&quot;,&quot;1G&quot;,null,&quot;100&quot;,&quot;1&quot;,&quot;1000&quot;]]"/>
</center>
<answer type="loncapa/python">
dc_value = "dc analysis not found"
for response in submission[0]:
if response[0] == 'dc':
for node in response[1:]:
dc_value = node['output']
if dc_value == .5:
correct = ['correct']
else:
correct = ['incorrect']
</answer>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>
You can form a voltage divider that evenly divides the input
voltage with two identically valued resistors, with the sampled
voltage taken in between the two.
</p>
<p><img src="/static/images/voltage_divider.png" alt=""/></p>
</div>
</solution>
</schematicresponse>
<schematicresponse>
<p>Make a high-pass filter.</p>
<center>
<schematic height="500" width="600" parts="g,r,s,c" analyses="ac"
submit_analyses="{&quot;ac&quot;:[[&quot;NodeA&quot;,1,9]]}"
initial_value="[[&quot;v&quot;,[160,152,0],{&quot;name&quot;:&quot;v1&quot;,&quot;value&quot;:&quot;sin(0,1,1,0,0)&quot;,&quot;_json_&quot;:0},[&quot;1&quot;,&quot;0&quot;]],[&quot;w&quot;,[160,200,240,200]],[&quot;g&quot;,[160,200,0],{&quot;_json_&quot;:2},[&quot;0&quot;]],[&quot;L&quot;,[240,152,3],{&quot;label&quot;:&quot;NodeA&quot;,&quot;_json_&quot;:3},[&quot;NodeA&quot;]],[&quot;s&quot;,[240,152,0],{&quot;color&quot;:&quot;cyan&quot;,&quot;offset&quot;:&quot;0&quot;,&quot;_json_&quot;:4},[&quot;NodeA&quot;]],[&quot;view&quot;,64.55878906250004,54.114697265625054,2.5000000000000004,&quot;50&quot;,&quot;10&quot;,&quot;1G&quot;,null,&quot;100&quot;,&quot;1&quot;,&quot;1000&quot;]]"/>
</center>
<answer type="loncapa/python">
ac_values = None
for response in submission[0]:
if response[0] == 'ac':
for node in response[1:]:
ac_values = node['NodeA']
print("the ac analysis value:", ac_values)
if ac_values == None:
correct = ['incorrect']
elif ac_values[0][1] &lt; ac_values[1][1]:
correct = ['correct']
else:
correct = ['incorrect']
</answer>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>
You can form a simple high-pass filter without any further
constraints by simply putting a resistor in series with a
capacitor. The actual values of the components do not really
matter in this problem.
</p>
<p><img src="/static/images/high_pass_filter.png" alt=""/></p>
</div>
</solution>
</schematicresponse>
</problem>

View File

@@ -1,81 +0,0 @@
---
metadata:
display_name: Custom Python-Evaluated Input
markdown: !!null
data: |
<problem>
<p>
In custom Python-evaluated input (also called "write-your-own-grader"
problems), the grader uses a Python script that you create and embed in
the problem to evaluate a learner's response or provide hints. These
problems can be any type. Numerical input and text input problems are
the most common write-your-own-grader problems.
</p>
<p>
You can use script tag format or answer tag format to create these problems.
</p>
<p>
You can create custom Python-evaluated input problems that provide
partial credit or that randomize variables in the Python code. You can
also add images to the solution by using an HTML "img" tag. Note that
the "img" tag must be between the "div" tags that are inside the
"solution" tags, and that learners do not see these images until they
click the "Show Answer" button.
</p>
<p>
For more information, see <a href="https://docs.openedx.org/en/latest/educators/references/course_development/exercise_tools/guide_custom_python_problem.html" target="_blank">
Write-Your-Own-Grader Problem</a> in <i>Building and Running an Open edX Course</i>.
</p>
<p>
When you add the problem, be sure to select <strong>Settings</strong>
to specify a <strong>Display Name</strong> and other values that apply.
</p>
<p>You can use the following example problem as a model.</p>
<hr/>
<customresponse cfn="test_add_to_ten">
<script type="loncapa/python">
def test_add_to_ten(expect, ans):
return test_add(10, ans)
</script>
<label>Enter two integers that sum to 10.</label>
<textline size="40" correct_answer="3" label="Enter first number" /><br/>
<textline size="40" correct_answer="7" label="Enter second number" />
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Any set of integers on the line \(y = 10 - x\) satisfy these constraints.</p>
</div>
</solution>
</customresponse>
<customresponse cfn="test_add" expect="20">
<script type="loncapa/python">
def test_add(expect, ans):
try:
a1=int(ans[0])
a2=int(ans[1])
return (a1+a2) == int(expect)
except ValueError:
return False
</script>
<label>Enter two integers that sum to 20.</label>
<textline size="40" correct_answer="11" label="Enter first number" /><br/>
<textline size="40" correct_answer="9" label="Enter second number" />
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Any set of integers on the line \(y = 20 - x\) satisfy these constraints.</p>
<p>To add an image to the solution, use an HTML "img" tag. Make sure to include alt text.</p>
<img src="/static/images/placeholder-image.png" width="400"
alt="Description of image, with a primary goal of explaining its
relevance to the problem or concept being illustrated for someone
who is unable to see the image."/>
</div>
</solution>
</customresponse>
</problem>

View File

@@ -1,85 +0,0 @@
---
metadata:
display_name: Drag and Drop (Deprecated Version)
markdown: !!null
showanswer: never
data: |
<problem>
<p>In drag and drop problems, students respond to a question by dragging text or objects to a specific location on an image.</p>
<p>
For more information, see
<a href="https://docs.openedx.org/en/latest/educators/references/course_development/exercise_tools/guide_drag_and_drop.html" target="_blank">
Drag and Drop Problem (Deprecated)</a> in <i>Building and Running an Open edX Course</i>.
</p>
<p>
When you add the problem, be sure to select <strong>Settings</strong>
to specify a <strong>Display Name</strong> and other values that apply.
</p>
<p>You can use the following example problems as models.</p>
<hr/>
<customresponse>
<h3>Simple Drag and Drop</h3>
<p>Drag each word in the scrollbar to the bucket that matches the number of letters in the word.</p>
<drag_and_drop_input img="https://studio.edx.org/c4x/edX/DemoX/asset/L9_buckets.png">
<draggable id="1" label="a"/>
<draggable id="2" label="bog"/>
<draggable id="3" label="droll"/>
<draggable id="4" label="oboe"/>
<draggable id="5" label="swain"/>
<draggable id="6" label="in"/>
<draggable id="7" label="onyx"/>
<draggable id="8" label="of"/>
<draggable id="9" label="tap"/>
<draggable id="10" label="strop"/>
<draggable id="11" label="few"/>
</drag_and_drop_input>
<answer type="loncapa/python">
correct_answer = {
'1': [[70, 150], 121],
'6': [[190, 150], 121],
'8': [[190, 150], 121],
'2': [[310, 150], 121],
'9': [[310, 150], 121],
'11': [[310, 150], 121],
'4': [[420, 150], 121],
'7': [[420, 150], 121],
'3': [[550, 150], 121],
'5': [[550, 150], 121],
'10': [[550, 150], 121]}
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
</answer>
</customresponse>
<customresponse>
<h3>Drag and Drop with Outline</h3>
<p>Label the hydrogen atoms connected with the left carbon atom.</p>
<drag_and_drop_input img="https://studio.edx.org/c4x/edX/DemoX/asset/ethglycol.jpg" target_outline="true" one_per_target="true" no_labels="true" label_bg_color="rgb(222, 139, 238)">
<draggable id="1" label="Hydrogen" />
<draggable id="2" label="Hydrogen" />
<target id="t1_o" x="10" y="67" w="100" h="100"/>
<target id="t2" x="133" y="3" w="70" h="70"/>
<target id="t3" x="2" y="384" w="70" h="70"/>
<target id="t4" x="95" y="386" w="70" h="70"/>
<target id="t5_c" x="94" y="293" w="91" h="91"/>
<target id="t6_c" x="328" y="294" w="91" h="91"/>
<target id="t7" x="393" y="463" w="70" h="70"/>
<target id="t8" x="344" y="214" w="70" h="70"/>
<target id="t9_o" x="445" y="162" w="100" h="100"/>
<target id="t10" x="591" y="132" w="70" h="70"/>
</drag_and_drop_input>
<answer type="loncapa/python">
correct_answer = [{
'draggables': ['1', '2'],
'targets': ['t2', 't3', 't4' ],
'rule':'anyof'
}]
if draganddrop.grade(submission[0], correct_answer):
correct = ['correct']
else:
correct = ['incorrect']
</answer>
</customresponse>
</problem>

View File

@@ -1,14 +0,0 @@
---
metadata:
display_name: Math Expression Input
markdown: !!null
data: |
<problem>
<formularesponse type="ci" samples="R_1,R_2,R_3@1,2,3:3,4,5#10" answer="R_1*R_2/R_3">
<p>You can use this template as a guide to the OLX markup to use for math expression problems. Edit this component to replace the example with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required. Example: Write an expression for the product of R_1, R_2, and the inverse of R_3.</label>
<description>You can add an optional tip or note related to the prompt like this. Example: To test this example, the correct answer is R_1*R_2/R_3</description>
<responseparam type="tolerance" default="0.00001"/>
<formulaequationinput size="40"/>
</formularesponse>
</problem>

View File

@@ -1,30 +0,0 @@
---
metadata:
display_name: Image Mapped Input
markdown: !!null
data: |
<problem>
<p>
In an image mapped input problem, also known as a "pointing on a picture" problem, students click inside a defined region in an image. You define this region by including coordinates in the body of the problem. You can define one rectangular region,
multiple rectangular regions, or one non-rectangular region. For more information, see
<a href="https://docs.openedx.org/en/latest/educators/concepts/exercise_tools/about_image_mapped_input.html" target="_blank">Image Mapped Input Problem</a>
in
<i>Building and Running an Open edX Course</i>.
</p>
<p>When you add the problem, be sure to select
<strong>Settings</strong>
to specify a
<strong>Display Name</strong>
and other values that apply.</p>
<p>You can use the following example problem as a model.</p>
<imageresponse>
<p>What country is home to the Great Pyramid of Giza as well as the cities of Cairo and Memphis? Click the country on the map below.</p>
<imageinput src="https://studio.edx.org/c4x/edX/DemoX/asset/Africa.png" width="600" height="638" rectangle="(338,98)-(412,168)" alt="Map of Africa"/>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Egypt is home to not only the Pyramids, Cairo, and Memphis, but also the Sphinx and the ancient Royal Library of Alexandria.</p>
</div>
</solution>
</imageresponse>
</problem>

View File

@@ -1,72 +0,0 @@
---
metadata:
display_name: Custom JavaScript Display and Grading
markdown: !!null
showanswer: never
data: |
<problem>
<p>
In these problems (also called custom JavaScript problems or JS Input
problems), you add a problem or tool that uses JavaScript in Studio.
Studio embeds the problem in an IFrame so that your learners can
interact with it in the LMS. You can grade your learners' work using
JavaScript and some basic Python, and the grading is integrated into the
edX grading system.
</p>
<p>
The JS Input problem that you create must use HTML, JavaScript, and
cascading style sheets (CSS). You can use any application creation tool,
such as the Google Web Toolkit (GWT), to create your JS Input problem.
</p>
<p>
For more information, see
<a href="https://docs.openedx.org/en/latest/educators/references/course_development/exercise_tools/custom_javascript.html" target="_blank">
Custom JavaScript Problem</a> in <i>Building and Running an Open edX Course</i>.
</p>
<p>
When you add the problem, be sure to select <strong>Settings</strong>
to specify a <strong>Display Name</strong> and other values that apply.
Also, be sure to specify a <strong>title</strong> attribute on the <strong>jsinput</strong> tag;
this title is used for the title attribute on the generated IFrame. Generally,
the title attribute on the IFrame should match the title tag of the HTML hosted
within the IFrame, which is specified by the <strong>html_file</strong> attribute.
</p>
<p>You can use the following example problem as a model.</p>
<customresponse cfn="check_function">
<script type="loncapa/python">
<![CDATA[
import json
def check_function(e, ans):
"""
"response" is a dictionary that contains two keys, "answer" and "state".
The value of "answer" is the JSON string that "getGrade" returns.
The value of "state" is the JSON string that "getState" returns.
Clicking either "Submit" or "Save" registers the current state.
"""
response = json.loads(ans)
# You can use the value of the answer key to grade:
answer = json.loads(response["answer"])
return answer == "correct"
# Or you can use the value of the state key to grade:
"""
state = json.loads(response["state"])
return state["selectedChoice"] == "correct"
"""
]]>
</script>
<p>This is paragraph text displayed before the IFrame.</p>
<jsinput
gradefn="JSInputDemo.getGrade"
get_statefn="JSInputDemo.getState"
set_statefn="JSInputDemo.setState"
initial_state='{"selectedChoice": "incorrect1", "availableChoices": ["incorrect1", "correct", "incorrect2"]}'
width="600"
height="100"
html_file="https://files.edx.org/custom-js-example/jsinput_example.html"
title="Dropdown with Dynamic Text"
sop="false"/>
</customresponse>
</problem>

View File

@@ -1,247 +0,0 @@
---
metadata:
display_name: Problem Written in LaTeX
source_code: |
% Nearly any kind of edX problem can be authored using LaTeX as
% the source language. Write LaTeX as usual, including equations. The
% key feature is the \edXabox{} macro, which specifies an "answer
% box" that queries students for a response, and specifies what the
% expected (correct) answer is.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example "option" problem}
Which of the following countries celebrates its independence on August 15?
\edXabox{options='India','Spain','China','Bermuda' expect='India'}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example "multiple choice" problem}
Which of the following countries has the largest population?
\edXabox{ type="multichoice" expect="Indonesia" options="Brazil","Germany","Indonesia","Russia" }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example "symbolic" problem}
What is Einstein's equation for the energy equivalent of a mass $m$?
\edXabox{type='symbolic' size='90' expect='m*c^2' }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example "numerical" problem}
Estimate the energy savings (in J/y) if all the people
($3\times 10^8$) in the U.~S. switched from U.~S. code to low-flow
shower heads.
\edXinline{Energy saved = }\edXabox{expect="0.52" type="numerical" tolerance='0.02' inline='1' } %
\edXinline{~EJ/year}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example "string response" problem}
What was the first post-secondary school in China to allow both male and female students?
\edXabox{ type="string" expect="Nanjing Higher Normal Institute" options="ci" }
You can include an explanation of the answer by using the edXsolution
macro. Click "Show Answer" to see the explanation.
\begin{edXsolution}
Nanjing Higher Normal Institute first admitted female students in 1920.
\end{edXsolution}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example "custom response" problem}
In this problem, a custom Python script determines if the answer is correct.
\begin{edXscript}
def sumtest(expect,ans):
(a1,a2) = map(float,eval(ans))
return (a1+a2)==10
\end{edXscript}
Enter a Python list of two numbers that sum to 10. For example, your answer might be [9,1] or [4,6].
\edXabox{expect="[1,9]" type="custom" cfn="sumtest"}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example image}
Include an image by using the following macro:
\edXxml{<img src="https://courses.edx.org/static/images/placeholder-image.png"/>}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\subsection{Example show/hide explanation}
You can provide additional information that only appears at certain times by including a "showhide" flag.
\edXshowhide{sh1}{More explanation}{This is a hidden explanation. It
can contain equations, such as $\alpha = \frac{2}{\sqrt{1+\gamma}}$ }.
This is additional text after the hidden explanation.
markdown: !!null
data: |
<?xml version="1.0"?>
<problem>
<p>
If you have a problem that is already written in LaTeX, you can use this problem type to
easily convert your code into XML. After you paste your code into the LaTeX editor,
you only need to make a few minor adjustments.
</p>
<p>
For more information, see
<a href="https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/create_problem_in_latex.html" target="_blank">
Problem Written in LaTeX</a> in <i>Building and Running an Open edX Course</i>.
</p>
<p>You can use the following example problems as models.</p>
<p><strong>Example Option Problem</strong></p>
<br/>
<optionresponse>
<label>Which of the following countries celebrates its independence on August 15?</label>
<optioninput options="('India','Spain','China','Bermuda')" correct="India"></optioninput>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>India became an independent nation on August 15, 1947.</p>
</div>
</solution>
</optionresponse>
<br/>
<p><strong>Example Multiple Choice Problem</strong></p>
<multiplechoiceresponse>
<label>Which of the following countries has the largest population?</label>
<choicegroup type="MultipleChoice">
<choice correct="false" name="brazil">Brazil</choice>
<choice correct="false" name="germany">Germany</choice>
<choice correct="true" name="indonesia">Indonesia</choice>
<choice correct="false" name="russia">Russia</choice>
</choicegroup>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>According to September 2014 estimates:</p>
<p>The population of Indonesia is approximately 250 million.</p>
<p>The population of Brazil is approximately 200 million.</p>
<p>The population of Russia is approximately 146 million.</p>
<p>The population of Germany is approximately 81 million.</p>
</div>
</solution>
</multiplechoiceresponse>
<br/>
<p><strong>Example Math Expression Problem</strong></p>
<symbolicresponse expect="m*c^2">
<p>What is Einstein's equation for the energy equivalent of a mass [mathjaxinline]m[/mathjaxinline]?</p>
<textline size="90" correct_answer="m*c^2" math="1"/>
</symbolicresponse>
<br/>
<p><strong>Example Numerical Problem</strong></p>
<numericalresponse inline="1" answer="0.52">
<label>Estimate the energy savings (in J/y) if all the people ([mathjaxinline]3\times 10^8[/mathjaxinline]) in the U.&#xA0;S. switched from U.&#xA0;S. code to low-flow shower heads.</label>
<formulaequationinput trailing_text="EJ/year" />
<responseparam type="tolerance" default="0.02"/>
</numericalresponse>
<br/>
<p><strong>Example Fill-in-the-Blank Problem</strong></p>
<stringresponse answer="Nanjing Higher Normal Institute" type="ci" >
<label>What was the first post-secondary school in China to allow both male and female students?</label>
<additional_answer>National Central University</additional_answer>
<additional_answer>Nanjing University</additional_answer>
<textline size="40"/>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Nanjing Higher Normal Institute first admitted female students in 1920.</p>
</div>
</solution>
</stringresponse>
<br/>
<p><strong>Example Custom Python-Evaluated Input Problem</strong></p>
<customresponse cfn="test_add_to_ten">
<script type="loncapa/python">
def test_add_to_ten(expect, ans):
return test_add(10, ans)
</script>
<p>Enter two integers that sum to 10.</p>
<textline size="40" correct_answer="3"/><br/>
<textline size="40" correct_answer="7"/>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Any set of integers on the line \(y = 10 - x\) satisfy these constraints.</p>
</div>
</solution>
</customresponse>
<customresponse cfn="test_add" expect="20">
<script type="loncapa/python">
def test_add(expect, ans):
try:
a1=int(ans[0])
a2=int(ans[1])
return (a1+a2) == int(expect)
except ValueError:
return False
</script>
<p>Enter two integers that sum to 20.</p>
<textline size="40" correct_answer="11"/><br/>
<textline size="40" correct_answer="9"/>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Any set of integers on the line \(y = 20 - x\) satisfy these constraints.</p>
<p>To add an image to the solution, use an HTML "img" tag. Make sure to include alt text.</p>
<img src="/static/images/placeholder-image.png" width="400" alt="Description of image"/>
</div>
</solution>
</customresponse>
<br/>
<p><strong>Example Image Mapped Input Problem</strong></p>
<imageresponse>
<p>
What country is home to the Great Pyramid of Giza as well as the cities
of Cairo and Memphis? Click the country on the map below.
</p>
<imageinput src="https://studio.edx.org/c4x/edX/DemoX/asset/Africa.png"
width="600" height="638" rectangle="(338,98)-(412,168)" alt="Map of
Africa"/>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Egypt is home to not only the Pyramids, Cairo, and Memphis, but also
the Sphinx and the ancient Royal Library of Alexandria.</p>
</div>
</solution>
</imageresponse>
<br/>
<p><strong>Example Hidden Explanation</strong></p>
<p>You can provide additional information that only appears at certain times by including a "showhide" flag. </p>
<p>
<table class="wikitable collapsible collapsed">
<tbody>
<tr>
<th> More Information [<a href="javascript:$('#sh1').toggle()" id="sh1l">show</a>]</th>
</tr>
<tr id="sh1" style="display:none">
<td>
<p>This is a hidden explanation. It can contain equations, such as [mathjaxinline]\alpha = \frac{2}{\sqrt {1+\gamma }}[/mathjaxinline]. </p>
<p>This is additional text after the hidden explanation. </p>
</td>
</tr>
</tbody>
</table>
</p>
</problem>

View File

@@ -1,26 +0,0 @@
---
metadata:
display_name: Multiple Choice
markdown: |
You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice problems. Edit this component to replace this template with your own assessment.
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
( ) an incorrect answer
(x) the correct answer
( ) an incorrect answer
data: |
<problem>
<multiplechoiceresponse>
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice problems. Edit this component to replace this template with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required.</label>
<description>You can add an optional tip or note related to the prompt like this.</description>
<choicegroup>
<choice correct="false">an incorrect answer</choice>
<choice correct="true">the correct answer</choice>
<choice correct="false">an incorrect answer</choice>
</choicegroup>
</multiplechoiceresponse>
</problem>

View File

@@ -1,40 +0,0 @@
---
metadata:
display_name: Multiple Choice with Hints and Feedback
markdown: |
You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice with hints and feedback problems. Edit this component to replace this template with your own assessment.
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
( ) an incorrect answer {{You can specify optional feedback like this, which appears after this answer is submitted.}}
(x) the correct answer
( ) an incorrect answer {{You can specify optional feedback for none, a subset, or all of the answers.}}
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
hinted: true
data: |
<problem>
<multiplechoiceresponse>
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required.</label>
<description>You can add an optional tip or note related to the prompt like this.</description>
<choicegroup>
<choice correct="false">an incorrect answer
<choicehint>You can specify optional feedback like this, which appears after this answer is submitted.</choicehint>
</choice>
<choice correct="true">the correct answer
</choice>
<choice correct="false">an incorrect answer
<choicehint>You can specify optional feedback for none, a subset, or all of the answers.</choicehint>
</choice>
</choicegroup>
</multiplechoiceresponse>
<demandhint>
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
</demandhint>
</problem>

View File

@@ -1,20 +0,0 @@
---
metadata:
display_name: Numerical Input
markdown: |
You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input problems. Edit this component to replace this template with your own assessment.
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
= 100 +-5
data: |
<problem>
<numericalresponse answer="100">
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input problems. Edit this component to replace this template with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required.</label>
<description>You can add an optional tip or note related to the prompt like this.</description>
<responseparam type="tolerance" default="5"/>
<formulaequationinput/>
</numericalresponse>
</problem>

View File

@@ -1,29 +0,0 @@
---
metadata:
display_name: Numerical Input with Hints and Feedback
markdown: |
You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input with hints and feedback problems. Edit this component to replace this template with your own assessment.
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
= 100 +-5 {{You can specify optional feedback like this, which appears after this answer is submitted.}}
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
hinted: true
data: |
<problem>
<numericalresponse answer="100">
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for numerical input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required.</label>
<description>You can add an optional tip or note related to the prompt like this.</description>
<responseparam type="tolerance" default="5"/>
<formulaequationinput/>
<correcthint>You can specify optional feedback like this, which appears after this answer is submitted.</correcthint>
</numericalresponse>
<demandhint>
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
</demandhint>
</problem>

View File

@@ -1,24 +0,0 @@
---
metadata:
display_name: Dropdown
markdown: |
You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown problems. Edit this component to replace this template with your own assessment.
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
[[
an incorrect answer
(the correct answer)
an incorrect answer
]]
data: |
<problem>
<optionresponse>
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown problems. Edit this component to replace this template with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required.</label>
<description>You can add an optional tip or note related to the prompt like this. </description>
<optioninput>
<option correct="False">an incorrect answer</option>
<option correct="True">the correct answer</option>
<option correct="False">an incorrect answer</option>
</optioninput>
</optionresponse>
</problem>

View File

@@ -1,33 +0,0 @@
---
metadata:
display_name: Dropdown with Hints and Feedback
markdown: |
You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown with hints and feedback problems. Edit this component to replace this template with your own assessment.
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
[[
an incorrect answer {{You can specify optional feedback like this, which appears after this answer is submitted.}}
(the correct answer)
an incorrect answer {{You can specify optional feedback for none, a subset, or all of the answers.}}
]]
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
hinted: true
data: |
<problem>
<optionresponse>
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required.</label>
<description>You can add an optional tip or note related to the prompt like this. </description>
<optioninput>
<option correct="False">an incorrect answer <optionhint>You can specify optional feedback like this, which appears after this answer is submitted.</optionhint></option>
<option correct="True">the correct answer</option>
<option correct="False">an incorrect answer <optionhint>You can specify optional feedback for none, a subset, or all of the answers.</optionhint></option>
</optioninput>
</optionresponse>
<demandhint>
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
</demandhint>
</problem>

View File

@@ -1,43 +0,0 @@
---
metadata:
display_name: Problem with Adaptive Hint
markdown: !!null
data: |
<problem>
<text>
<p><h4>Problem With Adaptive Hint</h4></p>
<p>This problem demonstrates a question with hints, based on using the <tt class="tt">hintfn</tt> method. </p>
<customresponse cfn="test_str" expect="python">
<script type="text/python" system_path="python_lib">
def test_str(expect, ans):
print(expect, ans)
ans = ans.strip("'")
ans = ans.strip('"')
return expect == ans.lower()
def hint_fn(answer_ids, student_answers, new_cmap, old_cmap):
aid = answer_ids[0]
ans = str(student_answers[aid]).lower()
print('hint_fn called, ans=', ans)
hint = ''
if 'java' in ans:
hint = 'that is only good for drinking'
elif 'perl' in ans:
hint = 'not that rich'
elif 'pascal' in ans:
hint = 'that is a beatnick language'
elif 'fortran' in ans:
hint = 'those were the good days'
elif 'clu' in ans:
hint = 'you must be invariant'
if hint:
hint = "&lt;font color='blue'&gt;Hint: {0}&lt;/font&gt;".format(hint)
new_cmap.set_hint_and_mode(aid,hint,'always')
</script>
<label>What is the best programming language that exists today? You may enter your answer in upper or lower case, with or without quotes.</label>
<textline correct_answer="python"/>
<hintgroup hintfn="hint_fn"/>
</customresponse>
</text>
</problem>

View File

@@ -1,88 +0,0 @@
---
metadata:
display_name: Problem with Adaptive Hint in Latex
source_code: |
\subsection{Problem With Adaptive Hint}
% Adaptive hints are messages provided to students which depend on
% student input. These hints are produced using a script embedded
% within the problem (written in Python).
%
% Here is an example. This example uses LaTeX as a high-level
% soure language for the problem. The problem can also be coded
% directly in XML.
This problem demonstrates a question with hints, based on using the
{\tt hintfn} method.
\begin{edXscript}
def test_str(expect, ans):
print(expect, ans)
ans = ans.strip("'")
ans = ans.strip('"')
return expect == ans.lower()
def hint_fn(answer_ids, student_answers, new_cmap, old_cmap):
aid = answer_ids[0]
ans = str(student_answers[aid]).lower()
print('hint_fn called, ans=', ans)
hint = ''
if 'java' in ans:
hint = 'that is only good for drinking'
elif 'perl' in ans:
hint = 'not that rich'
elif 'pascal' in ans:
hint = 'that is a beatnick language'
elif 'fortran' in ans:
hint = 'those were the good days'
elif 'clu' in ans:
hint = 'you must be invariant'
if hint:
hint = "<font color='blue'>Hint: {0}</font>".format(hint)
new_cmap.set_hint_and_mode(aid,hint,'always')
\end{edXscript}
What is the best programming language that exists today? You may
enter your answer in upper or lower case, with or without quotes.
\edXabox{type="custom" cfn='test_str' expect='python' hintfn='hint_fn'}
markdown: !!null
data: |
<problem>
<text>
<p><h4>Problem With Adaptive Hint</h4></p>
<p>This problem demonstrates a question with hints, based on using the <tt class="tt">hintfn</tt> method.</p>
<customresponse cfn="test_str" expect="python">
<script type="text/python" system_path="python_lib">
def test_str(expect, ans):
print(expect, ans)
ans = ans.strip("'")
ans = ans.strip('"')
return expect == ans.lower()
def hint_fn(answer_ids, student_answers, new_cmap, old_cmap):
aid = answer_ids[0]
ans = str(student_answers[aid]).lower()
print('hint_fn called, ans=', ans)
hint = ''
if 'java' in ans:
hint = 'that is only good for drinking'
elif 'perl' in ans:
hint = 'not that rich'
elif 'pascal' in ans:
hint = 'that is a beatnick language'
elif 'fortran' in ans:
hint = 'those were the good days'
elif 'clu' in ans:
hint = 'you must be invariant'
if hint:
hint = "&lt;font color='blue'&gt;Hint: {0}&lt;/font&gt;".format(hint)
new_cmap.set_hint_and_mode(aid,hint,'always')
</script>
<p>What is the best programming language that exists today? You may enter your answer in upper or lower case, with or without quotes.</p>
<textline correct_answer="python"/>
<hintgroup hintfn="hint_fn"/>
</customresponse>
</text>
</problem>

View File

@@ -1,21 +0,0 @@
---
metadata:
display_name: Text Input
markdown: |
You can use this template as a guide to the simple editor markdown and OLX markup to use for text input problems. Edit this component to replace this template with your own assessment.
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
= the correct answer
or= optional acceptable variant of the correct answer
data: |
<problem>
<stringresponse answer="the correct answer" type="ci">
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input problems. Edit this component to replace this template with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required.</label>
<description>You can add an optional tip or note related to the prompt like this.</description>
<additional_answer answer="optional acceptable variant of the correct answer"/>
<textline size="20"/>
</stringresponse>
</problem>

View File

@@ -1,33 +0,0 @@
---
metadata:
display_name: Text Input with Hints and Feedback
markdown: |
You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
= the correct answer {{You can specify optional feedback like this, which appears after this answer is submitted.}}
or= optional acceptable variant of the correct answer
not= optional incorrect answer such as a frequent misconception {{You can specify optional feedback for none, a subset, or all of the answers.}}
||You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.||
||If you add more than one hint, a different hint appears each time learners select the hint button.||
hinted: true
data: |
<problem>
<stringresponse answer="the correct answer" type="ci">
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required.</label>
<description>You can add an optional tip or note related to the prompt like this.</description>
<correcthint>You can specify optional feedback like this, which appears after this answer is submitted.</correcthint>
<additional_answer answer="optional acceptable variant of the correct answer"/>
<stringequalhint answer="optional incorrect answer such as a frequent misconception">You can specify optional feedback for none, a subset, or all of the answers.</stringequalhint>
<textline size="20"/>
</stringresponse>
<demandhint>
<hint>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</hint>
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
</demandhint>
</problem>