[ENH] Add option to ignore accents during tests

This commit is contained in:
2022-08-29 17:20:26 +01:00
parent e60b1ab5ef
commit 1f0079868f
8 changed files with 89 additions and 10 deletions

View File

@@ -141,6 +141,7 @@ exports.getGroupMembers = functions.https.onCall((data, context) => {
/** /**
* Creates new progress document. * Creates new progress document.
* @param {object} data The data passed to the function. * @param {object} data The data passed to the function.
* @param {boolean} data.ignoreAccents Whether accents on letters should matter during the test. Optional.
* @param {boolean} data.ignoreCaps Whether capitalisation of answers should matter during the test. Optional. * @param {boolean} data.ignoreCaps Whether capitalisation of answers should matter during the test. Optional.
* @param {boolean} data.limit The maximum number of lives/questions for the test. * @param {boolean} data.limit The maximum number of lives/questions for the test.
* @param {boolean} data.mode The mode to be tested in. Valid options are "questions" and "lives". * @param {boolean} data.mode The mode to be tested in. Valid options are "questions" and "lives".
@@ -174,7 +175,14 @@ exports.createProgress = functions.https.onCall((data, context) => {
data.ignoreCaps = false; data.ignoreCaps = false;
console.log("ignoreCaps not provided - using default value of false"); console.log("ignoreCaps not provided - using default value of false");
} else if (typeof data.ignoreCaps !== "boolean") { } else if (typeof data.ignoreCaps !== "boolean") {
throw new functions.https.HttpsError("invalid-argument", "showNumberOfAnswers must be a boolean"); throw new functions.https.HttpsError("invalid-argument", "ignoreCaps must be a boolean");
}
if (typeof data.ignoreAccents === "undefined") {
data.ignoreAccents = false;
console.log("ignoreAccents not provided - using default value of false");
} else if (typeof data.ignoreAccents !== "boolean") {
throw new functions.https.HttpsError("invalid-argument", "ignoreAccents must be a boolean");
} }
if (typeof data.showNumberOfAnswers === "undefined") { if (typeof data.showNumberOfAnswers === "undefined") {
@@ -256,6 +264,7 @@ exports.createProgress = functions.https.onCall((data, context) => {
set_titles: setIds.map((setId) => setTitlesDict[setId]), set_titles: setIds.map((setId) => setTitlesDict[setId]),
typo: false, typo: false,
ignoreCaps: data.ignoreCaps, ignoreCaps: data.ignoreCaps,
ignoreAccents: data.ignoreAccents,
showNumberOfAnswers: data.showNumberOfAnswers, showNumberOfAnswers: data.showNumberOfAnswers,
} }
@@ -420,9 +429,10 @@ exports.createProgressWithIncorrect = functions.https.onCall((data, context) =>
* @param {string} item The term/definition to remove the characters that should be ignored from. * @param {string} item The term/definition to remove the characters that should be ignored from.
* @return {string} The original string with the unwanted characters removed. * @return {string} The original string with the unwanted characters removed.
*/ */
function cleanseVocabString(item, ignoreCaps=false) { function cleanseVocabString(item, ignoreCaps=false, ignoreAccents=false) {
const chars = /[\p{P}\p{S} ]+/ug; const chars = /[\p{P}\p{S} ]+/ug;
const cleansed = item.replace(chars, ""); let cleansed = item.replace(chars, "");
if (ignoreAccents) cleansed = cleansed.normalize('NFD').replace(/\p{Diacritic}/gu, "");
if (ignoreCaps) { if (ignoreCaps) {
return cleansed.toLowerCase(); return cleansed.toLowerCase();
} else { } else {
@@ -494,6 +504,8 @@ exports.processAnswer = functions.https.onCall((data, context) => {
return transaction.get(progressDoc.data().switch_language ? termDocId : definitionDocId).then((answerDoc) => { return transaction.get(progressDoc.data().switch_language ? termDocId : definitionDocId).then((answerDoc) => {
const docData = progressDoc.data(); const docData = progressDoc.data();
docData.ignoreCaps = docData.ignoreCaps === true;
docData.ignoreAccents = docData.ignoreAccents === true;
const mode = docData.mode; const mode = docData.mode;
const correctAnswers = answerDoc.data().item; const correctAnswers = answerDoc.data().item;
const splitCorrectAnswers = correctAnswers.split("/"); const splitCorrectAnswers = correctAnswers.split("/");
@@ -507,15 +519,16 @@ exports.processAnswer = functions.https.onCall((data, context) => {
cleansedDoneSplitCorrectAnswers.push( cleansedDoneSplitCorrectAnswers.push(
cleanseVocabString( cleanseVocabString(
notDoneSplitCorrectAnswers.splice(index, 1)[0], notDoneSplitCorrectAnswers.splice(index, 1)[0],
docData.ignoreCaps docData.ignoreCaps,
docData.ignoreAccents
) )
); );
} }
}); });
const cleansedNotDoneSplitCorrectAnswers = notDoneSplitCorrectAnswers.map((answer) => cleanseVocabString(answer, docData.ignoreCaps)); const cleansedNotDoneSplitCorrectAnswers = notDoneSplitCorrectAnswers.map((answer) => cleanseVocabString(answer, docData.ignoreCaps, docData.ignoreAccents));
const cleansedSplitCorrectAnswers = cleansedNotDoneSplitCorrectAnswers.concat(cleansedDoneSplitCorrectAnswers); const cleansedSplitCorrectAnswers = cleansedNotDoneSplitCorrectAnswers.concat(cleansedDoneSplitCorrectAnswers);
const cleansedInputAnswer = cleanseVocabString(inputAnswer, docData.ignoreCaps); const cleansedInputAnswer = cleanseVocabString(inputAnswer, docData.ignoreCaps, docData.ignoreAccents);
let isCorrectAnswer = false; let isCorrectAnswer = false;
let correctAnswerIndex; let correctAnswerIndex;

View File

@@ -70,6 +70,14 @@ export default function ClassicTestStart(props) {
/> />
<span>Ignore capitals</span> <span>Ignore capitals</span>
</label> </label>
<label>
<Checkbox
checked={props.ignoreAccents}
onChange={props.handleIgnoreAccentsChange}
inputProps={{ 'aria-label': 'checkbox' }}
/>
<span>Ignore accents</span>
</label>
<label> <label>
<Checkbox <Checkbox
checked={props.showNumberOfAnswers} checked={props.showNumberOfAnswers}

View File

@@ -70,6 +70,14 @@ export default function LivesTestStart(props) {
/> />
<span>Ignore capitals</span> <span>Ignore capitals</span>
</label> </label>
<label>
<Checkbox
checked={props.ignoreAccents}
onChange={props.handleIgnoreAccentsChange}
inputProps={{ 'aria-label': 'checkbox' }}
/>
<span>Ignore accents</span>
</label>
<label> <label>
<Checkbox <Checkbox
checked={props.showNumberOfAnswers} checked={props.showNumberOfAnswers}

View File

@@ -72,6 +72,7 @@ export default withRouter(class LoggedInHome extends React.Component {
sliderValue: 1, sliderValue: 1,
switchLanguage: false, switchLanguage: false,
ignoreCaps: false, ignoreCaps: false,
ignoreAccents: false,
showNumberOfAnswers: false, showNumberOfAnswers: false,
totalTestQuestions: 1, totalTestQuestions: 1,
pendingDeletions: {}, pendingDeletions: {},
@@ -201,6 +202,7 @@ export default withRouter(class LoggedInHome extends React.Component {
mode: mode, mode: mode,
limit: this.state.sliderValue, limit: this.state.sliderValue,
ignoreCaps: this.state.ignoreCaps, ignoreCaps: this.state.ignoreCaps,
ignoreAccents: this.state.ignoreAccents,
showNumberOfAnswers: this.state.showNumberOfAnswers, showNumberOfAnswers: this.state.showNumberOfAnswers,
}).then((result) => { }).then((result) => {
const progressId = result.data; const progressId = result.data;
@@ -360,6 +362,12 @@ export default withRouter(class LoggedInHome extends React.Component {
}); });
} }
handleIgnoreAccentsChange = (event) => {
this.setState({
ignoreAccents: event.target.checked,
});
}
handleShowNumberOfAnswersChange = (event) => { handleShowNumberOfAnswersChange = (event) => {
this.setState({ this.setState({
showNumberOfAnswers: event.target.checked, showNumberOfAnswers: event.target.checked,
@@ -586,9 +594,11 @@ export default withRouter(class LoggedInHome extends React.Component {
onSliderChange={this.changeSliderValue} onSliderChange={this.changeSliderValue}
switchLanguage={this.state.switchLanguage} switchLanguage={this.state.switchLanguage}
ignoreCaps={this.state.ignoreCaps} ignoreCaps={this.state.ignoreCaps}
ignoreAccents={this.state.ignoreAccents}
showNumberOfAnswers={this.state.showNumberOfAnswers} showNumberOfAnswers={this.state.showNumberOfAnswers}
handleSwitchLanguageChange={this.handleSwitchLanguageChange} handleSwitchLanguageChange={this.handleSwitchLanguageChange}
handleIgnoreCapsChange={this.handleIgnoreCapsChange} handleIgnoreCapsChange={this.handleIgnoreCapsChange}
handleIgnoreAccentsChange={this.handleIgnoreAccentsChange}
handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange} handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange}
loading={this.state.loading} loading={this.state.loading}
/> />
@@ -603,9 +613,11 @@ export default withRouter(class LoggedInHome extends React.Component {
onSliderChange={this.changeSliderValue} onSliderChange={this.changeSliderValue}
switchLanguage={this.state.switchLanguage} switchLanguage={this.state.switchLanguage}
ignoreCaps={this.state.ignoreCaps} ignoreCaps={this.state.ignoreCaps}
ignoreAccents={this.state.ignoreAccents}
showNumberOfAnswers={this.state.showNumberOfAnswers} showNumberOfAnswers={this.state.showNumberOfAnswers}
handleSwitchLanguageChange={this.handleSwitchLanguageChange} handleSwitchLanguageChange={this.handleSwitchLanguageChange}
handleIgnoreCapsChange={this.handleIgnoreCapsChange} handleIgnoreCapsChange={this.handleIgnoreCapsChange}
handleIgnoreAccentsChange={this.handleIgnoreAccentsChange}
handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange} handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange}
loading={this.state.loading} loading={this.state.loading}
/> />

View File

@@ -62,6 +62,7 @@ export default withRouter(class Progress extends React.Component {
startTime: null, startTime: null,
sound: false, sound: false,
ignoreCaps: false, ignoreCaps: false,
ignoreAccents: false,
numberOfAnswers: null, numberOfAnswers: null,
virtualKeyboardLanguage: "english", virtualKeyboardLanguage: "english",
virtualKeyboardLayoutName: "default", virtualKeyboardLayoutName: "default",
@@ -757,8 +758,6 @@ export default withRouter(class Progress extends React.Component {
"Answers:" "Answers:"
} }
</h2> </h2>
{this.state.currentCorrect.map((vocab, index) =>
{this.state.currentCorrect.map((vocab, index) =>
{this.state.currentCorrect.map((vocab, index) => {this.state.currentCorrect.map((vocab, index) =>
<p key={index}>{vocab}</p> <p key={index}>{vocab}</p>
)} )}

View File

@@ -42,6 +42,7 @@ export default withRouter(class SearchSets extends Component {
showLivesTestStart: false, showLivesTestStart: false,
searchInput: "", searchInput: "",
ignoreCaps: false, ignoreCaps: false,
ignoreAccents: false,
showNumberOfAnswers: false, showNumberOfAnswers: false,
switchLanguage: false, switchLanguage: false,
sliderValue: 1, sliderValue: 1,
@@ -140,6 +141,7 @@ export default withRouter(class SearchSets extends Component {
mode: mode, mode: mode,
limit: this.state.sliderValue, limit: this.state.sliderValue,
ignoreCaps: this.state.ignoreCaps, ignoreCaps: this.state.ignoreCaps,
ignoreAccents: this.state.ignoreAccents,
showNumberOfAnswers: this.state.showNumberOfAnswers, showNumberOfAnswers: this.state.showNumberOfAnswers,
}).then((result) => { }).then((result) => {
const progressId = result.data; const progressId = result.data;
@@ -251,6 +253,12 @@ export default withRouter(class SearchSets extends Component {
}); });
} }
handleIgnoreAccentsChange = (event) => {
this.setState({
ignoreAccents: event.target.checked,
});
}
handleShowNumberOfAnswersChange = (event) => { handleShowNumberOfAnswersChange = (event) => {
this.setState({ this.setState({
showNumberOfAnswers: event.target.checked, showNumberOfAnswers: event.target.checked,
@@ -356,9 +364,11 @@ export default withRouter(class SearchSets extends Component {
onSliderChange={this.changeSliderValue} onSliderChange={this.changeSliderValue}
switchLanguage={this.state.switchLanguage} switchLanguage={this.state.switchLanguage}
ignoreCaps={this.state.ignoreCaps} ignoreCaps={this.state.ignoreCaps}
ignoreAccents={this.state.ignoreAccents}
showNumberOfAnswers={this.state.showNumberOfAnswers} showNumberOfAnswers={this.state.showNumberOfAnswers}
handleSwitchLanguageChange={this.handleSwitchLanguageChange} handleSwitchLanguageChange={this.handleSwitchLanguageChange}
handleIgnoreCapsChange={this.handleIgnoreCapsChange} handleIgnoreCapsChange={this.handleIgnoreCapsChange}
handleIgnoreAccentsChange={this.handleIgnoreAccentsChange}
handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange} handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange}
loading={this.state.loading} loading={this.state.loading}
/> />
@@ -373,9 +383,11 @@ export default withRouter(class SearchSets extends Component {
onSliderChange={this.changeSliderValue} onSliderChange={this.changeSliderValue}
switchLanguage={this.state.switchLanguage} switchLanguage={this.state.switchLanguage}
ignoreCaps={this.state.ignoreCaps} ignoreCaps={this.state.ignoreCaps}
ignoreAccents={this.state.ignoreAccents}
showNumberOfAnswers={this.state.showNumberOfAnswers} showNumberOfAnswers={this.state.showNumberOfAnswers}
handleSwitchLanguageChange={this.handleSwitchLanguageChange} handleSwitchLanguageChange={this.handleSwitchLanguageChange}
handleIgnoreCapsChange={this.handleIgnoreCapsChange} handleIgnoreCapsChange={this.handleIgnoreCapsChange}
handleIgnoreAccentsChange={this.handleIgnoreAccentsChange}
handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange} handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange}
loading={this.state.loading} loading={this.state.loading}
/> />

View File

@@ -54,6 +54,7 @@ export default withRouter(class SetPage extends React.Component {
sliderValue: 1, sliderValue: 1,
switchLanguage: false, switchLanguage: false,
ignoreCaps: false, ignoreCaps: false,
ignoreAccents: false,
showNumberOfAnswers: false, showNumberOfAnswers: false,
totalTestQuestions: 1, totalTestQuestions: 1,
}; };
@@ -137,6 +138,7 @@ export default withRouter(class SetPage extends React.Component {
mode: mode, mode: mode,
limit: this.state.sliderValue, limit: this.state.sliderValue,
ignoreCaps: this.state.ignoreCaps, ignoreCaps: this.state.ignoreCaps,
ignoreAccents: this.state.ignoreAccents,
showNumberOfAnswers: this.state.showNumberOfAnswers, showNumberOfAnswers: this.state.showNumberOfAnswers,
}).then((result) => { }).then((result) => {
const progressId = result.data; const progressId = result.data;
@@ -342,6 +344,12 @@ export default withRouter(class SetPage extends React.Component {
}); });
} }
handleIgnoreAccentsChange = (event) => {
this.setState({
ignoreAccents: event.target.checked,
});
}
handleShowNumberOfAnswersChange = (event) => { handleShowNumberOfAnswersChange = (event) => {
this.setState({ this.setState({
showNumberOfAnswers: event.target.checked, showNumberOfAnswers: event.target.checked,
@@ -496,9 +504,11 @@ export default withRouter(class SetPage extends React.Component {
onSliderChange={this.changeSliderValue} onSliderChange={this.changeSliderValue}
switchLanguage={this.state.switchLanguage} switchLanguage={this.state.switchLanguage}
ignoreCaps={this.state.ignoreCaps} ignoreCaps={this.state.ignoreCaps}
ignoreAccents={this.state.ignoreAccents}
showNumberOfAnswers={this.state.showNumberOfAnswers} showNumberOfAnswers={this.state.showNumberOfAnswers}
handleSwitchLanguageChange={this.handleSwitchLanguageChange} handleSwitchLanguageChange={this.handleSwitchLanguageChange}
handleIgnoreCapsChange={this.handleIgnoreCapsChange} handleIgnoreCapsChange={this.handleIgnoreCapsChange}
handleIgnoreAccentsChange={this.handleIgnoreAccentsChange}
handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange} handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange}
loading={this.state.loading} loading={this.state.loading}
disabled={!this.state.canStartTest} disabled={!this.state.canStartTest}
@@ -514,9 +524,11 @@ export default withRouter(class SetPage extends React.Component {
onSliderChange={this.changeSliderValue} onSliderChange={this.changeSliderValue}
switchLanguage={this.state.switchLanguage} switchLanguage={this.state.switchLanguage}
ignoreCaps={this.state.ignoreCaps} ignoreCaps={this.state.ignoreCaps}
ignoreAccents={this.state.ignoreAccents}
showNumberOfAnswers={this.state.showNumberOfAnswers} showNumberOfAnswers={this.state.showNumberOfAnswers}
handleSwitchLanguageChange={this.handleSwitchLanguageChange} handleSwitchLanguageChange={this.handleSwitchLanguageChange}
handleIgnoreCapsChange={this.handleIgnoreCapsChange} handleIgnoreCapsChange={this.handleIgnoreCapsChange}
handleIgnoreAccentsChange={this.handleIgnoreAccentsChange}
handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange} handleShowNumberOfAnswersChange={this.handleShowNumberOfAnswersChange}
loading={this.state.loading} loading={this.state.loading}
disabled={!this.state.canStartTest} disabled={!this.state.canStartTest}

View File

@@ -127,6 +127,9 @@ describe("Parandum Cloud Functions", function () {
sets: [setOne], sets: [setOne],
mode: "questions", mode: "questions",
limit: 2, limit: 2,
ignoreAccents: false,
ignoreCaps: false,
showNumberOfAnswers: false,
}; };
const progressId = await createProgress(requestData); const progressId = await createProgress(requestData);
@@ -164,7 +167,7 @@ describe("Parandum Cloud Functions", function () {
}); });
}); });
it("createProgress can create new questions mode progress file from multiple existing sets", async () => { it("createProgress can create new questions mode progress file from multiple existing sets with all test aids enabled", async () => {
const createProgress = test.wrap(cloudFunctions.createProgress); const createProgress = test.wrap(cloudFunctions.createProgress);
const setDataOne = { const setDataOne = {
@@ -218,6 +221,9 @@ describe("Parandum Cloud Functions", function () {
sets: [setOne, setTwo], sets: [setOne, setTwo],
mode: "questions", mode: "questions",
limit: 4, limit: 4,
ignoreAccents: true,
ignoreCaps: true,
showNumberOfAnswers: true,
}; };
const progressId = await createProgress(requestData); const progressId = await createProgress(requestData);
@@ -241,7 +247,7 @@ describe("Parandum Cloud Functions", function () {
assert.strictEqual(snapAfter.typo, false); assert.strictEqual(snapAfter.typo, false);
}); });
it("createProgress can create new lives mode progress file from existing set", async () => { it("createProgress can create new lives mode progress file from existing set with no test aids enabled", async () => {
const createProgress = test.wrap(cloudFunctions.createProgress); const createProgress = test.wrap(cloudFunctions.createProgress);
const setDataOne = { const setDataOne = {
@@ -271,6 +277,9 @@ describe("Parandum Cloud Functions", function () {
sets: [setOne], sets: [setOne],
mode: "lives", mode: "lives",
limit: 2, limit: 2,
ignoreAccents: false,
ignoreCaps: false,
showNumberOfAnswers: false,
}; };
const progressId = await createProgress(requestData); const progressId = await createProgress(requestData);
@@ -328,6 +337,9 @@ describe("Parandum Cloud Functions", function () {
sets: [setOne], sets: [setOne],
mode: "questions", mode: "questions",
limit: 2, limit: 2,
ignoreAccents: false,
ignoreCaps: false,
showNumberOfAnswers: false,
}; };
firebase.assertSucceeds(createProgress(requestData)); firebase.assertSucceeds(createProgress(requestData));
@@ -363,6 +375,9 @@ describe("Parandum Cloud Functions", function () {
sets: [setTwo], sets: [setTwo],
mode: "questions", mode: "questions",
limit: 2, limit: 2,
ignoreAccents: false,
ignoreCaps: false,
showNumberOfAnswers: false,
}; };
firebase.assertFails(createProgress(requestData)); firebase.assertFails(createProgress(requestData));