spanish-quizzer

An app to quiz you on Spanish vocabulary and verb conjugations
git clone https://git.ashermorgan.net/spanish-quizzer/
Log | Files | Refs | README

commit 4420dffb6136799d74c5dbaf6256ad876a0d5543
parent 1167d728c4f8731b1d2ca9d215ff13b16db01bc0
Author: AsherMorgan <59518073+AsherMorgan@users.noreply.github.com>
Date:   Wed, 16 Sep 2020 10:17:09 -0700

Merge pull request #19 from AsherMorgan/refactor-quizzer

Refactor quizzer
Diffstat:
MScripts/Home.js | 35++++++++++++++++++++++-------------
MScripts/Quizzer.js | 489+++++++++++++++++++++++++++++++++++++++++++------------------------------------
MScripts/Settings.js | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
MTests/index.html | 3+++
MTests/test.app.js | 60+++++-------------------------------------------------------
MTests/test.quizzer.js | 523+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
MTests/test.settings.js | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mindex.html | 33+++++----------------------------
8 files changed, 952 insertions(+), 363 deletions(-)

diff --git a/Scripts/Home.js b/Scripts/Home.js @@ -13,14 +13,13 @@ function loadVue() { darkTheme: false, verbFilters: [], vocabFilters: [], + errorMsg: "", promptType: localStorage.getItem("promptType") || "Text", inputType: localStorage.getItem("inputType") || "Text", repeatPrompts: localStorage.getItem("repeatPrompts") || "Never", prompts: [], promptIndex: 0, - responce: "", - responceActive: true, }, methods: { @@ -141,6 +140,23 @@ function loadVue() { else { return "en"; } + }, + updateProgress: function(prompts, index) { + // Get localStorage prefix + let prefix; + if (app.state === "vocabSettings" || app.state === "vocabQuizzer") { + prefix = "vocab-" + } + else if (app.state === "verbSettings" || app.state === "verbQuizzer") { + prefix = "verb-" + } + else { + return; + } + + // Save progress to local storage + localStorage.setItem(prefix + "prompts", JSON.stringify(prompts)); + localStorage.setItem(prefix + "prompt", JSON.stringify(index)); } }, @@ -175,17 +191,10 @@ function loadVue() { }, repeatPrompts: function(value) { localStorage.setItem("repeatPrompts", value); - } - }, - - computed: { - prompt: function() { - if (this.promptIndex < this.prompts.length) { - return this.prompts[this.promptIndex]; - } - else { - return ["", "", "", ""]; - } + }, + state: function() { + // Reset error message + app.errorMsg = ""; } }, diff --git a/Scripts/Quizzer.js b/Scripts/Quizzer.js @@ -1,242 +1,291 @@ -// Declare global variables -let Prefix; // Dictionary of quizzer settings - - - -// Reads a peice of text -function Read(text, label) -{ - var msg = new SpeechSynthesisUtterance(text); - if (label.toLowerCase().includes("english")) { - msg.lang = 'en'; - } - else if (label.toLowerCase().includes("spanish")){ - msg.lang = 'es'; - } - window.speechSynthesis.speak(msg); -} - - - -// Shuffles a list of items -function Shuffle(items) { - // Initialize variables - var currentIndex = items.length; - var temp; - var randomIndex; - - // While there are more elements to shuffle - while (0 !== currentIndex) { - // Pick a remaining element - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex--; - - // Swap the two elements - temp = items[currentIndex]; - items[currentIndex] = items[randomIndex]; - items[randomIndex] = temp; - } - - // Return shuffled items - return items; -} - +let quizzer = Vue.component("quizzer", { + props: { + active: { + type: Boolean, + default: false, + }, + + startingPrompts: { + type: Array, + default: function() { + return []; + }, + }, + startingIndex: { + type: Number, + default: 0, + }, + + promptType: { + type: String, + default: "Text", + }, + inputType: { + type: String, + default: "Text", + }, + repeatPrompts: { + type: String, + default: "Never", + }, + }, + + data: function() { + return { + prompts: this.startingPrompts, + index: this.startingIndex, + responce: "", + responceActive: true, + congratsActive: false, + }; + }, + + watch: { + active: function(value) { + if (value) { + // Update prompts + this.prompts = this.startingPrompts; + this.index = this.startingIndex - 1; + + // Reset quizzer + this.Reset(); + } + }, + }, + + methods: { + // Give the user a new prompt + Reset: function() { + // Check is Quizzer is active + if (!this.active) { + return; + } + // Show and hide elements + this.responceActive = true; + this.congratsActive = false; + + // Get new prompt + this.index++; + if (this.index == this.prompts.length) { + // The user just finished + this.prompts = Shuffle(this.prompts); + this.index = 0; + this.congratsActive = true; + } -// Starts the quizzer -function StartQuizzer(prefix) { - // Set variables and settings - app.promptIndex--; - Prefix = prefix; + // Emit new-prompt event + this.$emit("new-prompt", this.prompts, this.index); - // Validate Terms - if (!app.prompts || isNaN(app.promptIndex) || app.promptIndex < -1 || app.promptIndex > app.prompts.length) { - throw "Bad arguments."; - } - else if (app.prompts.length == 0) { - throw "Terms is empty."; - } - - // Validate browser for voice input - if (app.inputType != "Text") { - if (typeof InstallTrigger !== "undefined") { - // Browser is Firefox - alert("You must enable speech recognition in about:config."); - } - else if (!window.chrome || (!window.chrome.webstore && !window.chrome.runtime)) { - // Browser is not Googole Chrome or Microsoft (Chromium) Edge - alert("Your browser does not support voice input."); - return; - } - } + // Reset responce + this.responce = ""; - // Save terms to local storage - localStorage.setItem(Prefix + "prompts", JSON.stringify(app.prompts)); - - // Give iOS devices ringer warning for prompt audio - if (app.promptType != "Text") { - if (!!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform)) { - alert("Please make sure your ringer is on in order to hear audio prompts."); - } - } + // Read prompt + if (this.promptType != "Text") { + Read(this.prompt[1], this.prompt[0]); + } - // Give the user a prompt - Reset(); -} + // Get voice input + if (this.inputType != "Text") { + // Create recognition object + var recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.msSpeechRecognition)(); + + // Set language + if (this.prompt[2].toLowerCase().includes("english")) { + recognition.lang = 'en-US'; + } + else if (this.prompt[2].toLowerCase().includes("spanish")) { + recognition.lang = 'es-mx'; + } + + // Set options + recognition.continuous = true; + recognition.interimResults = false; + recognition.maxAlternatives = 16; + + // Start listening + recognition.start(); + recognition.onresult = (event) => { + let parsed_responce = "" + for (var result of event.results[0]) { + parsed_responce += `${result.transcript}, `; + parsed_responce += `${result.transcript.split(" or ").join(", ")}, `; + } + this.responce = parsed_responce; + this.Submit(); + }; + } + }, + // Processes a user's submitted responce + Submit: function() { + // Check is Quizzer is active + if (!this.active) { + return; + } + // Parse responce + var responce = this.responce.toLowerCase(); // Make responce lowercase + responce = responce.replace(/a`/g, "á"); // Apply accented a shortcut + responce = responce.replace(/e`/g, "é"); // Apply accented e shortcut + responce = responce.replace(/i`/g, "í"); // Apply accented i shortcut + responce = responce.replace(/n`/g, "ñ"); // Apply n with tilde shortcut + responce = responce.replace(/n~/g, "ñ"); // Apply n with tilde shortcut + responce = responce.replace(/o`/g, "ó"); // Apply accented o shortcut + responce = responce.replace(/u`/g, "ú"); // Apply accented u shortcut + responce = responce.replace(/u~/g, "ü"); // Apply u with diaeresis shortcut + var responces = responce.split(","); // Split string by commas + for (var i = 0; i < responces.length; i++) { + responces[i] = responces[i].split(" ").filter(function(x){return x !== "";}).join(" "); // Trim whitespace + } -// Give the user a new prompt -function Reset() { - // Show and hide elements - document.getElementById("quizzerCongrats").hidden = true; - document.getElementById("quizzerInput").focus(); - app.responceActive = true; - - // Get new prompt - app.promptIndex++; - if (app.promptIndex == app.prompts.length) { - // The user just finished - app.prompts = Shuffle(app.prompts); - app.promptIndex = 0; - document.getElementById("quizzerCongrats").hidden = false; - } + // Parse answer + let answers = this.prompt[3].toLowerCase().split(","); // Split string by commas + for (var i = 0; i < answers.length; i++) { + answers[i] = answers[i].trim(); // Trim whitespace + } - // Save progress to local storage - localStorage.setItem(Prefix + "prompt", app.promptIndex); + // Check responce + var correct = true; + for (var answer of answers) { + if (!responces.includes(answer)) { + correct = false; + } + } - // Reset responce - app.responce = ""; + // Give user feedback + if (!correct) { + // Show and hide elements + this.congratsActive = false; + this.responceActive = false; + try { + // Will fail if not mounted + this.$refs.feedback.scrollIntoView(false); + this.$refs.input.focus(); + } + catch { } + } + else { + // Responce was correct + this.Reset(); + } + }, - // Read prompt - if (app.promptType != "Text") { - Read(app.prompt[1], app.prompt[0]); - } + // Processes an incorrect responce and then resets the quizzer + Continue: function() { + // Check is Quizzer is active + if (!this.active) { + return; + } + + // Repeat prompt + switch (this.repeatPrompts) + { + case "Never": + // Don't repeat + break; + case "Immediately": + // Repeat imitiately + this.index--; + break; + case "5 prompts later": + // Repeat 5 prompts later + var temp = this.prompt; + this.prompts.splice(this.index, 1); + this.prompts.splice(this.index + 5, 0, temp); + this.index--; + break; + case "At the end": + // Repeat at end of Terms + var temp = this.prompt; + this.prompts.splice(this.index, 1); + this.prompts.push(temp); + this.index--; + break; + } - // Get voice input - if (app.inputType != "Text") { - // Create recognition object - var recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.msSpeechRecognition)(); + // Reset quizzer + this.Reset(); + }, - // Set language - if (app.prompt[2].toLowerCase().includes("english")) { - recognition.lang = 'en-US'; - } - else if (app.prompt[2].toLowerCase().includes("spanish")) { - recognition.lang = 'es-mx'; - } - - // Set options - recognition.continuous = true; - recognition.interimResults = false; - recognition.maxAlternatives = 16; - - // Start listening - recognition.start(); - recognition.onresult = function(event) { - responce = "" - for (var result of event.results[0]) { - responce += `${result.transcript}, `; - responce += `${result.transcript.split(" or ").join(", ")}, `; - } - app.responce = responce; - Submit() - }; - } -} - - - -// Processes a user's submitted responce -function Submit() { - // Parse responce - var responce = app.responce.toLowerCase(); // Make responce lowercase - responce = responce.replace(/a`/g, "á"); // Apply accented a shortcut - responce = responce.replace(/e`/g, "é"); // Apply accented e shortcut - responce = responce.replace(/i`/g, "í"); // Apply accented i shortcut - responce = responce.replace(/n`/g, "ñ"); // Apply n with tilde shortcut - responce = responce.replace(/n~/g, "ñ"); // Apply n with tilde shortcut - responce = responce.replace(/o`/g, "ó"); // Apply accented o shortcut - responce = responce.replace(/u`/g, "ú"); // Apply accented u shortcut - responce = responce.replace(/u~/g, "ü"); // Apply u with diaeresis shortcut - var responces = responce.split(","); // Split string by commas - for (var i = 0; i < responces.length; i++) { - responces[i] = responces[i].split(" ").filter(function(x){return x !== "";}).join(" "); // Trim whitespace - } - - // Parse answer - let answers = app.prompt[3].toLowerCase().split(","); // Split string by commas - for (var i = 0; i < answers.length; i++) { - answers[i] = answers[i].trim(); // Trim whitespace - } + // Called when the user hits enter or presses the enter button + Enter: function() { + // Check is Quizzer is active + if (!this.active) { + return; + } + + if (this.responceActive) { + this.Submit(); + } + else { + this.Continue(); + } + }, + + getLang: function(label) { + if (label.toLowerCase().includes("spanish")) { + return "es"; + } + else { + return "en"; + } + }, + }, - // Check responce - var correct = true; - for (var answer of answers) { - if (!responces.includes(answer)) { - correct = false; + computed: { + prompt: function() { + if (this.index < this.prompts.length) { + return this.prompts[this.index]; + } + else { + return ["", "", "", ""]; + } } - } - - // Give user feedback - if (!correct) { - // Show and hide elements - document.getElementById("quizzerCongrats").hidden = true; - document.getElementById("quizzerFeedback").scrollIntoView(false); - document.getElementById("quizzerInput").focus(); - app.responceActive = false; - } - else { - // Responce was correct - Reset(); - } -} - - - -// Processes an incorrect responce and then resets the quizzer -function Continue() { - // Repeat prompt - switch (app.repeatPrompts) - { - case "Never": - // Don't repeat - break; - case "Immediately": - // Repeat imitiately - app.promptIndex--; - break; - case "5 prompts later": - // Repeat 5 prompts later - var temp = app.prompt; - app.prompts.splice(app.promptIndex, 1); - app.prompts.splice(app.promptIndex + 5, 0, temp); - app.promptIndex--; - break; - case "At the end": - // Repeat at end of Terms - var temp = app.prompt; - app.prompts.splice(app.promptIndex, 1); - app.prompts.push(temp); - app.promptIndex--; - break; - } + }, - // Save terms to local storage - localStorage.setItem(Prefix + "terms", JSON.stringify(app.prompts)); - - // Reset quizzer - Reset(); -} + template: ` + <div> + <p id="quizzerProgress">{{ index }} / {{ prompts.length }}</p> + + <section> + <label id="quizzerPromptType" for="quizzerPrompt" :lang="getLang(prompt[0])">{{ prompt[0] }}</label> + <span id="quizzerPrompt" @click="Read(prompt[1], prompt[0]);">{{ prompt[1] }}</span> + </section> + + <section> + <label id="quizzerInputType" for="quizzerInput">{{ prompt[2] }}</label> + <input id="quizzerInput" ref="input" type="text" v-model="responce" :readonly="!responceActive || inputType == 'Voice'" + @keyup.ctrl.enter.exact="Reset();" @keyup.enter.exact="Enter();" :lang="getLang(prompt[2])" + autocomplete="off" spellcheck="false" autocorrect="off" placeholder="Type the answer"> + </section> + + <div id="quizzerButtons"> + <button v-if="responceActive" :disabled="inputType == 'Voice'" @click="Submit();">Submit</button> + <button v-else @click="Continue();">Continue</button> + <button @click="Reset();">Skip</button> + </div> + + <div id="quizzerFeedback" ref="feedback" v-show="!responceActive" class="bad"> + The correct answer is + <span id="quizzerFeedbackTerm" @click="Read(prompt[3], prompt[2]);">{{ prompt[3].toLowerCase() }}</span>. + </div> + <div id="quizzerCongrats" class="good" v-show="congratsActive">Congratulations! You made it back to the beginning!</div> + </div> + `, +}); -// Called when the user hits enter or presses the enter button -function Enter() { - if (app.responceActive) { - Submit(); +// Reads a peice of text +function Read(text, label) +{ + var msg = new SpeechSynthesisUtterance(text); + if (label.toLowerCase().includes("english")) { + msg.lang = 'en'; } - else { - Continue(); + else if (label.toLowerCase().includes("spanish")){ + msg.lang = 'es'; } + window.speechSynthesis.speak(msg); } diff --git a/Scripts/Settings.js b/Scripts/Settings.js @@ -1,7 +1,6 @@ // Start a new session function CreateSession() { - // Get prompts and localStorage prefix - let prefix; + // Get prompts if (app.state == "vocabSettings") { // Filter and load Sets into prompts app.prompts = []; @@ -13,16 +12,10 @@ function CreateSession() { // Shuffle prompts app.prompts = Shuffle(app.prompts); - - // Set prefix - prefix = "vocab-" } else if (app.state == "verbSettings") { // Get prompts app.prompts = Shuffle(ApplyVerbFilter(Sets["Verbs"], app.verbFilters)); - - // Set prefix - prefix = "verb-" } // Set progress @@ -30,27 +23,18 @@ function CreateSession() { // Start quizzer try { - // Start quizzer - StartQuizzer(prefix); - - // Show and hide elements - if (app.state == "verbSettings") { - app.state = "verbQuizzer"; - } - if (app.state == "vocabSettings") { - app.state = "vocabQuizzer"; - } + StartSession(); } catch (e) { switch (e) { case "Terms is empty.": - document.getElementById("settingsError").textContent = "Your custom vocabulary set must contain at least one term."; + app.errorMsg = "Your custom vocabulary set must contain at least one term."; document.getElementById("settingsError").scrollIntoView(false); break; default: - document.getElementById("settingsError").textContent = "An error occured."; + app.errorMsg = "An error occured."; document.getElementById("settingsError").scrollIntoView(false); - break; + throw e; } } } @@ -74,32 +58,68 @@ function ResumeSession() { // Start quizzer try { - StartQuizzer(prefix); - - // Show and hide elements - if (app.state == "verbSettings") { - app.state = "verbQuizzer"; - } - if (app.state == "vocabSettings") { - app.state = "vocabQuizzer"; - } + StartSession(); } catch (e) { switch (e) { case "Bad arguments.": - document.getElementById("settingsError").textContent = "An error occured while resuming the previous session."; + app.errorMsg = "An error occured while resuming the previous session."; document.getElementById("settingsError").scrollIntoView(false); break; case "Terms is empty.": - document.getElementById("settingsError").textContent = "Your custom vocabulary set must contain at least one term."; + app.errorMsg = "Your custom vocabulary set must contain at least one term."; document.getElementById("settingsError").scrollIntoView(false); break; default: - document.getElementById("settingsError").textContent = "An error occured."; + app.errorMsg = "An error occured."; document.getElementById("settingsError").scrollIntoView(false); - break; + throw e; + } + } +} + + + +// Performs validations and then starts the quizzer +function StartSession() { + // Validate prompts and promptIndex + if (!app.prompts) { + throw "Bad arguments."; + } + else if (app.prompts.length == 0) { + throw "Terms is empty."; + } + else if (isNaN(app.promptIndex) || app.promptIndex < 0 || app.promptIndex >= app.prompts.length) { + throw "Bad arguments."; + } + + // Validate browser for voice input + if (app.inputType != "Text") { + if (typeof InstallTrigger !== "undefined") { + // Browser is Firefox + alert("You must enable speech recognition in about:config."); + } + else if (!window.chrome || (!window.chrome.webstore && !window.chrome.runtime)) { + // Browser is not Googole Chrome or Microsoft (Chromium) Edge + alert("Your browser does not support voice input."); + return; } } + + // Give iOS devices ringer warning for prompt audio + if (app.promptType != "Text") { + if (!!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform)) { + alert("Please make sure your ringer is on in order to hear audio prompts."); + } + } + + // Show and hide elements (also enables the quizzer) + if (app.state == "verbSettings") { + app.state = "verbQuizzer"; + } + else if (app.state == "vocabSettings") { + app.state = "vocabQuizzer"; + } } @@ -290,3 +310,28 @@ function ApplyVerbFilter(terms, filterInfo) { } return results; } + + + +// Shuffles a list of items +function Shuffle(items) { + // Initialize variables + var currentIndex = items.length; + var temp; + var randomIndex; + + // While there are more elements to shuffle + while (0 !== currentIndex) { + // Pick a remaining element + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex--; + + // Swap the two elements + temp = items[currentIndex]; + items[currentIndex] = items[randomIndex]; + items[randomIndex] = temp; + } + + // Return shuffled items + return items; +} diff --git a/Tests/index.html b/Tests/index.html @@ -13,6 +13,9 @@ <body> <div id="mocha"></div> + <!-- To prevent document.getElementById errors --> + <div id="app" hidden></div> + <!-- Scripts being tested --> <script src="../Scripts/Home.js"></script> <script src="../Scripts/Quizzer.js"></script> diff --git a/Tests/test.app.js b/Tests/test.app.js @@ -8,6 +8,7 @@ describe("App", function() { it("State should be 'home'", function() { expect(app.state).to.equal("home"); }); + it("VerFilters should be empty", function() { expect(app.verbFilters.length).to.equal(0); }); @@ -16,6 +17,10 @@ describe("App", function() { expect(app.vocabFilters.length).to.equal(0); }); + it("ErrorMsg should be empty", function() { + expect(app.errorMsg).to.equal(""); + }); + it("Prompts should be empty", function() { expect(app.vocabFilters.length).to.equal(0); }); @@ -23,14 +28,6 @@ describe("App", function() { it("PromptIndex should be 0", function() { expect(app.promptIndex).to.equal(0); }); - - it("Responce should be empty", function() { - expect(app.responce).to.equal(""); - }); - - it("ResponceActive should be true", function() { - expect(app.responceActive).to.equal(true); - }); }); describe("Back method", function() { @@ -313,53 +310,6 @@ describe("App", function() { }) }); - describe("Prompt property", function() { - it("Should be empty if there aren't any prompt", function() { - // Assert prompts and promptIndex are correct - expect(app.promptIndex).to.equal(0); - expect(app.prompts.length).to.equal(0); - - // Assert prompt is empty - expect(app.prompt.length).to.equal(4); - expect(app.prompt[0]).to.equal(""); - expect(app.prompt[1]).to.equal(""); - expect(app.prompt[2]).to.equal(""); - expect(app.prompt[3]).to.equal(""); - }); - - it("Should be empty if promptIndex is invalid", function() { - // Initialize promptIndex - app.promptIndex = 2; - - // Assert prompts is correct - expect(app.prompts.length).to.equal(0); - - // Assert prompt is empty - expect(app.prompt.length).to.equal(4); - expect(app.prompt[0]).to.equal(""); - expect(app.prompt[1]).to.equal(""); - expect(app.prompt[2]).to.equal(""); - expect(app.prompt[3]).to.equal(""); - }); - - it("Should be the current prompt if promptIndex is valid", function() { - // Initialize prompts and promptIndex - app.promptIndex = 1; - app.prompts = [ - ["a1", "b1", "c1", "d1"], - ["a2", "b2", "c2", "d2"], - ["a3", "b3", "c3", "d3"], - ]; - - // Assert prompt is correct - expect(app.prompt.length).to.equal(4); - expect(app.prompt[0]).to.equal("a2"); - expect(app.prompt[1]).to.equal("b2"); - expect(app.prompt[2]).to.equal("c2"); - expect(app.prompt[3]).to.equal("d2"); - }); - }); - describe("PromptType watch", function() { it("Should update setting in localStorage", async function() { // Save original setting from localStorage diff --git a/Tests/test.quizzer.js b/Tests/test.quizzer.js @@ -1,17 +1,514 @@ describe("Quizzer", function() { - describe("Shuffle method", function() { - it("Should not alter list", function() { - // Initialize list - let list1 = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o"]; // 15 items - - // Shuffle list - let list2 = Shuffle(list1); - - // Assert list shuffled - expect(list2.length).to.equal(list1.length); - for (let item of list2) { - expect(list1.includes(item)).to.equal(true); - } + let Quizzer; + beforeEach(function() { + // Create quizzer component + Quizzer = new quizzer(); + }); + + describe("Initial state", function() { + it("Active should be false", function() { + expect(Quizzer.active).to.equal(false); + }); + + it("StartingPrompts should be empty", function() { + expect(Quizzer.startingPrompts.length).to.equal(0); + }); + + it("StartingIndex should be 0", function() { + expect(Quizzer.startingIndex).to.equal(0); + }); + + it("PromptType should be text", function() { + expect(Quizzer.promptType).to.equal("Text"); + }); + + it("InputType should be text", function() { + expect(Quizzer.inputType).to.equal("Text"); + }); + + it("RepeatPrompts should be never", function() { + expect(Quizzer.repeatPrompts).to.equal("Never"); + }); + + it("Prompts should be empty", function() { + expect(Quizzer.prompts.length).to.equal(0); + }); + + it("Index should be 0", function() { + expect(Quizzer.index).to.equal(0); + }); + + it("Responce should be empty", function() { + expect(Quizzer.responce).to.equal(""); + }); + + it("ResponceActive should be true", function() { + expect(Quizzer.responceActive).to.equal(true); + }); + + it("CongratsActive should be true", function() { + expect(Quizzer.congratsActive).to.equal(false); + }); + }); + + describe("Reset method", function() { + it("Shouldn't do anything if active is false", function() { + // Run Reset + Quizzer.Reset(); + + // Assert nothing changed + expect(Quizzer.prompts.length).to.equal(0); + expect(Quizzer.index).to.equal(0); + expect(Quizzer.responce).to.equal(""); + expect(Quizzer.responceActive).to.equal(true); + expect(Quizzer.congratsActive).to.equal(false); + }); + + it("Should set responceActive to true", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.responceActive = false; + + // Run reset + Quizzer.Reset(); + + // Assert responceActive is true + expect(Quizzer.responceActive).to.equal(true); + }); + + it("Should set congratsActive to false", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.congratsActive = true; + + // Run reset + Quizzer.Reset(); + + // Assert congratsActive is true + expect(Quizzer.congratsActive).to.equal(false); + }); + + it("Should set congratsActive to true if on last term", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "A4"], ["B1", "B2", "B3", "B4"]]; + Quizzer.index = 1; + + // Run reset + Quizzer.Reset(); + + // Assert congratsActive is true + expect(Quizzer.congratsActive).to.equal(true); + }); + + it("Should reset responce", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.responce = "test"; // Set to empty string when reset is called + expect(Quizzer.responce).to.equal("test"); + + // Run reset + Quizzer.Reset(); + + // Assert reset called + expect(Quizzer.responce).to.equal(""); + }); + }); + + describe("Submit method", function() { + beforeEach(function() { + // Initialize Vue to avoid document.getElementById errors + loadVue(); + app.state = "verbQuizzer"; + }); + + it("Shouldn't do anything if active is false", function() { + // Initialize variables + Quizzer.responceActive = "test"; // Will be changed whether or not resopnce is correct + + // Run Submit + Quizzer.Submit(); + + // Assert nothing changed + expect(Quizzer.responceActive).to.equal("test"); + }); + + it("Should call Reset if responce is correct", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "A4"]] + Quizzer.responce = "A4"; + + // Call Submit + Quizzer.Submit(); + + // Assert Reset called + expect(Quizzer.congratsActive).to.equal(true); // Reset will show congrats + }); + + it("Should set responceActive to false if responce is incorrect", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "A4"]] + Quizzer.responce = "A5"; + + // Call Submit + Quizzer.Submit(); + + // Assert responceActive set to false + expect(Quizzer.responceActive).to.equal(false); + }); + + it("Should accept multiple responces", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "A4"]] + Quizzer.responce = "A1, A2, A3, A4"; + + // Call Submit + Quizzer.Submit(); + + // Assert responce accepted + expect(Quizzer.congratsActive).to.equal(true); // Reset will show congrats + }); + + it("Should accept multiple answers", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "A1, A2, A3, A4"]] + Quizzer.responce = "A1, A2, A3, A4"; + + // Call Submit + Quizzer.Submit(); + + // Assert answer accepted + expect(Quizzer.congratsActive).to.equal(true); // Reset will show congrats + }); + + it("Should require all answers", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "A1, A2, A3, A4"]] + Quizzer.responce = "A1, A2, A3"; + + // Call Submit + Quizzer.Submit(); + + // Assert answer no accepted + expect(Quizzer.responceActive).to.equal(false); + }); + + it("Should accept mixed-case responces", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "A4"]] + Quizzer.responce = "a4"; + + // Call Submit + Quizzer.Submit(); + + // Assert responce accepted + expect(Quizzer.congratsActive).to.equal(true); // Reset will show congrats + }); + + it("Should accept responces with extra spaces", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "A4"]] + Quizzer.responce = " a4 "; + + // Call Submit + Quizzer.Submit(); + + // Assert responce accepted + expect(Quizzer.congratsActive).to.equal(true); // Reset will show congrats + }); + + it("Should convert accented characters", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "Á4"]] + Quizzer.responce = "a`4"; + + // Call Submit + Quizzer.Submit(); + + // Assert responce accepted + expect(Quizzer.congratsActive).to.equal(true); // Reset will show congrats + }); + }); + + describe("Continue method", function() { + it("Shouldn't do anything if active is false", function() { + // Initialize variables + Quizzer.prompts = [["A1", "A2", "A3", "A4"], ["B1", "B2", "B3", "B4"]]; + Quizzer.index = 0; + Quizzer.repeatPrompts = "At the end"; + Quizzer.responceActive = false; // Will be changed if Reset is called + + // Run Continue + Quizzer.Continue(); + + // Assert nothing changed + expect(Quizzer.prompts[0]).to.have.members(["A1", "A2", "A3", "A4"]); + expect(Quizzer.prompts[1]).to.have.members(["B1", "B2", "B3", "B4"]); + expect(Quizzer.index).to.equal(0); + expect(Quizzer.responceActive).to.equal(false); + }); + + it("Shouldn't change prompts if repeatPrompts is Never", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "A4"], ["B1", "B2", "B3", "B4"]]; + Quizzer.index = 0; + Quizzer.repeatPrompts = "Never"; + + // Run Continue + Quizzer.Continue(); + + // Assert prompts not changed + expect(Quizzer.prompts[0]).to.have.members(["A1", "A2", "A3", "A4"]); + expect(Quizzer.prompts[1]).to.have.members(["B1", "B2", "B3", "B4"]); + expect(Quizzer.index).to.equal(1); + }); + + it("Shouldn't change prompts if repeatPrompts isn't recognized", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "A4"], ["B1", "B2", "B3", "B4"]]; + Quizzer.index = 0; + Quizzer.repeatPrompts = "test"; + + // Run Continue + Quizzer.Continue(); + + // Assert prompts not changed + expect(Quizzer.prompts[0]).to.have.members(["A1", "A2", "A3", "A4"]); + expect(Quizzer.prompts[1]).to.have.members(["B1", "B2", "B3", "B4"]); + expect(Quizzer.index).to.equal(1); + }); + + it("Should only change index if repeatPrompts is Immediately", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "A4"], ["B1", "B2", "B3", "B4"]]; + Quizzer.index = 0; + Quizzer.repeatPrompts = "Immediately"; + + // Run Continue + Quizzer.Continue(); + + // Assert prompts not changed + expect(Quizzer.prompts[0]).to.have.members(["A1", "A2", "A3", "A4"]); + expect(Quizzer.prompts[1]).to.have.members(["B1", "B2", "B3", "B4"]); + expect(Quizzer.index).to.equal(0); + }); + + it("Should only update prompts if repeatPrompts is 5 prompts later", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [ + ["A1", "A2", "A3", "A4"], + ["B1", "B2", "B3", "B4"], + ["C1", "C2", "C3", "C4"], + ["D1", "D2", "D3", "D4"], + ["E1", "E2", "E3", "E4"], + ["F1", "F2", "F3", "F4"], + ["G1", "G2", "G3", "G4"], + ]; + Quizzer.index = 0; + Quizzer.repeatPrompts = "5 prompts later"; + + // Run Continue + Quizzer.Continue(); + + // Assert prompts not changed + expect(Quizzer.prompts[0]).to.have.members(["B1", "B2", "B3", "B4"]); + expect(Quizzer.prompts[1]).to.have.members(["C1", "C2", "C3", "C4"]); + expect(Quizzer.prompts[2]).to.have.members(["D1", "D2", "D3", "D4"]); + expect(Quizzer.prompts[3]).to.have.members(["E1", "E2", "E3", "E4"]); + expect(Quizzer.prompts[4]).to.have.members(["F1", "F2", "F3", "F4"]); + expect(Quizzer.prompts[5]).to.have.members(["A1", "A2", "A3", "A4"]); + expect(Quizzer.prompts[6]).to.have.members(["G1", "G2", "G3", "G4"]); + expect(Quizzer.index).to.equal(0); + }); + + it("Should only update prompts if repeatPrompts is At the end", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [ + ["A1", "A2", "A3", "A4"], + ["B1", "B2", "B3", "B4"], + ["C1", "C2", "C3", "C4"], + ["D1", "D2", "D3", "D4"], + ["E1", "E2", "E3", "E4"], + ["F1", "F2", "F3", "F4"], + ["G1", "G2", "G3", "G4"], + ]; + Quizzer.index = 0; + Quizzer.repeatPrompts = "At the end"; + + // Run Continue + Quizzer.Continue(); + + // Assert prompts not changed + expect(Quizzer.prompts[0]).to.have.members(["B1", "B2", "B3", "B4"]); + expect(Quizzer.prompts[1]).to.have.members(["C1", "C2", "C3", "C4"]); + expect(Quizzer.prompts[2]).to.have.members(["D1", "D2", "D3", "D4"]); + expect(Quizzer.prompts[3]).to.have.members(["E1", "E2", "E3", "E4"]); + expect(Quizzer.prompts[4]).to.have.members(["F1", "F2", "F3", "F4"]); + expect(Quizzer.prompts[5]).to.have.members(["G1", "G2", "G3", "G4"]); + expect(Quizzer.prompts[6]).to.have.members(["A1", "A2", "A3", "A4"]); + expect(Quizzer.index).to.equal(0); + }); + }); + + describe("Enter method", function() { + it("Shouldn't do anything if active is false", function() { + // Initialize variables + Quizzer.prompts = [["A1", "A2", "A3", "A4"], ["B1", "B2", "B3", "B4"]]; // Will change if Continue is called + Quizzer.index = 0; // Will be changed if Reset is called + Quizzer.repeatPrompts = "At the end"; + + // Run Enter + Quizzer.Enter(); + + // Assert nothing changed + expect(Quizzer.prompts[0]).to.have.members(["A1", "A2", "A3", "A4"]); + expect(Quizzer.prompts[1]).to.have.members(["B1", "B2", "B3", "B4"]); + expect(Quizzer.index).to.equal(0); + }); + + it("Should call Submit if responceActive is true", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "A4"], ["B1", "B2", "B3", "B4"]]; // Will change if Continue is called + Quizzer.index = 0; // Will be changed if Reset is called + Quizzer.responce = "A4"; + Quizzer.repeatPrompts = "At the end"; + Quizzer.responceActive = true; + + // Run Enter + Quizzer.Enter(); + + // Assert Submit called + expect(Quizzer.prompts[0]).to.have.members(["A1", "A2", "A3", "A4"]); + expect(Quizzer.prompts[1]).to.have.members(["B1", "B2", "B3", "B4"]); + expect(Quizzer.index).to.equal(1); + }); + + it("Should call Continue if responceActive is false", function() { + // Initialize variables + Quizzer.active = true; + Quizzer.prompts = [["A1", "A2", "A3", "A4"], ["B1", "B2", "B3", "B4"]]; // Will change if Continue is called + Quizzer.index = 0; // Will be changed if Reset is called + Quizzer.repeatPrompts = "At the end"; + Quizzer.responceActive = false; + + // Run Enter + Quizzer.Enter(); + + // Assert Continue called + expect(Quizzer.prompts[0]).to.have.members(["B1", "B2", "B3", "B4"]); + expect(Quizzer.prompts[1]).to.have.members(["A1", "A2", "A3", "A4"]); + expect(Quizzer.index).to.equal(0); + }); + }); + + describe("GetLang method", function () { + it("Should return English by default", function() { + expect(Quizzer.getLang("")).to.equal("en"); + expect(Quizzer.getLang("test")).to.equal("en"); + }); + + it("Should return English for English labels", function() { + expect(Quizzer.getLang("test english test")).to.equal("en"); + expect(Quizzer.getLang("ENGLISH")).to.equal("en"); + }); + + it("Should return Spanish for Spanish labels", function() { + expect(Quizzer.getLang("test spanish test")).to.equal("es"); + expect(Quizzer.getLang("SPANISH")).to.equal("es"); + }); + }); + + describe("Active watch", function() { + it("Should update prompts and index", async function() { + // Initialize variables + Quizzer.startingPrompts = [["A1", "A2", "A3", "A4"], ["B1", "B2", "B3", "B4"]]; + Quizzer.startingIndex = 1; + + // Assert prompts and index not updated yet + expect(Quizzer.prompts.length).to.equal(0); + expect(Quizzer.index).to.equal(0); + + // Set active to true + Quizzer.active = true; + await Quizzer.$nextTick(); + + // Assert prompts and index updated + expect(Quizzer.prompts).to.have.deep.members([["A1", "A2", "A3", "A4"], ["B1", "B2", "B3", "B4"]]); + expect(Quizzer.index).to.equal(1); + }); + + it("Should call Reset when set to true", async function() { + // Initialize variables + Quizzer.responce = "test"; // Set to empty string when reset is called + expect(Quizzer.responce).to.equal("test"); + + // Set active to true + Quizzer.active = true; + await Quizzer.$nextTick(); + + // Assert reset called + expect(Quizzer.responce).to.equal(""); + }); + }); + + describe("Prompt property", function() { + it("Should be empty if there aren't any prompt", function() { + // Assert prompts and index are correct + expect(Quizzer.index).to.equal(0); + expect(Quizzer.prompts.length).to.equal(0); + + // Assert prompt is empty + expect(Quizzer.prompt.length).to.equal(4); + expect(Quizzer.prompt[0]).to.equal(""); + expect(Quizzer.prompt[1]).to.equal(""); + expect(Quizzer.prompt[2]).to.equal(""); + expect(Quizzer.prompt[3]).to.equal(""); + }); + + it("Should be empty if index is invalid", function() { + // Initialize index + Quizzer.index = 2; + + // Assert prompts is correct + expect(Quizzer.prompts.length).to.equal(0); + + // Assert prompt is empty + expect(Quizzer.prompt.length).to.equal(4); + expect(Quizzer.prompt[0]).to.equal(""); + expect(Quizzer.prompt[1]).to.equal(""); + expect(Quizzer.prompt[2]).to.equal(""); + expect(Quizzer.prompt[3]).to.equal(""); + }); + + it("Should be the current prompt if index is valid", function() { + // Initialize prompts and index + Quizzer.index = 1; + Quizzer.prompts = [ + ["a1", "b1", "c1", "d1"], + ["a2", "b2", "c2", "d2"], + ["a3", "b3", "c3", "d3"], + ]; + + // Assert prompt is correct + expect(Quizzer.prompt.length).to.equal(4); + expect(Quizzer.prompt[0]).to.equal("a2"); + expect(Quizzer.prompt[1]).to.equal("b2"); + expect(Quizzer.prompt[2]).to.equal("c2"); + expect(Quizzer.prompt[3]).to.equal("d2"); }); }); }); diff --git a/Tests/test.settings.js b/Tests/test.settings.js @@ -462,4 +462,63 @@ describe("Settings", function() { } }); }); + + describe("StartSession method", function() { + beforeEach(function() { + // Initialize Vue + loadVue(); + }); + + it("Should throw \"Bad Arguments\" when prompts is null", function() { + // Initialize prompts and promptIndex + app.prompts = null; + app.promptIndex = 0; + + // Assert raises error + expect(StartSession).to.throw("Bad arguments."); + }); + + it("Should throw \"Bad Arguments\" when promptIndex is negative", function() { + // Initialize prompts and promptIndex + app.prompts = ["a", "b", "c"]; + app.promptIndex = -1; + + // Assert raises error + expect(StartSession).to.throw("Bad arguments."); + }); + + it("Should throw \"Bad Arguments\" when promptIndex is invalid", function() { + // Initialize prompts and promptIndex + app.prompts = ["a", "b", "c"]; + app.promptIndex = 3; + + // Assert raises error + expect(StartSession).to.throw("Bad arguments."); + }); + + it("Should throw \"Terms is empty\" when prompts is empty", function() { + // Initialize prompts and promptIndex + app.prompts = []; + app.promptIndex = 0; + + // Assert raises error + expect(StartSession).to.throw("Terms is empty."); + }); + }); + + describe("Shuffle method", function() { + it("Should not alter list", function() { + // Initialize list + let list1 = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o"]; // 15 items + + // Shuffle list + let list2 = Shuffle(list1); + + // Assert list shuffled + expect(list2.length).to.equal(list1.length); + for (let item of list2) { + expect(list1.includes(item)).to.equal(true); + } + }); + }); }); diff --git a/index.html b/index.html @@ -151,38 +151,15 @@ <button id="settingsResume" onclick="ResumeSession();">Resume</button> </div> - <div id="settingsError" class="centered bad"></div> + <div id="settingsError" v-show="errorMsg" class="centered bad">{{ errorMsg }}</div> </div> </div> - <div id="quizzer" v-show="state == 'verbQuizzer' || state == 'vocabQuizzer'" hidden> - <p id="quizzerProgress">{{ promptIndex }} / {{ prompts.length}}</p> - - <section> - <label id="quizzerPromptType" for="quizzerPrompt" :lang="getLang(prompt[0])">{{ prompt[0] }}</label> - <span id="quizzerPrompt" @click="Read(prompt[1], prompt[0]);">{{ prompt[1] }}</span> - </section> - - <section> - <label id="quizzerInputType" for="quizzerInput">{{ prompt[2] }}</label> - <input id="quizzerInput" type="text" v-model="responce" :readonly="!responceActive || inputType == 'Voice'" - @keyup.ctrl.enter.exact="Reset();" @keyup.enter.exact="Enter();" :lang="getLang(prompt[2])" - autocomplete="off" spellcheck="false" autocorrect="off" placeholder="Type the answer"> - </section> - - <div id="quizzerButtons"> - <button v-if="responceActive" :disabled="inputType == 'Voice'" @click="Submit();">Submit</button> - <button v-else @click="Continue();">Continue</button> - <button @click="Reset();">Skip</button> - </div> - - <div id="quizzerFeedback" v-show="!responceActive" class="bad"> - The correct answer is - <span id="quizzerFeedbackTerm" @click="Read(prompt[3], prompt[2]);">{{ prompt[3].toLowerCase() }}</span>. - </div> - <div id="quizzerCongrats" class="good">Congratulations! You made it back to the beginning!</div> - </div> + <quizzer id="quizzer" v-show="state == 'verbQuizzer' || state == 'vocabQuizzer'" :active="state == 'verbQuizzer' || state == 'vocabQuizzer'" hidden + :starting-index="promptIndex" :starting-prompts="prompts" @new-prompt="updateProgress" + :prompt-type="promptType" :input-type="inputType" :repeat-prompts="repeatPrompts"> + </quizzer> </main>