From 16cb7d201e4f68e2bda036cd3686f3b954b7c7d8 Mon Sep 17 00:00:00 2001 From: redY132 Date: Wed, 25 Feb 2026 12:02:24 -0500 Subject: [PATCH 01/13] integrated toggles frontend with backend json --- src/parsons.js | 5 +++-- src/parsonsLine.js | 31 ++++++++++++++++++++++++++----- src/parsonsTextInput.js | 9 +++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 src/parsonsTextInput.js diff --git a/src/parsons.js b/src/parsons.js index 18eea3b..0d80ebd 100644 --- a/src/parsons.js +++ b/src/parsons.js @@ -315,8 +315,9 @@ export default class Parsons extends RunestoneBase { const displayMath = Boolean(pifBlock.displaymath); //make togglesArray work with backend later - var togglesArray = []; - var line = new ParsonsLine(this, blockText, displayMath, togglesArray); + var togglesArray = pifBlock.toggle_options; + var textArray = pifBlock.text_options; + var line = new ParsonsLine(this, blockText, displayMath, togglesArray, textArray); // Set properties - handle various indent formats const indentValue = pifBlock.indent; diff --git a/src/parsonsLine.js b/src/parsonsLine.js index 2acc1a0..5e7ed75 100644 --- a/src/parsonsLine.js +++ b/src/parsonsLine.js @@ -18,14 +18,16 @@ // Initialize from codestring import ParsonsToggle from './parsonsToggle.js'; +import ParsonsTextInput from './parsonsTextInput.js'; export default class ParsonsLine { - constructor(problem, codestring, displaymath, togglesArray) { + constructor(problem, codestring, displaymath, togglesArray, textArray) { this.problem = problem; this.index = problem.lines.length; var trimmed = codestring.replace(/\s*$/, ""); this.text = trimmed.replace(/^\s*/, ""); this.toggles = []; + this.textInputs= []; //28-31: Not from Runestone // this.text = this.text.replace(/\*\*(.*?)\*\*/g, '\(\textbf{$1}\)'); @@ -50,6 +52,7 @@ export default class ParsonsLine { } view.id = problem.counterId + "-line-" + this.index; + var offset = 0; //creating toggles within text if(togglesArray.length > 0){ for(let i = 0; i < togglesArray.length; i++){ @@ -58,10 +61,28 @@ export default class ParsonsLine { this.toggles.push(toggle); //inserts toggle into the inner html - const index = togglesArray[i].pos; - const endString = this.text.slice(index); - const startString = this.text.slice(0, index); - this.text = startString + " " + toggle.htmlContent + " " + endString; + const startIndex = togglesArray[i].start_index + offset; + const endIndex = togglesArray[i].end_index + offset; + + offset += toggle.htmlContent.length - (endIndex - startIndex); + + this.text = this.text.slice(0, startIndex) + toggle.htmlContent + this.text.slice(endIndex); + } + } + + if(textArray.length > 0){ + for(let i = 0; i < textArray.length; i++){ + //creates a new toggleobject + const textInput = new ParsonsTextInput(); + this.textInputs.push(textInput); + + //inserts toggle into the inner html + const startIndex = textArray[i].start_index + offset; + const endIndex = textArray[i].end_index + offset; + + offset += textInput.htmlContent.length - (endIndex - startIndex); + + this.text = this.text.slice(0, startIndex) + textInput.htmlContent + this.text.slice(endIndex); } } diff --git a/src/parsonsTextInput.js b/src/parsonsTextInput.js new file mode 100644 index 0000000..652d2d0 --- /dev/null +++ b/src/parsonsTextInput.js @@ -0,0 +1,9 @@ + +export default class ParsonsTextInput { + static textCount = 0; + constructor(){ + ParsonsTextInput.textCount++; + this.id = "text_input" + ParsonsTextInput.textCount; + this.htmlContent = `` + } +} From 4563c4e24a74ce2f501d6d0a8194683f932b84b1 Mon Sep 17 00:00:00 2001 From: Kwasi Biritwum-Nyarko Date: Fri, 6 Mar 2026 21:59:48 -0500 Subject: [PATCH 02/13] env.example update --- .DS_Store | Bin 6148 -> 6148 bytes .env.example | 4 +++- .gitignore | 1 + .vscode/settings.json | 4 ---- package-lock.json | 4 ++-- server/helpers/parsePIF.js | 10 +++++++--- 6 files changed, 13 insertions(+), 10 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.DS_Store b/.DS_Store index fb62fc239f8f2d7b379a7c930a8fa81d3259903d..6bc27d5606b7d7f75baaae2e6719c1ff4b7fb3e6 100644 GIT binary patch delta 63 zcmZoMXfc@JFT};bz`)4BAi$7PoSc)CpP$3HS&(BDvlvL6g&~C@lcAWQ1R=-6@{D;i IJI7ys0L*C*<^TWy delta 46 zcmZoMXfc@JFT%;dz`)4BAiz)-T$GoSpO+5gZ%kOh%*eS}k>wfl!~)UH>>Pjj0R%e? AIsgCw diff --git a/.env.example b/.env.example index 0e41d3e..2bf5db1 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,3 @@ -PORT = 3000 \ No newline at end of file +NODE_ENV= +PARSE_HOST= +PARSE_PATH= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 87eb213..cc3ff17 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules .idea/ dist/ +.vscode #sample input files for testing. Not essential to ignore #test-inputs/ diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 25dd8e5..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "terminal.explorerKind": "external", - "terminal.integrated.accessibleViewFocusOnCommandExecution": true -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 712e00b..97611ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "parsons-prakhar", - "version": "1.0.2", + "version": "1.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "parsons-prakhar", - "version": "1.0.2", + "version": "1.0.4", "license": "ISC", "dependencies": { "css-loader": "7.1.2", diff --git a/server/helpers/parsePIF.js b/server/helpers/parsePIF.js index ccf2a03..431fc5f 100644 --- a/server/helpers/parsePIF.js +++ b/server/helpers/parsePIF.js @@ -1,5 +1,6 @@ const fs = require('fs'); const https = require('https'); +const http = require('http'); const path = require('path'); const FormData = require('form-data'); const { logEvent } = require('./logger'); @@ -21,15 +22,18 @@ async function parsePIF(source, filename) { ); formBody.append('is_pif', 'true'); + const isDevEnv = process.env.NODE_ENV === 'development'; const parseCallOptions = { method: 'POST', - host: 'endeavour.cs.vt.edu', - path: '/peml-live/api/parse', + host: isDevEnv ? process.env.PARSE_HOST : 'endeavour.cs.vt.edu', + port: isDevEnv ? process.env.PARSE_PORT : undefined, + path: isDevEnv? process.env.PARSE_PATH : '/peml-live/api/parse', headers: formBody.getHeaders(), }; return new Promise((resolve, reject) => { - const req = https.request(parseCallOptions, (res) => { + const httpx = isDevEnv ? http : https; + const req = httpx.request(parseCallOptions, (res) => { let responseBody = ''; res.setEncoding('utf-8'); From bbf94fda7ebac5023e7614188f23477db0473c21 Mon Sep 17 00:00:00 2001 From: Kwasi Biritwum-Nyarko Date: Sat, 7 Mar 2026 16:37:09 -0500 Subject: [PATCH 03/13] code extract logged in console when is clicked for grading --- src/parsons.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parsons.js b/src/parsons.js index 0d80ebd..ca55fe3 100644 --- a/src/parsons.js +++ b/src/parsons.js @@ -479,7 +479,7 @@ export default class Parsons extends RunestoneBase { if (that.options.grader === "exec" || that.hasReusable) { //TODO: Implement executable grading and uncomment the line below var extractedCode = that.extractCode(); - console.log(`EXTRACTED CODE = ${extractedCode}`) + console.log("EXTRACTED CODE =\n" + JSON.stringify(extractedCode, null, 2)); const errorMessage = "Executable grading not yet implemented."; $('body').append(` From 2820d996dfe2ec53c62916f19a253cd1fe529a6b Mon Sep 17 00:00:00 2001 From: redY132 Date: Mon, 16 Mar 2026 19:00:21 -0400 Subject: [PATCH 04/13] improved toggles and text input replacement, added default text for text inputs --- src/parsons.js | 4 +++ src/parsonsLine.js | 60 ++++++++++++++++++++++------------------- src/parsonsTextInput.js | 13 ++++++--- src/parsonsToggle.js | 13 ++++++--- 4 files changed, 57 insertions(+), 33 deletions(-) diff --git a/src/parsons.js b/src/parsons.js index 0d80ebd..bb91e15 100644 --- a/src/parsons.js +++ b/src/parsons.js @@ -276,6 +276,7 @@ export default class Parsons extends RunestoneBase { // Get blocks from PIF data - handle both direct and nested structure const pifBlocks = this.pifData?.blocks || this.pifData?.value?.blocks || []; + console.log(pifBlocks); if (!Array.isArray(pifBlocks) || pifBlocks.length === 0) { console.warn('No valid blocks found in PIF data'); @@ -690,6 +691,9 @@ export default class Parsons extends RunestoneBase { for (let i = 0; i < line.indent; i++) { code += " "; } + + //replace toggle button html content with inner content + line.text = line.text.replace(/]*>(.*?)<\/button>/g, '$1'); code += line.text + "\n"; } } diff --git a/src/parsonsLine.js b/src/parsonsLine.js index 5e7ed75..9c73944 100644 --- a/src/parsonsLine.js +++ b/src/parsonsLine.js @@ -21,13 +21,13 @@ import ParsonsToggle from './parsonsToggle.js'; import ParsonsTextInput from './parsonsTextInput.js'; export default class ParsonsLine { - constructor(problem, codestring, displaymath, togglesArray, textArray) { + constructor(problem, codestring, displaymath, togglesArray = [], textArray = []) { this.problem = problem; this.index = problem.lines.length; var trimmed = codestring.replace(/\s*$/, ""); this.text = trimmed.replace(/^\s*/, ""); this.toggles = []; - this.textInputs= []; + this.textInputs = []; //28-31: Not from Runestone // this.text = this.text.replace(/\*\*(.*?)\*\*/g, '\(\textbf{$1}\)'); @@ -52,41 +52,47 @@ export default class ParsonsLine { } view.id = problem.counterId + "-line-" + this.index; - var offset = 0; - //creating toggles within text - if(togglesArray.length > 0){ - for(let i = 0; i < togglesArray.length; i++){ - //creates a new toggleobject - const toggle = new ParsonsToggle(togglesArray[i].values); - this.toggles.push(toggle); + //combine toggle and text arrays into one + const togglesAndTextInput = [ + ...togglesArray.map(toggle => ({ ...toggle, type: 'toggle'})), + ...textArray.map(textInput => ({ ...textInput, type: 'text'})) + ] - //inserts toggle into the inner html - const startIndex = togglesArray[i].start_index + offset; - const endIndex = togglesArray[i].end_index + offset; + //sort by earliest start index + togglesAndTextInput.sort((a, b) => a.start_index - b.start_index); - offset += toggle.htmlContent.length - (endIndex - startIndex); + //array for dom nodes that will be appended to view later + this.nodes = []; + let lastIndex = 0; - this.text = this.text.slice(0, startIndex) + toggle.htmlContent + this.text.slice(endIndex); + togglesAndTextInput.forEach(t => { + if (t.start_index > lastIndex) { + const leadingText = this.text.slice(lastIndex, t.start_index); + this.nodes.push(document.createTextNode(leadingText)); } - } - if(textArray.length > 0){ - for(let i = 0; i < textArray.length; i++){ - //creates a new toggleobject - const textInput = new ParsonsTextInput(); + if (t.type === 'toggle') { + const toggle = new ParsonsToggle(t); + this.toggles.push(toggle); + this.nodes.push(toggle.button); + } else { + const textInput = new ParsonsTextInput(t); this.textInputs.push(textInput); + this.nodes.push(textInput.text_input); + } - //inserts toggle into the inner html - const startIndex = textArray[i].start_index + offset; - const endIndex = textArray[i].end_index + offset; - - offset += textInput.htmlContent.length - (endIndex - startIndex); + lastIndex = t.end_index; + }); - this.text = this.text.slice(0, startIndex) + textInput.htmlContent + this.text.slice(endIndex); - } + // Final tail of the string + if (lastIndex < this.text.length) { + this.nodes.push(document.createTextNode(this.text.slice(lastIndex))); } - view.innerHTML += this.text; + this.nodes.forEach(node => { + view.appendChild(node); + }) + this.view = view; problem.lines.push(this); } diff --git a/src/parsonsTextInput.js b/src/parsonsTextInput.js index 652d2d0..866d7a4 100644 --- a/src/parsonsTextInput.js +++ b/src/parsonsTextInput.js @@ -1,9 +1,16 @@ - export default class ParsonsTextInput { static textCount = 0; - constructor(){ + constructor({start_index, end_index, inner_content}){ ParsonsTextInput.textCount++; this.id = "text_input" + ParsonsTextInput.textCount; - this.htmlContent = `` + this.inner_content = inner_content; + + this.start_index = start_index + this.end_index = end_index + + this.text_input = document.createElement('input'); + this.text_input.type = 'text'; + this.text_input.defaultValue = this.inner_content; + console.log(this.text_input); } } diff --git a/src/parsonsToggle.js b/src/parsonsToggle.js index fccbef4..26c8409 100644 --- a/src/parsonsToggle.js +++ b/src/parsonsToggle.js @@ -1,11 +1,18 @@ export default class ParsonsToggle { static toggleCount = 0; - constructor(values){ + constructor({start_index, end_index, values}){ ParsonsToggle.toggleCount++; - this.values = values; + this.id = "toggle" + ParsonsToggle.toggleCount; + this.values = values; this.currentIndex = 0; - this.htmlContent = ``; + + this.start_index = start_index; + this.end_index = end_index; + + this.button = document.createElement('button'); + this.button.id = this.id; + this.button.textContent = this.values[0]; } nextValue(){ From 229fe1946292191d310ca55e41ef4c6621ab4c27 Mon Sep 17 00:00:00 2001 From: Kwasi Biritwum-Nyarko Date: Wed, 18 Mar 2026 21:29:29 -0400 Subject: [PATCH 05/13] parsons assets url updated in render template --- downloads/fixed-demo.peml | 59 --------------------------------------- server/renderer.js | 3 +- webpack.config.js | 2 +- 3 files changed, 2 insertions(+), 62 deletions(-) delete mode 100644 downloads/fixed-demo.peml diff --git a/downloads/fixed-demo.peml b/downloads/fixed-demo.peml deleted file mode 100644 index 59ded2e..0000000 --- a/downloads/fixed-demo.peml +++ /dev/null @@ -1,59 +0,0 @@ -# Demonstration of intermixed "fixed" blocks with other blocks. - -exercise_id: https://github.com/CSSPLICE/peml-feasibility-examples/blob/main/parsons/fixed-demo.peml - -title: Fixed Block Demo -author: Cliff Shaffer -license.id: MIT -license.owner.name: Cliff Shaffer -license.owner.email: shaffer@vt.edu - -tags.topics: PEML Demo Parsons Problem -tags.style: parsons, order - -instructions:---------- -Put the blocks in the proper order. ----------- - -# Note that the DAG implied by the depends tags -# only indicates the relative positioning of the -# adjustable blocks. It is the responsibility of the system -# implementation to deal with things like how the fixed blocks are -# displayed with respect to the orderable blocks, and limiting where -# the user can place an orderable block so that things appear in a -# reasonable order. - -[assets.code.blocks.content] -blockid: fixed -display: Fixed Start - -blockid: randomg1b1 -display: Random Group 1 Block 1 -depends: - -blockid: randomg1b2 -display: Random Group 1 Block 2 -depends: randomg1b1 - -blockid: randomg1b3 -display: Random Group 1 Block 3 -depends: randomg1b2 - -blockid: fixed -display: Fixed Middle - -blockid: randomg2b1 -display: Random Group 2 Block 1 -depends: randomg1b3 - -blockid: randomg2b2 -display: Random Group 2 Block 2 -depends: randomg2b1 - -blockid: randomg2b3 -display: Random Group 2 Block 3 -depends: randomg2b2 - -blockid: fixed -display: Fixed End -[] diff --git a/server/renderer.js b/server/renderer.js index 944fd69..d77728b 100644 --- a/server/renderer.js +++ b/server/renderer.js @@ -126,8 +126,7 @@ const parsonsPageTemplate = ` - - + + `); + + res.send(dom.serialize()); +}); + // Home page - list available files and upload option app.get('/parsons/', async (req, res) => { try { diff --git a/tests/positive/test.json b/tests/positive/test.json new file mode 100644 index 0000000..61c07b7 --- /dev/null +++ b/tests/positive/test.json @@ -0,0 +1,83 @@ +{ + "value": { + "question_text": "

The constructed code should print the minimum of variables a and b.

\n", + "options": { + "grader": { + "type": "exec", + "showFeedback": true + }, + "maxdist": 0, + "order": "", + "indent": { + "active": true, + "mode": "free", + "max_indents":3 + }, + "adaptive": true, + "numbered": false, + "language": "python", + "runnable": true + }, + "blocks": [ + { + "text": "if ``a`b`c`` ``<`>`>=`` b", + "type": "", + "tag": "one1", + "depends": "", + "indent": "0", + "displaymath": true, + "feedback": "", + "toggle_options": [ + { + "start_index": 3, + "end_index": 12, + "values": [ + "a", + "b", + "c" + ] + }, + { + "start_index": 13, + "end_index": 23, + "values": [ + "<", + ">", + ">=" + ] + } + ] + }, + { + "text": "print(a)", + "type": "", + "tag": "two", + "depends": "", + "indent": "1", + "displaymath": true, + "feedback": "" + }, + { + "text": "else", + "type": "", + "tag": "three", + "depends": "", + "indent": "0", + "displaymath": true, + "feedback": "" + }, + { + "text": "print(b)", + "type": "", + "tag": "four", + "depends": "", + "indent": "1", + "displaymath": true, + "feedback": "" + } + ] + }, + "diags": [ + + ] +} \ No newline at end of file From 0a9c6a807a00fe86e98bbcbd88cce1fbd81d98ac Mon Sep 17 00:00:00 2001 From: redY132 Date: Wed, 15 Apr 2026 22:24:01 -0400 Subject: [PATCH 12/13] remove console log --- src/parsons.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/parsons.js b/src/parsons.js index d73551b..b5848fb 100644 --- a/src/parsons.js +++ b/src/parsons.js @@ -684,7 +684,6 @@ export default class Parsons extends RunestoneBase { let code = ""; for (const block of this.answerBlocks()) { for (const line of block.lines) { - console.log(line); for (let i = 0; i < line.indent; i++) { code += " "; } @@ -3382,7 +3381,6 @@ export default class Parsons extends RunestoneBase { } state = newState; this.state = state; - console.log(this.state); } addBlockLabels(blocks) { From b244dca31153c3e6ecd9f6325ffb0976073f165e Mon Sep 17 00:00:00 2001 From: redY132 Date: Wed, 15 Apr 2026 23:11:26 -0400 Subject: [PATCH 13/13] json tests and max indents --- src/parsons.js | 54 +++++----- tests/positive/fixed-multi-demo-starter.json | 98 +++++++++++++++++++ .../jsparsons-toggle-blockchange.json | 73 ++++++++++++++ tests/positive/jsparsons-toggle.json | 73 ++++++++++++++ 4 files changed, 275 insertions(+), 23 deletions(-) create mode 100644 tests/positive/fixed-multi-demo-starter.json create mode 100644 tests/positive/jsparsons-toggle-blockchange.json create mode 100644 tests/positive/jsparsons-toggle.json diff --git a/src/parsons.js b/src/parsons.js index 926939d..262fcf5 100644 --- a/src/parsons.js +++ b/src/parsons.js @@ -185,8 +185,8 @@ export default class Parsons extends RunestoneBase { "c++": "prettyprint lang-cpp", cpp: "prettyprint lang-cpp", ruby: "prettyprint lang-rb", - math: "", - natural: "" + math: "", // No prettify for math + natural: "" // No prettify for natural language }[options.language] || ""; options.prettifyLanguage = prettifyLanguage; @@ -790,15 +790,16 @@ export default class Parsons extends RunestoneBase { // Determine how much indent should be possible in the answer area var indent = 0; if (!this.noindent) { - if (this.options.language == "natural") { - indent = this.solutionIndent(); - } else { - indent = Math.max(0, this.solutionIndent()); - } - - if(this.options.grader === "exec") { - indent = this.blocks.length - 1; - } + indent = this.solutionIndent(); + // if (this.options.language == "natural") { + // indent = this.solutionIndent(); + // } else { + // indent = Math.max(0, this.solutionIndent()); + // } + // + // if(this.options.grader === "exec") { + // indent = this.blocks.length - 1; + // } } this.indent = indent; // For rendering, place in an onscreen position @@ -1843,6 +1844,7 @@ export default class Parsons extends RunestoneBase { answerLines.push(block.lines[j]); } } + console.log(answerLines); return answerLines; } @@ -1857,12 +1859,18 @@ export default class Parsons extends RunestoneBase { // Return the maximum indent for the solution solutionIndent() { - var indent = 0; - for (var i = 0; i < this.blocks.length; i++) { - var block = this.blocks[i]; - indent = Math.max(indent, block.solutionIndent()); + const maxIndents = this.pifData.options.indent.max_indents; + const active = this.pifData.options.indent.active; + + if(!active){ + return 0; + } + + if(maxIndents){ + return maxIndents + } else { + return 3; } - return indent; } /* ===================================================================== @@ -1916,12 +1924,12 @@ export default class Parsons extends RunestoneBase { } // end outer if not solved // if now or previous was correct, display runnable - // if (this.hasSolved && this.options.runnable) { - // if (!this.runnableDiv) - // this.generateRunableVersion(); - // else //reveal "reset" runnable - // this.runnableDiv.style.display = null; - // } + if (this.hasSolved && this.options.runnable) { + if (!this.runnableDiv) + this.generateRunableVersion(); + else //reveal "reset" runnable + this.runnableDiv.style.display = null; + } } // Conver the parsons-runnable into an activecode and display it @@ -2111,7 +2119,7 @@ export default class Parsons extends RunestoneBase { // Return a boolean of whether the user must deal with indentation usesIndentation() { - if (this.noindent || this.solutionIndent() == 0) { + if (this.noindent || this.solutionIndent() == 0 || !(this.pifData.options.indent.active)) { // was $(this.answerArea).hasClass("answer") - bje changed return false; } else { diff --git a/tests/positive/fixed-multi-demo-starter.json b/tests/positive/fixed-multi-demo-starter.json new file mode 100644 index 0000000..f76ccb0 --- /dev/null +++ b/tests/positive/fixed-multi-demo-starter.json @@ -0,0 +1,98 @@ +{ + "value": { + "question_text": "

Put the blocks in the proper order.

\n", + "options": { + "grader": { + "type": "dag", + "showFeedback": true + }, + "maxdist": 0, + "order": "", + "indent": { + "active": false + }, + "adaptive": true, + "numbered": false, + "language": "math", + "runnable": true + }, + "blocks": [ + { + "text": "Random Group 1 Block 1 $\\$\n This block is the first block after the \u201cFixed Start\u201d block.", + "type": "", + "tag": "randomg1b1", + "depends": "", + "displaymath": true, + "feedback": "" + }, + { + "text": "Random Group 1 Block 2 $\\$\n This block is the second block after the \u201cFixed Start\u201d block.", + "type": "", + "tag": "randomg1b2", + "depends": "randomg1b1", + "displaymath": true, + "feedback": "" + }, + { + "text": "Random Group 1 Block 3: the third block in the first group", + "type": "", + "tag": "randomg1b3", + "depends": "randomg1b2", + "displaymath": true, + "feedback": "" + }, + { + "text": "Fixed Middle: Appears in the middle of the exercise", + "type": "", + "tag": "fixed", + "depends": "", + "displaymath": true, + "feedback": "" + }, + { + "text": "Random Group 2 Block 1 $\\$\n This block is the first block after the \u201cFixed Middle\u201d block.", + "type": "", + "tag": "randomg2b1", + "depends": "randomg1b3", + "displaymath": true, + "feedback": "" + }, + { + "text": "Random Group 2 Block 2 $\\$\n This block is the second block $\\$\nthat comes after the \u201cFixed Start\u201d block.", + "type": "", + "tag": "randomg2b2", + "depends": "randomg2b1", + "displaymath": true, + "feedback": "" + }, + { + "text": "Random Group 2 Block 3 $\\$\nThis is the final adjustable block.", + "type": "", + "tag": "randomg2b3", + "depends": "randomg2b2", + "displaymath": true, + "feedback": "" + }, + { + "text": "Fixed Start\n This block goes at the beginning\n of the exercise.", + "type": "", + "toggle_options": [], + "text_options": [], + "tag": "fixed", + "depends": "", + "displaymath": true, + "feedback": "" + }, + { + "text": "Fixed End\n This block appears at the end.", + "type": "", + "toggle_options": [], + "text_options": [], + "tag": "fixed", + "depends": "", + "displaymath": true, + "feedback": "" + } + ] + } +} diff --git a/tests/positive/jsparsons-toggle-blockchange.json b/tests/positive/jsparsons-toggle-blockchange.json new file mode 100644 index 0000000..5d66426 --- /dev/null +++ b/tests/positive/jsparsons-toggle-blockchange.json @@ -0,0 +1,73 @@ +{ + "question_text": "

The constructed code should print the minimum of variables a and b.

\n", + "options": { + "grader": { + "type": "exec", + "showFeedback": true + }, + "maxdist": 0, + "order": "", + "indent": { + "active": true, + "mode": "free" + }, + "adaptive": true, + "numbered": false, + "language": "python", + "runnable": true + }, + "blocks": [ + { + "text": "if \\\\a\\b\\c\\\\ \\\\<\\>\\>=\\\\ b", + "type": "", + "tag": "one1", + "depends": "", + "displaymath": true, + "feedback": "", + "toggle_options": [ + { + "start_index": 3, + "end_index": 12, + "values": [ + "a", + "b", + "c" + ] + }, + { + "start_index": 13, + "end_index": 23, + "values": [ + "<", + ">", + ">=" + ] + } + ] + }, + { + "text": "print(a)", + "type": "", + "tag": "two", + "depends": "", + "displaymath": true, + "feedback": "" + }, + { + "text": "else", + "type": "", + "tag": "three", + "depends": "", + "displaymath": true, + "feedback": "" + }, + { + "text": "print(b)", + "type": "", + "tag": "four", + "depends": "", + "displaymath": true, + "feedback": "" + } + ] +} diff --git a/tests/positive/jsparsons-toggle.json b/tests/positive/jsparsons-toggle.json new file mode 100644 index 0000000..7ad86db --- /dev/null +++ b/tests/positive/jsparsons-toggle.json @@ -0,0 +1,73 @@ +{ + "question_text": "

The constructed code should print the minimum of variables a and b.

\n", + "options": { + "grader": { + "type": "exec", + "showFeedback": true + }, + "maxdist": 0, + "order": "", + "indent": { + "active": true, + "mode": "free" + }, + "adaptive": true, + "numbered": false, + "language": "python", + "runnable": true + }, + "blocks": [ + { + "text": "if ``a`b`c`` ``<`>`>=`` b", + "type": "", + "tag": "one1", + "depends": "", + "displaymath": true, + "feedback": "", + "toggle_options": [ + { + "start_index": 3, + "end_index": 12, + "values": [ + "a", + "b", + "c" + ] + }, + { + "start_index": 13, + "end_index": 23, + "values": [ + "<", + ">", + ">=" + ] + } + ] + }, + { + "text": "print(a)", + "type": "", + "tag": "two", + "depends": "", + "displaymath": true, + "feedback": "" + }, + { + "text": "else", + "type": "", + "tag": "three", + "depends": "", + "displaymath": true, + "feedback": "" + }, + { + "text": "print(b)", + "type": "", + "tag": "four", + "depends": "", + "displaymath": true, + "feedback": "" + } + ] +}