Buttons to restart test or use incorrect answers

This commit is contained in:
2021-10-22 13:43:45 +01:00
parent 377ba23361
commit 53bd964916
4 changed files with 780 additions and 154 deletions

View File

@@ -297,6 +297,95 @@ exports.createProgress = functions.https.onCall((data, context) => {
}); });
}); });
/**
* Creates new progress document using the incorrect answers from another progess document.
* @param {string} data The progress ID of the existing progress document to use.
* @return {string} The ID of the created progress document.
*/
exports.createProgressWithIncorrect = functions.https.onCall((data, context) => {
const uid = LOCAL_TESTING ? "M3JPrFRH6Fdo8XMUbF0l2zVZUCH3" : context.auth.uid;
if (context.app == undefined && !LOCAL_TESTING) {
throw new functions.https.HttpsError(
"failed-precondition",
"The function must be called from an App Check verified app.");
}
return db.runTransaction(async (transaction) => {
if (typeof data !== "string") {
throw new functions.https.HttpsError("invalid-argument", "Progress ID must be a string");
}
const oldProgressDocId = db.collection("progress").doc(data);
return transaction.get(oldProgressDocId)
.then(async (doc) => {
if (!doc.exists) throw new functions.https.HttpsError("invalid-argument", "Progress record doesn't exist");
if (doc.data().uid !== uid) throw new functions.https.HttpsError("permission-denied", "Can't use other users' progress records");
if (doc.data().incorrect.length < 1) throw new functions.https.HttpsError("failed-precondition", "Progress record must have at least one incorrect answer");
let progressData = doc.data();
let dataToSet = {
correct: [],
incorrect: [],
questions: shuffleArray([... new Set(progressData.incorrect)]),
duration: null,
progress: 0,
start_time: Date.now(),
set_title: progressData.set_title,
uid: progressData.uid,
switch_language: progressData.switch_language,
mode: progressData.mode,
current_correct: [],
typo: false,
setIds: progressData.setIds,
};
if (progressData.mode === "lives") {
dataToSet.lives = progressData.start_lives;
dataToSet.start_lives = progressData.start_lives;
}
const newProgressDocId = db.collection("progress").doc();
let batches = [db.batch()];
let promises = [];
dataToSet.questions.map(async (vocabId, index) => {
if (index % 248 === 0) {
batches.push(db.batch());
}
let currentBatchIndex = batches.length - 1;
promises.push(transaction.get(oldProgressDocId.collection("terms").doc(vocabId))
.then((termDoc) => {
return batches[currentBatchIndex].set(
newProgressDocId.collection("terms").doc(vocabId),
termDoc.data()
);
}));
promises.push(transaction.get(oldProgressDocId.collection("definitions").doc(vocabId))
.then((termDoc) => {
return batches[currentBatchIndex].set(
newProgressDocId.collection("definitions").doc(vocabId),
termDoc.data()
);
}));
});
batches[batches.length - 1].set(
newProgressDocId,
dataToSet
);
await Promise.all(promises);
await Promise.all(batches.map((batch) => batch.commit()));
return newProgressDocId.id;
})
.catch((error) => {
throw new functions.https.HttpsError("unknown", "Can't create new progress record from existing one");
});
});
});
/** /**
* Processes a response to a question in a vocab set. * Processes a response to a question in a vocab set.
* @param {string} progressId The ID of the progress file to retrieve the prompt from. * @param {string} progressId The ID of the progress file to retrieve the prompt from.

View File

@@ -3,18 +3,60 @@ import { withRouter } from "react-router-dom";
import { HomeRounded as HomeRoundedIcon, ArrowForwardRounded as ArrowForwardRoundedIcon, SettingsRounded as SettingsRoundedIcon, CloseRounded as CloseRoundedIcon, PeopleRounded as PeopleRoundedIcon, QuestionAnswerRounded as QuestionAnswerRoundedIcon } from "@material-ui/icons"; import { HomeRounded as HomeRoundedIcon, ArrowForwardRounded as ArrowForwardRoundedIcon, SettingsRounded as SettingsRoundedIcon, CloseRounded as CloseRoundedIcon, PeopleRounded as PeopleRoundedIcon, QuestionAnswerRounded as QuestionAnswerRoundedIcon } from "@material-ui/icons";
import NavBar from "./NavBar"; import NavBar from "./NavBar";
import Button from "./Button"; import Button from "./Button";
import LinkButton from "./LinkButton";
import Error404 from "./Error404"; import Error404 from "./Error404";
import SettingsContent from "./SettingsContent"; import SettingsContent from "./SettingsContent";
import Footer from "./Footer"; import Footer from "./Footer";
import LineChart from './LineChart'; import LineChart from './LineChart';
import ProgressStats from './ProgressStats'; import ProgressStats from './ProgressStats';
import ConfirmationDialog from "./ConfirmationDialog";
import "./css/PopUp.css"; import "./css/PopUp.css";
import "./css/Progress.css"; import "./css/Progress.css";
import "./css/Chart.css"; import "./css/Chart.css";
export default withRouter(class Progress extends React.Component { export default withRouter(class Progress extends React.Component {
changeableStateItems = {
loading: false,
canProceed: true,
canStartTest: true,
showTestRestart: false,
showIncorrectTestStart: false,
progressInaccessible: false,
correct: 0,
incorrect: 0,
totalQuestions: 0,
progress: 0,
setTitle: "",
switchLanguage: false,
answerInput: "",
currentPrompt: "",
currentSound: false,
currentSetOwner: "",
nextPrompt: "",
nextSound: false,
nextSetOwner: "",
currentAnswerStatus: null,
currentCorrect: [],
moreAnswers: true,
duration: 0,
incorrectAnswers: {},
showSettings: false,
soundInput: this.props.sound,
themeInput: this.props.theme,
coloredEdgesInput: this.props.coloredEdges,
setIds: [],
attemptNumber: 1,
attemptHistory: {},
questions: [],
originalTotalQuestions: 1,
lives: 1,
startLives: null,
setComplete: false,
averagePercentage: null,
pageLoaded: false,
startTime: null,
}
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
@@ -22,9 +64,9 @@ export default withRouter(class Progress extends React.Component {
db: props.db, db: props.db,
functions: { functions: {
processAnswer: props.functions.httpsCallable("processAnswer"), processAnswer: props.functions.httpsCallable("processAnswer"),
createProgress: props.functions.httpsCallable("createProgress"),
createProgressWithIncorrect: props.functions.httpsCallable("createProgressWithIncorrect"),
}, },
loading: false,
canProceed: true,
navbarItems: [ navbarItems: [
{ {
type: "button", type: "button",
@@ -39,40 +81,7 @@ export default withRouter(class Progress extends React.Component {
hideTextMobile: true, hideTextMobile: true,
} }
], ],
progressInaccessible: false, ...this.changeableStateItems,
correct: 0,
incorrect: 0,
totalQuestions: 0,
progress: 0,
setTitle: "",
switchLanguage: false,
answerInput: "",
currentPrompt: "",
currentSound: false,
currentSetOwner: "",
nextPrompt: "",
nextSound: false,
nextSetOwner: "",
currentAnswerStatus: null,
currentCorrect: [],
moreAnswers: true,
duration: 0,
incorrectAnswers: {},
showSettings: false,
soundInput: this.props.sound,
themeInput: this.props.theme,
coloredEdgesInput: this.props.coloredEdges,
setIds: [],
attemptNumber: 1,
attemptHistory: {},
questions: [],
originalTotalQuestions: 1,
lives: 1,
startLives: null,
setComplete: false,
averagePercentage: null,
pageLoaded: false,
startTime: null,
}; };
let isMounted = true; let isMounted = true;
@@ -87,6 +96,10 @@ export default withRouter(class Progress extends React.Component {
} }
async componentDidMount() { async componentDidMount() {
this.unlisten = this.props.history.listen((location, action) => {
if (location.pathname.startsWith("/progress/")) this.setState(this.changeableStateItems, () => this.componentDidMount());
});
const progressId = this.props.match.params.progressId; const progressId = this.props.match.params.progressId;
const progressRef = this.state.db.collection("progress").doc(progressId); const progressRef = this.state.db.collection("progress").doc(progressId);
@@ -129,94 +142,95 @@ export default withRouter(class Progress extends React.Component {
]; ];
}); });
if (!newState.progressInaccessible && !setDone) { if (!newState.progressInaccessible) {
let nextPromptRef; if (!setDone) {
if (!newState.switchLanguage) { let nextPromptRef;
nextPromptRef = progressRef if (!newState.switchLanguage) {
.collection("terms") nextPromptRef = progressRef
.doc(newState.questions[newState.progress]); .collection("terms")
} else { .doc(newState.questions[newState.progress]);
nextPromptRef = progressRef } else {
.collection("definitions") nextPromptRef = progressRef
.doc(newState.questions[newState.progress]); .collection("definitions")
} .doc(newState.questions[newState.progress]);
}
await nextPromptRef.get().then((doc) => {
newState.currentPrompt = doc.data().item; await nextPromptRef.get().then((doc) => {
newState.currentSound = doc.data().sound === true; newState.currentPrompt = doc.data().item;
}).catch((error) => { newState.currentSound = doc.data().sound === true;
newState.progressInaccessible = true;
console.log(`Progress data inaccessible: ${error}`);
});
} else if (setDone) {
newState.moreAnswers = false;
newState.currentAnswerStatus = true;
newState.duration = duration;
let promises = [];
promises.push(this.state.db.collection("progress")
.where("uid", "==", this.state.user.uid)
.where("setIds", "==", newState.setIds)
.orderBy("start_time")
.get()
.then((querySnapshot) => {
newState.attemptNumber = querySnapshot.docs.map((doc) => doc.id).indexOf(this.props.match.params.progressId) + 1;
if (querySnapshot.docs.length > 1)
newState.attemptHistory = querySnapshot.docs.filter((doc) => doc.data().duration !== null)
.map((doc) => {
if (doc.id === this.props.match.params.progressId) newState.startTime = doc.data().start_time;
return {
x: new Date(doc.data().start_time),
y: (doc.data().correct.length / doc.data().questions.length * 100),
}
});
}));
promises.push(this.state.db.collection("completed_progress")
.doc(setIds.sort().join("__"))
.get()
.then((completedProgressDoc) => {
newState.averagePercentage = (completedProgressDoc.data().total_percentage / completedProgressDoc.data().attempts).toFixed(2);
}).catch((error) => { }).catch((error) => {
console.log(`Couldn't get average percentage: ${error}`); newState.progressInaccessible = true;
newState.averagePercentage = null; console.log(`Progress data inaccessible: ${error}`);
})); });
} else {
newState.moreAnswers = false;
newState.currentAnswerStatus = true;
newState.duration = duration;
if (incorrectAnswers.length > 0) { let promises = [];
newState.incorrectAnswers = {}; promises.push(this.state.db.collection("progress")
.where("uid", "==", this.state.user.uid)
promises.push(Promise.all(incorrectAnswers.map((vocabId) => { .where("setIds", "==", newState.setIds)
if (newState.incorrectAnswers[vocabId]) { .orderBy("start_time")
return newState.incorrectAnswers[vocabId].count++; .get()
} else { .then((querySnapshot) => {
newState.incorrectAnswers[vocabId] = { newState.attemptNumber = querySnapshot.docs.map((doc) => doc.id).indexOf(this.props.match.params.progressId) + 1;
count: 1, if (querySnapshot.docs.length > 1)
}; newState.attemptHistory = querySnapshot.docs.filter((doc) => doc.data().duration !== null)
.map((doc) => {
if (doc.id === this.props.match.params.progressId) newState.startTime = doc.data().start_time;
return {
x: new Date(doc.data().start_time),
y: (doc.data().correct.length / doc.data().questions.length * 100),
}
});
}));
return Promise.all([ promises.push(this.state.db.collection("completed_progress")
progressRef.collection("terms") .doc(setIds.sort().join("__"))
.doc(vocabId) .get()
.get().then((termDoc) => { .then((completedProgressDoc) => {
newState.switchLanguage ? newState.incorrectAnswers[vocabId].answer = termDoc.data().item.split("/") : newState.incorrectAnswers[vocabId].prompt = termDoc.data().item; newState.averagePercentage = (completedProgressDoc.data().total_percentage / completedProgressDoc.data().attempts).toFixed(2);
}), }).catch((error) => {
progressRef.collection("definitions") console.log(`Couldn't get average percentage: ${error}`);
.doc(vocabId) newState.averagePercentage = null;
.get().then((definitionDoc) => { }));
newState.switchLanguage ? newState.incorrectAnswers[vocabId].prompt = definitionDoc.data().item : newState.incorrectAnswers[vocabId].answer = definitionDoc.data().item.split("/");
}) if (incorrectAnswers.length > 0) {
]); newState.incorrectAnswers = {};
}
})).catch((error) => { promises.push(Promise.all(incorrectAnswers.map((vocabId) => {
console.log(`Couldn't retrieve incorrect answers: ${error}`); if (newState.incorrectAnswers[vocabId]) {
})); return newState.incorrectAnswers[vocabId].count++;
} else {
newState.incorrectAnswers[vocabId] = {
count: 1,
};
return Promise.all([
progressRef.collection("terms")
.doc(vocabId)
.get().then((termDoc) => {
newState.switchLanguage ? newState.incorrectAnswers[vocabId].answer = termDoc.data().item.split("/") : newState.incorrectAnswers[vocabId].prompt = termDoc.data().item;
}),
progressRef.collection("definitions")
.doc(vocabId)
.get().then((definitionDoc) => {
newState.switchLanguage ? newState.incorrectAnswers[vocabId].prompt = definitionDoc.data().item : newState.incorrectAnswers[vocabId].answer = definitionDoc.data().item.split("/");
})
]);
}
})).catch((error) => {
console.log(`Couldn't retrieve incorrect answers: ${error}`);
}));
}
await Promise.all(promises);
} }
await Promise.all(promises);
} }
this.setState(newState, () => { this.setState(newState, () => {
if (!setDone) this.answerInput.focus(); if (!newState.progressInaccessible && !setDone) this.answerInput.focus();
}); });
this.props.page.load(); this.props.page.load();
@@ -230,6 +244,7 @@ export default withRouter(class Progress extends React.Component {
componentWillUnmount() { componentWillUnmount() {
this.isMounted = false; this.isMounted = false;
this.props.page.unload(); this.props.page.unload();
this.unlisten();
} }
showSettings = () => { showSettings = () => {
@@ -277,7 +292,7 @@ export default withRouter(class Progress extends React.Component {
} }
} }
showNextItem = () => { proceed = () => {
if (this.state.canProceed) { if (this.state.canProceed) {
if (this.state.currentAnswerStatus === null) { if (this.state.currentAnswerStatus === null) {
this.processAnswer(); this.processAnswer();
@@ -342,6 +357,7 @@ export default withRouter(class Progress extends React.Component {
loading: false, loading: false,
canProceed: true, canProceed: true,
typo: false, typo: false,
canStartTest: true,
}; };
if (data.correct) { if (data.correct) {
@@ -519,11 +535,98 @@ export default withRouter(class Progress extends React.Component {
return `${hours}:${minutes}:${seconds}`; return `${hours}:${minutes}:${seconds}`;
} }
startLoading = () => {
this.setState({
canStartTest: false,
loading: true,
});
}
stopLoading = () => {
this.setState({
canStartTest: true,
loading: false,
});
}
recreateSameTest = () => {
if (!this.state.loading) {
this.state.functions.createProgress({
sets: this.state.setIds,
switch_language: this.state.switchLanguage,
mode: this.state.mode,
limit: this.state.mode === "questions" ? this.state.progress - this.state.incorrect
: this.state.mode === "lives" ? this.state.lives
: 1,
}).then((result) => {
const progressId = result.data;
this.stopLoading();
this.props.history.push("/progress/" + progressId);
this.props.logEvent("restart_test", {
progress_id: progressId,
});
}).catch((error) => {
console.log(`Couldn't start test: ${error}`);
this.stopLoading();
});
this.startLoading();
}
}
createTestWithIncorrect = () => {
if (!this.state.loading) {
this.state.functions.createProgressWithIncorrect(this.props.match.params.progressId).then((result) => {
const progressId = result.data;
this.stopLoading();
this.props.history.push("/progress/" + progressId);
this.props.logEvent("start_test_with_incorrect", {
progress_id: progressId,
});
}).catch((error) => {
console.log(`Couldn't create test with incorrect answers: ${error}`);
this.stopLoading();
});
this.startLoading();
}
}
showTestRestart = () => {
if (this.state.canStartTest) {
this.setState({
showTestRestart: true,
});
}
}
hideTestRestart = () => {
this.setState({
showTestRestart: false,
});
}
showIncorrectTestStart = () => {
if (this.state.canStartTest) {
this.setState({
showIncorrectTestStart: true,
});
}
}
hideIncorrectTestStart = () => {
this.setState({
showIncorrectTestStart: false,
});
}
render() { render() {
return ( return (
<div> <div>
{ {
this.state.pageLoaded && this.props.page.loaded &&
(this.state.progressInaccessible (this.state.progressInaccessible
? ?
<Error404 /> <Error404 />
@@ -537,7 +640,7 @@ export default withRouter(class Progress extends React.Component {
<div> <div>
<p className="current-prompt">{this.state.currentPrompt}</p> <p className="current-prompt">{this.state.currentPrompt}</p>
<form className="answer-input-container" onSubmit={(e) => e.preventDefault()} > <form className="answer-input-container" onSubmit={(e) => e.preventDefault()} >
<input type="submit" className="form-submit" onClick={this.showNextItem} /> <input type="submit" className="form-submit" onClick={this.proceed} />
<input <input
type="text" type="text"
name="answer_input" name="answer_input"
@@ -632,6 +735,23 @@ export default withRouter(class Progress extends React.Component {
</div> </div>
} }
</div> </div>
<div className="progress-end-button-container">
<Button
onClick={this.showTestRestart}
>
Restart test
</Button>
{
this.state.incorrect > 0 &&
<Button
onClick={this.showIncorrectTestStart}
>
Create test with incorrect
</Button>
}
</div>
{ {
this.state.incorrectAnswers && Object.keys(this.state.incorrectAnswers).length > 0 && this.state.incorrectAnswers && Object.keys(this.state.incorrectAnswers).length > 0 &&
<> <>
@@ -663,15 +783,6 @@ export default withRouter(class Progress extends React.Component {
<LineChart data={this.state.attemptHistory} currentPointX={this.state.startTime} /> <LineChart data={this.state.attemptHistory} currentPointX={this.state.startTime} />
</> </>
} }
<div className="progress-end-button-container">
<LinkButton
to="/"
className="progress-end-button"
>
Done
</LinkButton>
</div>
</main> </main>
: :
<main className="progress-container"> <main className="progress-container">
@@ -679,7 +790,7 @@ export default withRouter(class Progress extends React.Component {
<div> <div>
<p className="current-prompt">{this.state.currentPrompt}</p> <p className="current-prompt">{this.state.currentPrompt}</p>
<form className="answer-input-container answer-input-container--answer-entered" onSubmit={(e) => e.preventDefault()} > <form className="answer-input-container answer-input-container--answer-entered" onSubmit={(e) => e.preventDefault()} >
<input type="submit" className="form-submit" onClick={this.showNextItem} /> <input type="submit" className="form-submit" onClick={this.proceed} />
<input <input
type="text" type="text"
name="answer_input" name="answer_input"
@@ -781,6 +892,24 @@ export default withRouter(class Progress extends React.Component {
</div> </div>
</> </>
} }
{
this.state.showTestRestart &&
<ConfirmationDialog
yesFunction={this.recreateSameTest}
noFunction={this.hideTestRestart}
message="Restart test?"
loading={this.state.loading}
/>
}
{
this.state.showIncorrectTestStart &&
<ConfirmationDialog
yesFunction={this.createTestWithIncorrect}
noFunction={this.hideIncorrectTestStart}
message="Create test with incorrect answers?"
loading={this.state.loading}
/>
}
</>) </>)
} }
</div> </div>

View File

@@ -53,22 +53,18 @@
} }
.progress-end-button-container { .progress-end-button-container {
position: fixed; display: flex;
left: 0; flex-direction: row;
right: 0; flex-wrap: wrap;
margin-left: auto; justify-content: flex-start;
margin-right: auto; word-wrap: break-word;
max-width: 1080px; align-items: center;
bottom: 0; column-gap: 8px;
width: 100%; row-gap: 8px;
height: 0;
} }
.progress-end-button-container > .button { .progress-end-button-container > .button {
margin: 48px; margin: 0;
position: absolute;
right: 0;
bottom: 0;
} }
.progress-settings-overlay-content { .progress-settings-overlay-content {
@@ -133,12 +129,6 @@
} }
@media screen and (max-width: 720px) {
.progress-end-button-container > .button {
margin: 24px;
}
}
@media screen and (max-width: 660px) { @media screen and (max-width: 660px) {
.progress-settings-overlay-content > .settings-themes-container { .progress-settings-overlay-content > .settings-themes-container {
width: 100%; width: 100%;
@@ -158,9 +148,6 @@
} }
@media screen and (max-height: 600px) { @media screen and (max-height: 600px) {
.progress-end-button-container > .button {
margin: 24px;
}
.progress-settings-overlay-content { .progress-settings-overlay-content {
justify-content: flex-start; justify-content: flex-start;
} }

View File

@@ -31,6 +31,10 @@ const soundOne = true;
const vocabTwo = "vocab_02"; const vocabTwo = "vocab_02";
const termTwo = "term_02"; const termTwo = "term_02";
const definitionTwo = "definition_02"; const definitionTwo = "definition_02";
const vocabThree = "vocab_03";
const termThree = "term_03";
const definitionThree = "definition_03";
const soundThree = true;
const soundTwo = true; const soundTwo = true;
const groupOne = "group_01"; const groupOne = "group_01";
const groupTwo = "group_02"; const groupTwo = "group_02";
@@ -39,11 +43,11 @@ const doubleDefinitionTwo = "definition/02";
const punctuationDefinitionOne = "definition .,()-_'\"01"; const punctuationDefinitionOne = "definition .,()-_'\"01";
const progressVocabOne = userOne + "__" + vocabOne; const progressVocabOne = userOne + "__" + vocabOne;
const progressVocabTwo = userOne + "__" + vocabTwo; const progressVocabTwo = userOne + "__" + vocabTwo;
const vocabThree = "vocab_03";
const vocabFour = "vocab_04"; const vocabFour = "vocab_04";
const progressVocabThree = userOne + "__" + vocabThree; const progressVocabThree = userOne + "__" + vocabThree;
const progressVocabFour = userOne + "__" + vocabFour; const progressVocabFour = userOne + "__" + vocabFour;
const incorrectAnswer = "incorrect"; const incorrectAnswer = "incorrect";
const progressOne = "progress_01";
async function deleteCollection(db, collectionPath, batchSize) { async function deleteCollection(db, collectionPath, batchSize) {
const collectionRef = db.collection(collectionPath); const collectionRef = db.collection(collectionPath);
@@ -2069,6 +2073,8 @@ describe("Parandum Cloud Functions", function () {
const groupId = await createGroup(groupOne); const groupId = await createGroup(groupOne);
const groupDocId = firestore.collection("groups").doc(groupId); const groupDocId = firestore.collection("groups").doc(groupId);
await new Promise(res => setTimeout(res, 1000));
const snapGroupAfter = await groupDocId.get().then((doc) => doc.data()); const snapGroupAfter = await groupDocId.get().then((doc) => doc.data());
const userGroupDocId = firestore.collection("users").doc(userOne).collection("groups").doc(groupId); const userGroupDocId = firestore.collection("users").doc(userOne).collection("groups").doc(groupId);
@@ -2091,6 +2097,421 @@ describe("Parandum Cloud Functions", function () {
}); });
}); });
it("createProgressWithIncorrect correctly creates new progress record from progress record with incorrect answers in questions mode", async () => {
const createProgressWithIncorrect = test.wrap(cloudFunctions.createProgressWithIncorrect);
const vocabIdOne = `${setOne}__${vocabOne}`;
const vocabIdTwo = `${setOne}__${vocabTwo}`;
const vocabIdThree = `${setTwo}__${vocabThree}`;
const progressData = {
correct: [vocabIdTwo],
incorrect: [vocabIdTwo, vocabIdTwo, vocabIdOne],
questions: [vocabIdOne, vocabIdTwo, vocabIdThree],
duration: null,
progress: 1,
start_time: 1627308670962,
set_title: `${setOne} & ${setTwo}`,
uid: userOne,
switch_language: false,
mode: "questions",
current_correct: [],
typo: false,
setIds: [setOne, setTwo],
};
const termDataOne = {
term: termOne,
sound: soundOne,
};
const termDataTwo = {
item: termTwo,
sound: soundTwo,
};
const termDataThree = {
term: termThree,
sound: soundThree,
};
const definitionDataOne = {
item: definitionOne,
sound: soundOne,
};
const definitionDataTwo = {
item: definitionTwo,
sound: soundTwo,
};
const definitionDataThree = {
item: definitionThree,
sound: soundThree,
};
const progressDocId = firestore.collection("progress").doc(progressOne);
await progressDocId.set(progressData);
await progressDocId
.collection("terms").doc(vocabIdOne).set(termDataOne);
await progressDocId
.collection("terms").doc(vocabIdTwo).set(termDataTwo);
await progressDocId
.collection("terms").doc(vocabIdThree).set(termDataThree);
await progressDocId
.collection("definitions").doc(vocabIdOne).set(definitionDataOne);
await progressDocId
.collection("definitions").doc(vocabIdTwo).set(definitionDataTwo);
await progressDocId
.collection("definitions").doc(vocabIdThree).set(definitionDataThree);
const returnData = await createProgressWithIncorrect(progressOne);
assert.strictEqual(typeof returnData, "string");
await progressDocId.get((doc) => {
const data = doc.data();
assert.deepStrictEqual(data.correct, []);
assert.deepStrictEqual(data.incorrect, []);
assert.deepStrictEqual(data.correct, []);
hamjest.assertThat(data.questions, hamjest.anyOf(
hamjest.is([vocabOne, vocabTwo, vocabThree]),
hamjest.is([vocabOne, vocabThree, vocabTwo]),
hamjest.is([vocabTwo, vocabThree, vocabOne]),
hamjest.is([vocabTwo, vocabOne, vocabThree]),
hamjest.is([vocabOne, vocabTwo, vocabThree]),
hamjest.is([vocabOne, vocabThree, vocabTwo])
));
assert.strictEqual(data.duration, null);
assert.strictEqual(data.progress, 0);
assert.strictEqual(data.start_time, 1627308670962);
assert.strictEqual(data.set_title, `${setOne} & ${setTwo}`);
assert.strictEqual(data.uid, userOne);
assert.strictEqual(data.switch_language, false);
assert.strictEqual(data.mode, "questions");
assert.deepStrictEqual(data.current_correct, []);
assert.strictEqual(data.typo, false);
assert.deepStrictEqual(data.setIds, []);
});
await progressDocId
.collection("terms").doc(`${setOne}__${vocabOne}`).get()
.then((doc) => assert.deepStrictEqual(doc.data(), termDataOne));
await progressDocId
.collection("terms").doc(`${setOne}__${vocabTwo}`).get()
.then((doc) => assert.deepStrictEqual(doc.data(), termDataTwo));
await progressDocId
.collection("terms").doc(`${setTwo}__${vocabThree}`).get()
.then((doc) => assert.deepStrictEqual(doc.data(), termDataThree));
await progressDocId
.collection("definitions").doc(`${setOne}__${vocabOne}`).get()
.then((doc) => assert.deepStrictEqual(doc.data(), definitionDataOne));
await progressDocId
.collection("definitions").doc(`${setOne}__${vocabTwo}`).get()
.then((doc) => assert.deepStrictEqual(doc.data(), definitionDataTwo));
await progressDocId
.collection("definitions").doc(`${setTwo}__${vocabThree}`).get()
.then((doc) => assert.deepStrictEqual(doc.data(), definitionDataThree));
});
it("createProgressWithIncorrect correctly creates new progress record from progress record with incorrect answers in lives mode", async () => {
const createProgressWithIncorrect = test.wrap(cloudFunctions.createProgressWithIncorrect);
const vocabIdOne = `${setOne}__${vocabOne}`;
const vocabIdTwo = `${setOne}__${vocabTwo}`;
const vocabIdThree = `${setTwo}__${vocabThree}`;
const progressData = {
correct: [vocabIdTwo],
incorrect: [vocabIdTwo, vocabIdTwo, vocabIdOne],
questions: [vocabIdOne, vocabIdTwo, vocabIdThree],
duration: null,
progress: 1,
start_time: 1627308670962,
set_title: `${setOne} & ${setTwo}`,
uid: userOne,
switch_language: false,
mode: "lives",
current_correct: [],
typo: false,
setIds: [setOne, setTwo],
lives: 2,
start_lives: 5,
};
const termDataOne = {
term: termOne,
sound: soundOne,
};
const termDataTwo = {
item: termTwo,
sound: soundTwo,
};
const termDataThree = {
term: termThree,
sound: soundThree,
};
const definitionDataOne = {
item: definitionOne,
sound: soundOne,
};
const definitionDataTwo = {
item: definitionTwo,
sound: soundTwo,
};
const definitionDataThree = {
item: definitionThree,
sound: soundThree,
};
const progressDocId = firestore.collection("progress").doc(progressOne);
await progressDocId.set(progressData);
await progressDocId
.collection("terms").doc(vocabIdOne).set(termDataOne);
await progressDocId
.collection("terms").doc(vocabIdTwo).set(termDataTwo);
await progressDocId
.collection("terms").doc(vocabIdThree).set(termDataThree);
await progressDocId
.collection("definitions").doc(vocabIdOne).set(definitionDataOne);
await progressDocId
.collection("definitions").doc(vocabIdTwo).set(definitionDataTwo);
await progressDocId
.collection("definitions").doc(vocabIdThree).set(definitionDataThree);
const returnData = await createProgressWithIncorrect(progressOne);
assert.strictEqual(typeof returnData, "string");
await progressDocId.get((doc) => {
const data = doc.data();
assert.deepStrictEqual(data.correct, []);
assert.deepStrictEqual(data.incorrect, []);
assert.deepStrictEqual(data.correct, []);
hamjest.assertThat(data.questions, hamjest.anyOf(
hamjest.is([vocabOne, vocabTwo, vocabThree]),
hamjest.is([vocabOne, vocabThree, vocabTwo]),
hamjest.is([vocabTwo, vocabThree, vocabOne]),
hamjest.is([vocabTwo, vocabOne, vocabThree]),
hamjest.is([vocabOne, vocabTwo, vocabThree]),
hamjest.is([vocabOne, vocabThree, vocabTwo])
));
assert.strictEqual(data.duration, null);
assert.strictEqual(data.progress, 0);
assert.strictEqual(data.start_time, 1627308670962);
assert.strictEqual(data.set_title, `${setOne} & ${setTwo}`);
assert.strictEqual(data.uid, userOne);
assert.strictEqual(data.switch_language, false);
assert.strictEqual(data.mode, "questions");
assert.deepStrictEqual(data.current_correct, []);
assert.strictEqual(data.typo, false);
assert.deepStrictEqual(data.setIds, []);
assert.strictEqual(data.lives, 5);
assert.strictEqual(data.start_lives, 5);
});
await progressDocId
.collection("terms").doc(`${setOne}__${vocabOne}`).get()
.then((doc) => assert.deepStrictEqual(doc.data(), termDataOne));
await progressDocId
.collection("terms").doc(`${setOne}__${vocabTwo}`).get()
.then((doc) => assert.deepStrictEqual(doc.data(), termDataTwo));
await progressDocId
.collection("terms").doc(`${setTwo}__${vocabThree}`).get()
.then((doc) => assert.deepStrictEqual(doc.data(), termDataThree));
await progressDocId
.collection("definitions").doc(`${setOne}__${vocabOne}`).get()
.then((doc) => assert.deepStrictEqual(doc.data(), definitionDataOne));
await progressDocId
.collection("definitions").doc(`${setOne}__${vocabTwo}`).get()
.then((doc) => assert.deepStrictEqual(doc.data(), definitionDataTwo));
await progressDocId
.collection("definitions").doc(`${setTwo}__${vocabThree}`).get()
.then((doc) => assert.deepStrictEqual(doc.data(), definitionDataThree));
});
it("createProgressWithIncorrect won't create new progress record when old progress record belongs to different user", async () => {
const createProgressWithIncorrect = test.wrap(cloudFunctions.createProgressWithIncorrect);
const progressData = {
correct: [vocabTwo],
incorrect: [vocabTwo, vocabTwo, vocabOne],
questions: [vocabOne, vocabTwo, vocabThree],
duration: null,
progress: 1,
start_time: 1627308670962,
set_title: `${setOne} & ${setTwo}`,
uid: userTwo,
switch_language: false,
mode: "questions",
current_correct: [],
typo: false,
setIds: [setOne, setTwo],
};
const termDataOne = {
term: termOne,
sound: soundOne,
};
const termDataTwo = {
item: termTwo,
sound: soundTwo,
};
const termDataThree = {
term: termThree,
sound: soundThree,
};
const definitionDataOne = {
item: definitionOne,
sound: soundOne,
};
const definitionDataTwo = {
item: definitionTwo,
sound: soundTwo,
};
const definitionDataThree = {
item: definitionThree,
sound: soundThree,
};
const progressDocId = firestore.collection("progress").doc(progressOne);
await progressDocId.set(progressData);
await progressDocId
.collection("terms").doc(`${setOne}__${vocabOne}`).set(termDataOne);
await progressDocId
.collection("terms").doc(`${setOne}__${vocabTwo}`).set(termDataTwo);
await progressDocId
.collection("terms").doc(`${setTwo}__${vocabThree}`).set(termDataThree);
await progressDocId
.collection("definitions").doc(`${setOne}__${vocabOne}`).set(definitionDataOne);
await progressDocId
.collection("definitions").doc(`${setOne}__${vocabTwo}`).set(definitionDataTwo);
await progressDocId
.collection("definitions").doc(`${setTwo}__${vocabThree}`).set(definitionDataThree);
firebase.assertFails(createProgressWithIncorrect(progressOne));
});
it("createProgressWithIncorrect won't create new progress record when progress ID argument isn't a string", async () => {
const createProgressWithIncorrect = test.wrap(cloudFunctions.createProgressWithIncorrect);
const progressData = {
correct: [vocabTwo],
incorrect: [vocabTwo, vocabTwo, vocabOne],
questions: [vocabOne, vocabTwo, vocabThree],
duration: null,
progress: 1,
start_time: 1627308670962,
set_title: `${setOne} & ${setTwo}`,
uid: userTwo,
switch_language: false,
mode: "questions",
current_correct: [],
typo: false,
setIds: [setOne, setTwo],
};
const termDataOne = {
term: termOne,
sound: soundOne,
};
const termDataTwo = {
item: termTwo,
sound: soundTwo,
};
const termDataThree = {
term: termThree,
sound: soundThree,
};
const definitionDataOne = {
item: definitionOne,
sound: soundOne,
};
const definitionDataTwo = {
item: definitionTwo,
sound: soundTwo,
};
const definitionDataThree = {
item: definitionThree,
sound: soundThree,
};
const progressDocId = firestore.collection("progress").doc("1");
await progressDocId.set(progressData);
await progressDocId
.collection("terms").doc(`${setOne}__${vocabOne}`).set(termDataOne);
await progressDocId
.collection("terms").doc(`${setOne}__${vocabTwo}`).set(termDataTwo);
await progressDocId
.collection("terms").doc(`${setTwo}__${vocabThree}`).set(termDataThree);
await progressDocId
.collection("definitions").doc(`${setOne}__${vocabOne}`).set(definitionDataOne);
await progressDocId
.collection("definitions").doc(`${setOne}__${vocabTwo}`).set(definitionDataTwo);
await progressDocId
.collection("definitions").doc(`${setTwo}__${vocabThree}`).set(definitionDataThree);
firebase.assertFails(createProgressWithIncorrect(1));
});
it("createProgressWithIncorrect won't create new progress record when old progress record doesn't exist", async () => {
const createProgressWithIncorrect = test.wrap(cloudFunctions.createProgressWithIncorrect);
firebase.assertFails(createProgressWithIncorrect("invalid"));
});
it("createProgressWithIncorrect won't create new progress record when old progress record has no incorrect answers", async () => {
const createProgressWithIncorrect = test.wrap(cloudFunctions.createProgressWithIncorrect);
const progressData = {
correct: [vocabTwo],
incorrect: [],
questions: [vocabOne, vocabTwo, vocabThree],
duration: null,
progress: 1,
start_time: 1627308670962,
set_title: `${setOne} & ${setTwo}`,
uid: userOne,
switch_language: false,
mode: "questions",
current_correct: [],
typo: false,
setIds: [setOne, setTwo],
};
const termDataOne = {
term: termOne,
sound: soundOne,
};
const termDataTwo = {
item: termTwo,
sound: soundTwo,
};
const termDataThree = {
term: termThree,
sound: soundThree,
};
const definitionDataOne = {
item: definitionOne,
sound: soundOne,
};
const definitionDataTwo = {
item: definitionTwo,
sound: soundTwo,
};
const definitionDataThree = {
item: definitionThree,
sound: soundThree,
};
const progressDocId = firestore.collection("progress").doc(progressOne);
await progressDocId.set(progressData);
await progressDocId
.collection("terms").doc(`${setOne}__${vocabOne}`).set(termDataOne);
await progressDocId
.collection("terms").doc(`${setOne}__${vocabTwo}`).set(termDataTwo);
await progressDocId
.collection("terms").doc(`${setTwo}__${vocabThree}`).set(termDataThree);
await progressDocId
.collection("definitions").doc(`${setOne}__${vocabOne}`).set(definitionDataOne);
await progressDocId
.collection("definitions").doc(`${setOne}__${vocabTwo}`).set(definitionDataTwo);
await progressDocId
.collection("definitions").doc(`${setTwo}__${vocabThree}`).set(definitionDataThree);
firebase.assertFails(createProgressWithIncorrect(progressOne));
});
/*xit("getGroupMembers returns group members correctly", async () => { /*xit("getGroupMembers returns group members correctly", async () => {
const getGroupMembers = test.wrap(cloudFunctions.getGroupMembers); const getGroupMembers = test.wrap(cloudFunctions.getGroupMembers);