diff --git a/public/tasks/task-nested.md b/public/tasks/task-nested.md new file mode 100644 index 0000000000..6d29f9369f --- /dev/null +++ b/public/tasks/task-nested.md @@ -0,0 +1,5 @@ +# Task - Nested + +Version: 0.0.1 + +Implement functions that work with nested arrays and objects immutably. diff --git a/src/data/questions.json b/src/data/questions.json index 3b19537526..0411f30afe 100644 --- a/src/data/questions.json +++ b/src/data/questions.json @@ -73,7 +73,148 @@ "published": false } ], - "SIMPLE_QUESTIONS_2": [], - "EMPTY_QUESTIONS": [], - "TRIVIA_QUESTIONS": [] + "TRIVIA_QUESTIONS": [ + { + "id": 1, + "name": "Mascot", + "body": "What is the name of the UD Mascot?", + "type": "multiple_choice_question", + "options": ["Bluey", "YoUDee", "Charles the Wonder Dog"], + "expected": "YoUDee", + "points": 7, + "published": false + }, + { + "id": 2, + "name": "Motto", + "body": "What is the University of Delaware's motto?", + "type": "multiple_choice_question", + "options": [ + "Knowledge is the light of the mind", + "Just U Do it", + "Nothing, what's the motto with you?" + ], + "expected": "Knowledge is the light of the mind", + "points": 3, + "published": false + }, + { + "id": 3, + "name": "Goats", + "body": "How many goats are there usually on the Green?", + "type": "multiple_choice_question", + "options": [ + "Zero, why would there be goats on the green?", + "18420", + "Two" + ], + "expected": "Two", + "points": 10, + "published": false + } + ], + "EMPTY_QUESTIONS": [ + { + "id": 1, + "name": "Empty 1", + "body": "This question is not empty, right?", + "type": "multiple_choice_question", + "options": ["correct", "it is", "not"], + "expected": "correct", + "points": 5, + "published": true + }, + { + "id": 2, + "name": "Empty 2", + "body": "", + "type": "multiple_choice_question", + "options": ["this", "one", "is", "not", "empty", "either"], + "expected": "one", + "points": 5, + "published": true + }, + { + "id": 3, + "name": "Empty 3", + "body": "This questions is not empty either!", + "type": "short_answer_question", + "options": [], + "expected": "", + "points": 5, + "published": true + }, + { + "id": 4, + "name": "Empty 4", + "body": "", + "type": "short_answer_question", + "options": [], + "expected": "Even this one is not empty", + "points": 5, + "published": true + }, + { + "id": 5, + "name": "Empty 5 (Actual)", + "body": "", + "type": "short_answer_question", + "options": [], + "expected": "", + "points": 5, + "published": false + } + ], + "SIMPLE_QUESTIONS_2": [ + { + "id": 478, + "name": "Students", + "body": "How many students are taking CISC275 this semester?", + "type": "short_answer_question", + "options": [], + "expected": "90", + "points": 53, + "published": true + }, + { + "id": 1937, + "name": "Importance", + "body": "On a scale of 1 to 10, how important is this quiz for them?", + "type": "short_answer_question", + "options": [], + "expected": "10", + "points": 47, + "published": true + }, + { + "id": 479, + "name": "Sentience", + "body": "Is it technically possible for this quiz to become sentient?", + "type": "short_answer_question", + "options": [], + "expected": "Yes", + "points": 40, + "published": true + }, + { + "id": 777, + "name": "Danger", + "body": "If this quiz became sentient, would it pose a danger to others?", + "type": "short_answer_question", + "options": [], + "expected": "Yes", + "points": 60, + "published": true + }, + { + "id": 1937, + "name": "Listening", + "body": "Is this quiz listening to us right now?", + "type": "short_answer_question", + "options": [], + "expected": "Yes", + "points": 100, + "published": true + } + ] } diff --git a/src/interfaces/answer.ts b/src/interfaces/answer.ts new file mode 100644 index 0000000000..743ee8dff9 --- /dev/null +++ b/src/interfaces/answer.ts @@ -0,0 +1,13 @@ +/*** + * A representation of a students' answer in a quizzing game + */ +export interface Answer { + /** The ID of the question being answered. */ + questionId: number; + /** The text that the student entered for their answer. */ + text: string; + /** Whether or not the student has submitted this answer. */ + submitted: boolean; + /** Whether or not the students' answer matched the expected. */ + correct: boolean; +} diff --git a/src/interfaces/question.ts b/src/interfaces/question.ts index a39431565e..5def48f2f7 100644 --- a/src/interfaces/question.ts +++ b/src/interfaces/question.ts @@ -1,6 +1,7 @@ /** QuestionType influences how a question is asked and what kinds of answers are possible */ export type QuestionType = "multiple_choice_question" | "short_answer_question"; +/** A representation of a Question in a quizzing application */ export interface Question { /** A unique identifier for the question */ id: number; diff --git a/src/nested.test.ts b/src/nested.test.ts new file mode 100644 index 0000000000..7f52bfdf94 --- /dev/null +++ b/src/nested.test.ts @@ -0,0 +1,1246 @@ +import { Question } from "./interfaces/question"; +import { + getPublishedQuestions, + getNonEmptyQuestions, + findQuestion, + removeQuestion, + getNames, + sumPoints, + sumPublishedPoints, + toCSV, + makeAnswers, + publishAll, + sameType, + addNewQuestion, + renameQuestionById, + changeQuestionTypeById, + editOption, + duplicateQuestionInArray, +} from "./nested"; +import testQuestionData from "./data/questions.json"; +import backupQuestionData from "./data/questions.json"; + +const { + BLANK_QUESTIONS, + SIMPLE_QUESTIONS, + TRIVIA_QUESTIONS, + EMPTY_QUESTIONS, + SIMPLE_QUESTIONS_2, +}: Record = + // Typecast the test data that we imported to be a record matching + // strings to the question list + testQuestionData as Record; + +// We have backup versions of the data to make sure all changes are immutable +const { + BLANK_QUESTIONS: BACKUP_BLANK_QUESTIONS, + SIMPLE_QUESTIONS: BACKUP_SIMPLE_QUESTIONS, + TRIVIA_QUESTIONS: BACKUP_TRIVIA_QUESTIONS, + EMPTY_QUESTIONS: BACKUP_EMPTY_QUESTIONS, + SIMPLE_QUESTIONS_2: BACKUP_SIMPLE_QUESTIONS_2, +}: Record = backupQuestionData as Record< + string, + Question[] +>; + +const NEW_BLANK_QUESTION = { + id: 142, + name: "A new question", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: false, +}; + +const NEW_TRIVIA_QUESTION = { + id: 449, + name: "Colors", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + /*body: "The official colors of UD are Blue and ...?", + type: "multiple_choice_question", + options: ["Black, like my soul", "Blue again, we're tricky.", "#FFD200"], + expected: "#FFD200",*/ + points: 1, + published: false, +}; + +//////////////////////////////////////////// +// Actual tests + +describe("Testing the Question[] functions", () => { + ////////////////////////////////// + // getPublishedQuestions + + test("(3 pts) Testing the getPublishedQuestions function", () => { + expect(getPublishedQuestions(BLANK_QUESTIONS)).toEqual([]); + expect(getPublishedQuestions(SIMPLE_QUESTIONS)).toEqual([ + { + id: 1, + name: "Addition", + body: "What is 2+2?", + type: "short_answer_question", + options: [], + expected: "4", + points: 1, + published: true, + }, + { + id: 5, + name: "Colors", + body: "Which of these is a color?", + type: "multiple_choice_question", + options: ["red", "apple", "firetruck"], + expected: "red", + points: 1, + published: true, + }, + ]); + expect(getPublishedQuestions(TRIVIA_QUESTIONS)).toEqual([]); + expect(getPublishedQuestions(SIMPLE_QUESTIONS_2)).toEqual( + BACKUP_SIMPLE_QUESTIONS_2, + ); + expect(getPublishedQuestions(EMPTY_QUESTIONS)).toEqual([ + { + id: 1, + name: "Empty 1", + body: "This question is not empty, right?", + type: "multiple_choice_question", + options: ["correct", "it is", "not"], + expected: "correct", + points: 5, + published: true, + }, + { + id: 2, + name: "Empty 2", + body: "", + type: "multiple_choice_question", + options: ["this", "one", "is", "not", "empty", "either"], + expected: "one", + points: 5, + published: true, + }, + { + id: 3, + name: "Empty 3", + body: "This questions is not empty either!", + type: "short_answer_question", + options: [], + expected: "", + points: 5, + published: true, + }, + { + id: 4, + name: "Empty 4", + body: "", + type: "short_answer_question", + options: [], + expected: "Even this one is not empty", + points: 5, + published: true, + }, + ]); + }); + + test("(3 pts) Testing the getNonEmptyQuestions functions", () => { + expect(getNonEmptyQuestions(BLANK_QUESTIONS)).toEqual([]); + expect(getNonEmptyQuestions(SIMPLE_QUESTIONS)).toEqual( + BACKUP_SIMPLE_QUESTIONS, + ); + expect(getNonEmptyQuestions(TRIVIA_QUESTIONS)).toEqual( + BACKUP_TRIVIA_QUESTIONS, + ); + expect(getNonEmptyQuestions(SIMPLE_QUESTIONS_2)).toEqual( + BACKUP_SIMPLE_QUESTIONS_2, + ); + expect(getNonEmptyQuestions(EMPTY_QUESTIONS)).toEqual([ + { + id: 1, + name: "Empty 1", + body: "This question is not empty, right?", + type: "multiple_choice_question", + options: ["correct", "it is", "not"], + expected: "correct", + points: 5, + published: true, + }, + { + id: 2, + name: "Empty 2", + body: "", + type: "multiple_choice_question", + options: ["this", "one", "is", "not", "empty", "either"], + expected: "one", + points: 5, + published: true, + }, + { + id: 3, + name: "Empty 3", + body: "This questions is not empty either!", + type: "short_answer_question", + options: [], + expected: "", + points: 5, + published: true, + }, + { + id: 4, + name: "Empty 4", + body: "", + type: "short_answer_question", + options: [], + expected: "Even this one is not empty", + points: 5, + published: true, + }, + ]); + }); + + test("(3 pts) Testing the findQuestion function", () => { + expect(findQuestion(BLANK_QUESTIONS, 1)).toEqual(BLANK_QUESTIONS[0]); + expect(findQuestion(BLANK_QUESTIONS, 47)).toEqual(BLANK_QUESTIONS[1]); + expect(findQuestion(BLANK_QUESTIONS, 2)).toEqual(BLANK_QUESTIONS[2]); + expect(findQuestion(BLANK_QUESTIONS, 3)).toEqual(null); + expect(findQuestion(SIMPLE_QUESTIONS, 1)).toEqual(SIMPLE_QUESTIONS[0]); + expect(findQuestion(SIMPLE_QUESTIONS, 2)).toEqual(SIMPLE_QUESTIONS[1]); + expect(findQuestion(SIMPLE_QUESTIONS, 5)).toEqual(SIMPLE_QUESTIONS[2]); + expect(findQuestion(SIMPLE_QUESTIONS, 9)).toEqual(SIMPLE_QUESTIONS[3]); + expect(findQuestion(SIMPLE_QUESTIONS, 6)).toEqual(null); + expect(findQuestion(SIMPLE_QUESTIONS_2, 478)).toEqual( + SIMPLE_QUESTIONS_2[0], + ); + expect(findQuestion([], 0)).toEqual(null); + }); + + test("(3 pts) Testing the removeQuestion", () => { + expect(removeQuestion(BLANK_QUESTIONS, 1)).toEqual([ + { + id: 47, + name: "My New Question", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 2, + name: "Question 2", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: false, + }, + ]); + expect(removeQuestion(BLANK_QUESTIONS, 47)).toEqual([ + { + id: 1, + name: "Question 1", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 2, + name: "Question 2", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: false, + }, + ]); + expect(removeQuestion(BLANK_QUESTIONS, 2)).toEqual([ + { + id: 1, + name: "Question 1", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 47, + name: "My New Question", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + ]); + expect(removeQuestion(SIMPLE_QUESTIONS, 9)).toEqual([ + { + id: 1, + name: "Addition", + body: "What is 2+2?", + type: "short_answer_question", + options: [], + expected: "4", + points: 1, + published: true, + }, + { + id: 2, + name: "Letters", + body: "What is the last letter of the English alphabet?", + type: "short_answer_question", + options: [], + expected: "Z", + points: 1, + published: false, + }, + { + id: 5, + name: "Colors", + body: "Which of these is a color?", + type: "multiple_choice_question", + options: ["red", "apple", "firetruck"], + expected: "red", + points: 1, + published: true, + }, + ]); + expect(removeQuestion(SIMPLE_QUESTIONS, 5)).toEqual([ + { + id: 1, + name: "Addition", + body: "What is 2+2?", + type: "short_answer_question", + options: [], + expected: "4", + points: 1, + published: true, + }, + { + id: 2, + name: "Letters", + body: "What is the last letter of the English alphabet?", + type: "short_answer_question", + options: [], + expected: "Z", + points: 1, + published: false, + }, + { + id: 9, + name: "Shapes", + body: "What shape can you make with one line?", + type: "multiple_choice_question", + options: ["square", "triangle", "circle"], + expected: "circle", + points: 2, + published: false, + }, + ]); + }); + + test("(3 pts) Testing the getNames function", () => { + expect(getNames(BLANK_QUESTIONS)).toEqual([ + "Question 1", + "My New Question", + "Question 2", + ]); + expect(getNames(SIMPLE_QUESTIONS)).toEqual([ + "Addition", + "Letters", + "Colors", + "Shapes", + ]); + expect(getNames(TRIVIA_QUESTIONS)).toEqual([ + "Mascot", + "Motto", + "Goats", + ]); + expect(getNames(SIMPLE_QUESTIONS_2)).toEqual([ + "Students", + "Importance", + "Sentience", + "Danger", + "Listening", + ]); + expect(getNames(EMPTY_QUESTIONS)).toEqual([ + "Empty 1", + "Empty 2", + "Empty 3", + "Empty 4", + "Empty 5 (Actual)", + ]); + }); + + test("(3 pts) Testing the sumPoints function", () => { + expect(sumPoints(BLANK_QUESTIONS)).toEqual(3); + expect(sumPoints(SIMPLE_QUESTIONS)).toEqual(5); + expect(sumPoints(TRIVIA_QUESTIONS)).toEqual(20); + expect(sumPoints(EMPTY_QUESTIONS)).toEqual(25); + expect(sumPoints(SIMPLE_QUESTIONS_2)).toEqual(300); + }); + + test("(3 pts) Testing the sumPublishedPoints function", () => { + expect(sumPublishedPoints(BLANK_QUESTIONS)).toEqual(0); + expect(sumPublishedPoints(SIMPLE_QUESTIONS)).toEqual(2); + expect(sumPublishedPoints(TRIVIA_QUESTIONS)).toEqual(0); + expect(sumPublishedPoints(EMPTY_QUESTIONS)).toEqual(20); + expect(sumPublishedPoints(SIMPLE_QUESTIONS_2)).toEqual(300); + }); + + test("(3 pts) Testing the toCSV function", () => { + expect(toCSV(BLANK_QUESTIONS)).toEqual(`id,name,options,points,published +1,Question 1,0,1,false +47,My New Question,0,1,false +2,Question 2,0,1,false`); + expect(toCSV(SIMPLE_QUESTIONS)) + .toEqual(`id,name,options,points,published +1,Addition,0,1,true +2,Letters,0,1,false +5,Colors,3,1,true +9,Shapes,3,2,false`); + expect(toCSV(TRIVIA_QUESTIONS)) + .toEqual(`id,name,options,points,published +1,Mascot,3,7,false +2,Motto,3,3,false +3,Goats,3,10,false`); + expect(toCSV(EMPTY_QUESTIONS)).toEqual(`id,name,options,points,published +1,Empty 1,3,5,true +2,Empty 2,6,5,true +3,Empty 3,0,5,true +4,Empty 4,0,5,true +5,Empty 5 (Actual),0,5,false`); + expect(toCSV(SIMPLE_QUESTIONS_2)) + .toEqual(`id,name,options,points,published +478,Students,0,53,true +1937,Importance,0,47,true +479,Sentience,0,40,true +777,Danger,0,60,true +1937,Listening,0,100,true`); + }); + + test("(3 pts) Testing the makeAnswers function", () => { + expect(makeAnswers(BLANK_QUESTIONS)).toEqual([ + { questionId: 1, correct: false, text: "", submitted: false }, + { questionId: 47, correct: false, text: "", submitted: false }, + { questionId: 2, correct: false, text: "", submitted: false }, + ]); + expect(makeAnswers(SIMPLE_QUESTIONS)).toEqual([ + { questionId: 1, correct: false, text: "", submitted: false }, + { questionId: 2, correct: false, text: "", submitted: false }, + { questionId: 5, correct: false, text: "", submitted: false }, + { questionId: 9, correct: false, text: "", submitted: false }, + ]); + expect(makeAnswers(TRIVIA_QUESTIONS)).toEqual([ + { questionId: 1, correct: false, text: "", submitted: false }, + { questionId: 2, correct: false, text: "", submitted: false }, + { questionId: 3, correct: false, text: "", submitted: false }, + ]); + expect(makeAnswers(SIMPLE_QUESTIONS_2)).toEqual([ + { questionId: 478, correct: false, text: "", submitted: false }, + { questionId: 1937, correct: false, text: "", submitted: false }, + { questionId: 479, correct: false, text: "", submitted: false }, + { questionId: 777, correct: false, text: "", submitted: false }, + { questionId: 1937, correct: false, text: "", submitted: false }, + ]); + expect(makeAnswers(EMPTY_QUESTIONS)).toEqual([ + { questionId: 1, correct: false, text: "", submitted: false }, + { questionId: 2, correct: false, text: "", submitted: false }, + { questionId: 3, correct: false, text: "", submitted: false }, + { questionId: 4, correct: false, text: "", submitted: false }, + { questionId: 5, correct: false, text: "", submitted: false }, + ]); + }); + + test("(3 pts) Testing the publishAll function", () => { + expect(publishAll(BLANK_QUESTIONS)).toEqual([ + { + id: 1, + name: "Question 1", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: true, + }, + { + id: 47, + name: "My New Question", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: true, + }, + { + id: 2, + name: "Question 2", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: true, + }, + ]); + expect(publishAll(SIMPLE_QUESTIONS)).toEqual([ + { + id: 1, + name: "Addition", + body: "What is 2+2?", + type: "short_answer_question", + options: [], + expected: "4", + points: 1, + published: true, + }, + { + id: 2, + name: "Letters", + body: "What is the last letter of the English alphabet?", + type: "short_answer_question", + options: [], + expected: "Z", + points: 1, + published: true, + }, + { + id: 5, + name: "Colors", + body: "Which of these is a color?", + type: "multiple_choice_question", + options: ["red", "apple", "firetruck"], + expected: "red", + points: 1, + published: true, + }, + { + id: 9, + name: "Shapes", + body: "What shape can you make with one line?", + type: "multiple_choice_question", + options: ["square", "triangle", "circle"], + expected: "circle", + points: 2, + published: true, + }, + ]); + expect(publishAll(TRIVIA_QUESTIONS)).toEqual([ + { + id: 1, + name: "Mascot", + body: "What is the name of the UD Mascot?", + type: "multiple_choice_question", + options: ["Bluey", "YoUDee", "Charles the Wonder Dog"], + expected: "YoUDee", + points: 7, + published: true, + }, + { + id: 2, + name: "Motto", + body: "What is the University of Delaware's motto?", + type: "multiple_choice_question", + options: [ + "Knowledge is the light of the mind", + "Just U Do it", + "Nothing, what's the motto with you?", + ], + expected: "Knowledge is the light of the mind", + points: 3, + published: true, + }, + { + id: 3, + name: "Goats", + body: "How many goats are there usually on the Green?", + type: "multiple_choice_question", + options: [ + "Zero, why would there be goats on the green?", + "18420", + "Two", + ], + expected: "Two", + points: 10, + published: true, + }, + ]); + expect(publishAll(EMPTY_QUESTIONS)).toEqual([ + { + id: 1, + name: "Empty 1", + body: "This question is not empty, right?", + type: "multiple_choice_question", + options: ["correct", "it is", "not"], + expected: "correct", + points: 5, + published: true, + }, + { + id: 2, + name: "Empty 2", + body: "", + type: "multiple_choice_question", + options: ["this", "one", "is", "not", "empty", "either"], + expected: "one", + points: 5, + published: true, + }, + { + id: 3, + name: "Empty 3", + body: "This questions is not empty either!", + type: "short_answer_question", + options: [], + expected: "", + points: 5, + published: true, + }, + { + id: 4, + name: "Empty 4", + body: "", + type: "short_answer_question", + options: [], + expected: "Even this one is not empty", + points: 5, + published: true, + }, + { + id: 5, + name: "Empty 5 (Actual)", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 5, + published: true, + }, + ]); + expect(publishAll(SIMPLE_QUESTIONS_2)).toEqual(SIMPLE_QUESTIONS_2); + }); + + test("(3 pts) Testing the sameType function", () => { + expect(sameType([])).toEqual(true); + expect(sameType(BLANK_QUESTIONS)).toEqual(false); + expect(sameType(SIMPLE_QUESTIONS)).toEqual(false); + expect(sameType(TRIVIA_QUESTIONS)).toEqual(true); + expect(sameType(EMPTY_QUESTIONS)).toEqual(false); + expect(sameType(SIMPLE_QUESTIONS_2)).toEqual(true); + }); + + test("(3 pts) Testing the addNewQuestion function", () => { + expect( + addNewQuestion([], 142, "A new question", "short_answer_question"), + ).toEqual([NEW_BLANK_QUESTION]); + expect( + addNewQuestion( + BLANK_QUESTIONS, + 142, + "A new question", + "short_answer_question", + ), + ).toEqual([...BLANK_QUESTIONS, NEW_BLANK_QUESTION]); + expect( + addNewQuestion( + TRIVIA_QUESTIONS, + 449, + "Colors", + "multiple_choice_question", + ), + ).toEqual([...TRIVIA_QUESTIONS, NEW_TRIVIA_QUESTION]); + }); + + test("(3 pts) Testing the renameQuestionById function", () => { + expect(renameQuestionById(BLANK_QUESTIONS, 1, "New Name")).toEqual([ + { + id: 1, + name: "New Name", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 47, + name: "My New Question", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 2, + name: "Question 2", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: false, + }, + ]); + expect(renameQuestionById(BLANK_QUESTIONS, 47, "Another Name")).toEqual( + [ + { + id: 1, + name: "Question 1", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 47, + name: "Another Name", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 2, + name: "Question 2", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: false, + }, + ], + ); + expect(renameQuestionById(SIMPLE_QUESTIONS, 5, "Colours")).toEqual([ + { + id: 1, + name: "Addition", + body: "What is 2+2?", + type: "short_answer_question", + options: [], + expected: "4", + points: 1, + published: true, + }, + { + id: 2, + name: "Letters", + body: "What is the last letter of the English alphabet?", + type: "short_answer_question", + options: [], + expected: "Z", + points: 1, + published: false, + }, + { + id: 5, + name: "Colours", + body: "Which of these is a color?", + type: "multiple_choice_question", + options: ["red", "apple", "firetruck"], + expected: "red", + points: 1, + published: true, + }, + { + id: 9, + name: "Shapes", + body: "What shape can you make with one line?", + type: "multiple_choice_question", + options: ["square", "triangle", "circle"], + expected: "circle", + points: 2, + published: false, + }, + ]); + }); + + test("(3 pts) Test the changeQuestionTypeById function", () => { + expect( + changeQuestionTypeById( + BLANK_QUESTIONS, + 1, + "multiple_choice_question", + ), + ).toEqual(BLANK_QUESTIONS); + expect( + changeQuestionTypeById(BLANK_QUESTIONS, 1, "short_answer_question"), + ).toEqual([ + { + id: 1, + name: "Question 1", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 47, + name: "My New Question", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 2, + name: "Question 2", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: false, + }, + ]); + expect( + changeQuestionTypeById( + BLANK_QUESTIONS, + 47, + "short_answer_question", + ), + ).toEqual([ + { + id: 1, + name: "Question 1", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 47, + name: "My New Question", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 2, + name: "Question 2", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: false, + }, + ]); + expect( + changeQuestionTypeById( + TRIVIA_QUESTIONS, + 3, + "short_answer_question", + ), + ).toEqual([ + { + id: 1, + name: "Mascot", + body: "What is the name of the UD Mascot?", + type: "multiple_choice_question", + options: ["Bluey", "YoUDee", "Charles the Wonder Dog"], + expected: "YoUDee", + points: 7, + published: false, + }, + { + id: 2, + name: "Motto", + body: "What is the University of Delaware's motto?", + type: "multiple_choice_question", + options: [ + "Knowledge is the light of the mind", + "Just U Do it", + "Nothing, what's the motto with you?", + ], + expected: "Knowledge is the light of the mind", + points: 3, + published: false, + }, + { + id: 3, + name: "Goats", + body: "How many goats are there usually on the Green?", + type: "short_answer_question", + options: [], + expected: "Two", + points: 10, + published: false, + }, + ]); + }); + + test("(3 pts) Testing the editOption function", () => { + expect(editOption(BLANK_QUESTIONS, 1, -1, "NEW OPTION")).toEqual([ + { + id: 1, + name: "Question 1", + body: "", + type: "multiple_choice_question", + options: ["NEW OPTION"], + expected: "", + points: 1, + published: false, + }, + { + id: 47, + name: "My New Question", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 2, + name: "Question 2", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: false, + }, + ]); + expect(editOption(BLANK_QUESTIONS, 47, -1, "Another option")).toEqual([ + { + id: 1, + name: "Question 1", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 47, + name: "My New Question", + body: "", + type: "multiple_choice_question", + options: ["Another option"], + expected: "", + points: 1, + published: false, + }, + { + id: 2, + name: "Question 2", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: false, + }, + ]); + expect(editOption(SIMPLE_QUESTIONS, 5, -1, "newspaper")).toEqual([ + { + id: 1, + name: "Addition", + body: "What is 2+2?", + type: "short_answer_question", + options: [], + expected: "4", + points: 1, + published: true, + }, + { + id: 2, + name: "Letters", + body: "What is the last letter of the English alphabet?", + type: "short_answer_question", + options: [], + expected: "Z", + points: 1, + published: false, + }, + { + id: 5, + name: "Colors", + body: "Which of these is a color?", + type: "multiple_choice_question", + options: ["red", "apple", "firetruck", "newspaper"], + expected: "red", + points: 1, + published: true, + }, + { + id: 9, + name: "Shapes", + body: "What shape can you make with one line?", + type: "multiple_choice_question", + options: ["square", "triangle", "circle"], + expected: "circle", + points: 2, + published: false, + }, + ]); + expect(editOption(SIMPLE_QUESTIONS, 5, 0, "newspaper")).toEqual([ + { + id: 1, + name: "Addition", + body: "What is 2+2?", + type: "short_answer_question", + options: [], + expected: "4", + points: 1, + published: true, + }, + { + id: 2, + name: "Letters", + body: "What is the last letter of the English alphabet?", + type: "short_answer_question", + options: [], + expected: "Z", + points: 1, + published: false, + }, + { + id: 5, + name: "Colors", + body: "Which of these is a color?", + type: "multiple_choice_question", + options: ["newspaper", "apple", "firetruck"], + expected: "red", + points: 1, + published: true, + }, + { + id: 9, + name: "Shapes", + body: "What shape can you make with one line?", + type: "multiple_choice_question", + options: ["square", "triangle", "circle"], + expected: "circle", + points: 2, + published: false, + }, + ]); + + expect(editOption(SIMPLE_QUESTIONS, 5, 2, "newspaper")).toEqual([ + { + id: 1, + name: "Addition", + body: "What is 2+2?", + type: "short_answer_question", + options: [], + expected: "4", + points: 1, + published: true, + }, + { + id: 2, + name: "Letters", + body: "What is the last letter of the English alphabet?", + type: "short_answer_question", + options: [], + expected: "Z", + points: 1, + published: false, + }, + { + id: 5, + name: "Colors", + body: "Which of these is a color?", + type: "multiple_choice_question", + options: ["red", "apple", "newspaper"], + expected: "red", + points: 1, + published: true, + }, + { + id: 9, + name: "Shapes", + body: "What shape can you make with one line?", + type: "multiple_choice_question", + options: ["square", "triangle", "circle"], + expected: "circle", + points: 2, + published: false, + }, + ]); + }); + + test("(3 pts) Testing the duplicateQuestionInArray function", () => { + expect(duplicateQuestionInArray(BLANK_QUESTIONS, 1, 27)).toEqual([ + { + id: 1, + name: "Question 1", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 27, + name: "Copy of Question 1", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 47, + name: "My New Question", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 2, + name: "Question 2", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: false, + }, + ]); + expect(duplicateQuestionInArray(BLANK_QUESTIONS, 47, 19)).toEqual([ + { + id: 1, + name: "Question 1", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 47, + name: "My New Question", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 19, + name: "Copy of My New Question", + body: "", + type: "multiple_choice_question", + options: [], + expected: "", + points: 1, + published: false, + }, + { + id: 2, + name: "Question 2", + body: "", + type: "short_answer_question", + options: [], + expected: "", + points: 1, + published: false, + }, + ]); + expect(duplicateQuestionInArray(TRIVIA_QUESTIONS, 3, 111)).toEqual([ + { + id: 1, + name: "Mascot", + body: "What is the name of the UD Mascot?", + type: "multiple_choice_question", + options: ["Bluey", "YoUDee", "Charles the Wonder Dog"], + expected: "YoUDee", + points: 7, + published: false, + }, + { + id: 2, + name: "Motto", + body: "What is the University of Delaware's motto?", + type: "multiple_choice_question", + options: [ + "Knowledge is the light of the mind", + "Just U Do it", + "Nothing, what's the motto with you?", + ], + expected: "Knowledge is the light of the mind", + points: 3, + published: false, + }, + { + id: 3, + name: "Goats", + body: "How many goats are there usually on the Green?", + type: "multiple_choice_question", + options: [ + "Zero, why would there be goats on the green?", + "18420", + "Two", + ], + expected: "Two", + points: 10, + published: false, + }, + { + id: 111, + name: "Copy of Goats", + body: "How many goats are there usually on the Green?", + type: "multiple_choice_question", + options: [ + "Zero, why would there be goats on the green?", + "18420", + "Two", + ], + expected: "Two", + points: 10, + published: false, + }, + ]); + }); + + afterEach(() => { + expect(BLANK_QUESTIONS).toEqual(BACKUP_BLANK_QUESTIONS); + expect(SIMPLE_QUESTIONS).toEqual(BACKUP_SIMPLE_QUESTIONS); + expect(TRIVIA_QUESTIONS).toEqual(BACKUP_TRIVIA_QUESTIONS); + expect(SIMPLE_QUESTIONS_2).toEqual(BACKUP_SIMPLE_QUESTIONS_2); + expect(EMPTY_QUESTIONS).toEqual(BACKUP_EMPTY_QUESTIONS); + }); +}); diff --git a/src/nested.ts b/src/nested.ts new file mode 100644 index 0000000000..be2dda695a --- /dev/null +++ b/src/nested.ts @@ -0,0 +1,267 @@ +import { Answer } from "./interfaces/answer"; +import { Question, QuestionType } from "./interfaces/question"; +import { duplicateQuestion, makeBlankQuestion } from "./objects"; + +/** + * Consumes an array of questions and returns a new array with only the questions + * that are `published`. + */ +export function getPublishedQuestions(questions: Question[]): Question[] { + const published = questions.filter((p: Question): boolean => p.published); + return published; +} + +/** + * Consumes an array of questions and returns a new array of only the questions that are + * considered "non-empty". An empty question has an empty string for its `body` and + * `expected`, and an empty array for its `options`. + */ +export function getNonEmptyQuestions(questions: Question[]): Question[] { + const nonEmpty = questions.filter( + (q: Question): boolean => + q.body !== "" || q.expected !== "" || q.options.length !== 0, + ); + return nonEmpty; +} + +/*** + * Consumes an array of questions and returns the question with the given `id`. If the + * question is not found, return `null` instead. + */ +export function findQuestion( + questions: Question[], + id: number, +): Question | null { + const _question = questions.filter((q: Question): boolean => q.id === id); + if (_question.length === 0) { + return null; + } + return _question[0]; +} + +/** + * Consumes an array of questions and returns a new array that does not contain the question + * with the given `id`. + */ +export function removeQuestion(questions: Question[], id: number): Question[] { + const _question = questions.filter((q: Question): boolean => q.id !== id); + + return _question; +} + +/*** + * Consumes an array of questions and returns a new array containing just the names of the + * questions, as an array. + */ +export function getNames(questions: Question[]): string[] { + const names = questions.map((q: Question): string => q.name); + return names; +} + +/*** + * Consumes an array of questions and returns the sum total of all their points added together. + */ +export function sumPoints(questions: Question[]): number { + const totalPoints = questions.reduce( + (currentSum: number, p: Question) => currentSum + p.points, + 0, + ); + return totalPoints; +} + +/*** + * Consumes an array of questions and returns the sum total of the PUBLISHED questions. + */ +export function sumPublishedPoints(questions: Question[]): number { + const totalPublished: number = questions.reduce( + (count: number, p: Question) => { + if (p.published) { + return count + p.points; + } + return count; + }, + 0, + ); + return totalPublished; +} + +/*** + * Consumes an array of questions, and produces a Comma-Separated Value (CSV) string representation. + * A CSV is a type of file frequently used to share tabular data; we will use a single string + * to represent the entire file. The first line of the file is the headers "id", "name", "options", + * "points", and "published". The following line contains the value for each question, separated by + * commas. For the `options` field, use the NUMBER of options. + * + * Here is an example of what this will look like (do not include the border). + *` +id,name,options,points,published +1,Addition,0,1,true +2,Letters,0,1,false +5,Colors,3,1,true +9,Shapes,3,2,false +` * + * Check the unit tests for more examples! + */ +export function toCSV(questions: Question[]): string { + const headers: string = "id,name,options,points,published"; + const questionCSV = questions + .map( + (q: Question): string => + `${q.id},${q.name},${q.options.length > 0 ? `${q.options.length}` : "0"},${q.points},${q.published ? "true" : "false"}`, + ) + .join("\n"); + return headers + "\n" + questionCSV; +} + +/** + * Consumes an array of Questions and produces a corresponding array of + * Answers. Each Question gets its own Answer, copying over the `id` as the `questionId`, + * making the `text` an empty string, and using false for both `submitted` and `correct`. + */ +export function makeAnswers(questions: Question[]): Answer[] { + const answers = questions.map( + (q: Question): Answer => ({ + questionId: q.id, + text: "", + submitted: false, + correct: false, + }), + ); + return answers; +} + +/*** + * Consumes an array of Questions and produces a new array of questions, where + * each question is now published, regardless of its previous published status. + */ +export function publishAll(questions: Question[]): Question[] { + const publish = questions.map( + (q: Question): Question => ({ ...q, published: true }), + ); + return publish; +} + +/*** + * Consumes an array of Questions and produces whether or not all the questions + * are the same type. They can be any type, as long as they are all the SAME type. + */ +export function sameType(questions: Question[]): boolean { + if (questions.every((v) => v.type === "multiple_choice_question")) { + return true; + } + if (questions.every((v) => v.type === "short_answer_question")) { + return true; + } + return false; +} + +/*** + * Consumes an array of Questions and produces a new array of the same Questions, + * except that a blank question has been added onto the end. Reuse the `makeBlankQuestion` + * you defined in the `objects.ts` file. + */ +export function addNewQuestion( + questions: Question[], + id: number, + name: string, + type: QuestionType, +): Question[] { + return [...questions, makeBlankQuestion(id, name, type)]; +} + +/*** + * Consumes an array of Questions and produces a new array of Questions, where all + * the Questions are the same EXCEPT for the one with the given `targetId`. That + * Question should be the same EXCEPT that its name should now be `newName`. + */ +export function renameQuestionById( + questions: Question[], + targetId: number, + newName: string, +): Question[] { + const _questions = questions.map( + (q: Question): Question => + q.id === targetId ? { ...q, name: newName } : q, + ); + return _questions; +} + +/*** + * Consumes an array of Questions and produces a new array of Questions, where all + * the Questions are the same EXCEPT for the one with the given `targetId`. That + * Question should be the same EXCEPT that its `type` should now be the `newQuestionType` + * AND if the `newQuestionType` is no longer "multiple_choice_question" than the `options` + * must be set to an empty list. + */ +export function changeQuestionTypeById( + questions: Question[], + targetId: number, + newQuestionType: QuestionType, +): Question[] { + const _questions = questions.map( + (q: Question): Question => + q.id === targetId ? + newQuestionType !== "multiple_choice_question" ? + { ...q, type: newQuestionType, options: [] } + : { ...q, type: newQuestionType } + : q, + ); + return _questions; +} + +/** + * Consumes an array of Questions and produces a new array of Questions, where all + * the Questions are the same EXCEPT for the one with the given `targetId`. That + * Question should be the same EXCEPT that its `option` array should have a new element. + * If the `targetOptionIndex` is -1, the `newOption` should be added to the end of the list. + * Otherwise, it should *replace* the existing element at the `targetOptionIndex`. + * + * Remember, if a function starts getting too complicated, think about how a helper function + * can make it simpler! Break down complicated tasks into little pieces. + */ +export function editOption( + questions: Question[], + targetId: number, + targetOptionIndex: number, + newOption: string, +): Question[] { + const updateOptions = (options: string[]): string[] => { + if (targetOptionIndex === -1) { + return [...options, newOption]; + } + + return options.map((opt, index) => + index === targetOptionIndex ? newOption : opt, + ); + }; + + return questions.map((q) => { + if (q.id !== targetId) return q; + + return { + ...q, + options: updateOptions(q.options), + }; + }); +} + +/*** + * Consumes an array of questions, and produces a new array based on the original array. + * The only difference is that the question with id `targetId` should now be duplicated, with + * the duplicate inserted directly after the original question. Use the `duplicateQuestion` + * function you defined previously; the `newId` is the parameter to use for the duplicate's ID. + */ +export function duplicateQuestionInArray( + questions: Question[], + targetId: number, + newId: number, +): Question[] { + const dupe = questions.reduce((count: Question[], q: Question) => { + count.push(q); + if (q.id === targetId) { + count.push(duplicateQuestion(newId, q)); + } + return count; + }, []); + return dupe; +} diff --git a/src/objects.test.ts b/src/objects.test.ts index 4d3117405d..3258412e56 100644 --- a/src/objects.test.ts +++ b/src/objects.test.ts @@ -48,7 +48,7 @@ describe("Testing the object functions", () => { ////////////////////////////////// // makeBlankQuestion - test("(3 pts) Testing the makeBlankQuestion function", () => { + test("Testing the makeBlankQuestion function", () => { expect( makeBlankQuestion(1, "Question 1", "multiple_choice_question"), ).toEqual(BLANK_QUESTIONS[0]); @@ -66,7 +66,7 @@ describe("Testing the object functions", () => { /////////////////////////////////// // isCorrect - test("(3 pts) Testing the isCorrect function", () => { + test("Testing the isCorrect function", () => { expect(isCorrect(ADDITION_QUESTION, "4")).toEqual(true); expect(isCorrect(ADDITION_QUESTION, "2")).toEqual(false); expect(isCorrect(ADDITION_QUESTION, " 4\n")).toEqual(true); @@ -85,7 +85,7 @@ describe("Testing the object functions", () => { /////////////////////////////////// // isValid - test("(3 pts) Testing the isValid function", () => { + test("Testing the isValid function", () => { expect(isValid(ADDITION_QUESTION, "4")).toEqual(true); expect(isValid(ADDITION_QUESTION, "2")).toEqual(true); expect(isValid(ADDITION_QUESTION, " 4\n")).toEqual(true); @@ -108,7 +108,7 @@ describe("Testing the object functions", () => { /////////////////////////////////// // toShortForm - test("(3 pts) Testing the toShortForm function", () => { + test("Testing the toShortForm function", () => { expect(toShortForm(ADDITION_QUESTION)).toEqual("1: Addition"); expect(toShortForm(LETTER_QUESTION)).toEqual("2: Letters"); expect(toShortForm(COLOR_QUESTION)).toEqual("5: Colors"); @@ -118,7 +118,7 @@ describe("Testing the object functions", () => { /////////////////////////////////// // toMarkdown - test("(3 pts) Testing the toMarkdown function", () => { + test("Testing the toMarkdown function", () => { expect(toMarkdown(ADDITION_QUESTION)).toEqual(`# Addition What is 2+2?`); expect(toMarkdown(LETTER_QUESTION)).toEqual(`# Letters @@ -145,7 +145,7 @@ What shape can you make with one line? /////////////////////////////////// // renameQuestion - test("(3 pts) Testing the renameQuestion function", () => { + test("Testing the renameQuestion function", () => { expect( renameQuestion(ADDITION_QUESTION, "My Addition Question"), ).toEqual({ @@ -174,7 +174,7 @@ What shape can you make with one line? /////////////////////////////////// // publishQuestion - test("(3 pts) Testing the publishQuestion function", () => { + test("Testing the publishQuestion function", () => { expect(publishQuestion(ADDITION_QUESTION)).toEqual({ id: 1, name: "Addition", @@ -209,7 +209,7 @@ What shape can you make with one line? /////////////////////////////////// // duplicateQuestion - test("(3 pts) Testing the duplicateQuestion function", () => { + test("Testing the duplicateQuestion function", () => { expect(duplicateQuestion(9, ADDITION_QUESTION)).toEqual({ id: 9, name: "Copy of Addition", @@ -234,7 +234,7 @@ What shape can you make with one line? /////////////////////////////////// // addOption - test("(3 pts) Testing the addOption function", () => { + test("Testing the addOption function", () => { expect(addOption(SHAPE_QUESTION, "heptagon")).toEqual({ id: 9, name: "Shapes", @@ -259,7 +259,7 @@ What shape can you make with one line? /////////////////////////////////// // mergeQuestion - test("(3 pts) Testing the mergeQuestion function", () => { + test("Testing the mergeQuestion function", () => { expect( mergeQuestion( 192,