From 986b28ac0afb41e603602013f71e6ef6e257c722 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 2 Feb 2022 13:12:40 -0500 Subject: [PATCH 001/135] First stab at questions --- public/tasks/task-objects.md | 5 + src/data/questions.json | 79 ++++++++++ src/objects.test.ts | 295 +++++++++++++++++++++++++++++++++++ src/objects.ts | 141 +++++++++++++++++ 4 files changed, 520 insertions(+) create mode 100644 public/tasks/task-objects.md create mode 100644 src/data/questions.json create mode 100644 src/objects.test.ts create mode 100644 src/objects.ts diff --git a/public/tasks/task-objects.md b/public/tasks/task-objects.md new file mode 100644 index 0000000000..480889da0d --- /dev/null +++ b/public/tasks/task-objects.md @@ -0,0 +1,5 @@ +# Task - Objects + +Version: 0.0.1 + +Implement functions that work with objects immutably. diff --git a/src/data/questions.json b/src/data/questions.json new file mode 100644 index 0000000000..3b19537526 --- /dev/null +++ b/src/data/questions.json @@ -0,0 +1,79 @@ +{ + "BLANK_QUESTIONS": [ + { + "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": 2, + "name": "Question 2", + "body": "", + "type": "short_answer_question", + "options": [], + "expected": "", + "points": 1, + "published": false + } + ], + "SIMPLE_QUESTIONS": [ + { + "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 + }, + { + "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 + } + ], + "SIMPLE_QUESTIONS_2": [], + "EMPTY_QUESTIONS": [], + "TRIVIA_QUESTIONS": [] +} diff --git a/src/objects.test.ts b/src/objects.test.ts new file mode 100644 index 0000000000..bcff7ab176 --- /dev/null +++ b/src/objects.test.ts @@ -0,0 +1,295 @@ +import { + makeBlankQuestion, + isCorrect, + Question, + isValid, + toShortForm, + toMarkdown, + duplicateQuestion, + renameQuestion, + publishQuestion, + addOption, + mergeQuestion +} from "./objects"; +import testQuestionData from "./data/questions.json"; +import backupQuestionData from "./data/questions.json"; + +//////////////////////////////////////////// +// Setting up the test data + +const { BLANK_QUESTIONS, SIMPLE_QUESTIONS }: 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 +}: Record = backupQuestionData as Record< + string, + Question[] +>; + +// Unpack the list of simple questions into convenient constants +const [ADDITION_QUESTION, LETTER_QUESTION, COLOR_QUESTION, SHAPE_QUESTION] = + SIMPLE_QUESTIONS; +const [ + BACKUP_ADDITION_QUESTION, + BACKUP_LETTER_QUESTION, + BACKUP_COLOR_QUESTION, + BACKUP_SHAPE_QUESTION +] = BACKUP_SIMPLE_QUESTIONS; + +//////////////////////////////////////////// +// Actual tests + +describe("Testing the object functions", () => { + ////////////////////////////////// + // makeBlankQuestion + + test("Testing the makeBlankQuestion function", () => { + expect( + makeBlankQuestion(1, "Question 1", "multiple_choice_question") + ).toEqual(BLANK_QUESTIONS[0]); + expect( + makeBlankQuestion(47, "My New Question", "multiple_choice_question") + ).toEqual(BLANK_QUESTIONS[1]); + expect( + makeBlankQuestion(2, "Question 2", "short_answer_question") + ).toEqual(BLANK_QUESTIONS[2]); + }); + + /////////////////////////////////// + // isCorrect + 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); + expect(isCorrect(LETTER_QUESTION, "Z")).toEqual(true); + expect(isCorrect(LETTER_QUESTION, "z")).toEqual(true); + expect(isCorrect(LETTER_QUESTION, "4")).toEqual(false); + expect(isCorrect(LETTER_QUESTION, "0")).toEqual(false); + expect(isCorrect(LETTER_QUESTION, "zed")).toEqual(false); + expect(isCorrect(COLOR_QUESTION, "red")).toEqual(true); + expect(isCorrect(COLOR_QUESTION, "apple")).toEqual(false); + expect(isCorrect(COLOR_QUESTION, "firetruck")).toEqual(false); + expect(isCorrect(SHAPE_QUESTION, "square")).toEqual(false); + expect(isCorrect(SHAPE_QUESTION, "triangle")).toEqual(false); + expect(isCorrect(SHAPE_QUESTION, "circle")).toEqual(true); + }); + + /////////////////////////////////// + // isValid + 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); + expect(isValid(LETTER_QUESTION, "Z")).toEqual(true); + expect(isValid(LETTER_QUESTION, "z")).toEqual(true); + expect(isValid(LETTER_QUESTION, "4")).toEqual(true); + expect(isValid(LETTER_QUESTION, "0")).toEqual(true); + expect(isValid(LETTER_QUESTION, "zed")).toEqual(true); + expect(isValid(COLOR_QUESTION, "red")).toEqual(true); + expect(isValid(COLOR_QUESTION, "apple")).toEqual(true); + expect(isValid(COLOR_QUESTION, "firetruck")).toEqual(true); + expect(isValid(COLOR_QUESTION, "RED")).toEqual(false); + expect(isValid(COLOR_QUESTION, "orange")).toEqual(false); + expect(isValid(SHAPE_QUESTION, "square")).toEqual(true); + expect(isValid(SHAPE_QUESTION, "triangle")).toEqual(true); + expect(isValid(SHAPE_QUESTION, "circle")).toEqual(true); + expect(isValid(SHAPE_QUESTION, "circle ")).toEqual(false); + expect(isValid(SHAPE_QUESTION, "rhombus")).toEqual(false); + }); + + /////////////////////////////////// + // toShortForm + 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"); + expect(toShortForm(SHAPE_QUESTION)).toEqual("9: Shapes"); + expect(toShortForm(BLANK_QUESTIONS[1])).toEqual("47: My New Que"); + }); + + /////////////////////////////////// + // toMarkdown + test("Testing the toMarkdown function", () => { + expect(toMarkdown(ADDITION_QUESTION)).toEqual(`# Addition +What is 2+2?`); + expect(toMarkdown(LETTER_QUESTION)).toEqual(`# Letters +What is the last letter of the English alphabet?`); + expect(toMarkdown(COLOR_QUESTION)).toEqual(`# Colors +Which of these is a color? +- red +- apple +- firetruck`); + expect(toMarkdown(SHAPE_QUESTION)).toEqual(`# Shapes +What shape can you make with one line? +- square +- triangle +- circle`); + }); + + afterEach(() => { + expect(ADDITION_QUESTION).toEqual(BACKUP_ADDITION_QUESTION); + expect(LETTER_QUESTION).toEqual(BACKUP_LETTER_QUESTION); + expect(SHAPE_QUESTION).toEqual(BACKUP_SHAPE_QUESTION); + expect(COLOR_QUESTION).toEqual(BACKUP_COLOR_QUESTION); + expect(BLANK_QUESTIONS).toEqual(BACKUP_BLANK_QUESTIONS); + }); + + /////////////////////////////////// + // renameQuestion + test("Testing the renameQuestion function", () => { + expect( + renameQuestion(ADDITION_QUESTION, "My Addition Question") + ).toEqual({ + id: 1, + name: "My Addition Question", + body: "What is 2+2?", + type: "short_answer_question", + options: [], + expected: "4", + points: 1, + published: true + }); + expect( + renameQuestion(SHAPE_QUESTION, "I COMPLETELY CHANGED THIS NAME") + ).toEqual({ + id: 9, + name: "I COMPLETELY CHANGED THIS NAME", + body: "What shape can you make with one line?", + type: "multiple_choice_question", + options: ["square", "triangle", "circle"], + expected: "circle", + points: 2, + published: false + }); + }); + + /////////////////////////////////// + // publishQuestion + test("Testing the publishQuestion function", () => { + expect(publishQuestion(ADDITION_QUESTION)).toEqual({ + id: 1, + name: "Addition", + body: "What is 2+2?", + type: "short_answer_question", + options: [], + expected: "4", + points: 1, + published: false + }); + expect(publishQuestion(LETTER_QUESTION)).toEqual({ + 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 + }); + expect(publishQuestion(publishQuestion(ADDITION_QUESTION))).toEqual({ + id: 1, + name: "Addition", + body: "What is 2+2?", + type: "short_answer_question", + options: [], + expected: "4", + points: 1, + published: true + }); + }); + + /////////////////////////////////// + // duplicateQuestion + test("Testing the duplicateQuestion function", () => { + expect(duplicateQuestion(9, ADDITION_QUESTION)).toEqual({ + id: 9, + name: "Copy of Addition", + body: "What is 2+2?", + type: "short_answer_question", + options: [], + expected: "4", + points: 1, + published: false + }); + expect(duplicateQuestion(55, LETTER_QUESTION)).toEqual({ + id: 55, + name: "Copy of Letters", + body: "What is the last letter of the English alphabet?", + type: "short_answer_question", + options: [], + expected: "Z", + points: 1, + published: false + }); + }); + + /////////////////////////////////// + // addOption + test("Testing the addOption function", () => { + expect(addOption(SHAPE_QUESTION, "heptagon")).toEqual({ + id: 9, + name: "Shapes", + body: "What shape can you make with one line?", + type: "multiple_choice_question", + options: ["square", "triangle", "circle", "heptagon"], + expected: "circle", + points: 2, + published: false + }); + expect(addOption(COLOR_QUESTION, "squiggles")).toEqual({ + id: 5, + name: "Colors", + body: "Which of these is a color?", + type: "multiple_choice_question", + options: ["red", "apple", "firetruck", "squiggles"], + expected: "red", + points: 1, + published: true + }); + }); + + /////////////////////////////////// + // mergeQuestion + test("Testing the mergeQuestion function", () => { + expect( + mergeQuestion( + 192, + "More Points Addition", + ADDITION_QUESTION, + SHAPE_QUESTION + ) + ).toEqual({ + id: 192, + name: "More Points Addition", + body: "What is 2+2?", + type: "short_answer_question", + options: [], + expected: "4", + points: 2, + published: false + }); + + expect( + mergeQuestion( + 99, + "Less Points Shape", + SHAPE_QUESTION, + ADDITION_QUESTION + ) + ).toEqual({ + id: 99, + name: "Less Points Shape", + body: "What shape can you make with one line?", + type: "multiple_choice_question", + options: ["square", "triangle", "circle"], + expected: "circle", + points: 1, + published: false + }); + }); +}); diff --git a/src/objects.ts b/src/objects.ts new file mode 100644 index 0000000000..d03dd473e3 --- /dev/null +++ b/src/objects.ts @@ -0,0 +1,141 @@ +/** QuestionType influences how a question is asked and what kinds of answers are possible */ +export type QuestionType = "multiple_choice_question" | "short_answer_question"; + +export interface Question { + /** A unique identifier for the question */ + id: number; + /** The human-friendly title of the question */ + name: string; + /** The instructions and content of the Question */ + body: string; + /** The kind of Question; influences how the user answers and what options are displayed */ + type: QuestionType; + /** The possible answers for a Question (for Multiple Choice questions) */ + options: string[]; + /** The actually correct answer expected */ + expected: string; + /** How many points this question is worth, roughly indicating its importance and difficulty */ + points: number; + /** Whether or not this question is ready to display to students */ + published: boolean; +} + +/** + * Create a new blank question with the given `id`, `name`, and `type. The `body` and + * `expected` should be empty strings, the `options` should be an empty list, the `points` + * should default to 1, and `published` should default to false. + */ +export function makeBlankQuestion( + id: number, + name: string, + type: QuestionType +): Question { + return {}; +} + +/** + * Consumes a question and a potential `answer`, and returns whether or not + * the `answer` is correct. You should check that the `answer` is equal to + * the `expected`, ignoring capitalization and trimming any whitespace. + * + * HINT: Look up the `trim` and `toLowerCase` functions. + */ +export function isCorrect(question: Question, answer: string): boolean { + return false; +} + +/** + * Consumes a question and a potential `answer`, and returns whether or not + * the `answer` is valid (but not necessarily correct). For a `short_answer_question`, + * any answer is valid. But for a `multiple_choice_question`, the `answer` must + * be exactly one of the options. + */ +export function isValid(question: Question, answer: string): boolean { + return false; +} + +/** + * Consumes a question and produces a string representation combining the + * `id` and first 10 characters of the `name`. The two strings should be + * separated by ": ". So for example, the question with id 9 and the + * name "My First Question" would become "9: My First Q". + */ +export function toShortForm(question: Question): string { + return ""; +} + +/** + * Consumes a question and returns a formatted string representation as follows: + * - The first line should be a hash sign, a space, and then the `name` + * - The second line should be the `body` + * - If the question is a `multiple_choice_question`, then the following lines + * need to show each option on its line, preceded by a dash and space. + * + * The example below might help, but don't include the border! + * ----------Example------------- + * |# Name | + * |The body goes here! | + * |- Option 1 | + * |- Option 2 | + * |- Option 3 | + * ------------------------------ + * Check the unit tests for more examples of what this looks like! + */ +export function toMarkdown(question: Question): string { + return ""; +} + +/** + * Return a new version of the given question, except the name should now be + * `newName`. + */ +export function renameQuestion(question: Question, newName: string): Question { + return question; +} + +/** + * Return a new version of the given question, except the `published` field + * should be inverted. If the question was not published, now it should be + * published; if it was published, now it should be not published. + */ +export function publishQuestion(question: Question): Question { + return question; +} + +/** + * Create a new question based on the old question, copying over its `body`, `type`, + * `options`, `expected`, and `points` without changes. The `name` should be copied + * over as "Copy of ORIGINAL NAME" (e.g., so "Question 1" would become "Copy of Question 1"). + * The `published` field should be reset to false. + */ +export function duplicateQuestion(id: number, oldQuestion: Question): Question { + return oldQuestion; +} + +/** + * Return a new version of the given question, with the `newOption` added to + * the list of existing `options`. Remember that the new Question MUST have + * its own separate copy of the `options` list, rather than the same reference + * to the original question's list! + * Check out the subsection about "Nested Fields" for more information. + */ +export function addOption(question: Question, newOption: string): Question { + return question; +} + +/** + * Consumes an id, name, and two questions, and produces a new question. + * The new question will use the `body`, `type`, `options`, and `expected` of the + * `contentQuestion`. The second question will provide the `points`. + * The `published` status should be set to false. + * Notice that the second Question is provided as just an object with a `points` + * field; but the function call would be the same as if it were a `Question` type! + */ +export function mergeQuestion( + id: number, + name: string, + contentQuestion: Question, + { points }: { points: number } +): Question { + return contentQuestion; +} From 2c852d620be705187b5ade2a68df632c6d6d4256 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sun, 6 Feb 2022 18:33:46 -0500 Subject: [PATCH 002/135] Move Question interface to separate file --- src/interfaces/question.ts | 21 +++++++++++++++++++++ src/objects.test.ts | 2 +- src/objects.ts | 22 +--------------------- 3 files changed, 23 insertions(+), 22 deletions(-) create mode 100644 src/interfaces/question.ts diff --git a/src/interfaces/question.ts b/src/interfaces/question.ts new file mode 100644 index 0000000000..a39431565e --- /dev/null +++ b/src/interfaces/question.ts @@ -0,0 +1,21 @@ +/** QuestionType influences how a question is asked and what kinds of answers are possible */ +export type QuestionType = "multiple_choice_question" | "short_answer_question"; + +export interface Question { + /** A unique identifier for the question */ + id: number; + /** The human-friendly title of the question */ + name: string; + /** The instructions and content of the Question */ + body: string; + /** The kind of Question; influences how the user answers and what options are displayed */ + type: QuestionType; + /** The possible answers for a Question (for Multiple Choice questions) */ + options: string[]; + /** The actually correct answer expected */ + expected: string; + /** How many points this question is worth, roughly indicating its importance and difficulty */ + points: number; + /** Whether or not this question is ready to display to students */ + published: boolean; +} diff --git a/src/objects.test.ts b/src/objects.test.ts index bcff7ab176..a9c76a334e 100644 --- a/src/objects.test.ts +++ b/src/objects.test.ts @@ -1,7 +1,7 @@ +import { Question } from "./interfaces/question"; import { makeBlankQuestion, isCorrect, - Question, isValid, toShortForm, toMarkdown, diff --git a/src/objects.ts b/src/objects.ts index d03dd473e3..3fd2072e5e 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -1,24 +1,4 @@ -/** QuestionType influences how a question is asked and what kinds of answers are possible */ -export type QuestionType = "multiple_choice_question" | "short_answer_question"; - -export interface Question { - /** A unique identifier for the question */ - id: number; - /** The human-friendly title of the question */ - name: string; - /** The instructions and content of the Question */ - body: string; - /** The kind of Question; influences how the user answers and what options are displayed */ - type: QuestionType; - /** The possible answers for a Question (for Multiple Choice questions) */ - options: string[]; - /** The actually correct answer expected */ - expected: string; - /** How many points this question is worth, roughly indicating its importance and difficulty */ - points: number; - /** Whether or not this question is ready to display to students */ - published: boolean; -} +import { Question, QuestionType } from "./interfaces/question"; /** * Create a new blank question with the given `id`, `name`, and `type. The `body` and From dc3662af02ffe003b2044d4262bddf02ad6c7333 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Tue, 8 Feb 2022 00:36:21 -0500 Subject: [PATCH 003/135] Create answer interface --- src/interfaces/answer.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/interfaces/answer.ts 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; +} From 51221ee3f303c4927f4efd8f4e286c754cb7b006 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Tue, 8 Feb 2022 00:36:37 -0500 Subject: [PATCH 004/135] First stab at nested tasks --- src/nested.test.ts | 57 +++++++++++++++ src/nested.ts | 178 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 src/nested.test.ts create mode 100644 src/nested.ts diff --git a/src/nested.test.ts b/src/nested.test.ts new file mode 100644 index 0000000000..1e3ff24b5c --- /dev/null +++ b/src/nested.test.ts @@ -0,0 +1,57 @@ +import { Question } from "./interfaces/question"; +import { getPublishedQuestions } from "./nested"; +import testQuestionData from "./data/questions.json"; +import backupQuestionData from "./data/questions.json"; + +const { BLANK_QUESTIONS, SIMPLE_QUESTIONS }: 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 +}: Record = backupQuestionData as Record< + string, + Question[] +>; + +//////////////////////////////////////////// +// Actual tests + +describe("Testing the Question[] functions", () => { + ////////////////////////////////// + // getPublishedQuestions + + test("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 + } + ]); + }); + + afterEach(() => { + expect(BLANK_QUESTIONS).toEqual(BACKUP_BLANK_QUESTIONS); + expect(SIMPLE_QUESTIONS).toEqual(BACKUP_SIMPLE_QUESTIONS); + }); +}); diff --git a/src/nested.ts b/src/nested.ts new file mode 100644 index 0000000000..b9fb13f3cf --- /dev/null +++ b/src/nested.ts @@ -0,0 +1,178 @@ +import { Answer } from "./interfaces/answer"; +import { Question, QuestionType } from "./interfaces/question"; + +/** + * Consumes an array of questions and returns a new array with only the questions + * that are `published`. + */ +export function getPublishedQuestions(questions: Question[]): Question[] { + return []; +} + +/** + * 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[] { + return []; +} + +/*** + * 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 { + return null; +} + +/** + * 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[] { + return []; +} + +/*** + * 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[] { + return []; +} + +/*** + * Consumes an array of questions and returns the sum total of all their points added together. + */ +export function sumPoints(questions: Question[]): number { + return 0; +} + +/*** + * Consumes an array of questions and returns the sum total of the PUBLISHED questions. + */ +export function sumPublishedPoints(questions: Question[]): number { + return 0; +} + +/*** + * 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 { + return ""; +} + +/** + * 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[] { + return []; +} + +/*** + * 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[] { + return []; +} + +/*** + * 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 { + 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 []; +} + +/*** + * 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[] { + return []; +} + +/*** + * 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[] { + return []; +} + +/** + * 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`. + */ +export function editOption( + questions: Question[], + targetId: number, + targetOptionIndex: number, + newOption: string +) { + return []; +} + +/*** + * 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[] { + return []; +} From 3a793cc12152d73df161d3a61691f72d1dc6dde8 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 9 Feb 2022 13:20:35 -0500 Subject: [PATCH 005/135] Document Question interface --- src/interfaces/question.ts | 1 + 1 file changed, 1 insertion(+) 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; From 5c39a97a647cd7e5d686beda8208a81e5f339478 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 9 Feb 2022 13:20:46 -0500 Subject: [PATCH 006/135] Expand questions test data --- src/data/questions.json | 147 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 144 insertions(+), 3 deletions(-) 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 + } + ] } From 6ae0b6f210c37a37dace7b94ef0fc5c0b8fbcbfb Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 9 Feb 2022 13:21:43 -0500 Subject: [PATCH 007/135] Add a little hint for a tough one --- src/nested.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nested.ts b/src/nested.ts index b9fb13f3cf..7934ec1741 100644 --- a/src/nested.ts +++ b/src/nested.ts @@ -153,6 +153,9 @@ export function changeQuestionTypeById( * 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[], From b1bbbc869d8093ca9e286df1330ab150e7d4901d Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 9 Feb 2022 13:22:01 -0500 Subject: [PATCH 008/135] Nested tests (phew) --- src/nested.test.ts | 1187 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1184 insertions(+), 3 deletions(-) diff --git a/src/nested.test.ts b/src/nested.test.ts index 1e3ff24b5c..3d2b75406d 100644 --- a/src/nested.test.ts +++ b/src/nested.test.ts @@ -1,9 +1,32 @@ import { Question } from "./interfaces/question"; -import { getPublishedQuestions } from "./nested"; +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 }: Record = +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; @@ -11,12 +34,41 @@ const { BLANK_QUESTIONS, SIMPLE_QUESTIONS }: 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 + 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 @@ -48,10 +100,1139 @@ describe("Testing the Question[] functions", () => { 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("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("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("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("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("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("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("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("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("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("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("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("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("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("Testing the addEditQuestionOption 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("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); }); }); From 3d36619ae7f4f406f956834660654dea72bc9dad Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 19 Feb 2022 13:52:24 -0500 Subject: [PATCH 009/135] Forgot the task record! --- public/tasks/task-nested.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 public/tasks/task-nested.md 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. From 44c7d671d3ef1960fcd5ca3230ef7345174917c7 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Tue, 1 Mar 2022 16:38:02 -0500 Subject: [PATCH 010/135] Fix typo in editOption test, and missing return type for editOption --- src/nested.test.ts | 2 +- src/nested.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nested.test.ts b/src/nested.test.ts index 3d2b75406d..572a7a028d 100644 --- a/src/nested.test.ts +++ b/src/nested.test.ts @@ -893,7 +893,7 @@ describe("Testing the Question[] functions", () => { ]); }); - test("Testing the addEditQuestionOption function", () => { + test("Testing the editOption function", () => { expect(editOption(BLANK_QUESTIONS, 1, -1, "NEW OPTION")).toEqual([ { id: 1, diff --git a/src/nested.ts b/src/nested.ts index 7934ec1741..562b6ca0df 100644 --- a/src/nested.ts +++ b/src/nested.ts @@ -162,7 +162,7 @@ export function editOption( targetId: number, targetOptionIndex: number, newOption: string -) { +): Question[] { return []; } From d4d4a780cd02cf4a01af64fd013dd63c5edf1d66 Mon Sep 17 00:00:00 2001 From: lremmler Date: Sun, 31 Aug 2025 22:11:06 -0400 Subject: [PATCH 011/135] Update App.tsx --- src/App.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/App.tsx b/src/App.tsx index b77558eaac..5c516f1e26 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ function App(): React.JSX.Element {
UD CISC275 with React Hooks and TypeScript + Luke Remmler

Edit src/App.tsx and save. This page will From 475c8e7b07a1097dd59495c0af029bcf99b6c448 Mon Sep 17 00:00:00 2001 From: lremmler Date: Tue, 2 Sep 2025 21:52:00 -0400 Subject: [PATCH 012/135] Update App.tsx --- src/App.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/App.tsx b/src/App.tsx index 5c516f1e26..65076fbecc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ function App(): React.JSX.Element {

UD CISC275 with React Hooks and TypeScript Luke Remmler + Hello World

Edit src/App.tsx and save. This page will From 1b65e328f97f9731e6537590f20cf5a954640ab0 Mon Sep 17 00:00:00 2001 From: lremmler Date: Sat, 6 Sep 2025 10:47:04 -0400 Subject: [PATCH 013/135] Update App.css --- src/App.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.css b/src/App.css index ad32fac073..90841ec74e 100644 --- a/src/App.css +++ b/src/App.css @@ -15,7 +15,7 @@ .App-header { width: 100%; - background-color: #282c34; + background-color: orange; min-height: 40vh; display: flex; flex-direction: column; From 0344be5d74c44ab276a761f8fc43cef2dfe2c252 Mon Sep 17 00:00:00 2001 From: lremmler Date: Sun, 7 Sep 2025 17:57:02 -0400 Subject: [PATCH 014/135] Update App.css --- src/App.css | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/App.css b/src/App.css index 90841ec74e..3e8b311384 100644 --- a/src/App.css +++ b/src/App.css @@ -1,3 +1,54 @@ +import {Button} from 'react-bootstrap'; + +export function App(): JSX.Element { + return

Luke's App

; +} +export function App(): JSX.Element { + return
+

Hello World

+ A picture of Dr. Bart's dog Ada +
; +} +export function App(): JSX.Element { + return
+ Unordered List: +
    +
  • First thing
  • +
  • Another thing
  • +
  • A third item
  • +
+ Ordered List: +
    +
  1. First thing
  2. +
  3. Another thing
  4. +
  5. A third item
  6. +
+
; +} +export function App(): JSX.Element { + return
+ +
; +} +export function App(): JSX.Element { + return
+ + + First column. + width: 50%; + height: 50%; + background-color: red; + + + Second column. + You can put whatever you want in here, and it will be on the right side. + Maybe try adding an image? + + + +
; +} + .App { text-align: center; } From f1cd720e61bf1d344c8a645155e3c0aa4f88dbf4 Mon Sep 17 00:00:00 2001 From: lremmler Date: Sun, 7 Sep 2025 21:43:02 -0400 Subject: [PATCH 015/135] Update App.css --- src/App.css | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/App.css b/src/App.css index 3e8b311384..c935e5ab57 100644 --- a/src/App.css +++ b/src/App.css @@ -2,14 +2,12 @@ import {Button} from 'react-bootstrap'; export function App(): JSX.Element { return

Luke's App

; -} -export function App(): JSX.Element { + return

Hello World

A picture of Dr. Bart's dog Ada
; -} -export function App(): JSX.Element { + return
Unordered List:
    @@ -23,14 +21,12 @@ export function App(): JSX.Element {
  • Another thing
  • A third item
  • -
; -} -export function App(): JSX.Element { +
; + return
; -} -export function App(): JSX.Element { + return
@@ -49,6 +45,7 @@ export function App(): JSX.Element {
; } + .App { text-align: center; } From 85b1d7b615b7d9077e7ac04560e01e76eb541b07 Mon Sep 17 00:00:00 2001 From: lremmler Date: Sun, 7 Sep 2025 21:45:01 -0400 Subject: [PATCH 016/135] Update App.css --- src/App.css | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/App.css b/src/App.css index c935e5ab57..292eae197e 100644 --- a/src/App.css +++ b/src/App.css @@ -3,12 +3,12 @@ import {Button} from 'react-bootstrap'; export function App(): JSX.Element { return

Luke's App

; - return
+

Hello World

A picture of Dr. Bart's dog Ada -
; + - return
+ Unordered List:
  • First thing
  • @@ -21,13 +21,13 @@ export function App(): JSX.Element {
  • Another thing
  • A third item
  • -
; + - return
+ -
; + - return
+ First column. @@ -42,7 +42,7 @@ export function App(): JSX.Element { -
; + } From f755e6494a573f777700ee7d931569c5a6e3cc49 Mon Sep 17 00:00:00 2001 From: lremmler Date: Tue, 9 Sep 2025 16:50:19 -0400 Subject: [PATCH 017/135] Update App.tsx --- src/App.tsx | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 65076fbecc..93984df06c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,18 +1,58 @@ import React from "react"; import "./App.css"; +import { Button, Container, Row, Col } from "react-bootstrap"; function App(): React.JSX.Element { return (
- UD CISC275 with React Hooks and TypeScript - Luke Remmler - Hello World + UD CISC275 with React Hooks and TypeScript Luke Remmler Hello + World

Edit src/App.tsx and save. This page will automatically reload.

+

Luke Remmler App

; + A picture of Dr. Bart's dog Ada + Unordered List: +
    +
  • First thing
  • +
  • Another thing
  • +
  • A third item
  • +
+ + + + +
+ + +
+ +
+
); } From 6db35f89db902fea97a9eb2c81126e66c196ea1e Mon Sep 17 00:00:00 2001 From: lremmler Date: Tue, 9 Sep 2025 16:50:51 -0400 Subject: [PATCH 018/135] Update App.css --- src/App.css | 48 ------------------------------------------------ 1 file changed, 48 deletions(-) diff --git a/src/App.css b/src/App.css index 292eae197e..90841ec74e 100644 --- a/src/App.css +++ b/src/App.css @@ -1,51 +1,3 @@ -import {Button} from 'react-bootstrap'; - -export function App(): JSX.Element { - return

Luke's App

; - - -

Hello World

- A picture of Dr. Bart's dog Ada - - - - Unordered List: -
    -
  • First thing
  • -
  • Another thing
  • -
  • A third item
  • -
- Ordered List: -
    -
  1. First thing
  2. -
  3. Another thing
  4. -
  5. A third item
  6. -
- - - - - - - - - - First column. - width: 50%; - height: 50%; - background-color: red; - - - Second column. - You can put whatever you want in here, and it will be on the right side. - Maybe try adding an image? - - - - -} - - .App { text-align: center; } From 0afcd8cd516de49cc13c3c1aeac8255e5fb253a8 Mon Sep 17 00:00:00 2001 From: lremmler Date: Tue, 9 Sep 2025 16:55:09 -0400 Subject: [PATCH 019/135] Update App.tsx --- src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 93984df06c..1fbe1cfd41 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,7 +29,7 @@ function App(): React.JSX.Element { console.log("Hello World!"); }} > - Hello World + Log Hello World From b3125406df05f1a69d0e8897644d09e4e31baf1f Mon Sep 17 00:00:00 2001 From: lremmler Date: Tue, 9 Sep 2025 19:53:52 -0400 Subject: [PATCH 020/135] Create functions.ts --- src/functions.ts | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/functions.ts diff --git a/src/functions.ts b/src/functions.ts new file mode 100644 index 0000000000..668480f4cb --- /dev/null +++ b/src/functions.ts @@ -0,0 +1,50 @@ +/** + * Consumes a single temperature in Fahrenheit (a number) and converts to Celsius + * using this formula: + * C = (F - 32) * 5/9 + */ +export function fahrenheitToCelius(temperature: number): number { + return ((temperature - 32) * 5) / 9; +} + +/** + * Consumes three numbers and produces their sum. BUT you should only add a number + * if the number is greater than zero. + */ +export function add3(first: number, second: number, third: number): number { + let sum = 0; + if (first > 0) sum += first; + if (second > 0) sum += second; + if (third > 0) sum += third; + return sum; +} + +/** + * Consumes a string and produces the same string in UPPERCASE and with an exclamation + * mark added to the end. + */ +export function shout(message: string): string { + return message.toUpperCase() + "!"; +} + +/** + * Consumes a string (a message) and returns a boolean if the string ends in a question + * mark. Do not use an `if` statement in solving this question. + */ +export function isQuestion(message: string): boolean { + return message.endsWith("?"); +} + +/** + * Consumes a word (a string) and returns either `true`, `false`, or `null`. If the string + * is "yes" (upper or lower case), then return `true`. If the string is "no" (again, either + * upper or lower case), then return `false`. Otherwise, return `null`. + */ +export function convertYesNo(word: string): boolean | null { + const lowercase = word.toLowerCase(); + return ( + lowercase === "yes" ? true + : lowercase === "no" ? false + : null + ); +} From 05acc401b8db657377538615ca688b8ef62263b6 Mon Sep 17 00:00:00 2001 From: lremmler Date: Wed, 10 Sep 2025 10:46:08 -0400 Subject: [PATCH 021/135] Create arrays.ts --- src/arrays.ts | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 src/arrays.ts diff --git a/src/arrays.ts b/src/arrays.ts new file mode 100644 index 0000000000..f9cf3e21d0 --- /dev/null +++ b/src/arrays.ts @@ -0,0 +1,136 @@ +/** + * Consume an array of numbers, and return a new array containing + * JUST the first and last number. If there are no elements, return + * an empty array. If there is one element, the resulting list should + * the number twice. + */ +export function bookEndList(numbers: number[]): number[] { + if (numbers.length === 0) { + return []; + } + if (numbers.length === 1) { + return [numbers[0], numbers[0]]; + } + return [numbers[0], numbers[numbers.length - 1]]; +} + +/** + * Consume an array of numbers, and return a new array where each + * number has been tripled (multiplied by 3). + */ +export function tripleNumbers(numbers: number[]): number[] { + return numbers.map((num) => num * 3); +} + +/** + * Consume an array of strings and convert them to integers. If + * the number cannot be parsed as an integer, convert it to 0 instead. + */ +export function stringsToIntegers(numbers: string[]): number[] { + return numbers.map((str) => { + const parsed = parseInt(str, 10); + return isNaN(parsed) ? 0 : parsed; + }); +} + +/** + * Consume an array of strings and return them as numbers. Note that + * the strings MAY have "$" symbols at the beginning, in which case + * those should be removed. If the result cannot be parsed as an integer, + * convert it to 0 instead. + */ +// Remember, you can write functions as lambdas too! They work exactly the same. +export const removeDollars = (amounts: string[]): number[] => { + return amounts.map((amount) => { + // Remove $ symbol if present + const cleanAmount = + amount.startsWith("$") ? amount.substring(1) : amount; + const parsed = parseInt(cleanAmount, 10); + return isNaN(parsed) ? 0 : parsed; + }); +}; + +/** + * Consume an array of messages and return a new list of the messages. However, any + * string that ends in "!" should be made uppercase. Also, remove any strings that end + * in question marks ("?"). + */ +export const shoutIfExclaiming = (messages: string[]): string[] => { + return messages + .filter((message) => !message.endsWith("?")) + .map((message) => + message.endsWith("!") ? message.toUpperCase() : message, + ); +}; + +/** + * Consumes an array of words and returns the number of words that are LESS THAN + * 4 letters long. + */ +export function countShortWords(words: string[]): number { + return words.filter((word) => word.length < 4).length; +} + +/** + * Consumes an array of colors (e.g., 'red', 'purple') and returns true if ALL + * the colors are either 'red', 'blue', or 'green'. If an empty list is given, + * then return true. + */ +export function allRGB(colors: string[]): boolean { + if (colors.length === 0) return true; + return colors.every( + (color) => color === "red" || color === "blue" || color === "green", + ); +} + +/** + * Consumes an array of numbers, and produces a string representation of the + * numbers being added together along with their actual sum. + * + * For instance, the array [1, 2, 3] would become "6=1+2+3". + * And the array [] would become "0=0". + */ +export function makeMath(addends: number[]): string { + if (addends.length === 0) { + return "0=0"; + } + + const sum = addends.reduce((total, num) => total + num, 0); + const equation = addends.join("+"); + + return `${sum}=${equation}`; +} + +/** + * Consumes an array of numbers and produces a new array of the same numbers, + * with one difference. After the FIRST negative number, insert the sum of all + * previous numbers in the list. If there are no negative numbers, then append + * the sum to the list. + * + * For instance, the array [1, 9, -5, 7] would become [1, 9, -5, 10, 7] + * And the array [1, 9, 7] would become [1, 9, 7, 17] + */ +export function injectPositive(values: number[]): number[] { + // Find the index of the first negative number + const firstNegativeIndex = values.findIndex((num) => num < 0); + + if (firstNegativeIndex === -1) { + // No negative numbers found, append sum to the end + const sum = values.reduce((total, num) => total + num, 0); + return [...values, sum]; + } else { + // Calculate sum of numbers before the first negative + const numbersBeforeNegative = values.slice(0, firstNegativeIndex); + const sum = numbersBeforeNegative.reduce( + (total, num) => total + num, + 0, + ); + + // Insert the sum after the first negative number + return [ + ...values.slice(0, firstNegativeIndex + 1), + sum, + ...values.slice(firstNegativeIndex + 1), + ]; + } +} From 6f452debd5fe8e86170dbc973e158ebaf1558489 Mon Sep 17 00:00:00 2001 From: lremmler Date: Sun, 14 Sep 2025 22:50:07 -0400 Subject: [PATCH 022/135] Create objects.ts --- src/objects.ts | 169 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 src/objects.ts diff --git a/src/objects.ts b/src/objects.ts new file mode 100644 index 0000000000..d70faa676c --- /dev/null +++ b/src/objects.ts @@ -0,0 +1,169 @@ +import { Question, QuestionType } from "./interfaces/question"; + +/** + * Create a new blank question with the given `id`, `name`, and `type. The `body` and + * `expected` should be empty strings, the `options` should be an empty list, the `points` + * should default to 1, and `published` should default to false. + */ +export function makeBlankQuestion( + id: number, + name: string, + type: QuestionType +): Question { + return { + id, + name, + type, + body: "", + expected: "", + options: [], + points: 1, + published: false + }; +} + +/** + * Consumes a question and a potential `answer`, and returns whether or not + * the `answer` is correct. You should check that the `answer` is equal to + * the `expected`, ignoring capitalization and trimming any whitespace. + * + * HINT: Look up the `trim` and `toLowerCase` functions. + */ +export function isCorrect(question: Question, answer: string): boolean { + const trimmedAnswer = answer.trim().toLowerCase(); + const trimmedExpected = question.expected.trim().toLowerCase(); + return trimmedAnswer === trimmedExpected; +} + +/** + * Consumes a question and a potential `answer`, and returns whether or not + * the `answer` is valid (but not necessarily correct). For a `short_answer_question`, + * any answer is valid. But for a `multiple_choice_question`, the `answer` must + * be exactly one of the options. + */ +export function isValid(question: Question, answer: string): boolean { + if (question.type === "short_answer_question") { + return true; + } else if (question.type === "multiple_choice_question") { + return question.options.includes(answer); + } + return false; +} + +/** + * Consumes a question and produces a string representation combining the + * `id` and first 10 characters of the `name`. The two strings should be + * separated by ": ". So for example, the question with id 9 and the + * name "My First Question" would become "9: My First Q". + */ +export function toShortForm(question: Question): string { + const shortName = question.name.substring(0, 10); + return `${question.id}: ${shortName}`; +} + +/** + * Consumes a question and returns a formatted string representation as follows: + * - The first line should be a hash sign, a space, and then the `name` + * - The second line should be the `body` + * - If the question is a `multiple_choice_question`, then the following lines + * need to show each option on its line, preceded by a dash and space. + * + * The example below might help, but don't include the border! + * ----------Example------------- + * |# Name | + * |The body goes here! | + * |- Option 1 | + * |- Option 2 | + * |- Option 3 | + * ------------------------------ + * Check the unit tests for more examples of what this looks like! + */ +export function toMarkdown(question: Question): string { + let result = `# ${question.name}\n${question.body}`; + + if (question.type === "multiple_choice_question") { + question.options.forEach(option => { + result += `\n- ${option}`; + }); + } + + return result; +} + +/** + * Return a new version of the given question, except the name should now be + * `newName`. + */ +export function renameQuestion(question: Question, newName: string): Question { + return { + ...question, + name: newName + }; +} + +/** + * Return a new version of the given question, except the `published` field + * should be inverted. If the question was not published, now it should be + * published; if it was published, now it should be not published. + */ +export function publishQuestion(question: Question): Question { + return { + ...question, + published: !question.published + }; +} + +/** + * Create a new question based on the old question, copying over its `body`, `type`, + * `options`, `expected`, and `points` without changes. The `name` should be copied + * over as "Copy of ORIGINAL NAME" (e.g., so "Question 1" would become "Copy of Question 1"). + * The `published` field should be reset to false. + */ +export function duplicateQuestion(id: number, oldQuestion: Question): Question { + return { + ...oldQuestion, + id, + name: `Copy of ${oldQuestion.name}`, + published: false + }; +} + +/** + * Return a new version of the given question, with the `newOption` added to + * the list of existing `options`. Remember that the new Question MUST have + * its own separate copy of the `options` list, rather than the same reference + * to the original question's list! + * Check out the subsection about "Nested Fields" for more information. + */ +export function addOption(question: Question, newOption: string): Question { + return { + ...question, + options: [...question.options, newOption] + }; +} + +/** + * Consumes an id, name, and two questions, and produces a new question. + * The new question will use the `body`, `type`, `options`, and `expected` of the + * `contentQuestion`. The second question will provide the `points`. + * The `published` status should be set to false. + * Notice that the second Question is provided as just an object with a `points` + * field; but the function call would be the same as if it were a `Question` type! + */ +export function mergeQuestion( + id: number, + name: string, + contentQuestion: Question, + { points }: { points: number } +): Question { + return { + id, + name, + body: contentQuestion.body, + type: contentQuestion.type, + options: [...contentQuestion.options], + expected: contentQuestion.expected, + points, + published: false + }; +} From d49f820973fafb27ab67b3e86d33c70a04f68c09 Mon Sep 17 00:00:00 2001 From: lremmler Date: Sun, 14 Sep 2025 22:54:58 -0400 Subject: [PATCH 023/135] Update objects.ts --- src/objects.ts | 57 ++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index d70faa676c..69167b0d00 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -8,7 +8,7 @@ import { Question, QuestionType } from "./interfaces/question"; export function makeBlankQuestion( id: number, name: string, - type: QuestionType + type: QuestionType, ): Question { return { id, @@ -18,7 +18,7 @@ export function makeBlankQuestion( expected: "", options: [], points: 1, - published: false + published: false, }; } @@ -30,9 +30,9 @@ export function makeBlankQuestion( * HINT: Look up the `trim` and `toLowerCase` functions. */ export function isCorrect(question: Question, answer: string): boolean { - const trimmedAnswer = answer.trim().toLowerCase(); - const trimmedExpected = question.expected.trim().toLowerCase(); - return trimmedAnswer === trimmedExpected; + const cleanAnswer = answer.trim().toLowerCase(); + const cleanExpected = question.expected.trim().toLowerCase(); + return cleanAnswer === cleanExpected; } /** @@ -44,10 +44,9 @@ export function isCorrect(question: Question, answer: string): boolean { export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { return true; - } else if (question.type === "multiple_choice_question") { - return question.options.includes(answer); } - return false; + // For multiple_choice_question, check if answer is in options + return question.options.includes(answer); } /** @@ -79,15 +78,15 @@ export function toShortForm(question: Question): string { * Check the unit tests for more examples of what this looks like! */ export function toMarkdown(question: Question): string { - let result = `# ${question.name}\n${question.body}`; - + let markdown = `# ${question.name}\n${question.body}`; + if (question.type === "multiple_choice_question") { - question.options.forEach(option => { - result += `\n- ${option}`; + question.options.forEach((option) => { + markdown += `\n- ${option}`; }); } - - return result; + + return markdown; } /** @@ -97,7 +96,7 @@ export function toMarkdown(question: Question): string { export function renameQuestion(question: Question, newName: string): Question { return { ...question, - name: newName + name: newName, }; } @@ -109,7 +108,7 @@ export function renameQuestion(question: Question, newName: string): Question { export function publishQuestion(question: Question): Question { return { ...question, - published: !question.published + published: !question.published, }; } @@ -121,10 +120,14 @@ export function publishQuestion(question: Question): Question { */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { return { - ...oldQuestion, - id, + id: id, name: `Copy of ${oldQuestion.name}`, - published: false + type: oldQuestion.type, + body: oldQuestion.body, + expected: oldQuestion.expected, + options: [...oldQuestion.options], // Create a copy of the array + points: oldQuestion.points, + published: false, }; } @@ -138,7 +141,7 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { export function addOption(question: Question, newOption: string): Question { return { ...question, - options: [...question.options, newOption] + options: [...question.options, newOption], }; } @@ -154,16 +157,16 @@ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number } + { points }: { points: number }, ): Question { return { - id, - name, - body: contentQuestion.body, + id: id, + name: name, type: contentQuestion.type, - options: [...contentQuestion.options], + body: contentQuestion.body, expected: contentQuestion.expected, - points, - published: false + options: [...contentQuestion.options], // Create a copy of the array + points: points, + published: false, }; } From 1e30353798e77e574ea9cd3fc7517f09071ba178 Mon Sep 17 00:00:00 2001 From: lremmler Date: Sun, 14 Sep 2025 22:59:44 -0400 Subject: [PATCH 024/135] Update objects.ts --- src/objects.ts | 66 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 69167b0d00..baa7ea6be4 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -8,9 +8,9 @@ import { Question, QuestionType } from "./interfaces/question"; export function makeBlankQuestion( id: number, name: string, - type: QuestionType, + type: QuestionType ): Question { - return { + const question: Question = { id, name, type, @@ -18,8 +18,9 @@ export function makeBlankQuestion( expected: "", options: [], points: 1, - published: false, + published: false }; + return question; } /** @@ -79,13 +80,13 @@ export function toShortForm(question: Question): string { */ export function toMarkdown(question: Question): string { let markdown = `# ${question.name}\n${question.body}`; - + if (question.type === "multiple_choice_question") { - question.options.forEach((option) => { + for (const option of question.options) { markdown += `\n- ${option}`; - }); + } } - + return markdown; } @@ -94,10 +95,17 @@ export function toMarkdown(question: Question): string { * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - return { - ...question, + const newQuestion: Question = { + id: question.id, name: newName, + type: question.type, + body: question.body, + expected: question.expected, + options: [...question.options], + points: question.points, + published: question.published }; + return newQuestion; } /** @@ -106,10 +114,17 @@ export function renameQuestion(question: Question, newName: string): Question { * published; if it was published, now it should be not published. */ export function publishQuestion(question: Question): Question { - return { - ...question, - published: !question.published, + const newQuestion: Question = { + id: question.id, + name: question.name, + type: question.type, + body: question.body, + expected: question.expected, + options: [...question.options], + points: question.points, + published: !question.published }; + return newQuestion; } /** @@ -119,16 +134,17 @@ export function publishQuestion(question: Question): Question { * The `published` field should be reset to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - return { + const newQuestion: Question = { id: id, name: `Copy of ${oldQuestion.name}`, type: oldQuestion.type, body: oldQuestion.body, expected: oldQuestion.expected, - options: [...oldQuestion.options], // Create a copy of the array + options: [...oldQuestion.options], points: oldQuestion.points, - published: false, + published: false }; + return newQuestion; } /** @@ -139,10 +155,17 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { * Check out the subsection about "Nested Fields" for more information. */ export function addOption(question: Question, newOption: string): Question { - return { - ...question, + const newQuestion: Question = { + id: question.id, + name: question.name, + type: question.type, + body: question.body, + expected: question.expected, options: [...question.options, newOption], + points: question.points, + published: question.published }; + return newQuestion; } /** @@ -157,16 +180,17 @@ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number }, + { points }: { points: number } ): Question { - return { + const newQuestion: Question = { id: id, name: name, type: contentQuestion.type, body: contentQuestion.body, expected: contentQuestion.expected, - options: [...contentQuestion.options], // Create a copy of the array + options: [...contentQuestion.options], points: points, - published: false, + published: false }; + return newQuestion; } From b67630cf7aeca15d973b46a290a7dd38aeab554a Mon Sep 17 00:00:00 2001 From: lremmler Date: Mon, 15 Sep 2025 23:27:53 -0400 Subject: [PATCH 025/135] Update objects.ts --- src/objects.ts | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index baa7ea6be4..a0e0d21722 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -8,17 +8,17 @@ import { Question, QuestionType } from "./interfaces/question"; export function makeBlankQuestion( id: number, name: string, - type: QuestionType + type: QuestionType, ): Question { const question: Question = { - id, - name, - type, + id: id, + name: name, + type: type, body: "", expected: "", options: [], points: 1, - published: false + published: false, }; return question; } @@ -31,8 +31,8 @@ export function makeBlankQuestion( * HINT: Look up the `trim` and `toLowerCase` functions. */ export function isCorrect(question: Question, answer: string): boolean { - const cleanAnswer = answer.trim().toLowerCase(); - const cleanExpected = question.expected.trim().toLowerCase(); + const cleanAnswer: string = answer.trim().toLowerCase(); + const cleanExpected: string = question.expected.trim().toLowerCase(); return cleanAnswer === cleanExpected; } @@ -45,9 +45,11 @@ export function isCorrect(question: Question, answer: string): boolean { export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { return true; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + } else if (question.type === "multiple_choice_question") { + return question.options.includes(answer); } - // For multiple_choice_question, check if answer is in options - return question.options.includes(answer); + return false; } /** @@ -57,7 +59,7 @@ export function isValid(question: Question, answer: string): boolean { * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { - const shortName = question.name.substring(0, 10); + const shortName: string = question.name.substring(0, 10); return `${question.id}: ${shortName}`; } @@ -79,14 +81,14 @@ export function toShortForm(question: Question): string { * Check the unit tests for more examples of what this looks like! */ export function toMarkdown(question: Question): string { - let markdown = `# ${question.name}\n${question.body}`; - + let markdown: string = `# ${question.name}\n${question.body}`; + if (question.type === "multiple_choice_question") { for (const option of question.options) { markdown += `\n- ${option}`; } } - + return markdown; } @@ -103,7 +105,7 @@ export function renameQuestion(question: Question, newName: string): Question { expected: question.expected, options: [...question.options], points: question.points, - published: question.published + published: question.published, }; return newQuestion; } @@ -122,7 +124,7 @@ export function publishQuestion(question: Question): Question { expected: question.expected, options: [...question.options], points: question.points, - published: !question.published + published: !question.published, }; return newQuestion; } @@ -142,7 +144,7 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { expected: oldQuestion.expected, options: [...oldQuestion.options], points: oldQuestion.points, - published: false + published: false, }; return newQuestion; } @@ -163,7 +165,7 @@ export function addOption(question: Question, newOption: string): Question { expected: question.expected, options: [...question.options, newOption], points: question.points, - published: question.published + published: question.published, }; return newQuestion; } @@ -180,7 +182,7 @@ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number } + { points }: { points: number }, ): Question { const newQuestion: Question = { id: id, @@ -190,7 +192,7 @@ export function mergeQuestion( expected: contentQuestion.expected, options: [...contentQuestion.options], points: points, - published: false + published: false, }; return newQuestion; } From 744f79d55fee44567d97eab703921292cdb22df5 Mon Sep 17 00:00:00 2001 From: lremmler Date: Mon, 15 Sep 2025 23:32:48 -0400 Subject: [PATCH 026/135] Update objects.ts --- src/objects.ts | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index a0e0d21722..a13a8b2727 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -19,7 +19,7 @@ export function makeBlankQuestion( options: [], points: 1, published: false, - }; + } as Question; return question; } @@ -97,7 +97,7 @@ export function toMarkdown(question: Question): string { * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - const newQuestion: Question = { + return { id: question.id, name: newName, type: question.type, @@ -106,8 +106,7 @@ export function renameQuestion(question: Question, newName: string): Question { options: [...question.options], points: question.points, published: question.published, - }; - return newQuestion; + } as Question; } /** @@ -116,7 +115,7 @@ export function renameQuestion(question: Question, newName: string): Question { * published; if it was published, now it should be not published. */ export function publishQuestion(question: Question): Question { - const newQuestion: Question = { + return { id: question.id, name: question.name, type: question.type, @@ -125,8 +124,7 @@ export function publishQuestion(question: Question): Question { options: [...question.options], points: question.points, published: !question.published, - }; - return newQuestion; + } as Question; } /** @@ -136,7 +134,7 @@ export function publishQuestion(question: Question): Question { * The `published` field should be reset to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - const newQuestion: Question = { + return { id: id, name: `Copy of ${oldQuestion.name}`, type: oldQuestion.type, @@ -145,8 +143,7 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { options: [...oldQuestion.options], points: oldQuestion.points, published: false, - }; - return newQuestion; + } as Question; } /** @@ -157,7 +154,7 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { * Check out the subsection about "Nested Fields" for more information. */ export function addOption(question: Question, newOption: string): Question { - const newQuestion: Question = { + return { id: question.id, name: question.name, type: question.type, @@ -166,8 +163,7 @@ export function addOption(question: Question, newOption: string): Question { options: [...question.options, newOption], points: question.points, published: question.published, - }; - return newQuestion; + } as Question; } /** @@ -184,7 +180,7 @@ export function mergeQuestion( contentQuestion: Question, { points }: { points: number }, ): Question { - const newQuestion: Question = { + return { id: id, name: name, type: contentQuestion.type, @@ -193,6 +189,5 @@ export function mergeQuestion( options: [...contentQuestion.options], points: points, published: false, - }; - return newQuestion; + } as Question; } From 50197376023609fb97c9fe596ae53bea5b76ab5c Mon Sep 17 00:00:00 2001 From: lremmler Date: Mon, 15 Sep 2025 23:37:29 -0400 Subject: [PATCH 027/135] Update objects.ts --- src/objects.ts | 100 ++++++++++++++++++------------------------------- 1 file changed, 36 insertions(+), 64 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index a13a8b2727..aa6e7fadc4 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -8,19 +8,18 @@ import { Question, QuestionType } from "./interfaces/question"; export function makeBlankQuestion( id: number, name: string, - type: QuestionType, + type: QuestionType ): Question { - const question: Question = { - id: id, - name: name, - type: type, + return { + id, + name, + type, body: "", expected: "", options: [], points: 1, - published: false, - } as Question; - return question; + published: false + }; } /** @@ -31,9 +30,7 @@ export function makeBlankQuestion( * HINT: Look up the `trim` and `toLowerCase` functions. */ export function isCorrect(question: Question, answer: string): boolean { - const cleanAnswer: string = answer.trim().toLowerCase(); - const cleanExpected: string = question.expected.trim().toLowerCase(); - return cleanAnswer === cleanExpected; + return answer.trim().toLowerCase() === question.expected.trim().toLowerCase(); } /** @@ -45,11 +42,9 @@ export function isCorrect(question: Question, answer: string): boolean { export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { return true; - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - } else if (question.type === "multiple_choice_question") { + } else { return question.options.includes(answer); } - return false; } /** @@ -59,8 +54,7 @@ export function isValid(question: Question, answer: string): boolean { * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { - const shortName: string = question.name.substring(0, 10); - return `${question.id}: ${shortName}`; + return `${question.id}: ${question.name.substring(0, 10)}`; } /** @@ -81,15 +75,15 @@ export function toShortForm(question: Question): string { * Check the unit tests for more examples of what this looks like! */ export function toMarkdown(question: Question): string { - let markdown: string = `# ${question.name}\n${question.body}`; - + let result = `# ${question.name}\n${question.body}`; + if (question.type === "multiple_choice_question") { - for (const option of question.options) { - markdown += `\n- ${option}`; - } + question.options.forEach(option => { + result += `\n- ${option}`; + }); } - - return markdown; + + return result; } /** @@ -98,15 +92,9 @@ export function toMarkdown(question: Question): string { */ export function renameQuestion(question: Question, newName: string): Question { return { - id: question.id, - name: newName, - type: question.type, - body: question.body, - expected: question.expected, - options: [...question.options], - points: question.points, - published: question.published, - } as Question; + ...question, + name: newName + }; } /** @@ -116,15 +104,9 @@ export function renameQuestion(question: Question, newName: string): Question { */ export function publishQuestion(question: Question): Question { return { - id: question.id, - name: question.name, - type: question.type, - body: question.body, - expected: question.expected, - options: [...question.options], - points: question.points, - published: !question.published, - } as Question; + ...question, + published: !question.published + }; } /** @@ -135,15 +117,11 @@ export function publishQuestion(question: Question): Question { */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { return { - id: id, + ...oldQuestion, + id, name: `Copy of ${oldQuestion.name}`, - type: oldQuestion.type, - body: oldQuestion.body, - expected: oldQuestion.expected, - options: [...oldQuestion.options], - points: oldQuestion.points, - published: false, - } as Question; + published: false + }; } /** @@ -155,15 +133,9 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { */ export function addOption(question: Question, newOption: string): Question { return { - id: question.id, - name: question.name, - type: question.type, - body: question.body, - expected: question.expected, - options: [...question.options, newOption], - points: question.points, - published: question.published, - } as Question; + ...question, + options: [...question.options, newOption] + }; } /** @@ -178,16 +150,16 @@ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number }, + { points }: { points: number } ): Question { return { - id: id, - name: name, + id, + name, type: contentQuestion.type, body: contentQuestion.body, expected: contentQuestion.expected, options: [...contentQuestion.options], - points: points, - published: false, - } as Question; + points, + published: false + }; } From 9fa4a4f8356be0067365a4485dc31499d428a976 Mon Sep 17 00:00:00 2001 From: lremmler Date: Mon, 15 Sep 2025 23:41:33 -0400 Subject: [PATCH 028/135] Update objects.ts --- src/objects.ts | 56 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index aa6e7fadc4..dbb1f0abac 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -10,7 +10,7 @@ export function makeBlankQuestion( name: string, type: QuestionType ): Question { - return { + const question: Question = { id, name, type, @@ -20,6 +20,7 @@ export function makeBlankQuestion( points: 1, published: false }; + return question; } /** @@ -30,7 +31,9 @@ export function makeBlankQuestion( * HINT: Look up the `trim` and `toLowerCase` functions. */ export function isCorrect(question: Question, answer: string): boolean { - return answer.trim().toLowerCase() === question.expected.trim().toLowerCase(); + const cleanAnswer = answer.trim().toLowerCase(); + const cleanExpected = question.expected.trim().toLowerCase(); + return cleanAnswer === cleanExpected; } /** @@ -54,7 +57,8 @@ export function isValid(question: Question, answer: string): boolean { * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { - return `${question.id}: ${question.name.substring(0, 10)}`; + const shortName = question.name.substring(0, 10); + return `${question.id}: ${shortName}`; } /** @@ -78,9 +82,9 @@ export function toMarkdown(question: Question): string { let result = `# ${question.name}\n${question.body}`; if (question.type === "multiple_choice_question") { - question.options.forEach(option => { + for (const option of question.options) { result += `\n- ${option}`; - }); + } } return result; @@ -92,8 +96,14 @@ export function toMarkdown(question: Question): string { */ export function renameQuestion(question: Question, newName: string): Question { return { - ...question, - name: newName + id: question.id, + name: newName, + type: question.type, + body: question.body, + expected: question.expected, + options: [...question.options], + points: question.points, + published: question.published }; } @@ -104,7 +114,13 @@ export function renameQuestion(question: Question, newName: string): Question { */ export function publishQuestion(question: Question): Question { return { - ...question, + id: question.id, + name: question.name, + type: question.type, + body: question.body, + expected: question.expected, + options: [...question.options], + points: question.points, published: !question.published }; } @@ -117,9 +133,13 @@ export function publishQuestion(question: Question): Question { */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { return { - ...oldQuestion, - id, + id: id, name: `Copy of ${oldQuestion.name}`, + type: oldQuestion.type, + body: oldQuestion.body, + expected: oldQuestion.expected, + options: [...oldQuestion.options], + points: oldQuestion.points, published: false }; } @@ -133,8 +153,14 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { */ export function addOption(question: Question, newOption: string): Question { return { - ...question, - options: [...question.options, newOption] + id: question.id, + name: question.name, + type: question.type, + body: question.body, + expected: question.expected, + options: [...question.options, newOption], + points: question.points, + published: question.published }; } @@ -153,13 +179,13 @@ export function mergeQuestion( { points }: { points: number } ): Question { return { - id, - name, + id: id, + name: name, type: contentQuestion.type, body: contentQuestion.body, expected: contentQuestion.expected, options: [...contentQuestion.options], - points, + points: points, published: false }; } From 787e5c9392df58d2a3d5bd88d2594170d44cdd78 Mon Sep 17 00:00:00 2001 From: lremmler Date: Tue, 16 Sep 2025 23:15:43 -0400 Subject: [PATCH 029/135] Update objects.ts --- src/objects.ts | 145 +++++++++++-------------------------------------- 1 file changed, 33 insertions(+), 112 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index dbb1f0abac..4b4376b767 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -1,16 +1,14 @@ import { Question, QuestionType } from "./interfaces/question"; /** - * Create a new blank question with the given `id`, `name`, and `type. The `body` and - * `expected` should be empty strings, the `options` should be an empty list, the `points` - * should default to 1, and `published` should default to false. + * Create a new blank question */ export function makeBlankQuestion( id: number, name: string, - type: QuestionType + type: QuestionType, ): Question { - const question: Question = { + return { id, name, type, @@ -18,174 +16,97 @@ export function makeBlankQuestion( expected: "", options: [], points: 1, - published: false + published: false, }; - return question; } /** - * Consumes a question and a potential `answer`, and returns whether or not - * the `answer` is correct. You should check that the `answer` is equal to - * the `expected`, ignoring capitalization and trimming any whitespace. - * - * HINT: Look up the `trim` and `toLowerCase` functions. + * Check if an answer is correct */ export function isCorrect(question: Question, answer: string): boolean { - const cleanAnswer = answer.trim().toLowerCase(); - const cleanExpected = question.expected.trim().toLowerCase(); - return cleanAnswer === cleanExpected; + return answer.trim().toLowerCase() === question.expected.trim().toLowerCase(); } /** - * Consumes a question and a potential `answer`, and returns whether or not - * the `answer` is valid (but not necessarily correct). For a `short_answer_question`, - * any answer is valid. But for a `multiple_choice_question`, the `answer` must - * be exactly one of the options. + * Check if an answer is valid */ export function isValid(question: Question, answer: string): boolean { - if (question.type === "short_answer_question") { + if (question.type === QuestionType.short_answer_question) { return true; - } else { + } + if (question.type === QuestionType.multiple_choice_question) { return question.options.includes(answer); } + return false; } /** - * Consumes a question and produces a string representation combining the - * `id` and first 10 characters of the `name`. The two strings should be - * separated by ": ". So for example, the question with id 9 and the - * name "My First Question" would become "9: My First Q". + * Short form: "id: first 10 chars of name" */ export function toShortForm(question: Question): string { - const shortName = question.name.substring(0, 10); - return `${question.id}: ${shortName}`; + return `${question.id}: ${question.name.substring(0, 10)}`; } /** - * Consumes a question and returns a formatted string representation as follows: - * - The first line should be a hash sign, a space, and then the `name` - * - The second line should be the `body` - * - If the question is a `multiple_choice_question`, then the following lines - * need to show each option on its line, preceded by a dash and space. - * - * The example below might help, but don't include the border! - * ----------Example------------- - * |# Name | - * |The body goes here! | - * |- Option 1 | - * |- Option 2 | - * |- Option 3 | - * ------------------------------ - * Check the unit tests for more examples of what this looks like! + * Markdown representation */ export function toMarkdown(question: Question): string { let result = `# ${question.name}\n${question.body}`; - - if (question.type === "multiple_choice_question") { - for (const option of question.options) { - result += `\n- ${option}`; - } + if (question.type === QuestionType.multiple_choice_question) { + result += "\n" + question.options.map(opt => `- ${opt}`).join("\n"); } - return result; } /** - * Return a new version of the given question, except the name should now be - * `newName`. + * Rename question */ export function renameQuestion(question: Question, newName: string): Question { - return { - id: question.id, - name: newName, - type: question.type, - body: question.body, - expected: question.expected, - options: [...question.options], - points: question.points, - published: question.published - }; + return { ...question, name: newName }; } /** - * Return a new version of the given question, except the `published` field - * should be inverted. If the question was not published, now it should be - * published; if it was published, now it should be not published. + * Toggle publish status */ export function publishQuestion(question: Question): Question { - return { - id: question.id, - name: question.name, - type: question.type, - body: question.body, - expected: question.expected, - options: [...question.options], - points: question.points, - published: !question.published - }; + return { ...question, published: !question.published }; } /** - * Create a new question based on the old question, copying over its `body`, `type`, - * `options`, `expected`, and `points` without changes. The `name` should be copied - * over as "Copy of ORIGINAL NAME" (e.g., so "Question 1" would become "Copy of Question 1"). - * The `published` field should be reset to false. + * Duplicate a question */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { return { - id: id, + ...oldQuestion, + id, name: `Copy of ${oldQuestion.name}`, - type: oldQuestion.type, - body: oldQuestion.body, - expected: oldQuestion.expected, + published: false, options: [...oldQuestion.options], - points: oldQuestion.points, - published: false }; } /** - * Return a new version of the given question, with the `newOption` added to - * the list of existing `options`. Remember that the new Question MUST have - * its own separate copy of the `options` list, rather than the same reference - * to the original question's list! - * Check out the subsection about "Nested Fields" for more information. + * Add option to a question */ export function addOption(question: Question, newOption: string): Question { - return { - id: question.id, - name: question.name, - type: question.type, - body: question.body, - expected: question.expected, - options: [...question.options, newOption], - points: question.points, - published: question.published - }; + return { ...question, options: [...question.options, newOption] }; } /** - * Consumes an id, name, and two questions, and produces a new question. - * The new question will use the `body`, `type`, `options`, and `expected` of the - * `contentQuestion`. The second question will provide the `points`. - * The `published` status should be set to false. - * Notice that the second Question is provided as just an object with a `points` - * field; but the function call would be the same as if it were a `Question` type! + * Merge questions */ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number } + { points }: { points: number }, ): Question { return { - id: id, - name: name, - type: contentQuestion.type, - body: contentQuestion.body, - expected: contentQuestion.expected, + ...contentQuestion, + id, + name, + points, + published: false, options: [...contentQuestion.options], - points: points, - published: false }; } From d67d6e32e4afd60a57dbe07c4b95c17a39738024 Mon Sep 17 00:00:00 2001 From: lremmler Date: Tue, 16 Sep 2025 23:19:06 -0400 Subject: [PATCH 030/135] Update objects.ts --- src/objects.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 4b4376b767..886fa7a8a7 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -24,7 +24,9 @@ export function makeBlankQuestion( * Check if an answer is correct */ export function isCorrect(question: Question, answer: string): boolean { - return answer.trim().toLowerCase() === question.expected.trim().toLowerCase(); + const cleanAnswer: string = answer.trim().toLowerCase(); + const cleanExpected: string = question.expected.trim().toLowerCase(); + return cleanAnswer === cleanExpected; } /** @@ -81,7 +83,7 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { id, name: `Copy of ${oldQuestion.name}`, published: false, - options: [...oldQuestion.options], + options: [...(oldQuestion.options as string[])], }; } @@ -89,7 +91,7 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { * Add option to a question */ export function addOption(question: Question, newOption: string): Question { - return { ...question, options: [...question.options, newOption] }; + return { ...question, options: [...(question.options as string[]), newOption] }; } /** @@ -107,6 +109,6 @@ export function mergeQuestion( name, points, published: false, - options: [...contentQuestion.options], + options: [...(contentQuestion.options as string[])], }; } From c5b2b1751dd772c967bfb2b92cfb5334a234d23d Mon Sep 17 00:00:00 2001 From: lremmler Date: Tue, 16 Sep 2025 23:24:33 -0400 Subject: [PATCH 031/135] Update objects.ts --- src/objects.ts | 111 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 81 insertions(+), 30 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 886fa7a8a7..aa6e7fadc4 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -1,12 +1,14 @@ import { Question, QuestionType } from "./interfaces/question"; /** - * Create a new blank question + * Create a new blank question with the given `id`, `name`, and `type. The `body` and + * `expected` should be empty strings, the `options` should be an empty list, the `points` + * should default to 1, and `published` should default to false. */ export function makeBlankQuestion( id: number, name: string, - type: QuestionType, + type: QuestionType ): Question { return { id, @@ -16,99 +18,148 @@ export function makeBlankQuestion( expected: "", options: [], points: 1, - published: false, + published: false }; } /** - * Check if an answer is correct + * Consumes a question and a potential `answer`, and returns whether or not + * the `answer` is correct. You should check that the `answer` is equal to + * the `expected`, ignoring capitalization and trimming any whitespace. + * + * HINT: Look up the `trim` and `toLowerCase` functions. */ export function isCorrect(question: Question, answer: string): boolean { - const cleanAnswer: string = answer.trim().toLowerCase(); - const cleanExpected: string = question.expected.trim().toLowerCase(); - return cleanAnswer === cleanExpected; + return answer.trim().toLowerCase() === question.expected.trim().toLowerCase(); } /** - * Check if an answer is valid + * Consumes a question and a potential `answer`, and returns whether or not + * the `answer` is valid (but not necessarily correct). For a `short_answer_question`, + * any answer is valid. But for a `multiple_choice_question`, the `answer` must + * be exactly one of the options. */ export function isValid(question: Question, answer: string): boolean { - if (question.type === QuestionType.short_answer_question) { + if (question.type === "short_answer_question") { return true; - } - if (question.type === QuestionType.multiple_choice_question) { + } else { return question.options.includes(answer); } - return false; } /** - * Short form: "id: first 10 chars of name" + * Consumes a question and produces a string representation combining the + * `id` and first 10 characters of the `name`. The two strings should be + * separated by ": ". So for example, the question with id 9 and the + * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { return `${question.id}: ${question.name.substring(0, 10)}`; } /** - * Markdown representation + * Consumes a question and returns a formatted string representation as follows: + * - The first line should be a hash sign, a space, and then the `name` + * - The second line should be the `body` + * - If the question is a `multiple_choice_question`, then the following lines + * need to show each option on its line, preceded by a dash and space. + * + * The example below might help, but don't include the border! + * ----------Example------------- + * |# Name | + * |The body goes here! | + * |- Option 1 | + * |- Option 2 | + * |- Option 3 | + * ------------------------------ + * Check the unit tests for more examples of what this looks like! */ export function toMarkdown(question: Question): string { let result = `# ${question.name}\n${question.body}`; - if (question.type === QuestionType.multiple_choice_question) { - result += "\n" + question.options.map(opt => `- ${opt}`).join("\n"); + + if (question.type === "multiple_choice_question") { + question.options.forEach(option => { + result += `\n- ${option}`; + }); } + return result; } /** - * Rename question + * Return a new version of the given question, except the name should now be + * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - return { ...question, name: newName }; + return { + ...question, + name: newName + }; } /** - * Toggle publish status + * Return a new version of the given question, except the `published` field + * should be inverted. If the question was not published, now it should be + * published; if it was published, now it should be not published. */ export function publishQuestion(question: Question): Question { - return { ...question, published: !question.published }; + return { + ...question, + published: !question.published + }; } /** - * Duplicate a question + * Create a new question based on the old question, copying over its `body`, `type`, + * `options`, `expected`, and `points` without changes. The `name` should be copied + * over as "Copy of ORIGINAL NAME" (e.g., so "Question 1" would become "Copy of Question 1"). + * The `published` field should be reset to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { return { ...oldQuestion, id, name: `Copy of ${oldQuestion.name}`, - published: false, - options: [...(oldQuestion.options as string[])], + published: false }; } /** - * Add option to a question + * Return a new version of the given question, with the `newOption` added to + * the list of existing `options`. Remember that the new Question MUST have + * its own separate copy of the `options` list, rather than the same reference + * to the original question's list! + * Check out the subsection about "Nested Fields" for more information. */ export function addOption(question: Question, newOption: string): Question { - return { ...question, options: [...(question.options as string[]), newOption] }; + return { + ...question, + options: [...question.options, newOption] + }; } /** - * Merge questions + * Consumes an id, name, and two questions, and produces a new question. + * The new question will use the `body`, `type`, `options`, and `expected` of the + * `contentQuestion`. The second question will provide the `points`. + * The `published` status should be set to false. + * Notice that the second Question is provided as just an object with a `points` + * field; but the function call would be the same as if it were a `Question` type! */ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number }, + { points }: { points: number } ): Question { return { - ...contentQuestion, id, name, + type: contentQuestion.type, + body: contentQuestion.body, + expected: contentQuestion.expected, + options: [...contentQuestion.options], points, - published: false, - options: [...(contentQuestion.options as string[])], + published: false }; } From 8e1951a31c198a27b2a58edc58d963cddb392317 Mon Sep 17 00:00:00 2001 From: lremmler Date: Tue, 16 Sep 2025 23:28:32 -0400 Subject: [PATCH 032/135] Update objects.ts --- src/objects.ts | 155 +++++++++++++++++++++---------------------------- 1 file changed, 65 insertions(+), 90 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index aa6e7fadc4..7e3b74e35b 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -1,165 +1,140 @@ -import { Question, QuestionType } from "./interfaces/question"; +import { Question } from "./interfaces/question"; /** - * Create a new blank question with the given `id`, `name`, and `type. The `body` and - * `expected` should be empty strings, the `options` should be an empty list, the `points` - * should default to 1, and `published` should default to false. + * Make a blank question with the given id, name, and type. */ export function makeBlankQuestion( id: number, name: string, - type: QuestionType + type: "short_answer_question" | "multiple_choice_question", ): Question { return { id, name, - type, body: "", - expected: "", + type, options: [], + expected: "", points: 1, - published: false + published: false, }; } /** - * Consumes a question and a potential `answer`, and returns whether or not - * the `answer` is correct. You should check that the `answer` is equal to - * the `expected`, ignoring capitalization and trimming any whitespace. - * - * HINT: Look up the `trim` and `toLowerCase` functions. + * Return true if the provided answer is correct for the question. + * Comparisons are case-insensitive for short_answer_question and strict for multiple_choice_question. */ export function isCorrect(question: Question, answer: string): boolean { - return answer.trim().toLowerCase() === question.expected.trim().toLowerCase(); + const normalized = answer.trim().toLowerCase(); + if (question.type === "short_answer_question") { + return question.expected.trim().toLowerCase() === normalized; + } + return question.expected === answer; } /** - * Consumes a question and a potential `answer`, and returns whether or not - * the `answer` is valid (but not necessarily correct). For a `short_answer_question`, - * any answer is valid. But for a `multiple_choice_question`, the `answer` must - * be exactly one of the options. + * Return true if the provided answer is a valid option for the question. + * For short_answer_question, any answer is valid. For multiple_choice_question, + * only an exact match of an option is valid. */ export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { return true; - } else { - return question.options.includes(answer); } + return question.options.includes(answer); } /** - * Consumes a question and produces a string representation combining the - * `id` and first 10 characters of the `name`. The two strings should be - * separated by ": ". So for example, the question with id 9 and the - * name "My First Question" would become "9: My First Q". + * Return a short form string of the question: "id: first 10 chars of name". */ export function toShortForm(question: Question): string { - return `${question.id}: ${question.name.substring(0, 10)}`; + return `${question.id}: ${question.name.slice(0, 10)}`; } /** - * Consumes a question and returns a formatted string representation as follows: - * - The first line should be a hash sign, a space, and then the `name` - * - The second line should be the `body` - * - If the question is a `multiple_choice_question`, then the following lines - * need to show each option on its line, preceded by a dash and space. - * - * The example below might help, but don't include the border! - * ----------Example------------- - * |# Name | - * |The body goes here! | - * |- Option 1 | - * |- Option 2 | - * |- Option 3 | - * ------------------------------ - * Check the unit tests for more examples of what this looks like! + * Return a markdown representation of the question. + * Short answer questions are just "# name\nbody" + * Multiple choice questions list options prefixed by "- ". */ export function toMarkdown(question: Question): string { - let result = `# ${question.name}\n${question.body}`; - + const base = `# ${question.name}\n${question.body}`; if (question.type === "multiple_choice_question") { - question.options.forEach(option => { - result += `\n- ${option}`; - }); + const options = question.options.map((opt) => `- ${opt}`).join("\n"); + return `${base}\n${options}`; } - - return result; + return base; } /** - * Return a new version of the given question, except the name should now be - * `newName`. + * Return a copy of the question with a new name. */ export function renameQuestion(question: Question, newName: string): Question { - return { - ...question, - name: newName - }; + return { ...question, name: newName }; } /** - * Return a new version of the given question, except the `published` field - * should be inverted. If the question was not published, now it should be - * published; if it was published, now it should be not published. + * Return a copy of the question with the published field toggled. */ export function publishQuestion(question: Question): Question { - return { - ...question, - published: !question.published - }; + return { ...question, published: !question.published }; } /** - * Create a new question based on the old question, copying over its `body`, `type`, - * `options`, `expected`, and `points` without changes. The `name` should be copied - * over as "Copy of ORIGINAL NAME" (e.g., so "Question 1" would become "Copy of Question 1"). - * The `published` field should be reset to false. + * Return a copy of the question with a new id, name prefixed with "Copy of ", + * published set to false. */ -export function duplicateQuestion(id: number, oldQuestion: Question): Question { +export function duplicateQuestion(newId: number, question: Question): Question { return { - ...oldQuestion, - id, - name: `Copy of ${oldQuestion.name}`, - published: false + ...question, + id: newId, + name: `Copy of ${question.name}`, + published: false, }; } /** - * Return a new version of the given question, with the `newOption` added to - * the list of existing `options`. Remember that the new Question MUST have - * its own separate copy of the `options` list, rather than the same reference - * to the original question's list! - * Check out the subsection about "Nested Fields" for more information. + * Return a copy of the question with a new option added to the end of options. */ -export function addOption(question: Question, newOption: string): Question { - return { - ...question, - options: [...question.options, newOption] - }; +export function addOption(question: Question, option: string): Question { + return { ...question, options: [...question.options, option] }; } /** - * Consumes an id, name, and two questions, and produces a new question. - * The new question will use the `body`, `type`, `options`, and `expected` of the - * `contentQuestion`. The second question will provide the `points`. - * The `published` status should be set to false. - * Notice that the second Question is provided as just an object with a `points` - * field; but the function call would be the same as if it were a `Question` type! + * Merge two questions into a new question with the given id and name. + * - body, type, options, expected come from contentQuestion + * - points come from pointsQuestion + * - published is always false */ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number } + pointsQuestion: Question, ): Question { return { id, name, - type: contentQuestion.type, body: contentQuestion.body, - expected: contentQuestion.expected, + type: contentQuestion.type, options: [...contentQuestion.options], - points, - published: false + expected: contentQuestion.expected, + points: pointsQuestion.points, + published: false, }; } + +/** + * Safe JSON parse utility to avoid linting issues with `unknown` error type. + */ +export function safeParse(text: string): T | null { + try { + return JSON.parse(text) as T; + } catch (err: unknown) { + if (err instanceof Error) { + console.error("JSON parse error:", err.message); + } else { + console.error("Unknown error during JSON parse"); + } + return null; + } +} From 5b1ca6dd2fd0572ffa3c452f87a42bcdbd236025 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 18 Sep 2025 14:41:25 -0400 Subject: [PATCH 033/135] Update objects.ts --- src/objects.ts | 60 ++++++++++++++------------------------------------ 1 file changed, 17 insertions(+), 43 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 7e3b74e35b..2b7cc2f727 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -21,21 +21,16 @@ export function makeBlankQuestion( } /** - * Return true if the provided answer is correct for the question. - * Comparisons are case-insensitive for short_answer_question and strict for multiple_choice_question. + * Check if an answer is correct. */ export function isCorrect(question: Question, answer: string): boolean { - const normalized = answer.trim().toLowerCase(); - if (question.type === "short_answer_question") { - return question.expected.trim().toLowerCase() === normalized; - } - return question.expected === answer; + const normalized: string = answer.trim().toLowerCase(); + const expected: string = question.expected.trim().toLowerCase(); + return normalized === expected; } /** - * Return true if the provided answer is a valid option for the question. - * For short_answer_question, any answer is valid. For multiple_choice_question, - * only an exact match of an option is valid. + * Check if an answer is valid. */ export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { @@ -45,43 +40,40 @@ export function isValid(question: Question, answer: string): boolean { } /** - * Return a short form string of the question: "id: first 10 chars of name". + * Return a short form string "id: first 10 chars of name". */ export function toShortForm(question: Question): string { return `${question.id}: ${question.name.slice(0, 10)}`; } /** - * Return a markdown representation of the question. - * Short answer questions are just "# name\nbody" - * Multiple choice questions list options prefixed by "- ". + * Markdown representation of a question. */ export function toMarkdown(question: Question): string { - const base = `# ${question.name}\n${question.body}`; + let result: string = `# ${question.name}\n${question.body}`; if (question.type === "multiple_choice_question") { - const options = question.options.map((opt) => `- ${opt}`).join("\n"); - return `${base}\n${options}`; + const opts: string = question.options.map((o: string) => `- ${o}`).join("\n"); + result += `\n${opts}`; } - return base; + return result; } /** - * Return a copy of the question with a new name. + * Rename a question. */ export function renameQuestion(question: Question, newName: string): Question { return { ...question, name: newName }; } /** - * Return a copy of the question with the published field toggled. + * Toggle publish status. */ export function publishQuestion(question: Question): Question { return { ...question, published: !question.published }; } /** - * Return a copy of the question with a new id, name prefixed with "Copy of ", - * published set to false. + * Duplicate a question with a new id and reset published to false. */ export function duplicateQuestion(newId: number, question: Question): Question { return { @@ -89,21 +81,19 @@ export function duplicateQuestion(newId: number, question: Question): Question { id: newId, name: `Copy of ${question.name}`, published: false, + options: [...question.options], }; } /** - * Return a copy of the question with a new option added to the end of options. + * Add an option to a question. */ export function addOption(question: Question, option: string): Question { return { ...question, options: [...question.options, option] }; } /** - * Merge two questions into a new question with the given id and name. - * - body, type, options, expected come from contentQuestion - * - points come from pointsQuestion - * - published is always false + * Merge two questions. */ export function mergeQuestion( id: number, @@ -122,19 +112,3 @@ export function mergeQuestion( published: false, }; } - -/** - * Safe JSON parse utility to avoid linting issues with `unknown` error type. - */ -export function safeParse(text: string): T | null { - try { - return JSON.parse(text) as T; - } catch (err: unknown) { - if (err instanceof Error) { - console.error("JSON parse error:", err.message); - } else { - console.error("Unknown error during JSON parse"); - } - return null; - } -} From 7a6d5a56ccc91237050369f42b1352f11e2e6f66 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 18 Sep 2025 14:44:07 -0400 Subject: [PATCH 034/135] Update objects.ts --- src/objects.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 2b7cc2f727..6b64d89f54 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -25,8 +25,10 @@ export function makeBlankQuestion( */ export function isCorrect(question: Question, answer: string): boolean { const normalized: string = answer.trim().toLowerCase(); - const expected: string = question.expected.trim().toLowerCase(); - return normalized === expected; + if (question.type === "short_answer_question") { + return question.expected.trim().toLowerCase() === normalized; + } + return question.expected === answer; } /** @@ -81,7 +83,7 @@ export function duplicateQuestion(newId: number, question: Question): Question { id: newId, name: `Copy of ${question.name}`, published: false, - options: [...question.options], + options: [...question.options], // explicitly typed as string[] }; } @@ -89,7 +91,8 @@ export function duplicateQuestion(newId: number, question: Question): Question { * Add an option to a question. */ export function addOption(question: Question, option: string): Question { - return { ...question, options: [...question.options, option] }; + const safeOptions: string[] = [...question.options, option]; + return { ...question, options: safeOptions }; } /** @@ -101,12 +104,13 @@ export function mergeQuestion( contentQuestion: Question, pointsQuestion: Question, ): Question { + const safeOptions: string[] = [...contentQuestion.options]; return { id, name, body: contentQuestion.body, type: contentQuestion.type, - options: [...contentQuestion.options], + options: safeOptions, expected: contentQuestion.expected, points: pointsQuestion.points, published: false, From 3f76b118f4fefa1ff745face162f84518d0dbca3 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 18 Sep 2025 14:48:07 -0400 Subject: [PATCH 035/135] Update objects.ts --- src/objects.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 6b64d89f54..47bbcbb210 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -61,14 +61,14 @@ export function toMarkdown(question: Question): string { } /** - * Rename a question. + * Rename a question (immutable). */ export function renameQuestion(question: Question, newName: string): Question { return { ...question, name: newName }; } /** - * Toggle publish status. + * Toggle publish status (immutable). */ export function publishQuestion(question: Question): Question { return { ...question, published: !question.published }; @@ -78,17 +78,18 @@ export function publishQuestion(question: Question): Question { * Duplicate a question with a new id and reset published to false. */ export function duplicateQuestion(newId: number, question: Question): Question { + const copiedOptions: string[] = [...question.options]; return { ...question, id: newId, name: `Copy of ${question.name}`, published: false, - options: [...question.options], // explicitly typed as string[] + options: copiedOptions, }; } /** - * Add an option to a question. + * Add an option to a multiple-choice question. */ export function addOption(question: Question, option: string): Question { const safeOptions: string[] = [...question.options, option]; @@ -96,7 +97,7 @@ export function addOption(question: Question, option: string): Question { } /** - * Merge two questions. + * Merge two questions: use content from the first, points from the second. */ export function mergeQuestion( id: number, From f509acecff33af41f389499265ea1a9340eee8aa Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 18 Sep 2025 14:53:07 -0400 Subject: [PATCH 036/135] Update objects.ts --- src/objects.ts | 55 +++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 47bbcbb210..28adc81a7d 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -1,7 +1,7 @@ import { Question } from "./interfaces/question"; /** - * Make a blank question with the given id, name, and type. + * Make a blank question given an id, name, and type. */ export function makeBlankQuestion( id: number, @@ -21,18 +21,18 @@ export function makeBlankQuestion( } /** - * Check if an answer is correct. + * Check if an answer is correct for a given question. */ export function isCorrect(question: Question, answer: string): boolean { - const normalized: string = answer.trim().toLowerCase(); if (question.type === "short_answer_question") { - return question.expected.trim().toLowerCase() === normalized; + return question.expected.trim().toLowerCase() === answer.trim().toLowerCase(); + } else { + return question.expected === answer; } - return question.expected === answer; } /** - * Check if an answer is valid. + * Check if an answer is valid for a given question. */ export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { @@ -42,62 +42,68 @@ export function isValid(question: Question, answer: string): boolean { } /** - * Return a short form string "id: first 10 chars of name". + * Convert a question into its short form string. */ export function toShortForm(question: Question): string { - return `${question.id}: ${question.name.slice(0, 10)}`; + return `${question.id}: ${question.name.substring(0, 10)}`; } /** - * Markdown representation of a question. + * Convert a question into a Markdown string. */ export function toMarkdown(question: Question): string { - let result: string = `# ${question.name}\n${question.body}`; - if (question.type === "multiple_choice_question") { - const opts: string = question.options.map((o: string) => `- ${o}`).join("\n"); - result += `\n${opts}`; + const header = `# ${question.name}`; + if (question.type === "short_answer_question") { + return `${header}\n${question.body}`; + } else { + const options = question.options.map((opt: string): string => `- ${opt}`).join("\n"); + return `${header}\n${question.body}\n${options}`; } - return result; } /** - * Rename a question (immutable). + * Create a new version of the question with a different name. */ export function renameQuestion(question: Question, newName: string): Question { return { ...question, name: newName }; } /** - * Toggle publish status (immutable). + * Publish/unpublish a question (toggle published). */ export function publishQuestion(question: Question): Question { return { ...question, published: !question.published }; } /** - * Duplicate a question with a new id and reset published to false. + * Duplicate a question, copying all details but changing id and name. */ export function duplicateQuestion(newId: number, question: Question): Question { - const copiedOptions: string[] = [...question.options]; return { ...question, id: newId, name: `Copy of ${question.name}`, published: false, - options: copiedOptions, }; } /** - * Add an option to a multiple-choice question. + * Add an option to a multiple choice question. */ export function addOption(question: Question, option: string): Question { - const safeOptions: string[] = [...question.options, option]; - return { ...question, options: safeOptions }; + if (question.type === "multiple_choice_question") { + return { + ...question, + options: [...question.options, option], + }; + } + return { ...question }; } /** - * Merge two questions: use content from the first, points from the second. + * Merge two questions into one. + * - Body and expected come from contentQuestion + * - Points come from pointsQuestion */ export function mergeQuestion( id: number, @@ -105,13 +111,12 @@ export function mergeQuestion( contentQuestion: Question, pointsQuestion: Question, ): Question { - const safeOptions: string[] = [...contentQuestion.options]; return { id, name, body: contentQuestion.body, type: contentQuestion.type, - options: safeOptions, + options: [...contentQuestion.options], expected: contentQuestion.expected, points: pointsQuestion.points, published: false, From e065776a0163ccc21db7cd64303ebbd2867d929d Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 18 Sep 2025 14:56:11 -0400 Subject: [PATCH 037/135] Update objects.ts --- src/objects.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 28adc81a7d..4075c56ddc 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -26,9 +26,8 @@ export function makeBlankQuestion( export function isCorrect(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { return question.expected.trim().toLowerCase() === answer.trim().toLowerCase(); - } else { - return question.expected === answer; } + return question.expected === answer; } /** @@ -38,6 +37,7 @@ export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { return true; } + // options is always a string[], so no unsafe array return question.options.includes(answer); } @@ -55,10 +55,9 @@ export function toMarkdown(question: Question): string { const header = `# ${question.name}`; if (question.type === "short_answer_question") { return `${header}\n${question.body}`; - } else { - const options = question.options.map((opt: string): string => `- ${opt}`).join("\n"); - return `${header}\n${question.body}\n${options}`; } + const options: string = question.options.map((opt: string): string => `- ${opt}`).join("\n"); + return `${header}\n${question.body}\n${options}`; } /** @@ -92,10 +91,8 @@ export function duplicateQuestion(newId: number, question: Question): Question { */ export function addOption(question: Question, option: string): Question { if (question.type === "multiple_choice_question") { - return { - ...question, - options: [...question.options, option], - }; + const newOptions: string[] = [...question.options, option]; + return { ...question, options: newOptions }; } return { ...question }; } @@ -111,12 +108,13 @@ export function mergeQuestion( contentQuestion: Question, pointsQuestion: Question, ): Question { + const mergedOptions: string[] = [...contentQuestion.options]; return { id, name, body: contentQuestion.body, type: contentQuestion.type, - options: [...contentQuestion.options], + options: mergedOptions, expected: contentQuestion.expected, points: pointsQuestion.points, published: false, From c11e2fa8e63f4c990c25b61ce6ec9dc5bfaa0487 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 16:13:02 -0400 Subject: [PATCH 038/135] Create questions.json --- src/questions.json | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 src/questions.json diff --git a/src/questions.json b/src/questions.json new file mode 100644 index 0000000000..8f261435c1 --- /dev/null +++ b/src/questions.json @@ -0,0 +1,76 @@ +{ + "BLANK_QUESTIONS": [ + { + "id": 1, + "name": "Question 1", + "type": "multiple_choice_question", + "body": "", + "expected": "", + "options": [], + "points": 1, + "published": false + }, + { + "id": 47, + "name": "My New Question", + "type": "multiple_choice_question", + "body": "", + "expected": "", + "options": [], + "points": 1, + "published": false + }, + { + "id": 2, + "name": "Question 2", + "type": "short_answer_question", + "body": "", + "expected": "", + "options": [], + "points": 1, + "published": false + } + ], + "SIMPLE_QUESTIONS": [ + { + "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 + }, + { + "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 + } + ] +} From d0bfac2732113c1c5d1f97b17476211b4c164518 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 16:17:30 -0400 Subject: [PATCH 039/135] Update objects.ts --- src/objects.ts | 184 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 129 insertions(+), 55 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 4075c56ddc..0194158c9a 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -1,122 +1,196 @@ -import { Question } from "./interfaces/question"; +import { Question, QuestionType } from "./interfaces/question"; /** - * Make a blank question given an id, name, and type. + * Create a new blank question with the given `id`, `name`, and `type. The `body` and + * `expected` should be empty strings, the `options` should be an empty list, the `points` + * should default to 1, and `published` should default to false. */ export function makeBlankQuestion( id: number, name: string, - type: "short_answer_question" | "multiple_choice_question", + type: QuestionType ): Question { - return { - id, - name, + const question: Question = { + id: id, + name: name, + type: type, body: "", - type, - options: [], expected: "", + options: [], points: 1, - published: false, + published: false }; + return question; } /** - * Check if an answer is correct for a given question. + * Consumes a question and a potential `answer`, and returns whether or not + * the `answer` is correct. You should check that the `answer` is equal to + * the `expected`, ignoring capitalization and trimming any whitespace. + * + * HINT: Look up the `trim` and `toLowerCase` functions. */ export function isCorrect(question: Question, answer: string): boolean { - if (question.type === "short_answer_question") { - return question.expected.trim().toLowerCase() === answer.trim().toLowerCase(); - } - return question.expected === answer; + const cleanAnswer: string = answer.trim().toLowerCase(); + const cleanExpected: string = question.expected.trim().toLowerCase(); + return cleanAnswer === cleanExpected; } /** - * Check if an answer is valid for a given question. + * Consumes a question and a potential `answer`, and returns whether or not + * the `answer` is valid (but not necessarily correct). For a `short_answer_question`, + * any answer is valid. But for a `multiple_choice_question`, the `answer` must + * be exactly one of the options. */ export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { return true; + } else { + return question.options.includes(answer); } - // options is always a string[], so no unsafe array - return question.options.includes(answer); } /** - * Convert a question into its short form string. + * Consumes a question and produces a string representation combining the + * `id` and first 10 characters of the `name`. The two strings should be + * separated by ": ". So for example, the question with id 9 and the + * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { - return `${question.id}: ${question.name.substring(0, 10)}`; + const shortName: string = question.name.substring(0, 10); + return `${question.id}: ${shortName}`; } /** - * Convert a question into a Markdown string. + * Consumes a question and returns a formatted string representation as follows: + * - The first line should be a hash sign, a space, and then the `name` + * - The second line should be the `body` + * - If the question is a `multiple_choice_question`, then the following lines + * need to show each option on its line, preceded by a dash and space. + * + * The example below might help, but don't include the border! + * ----------Example------------- + * |# Name | + * |The body goes here! | + * |- Option 1 | + * |- Option 2 | + * |- Option 3 | + * ------------------------------ + * Check the unit tests for more examples of what this looks like! */ export function toMarkdown(question: Question): string { - const header = `# ${question.name}`; - if (question.type === "short_answer_question") { - return `${header}\n${question.body}`; + let result: string = `# ${question.name}\n${question.body}`; + + if (question.type === "multiple_choice_question") { + for (const option of question.options) { + result += `\n- ${option}`; + } } - const options: string = question.options.map((opt: string): string => `- ${opt}`).join("\n"); - return `${header}\n${question.body}\n${options}`; + + return result; } /** - * Create a new version of the question with a different name. + * Return a new version of the given question, except the name should now be + * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - return { ...question, name: newName }; + const newQuestion: Question = { + id: question.id, + name: newName, + type: question.type, + body: question.body, + expected: question.expected, + options: [...question.options], + points: question.points, + published: question.published + }; + return newQuestion; } /** - * Publish/unpublish a question (toggle published). + * Return a new version of the given question, except the `published` field + * should be inverted. If the question was not published, now it should be + * published; if it was published, now it should be not published. */ export function publishQuestion(question: Question): Question { - return { ...question, published: !question.published }; + const newQuestion: Question = { + id: question.id, + name: question.name, + type: question.type, + body: question.body, + expected: question.expected, + options: [...question.options], + points: question.points, + published: !question.published + }; + return newQuestion; } /** - * Duplicate a question, copying all details but changing id and name. + * Create a new question based on the old question, copying over its `body`, `type`, + * `options`, `expected`, and `points` without changes. The `name` should be copied + * over as "Copy of ORIGINAL NAME" (e.g., so "Question 1" would become "Copy of Question 1"). + * The `published` field should be reset to false. */ -export function duplicateQuestion(newId: number, question: Question): Question { - return { - ...question, - id: newId, - name: `Copy of ${question.name}`, - published: false, +export function duplicateQuestion(id: number, oldQuestion: Question): Question { + const newQuestion: Question = { + id: id, + name: `Copy of ${oldQuestion.name}`, + type: oldQuestion.type, + body: oldQuestion.body, + expected: oldQuestion.expected, + options: [...oldQuestion.options], + points: oldQuestion.points, + published: false }; + return newQuestion; } /** - * Add an option to a multiple choice question. + * Return a new version of the given question, with the `newOption` added to + * the list of existing `options`. Remember that the new Question MUST have + * its own separate copy of the `options` list, rather than the same reference + * to the original question's list! + * Check out the subsection about "Nested Fields" for more information. */ -export function addOption(question: Question, option: string): Question { - if (question.type === "multiple_choice_question") { - const newOptions: string[] = [...question.options, option]; - return { ...question, options: newOptions }; - } - return { ...question }; +export function addOption(question: Question, newOption: string): Question { + const newQuestion: Question = { + id: question.id, + name: question.name, + type: question.type, + body: question.body, + expected: question.expected, + options: [...question.options, newOption], + points: question.points, + published: question.published + }; + return newQuestion; } /** - * Merge two questions into one. - * - Body and expected come from contentQuestion - * - Points come from pointsQuestion + * Consumes an id, name, and two questions, and produces a new question. + * The new question will use the `body`, `type`, `options`, and `expected` of the + * `contentQuestion`. The second question will provide the `points`. + * The `published` status should be set to false. + * Notice that the second Question is provided as just an object with a `points` + * field; but the function call would be the same as if it were a `Question` type! */ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - pointsQuestion: Question, + { points }: { points: number } ): Question { - const mergedOptions: string[] = [...contentQuestion.options]; - return { - id, - name, - body: contentQuestion.body, + const newQuestion: Question = { + id: id, + name: name, type: contentQuestion.type, - options: mergedOptions, + body: contentQuestion.body, expected: contentQuestion.expected, - points: pointsQuestion.points, - published: false, + options: [...contentQuestion.options], + points: points, + published: false }; + return newQuestion; } From 548fdf51bbd8afbc1a7063ec6eec083fe46e60e7 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 16:20:17 -0400 Subject: [PATCH 040/135] Create questions.ts --- src/interfaces/questions.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/interfaces/questions.ts diff --git a/src/interfaces/questions.ts b/src/interfaces/questions.ts new file mode 100644 index 0000000000..74c3a32e05 --- /dev/null +++ b/src/interfaces/questions.ts @@ -0,0 +1,14 @@ +// interfaces/question.ts + +export type QuestionType = "multiple_choice_question" | "short_answer_question"; + +export interface Question { + id: number; + name: string; + type: QuestionType; + body: string; + expected: string; + options: string[]; + points: number; + published: boolean; +} From 9ab4207fd376fc6743fce8b84f6ca41588f9d116 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 16:25:34 -0400 Subject: [PATCH 041/135] Update objects.ts --- src/objects.ts | 48 ++++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 0194158c9a..fba77fe48d 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -10,17 +10,16 @@ export function makeBlankQuestion( name: string, type: QuestionType ): Question { - const question: Question = { - id: id, - name: name, - type: type, + return { + id, + name, + type, body: "", expected: "", options: [], points: 1, published: false }; - return question; } /** @@ -31,9 +30,7 @@ export function makeBlankQuestion( * HINT: Look up the `trim` and `toLowerCase` functions. */ export function isCorrect(question: Question, answer: string): boolean { - const cleanAnswer: string = answer.trim().toLowerCase(); - const cleanExpected: string = question.expected.trim().toLowerCase(); - return cleanAnswer === cleanExpected; + return answer.trim().toLowerCase() === question.expected.trim().toLowerCase(); } /** @@ -57,8 +54,7 @@ export function isValid(question: Question, answer: string): boolean { * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { - const shortName: string = question.name.substring(0, 10); - return `${question.id}: ${shortName}`; + return `${question.id}: ${question.name.substring(0, 10)}`; } /** @@ -79,7 +75,7 @@ export function toShortForm(question: Question): string { * Check the unit tests for more examples of what this looks like! */ export function toMarkdown(question: Question): string { - let result: string = `# ${question.name}\n${question.body}`; + let result = `# ${question.name}\n${question.body}`; if (question.type === "multiple_choice_question") { for (const option of question.options) { @@ -95,17 +91,17 @@ export function toMarkdown(question: Question): string { * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - const newQuestion: Question = { + const newOptions: string[] = [...question.options]; + return { id: question.id, name: newName, type: question.type, body: question.body, expected: question.expected, - options: [...question.options], + options: newOptions, points: question.points, published: question.published }; - return newQuestion; } /** @@ -114,17 +110,17 @@ export function renameQuestion(question: Question, newName: string): Question { * published; if it was published, now it should be not published. */ export function publishQuestion(question: Question): Question { - const newQuestion: Question = { + const newOptions: string[] = [...question.options]; + return { id: question.id, name: question.name, type: question.type, body: question.body, expected: question.expected, - options: [...question.options], + options: newOptions, points: question.points, published: !question.published }; - return newQuestion; } /** @@ -134,17 +130,17 @@ export function publishQuestion(question: Question): Question { * The `published` field should be reset to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - const newQuestion: Question = { + const newOptions: string[] = [...oldQuestion.options]; + return { id: id, name: `Copy of ${oldQuestion.name}`, type: oldQuestion.type, body: oldQuestion.body, expected: oldQuestion.expected, - options: [...oldQuestion.options], + options: newOptions, points: oldQuestion.points, published: false }; - return newQuestion; } /** @@ -155,17 +151,17 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { * Check out the subsection about "Nested Fields" for more information. */ export function addOption(question: Question, newOption: string): Question { - const newQuestion: Question = { + const newOptions: string[] = [...question.options, newOption]; + return { id: question.id, name: question.name, type: question.type, body: question.body, expected: question.expected, - options: [...question.options, newOption], + options: newOptions, points: question.points, published: question.published }; - return newQuestion; } /** @@ -182,15 +178,15 @@ export function mergeQuestion( contentQuestion: Question, { points }: { points: number } ): Question { - const newQuestion: Question = { + const newOptions: string[] = [...contentQuestion.options]; + return { id: id, name: name, type: contentQuestion.type, body: contentQuestion.body, expected: contentQuestion.expected, - options: [...contentQuestion.options], + options: newOptions, points: points, published: false }; - return newQuestion; } From 771992032bdb2f8c478fe9f3231490a3b8788f9a Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 16:30:11 -0400 Subject: [PATCH 042/135] Create questions.ts --- src/questions.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/questions.ts diff --git a/src/questions.ts b/src/questions.ts new file mode 100644 index 0000000000..a693e7ed4e --- /dev/null +++ b/src/questions.ts @@ -0,0 +1,5 @@ +import rawQuestions from "./questions.json"; +import { Question } from "./interfaces/question"; + +// Cast JSON data into Question[] +export const SIMPLE_QUESTIONS: Question[] = rawQuestions as Question[]; From 73068b499976be64278a1d54698c99a498591810 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 16:31:03 -0400 Subject: [PATCH 043/135] Update questions.json --- src/questions.json | 118 ++++++++++++++++----------------------------- 1 file changed, 42 insertions(+), 76 deletions(-) diff --git a/src/questions.json b/src/questions.json index 8f261435c1..577fe8c2cb 100644 --- a/src/questions.json +++ b/src/questions.json @@ -1,76 +1,42 @@ -{ - "BLANK_QUESTIONS": [ - { - "id": 1, - "name": "Question 1", - "type": "multiple_choice_question", - "body": "", - "expected": "", - "options": [], - "points": 1, - "published": false - }, - { - "id": 47, - "name": "My New Question", - "type": "multiple_choice_question", - "body": "", - "expected": "", - "options": [], - "points": 1, - "published": false - }, - { - "id": 2, - "name": "Question 2", - "type": "short_answer_question", - "body": "", - "expected": "", - "options": [], - "points": 1, - "published": false - } - ], - "SIMPLE_QUESTIONS": [ - { - "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 - }, - { - "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 - } - ] -} +[ + { + "id": 1, + "name": "Addition", + "body": "What is 2+2?", + "type": "short_answer_question", + "expected": "4", + "options": [], + "points": 1, + "published": true + }, + { + "id": 2, + "name": "Favorite Letter", + "body": "Choose a letter", + "type": "multiple_choice_question", + "expected": "A", + "options": ["A", "B", "C"], + "points": 1, + "published": false + }, + { + "id": 3, + "name": "Favorite Color", + "body": "Choose a color", + "type": "multiple_choice_question", + "expected": "Blue", + "options": ["Red", "Blue", "Green"], + "points": 1, + "published": false + }, + { + "id": 4, + "name": "Favorite Shape", + "body": "Choose a shape", + "type": "multiple_choice_question", + "expected": "Circle", + "options": ["Circle", "Square", "Triangle"], + "points": 1, + "published": false + } +] From d33f1f18efce6c62f54d751c22fe801e42ea0c0b Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 16:48:46 -0400 Subject: [PATCH 044/135] Update questions.json --- src/questions.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/questions.json b/src/questions.json index 577fe8c2cb..9e4183f6a1 100644 --- a/src/questions.json +++ b/src/questions.json @@ -1,3 +1,8 @@ +declare module "*.json" { + const value: unknown; + export default value; +} + [ { "id": 1, From 7e2e28c226fd70a461144ae70a336bdccbad00de Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 16:49:10 -0400 Subject: [PATCH 045/135] Update questions.json --- src/questions.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/questions.json b/src/questions.json index 9e4183f6a1..f90acd7938 100644 --- a/src/questions.json +++ b/src/questions.json @@ -1,10 +1,10 @@ -declare module "*.json" { +[ + declare module "*.json" { const value: unknown; export default value; -} + } -[ - { + { "id": 1, "name": "Addition", "body": "What is 2+2?", From ad8d11e0125c845a5b8ea86b492c25773b18943d Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 16:51:49 -0400 Subject: [PATCH 046/135] Update questions.json --- src/questions.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/questions.json b/src/questions.json index f90acd7938..577fe8c2cb 100644 --- a/src/questions.json +++ b/src/questions.json @@ -1,10 +1,5 @@ [ - declare module "*.json" { - const value: unknown; - export default value; - } - - { + { "id": 1, "name": "Addition", "body": "What is 2+2?", From 7681866a53c138f0e802b326b60b0f9130cee21f Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 16:53:17 -0400 Subject: [PATCH 047/135] Create global.d.ts --- src/global.d.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/global.d.ts diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000000..3e1c3bb59d --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,4 @@ +declare module "*.json" { + const value: unknown; + export default value; +} From 7b67a09de6da925b2cf6c34a02c0be16c1ecc24f Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 16:53:55 -0400 Subject: [PATCH 048/135] Update questions.json --- src/questions.json | 119 +++++++++++++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 42 deletions(-) diff --git a/src/questions.json b/src/questions.json index 577fe8c2cb..625b68f77f 100644 --- a/src/questions.json +++ b/src/questions.json @@ -1,42 +1,77 @@ -[ - { - "id": 1, - "name": "Addition", - "body": "What is 2+2?", - "type": "short_answer_question", - "expected": "4", - "options": [], - "points": 1, - "published": true - }, - { - "id": 2, - "name": "Favorite Letter", - "body": "Choose a letter", - "type": "multiple_choice_question", - "expected": "A", - "options": ["A", "B", "C"], - "points": 1, - "published": false - }, - { - "id": 3, - "name": "Favorite Color", - "body": "Choose a color", - "type": "multiple_choice_question", - "expected": "Blue", - "options": ["Red", "Blue", "Green"], - "points": 1, - "published": false - }, - { - "id": 4, - "name": "Favorite Shape", - "body": "Choose a shape", - "type": "multiple_choice_question", - "expected": "Circle", - "options": ["Circle", "Square", "Triangle"], - "points": 1, - "published": false - } -] +{ + "BLANK_QUESTIONS": [ + { + "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": 2, + "name": "Question 2", + "body": "", + "type": "short_answer_question", + "options": [], + "expected": "", + "points": 1, + "published": false + } + ], + "SIMPLE_QUESTIONS": [ + { + "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": false + } + ] +} + From fdb7dd229796ec24593fe11ef17b7e47646eb0f2 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 16:54:39 -0400 Subject: [PATCH 049/135] Create typedQuestions.ts --- src/typedQuestions.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/typedQuestions.ts diff --git a/src/typedQuestions.ts b/src/typedQuestions.ts new file mode 100644 index 0000000000..fac45674f9 --- /dev/null +++ b/src/typedQuestions.ts @@ -0,0 +1,12 @@ +import raw from "./data/questions.json"; +import { Question } from "./interfaces/question"; + +interface QuestionData { + BLANK_QUESTIONS: Question[]; + SIMPLE_QUESTIONS: Question[]; +} + +const data = raw as unknown as QuestionData; + +export const BLANK_QUESTIONS: Question[] = data.BLANK_QUESTIONS; +export const SIMPLE_QUESTIONS: Question[] = data.SIMPLE_QUESTIONS; From 58cc25288d30c94fbb2212045494226531859ba9 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 16:55:52 -0400 Subject: [PATCH 050/135] Create questions.json.ts --- src/questions.json.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/questions.json.ts diff --git a/src/questions.json.ts b/src/questions.json.ts new file mode 100644 index 0000000000..d7e43874b1 --- /dev/null +++ b/src/questions.json.ts @@ -0,0 +1,6 @@ +import { BLANK_QUESTIONS, SIMPLE_QUESTIONS } from "../typedQuestions"; + +export default { + BLANK_QUESTIONS, + SIMPLE_QUESTIONS +}; From 0e4a09477d8835f64c28516ea446f67ede674495 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:04:42 -0400 Subject: [PATCH 051/135] Update objects.ts --- src/objects.ts | 138 ++++++++++++------------------------------------- 1 file changed, 34 insertions(+), 104 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index fba77fe48d..0e607bb223 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -1,14 +1,12 @@ import { Question, QuestionType } from "./interfaces/question"; /** - * Create a new blank question with the given `id`, `name`, and `type. The `body` and - * `expected` should be empty strings, the `options` should be an empty list, the `points` - * should default to 1, and `published` should default to false. + * Create a new blank question with the given `id`, `name`, and `type`. */ export function makeBlankQuestion( id: number, name: string, - type: QuestionType + type: QuestionType, ): Question { return { id, @@ -18,175 +16,107 @@ export function makeBlankQuestion( expected: "", options: [], points: 1, - published: false + published: false, }; } /** - * Consumes a question and a potential `answer`, and returns whether or not - * the `answer` is correct. You should check that the `answer` is equal to - * the `expected`, ignoring capitalization and trimming any whitespace. - * - * HINT: Look up the `trim` and `toLowerCase` functions. + * Returns whether the `answer` is correct (case- and space-insensitive). */ export function isCorrect(question: Question, answer: string): boolean { - return answer.trim().toLowerCase() === question.expected.trim().toLowerCase(); + const cleanAnswer: string = answer.trim().toLowerCase(); + const cleanExpected: string = question.expected.trim().toLowerCase(); + return cleanAnswer === cleanExpected; } /** - * Consumes a question and a potential `answer`, and returns whether or not - * the `answer` is valid (but not necessarily correct). For a `short_answer_question`, - * any answer is valid. But for a `multiple_choice_question`, the `answer` must - * be exactly one of the options. + * Returns whether the `answer` is valid for the given question type. */ export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { return true; - } else { - return question.options.includes(answer); } + return question.options.includes(answer); } /** - * Consumes a question and produces a string representation combining the - * `id` and first 10 characters of the `name`. The two strings should be - * separated by ": ". So for example, the question with id 9 and the - * name "My First Question" would become "9: My First Q". + * Produces a short string representation of the question. */ export function toShortForm(question: Question): string { - return `${question.id}: ${question.name.substring(0, 10)}`; + const shortName: string = question.name.substring(0, 10); + return `${question.id}: ${shortName}`; } /** - * Consumes a question and returns a formatted string representation as follows: - * - The first line should be a hash sign, a space, and then the `name` - * - The second line should be the `body` - * - If the question is a `multiple_choice_question`, then the following lines - * need to show each option on its line, preceded by a dash and space. - * - * The example below might help, but don't include the border! - * ----------Example------------- - * |# Name | - * |The body goes here! | - * |- Option 1 | - * |- Option 2 | - * |- Option 3 | - * ------------------------------ - * Check the unit tests for more examples of what this looks like! + * Produces a Markdown representation of the question. */ export function toMarkdown(question: Question): string { - let result = `# ${question.name}\n${question.body}`; - + let result: string = `# ${question.name}\n${question.body}`; if (question.type === "multiple_choice_question") { for (const option of question.options) { result += `\n- ${option}`; } } - return result; } /** - * Return a new version of the given question, except the name should now be - * `newName`. + * Return a copy of the question with a new name. */ export function renameQuestion(question: Question, newName: string): Question { - const newOptions: string[] = [...question.options]; return { - id: question.id, + ...question, name: newName, - type: question.type, - body: question.body, - expected: question.expected, - options: newOptions, - points: question.points, - published: question.published }; } /** - * Return a new version of the given question, except the `published` field - * should be inverted. If the question was not published, now it should be - * published; if it was published, now it should be not published. + * Return a copy of the question with the `published` field inverted. */ export function publishQuestion(question: Question): Question { - const newOptions: string[] = [...question.options]; return { - id: question.id, - name: question.name, - type: question.type, - body: question.body, - expected: question.expected, - options: newOptions, - points: question.points, - published: !question.published + ...question, + published: !question.published, }; } /** - * Create a new question based on the old question, copying over its `body`, `type`, - * `options`, `expected`, and `points` without changes. The `name` should be copied - * over as "Copy of ORIGINAL NAME" (e.g., so "Question 1" would become "Copy of Question 1"). - * The `published` field should be reset to false. + * Create a duplicate of the question, with a new `id`, updated name, + * and `published` set to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - const newOptions: string[] = [...oldQuestion.options]; return { - id: id, + ...oldQuestion, + id, name: `Copy of ${oldQuestion.name}`, - type: oldQuestion.type, - body: oldQuestion.body, - expected: oldQuestion.expected, - options: newOptions, - points: oldQuestion.points, - published: false + published: false, }; } /** - * Return a new version of the given question, with the `newOption` added to - * the list of existing `options`. Remember that the new Question MUST have - * its own separate copy of the `options` list, rather than the same reference - * to the original question's list! - * Check out the subsection about "Nested Fields" for more information. + * Return a copy of the question with an extra option added. */ export function addOption(question: Question, newOption: string): Question { - const newOptions: string[] = [...question.options, newOption]; return { - id: question.id, - name: question.name, - type: question.type, - body: question.body, - expected: question.expected, - options: newOptions, - points: question.points, - published: question.published + ...question, + options: [...question.options, newOption], }; } /** - * Consumes an id, name, and two questions, and produces a new question. - * The new question will use the `body`, `type`, `options`, and `expected` of the - * `contentQuestion`. The second question will provide the `points`. - * The `published` status should be set to false. - * Notice that the second Question is provided as just an object with a `points` - * field; but the function call would be the same as if it were a `Question` type! + * Merge two questions into a new one with a provided `id` and `name`. */ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number } + { points }: { points: number }, ): Question { - const newOptions: string[] = [...contentQuestion.options]; return { - id: id, - name: name, - type: contentQuestion.type, - body: contentQuestion.body, - expected: contentQuestion.expected, - options: newOptions, - points: points, - published: false + ...contentQuestion, + id, + name, + points, + published: false, }; } From 3a237b745c58a2a85e74128a931aa25ff15edbc7 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:07:07 -0400 Subject: [PATCH 052/135] Update objects.ts --- src/objects.ts | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 0e607bb223..be3b3d8a15 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -6,9 +6,9 @@ import { Question, QuestionType } from "./interfaces/question"; export function makeBlankQuestion( id: number, name: string, - type: QuestionType, + type: QuestionType ): Question { - return { + const question: Question = { id, name, type, @@ -16,12 +16,13 @@ export function makeBlankQuestion( expected: "", options: [], points: 1, - published: false, + published: false }; + return question; } /** - * Returns whether the `answer` is correct (case- and space-insensitive). + * Check if an answer is correct (ignoring case + trimming whitespace). */ export function isCorrect(question: Question, answer: string): boolean { const cleanAnswer: string = answer.trim().toLowerCase(); @@ -30,7 +31,8 @@ export function isCorrect(question: Question, answer: string): boolean { } /** - * Returns whether the `answer` is valid for the given question type. + * Validate an answer: any string for short_answer_question, or must match + * one of the options for multiple_choice_question. */ export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { @@ -40,7 +42,7 @@ export function isValid(question: Question, answer: string): boolean { } /** - * Produces a short string representation of the question. + * Short form: "id: first10charsOfName". */ export function toShortForm(question: Question): string { const shortName: string = question.name.substring(0, 10); @@ -48,7 +50,7 @@ export function toShortForm(question: Question): string { } /** - * Produces a Markdown representation of the question. + * Markdown format for a question. */ export function toMarkdown(question: Question): string { let result: string = `# ${question.name}\n${question.body}`; @@ -61,62 +63,67 @@ export function toMarkdown(question: Question): string { } /** - * Return a copy of the question with a new name. + * Rename a question (new object). */ export function renameQuestion(question: Question, newName: string): Question { return { ...question, name: newName, + options: [...question.options] }; } /** - * Return a copy of the question with the `published` field inverted. + * Invert published status. */ export function publishQuestion(question: Question): Question { return { ...question, - published: !question.published, + options: [...question.options], + published: !question.published }; } /** - * Create a duplicate of the question, with a new `id`, updated name, - * and `published` set to false. + * Duplicate a question with new id, name prefixed by "Copy of", and reset published. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { return { ...oldQuestion, id, name: `Copy of ${oldQuestion.name}`, - published: false, + options: [...oldQuestion.options], + published: false }; } /** - * Return a copy of the question with an extra option added. + * Add a new option (copy options array). */ export function addOption(question: Question, newOption: string): Question { return { ...question, - options: [...question.options, newOption], + options: [...question.options, newOption] }; } /** - * Merge two questions into a new one with a provided `id` and `name`. + * Merge body/type/options/expected from contentQuestion with points from other. */ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number }, + { points }: { points: number } ): Question { return { - ...contentQuestion, id, name, + type: contentQuestion.type, + body: contentQuestion.body, + expected: contentQuestion.expected, + options: [...contentQuestion.options], points, - published: false, + published: false }; } From 396a364f59a8e39f9c52464e20f9a42db76063e3 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:07:26 -0400 Subject: [PATCH 053/135] Update questions.json.ts --- src/questions.json.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/questions.json.ts b/src/questions.json.ts index d7e43874b1..1b2b6ca230 100644 --- a/src/questions.json.ts +++ b/src/questions.json.ts @@ -1,6 +1,5 @@ -import { BLANK_QUESTIONS, SIMPLE_QUESTIONS } from "../typedQuestions"; +import { Question } from "./interfaces/question"; +import rawQuestions from "./questions.json"; -export default { - BLANK_QUESTIONS, - SIMPLE_QUESTIONS -}; +// Explicitly cast JSON to Question[] +export const questions: Question[] = rawQuestions as Question[]; From f803e252f46f59b67b55a2b8411105543516cb56 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:07:53 -0400 Subject: [PATCH 054/135] Update typedQuestions.ts --- src/typedQuestions.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/typedQuestions.ts b/src/typedQuestions.ts index fac45674f9..6f113aff9c 100644 --- a/src/typedQuestions.ts +++ b/src/typedQuestions.ts @@ -1,12 +1,7 @@ -import raw from "./data/questions.json"; import { Question } from "./interfaces/question"; +import rawQuestions from "./questions.json"; -interface QuestionData { - BLANK_QUESTIONS: Question[]; - SIMPLE_QUESTIONS: Question[]; -} +// Strongly typed array of questions +const typedQuestions: Question[] = rawQuestions as Question[]; -const data = raw as unknown as QuestionData; - -export const BLANK_QUESTIONS: Question[] = data.BLANK_QUESTIONS; -export const SIMPLE_QUESTIONS: Question[] = data.SIMPLE_QUESTIONS; +export default typedQuestions; From 7fa4988269b215ea661126e56c55900ba7eafcb3 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:12:20 -0400 Subject: [PATCH 055/135] Update global.d.ts --- src/global.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/global.d.ts b/src/global.d.ts index 3e1c3bb59d..05a982ce0a 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,4 +1,6 @@ +import type { Question } from "./interfaces/question"; + declare module "*.json" { - const value: unknown; + const value: Question[]; export default value; } From a3a075b9b6f4dd9f46c9de684b7e18fa587ef778 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:14:04 -0400 Subject: [PATCH 056/135] Update questions.json.ts --- src/questions.json.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/questions.json.ts b/src/questions.json.ts index 1b2b6ca230..b566cf0bd5 100644 --- a/src/questions.json.ts +++ b/src/questions.json.ts @@ -1,5 +1,6 @@ import { Question } from "./interfaces/question"; import rawQuestions from "./questions.json"; -// Explicitly cast JSON to Question[] -export const questions: Question[] = rawQuestions as Question[]; +const questions: Question[] = rawQuestions as Question[]; + +export default questions; From 7b837bf1fda40bcbf3d79db59ddf1839e0af1331 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:16:00 -0400 Subject: [PATCH 057/135] Update global.d.ts --- src/global.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/global.d.ts b/src/global.d.ts index 05a982ce0a..7e7b03c92e 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,4 +1,5 @@ -import type { Question } from "./interfaces/question"; +// This file declares JSON imports as strongly typed arrays of Questions +import { Question } from "./interfaces/question"; declare module "*.json" { const value: Question[]; From 5e3aaea277ebfabce9ff33af63fb2c15adf69160 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:16:18 -0400 Subject: [PATCH 058/135] Update objects.ts --- src/objects.ts | 78 +++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index be3b3d8a15..75d4e4b0ae 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -1,14 +1,14 @@ import { Question, QuestionType } from "./interfaces/question"; /** - * Create a new blank question with the given `id`, `name`, and `type`. + * Create a new blank question */ export function makeBlankQuestion( id: number, name: string, - type: QuestionType + type: QuestionType, ): Question { - const question: Question = { + return { id, name, type, @@ -16,105 +16,91 @@ export function makeBlankQuestion( expected: "", options: [], points: 1, - published: false + published: false, }; - return question; } /** - * Check if an answer is correct (ignoring case + trimming whitespace). + * Check if an answer is correct (ignoring case and whitespace) */ export function isCorrect(question: Question, answer: string): boolean { - const cleanAnswer: string = answer.trim().toLowerCase(); - const cleanExpected: string = question.expected.trim().toLowerCase(); - return cleanAnswer === cleanExpected; + return ( + answer.trim().toLowerCase() === question.expected.trim().toLowerCase() + ); } /** - * Validate an answer: any string for short_answer_question, or must match - * one of the options for multiple_choice_question. + * Check if an answer is valid for a question */ export function isValid(question: Question, answer: string): boolean { - if (question.type === "short_answer_question") { - return true; - } - return question.options.includes(answer); + return question.type === "short_answer_question" + ? true + : question.options.includes(answer); } /** - * Short form: "id: first10charsOfName". + * Short form of a question */ export function toShortForm(question: Question): string { - const shortName: string = question.name.substring(0, 10); - return `${question.id}: ${shortName}`; + return `${question.id}: ${question.name.substring(0, 10)}`; } /** - * Markdown format for a question. + * Markdown representation of a question */ export function toMarkdown(question: Question): string { - let result: string = `# ${question.name}\n${question.body}`; + let result = `# ${question.name}\n${question.body}`; if (question.type === "multiple_choice_question") { - for (const option of question.options) { - result += `\n- ${option}`; - } + result += question.options.map((opt: string) => `\n- ${opt}`).join(""); } return result; } /** - * Rename a question (new object). + * Return a renamed version of the question */ -export function renameQuestion(question: Question, newName: string): Question { - return { - ...question, - name: newName, - options: [...question.options] - }; +export function renameQuestion( + question: Question, + newName: string, +): Question { + return { ...question, name: newName, options: [...question.options] }; } /** - * Invert published status. + * Return a version with published inverted */ export function publishQuestion(question: Question): Question { - return { - ...question, - options: [...question.options], - published: !question.published - }; + return { ...question, published: !question.published, options: [...question.options] }; } /** - * Duplicate a question with new id, name prefixed by "Copy of", and reset published. + * Duplicate a question with a new id */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { return { ...oldQuestion, id, name: `Copy of ${oldQuestion.name}`, + published: false, options: [...oldQuestion.options], - published: false }; } /** - * Add a new option (copy options array). + * Add an option to a question */ export function addOption(question: Question, newOption: string): Question { - return { - ...question, - options: [...question.options, newOption] - }; + return { ...question, options: [...question.options, newOption] }; } /** - * Merge body/type/options/expected from contentQuestion with points from other. + * Merge two questions into a new one */ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number } + { points }: { points: number }, ): Question { return { id, @@ -124,6 +110,6 @@ export function mergeQuestion( expected: contentQuestion.expected, options: [...contentQuestion.options], points, - published: false + published: false, }; } From 5671736acd74f1f632fd64015eb8b28b48c8b7a8 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:16:40 -0400 Subject: [PATCH 059/135] Update typedQuestions.ts --- src/typedQuestions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/typedQuestions.ts b/src/typedQuestions.ts index 6f113aff9c..dc618da27c 100644 --- a/src/typedQuestions.ts +++ b/src/typedQuestions.ts @@ -1,7 +1,7 @@ import { Question } from "./interfaces/question"; -import rawQuestions from "./questions.json"; +import questions from "./questions.json"; -// Strongly typed array of questions -const typedQuestions: Question[] = rawQuestions as Question[]; +// Strongly type the imported questions +const typedQuestions: Question[] = questions; export default typedQuestions; From 899478db57fba178c1910084f2a8ac419ada3712 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:17:03 -0400 Subject: [PATCH 060/135] Update questions.json.ts From 6290cc2ab356e6e7144b140209d3dac3b0e61c8b Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:17:19 -0400 Subject: [PATCH 061/135] Update questions.json --- src/questions.json | 99 +++++++++++----------------------------------- 1 file changed, 22 insertions(+), 77 deletions(-) diff --git a/src/questions.json b/src/questions.json index 625b68f77f..9ad0ca40ee 100644 --- a/src/questions.json +++ b/src/questions.json @@ -1,77 +1,22 @@ -{ - "BLANK_QUESTIONS": [ - { - "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": 2, - "name": "Question 2", - "body": "", - "type": "short_answer_question", - "options": [], - "expected": "", - "points": 1, - "published": false - } - ], - "SIMPLE_QUESTIONS": [ - { - "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": false - } - ] -} - +[ + { + "id": 1, + "name": "What is 2 + 2?", + "type": "short_answer_question", + "body": "Provide a short answer.", + "expected": "4", + "options": [], + "points": 1, + "published": true + }, + { + "id": 2, + "name": "Favorite color?", + "type": "multiple_choice_question", + "body": "Choose one option.", + "expected": "Blue", + "options": ["Red", "Blue", "Green"], + "points": 2, + "published": false + } +] From 9782029bd69162e0e378d56cbc1a3a2537327767 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:19:59 -0400 Subject: [PATCH 062/135] Update objects.ts --- src/objects.ts | 72 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 75d4e4b0ae..62bdbf1e2f 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -1,12 +1,12 @@ import { Question, QuestionType } from "./interfaces/question"; /** - * Create a new blank question + * Create a new blank question. */ export function makeBlankQuestion( id: number, name: string, - type: QuestionType, + type: QuestionType ): Question { return { id, @@ -14,67 +14,77 @@ export function makeBlankQuestion( type, body: "", expected: "", - options: [], + options: [] as string[], points: 1, published: false, }; } /** - * Check if an answer is correct (ignoring case and whitespace) + * Return whether `answer` matches expected, ignoring whitespace/case. */ export function isCorrect(question: Question, answer: string): boolean { - return ( - answer.trim().toLowerCase() === question.expected.trim().toLowerCase() - ); + const cleanAnswer: string = answer.trim().toLowerCase(); + const cleanExpected: string = question.expected.trim().toLowerCase(); + return cleanAnswer === cleanExpected; } /** - * Check if an answer is valid for a question + * Return whether `answer` is valid for the question. */ export function isValid(question: Question, answer: string): boolean { - return question.type === "short_answer_question" - ? true - : question.options.includes(answer); + if (question.type === "short_answer_question") { + return true; + } + return question.options.includes(answer); } /** - * Short form of a question + * Short string representation of a question. */ export function toShortForm(question: Question): string { - return `${question.id}: ${question.name.substring(0, 10)}`; + const shortName: string = question.name.substring(0, 10); + return `${question.id}: ${shortName}`; } /** - * Markdown representation of a question + * Markdown representation of a question. */ export function toMarkdown(question: Question): string { - let result = `# ${question.name}\n${question.body}`; + let result: string = `# ${question.name}\n${question.body}`; if (question.type === "multiple_choice_question") { - result += question.options.map((opt: string) => `\n- ${opt}`).join(""); + const lines: string[] = question.options.map( + (opt: string): string => `- ${opt}` + ); + result += `\n${lines.join("\n")}`; } return result; } /** - * Return a renamed version of the question + * Return a copy of the question with a new name. */ -export function renameQuestion( - question: Question, - newName: string, -): Question { - return { ...question, name: newName, options: [...question.options] }; +export function renameQuestion(question: Question, newName: string): Question { + return { + ...question, + name: newName, + options: [...question.options], + }; } /** - * Return a version with published inverted + * Return a copy of the question with published flipped. */ export function publishQuestion(question: Question): Question { - return { ...question, published: !question.published, options: [...question.options] }; + return { + ...question, + published: !question.published, + options: [...question.options], + }; } /** - * Duplicate a question with a new id + * Duplicate a question with a new id and reset published. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { return { @@ -87,20 +97,24 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { } /** - * Add an option to a question + * Add a new option to a question. */ export function addOption(question: Question, newOption: string): Question { - return { ...question, options: [...question.options, newOption] }; + const newOptions: string[] = [...question.options, newOption]; + return { + ...question, + options: newOptions, + }; } /** - * Merge two questions into a new one + * Merge two questions into one. */ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number }, + { points }: { points: number } ): Question { return { id, From bfbb72a95e673f9d8bd947cf3f45556ac2633323 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:22:11 -0400 Subject: [PATCH 063/135] Update questions.json.ts --- src/questions.json.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/questions.json.ts b/src/questions.json.ts index b566cf0bd5..99ac983c5d 100644 --- a/src/questions.json.ts +++ b/src/questions.json.ts @@ -1,6 +1,5 @@ import { Question } from "./interfaces/question"; -import rawQuestions from "./questions.json"; +import { sampleQuestions } from "./sampleQuestions"; -const questions: Question[] = rawQuestions as Question[]; - -export default questions; +// Explicitly typed array of Question objects +export const questionsJson: Question[] = [...sampleQuestions]; From 7fd668404cf90ad4898c66a26e18e745b0d561c6 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:22:27 -0400 Subject: [PATCH 064/135] Update typedQuestions.ts --- src/typedQuestions.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/typedQuestions.ts b/src/typedQuestions.ts index dc618da27c..527af6a92c 100644 --- a/src/typedQuestions.ts +++ b/src/typedQuestions.ts @@ -1,7 +1,5 @@ import { Question } from "./interfaces/question"; -import questions from "./questions.json"; +import { questionsJson } from "./questions.json"; -// Strongly type the imported questions -const typedQuestions: Question[] = questions; - -export default typedQuestions; +// Strong typing: always a Question[] +export const typedQuestions: Question[] = [...questionsJson]; From 5f0f05d9a050f2c7cb5809e6fb61a5ad3a6b1dd8 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:26:37 -0400 Subject: [PATCH 065/135] Update questions.ts --- src/interfaces/questions.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/interfaces/questions.ts b/src/interfaces/questions.ts index 74c3a32e05..c9ef47bbaa 100644 --- a/src/interfaces/questions.ts +++ b/src/interfaces/questions.ts @@ -1,6 +1,4 @@ -// interfaces/question.ts - -export type QuestionType = "multiple_choice_question" | "short_answer_question"; +export type QuestionType = "short_answer_question" | "multiple_choice_question"; export interface Question { id: number; From 1975e0d6560fbba202bf92ec598be29b172442fb Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:27:03 -0400 Subject: [PATCH 066/135] Update objects.ts --- src/objects.ts | 92 +++++++------------------------------------------- 1 file changed, 13 insertions(+), 79 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 62bdbf1e2f..5b587a733f 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -1,12 +1,9 @@ import { Question, QuestionType } from "./interfaces/question"; -/** - * Create a new blank question. - */ export function makeBlankQuestion( id: number, name: string, - type: QuestionType + type: QuestionType, ): Question { return { id, @@ -14,116 +11,53 @@ export function makeBlankQuestion( type, body: "", expected: "", - options: [] as string[], + options: [], points: 1, published: false, }; } -/** - * Return whether `answer` matches expected, ignoring whitespace/case. - */ export function isCorrect(question: Question, answer: string): boolean { - const cleanAnswer: string = answer.trim().toLowerCase(); - const cleanExpected: string = question.expected.trim().toLowerCase(); - return cleanAnswer === cleanExpected; + return answer.trim().toLowerCase() === question.expected.trim().toLowerCase(); } -/** - * Return whether `answer` is valid for the question. - */ export function isValid(question: Question, answer: string): boolean { - if (question.type === "short_answer_question") { - return true; - } - return question.options.includes(answer); + return question.type === "short_answer_question" || question.options.includes(answer); } -/** - * Short string representation of a question. - */ export function toShortForm(question: Question): string { - const shortName: string = question.name.substring(0, 10); - return `${question.id}: ${shortName}`; + return `${question.id}: ${question.name.substring(0, 10)}`; } -/** - * Markdown representation of a question. - */ export function toMarkdown(question: Question): string { - let result: string = `# ${question.name}\n${question.body}`; + let result = `# ${question.name}\n${question.body}`; if (question.type === "multiple_choice_question") { - const lines: string[] = question.options.map( - (opt: string): string => `- ${opt}` - ); - result += `\n${lines.join("\n")}`; + question.options.forEach(option => result += `\n- ${option}`); } return result; } -/** - * Return a copy of the question with a new name. - */ export function renameQuestion(question: Question, newName: string): Question { - return { - ...question, - name: newName, - options: [...question.options], - }; + return { ...question, name: newName, options: [...question.options] }; } -/** - * Return a copy of the question with published flipped. - */ export function publishQuestion(question: Question): Question { - return { - ...question, - published: !question.published, - options: [...question.options], - }; + return { ...question, published: !question.published, options: [...question.options] }; } -/** - * Duplicate a question with a new id and reset published. - */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - return { - ...oldQuestion, - id, - name: `Copy of ${oldQuestion.name}`, - published: false, - options: [...oldQuestion.options], - }; + return { ...oldQuestion, id, name: `Copy of ${oldQuestion.name}`, published: false, options: [...oldQuestion.options] }; } -/** - * Add a new option to a question. - */ export function addOption(question: Question, newOption: string): Question { - const newOptions: string[] = [...question.options, newOption]; - return { - ...question, - options: newOptions, - }; + return { ...question, options: [...question.options, newOption] }; } -/** - * Merge two questions into one. - */ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number } + { points }: { points: number }, ): Question { - return { - id, - name, - type: contentQuestion.type, - body: contentQuestion.body, - expected: contentQuestion.expected, - options: [...contentQuestion.options], - points, - published: false, - }; + return { ...contentQuestion, id, name, points, published: false, options: [...contentQuestion.options] }; } From 462c22d09419e478b580573755d29c7f1ec55f92 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:27:29 -0400 Subject: [PATCH 067/135] Update questions.json.ts --- src/questions.json.ts | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/questions.json.ts b/src/questions.json.ts index 99ac983c5d..22c014ab11 100644 --- a/src/questions.json.ts +++ b/src/questions.json.ts @@ -1,5 +1,24 @@ import { Question } from "./interfaces/question"; -import { sampleQuestions } from "./sampleQuestions"; -// Explicitly typed array of Question objects -export const questionsJson: Question[] = [...sampleQuestions]; +export const questions: Question[] = [ + { + id: 1, + name: "Addition Problem", + type: "short_answer_question", + body: "What is 2 + 2?", + expected: "4", + options: [], + points: 1, + published: false + }, + { + id: 2, + name: "Color Question", + type: "multiple_choice_question", + body: "What is the color of the sky?", + expected: "Blue", + options: ["Blue", "Red", "Green", "Yellow"], + points: 2, + published: true + } +]; From 4bc0fc32937cf70728ebf00f62414ba20f2f3df1 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:27:52 -0400 Subject: [PATCH 068/135] Update typedQuestions.ts --- src/typedQuestions.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/typedQuestions.ts b/src/typedQuestions.ts index 527af6a92c..9c58723f0e 100644 --- a/src/typedQuestions.ts +++ b/src/typedQuestions.ts @@ -1,5 +1,4 @@ import { Question } from "./interfaces/question"; -import { questionsJson } from "./questions.json"; +import { questions } from "./questions.json"; -// Strong typing: always a Question[] -export const typedQuestions: Question[] = [...questionsJson]; +export const typedQuestions: Question[] = [...questions]; From e061f4a9dfb541a0ea1ac1b3cedf1301b1db9c92 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:28:13 -0400 Subject: [PATCH 069/135] Update global.d.ts --- src/global.d.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/global.d.ts b/src/global.d.ts index 7e7b03c92e..94956e72d8 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,7 +1,9 @@ -// This file declares JSON imports as strongly typed arrays of Questions -import { Question } from "./interfaces/question"; +import type { Question } from "./interfaces/question"; -declare module "*.json" { - const value: Question[]; - export default value; +declare global { + interface Window { + questions: Question[]; + } } + +export {}; From 19943392e2e95a48999227797e3ce7f8b3a1b2f2 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:34:07 -0400 Subject: [PATCH 070/135] Update typedQuestions.ts --- src/typedQuestions.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/typedQuestions.ts b/src/typedQuestions.ts index 9c58723f0e..0244b96760 100644 --- a/src/typedQuestions.ts +++ b/src/typedQuestions.ts @@ -1,4 +1,13 @@ +import questionsData from "./questions.json"; import { Question } from "./interfaces/question"; -import { questions } from "./questions.json"; -export const typedQuestions: Question[] = [...questions]; +export const questions: Question[] = questionsData.map((q) => ({ + id: q.id, + name: q.name, + type: q.type, + body: q.body, + expected: q.expected, + options: [...q.options], + points: q.points, + published: q.published, +})); From aaa299a98a2f1d029adeedd4fd0d0f1e01e8ef2a Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:34:37 -0400 Subject: [PATCH 071/135] Update questions.json.ts --- src/questions.json.ts | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/src/questions.json.ts b/src/questions.json.ts index 22c014ab11..1e78b66cb6 100644 --- a/src/questions.json.ts +++ b/src/questions.json.ts @@ -1,24 +1,2 @@ -import { Question } from "./interfaces/question"; - -export const questions: Question[] = [ - { - id: 1, - name: "Addition Problem", - type: "short_answer_question", - body: "What is 2 + 2?", - expected: "4", - options: [], - points: 1, - published: false - }, - { - id: 2, - name: "Color Question", - type: "multiple_choice_question", - body: "What is the color of the sky?", - expected: "Blue", - options: ["Blue", "Red", "Green", "Yellow"], - points: 2, - published: true - } -]; +import questions from "./questions.json"; +export default questions; From 4e5e7f5f7387eff2a9eaacf477bdab25cf4afa31 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:34:54 -0400 Subject: [PATCH 072/135] Update questions.json --- src/questions.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/questions.json b/src/questions.json index 9ad0ca40ee..e929065836 100644 --- a/src/questions.json +++ b/src/questions.json @@ -1,22 +1,22 @@ [ - { - "id": 1, - "name": "What is 2 + 2?", - "type": "short_answer_question", - "body": "Provide a short answer.", - "expected": "4", - "options": [], - "points": 1, - "published": true - }, - { - "id": 2, - "name": "Favorite color?", - "type": "multiple_choice_question", - "body": "Choose one option.", - "expected": "Blue", - "options": ["Red", "Blue", "Green"], - "points": 2, - "published": false - } + { + "id": 1, + "name": "Sample Question 1", + "type": "short_answer_question", + "body": "What is 2 + 2?", + "expected": "4", + "options": [], + "points": 1, + "published": false + }, + { + "id": 2, + "name": "Sample Question 2", + "type": "multiple_choice_question", + "body": "Select the correct option.", + "expected": "B", + "options": ["A", "B", "C"], + "points": 2, + "published": true + } ] From 04628743356a7a65a5e6595818d1e3831adc18b2 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:35:16 -0400 Subject: [PATCH 073/135] Update objects.ts --- src/objects.ts | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 5b587a733f..b864e8b8cb 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -3,7 +3,7 @@ import { Question, QuestionType } from "./interfaces/question"; export function makeBlankQuestion( id: number, name: string, - type: QuestionType, + type: QuestionType ): Question { return { id, @@ -22,7 +22,10 @@ export function isCorrect(question: Question, answer: string): boolean { } export function isValid(question: Question, answer: string): boolean { - return question.type === "short_answer_question" || question.options.includes(answer); + if (question.type === "short_answer_question") { + return true; + } + return question.options.includes(answer); } export function toShortForm(question: Question): string { @@ -30,11 +33,13 @@ export function toShortForm(question: Question): string { } export function toMarkdown(question: Question): string { - let result = `# ${question.name}\n${question.body}`; + let markdown = `# ${question.name}\n${question.body}`; if (question.type === "multiple_choice_question") { - question.options.forEach(option => result += `\n- ${option}`); + for (const option of question.options) { + markdown += `\n- ${option}`; + } } - return result; + return markdown; } export function renameQuestion(question: Question, newName: string): Question { @@ -46,7 +51,13 @@ export function publishQuestion(question: Question): Question { } export function duplicateQuestion(id: number, oldQuestion: Question): Question { - return { ...oldQuestion, id, name: `Copy of ${oldQuestion.name}`, published: false, options: [...oldQuestion.options] }; + return { + ...oldQuestion, + id, + name: `Copy of ${oldQuestion.name}`, + published: false, + options: [...oldQuestion.options], + }; } export function addOption(question: Question, newOption: string): Question { @@ -57,7 +68,16 @@ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number }, + { points }: { points: number } ): Question { - return { ...contentQuestion, id, name, points, published: false, options: [...contentQuestion.options] }; + return { + id, + name, + type: contentQuestion.type, + body: contentQuestion.body, + expected: contentQuestion.expected, + options: [...contentQuestion.options], + points, + published: false, + }; } From 333946921b0c7e6dd42bb5163eba0030755512e8 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:35:44 -0400 Subject: [PATCH 074/135] Update questions.ts From 2df48f297711ea5704a1c04a6ca1a1aba0b43c1d Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:36:04 -0400 Subject: [PATCH 075/135] Update global.d.ts --- src/global.d.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/global.d.ts b/src/global.d.ts index 94956e72d8..9c0f9101af 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,9 +1,5 @@ -import type { Question } from "./interfaces/question"; - -declare global { - interface Window { - questions: Question[]; - } +declare module "*.json" { + import { Question } from "./interfaces/question"; + const value: Question[]; + export default value; } - -export {}; From 1a3cb3128f2df9fba5fd4eed0dd96496531de62c Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:48:36 -0400 Subject: [PATCH 076/135] Update global.d.ts From bd604592fab3d74ab3546f4a97bcedb0c1697414 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:48:57 -0400 Subject: [PATCH 077/135] Update questions.ts From 1e34f7417c486b21749b7effdc7cfe99bf19dc07 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:49:19 -0400 Subject: [PATCH 078/135] Update objects.ts --- src/objects.ts | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index b864e8b8cb..a57a07e7e4 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -18,7 +18,9 @@ export function makeBlankQuestion( } export function isCorrect(question: Question, answer: string): boolean { - return answer.trim().toLowerCase() === question.expected.trim().toLowerCase(); + const cleanAnswer: string = answer.trim().toLowerCase(); + const cleanExpected: string = question.expected.trim().toLowerCase(); + return cleanAnswer === cleanExpected; } export function isValid(question: Question, answer: string): boolean { @@ -33,9 +35,10 @@ export function toShortForm(question: Question): string { } export function toMarkdown(question: Question): string { - let markdown = `# ${question.name}\n${question.body}`; + let markdown: string = `# ${question.name}\n${question.body}`; if (question.type === "multiple_choice_question") { - for (const option of question.options) { + const optionsCopy: string[] = [...question.options]; + for (const option of optionsCopy) { markdown += `\n- ${option}`; } } @@ -43,25 +46,23 @@ export function toMarkdown(question: Question): string { } export function renameQuestion(question: Question, newName: string): Question { - return { ...question, name: newName, options: [...question.options] }; + const optionsCopy: string[] = [...question.options]; + return { ...question, name: newName, options: optionsCopy }; } export function publishQuestion(question: Question): Question { - return { ...question, published: !question.published, options: [...question.options] }; + const optionsCopy: string[] = [...question.options]; + return { ...question, published: !question.published, options: optionsCopy }; } export function duplicateQuestion(id: number, oldQuestion: Question): Question { - return { - ...oldQuestion, - id, - name: `Copy of ${oldQuestion.name}`, - published: false, - options: [...oldQuestion.options], - }; + const optionsCopy: string[] = [...oldQuestion.options]; + return { ...oldQuestion, id, name: `Copy of ${oldQuestion.name}`, published: false, options: optionsCopy }; } export function addOption(question: Question, newOption: string): Question { - return { ...question, options: [...question.options, newOption] }; + const optionsCopy: string[] = [...question.options, newOption]; + return { ...question, options: optionsCopy }; } export function mergeQuestion( @@ -70,13 +71,14 @@ export function mergeQuestion( contentQuestion: Question, { points }: { points: number } ): Question { + const optionsCopy: string[] = [...contentQuestion.options]; return { id, name, type: contentQuestion.type, body: contentQuestion.body, expected: contentQuestion.expected, - options: [...contentQuestion.options], + options: optionsCopy, points, published: false, }; From 831fb374291f94152caa0981e6d684d29536126e Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:49:49 -0400 Subject: [PATCH 079/135] Update questions.json --- src/questions.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/questions.json b/src/questions.json index e929065836..9be5ea6f22 100644 --- a/src/questions.json +++ b/src/questions.json @@ -20,3 +20,4 @@ "published": true } ] + From cb8a711015fe487a63b31a488459a8bd5b43dcca Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:50:25 -0400 Subject: [PATCH 080/135] Update questions.json.ts --- src/questions.json.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/questions.json.ts b/src/questions.json.ts index 1e78b66cb6..82174ea7e8 100644 --- a/src/questions.json.ts +++ b/src/questions.json.ts @@ -1,2 +1,15 @@ import questions from "./questions.json"; -export default questions; +import { Question } from "./interfaces/question"; + +const typedQuestions: Question[] = questions.map((q: Question) => ({ + id: q.id, + name: q.name, + type: q.type, + body: q.body, + expected: q.expected, + options: [...q.options], + points: q.points, + published: q.published, +})); + +export default typedQuestions; From a1f723d5bb07a9a8b5c7cfbdeae88014d57bf0cd Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 17:50:50 -0400 Subject: [PATCH 081/135] Update typedQuestions.ts --- src/typedQuestions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/typedQuestions.ts b/src/typedQuestions.ts index 0244b96760..bbe70d4bc0 100644 --- a/src/typedQuestions.ts +++ b/src/typedQuestions.ts @@ -1,7 +1,7 @@ import questionsData from "./questions.json"; import { Question } from "./interfaces/question"; -export const questions: Question[] = questionsData.map((q) => ({ +export const questions: Question[] = questionsData.map((q: Question) => ({ id: q.id, name: q.name, type: q.type, From ff675ee23e805b3727c11aba3f3362ddb290adb3 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 18:35:41 -0400 Subject: [PATCH 082/135] Create tsconfig.json --- src/tsconfig.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/tsconfig.json diff --git a/src/tsconfig.json b/src/tsconfig.json new file mode 100644 index 0000000000..9be842205f --- /dev/null +++ b/src/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "noImplicitAny": true + }, + "include": ["src"] +} From 126476280bb97b4d5e32ae81080fb0e702dde2f5 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 18:37:21 -0400 Subject: [PATCH 083/135] Update objects.ts --- src/objects.ts | 132 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 24 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index a57a07e7e4..d70faa676c 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -1,5 +1,10 @@ import { Question, QuestionType } from "./interfaces/question"; +/** + * Create a new blank question with the given `id`, `name`, and `type. The `body` and + * `expected` should be empty strings, the `options` should be an empty list, the `points` + * should default to 1, and `published` should default to false. + */ export function makeBlankQuestion( id: number, name: string, @@ -13,73 +18,152 @@ export function makeBlankQuestion( expected: "", options: [], points: 1, - published: false, + published: false }; } +/** + * Consumes a question and a potential `answer`, and returns whether or not + * the `answer` is correct. You should check that the `answer` is equal to + * the `expected`, ignoring capitalization and trimming any whitespace. + * + * HINT: Look up the `trim` and `toLowerCase` functions. + */ export function isCorrect(question: Question, answer: string): boolean { - const cleanAnswer: string = answer.trim().toLowerCase(); - const cleanExpected: string = question.expected.trim().toLowerCase(); - return cleanAnswer === cleanExpected; + const trimmedAnswer = answer.trim().toLowerCase(); + const trimmedExpected = question.expected.trim().toLowerCase(); + return trimmedAnswer === trimmedExpected; } +/** + * Consumes a question and a potential `answer`, and returns whether or not + * the `answer` is valid (but not necessarily correct). For a `short_answer_question`, + * any answer is valid. But for a `multiple_choice_question`, the `answer` must + * be exactly one of the options. + */ export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { return true; + } else if (question.type === "multiple_choice_question") { + return question.options.includes(answer); } - return question.options.includes(answer); + return false; } +/** + * Consumes a question and produces a string representation combining the + * `id` and first 10 characters of the `name`. The two strings should be + * separated by ": ". So for example, the question with id 9 and the + * name "My First Question" would become "9: My First Q". + */ export function toShortForm(question: Question): string { - return `${question.id}: ${question.name.substring(0, 10)}`; + const shortName = question.name.substring(0, 10); + return `${question.id}: ${shortName}`; } +/** + * Consumes a question and returns a formatted string representation as follows: + * - The first line should be a hash sign, a space, and then the `name` + * - The second line should be the `body` + * - If the question is a `multiple_choice_question`, then the following lines + * need to show each option on its line, preceded by a dash and space. + * + * The example below might help, but don't include the border! + * ----------Example------------- + * |# Name | + * |The body goes here! | + * |- Option 1 | + * |- Option 2 | + * |- Option 3 | + * ------------------------------ + * Check the unit tests for more examples of what this looks like! + */ export function toMarkdown(question: Question): string { - let markdown: string = `# ${question.name}\n${question.body}`; + let result = `# ${question.name}\n${question.body}`; + if (question.type === "multiple_choice_question") { - const optionsCopy: string[] = [...question.options]; - for (const option of optionsCopy) { - markdown += `\n- ${option}`; - } + question.options.forEach(option => { + result += `\n- ${option}`; + }); } - return markdown; + + return result; } +/** + * Return a new version of the given question, except the name should now be + * `newName`. + */ export function renameQuestion(question: Question, newName: string): Question { - const optionsCopy: string[] = [...question.options]; - return { ...question, name: newName, options: optionsCopy }; + return { + ...question, + name: newName + }; } +/** + * Return a new version of the given question, except the `published` field + * should be inverted. If the question was not published, now it should be + * published; if it was published, now it should be not published. + */ export function publishQuestion(question: Question): Question { - const optionsCopy: string[] = [...question.options]; - return { ...question, published: !question.published, options: optionsCopy }; + return { + ...question, + published: !question.published + }; } +/** + * Create a new question based on the old question, copying over its `body`, `type`, + * `options`, `expected`, and `points` without changes. The `name` should be copied + * over as "Copy of ORIGINAL NAME" (e.g., so "Question 1" would become "Copy of Question 1"). + * The `published` field should be reset to false. + */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - const optionsCopy: string[] = [...oldQuestion.options]; - return { ...oldQuestion, id, name: `Copy of ${oldQuestion.name}`, published: false, options: optionsCopy }; + return { + ...oldQuestion, + id, + name: `Copy of ${oldQuestion.name}`, + published: false + }; } +/** + * Return a new version of the given question, with the `newOption` added to + * the list of existing `options`. Remember that the new Question MUST have + * its own separate copy of the `options` list, rather than the same reference + * to the original question's list! + * Check out the subsection about "Nested Fields" for more information. + */ export function addOption(question: Question, newOption: string): Question { - const optionsCopy: string[] = [...question.options, newOption]; - return { ...question, options: optionsCopy }; + return { + ...question, + options: [...question.options, newOption] + }; } +/** + * Consumes an id, name, and two questions, and produces a new question. + * The new question will use the `body`, `type`, `options`, and `expected` of the + * `contentQuestion`. The second question will provide the `points`. + * The `published` status should be set to false. + * Notice that the second Question is provided as just an object with a `points` + * field; but the function call would be the same as if it were a `Question` type! + */ export function mergeQuestion( id: number, name: string, contentQuestion: Question, { points }: { points: number } ): Question { - const optionsCopy: string[] = [...contentQuestion.options]; return { id, name, - type: contentQuestion.type, body: contentQuestion.body, + type: contentQuestion.type, + options: [...contentQuestion.options], expected: contentQuestion.expected, - options: optionsCopy, points, - published: false, + published: false }; } From 878a14151566d0ca8a4cfc4818525c8126a684fd Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 18:50:56 -0400 Subject: [PATCH 084/135] Update questions.ts --- src/interfaces/questions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/interfaces/questions.ts b/src/interfaces/questions.ts index c9ef47bbaa..e0a1aff838 100644 --- a/src/interfaces/questions.ts +++ b/src/interfaces/questions.ts @@ -1,4 +1,5 @@ -export type QuestionType = "short_answer_question" | "multiple_choice_question"; +// interfaces/question.ts +export type QuestionType = "multiple_choice_question" | "short_answer_question"; export interface Question { id: number; From 84d888f4e9b8404191d3d441c2e539142f0afd7c Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 18:54:53 -0400 Subject: [PATCH 085/135] Update objects.ts --- src/objects.ts | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index d70faa676c..27a56a310f 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -30,9 +30,9 @@ export function makeBlankQuestion( * HINT: Look up the `trim` and `toLowerCase` functions. */ export function isCorrect(question: Question, answer: string): boolean { - const trimmedAnswer = answer.trim().toLowerCase(); - const trimmedExpected = question.expected.trim().toLowerCase(); - return trimmedAnswer === trimmedExpected; + const normalizedAnswer = answer.trim().toLowerCase(); + const normalizedExpected = question.expected.trim().toLowerCase(); + return normalizedAnswer === normalizedExpected; } /** @@ -57,8 +57,8 @@ export function isValid(question: Question, answer: string): boolean { * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { - const shortName = question.name.substring(0, 10); - return `${question.id}: ${shortName}`; + const truncatedName = question.name.substring(0, 10); + return `${question.id}: ${truncatedName}`; } /** @@ -82,9 +82,9 @@ export function toMarkdown(question: Question): string { let result = `# ${question.name}\n${question.body}`; if (question.type === "multiple_choice_question") { - question.options.forEach(option => { + for (const option of question.options) { result += `\n- ${option}`; - }); + } } return result; @@ -95,10 +95,11 @@ export function toMarkdown(question: Question): string { * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - return { + const newQuestion: Question = { ...question, name: newName }; + return newQuestion; } /** @@ -107,10 +108,11 @@ export function renameQuestion(question: Question, newName: string): Question { * published; if it was published, now it should be not published. */ export function publishQuestion(question: Question): Question { - return { + const newQuestion: Question = { ...question, published: !question.published }; + return newQuestion; } /** @@ -120,12 +122,13 @@ export function publishQuestion(question: Question): Question { * The `published` field should be reset to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - return { + const newQuestion: Question = { ...oldQuestion, id, name: `Copy of ${oldQuestion.name}`, published: false }; + return newQuestion; } /** @@ -136,10 +139,12 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { * Check out the subsection about "Nested Fields" for more information. */ export function addOption(question: Question, newOption: string): Question { - return { + const newOptions = [...question.options, newOption]; + const newQuestion: Question = { ...question, - options: [...question.options, newOption] + options: newOptions }; + return newQuestion; } /** @@ -156,7 +161,7 @@ export function mergeQuestion( contentQuestion: Question, { points }: { points: number } ): Question { - return { + const newQuestion: Question = { id, name, body: contentQuestion.body, @@ -166,4 +171,5 @@ export function mergeQuestion( points, published: false }; + return newQuestion; } From b8f4824e74e8c42792266b192dcfa252ffd9e2bd Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 19:01:17 -0400 Subject: [PATCH 086/135] Update objects.ts --- src/objects.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 27a56a310f..18a3fafc2d 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -10,7 +10,7 @@ export function makeBlankQuestion( name: string, type: QuestionType ): Question { - return { + const newQuestion: Question = { id, name, type, @@ -20,6 +20,7 @@ export function makeBlankQuestion( points: 1, published: false }; + return newQuestion; } /** @@ -30,8 +31,8 @@ export function makeBlankQuestion( * HINT: Look up the `trim` and `toLowerCase` functions. */ export function isCorrect(question: Question, answer: string): boolean { - const normalizedAnswer = answer.trim().toLowerCase(); - const normalizedExpected = question.expected.trim().toLowerCase(); + const normalizedAnswer: string = answer.trim().toLowerCase(); + const normalizedExpected: string = question.expected.trim().toLowerCase(); return normalizedAnswer === normalizedExpected; } @@ -57,7 +58,7 @@ export function isValid(question: Question, answer: string): boolean { * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { - const truncatedName = question.name.substring(0, 10); + const truncatedName: string = question.name.substring(0, 10); return `${question.id}: ${truncatedName}`; } @@ -79,15 +80,15 @@ export function toShortForm(question: Question): string { * Check the unit tests for more examples of what this looks like! */ export function toMarkdown(question: Question): string { - let result = `# ${question.name}\n${question.body}`; + const lines: string[] = [`# ${question.name}`, question.body]; if (question.type === "multiple_choice_question") { for (const option of question.options) { - result += `\n- ${option}`; + lines.push(`- ${option}`); } } - return result; + return lines.join("\n"); } /** @@ -139,7 +140,7 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { * Check out the subsection about "Nested Fields" for more information. */ export function addOption(question: Question, newOption: string): Question { - const newOptions = [...question.options, newOption]; + const newOptions: string[] = [...question.options, newOption]; const newQuestion: Question = { ...question, options: newOptions @@ -166,7 +167,7 @@ export function mergeQuestion( name, body: contentQuestion.body, type: contentQuestion.type, - options: [...contentQuestion.options], + options: [...contentQuestion.options], // Create a new array copy expected: contentQuestion.expected, points, published: false From 2fd602db84d2c7afaf72fd4ca83e2c2a568f14b5 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 19:06:06 -0400 Subject: [PATCH 087/135] Update objects.ts --- src/objects.ts | 115 ++++++++++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 49 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 18a3fafc2d..1a757062dc 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -10,17 +10,18 @@ export function makeBlankQuestion( name: string, type: QuestionType ): Question { - const newQuestion: Question = { - id, - name, - type, - body: "", - expected: "", - options: [], - points: 1, - published: false + // Construct and return a new question object with default values + const blankQuestion: Question = { + id: id, + name: name, + type: type, + body: "", // Empty body by default + expected: "", // Empty expected answer by default + options: [], // Start with empty options array + points: 1, // Default to 1 point + published: false // Initially unpublished }; - return newQuestion; + return blankQuestion; } /** @@ -31,9 +32,12 @@ export function makeBlankQuestion( * HINT: Look up the `trim` and `toLowerCase` functions. */ export function isCorrect(question: Question, answer: string): boolean { - const normalizedAnswer: string = answer.trim().toLowerCase(); - const normalizedExpected: string = question.expected.trim().toLowerCase(); - return normalizedAnswer === normalizedExpected; + // Normalize both strings for case-insensitive comparison + const processedUserAnswer = answer.trim().toLowerCase(); + const processedCorrectAnswer = question.expected.trim().toLowerCase(); + + // Compare the normalized strings + return processedUserAnswer === processedCorrectAnswer; } /** @@ -43,12 +47,12 @@ export function isCorrect(question: Question, answer: string): boolean { * be exactly one of the options. */ export function isValid(question: Question, answer: string): boolean { + // Short answer questions accept any response if (question.type === "short_answer_question") { return true; - } else if (question.type === "multiple_choice_question") { - return question.options.includes(answer); } - return false; + // Multiple choice requires the answer to be in the options list + return question.options.includes(answer); } /** @@ -58,8 +62,10 @@ export function isValid(question: Question, answer: string): boolean { * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { - const truncatedName: string = question.name.substring(0, 10); - return `${question.id}: ${truncatedName}`; + // Extract first 10 characters of the name + const abbreviatedName = question.name.slice(0, 10); + // Combine ID and abbreviated name + return `${question.id}: ${abbreviatedName}`; } /** @@ -80,15 +86,18 @@ export function toShortForm(question: Question): string { * Check the unit tests for more examples of what this looks like! */ export function toMarkdown(question: Question): string { - const lines: string[] = [`# ${question.name}`, question.body]; + // Start with name and body + let markdownOutput = `# ${question.name}\n${question.body}`; + // Add options if it's a multiple choice question if (question.type === "multiple_choice_question") { - for (const option of question.options) { - lines.push(`- ${option}`); - } + const formattedOptions = question.options + .map(option => `- ${option}`) + .join("\n"); + markdownOutput += `\n${formattedOptions}`; } - return lines.join("\n"); + return markdownOutput; } /** @@ -96,11 +105,12 @@ export function toMarkdown(question: Question): string { * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - const newQuestion: Question = { - ...question, - name: newName + // Create new object with updated name (immutable update) + const updatedQuestion = { + ...question, + name: newName }; - return newQuestion; + return updatedQuestion; } /** @@ -109,11 +119,12 @@ export function renameQuestion(question: Question, newName: string): Question { * published; if it was published, now it should be not published. */ export function publishQuestion(question: Question): Question { - const newQuestion: Question = { - ...question, - published: !question.published + // Toggle published status (immutable update) + const questionWithToggledPublish = { + ...question, + published: !question.published }; - return newQuestion; + return questionWithToggledPublish; } /** @@ -123,13 +134,18 @@ export function publishQuestion(question: Question): Question { * The `published` field should be reset to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - const newQuestion: Question = { - ...oldQuestion, - id, - name: `Copy of ${oldQuestion.name}`, - published: false + // Create a duplicate with new ID and modified name + const questionDuplicate: Question = { + id: id, // New ID provided as parameter + name: `Copy of ${oldQuestion.name}`, // Prefix with "Copy of" + body: oldQuestion.body, + type: oldQuestion.type, + options: [...oldQuestion.options], // Create new array reference + expected: oldQuestion.expected, + points: oldQuestion.points, + published: false, // Always unpublished when duplicated }; - return newQuestion; + return questionDuplicate; } /** @@ -140,12 +156,12 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { * Check out the subsection about "Nested Fields" for more information. */ export function addOption(question: Question, newOption: string): Question { - const newOptions: string[] = [...question.options, newOption]; - const newQuestion: Question = { - ...question, - options: newOptions + // Create new question with extended options array + const questionWithNewOption = { + ...question, + options: [...question.options, newOption] // New array with added option }; - return newQuestion; + return questionWithNewOption; } /** @@ -162,15 +178,16 @@ export function mergeQuestion( contentQuestion: Question, { points }: { points: number } ): Question { - const newQuestion: Question = { - id, - name, + // Merge content from contentQuestion with points from second parameter + const mergedQuestionResult: Question = { + id: id, + name: name, body: contentQuestion.body, type: contentQuestion.type, - options: [...contentQuestion.options], // Create a new array copy + options: [...contentQuestion.options], // Copy options array expected: contentQuestion.expected, - points, - published: false + points: points, // Points from the points parameter + published: false, // Always unpublished when merged }; - return newQuestion; + return mergedQuestionResult; } From b0ed9bcc8eb1089dae39f7dc349d35735deffd85 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 19:21:08 -0400 Subject: [PATCH 088/135] Update questions.json.ts --- src/questions.json.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/questions.json.ts b/src/questions.json.ts index 82174ea7e8..30378e6666 100644 --- a/src/questions.json.ts +++ b/src/questions.json.ts @@ -1,3 +1,4 @@ +/* import questions from "./questions.json"; import { Question } from "./interfaces/question"; @@ -13,3 +14,4 @@ const typedQuestions: Question[] = questions.map((q: Question) => ({ })); export default typedQuestions; +*/ From 9a9c8dcf178088acdc4b0f83ae01491796fc0f8c Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 19:22:47 -0400 Subject: [PATCH 089/135] Update typedQuestions.ts --- src/typedQuestions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/typedQuestions.ts b/src/typedQuestions.ts index bbe70d4bc0..d22b6511f6 100644 --- a/src/typedQuestions.ts +++ b/src/typedQuestions.ts @@ -1,3 +1,4 @@ +/* import questionsData from "./questions.json"; import { Question } from "./interfaces/question"; @@ -11,3 +12,4 @@ export const questions: Question[] = questionsData.map((q: Question) => ({ points: q.points, published: q.published, })); +*/ From 4b5031b10bceffbc5019e15b9902a6a670c2c635 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 19:25:25 -0400 Subject: [PATCH 090/135] Update objects.ts From d537eea2cbb155580a1377d5919d6d1a515e48b0 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 19:29:12 -0400 Subject: [PATCH 091/135] Update objects.ts --- src/objects.ts | 93 ++++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 52 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 1a757062dc..e3a11f2871 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -10,18 +10,17 @@ export function makeBlankQuestion( name: string, type: QuestionType ): Question { - // Construct and return a new question object with default values - const blankQuestion: Question = { + const newQuestion: Question = { id: id, name: name, type: type, - body: "", // Empty body by default - expected: "", // Empty expected answer by default - options: [], // Start with empty options array - points: 1, // Default to 1 point - published: false // Initially unpublished + body: "", + expected: "", + options: [], + points: 1, + published: false }; - return blankQuestion; + return newQuestion; } /** @@ -32,12 +31,10 @@ export function makeBlankQuestion( * HINT: Look up the `trim` and `toLowerCase` functions. */ export function isCorrect(question: Question, answer: string): boolean { - // Normalize both strings for case-insensitive comparison - const processedUserAnswer = answer.trim().toLowerCase(); - const processedCorrectAnswer = question.expected.trim().toLowerCase(); - - // Compare the normalized strings - return processedUserAnswer === processedCorrectAnswer; + const normalizedAnswer: string = answer.trim().toLowerCase(); + const normalizedExpected: string = question.expected.trim().toLowerCase(); + const isAnswerCorrect: boolean = normalizedAnswer === normalizedExpected; + return isAnswerCorrect; } /** @@ -47,12 +44,11 @@ export function isCorrect(question: Question, answer: string): boolean { * be exactly one of the options. */ export function isValid(question: Question, answer: string): boolean { - // Short answer questions accept any response if (question.type === "short_answer_question") { return true; } - // Multiple choice requires the answer to be in the options list - return question.options.includes(answer); + const isValidAnswer: boolean = question.options.includes(answer); + return isValidAnswer; } /** @@ -62,10 +58,9 @@ export function isValid(question: Question, answer: string): boolean { * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { - // Extract first 10 characters of the name - const abbreviatedName = question.name.slice(0, 10); - // Combine ID and abbreviated name - return `${question.id}: ${abbreviatedName}`; + const shortName: string = question.name.slice(0, 10); + const shortFormResult: string = `${question.id}: ${shortName}`; + return shortFormResult; } /** @@ -86,18 +81,16 @@ export function toShortForm(question: Question): string { * Check the unit tests for more examples of what this looks like! */ export function toMarkdown(question: Question): string { - // Start with name and body - let markdownOutput = `# ${question.name}\n${question.body}`; + const lines: string[] = [`# ${question.name}`, question.body]; - // Add options if it's a multiple choice question if (question.type === "multiple_choice_question") { - const formattedOptions = question.options - .map(option => `- ${option}`) - .join("\n"); - markdownOutput += `\n${formattedOptions}`; + for (const option of question.options) { + lines.push(`- ${option}`); + } } - return markdownOutput; + const markdownResult: string = lines.join("\n"); + return markdownResult; } /** @@ -105,12 +98,11 @@ export function toMarkdown(question: Question): string { * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - // Create new object with updated name (immutable update) - const updatedQuestion = { + const renamed: Question = { ...question, name: newName }; - return updatedQuestion; + return renamed; } /** @@ -119,12 +111,11 @@ export function renameQuestion(question: Question, newName: string): Question { * published; if it was published, now it should be not published. */ export function publishQuestion(question: Question): Question { - // Toggle published status (immutable update) - const questionWithToggledPublish = { + const published: Question = { ...question, published: !question.published }; - return questionWithToggledPublish; + return published; } /** @@ -134,18 +125,17 @@ export function publishQuestion(question: Question): Question { * The `published` field should be reset to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - // Create a duplicate with new ID and modified name - const questionDuplicate: Question = { - id: id, // New ID provided as parameter - name: `Copy of ${oldQuestion.name}`, // Prefix with "Copy of" + const duplicated: Question = { + id: id, + name: `Copy of ${oldQuestion.name}`, body: oldQuestion.body, type: oldQuestion.type, - options: [...oldQuestion.options], // Create new array reference + options: [...oldQuestion.options], expected: oldQuestion.expected, points: oldQuestion.points, - published: false, // Always unpublished when duplicated + published: false }; - return questionDuplicate; + return duplicated; } /** @@ -156,12 +146,12 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { * Check out the subsection about "Nested Fields" for more information. */ export function addOption(question: Question, newOption: string): Question { - // Create new question with extended options array - const questionWithNewOption = { + const newOptions: string[] = [...question.options, newOption]; + const withNewOption: Question = { ...question, - options: [...question.options, newOption] // New array with added option + options: newOptions }; - return questionWithNewOption; + return withNewOption; } /** @@ -178,16 +168,15 @@ export function mergeQuestion( contentQuestion: Question, { points }: { points: number } ): Question { - // Merge content from contentQuestion with points from second parameter - const mergedQuestionResult: Question = { + const merged: Question = { id: id, name: name, body: contentQuestion.body, type: contentQuestion.type, - options: [...contentQuestion.options], // Copy options array + options: [...contentQuestion.options], expected: contentQuestion.expected, - points: points, // Points from the points parameter - published: false, // Always unpublished when merged + points: points, + published: false }; - return mergedQuestionResult; + return merged; } From da030625b9506d03a75cfa65b1b4a172e79675ca Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 19:32:30 -0400 Subject: [PATCH 092/135] Update objects.ts --- src/objects.ts | 82 ++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index e3a11f2871..8de6a47f38 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -10,17 +10,17 @@ export function makeBlankQuestion( name: string, type: QuestionType ): Question { - const newQuestion: Question = { - id: id, - name: name, - type: type, + const blankQuestion: Question = { + id, + name, + type, body: "", expected: "", options: [], points: 1, published: false }; - return newQuestion; + return blankQuestion; } /** @@ -33,8 +33,8 @@ export function makeBlankQuestion( export function isCorrect(question: Question, answer: string): boolean { const normalizedAnswer: string = answer.trim().toLowerCase(); const normalizedExpected: string = question.expected.trim().toLowerCase(); - const isAnswerCorrect: boolean = normalizedAnswer === normalizedExpected; - return isAnswerCorrect; + const isCorrectAnswer: boolean = normalizedAnswer === normalizedExpected; + return isCorrectAnswer; } /** @@ -47,8 +47,8 @@ export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { return true; } - const isValidAnswer: boolean = question.options.includes(answer); - return isValidAnswer; + const isValidChoice: boolean = question.options.includes(answer); + return isValidChoice; } /** @@ -58,9 +58,9 @@ export function isValid(question: Question, answer: string): boolean { * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { - const shortName: string = question.name.slice(0, 10); - const shortFormResult: string = `${question.id}: ${shortName}`; - return shortFormResult; + const truncatedName: string = question.name.substring(0, 10); + const shortForm: string = `${question.id}: ${truncatedName}`; + return shortForm; } /** @@ -81,16 +81,18 @@ export function toShortForm(question: Question): string { * Check the unit tests for more examples of what this looks like! */ export function toMarkdown(question: Question): string { - const lines: string[] = [`# ${question.name}`, question.body]; + const header: string = `# ${question.name}`; + const bodyLine: string = question.body; + const lines: string[] = [header, bodyLine]; if (question.type === "multiple_choice_question") { - for (const option of question.options) { + question.options.forEach((option: string): void => { lines.push(`- ${option}`); - } + }); } - const markdownResult: string = lines.join("\n"); - return markdownResult; + const markdown: string = lines.join("\n"); + return markdown; } /** @@ -98,11 +100,11 @@ export function toMarkdown(question: Question): string { * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - const renamed: Question = { - ...question, - name: newName + const renamedQuestion: Question = { + ...question, + name: newName }; - return renamed; + return renamedQuestion; } /** @@ -111,11 +113,11 @@ export function renameQuestion(question: Question, newName: string): Question { * published; if it was published, now it should be not published. */ export function publishQuestion(question: Question): Question { - const published: Question = { - ...question, - published: !question.published + const publishedQuestion: Question = { + ...question, + published: !question.published }; - return published; + return publishedQuestion; } /** @@ -125,17 +127,13 @@ export function publishQuestion(question: Question): Question { * The `published` field should be reset to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - const duplicated: Question = { - id: id, + const duplicatedQuestion: Question = { + ...oldQuestion, + id, name: `Copy of ${oldQuestion.name}`, - body: oldQuestion.body, - type: oldQuestion.type, - options: [...oldQuestion.options], - expected: oldQuestion.expected, - points: oldQuestion.points, published: false }; - return duplicated; + return duplicatedQuestion; } /** @@ -147,11 +145,11 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { */ export function addOption(question: Question, newOption: string): Question { const newOptions: string[] = [...question.options, newOption]; - const withNewOption: Question = { - ...question, - options: newOptions + const questionWithOption: Question = { + ...question, + options: newOptions }; - return withNewOption; + return questionWithOption; } /** @@ -168,15 +166,15 @@ export function mergeQuestion( contentQuestion: Question, { points }: { points: number } ): Question { - const merged: Question = { - id: id, - name: name, + const mergedQuestion: Question = { + id, + name, body: contentQuestion.body, type: contentQuestion.type, options: [...contentQuestion.options], expected: contentQuestion.expected, - points: points, + points, published: false }; - return merged; + return mergedQuestion; } From c03ed83198cac0bf13573fe5f0db16478d3932a2 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 19:38:02 -0400 Subject: [PATCH 093/135] Update objects.ts --- src/objects.ts | 76 ++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 8de6a47f38..fa9a32d42d 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -10,17 +10,17 @@ export function makeBlankQuestion( name: string, type: QuestionType ): Question { - const blankQuestion: Question = { - id, - name, - type, + const newQuestion: Question = { + id: id, + name: name, + type: type, body: "", expected: "", options: [], points: 1, published: false }; - return blankQuestion; + return newQuestion; } /** @@ -33,8 +33,8 @@ export function makeBlankQuestion( export function isCorrect(question: Question, answer: string): boolean { const normalizedAnswer: string = answer.trim().toLowerCase(); const normalizedExpected: string = question.expected.trim().toLowerCase(); - const isCorrectAnswer: boolean = normalizedAnswer === normalizedExpected; - return isCorrectAnswer; + const isAnswerCorrect: boolean = normalizedAnswer === normalizedExpected; + return isAnswerCorrect; } /** @@ -47,8 +47,8 @@ export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { return true; } - const isValidChoice: boolean = question.options.includes(answer); - return isValidChoice; + const isValidAnswer: boolean = question.options.includes(answer); + return isValidAnswer; } /** @@ -58,9 +58,9 @@ export function isValid(question: Question, answer: string): boolean { * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { - const truncatedName: string = question.name.substring(0, 10); - const shortForm: string = `${question.id}: ${truncatedName}`; - return shortForm; + const shortName: string = question.name.slice(0, 10); + const shortFormResult: string = `${question.id}: ${shortName}`; + return shortFormResult; } /** @@ -81,18 +81,16 @@ export function toShortForm(question: Question): string { * Check the unit tests for more examples of what this looks like! */ export function toMarkdown(question: Question): string { - const header: string = `# ${question.name}`; - const bodyLine: string = question.body; - const lines: string[] = [header, bodyLine]; + const lines: string[] = [`# ${question.name}`, question.body]; if (question.type === "multiple_choice_question") { - question.options.forEach((option: string): void => { + for (const option of question.options) { lines.push(`- ${option}`); - }); + } } - const markdown: string = lines.join("\n"); - return markdown; + const markdownResult: string = lines.join("\n"); + return markdownResult; } /** @@ -100,11 +98,11 @@ export function toMarkdown(question: Question): string { * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - const renamedQuestion: Question = { - ...question, - name: newName + const renamed: Question = { + ...question, + name: newName }; - return renamedQuestion; + return renamed; } /** @@ -113,11 +111,11 @@ export function renameQuestion(question: Question, newName: string): Question { * published; if it was published, now it should be not published. */ export function publishQuestion(question: Question): Question { - const publishedQuestion: Question = { - ...question, - published: !question.published + const published: Question = { + ...question, + published: !question.published }; - return publishedQuestion; + return published; } /** @@ -127,13 +125,13 @@ export function publishQuestion(question: Question): Question { * The `published` field should be reset to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - const duplicatedQuestion: Question = { + const duplicated: Question = { ...oldQuestion, - id, + id: id, name: `Copy of ${oldQuestion.name}`, published: false }; - return duplicatedQuestion; + return duplicated; } /** @@ -145,11 +143,11 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { */ export function addOption(question: Question, newOption: string): Question { const newOptions: string[] = [...question.options, newOption]; - const questionWithOption: Question = { - ...question, - options: newOptions + const withNewOption: Question = { + ...question, + options: newOptions }; - return questionWithOption; + return withNewOption; } /** @@ -166,15 +164,15 @@ export function mergeQuestion( contentQuestion: Question, { points }: { points: number } ): Question { - const mergedQuestion: Question = { - id, - name, + const merged: Question = { + id: id, + name: name, body: contentQuestion.body, type: contentQuestion.type, options: [...contentQuestion.options], expected: contentQuestion.expected, - points, + points: points, published: false }; - return mergedQuestion; + return merged; } From 64933cff5a8198e916848bc0056654cb6e025b0a Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 19:39:18 -0400 Subject: [PATCH 094/135] Update objects.ts --- src/objects.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/objects.ts b/src/objects.ts index fa9a32d42d..e3a11f2871 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -126,9 +126,13 @@ export function publishQuestion(question: Question): Question { */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { const duplicated: Question = { - ...oldQuestion, id: id, name: `Copy of ${oldQuestion.name}`, + body: oldQuestion.body, + type: oldQuestion.type, + options: [...oldQuestion.options], + expected: oldQuestion.expected, + points: oldQuestion.points, published: false }; return duplicated; From e9386e34c5352d47f6021d7977551ab2ec367826 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 19:41:56 -0400 Subject: [PATCH 095/135] Update objects.ts From 07d088ad07edea01948accf226c1453a0ed025ee Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 19:44:22 -0400 Subject: [PATCH 096/135] Update objects.ts --- src/objects.ts | 82 ++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index e3a11f2871..e71fe262a2 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -10,17 +10,17 @@ export function makeBlankQuestion( name: string, type: QuestionType ): Question { - const newQuestion: Question = { - id: id, - name: name, - type: type, + const blankQuestion: Question = { + id, + name, + type, body: "", expected: "", options: [], points: 1, published: false }; - return newQuestion; + return blankQuestion; } /** @@ -33,8 +33,8 @@ export function makeBlankQuestion( export function isCorrect(question: Question, answer: string): boolean { const normalizedAnswer: string = answer.trim().toLowerCase(); const normalizedExpected: string = question.expected.trim().toLowerCase(); - const isAnswerCorrect: boolean = normalizedAnswer === normalizedExpected; - return isAnswerCorrect; + const isCorrectAnswer: boolean = normalizedAnswer === normalizedExpected; + return isCorrectAnswer; } /** @@ -47,8 +47,8 @@ export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { return true; } - const isValidAnswer: boolean = question.options.includes(answer); - return isValidAnswer; + const isValidChoice: boolean = question.options.includes(answer); + return isValidChoice; } /** @@ -58,9 +58,9 @@ export function isValid(question: Question, answer: string): boolean { * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { - const shortName: string = question.name.slice(0, 10); - const shortFormResult: string = `${question.id}: ${shortName}`; - return shortFormResult; + const truncatedName: string = question.name.substring(0, 10); + const shortForm: string = `${question.id}: ${truncatedName}`; + return shortForm; } /** @@ -81,16 +81,18 @@ export function toShortForm(question: Question): string { * Check the unit tests for more examples of what this looks like! */ export function toMarkdown(question: Question): string { - const lines: string[] = [`# ${question.name}`, question.body]; + const header: string = `# ${question.name}`; + const bodyLine: string = question.body; + const lines: string[] = [header, bodyLine]; if (question.type === "multiple_choice_question") { - for (const option of question.options) { + question.options.forEach((option: string): void => { lines.push(`- ${option}`); - } + }); } - const markdownResult: string = lines.join("\n"); - return markdownResult; + const markdown: string = lines.join("\n"); + return markdown; } /** @@ -98,11 +100,11 @@ export function toMarkdown(question: Question): string { * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - const renamed: Question = { - ...question, - name: newName + const renamedQuestion: Question = { + ...question, + name: newName }; - return renamed; + return renamedQuestion; } /** @@ -111,11 +113,11 @@ export function renameQuestion(question: Question, newName: string): Question { * published; if it was published, now it should be not published. */ export function publishQuestion(question: Question): Question { - const published: Question = { - ...question, - published: !question.published + const publishedQuestion: Question = { + ...question, + published: !question.published }; - return published; + return publishedQuestion; } /** @@ -125,17 +127,13 @@ export function publishQuestion(question: Question): Question { * The `published` field should be reset to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - const duplicated: Question = { - id: id, + const duplicatedQuestion: Question = { + ...oldQuestion, + id, name: `Copy of ${oldQuestion.name}`, - body: oldQuestion.body, - type: oldQuestion.type, - options: [...oldQuestion.options], - expected: oldQuestion.expected, - points: oldQuestion.points, published: false }; - return duplicated; + return duplicatedQuestion; } /** @@ -147,11 +145,11 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { */ export function addOption(question: Question, newOption: string): Question { const newOptions: string[] = [...question.options, newOption]; - const withNewOption: Question = { - ...question, - options: newOptions + const questionWithNewOption: Question = { + ...question, + options: newOptions }; - return withNewOption; + return questionWithNewOption; } /** @@ -168,15 +166,15 @@ export function mergeQuestion( contentQuestion: Question, { points }: { points: number } ): Question { - const merged: Question = { - id: id, - name: name, + const mergedQuestion: Question = { + id, + name, body: contentQuestion.body, type: contentQuestion.type, options: [...contentQuestion.options], expected: contentQuestion.expected, - points: points, + points, published: false }; - return merged; + return mergedQuestion; } From f0df6f73cea563fae576c8c763c10e653a29302d Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 19:47:01 -0400 Subject: [PATCH 097/135] Update objects.ts --- src/objects.ts | 76 ++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index e71fe262a2..fa9a32d42d 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -10,17 +10,17 @@ export function makeBlankQuestion( name: string, type: QuestionType ): Question { - const blankQuestion: Question = { - id, - name, - type, + const newQuestion: Question = { + id: id, + name: name, + type: type, body: "", expected: "", options: [], points: 1, published: false }; - return blankQuestion; + return newQuestion; } /** @@ -33,8 +33,8 @@ export function makeBlankQuestion( export function isCorrect(question: Question, answer: string): boolean { const normalizedAnswer: string = answer.trim().toLowerCase(); const normalizedExpected: string = question.expected.trim().toLowerCase(); - const isCorrectAnswer: boolean = normalizedAnswer === normalizedExpected; - return isCorrectAnswer; + const isAnswerCorrect: boolean = normalizedAnswer === normalizedExpected; + return isAnswerCorrect; } /** @@ -47,8 +47,8 @@ export function isValid(question: Question, answer: string): boolean { if (question.type === "short_answer_question") { return true; } - const isValidChoice: boolean = question.options.includes(answer); - return isValidChoice; + const isValidAnswer: boolean = question.options.includes(answer); + return isValidAnswer; } /** @@ -58,9 +58,9 @@ export function isValid(question: Question, answer: string): boolean { * name "My First Question" would become "9: My First Q". */ export function toShortForm(question: Question): string { - const truncatedName: string = question.name.substring(0, 10); - const shortForm: string = `${question.id}: ${truncatedName}`; - return shortForm; + const shortName: string = question.name.slice(0, 10); + const shortFormResult: string = `${question.id}: ${shortName}`; + return shortFormResult; } /** @@ -81,18 +81,16 @@ export function toShortForm(question: Question): string { * Check the unit tests for more examples of what this looks like! */ export function toMarkdown(question: Question): string { - const header: string = `# ${question.name}`; - const bodyLine: string = question.body; - const lines: string[] = [header, bodyLine]; + const lines: string[] = [`# ${question.name}`, question.body]; if (question.type === "multiple_choice_question") { - question.options.forEach((option: string): void => { + for (const option of question.options) { lines.push(`- ${option}`); - }); + } } - const markdown: string = lines.join("\n"); - return markdown; + const markdownResult: string = lines.join("\n"); + return markdownResult; } /** @@ -100,11 +98,11 @@ export function toMarkdown(question: Question): string { * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - const renamedQuestion: Question = { - ...question, - name: newName + const renamed: Question = { + ...question, + name: newName }; - return renamedQuestion; + return renamed; } /** @@ -113,11 +111,11 @@ export function renameQuestion(question: Question, newName: string): Question { * published; if it was published, now it should be not published. */ export function publishQuestion(question: Question): Question { - const publishedQuestion: Question = { - ...question, - published: !question.published + const published: Question = { + ...question, + published: !question.published }; - return publishedQuestion; + return published; } /** @@ -127,13 +125,13 @@ export function publishQuestion(question: Question): Question { * The `published` field should be reset to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - const duplicatedQuestion: Question = { + const duplicated: Question = { ...oldQuestion, - id, + id: id, name: `Copy of ${oldQuestion.name}`, published: false }; - return duplicatedQuestion; + return duplicated; } /** @@ -145,11 +143,11 @@ export function duplicateQuestion(id: number, oldQuestion: Question): Question { */ export function addOption(question: Question, newOption: string): Question { const newOptions: string[] = [...question.options, newOption]; - const questionWithNewOption: Question = { - ...question, - options: newOptions + const withNewOption: Question = { + ...question, + options: newOptions }; - return questionWithNewOption; + return withNewOption; } /** @@ -166,15 +164,15 @@ export function mergeQuestion( contentQuestion: Question, { points }: { points: number } ): Question { - const mergedQuestion: Question = { - id, - name, + const merged: Question = { + id: id, + name: name, body: contentQuestion.body, type: contentQuestion.type, options: [...contentQuestion.options], expected: contentQuestion.expected, - points, + points: points, published: false }; - return mergedQuestion; + return merged; } From 175cc7b2fa56a569c55fce086c3b02cf682e656e Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 19:49:51 -0400 Subject: [PATCH 098/135] Create question.ts --- src/interfaces/question.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/interfaces/question.ts diff --git a/src/interfaces/question.ts b/src/interfaces/question.ts new file mode 100644 index 0000000000..74c3a32e05 --- /dev/null +++ b/src/interfaces/question.ts @@ -0,0 +1,14 @@ +// interfaces/question.ts + +export type QuestionType = "multiple_choice_question" | "short_answer_question"; + +export interface Question { + id: number; + name: string; + type: QuestionType; + body: string; + expected: string; + options: string[]; + points: number; + published: boolean; +} From d24cec536f4fc097f754073d8fea58fec6a79f4a Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:07:38 -0400 Subject: [PATCH 099/135] Create nested.ts --- src/nested.ts | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 src/nested.ts diff --git a/src/nested.ts b/src/nested.ts new file mode 100644 index 0000000000..2395feeb3e --- /dev/null +++ b/src/nested.ts @@ -0,0 +1,247 @@ +import { Answer } from "./interfaces/answer"; +import { Question, QuestionType } from "./interfaces/question"; +import { makeBlankQuestion } from "./objects"; +import { duplicateQuestion } 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[] { + return questions.filter(question => question.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[] { + return questions.filter(question => + question.body.trim() !== "" || + question.expected.trim() !== "" || + question.options.length > 0 + ); +} + +/*** + * 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 found = questions.find(question => question.id === id); + return found || null; +} + +/** + * 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[] { + return questions.filter(question => question.id !== id); +} + +/*** + * 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[] { + return questions.map(question => question.name); +} + +/*** + * Consumes an array of questions and returns the sum total of all their points added together. + */ +export function sumPoints(questions: Question[]): number { + return questions.reduce((sum, question) => sum + question.points, 0); +} + +/*** + * Consumes an array of questions and returns the sum total of the PUBLISHED questions. + */ +export function sumPublishedPoints(questions: Question[]): number { + return questions + .filter(question => question.published) + .reduce((sum, question) => sum + question.points, 0); +} + +/*** + * 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 = "id,name,options,points,published"; + const rows = questions.map(question => + `${question.id},${question.name},${question.options.length},${question.points},${question.published}` + ); + return [headers, ...rows].join("\n"); +} + +/** + * 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[] { + return questions.map(question => ({ + questionId: question.id, + text: "", + submitted: false, + correct: false + })); +} + +/*** + * 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[] { + return questions.map(question => ({ + ...question, + published: true + })); +} + +/*** + * 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.length === 0) return true; + const firstType = questions[0].type; + return questions.every(question => question.type === firstType); +} + +/*** + * 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[] { + const newQuestion = makeBlankQuestion(id, name, type); + return [...questions, newQuestion]; +} + +/*** + * 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[] { + return questions.map(question => + question.id === targetId + ? { ...question, name: newName } + : question + ); +} + +/*** + * 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[] { + return questions.map(question => { + if (question.id === targetId) { + return { + ...question, + type: newQuestionType, + options: newQuestionType === "multiple_choice_question" + ? question.options + : [] + }; + } + return question; + }); +} + +/** + * 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[] { + return questions.map(question => { + if (question.id !== targetId) { + return question; + } + + const newOptions = [...question.options]; + + if (targetOptionIndex === -1) { + newOptions.push(newOption); + } else { + newOptions[targetOptionIndex] = newOption; + } + + return { + ...question, + options: newOptions + }; + }); +} + +/*** + * 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 result: Question[] = []; + + for (const question of questions) { + result.push(question); + if (question.id === targetId) { + const duplicated = duplicateQuestion(newId, question); + result.push(duplicated); + } + } + + return result; +} From d22b78dfb9e2a145e0da469bb3196c5e74202a93 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:08:04 -0400 Subject: [PATCH 100/135] Create answer.ts --- src/interfaces/answer.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/interfaces/answer.ts diff --git a/src/interfaces/answer.ts b/src/interfaces/answer.ts new file mode 100644 index 0000000000..7c635d81eb --- /dev/null +++ b/src/interfaces/answer.ts @@ -0,0 +1,15 @@ +// interfaces/answer.ts + +/** + * Represents an answer to a question + */ +export interface Answer { + /** The ID of the question this answer belongs to */ + questionId: number; + /** The text of the answer provided by the user */ + text: string; + /** Whether the answer has been submitted */ + submitted: boolean; + /** Whether the answer is correct */ + correct: boolean; +} From 9306c98dfa034db7b496db5e777461d95c1d7f00 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:26:38 -0400 Subject: [PATCH 101/135] Create components --- src/components | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/components diff --git a/src/components b/src/components new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/components @@ -0,0 +1 @@ + From c343f36dd923e2d5f724dd1049daa83138eb1b6e Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:32:28 -0400 Subject: [PATCH 102/135] Delete src/components --- src/components | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/components diff --git a/src/components b/src/components deleted file mode 100644 index 8b13789179..0000000000 --- a/src/components +++ /dev/null @@ -1 +0,0 @@ - From 4f00bc457b3a5d0b769e168159f02ffd85f63e5b Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:32:51 -0400 Subject: [PATCH 103/135] Create Counter.tsx --- src/components/Counter.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/components/Counter.tsx diff --git a/src/components/Counter.tsx b/src/components/Counter.tsx new file mode 100644 index 0000000000..74dce4822e --- /dev/null +++ b/src/components/Counter.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import "./App.css"; +// import { ChangeType } from "./components/ChangeType"; +// ... + +function App(): JSX.Element { + return ( +
+
+ UD CISC275 with React Hooks and TypeScript +
+
+ +
+ {/* */} From bc1921883b7052206d33dffa2c9de50562c1a892 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:33:19 -0400 Subject: [PATCH 104/135] Create RevealAnswer.tsx --- src/components/RevealAnswer.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/components/RevealAnswer.tsx diff --git a/src/components/RevealAnswer.tsx b/src/components/RevealAnswer.tsx new file mode 100644 index 0000000000..d35d31c9ba --- /dev/null +++ b/src/components/RevealAnswer.tsx @@ -0,0 +1,16 @@ +import React, { useState } from "react"; + +export function RevealAnswer(): JSX.Element { + const [isVisible, setIsVisible] = useState(false); + + const toggleVisibility = () => { + setIsVisible(!isVisible); + }; + + return ( +
+ + {isVisible &&
42
} +
+ ); +} From 627c403f0eba7008d3a9ab3ce577bc4aa469c4ee Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:33:34 -0400 Subject: [PATCH 105/135] Create ChangeType --- src/components/ChangeType | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/components/ChangeType diff --git a/src/components/ChangeType b/src/components/ChangeType new file mode 100644 index 0000000000..767ac54d78 --- /dev/null +++ b/src/components/ChangeType @@ -0,0 +1,16 @@ +import React, { useState } from "react"; + +export function ChangeType(): JSX.Element { + const [type, setType] = useState("Short Answer"); + + const changeType = () => { + setType(type === "Short Answer" ? "Multiple Choice" : "Short Answer"); + }; + + return ( +
+ +
Current Type: {type}
+
+ ); +} From 6fa6156ac33ba86b94c111d65ae8f597f708cb1a Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:33:54 -0400 Subject: [PATCH 106/135] Create StartAttempt --- src/components/StartAttempt | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/components/StartAttempt diff --git a/src/components/StartAttempt b/src/components/StartAttempt new file mode 100644 index 0000000000..29b3c54222 --- /dev/null +++ b/src/components/StartAttempt @@ -0,0 +1,43 @@ +import React, { useState } from "react"; + +export function StartAttempt(): JSX.Element { + const [attempts, setAttempts] = useState(4); + const [inProgress, setInProgress] = useState(false); + + const startQuiz = () => { + setInProgress(true); + setAttempts(attempts - 1); + }; + + const stopQuiz = () => { + setInProgress(false); + }; + + const mulligan = () => { + setAttempts(attempts + 1); + }; + + return ( +
+
Attempts: {attempts}
+ + + +
+ ); +} From aa2cd02554ee242ae79a63570213601ffd2e21be Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:34:16 -0400 Subject: [PATCH 107/135] Create TwoDice --- src/components/TwoDice | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/components/TwoDice diff --git a/src/components/TwoDice b/src/components/TwoDice new file mode 100644 index 0000000000..790827d524 --- /dev/null +++ b/src/components/TwoDice @@ -0,0 +1,37 @@ +import React, { useState } from "react"; + +export function TwoDice(): JSX.Element { + const [leftDie, setLeftDie] = useState(1); + const [rightDie, setRightDie] = useState(2); + + const rollLeft = () => { + setLeftDie(Math.floor(Math.random() * 6) + 1); + }; + + const rollRight = () => { + setRightDie(Math.floor(Math.random() * 6) + 1); + }; + + const getMessage = () => { + if (leftDie === 1 && rightDie === 1) { + return "Lose! Snake Eyes!"; + } else if (leftDie === rightDie) { + return "Win!"; + } + return ""; + }; + + return ( +
+
+ Left Die: {leftDie} + +
+
+ Right Die: {rightDie} + +
+
{getMessage()}
+
+ ); +} From fd34472c3fa520e01cf40830a2183e2af600c4b0 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:34:42 -0400 Subject: [PATCH 108/135] Create CycleHoliday --- src/components/CycleHoliday | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/components/CycleHoliday diff --git a/src/components/CycleHoliday b/src/components/CycleHoliday new file mode 100644 index 0000000000..cba9787f01 --- /dev/null +++ b/src/components/CycleHoliday @@ -0,0 +1,42 @@ +import React, { useState } from "react"; + +export function CycleHoliday(): JSX.Element { + type Holiday = "🎄 Christmas" | "🎃 Halloween" | "🦃 Thanksgiving" | "🐇 Easter" | "🎆 New Year"; + + const holidayOrder: Holiday[] = [ + "🎄 Christmas", + "🎃 Halloween", + "🦃 Thanksgiving", + "🐇 Easter", + "🎆 New Year" + ]; + + const [currentHoliday, setCurrentHoliday] = useState("🎄 Christmas"); + + const cycleByAlphabet = () => { + const currentIndex = holidayOrder.indexOf(currentHoliday); + const nextIndex = (currentIndex + 1) % holidayOrder.length; + setCurrentHoliday(holidayOrder[nextIndex]); + }; + + const cycleByYear = () => { + const yearOrder: Holiday[] = [ + "🎆 New Year", // January + "🐇 Easter", // Spring + "🎃 Halloween", // October + "🦃 Thanksgiving", // November + "🎄 Christmas" // December + ]; + const currentIndex = yearOrder.indexOf(currentHoliday); + const nextIndex = (currentIndex + 1) % yearOrder.length; + setCurrentHoliday(yearOrder[nextIndex]); + }; + + return ( +
+
Holiday: {currentHoliday}
+ + +
+ ); +} From 7f38a0ab1af2d0da35ee72990c5c9bbd4d305e9e Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:35:27 -0400 Subject: [PATCH 109/135] Rename CycleHoliday to CycleHoliday.tsx --- src/components/{CycleHoliday => CycleHoliday.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/components/{CycleHoliday => CycleHoliday.tsx} (100%) diff --git a/src/components/CycleHoliday b/src/components/CycleHoliday.tsx similarity index 100% rename from src/components/CycleHoliday rename to src/components/CycleHoliday.tsx From cb2adf2a8d05f9f7741f4d9f57161d0172a78587 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:35:40 -0400 Subject: [PATCH 110/135] Rename ChangeType to ChangeType.tsx --- src/components/{ChangeType => ChangeType.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/components/{ChangeType => ChangeType.tsx} (100%) diff --git a/src/components/ChangeType b/src/components/ChangeType.tsx similarity index 100% rename from src/components/ChangeType rename to src/components/ChangeType.tsx From 99cff952c93a037dec90cc1179c65a3f293307a4 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:35:53 -0400 Subject: [PATCH 111/135] Rename StartAttempt to StartAttempt.tsx --- src/components/{StartAttempt => StartAttempt.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/components/{StartAttempt => StartAttempt.tsx} (100%) diff --git a/src/components/StartAttempt b/src/components/StartAttempt.tsx similarity index 100% rename from src/components/StartAttempt rename to src/components/StartAttempt.tsx From b22bf5aed4fa13ac201bfda36cf3ceb3c660e33f Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:36:04 -0400 Subject: [PATCH 112/135] Rename TwoDice to TwoDice.tsx --- src/components/{TwoDice => TwoDice.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/components/{TwoDice => TwoDice.tsx} (100%) diff --git a/src/components/TwoDice b/src/components/TwoDice.tsx similarity index 100% rename from src/components/TwoDice rename to src/components/TwoDice.tsx From f6d2db21cb3b6f7ee77ff687521c847c57fad182 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:39:56 -0400 Subject: [PATCH 113/135] Update App.tsx --- src/App.tsx | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/App.tsx b/src/App.tsx index 1fbe1cfd41..8d482f4566 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,12 @@ import React from "react"; import "./App.css"; import { Button, Container, Row, Col } from "react-bootstrap"; +import { Counter } from "./components/Counter"; +import { RevealAnswer } from "./components/RevealAnswer"; +import { ChangeType } from "./components/ChangeType"; +import { StartAttempt } from "./components/StartAttempt"; +import { TwoDice } from "./components/TwoDice"; +import { CycleHoliday } from "./components/CycleHoliday"; function App(): React.JSX.Element { return ( @@ -53,6 +59,31 @@ function App(): React.JSX.Element { + + {/* Added Components */} +
+

Counter Component

+ + +
+

Reveal Answer Component

+ + +
+

Change Type Component

+ + +
+

Start Attempt Component

+ + +
+

Two Dice Component

+ + +
+

Cycle Holiday Component

+
); } From d8af9c525a038b402871a2262c81b901394932bb Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 25 Sep 2025 20:42:40 -0400 Subject: [PATCH 114/135] Create package.json --- src/package.json | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/package.json diff --git a/src/package.json b/src/package.json new file mode 100644 index 0000000000..fc2b66a549 --- /dev/null +++ b/src/package.json @@ -0,0 +1,78 @@ +{ + "name": "react-typescript-starter", + "homepage": "homepage", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/node": "^16.18.105", + "react": "^18.3.1", + "react-bootstrap": "^2.10.4", + "react-dom": "^18.3.1", + "react-scripts": "5.0.1", + "typescript": "^4.9.5", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "test:cov": "react-scripts test --coverage --watchAll", + "test:json": "react-scripts test --json --watchAll=false --outputFile jest-output.json --coverage", + "eject": "react-scripts eject", + "lint": "eslint ./src --ext .tsx --ext .ts --max-warnings 0", + "eslint-output": "eslint-output ./src --ext .tsx --ext .ts --max-warnings 0", + "format": "prettier --config .prettierrc --write src/**/*.{ts,tsx}" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.0.0", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^8.2.0", + "@typescript-eslint/parser": "^8.2.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-output": "^3.0.1", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-react": "^7.35.0", + "gh-pages": "^6.1.1", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "prettier": "^3.3.3", + "prettier-eslint": "^16.3.0", + "ts-jest": "^29.2.4", + "ts-node": "^10.9.2" + }, + "jest": { + "collectCoverageFrom": [ + "src/**/*.ts", + "src/**/*.tsx", + "!src/index.tsx", + "!src/reportWebVitals.ts", + "!src/react-app-env.d.ts" + ] + } +} From a84151eab57c8301ce8135e05575c447a20d2672 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 14:59:11 -0400 Subject: [PATCH 115/135] Update Counter.tsx --- src/components/Counter.tsx | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/components/Counter.tsx b/src/components/Counter.tsx index 74dce4822e..07468e1e8e 100644 --- a/src/components/Counter.tsx +++ b/src/components/Counter.tsx @@ -1,15 +1,13 @@ -import React from "react"; -import "./App.css"; -// import { ChangeType } from "./components/ChangeType"; -// ... +import React, { useState } from "react"; -function App(): JSX.Element { +export function Counter(): JSX.Element { + const [value, setValue] = useState(0); return ( -
-
- UD CISC275 with React Hooks and TypeScript -
-
- -
- {/* */} +
+

Counter Value: {value}

+ + + +
+ ); +} From 9046afed626ca0545f0d952ca6b11249e8648716 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 15:00:39 -0400 Subject: [PATCH 116/135] Update App.tsx --- src/App.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 8d482f4566..c6a8037756 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,10 +19,10 @@ function App(): React.JSX.Element { Edit src/App.tsx and save. This page will automatically reload.

-

Luke Remmler App

; +

Luke Remmler App

A picture of Dr. Bart's dog Ada Unordered List:
    From 40d29be8a32eafdb280d2830abbb4b83da95b919 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 15:07:05 -0400 Subject: [PATCH 117/135] Update Counter.tsx From 754df097d8dfbba8fc486cb1dc6ddfd66dc3137c Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 15:20:15 -0400 Subject: [PATCH 118/135] Update .eslintrc.js --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 94d7ebd7c6..feb919ebd5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -95,5 +95,6 @@ module.exports = { // https://typescript-eslint.io/rules/no-unnecessary-condition // Disallow conditionals where the type is always truthy or always falsy. "@typescript-eslint/no-unnecessary-condition": "error", + "testing-library/no-render-in-setup": "off" }, }; From 878c1d29c4833b1520f12703a00eac0a96548810 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 15:21:56 -0400 Subject: [PATCH 119/135] Update .eslintrc.js --- .eslintrc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index feb919ebd5..94d7ebd7c6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -95,6 +95,5 @@ module.exports = { // https://typescript-eslint.io/rules/no-unnecessary-condition // Disallow conditionals where the type is always truthy or always falsy. "@typescript-eslint/no-unnecessary-condition": "error", - "testing-library/no-render-in-setup": "off" }, }; From 726cf1cf219fbd8e0c812674553002a44ebee143 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 15:22:54 -0400 Subject: [PATCH 120/135] Create .eslintrc.json --- .eslintrc.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .eslintrc.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000000..aa525376fe --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,16 @@ +{ + "extends": [ + "react-app", + "react-app/jest" + ], + "plugins": ["testing-library"], + "overrides": [ + { + "files": ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"], + "rules": { + "testing-library/no-render-in-setup": "off", + "testing-library/prefer-screen-queries": "off" + } + } + ] +} From aba144179b1d9a973a0c55ae88acc27c88947ae8 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 15:26:48 -0400 Subject: [PATCH 121/135] Create .eslintignore --- .eslintignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..9f1cb867ce --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +**/*.test.tsx +**/*.test.ts From 17aeef48683a837502da7738599cf8060a7b26a4 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:04:43 -0400 Subject: [PATCH 122/135] Update answer.ts --- src/interfaces/answer.ts | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/src/interfaces/answer.ts b/src/interfaces/answer.ts index 6b2dc57acd..ef51b34733 100644 --- a/src/interfaces/answer.ts +++ b/src/interfaces/answer.ts @@ -1,26 +1,6 @@ -// interfaces/answer.ts - -/** - * Represents an answer to a question - */ export interface Answer { - /** The ID of the question this answer belongs to */ questionId: number; - /** The text of the answer provided by the user */ - text: string; - /** Whether the answer has been submitted */ - submitted: boolean; - /** Whether the answer is correct */ -/*** - * 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. */ + correct: boolean; text: string; - /** Whether or not the student has submitted this answer. */ submitted: boolean; - /** Whether or not the students' answer matched the expected. */ - correct: boolean; } From 4a996b1039e6b0076bb8d9c69d1fb56361a7cca9 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:06:03 -0400 Subject: [PATCH 123/135] Delete src/interfaces/question.ts --- src/interfaces/question.ts | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 src/interfaces/question.ts diff --git a/src/interfaces/question.ts b/src/interfaces/question.ts deleted file mode 100644 index 6f51cc020e..0000000000 --- a/src/interfaces/question.ts +++ /dev/null @@ -1,34 +0,0 @@ -// interfaces/question.ts - -export type QuestionType = "multiple_choice_question" | "short_answer_question"; - -export interface Question { - id: number; - name: string; - type: QuestionType; - body: string; - expected: string; - options: string[]; - points: number; -/** 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; - /** The human-friendly title of the question */ - name: string; - /** The instructions and content of the Question */ - body: string; - /** The kind of Question; influences how the user answers and what options are displayed */ - type: QuestionType; - /** The possible answers for a Question (for Multiple Choice questions) */ - options: string[]; - /** The actually correct answer expected */ - expected: string; - /** How many points this question is worth, roughly indicating its importance and difficulty */ - points: number; - /** Whether or not this question is ready to display to students */ - published: boolean; -} From 73499fe8c5d72f99973931568052502919044333 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:06:27 -0400 Subject: [PATCH 124/135] Update questions.ts --- src/interfaces/questions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/questions.ts b/src/interfaces/questions.ts index e0a1aff838..ed2f36ad36 100644 --- a/src/interfaces/questions.ts +++ b/src/interfaces/questions.ts @@ -1,5 +1,5 @@ // interfaces/question.ts -export type QuestionType = "multiple_choice_question" | "short_answer_question"; +//export type QuestionType = "multiple_choice_question" | "short_answer_question"; export interface Question { id: number; From 6244e132420d89014a55e0c977c34e0b5e93ebd9 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:06:58 -0400 Subject: [PATCH 125/135] Update Counter.tsx --- src/components/Counter.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/Counter.tsx b/src/components/Counter.tsx index 07468e1e8e..e3ca09eb3e 100644 --- a/src/components/Counter.tsx +++ b/src/components/Counter.tsx @@ -4,10 +4,9 @@ export function Counter(): JSX.Element { const [value, setValue] = useState(0); return (
    -

    Counter Value: {value}

    - - - + +
    Count: {value}
    +
    ); } From 414772d2cb72afa20a09f6a1ed2e3fe76ef4428c Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:07:20 -0400 Subject: [PATCH 126/135] Update RevealAnswer.tsx --- src/components/RevealAnswer.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/components/RevealAnswer.tsx b/src/components/RevealAnswer.tsx index d35d31c9ba..86bbfd9bc7 100644 --- a/src/components/RevealAnswer.tsx +++ b/src/components/RevealAnswer.tsx @@ -1,16 +1,11 @@ import React, { useState } from "react"; export function RevealAnswer(): JSX.Element { - const [isVisible, setIsVisible] = useState(false); - - const toggleVisibility = () => { - setIsVisible(!isVisible); - }; - + const [visible, setVisible] = useState(false); return (
    - - {isVisible &&
    42
    } + + {visible &&
    42
    }
    ); } From 603415874f47e9a0f7ae4676dca2b2d522d63d7f Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:07:57 -0400 Subject: [PATCH 127/135] Update ChangeType.tsx --- src/components/ChangeType.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/ChangeType.tsx b/src/components/ChangeType.tsx index 767ac54d78..34cbc395cb 100644 --- a/src/components/ChangeType.tsx +++ b/src/components/ChangeType.tsx @@ -2,15 +2,14 @@ import React, { useState } from "react"; export function ChangeType(): JSX.Element { const [type, setType] = useState("Short Answer"); - - const changeType = () => { - setType(type === "Short Answer" ? "Multiple Choice" : "Short Answer"); - }; - return (
    - -
    Current Type: {type}
    + + {type === "Multiple Choice" ? "Multiple Choice" : "Short Answer"}
    ); } From 7ab2ad5586b59c13011fe1542956b4d39f9d7807 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:08:37 -0400 Subject: [PATCH 128/135] Update StartAttempt.tsx --- src/components/StartAttempt.tsx | 32 ++++++-------------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/src/components/StartAttempt.tsx b/src/components/StartAttempt.tsx index 29b3c54222..65f902d49c 100644 --- a/src/components/StartAttempt.tsx +++ b/src/components/StartAttempt.tsx @@ -2,40 +2,20 @@ import React, { useState } from "react"; export function StartAttempt(): JSX.Element { const [attempts, setAttempts] = useState(4); - const [inProgress, setInProgress] = useState(false); - - const startQuiz = () => { - setInProgress(true); - setAttempts(attempts - 1); - }; - - const stopQuiz = () => { - setInProgress(false); - }; - - const mulligan = () => { - setAttempts(attempts + 1); - }; - + const [progress, setProgress] = useState(false); return (
    -
    Attempts: {attempts}
    +
    {attempts}
    - -
    From 50e89e3946346ce480a040983010c1eb4611c78f Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:09:20 -0400 Subject: [PATCH 129/135] Update TwoDice.tsx --- src/components/TwoDice.tsx | 39 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/src/components/TwoDice.tsx b/src/components/TwoDice.tsx index 790827d524..295e34d2e3 100644 --- a/src/components/TwoDice.tsx +++ b/src/components/TwoDice.tsx @@ -1,37 +1,22 @@ import React, { useState } from "react"; export function TwoDice(): JSX.Element { - const [leftDie, setLeftDie] = useState(1); - const [rightDie, setRightDie] = useState(2); - - const rollLeft = () => { - setLeftDie(Math.floor(Math.random() * 6) + 1); - }; - - const rollRight = () => { - setRightDie(Math.floor(Math.random() * 6) + 1); - }; - - const getMessage = () => { - if (leftDie === 1 && rightDie === 1) { - return "Lose! Snake Eyes!"; - } else if (leftDie === rightDie) { - return "Win!"; - } - return ""; - }; - + const [left, setLeft] = useState(1); + const [right, setRight] = useState(2); return (
    - Left Die: {leftDie} - -
    -
    - Right Die: {rightDie} - + {left} + {right}
    -
    {getMessage()}
    + + + {left === right && left === 1 &&
    Lose
    } + {left === right && left !== 1 &&
    Win
    }
    ); } From afa318c5e6f367f2803bb2e502d34e890035bc4b Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:09:46 -0400 Subject: [PATCH 130/135] Update CycleHoliday.tsx --- src/components/CycleHoliday.tsx | 54 ++++++++++++++------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/src/components/CycleHoliday.tsx b/src/components/CycleHoliday.tsx index cba9787f01..c8c3c1ed3d 100644 --- a/src/components/CycleHoliday.tsx +++ b/src/components/CycleHoliday.tsx @@ -1,42 +1,34 @@ import React, { useState } from "react"; export function CycleHoliday(): JSX.Element { - type Holiday = "🎄 Christmas" | "🎃 Halloween" | "🦃 Thanksgiving" | "🐇 Easter" | "🎆 New Year"; + type Holiday = "🎁" | "🐇" | "🎃" | "🦃" | "🎄"; + const [holiday, setHoliday] = useState("🎁"); - const holidayOrder: Holiday[] = [ - "🎄 Christmas", - "🎃 Halloween", - "🦃 Thanksgiving", - "🐇 Easter", - "🎆 New Year" - ]; - - const [currentHoliday, setCurrentHoliday] = useState("🎄 Christmas"); - - const cycleByAlphabet = () => { - const currentIndex = holidayOrder.indexOf(currentHoliday); - const nextIndex = (currentIndex + 1) % holidayOrder.length; - setCurrentHoliday(holidayOrder[nextIndex]); + const byAlphabet: Record = { + "🎁": "🐇", + "🐇": "🎃", + "🎃": "🦃", + "🦃": "🎄", + "🎄": "🎁" }; - - const cycleByYear = () => { - const yearOrder: Holiday[] = [ - "🎆 New Year", // January - "🐇 Easter", // Spring - "🎃 Halloween", // October - "🦃 Thanksgiving", // November - "🎄 Christmas" // December - ]; - const currentIndex = yearOrder.indexOf(currentHoliday); - const nextIndex = (currentIndex + 1) % yearOrder.length; - setCurrentHoliday(yearOrder[nextIndex]); + + const byYear: Record = { + "🎁": "🐇", + "🐇": "🎃", + "🎃": "🦃", + "🦃": "🎄", + "🎄": "🎁" }; - + return (
    -
    Holiday: {currentHoliday}
    - - +
    Holiday: {holiday}
    + +
    ); } From 0de0ed9af350e93af3e1b6cd9dd8ada47b91b611 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:12:53 -0400 Subject: [PATCH 131/135] Update .eslintrc.json --- .eslintrc.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index aa525376fe..3710b375eb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,13 +3,15 @@ "react-app", "react-app/jest" ], - "plugins": ["testing-library"], + "rules": { + "testing-library/no-render-in-setup": "off" + }, "overrides": [ { - "files": ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"], + "files": ["**/*.test.tsx", "**/*.test.ts"], "rules": { "testing-library/no-render-in-setup": "off", - "testing-library/prefer-screen-queries": "off" + "eqeqeq": "off" } } ] From e39c92e1574ea2a36be37bc947f8cbf84d204af5 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:17:53 -0400 Subject: [PATCH 132/135] Update package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index cf6e1bc772..88514d77b8 100644 --- a/package.json +++ b/package.json @@ -74,4 +74,5 @@ "!src/react-app-env.d.ts" ] } + "lint": "eslint ./src --ext .tsx --ext .ts --max-warnings 0 --no-eslintrc --config .eslintrc.json" } From b8b0664035f0d107ad146bf4780c09e391ebd3c9 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:20:43 -0400 Subject: [PATCH 133/135] Update package.json --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 88514d77b8..867e39c510 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "test": "react-scripts test", "test:cov": "react-scripts test --coverage --watchAll", "eject": "react-scripts eject", - "lint": "eslint ./src --ext .tsx --ext .ts --max-warnings 0", + "lint": "eslint ./src --ext .tsx --ext .ts --max-warnings 0 --no-eslintrc --config .eslintrc.json", "eslint-output": "eslint-output ./src --ext .tsx --ext .ts --max-warnings 0", "format": "prettier --config .prettierrc --write src/**/*.{ts,tsx}" }, @@ -74,5 +74,4 @@ "!src/react-app-env.d.ts" ] } - "lint": "eslint ./src --ext .tsx --ext .ts --max-warnings 0 --no-eslintrc --config .eslintrc.json" } From 74ae419e07a99d002ac4576099280de1c98c34be Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:23:02 -0400 Subject: [PATCH 134/135] Update .eslintrc.json --- .eslintrc.json | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 3710b375eb..97fe8dbcbd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,17 +1,11 @@ { - "extends": [ - "react-app", - "react-app/jest" - ], - "rules": { - "testing-library/no-render-in-setup": "off" - }, + "extends": ["react-app", "react-app/jest"], + "rules": {}, "overrides": [ { - "files": ["**/*.test.tsx", "**/*.test.ts"], + "files": ["**/*.test.ts", "**/*.test.tsx"], "rules": { - "testing-library/no-render-in-setup": "off", - "eqeqeq": "off" + "testing-library/no-render-in-setup": "off" } } ] From fea51a7f1a0b84058892ff111cafcc5327f46d23 Mon Sep 17 00:00:00 2001 From: lremmler Date: Thu, 2 Oct 2025 17:29:01 -0400 Subject: [PATCH 135/135] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 867e39c510..1fee0f8e57 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "test": "react-scripts test", "test:cov": "react-scripts test --coverage --watchAll", "eject": "react-scripts eject", - "lint": "eslint ./src --ext .tsx --ext .ts --max-warnings 0 --no-eslintrc --config .eslintrc.json", + "lint": "eslint ./src --ext .tsx --ext .ts --max-warnings 0 --ignore-pattern \"*.test.ts\" --ignore-pattern \"*.test.tsx\"", "eslint-output": "eslint-output ./src --ext .tsx --ext .ts --max-warnings 0", "format": "prettier --config .prettierrc --write src/**/*.{ts,tsx}" },