From 2b5f14cf6979706d394a7699aa00f2b5e9d02bbb Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Mon, 31 Jan 2022 16:37:54 -0500 Subject: [PATCH 01/79] Require Hello World in the document --- src/text.Test.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/text.Test.tsx diff --git a/src/text.Test.tsx b/src/text.Test.tsx new file mode 100644 index 0000000000..b32c330d3f --- /dev/null +++ b/src/text.Test.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import App from "./App"; + +test("renders the text 'Hello World' somewhere", () => { + render(); + const text = screen.getByText(/Hello World/); + expect(text).toBeInTheDocument(); +}); From a7dee05e0bee0379110c6189433d12482280146a Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Mon, 31 Jan 2022 16:41:17 -0500 Subject: [PATCH 02/79] Rename text.Test.tsx to text.test.tsx --- src/{text.Test.tsx => text.test.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{text.Test.tsx => text.test.tsx} (100%) diff --git a/src/text.Test.tsx b/src/text.test.tsx similarity index 100% rename from src/text.Test.tsx rename to src/text.test.tsx From 3e381f38b1d44afd102eb053a8ba9a48a069434e Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Mon, 31 Jan 2022 16:56:42 -0500 Subject: [PATCH 03/79] Include the task info --- public/tasks/task-first-branch.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 public/tasks/task-first-branch.md diff --git a/public/tasks/task-first-branch.md b/public/tasks/task-first-branch.md new file mode 100644 index 0000000000..94333338a0 --- /dev/null +++ b/public/tasks/task-first-branch.md @@ -0,0 +1,5 @@ +# Task - First Branch + +Version: 0.0.1 + +Pass a short test to have certain text on the page. From 986b28ac0afb41e603602013f71e6ef6e257c722 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 2 Feb 2022 13:12:40 -0500 Subject: [PATCH 04/79] 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 e6b1dab1961daf6f03459789cef974bf043501f2 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Thu, 3 Feb 2022 14:10:55 -0500 Subject: [PATCH 05/79] Allow one or more instances of the Hello World text --- src/text.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/text.test.tsx b/src/text.test.tsx index b32c330d3f..f99a063e76 100644 --- a/src/text.test.tsx +++ b/src/text.test.tsx @@ -4,6 +4,6 @@ import App from "./App"; test("renders the text 'Hello World' somewhere", () => { render(); - const text = screen.getByText(/Hello World/); - expect(text).toBeInTheDocument(); + const texts = screen.getAllByText(/Hello World/); + expect(texts.length).toBeGreaterThanOrEqual(1); }); From 2c852d620be705187b5ade2a68df632c6d6d4256 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sun, 6 Feb 2022 18:33:46 -0500 Subject: [PATCH 06/79] 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 07/79] 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 08/79] 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 09/79] 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 10/79] 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 11/79] 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 12/79] 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 ab9bfb53c8eb4842f3149957337db26e621eff2c Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 9 Feb 2022 18:10:22 -0500 Subject: [PATCH 13/79] Basic starter files for components --- src/App.tsx | 18 +++++++++++++++--- src/components/ChangeType.tsx | 5 +++++ src/components/CycleHoliday.tsx | 5 +++++ src/components/RevealAnswer.tsx | 5 +++++ src/components/StartAttempt.tsx | 5 +++++ src/components/TwoDice.tsx | 5 +++++ 6 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 src/components/ChangeType.tsx create mode 100644 src/components/CycleHoliday.tsx create mode 100644 src/components/RevealAnswer.tsx create mode 100644 src/components/StartAttempt.tsx create mode 100644 src/components/TwoDice.tsx diff --git a/src/App.tsx b/src/App.tsx index e4f028fd06..0c973b1754 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,10 @@ import React from "react"; import "./App.css"; +import { ChangeType } from "./components/ChangeType"; +import { RevealAnswer } from "./components/RevealAnswer"; +import { StartAttempt } from "./components/StartAttempt"; +import { TwoDice } from "./components/TwoDice"; +import { CycleHoliday } from "./components/CycleHoliday"; function App(): JSX.Element { return ( @@ -7,9 +12,16 @@ function App(): JSX.Element {
UD CISC275 with React Hooks and TypeScript
-

- Edit src/App.tsx and save to reload. -

+
+ +
+ +
+ +
+ +
+ ); } diff --git a/src/components/ChangeType.tsx b/src/components/ChangeType.tsx new file mode 100644 index 0000000000..9a856820c4 --- /dev/null +++ b/src/components/ChangeType.tsx @@ -0,0 +1,5 @@ +import React from "react"; + +export function ChangeType(): JSX.Element { + return
Change Type
; +} diff --git a/src/components/CycleHoliday.tsx b/src/components/CycleHoliday.tsx new file mode 100644 index 0000000000..b3d85fa55a --- /dev/null +++ b/src/components/CycleHoliday.tsx @@ -0,0 +1,5 @@ +import React from "react"; + +export function CycleHoliday(): JSX.Element { + return
Cycle Holiday
; +} diff --git a/src/components/RevealAnswer.tsx b/src/components/RevealAnswer.tsx new file mode 100644 index 0000000000..6d724c4ccf --- /dev/null +++ b/src/components/RevealAnswer.tsx @@ -0,0 +1,5 @@ +import React from "react"; + +export function RevealAnswer(): JSX.Element { + return
Reveal Answer
; +} diff --git a/src/components/StartAttempt.tsx b/src/components/StartAttempt.tsx new file mode 100644 index 0000000000..12786ec0cd --- /dev/null +++ b/src/components/StartAttempt.tsx @@ -0,0 +1,5 @@ +import React from "react"; + +export function StartAttempt(): JSX.Element { + return
Start Attempt
; +} diff --git a/src/components/TwoDice.tsx b/src/components/TwoDice.tsx new file mode 100644 index 0000000000..b9a5260966 --- /dev/null +++ b/src/components/TwoDice.tsx @@ -0,0 +1,5 @@ +import React from "react"; + +export function TwoDice(): JSX.Element { + return
Two Dice
; +} From 97658638bc1a69bfa9e7a84a90a0307145db7db2 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 9 Feb 2022 18:30:51 -0500 Subject: [PATCH 14/79] Another extra paren error --- .eslintrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index d5986e4354..36f0947989 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -38,7 +38,8 @@ { "nestedBinaryExpressions": false, "returnAssign": false, - "enforceForArrowConditionals": false + "enforceForArrowConditionals": false, + "ignoreJSX": "all" } ], "brace-style": ["error", "1tbs"], From c0bbc391d8eb1994783a8207e386f45578813333 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sun, 13 Feb 2022 14:38:40 -0500 Subject: [PATCH 15/79] Updated, complete tests for all state components --- src/App.tsx | 3 + src/components/ChangeType.test.tsx | 53 ++++++++ src/components/ChangeType.tsx | 4 +- src/components/Counter.test.tsx | 39 ++++++ src/components/Counter.tsx | 12 ++ src/components/CycleHoliday.test.tsx | 55 ++++++++ src/components/CycleHoliday.tsx | 3 +- src/components/RevealAnswer.test.tsx | 36 +++++ src/components/RevealAnswer.tsx | 3 +- src/components/StartAttempt.test.tsx | 196 +++++++++++++++++++++++++++ src/components/StartAttempt.tsx | 3 +- src/components/TwoDice.test.tsx | 140 +++++++++++++++++++ src/components/TwoDice.tsx | 13 +- 13 files changed, 555 insertions(+), 5 deletions(-) create mode 100644 src/components/ChangeType.test.tsx create mode 100644 src/components/Counter.test.tsx create mode 100644 src/components/Counter.tsx create mode 100644 src/components/CycleHoliday.test.tsx create mode 100644 src/components/RevealAnswer.test.tsx create mode 100644 src/components/StartAttempt.test.tsx create mode 100644 src/components/TwoDice.test.tsx diff --git a/src/App.tsx b/src/App.tsx index 0c973b1754..504138f1c3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,7 @@ import { RevealAnswer } from "./components/RevealAnswer"; import { StartAttempt } from "./components/StartAttempt"; import { TwoDice } from "./components/TwoDice"; import { CycleHoliday } from "./components/CycleHoliday"; +import { Counter } from "./components/Counter"; function App(): JSX.Element { return ( @@ -12,6 +13,8 @@ function App(): JSX.Element {
UD CISC275 with React Hooks and TypeScript
+
+

diff --git a/src/components/ChangeType.test.tsx b/src/components/ChangeType.test.tsx new file mode 100644 index 0000000000..10b4f0dc3c --- /dev/null +++ b/src/components/ChangeType.test.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { ChangeType } from "./ChangeType"; + +describe("ChangeType Component tests", () => { + beforeEach(() => { + render(); + }); + test("The initial type is Short Answer", () => { + // We use `getByText` because the text MUST be there + const typeText = screen.getByText(/Short Answer/i); + expect(typeText).toBeInTheDocument(); + }); + test("The initial type is not Multiple Choice", () => { + // We use `queryByText` because the text might not be there + const typeText = screen.queryByText(/Multiple Choice/i); + expect(typeText).toBeNull(); + }); + + test("There is a button labeled Change Type", () => { + const changeTypeButton = screen.getByRole("button", { + name: /Change Type/i + }); + expect(changeTypeButton).toBeInTheDocument(); + }); + + test("Clicking the button changes the type.", () => { + const changeTypeButton = screen.getByRole("button", { + name: /Change Type/i + }); + changeTypeButton.click(); + // Should be Multiple Choice + const typeTextMC = screen.getByText(/Multiple Choice/i); + expect(typeTextMC).toBeInTheDocument(); + // Should NOT be Short Answer + const typeTextSA = screen.queryByText(/Short Answer/i); + expect(typeTextSA).toBeNull(); + }); + + test("Clicking the button twice keeps the type the same.", () => { + const changeTypeButton = screen.getByRole("button", { + name: /Change Type/i + }); + changeTypeButton.click(); + changeTypeButton.click(); + // Should be Short Answer + const typeTextSA = screen.getByText(/Short Answer/i); + expect(typeTextSA).toBeInTheDocument(); + // Should NOT be Multiple Choice + const typeTextMC = screen.queryByText(/Multiple Choice/i); + expect(typeTextMC).toBeNull(); + }); +}); diff --git a/src/components/ChangeType.tsx b/src/components/ChangeType.tsx index 9a856820c4..5608076f64 100644 --- a/src/components/ChangeType.tsx +++ b/src/components/ChangeType.tsx @@ -1,4 +1,6 @@ -import React from "react"; +import React, { useState } from "react"; +import { Button } from "react-bootstrap"; +import { QuestionType } from "../interfaces/question"; export function ChangeType(): JSX.Element { return
Change Type
; diff --git a/src/components/Counter.test.tsx b/src/components/Counter.test.tsx new file mode 100644 index 0000000000..7a37c46e38 --- /dev/null +++ b/src/components/Counter.test.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { Counter } from "./Counter"; + +describe("Counter Component tests", () => { + beforeEach(() => { + render(); + }); + test("The initial value is 0", () => { + // We use `getByText` because the text MUST be there + const valueText = screen.getByText(/0/i); + expect(valueText).toBeInTheDocument(); + }); + test("The initial value is not 1", () => { + // We use `queryByText` because the text might not be there + const valueText = screen.queryByText(/1/i); + expect(valueText).toBeNull(); + }); + + test("There is a button named Add One", () => { + const addOneButton = screen.getByRole("button", { name: /Add One/i }); + expect(addOneButton).toBeInTheDocument(); + }); + + test("Clicking the button once adds one", () => { + const addOneButton = screen.getByRole("button", { name: /Add One/i }); + addOneButton.click(); + const valueText = screen.getByText(/1/i); + expect(valueText).toBeInTheDocument(); + }); + + test("Clicking the button twice adds two", () => { + const addOneButton = screen.getByRole("button", { name: /Add One/i }); + addOneButton.click(); + addOneButton.click(); + const valueText = screen.getByText(/2/i); + expect(valueText).toBeInTheDocument(); + }); +}); diff --git a/src/components/Counter.tsx b/src/components/Counter.tsx new file mode 100644 index 0000000000..1987698ed1 --- /dev/null +++ b/src/components/Counter.tsx @@ -0,0 +1,12 @@ +import React, { useState } from "react"; +import { Button } from "react-bootstrap"; + +export function Counter(): JSX.Element { + const [value, setValue] = useState(0); + return ( + + + to {value}. + + ); +} diff --git a/src/components/CycleHoliday.test.tsx b/src/components/CycleHoliday.test.tsx new file mode 100644 index 0000000000..145e2cb3c8 --- /dev/null +++ b/src/components/CycleHoliday.test.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { CycleHoliday } from "./CycleHoliday"; + +describe("CycleHoliday Component tests", () => { + beforeEach(() => { + render(); + }); + + test("An initial holiday is displayed", () => { + const initialHoliday = screen.getByText(/Holiday: (.*)/i); + expect(initialHoliday).toBeInTheDocument(); + }); + + test("There are two buttons", () => { + const alphabetButton = screen.getByRole("button", { + name: /Alphabet/i + }); + const yearButton = screen.getByRole("button", { + name: /Year/i + }); + expect(alphabetButton).toBeInTheDocument(); + expect(yearButton).toBeInTheDocument(); + }); + + test("Can cycle through 5 distinct holidays alphabetically", () => { + const alphabetButton = screen.getByRole("button", { + name: /Alphabet/i + }); + const initialHoliday = screen.getByText(/Holiday ?[:)-](.*)/i); + const states: string[] = []; + for (let i = 0; i < 6; i++) { + states.push(initialHoliday.textContent || ""); + alphabetButton.click(); + } + const uniqueStates = states.filter((x, y) => states.indexOf(x) == y); + expect(uniqueStates).toHaveLength(5); + expect(states[0]).toEqual(states[5]); + }); + + test("Can cycle through 5 distinct holidays by year", () => { + const yearButton = screen.getByRole("button", { + name: /Year/i + }); + const initialHoliday = screen.getByText(/Holiday ?[:)-](.*)/i); + const states: string[] = []; + for (let i = 0; i < 6; i++) { + states.push(initialHoliday.textContent || ""); + yearButton.click(); + } + const uniqueStates = states.filter((x, y) => states.indexOf(x) == y); + expect(uniqueStates).toHaveLength(5); + expect(states[0]).toEqual(states[5]); + }); +}); diff --git a/src/components/CycleHoliday.tsx b/src/components/CycleHoliday.tsx index b3d85fa55a..7c671f889f 100644 --- a/src/components/CycleHoliday.tsx +++ b/src/components/CycleHoliday.tsx @@ -1,4 +1,5 @@ -import React from "react"; +import React, { useState } from "react"; +import { Button } from "react-bootstrap"; export function CycleHoliday(): JSX.Element { return
Cycle Holiday
; diff --git a/src/components/RevealAnswer.test.tsx b/src/components/RevealAnswer.test.tsx new file mode 100644 index 0000000000..aa7996e964 --- /dev/null +++ b/src/components/RevealAnswer.test.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { RevealAnswer } from "./RevealAnswer"; + +describe("RevealAnswer Component tests", () => { + beforeEach(() => { + render(); + }); + test("The answer '42' is not visible initially", () => { + const answerText = screen.queryByText(/42/); + expect(answerText).toBeNull(); + }); + test("There is a Reveal Answer button", () => { + const revealButton = screen.getByRole("button", { + name: /Reveal Answer/i + }); + expect(revealButton).toBeInTheDocument(); + }); + test("Clicking Reveal Answer button reveals the '42'", () => { + const revealButton = screen.getByRole("button", { + name: /Reveal Answer/i + }); + revealButton.click(); + const answerText = screen.getByText(/42/); + expect(answerText).toBeInTheDocument(); + }); + test("Clicking Reveal Answer button twice hides the '42'", () => { + const revealButton = screen.getByRole("button", { + name: /Reveal Answer/i + }); + revealButton.click(); + revealButton.click(); + const answerText = screen.queryByText(/42/); + expect(answerText).toBeNull(); + }); +}); diff --git a/src/components/RevealAnswer.tsx b/src/components/RevealAnswer.tsx index 6d724c4ccf..07db6f62d2 100644 --- a/src/components/RevealAnswer.tsx +++ b/src/components/RevealAnswer.tsx @@ -1,4 +1,5 @@ -import React from "react"; +import React, { useState } from "react"; +import { Button } from "react-bootstrap"; export function RevealAnswer(): JSX.Element { return
Reveal Answer
; diff --git a/src/components/StartAttempt.test.tsx b/src/components/StartAttempt.test.tsx new file mode 100644 index 0000000000..3d41c953cf --- /dev/null +++ b/src/components/StartAttempt.test.tsx @@ -0,0 +1,196 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { StartAttempt } from "./StartAttempt"; + +/*** + * Helper function to extract a number from an HTMLElement's textContent. + * + * If you aren't familiar with Regular Expressions: + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + */ +export function extractDigits(element: HTMLElement): number | null { + const attemptNumberText = element.textContent || ""; + // We use a "regular expression" to find digits and extract them as text + const attemptNumberDigitsMatched = attemptNumberText.match(/\d+/); + // Provides a Matched Regular Expression or null + if (attemptNumberDigitsMatched === null) { + // Should never be possible, since then there was no number to have found. + // But TypeScript is cautious and demands we provide SOMETHING. + return null; + } else { + // Not null, get the first matched value and convert to number + return parseInt(attemptNumberDigitsMatched[0]); + } +} + +describe("StartAttempt Component tests", () => { + beforeEach(() => { + render(); + }); + test("The Number of attempts is displayed initially, without other numbers", () => { + const attemptNumber = screen.getByText(/(\d+)/); + expect(attemptNumber).toBeInTheDocument(); + }); + test("The Number of attempts is more than 0", () => { + const attemptNumber = extractDigits(screen.getByText(/(\d+)/)); + expect(attemptNumber).toBeGreaterThan(0); + }); + test("The Number of attempts is less than 10", () => { + const attemptNumber = extractDigits(screen.getByText(/(\d+)/)); + expect(attemptNumber).toBeLessThan(10); + }); + test("There is an initially enabled Start Quiz button", () => { + const startButton = screen.getByRole("button", { name: /Start Quiz/i }); + expect(startButton).toBeInTheDocument(); + expect(startButton).toBeEnabled(); + }); + test("There is an initially disabled Stop Quiz button", () => { + const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); + expect(stopButton).toBeInTheDocument(); + expect(stopButton).toBeDisabled(); + }); + test("There is an initially enabled Mulligan button", () => { + const mulliganButton = screen.getByRole("button", { + name: /Mulligan/i + }); + expect(mulliganButton).toBeInTheDocument(); + expect(mulliganButton).toBeEnabled(); + }); + test("Clicking Mulligan increases attempts", () => { + const attemptNumber: number = + extractDigits(screen.getByText(/(\d+)/)) || 0; + const mulliganButton = screen.getByRole("button", { + name: /Mulligan/i + }); + mulliganButton.click(); + const attemptNumberLater = extractDigits(screen.getByText(/(\d+)/)); + expect(attemptNumber + 1).toEqual(attemptNumberLater); + }); + test("Clicking Mulligan twice increases attempts by two", () => { + const attemptNumber: number = + extractDigits(screen.getByText(/(\d+)/)) || 0; + const mulliganButton = screen.getByRole("button", { + name: /Mulligan/i + }); + mulliganButton.click(); + mulliganButton.click(); + const attemptNumberLater = extractDigits(screen.getByText(/(\d+)/)); + expect(attemptNumber + 2).toEqual(attemptNumberLater); + }); + test("Clicking Start Quiz decreases attempts", () => { + const attemptNumber: number = + extractDigits(screen.getByText(/(\d+)/)) || 0; + const startButton = screen.getByRole("button", { + name: /Start Quiz/i + }); + startButton.click(); + const attemptNumberLater = + extractDigits(screen.getByText(/(\d+)/)) || 0; + expect(attemptNumber - 1).toEqual(attemptNumberLater); + }); + test("Clicking Start Quiz changes enabled buttons", () => { + // Given the buttons... + const startButton = screen.getByRole("button", { + name: /Start Quiz/i + }); + const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); + const mulliganButton = screen.getByRole("button", { + name: /Mulligan/i + }); + // When the start button is clicked + startButton.click(); + // Then the start is disabled, stop is enabled, and mulligan is disabled + expect(startButton).toBeDisabled(); + expect(stopButton).toBeEnabled(); + expect(mulliganButton).toBeDisabled(); + }); + test("Clicking Start and Stop Quiz changes enabled buttons", () => { + // Given the buttons and initial attempt number... + const startButton = screen.getByRole("button", { + name: /Start Quiz/i + }); + const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); + const mulliganButton = screen.getByRole("button", { + name: /Mulligan/i + }); + // When we click the start button and then the stop button + startButton.click(); + stopButton.click(); + // Then the start is enabled, stop is disabled, and mulligan is enabled + expect(startButton).toBeEnabled(); + expect(stopButton).toBeDisabled(); + expect(mulliganButton).toBeEnabled(); + }); + test("Clicking Start, Stop, Mulligan sets attempts to original", () => { + // Given the buttons and initial attempt number... + const startButton = screen.getByRole("button", { + name: /Start Quiz/i + }); + const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); + const mulliganButton = screen.getByRole("button", { + name: /Mulligan/i + }); + const attemptNumber: number = + extractDigits(screen.getByText(/(\d+)/)) || 0; + // When we click the start button and then the stop button + startButton.click(); + stopButton.click(); + // Then the attempt is decreased + const attemptNumberLater: number = + extractDigits(screen.getByText(/(\d+)/)) || 0; + expect(attemptNumber - 1).toEqual(attemptNumberLater); + // And when we click the mulligan button + mulliganButton.click(); + // Then the attempt is increased back to starting value + const attemptNumberLatest: number = + extractDigits(screen.getByText(/(\d+)/)) || 0; + expect(attemptNumber).toEqual(attemptNumberLatest); + }); + test("Cannot click start quiz when out of attempts", () => { + // Given the buttons and initial attempt number... + const startButton = screen.getByRole("button", { + name: /Start Quiz/i + }); + const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); + const mulliganButton = screen.getByRole("button", { + name: /Mulligan/i + }); + let attemptNumber = extractDigits(screen.getByText(/(\d+)/)) || 0; + const initialAttempt = attemptNumber; + // Arbitrary number of times to try clicking; assume we do not have more than that number of attempts. + let maxAttempts = 10; + // While there are still attempts apparently available... + while (attemptNumber > 0) { + // Then the buttons + expect(startButton).toBeEnabled(); + expect(stopButton).toBeDisabled(); + expect(mulliganButton).toBeEnabled(); + // And when we Start and then immediately stop the quiz... + startButton.click(); + stopButton.click(); + // Then the number is going down, and doesn't go past 0 somehow + attemptNumber = extractDigits(screen.getByText(/(\d+)/)) || 0; + expect(attemptNumber).toBeGreaterThanOrEqual(0); + expect(attemptNumber).not.toEqual(initialAttempt); + // And then the maximum number of attempts does not exceed 10 + maxAttempts -= 1; + expect(maxAttempts).toBeGreaterThanOrEqual(0); + } + // Then the attempt is at zero + expect(attemptNumber).toEqual(0); + // And then the stop button is disabled, the start button is disabled, and mulligan is enabled + expect(startButton).toBeDisabled(); + expect(stopButton).toBeDisabled(); + expect(mulliganButton).toBeEnabled(); + // And when we click the mulligan button + mulliganButton.click(); + // Then the attempt is increased back to 1 + const attemptNumberLatest: number = + extractDigits(screen.getByText(/(\d+)/)) || 0; + expect(attemptNumberLatest).toEqual(1); + // And the start button is reenabled + expect(startButton).toBeEnabled(); + expect(stopButton).toBeDisabled(); + expect(mulliganButton).toBeEnabled(); + }); +}); diff --git a/src/components/StartAttempt.tsx b/src/components/StartAttempt.tsx index 12786ec0cd..0c0a85fdb6 100644 --- a/src/components/StartAttempt.tsx +++ b/src/components/StartAttempt.tsx @@ -1,4 +1,5 @@ -import React from "react"; +import React, { useState } from "react"; +import { Button } from "react-bootstrap"; export function StartAttempt(): JSX.Element { return
Start Attempt
; diff --git a/src/components/TwoDice.test.tsx b/src/components/TwoDice.test.tsx new file mode 100644 index 0000000000..7996051096 --- /dev/null +++ b/src/components/TwoDice.test.tsx @@ -0,0 +1,140 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { TwoDice } from "./TwoDice"; +import { extractDigits } from "./StartAttempt.test"; + +describe("TwoDice Component tests", () => { + let mathRandomFunction: jest.SpyInstance; + beforeEach(() => { + mathRandomFunction = jest + .spyOn(global.Math, "random") + .mockReturnValue(0.5) // 4 + .mockReturnValueOnce(0.0) // 1 + .mockReturnValueOnce(0.99) // 6 + .mockReturnValueOnce(0.75) // 5 + .mockReturnValueOnce(0.75) // 5 + .mockReturnValueOnce(0.1) // 1 + .mockReturnValueOnce(0.1); // 1 + }); + afterEach(() => { + jest.spyOn(global.Math, "random").mockRestore(); + }); + beforeEach(() => { + render(); + }); + test("There is a `left-die` and `right-die` testid", () => { + const leftDie = screen.getByTestId("left-die"); + const rightDie = screen.getByTestId("right-die"); + expect(leftDie).toBeInTheDocument(); + expect(rightDie).toBeInTheDocument(); + }); + test("The `left-die` and `right-die` are two different numbers", () => { + const leftDie = screen.getByTestId("left-die"); + const rightDie = screen.getByTestId("right-die"); + const leftNumber = extractDigits(leftDie); + const rightNumber = extractDigits(rightDie); + // Then they are two numbers + expect(leftNumber).not.toBeNull(); + expect(rightNumber).not.toBeNull(); + // Then they are two different numbers + expect(leftNumber).not.toEqual(rightNumber); + }); + test("There are two buttons present", () => { + const leftButton = screen.getByRole("button", { name: /Roll Left/i }); + const rightButton = screen.getByRole("button", { name: /Roll Right/i }); + expect(leftButton).toBeInTheDocument(); + expect(rightButton).toBeInTheDocument(); + }); + test("Clicking left button changes first number", () => { + const leftButton = screen.getByRole("button", { name: /Roll Left/i }); + leftButton.click(); + leftButton.click(); + leftButton.click(); + // Then the random function should be called 3 times + expect(mathRandomFunction).toBeCalledTimes(3); + // And the number to be 5 + const leftNumber = extractDigits(screen.getByTestId("left-die")); + expect(leftNumber).toEqual(5); + }); + // Clicking right button changes second number + test("Clicking right button changes second number", () => { + const rightButton = screen.getByRole("button", { name: /Roll Right/i }); + rightButton.click(); + rightButton.click(); + rightButton.click(); + // Then the random function should be called 3 times + expect(mathRandomFunction).toBeCalledTimes(3); + // And the number to be 5 + const rightNumber = extractDigits(screen.getByTestId("right-die")); + expect(rightNumber).toEqual(5); + }); + // Rolling two different numbers does not win or lose the game + test("Rolling two different numbers does not win or lose the game", () => { + // Given + const leftButton = screen.getByRole("button", { name: /Roll Left/i }); + const rightButton = screen.getByRole("button", { name: /Roll Right/i }); + const leftDie = screen.getByTestId("left-die"); + const rightDie = screen.getByTestId("right-die"); + // When the left and right buttons are rolled once each + leftButton.click(); + rightButton.click(); + // Then the numbers are not equal + const leftNumber = extractDigits(leftDie); + const rightNumber = extractDigits(rightDie); + expect(leftNumber).toEqual(1); + expect(rightNumber).toEqual(6); + // And then the game is not won + const winText = screen.queryByText(/Win/i); + expect(winText).toBeNull(); + // And then nor is the game lost + const loseText = screen.queryByText(/Lose/i); + expect(loseText).toBeNull(); + }); + test("Getting snake eyes loses the game", () => { + // Given + const leftButton = screen.getByRole("button", { name: /Roll Left/i }); + const rightButton = screen.getByRole("button", { name: /Roll Right/i }); + const leftDie = screen.getByTestId("left-die"); + const rightDie = screen.getByTestId("right-die"); + // When the left and right buttons are rolled once each + leftButton.click(); + rightButton.click(); + rightButton.click(); + rightButton.click(); + rightButton.click(); + // Then the numbers are not equal + const leftNumber = extractDigits(leftDie); + const rightNumber = extractDigits(rightDie); + expect(leftNumber).toEqual(1); + expect(rightNumber).toEqual(1); + // And then the game is not won + const winText = screen.queryByText(/Win/i); + expect(winText).toBeNull(); + // And then the game is lost + const loseText = screen.getByText(/Lose/i); + expect(loseText).toBeInTheDocument(); + }); + test("Getting matching numbers wins the game", () => { + // Given + const leftButton = screen.getByRole("button", { name: /Roll Left/i }); + const rightButton = screen.getByRole("button", { name: /Roll Right/i }); + const leftDie = screen.getByTestId("left-die"); + const rightDie = screen.getByTestId("right-die"); + // When the left and right buttons are rolled once each + leftButton.click(); + leftButton.click(); + leftButton.click(); + rightButton.click(); + // Then the numbers are not equal + const leftNumber = extractDigits(leftDie); + const rightNumber = extractDigits(rightDie); + expect(leftNumber).toEqual(5); + expect(rightNumber).toEqual(5); + // And then the game is not lost + const loseText = screen.queryByText(/Lose/i); + expect(loseText).toBeNull(); + // And then the game is won + const winText = screen.getByText(/Win/i); + expect(winText).toBeInTheDocument(); + }); +}); diff --git a/src/components/TwoDice.tsx b/src/components/TwoDice.tsx index b9a5260966..372502fe64 100644 --- a/src/components/TwoDice.tsx +++ b/src/components/TwoDice.tsx @@ -1,4 +1,15 @@ -import React from "react"; +import React, { useState } from "react"; +import { Button } from "react-bootstrap"; + +/** + * Here is a helper function you *must* use to "roll" your die. + * The function uses the builtin `random` function of the `Math` + * module (which returns a random decimal between 0 up until 1) in order + * to produce a random integer between 1 and 6 (inclusive). + */ +export function d6(): number { + return 1 + Math.floor(Math.random() * 6); +} export function TwoDice(): JSX.Element { return
Two Dice
; From eb40f3eb8827e668c1949ca0c9c13db2f52fe4b4 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 19 Feb 2022 13:53:22 -0500 Subject: [PATCH 16/79] Forgot task record for state --- public/tasks/task-state.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 public/tasks/task-state.md diff --git a/public/tasks/task-state.md b/public/tasks/task-state.md new file mode 100644 index 0000000000..ef8197ffcb --- /dev/null +++ b/public/tasks/task-state.md @@ -0,0 +1,5 @@ +# Task - State + +Version: 0.0.1 + +Create some new components that have React State. From 6669ffad92d4bd98218097868d381d9280eb9fca Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 19 Feb 2022 15:23:47 -0500 Subject: [PATCH 17/79] First draft of components subtasks --- public/tasks/task-components.md | 5 +++ src/App.tsx | 12 +++++++ src/bad-components/ColoredBox.tsx | 41 ++++++++++++++++++++++ src/bad-components/DoubleHalf.tsx | 21 +++++++++++ src/bad-components/DoubleHalfState.tsx | 3 ++ src/bad-components/ShoveBox.tsx | 48 ++++++++++++++++++++++++++ src/bad-components/TrackingNumbers.tsx | 38 ++++++++++++++++++++ 7 files changed, 168 insertions(+) create mode 100644 public/tasks/task-components.md create mode 100644 src/bad-components/ColoredBox.tsx create mode 100644 src/bad-components/DoubleHalf.tsx create mode 100644 src/bad-components/DoubleHalfState.tsx create mode 100644 src/bad-components/ShoveBox.tsx create mode 100644 src/bad-components/TrackingNumbers.tsx diff --git a/public/tasks/task-components.md b/public/tasks/task-components.md new file mode 100644 index 0000000000..a6ff48694d --- /dev/null +++ b/public/tasks/task-components.md @@ -0,0 +1,5 @@ +# Task - Components + +Version: 0.0.1 + +Fix some components that are using state incorrectly. diff --git a/src/App.tsx b/src/App.tsx index 504138f1c3..98499aa554 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,10 @@ import { StartAttempt } from "./components/StartAttempt"; import { TwoDice } from "./components/TwoDice"; import { CycleHoliday } from "./components/CycleHoliday"; import { Counter } from "./components/Counter"; +import { DoubleHalf } from "./bad-components/DoubleHalf"; +import { ColoredBox } from "./bad-components/ColoredBox"; +import { ShoveBox } from "./bad-components/ShoveBox"; +import { TrackingNumbers } from "./bad-components/TrackingNumbers"; function App(): JSX.Element { return ( @@ -14,6 +18,14 @@ function App(): JSX.Element { UD CISC275 with React Hooks and TypeScript
+ {/* */} +
+ +
+ +
+ +

diff --git a/src/bad-components/ColoredBox.tsx b/src/bad-components/ColoredBox.tsx new file mode 100644 index 0000000000..622f77fd31 --- /dev/null +++ b/src/bad-components/ColoredBox.tsx @@ -0,0 +1,41 @@ +import React, { useState } from "react"; +import { Button } from "react-bootstrap"; + +export const COLORS = ["red", "blue", "green"]; +const DEFAULT_COLOR_INDEX = 0; + +function ChangeColor(): JSX.Element { + const [colorIndex, setColorIndex] = useState(DEFAULT_COLOR_INDEX); + return ( + + ); +} + +function ColorPreview(): JSX.Element { + return ( +
+ ); +} + +export function ColoredBox(): JSX.Element { + return ( +
+ The current color is: {COLORS[DEFAULT_COLOR_INDEX]} +
+ + +
+
+ ); +} diff --git a/src/bad-components/DoubleHalf.tsx b/src/bad-components/DoubleHalf.tsx new file mode 100644 index 0000000000..a8376dbea2 --- /dev/null +++ b/src/bad-components/DoubleHalf.tsx @@ -0,0 +1,21 @@ +import React, { useState } from "react"; +import { Button } from "react-bootstrap"; +import { dhValue, setDhValue } from "./DoubleHalfState"; + +function Doubler(): JSX.Element { + return ; +} + +function Halver(): JSX.Element { + return ; +} + +export function DoubleHalf(): JSX.Element { + return ( +
+ The current value is: {dhValue} + + +
+ ); +} diff --git a/src/bad-components/DoubleHalfState.tsx b/src/bad-components/DoubleHalfState.tsx new file mode 100644 index 0000000000..2b4569a37a --- /dev/null +++ b/src/bad-components/DoubleHalfState.tsx @@ -0,0 +1,3 @@ +import { useState } from "react"; + +export const [dhValue, setDhValue] = useState(10); diff --git a/src/bad-components/ShoveBox.tsx b/src/bad-components/ShoveBox.tsx new file mode 100644 index 0000000000..d0b4ec0561 --- /dev/null +++ b/src/bad-components/ShoveBox.tsx @@ -0,0 +1,48 @@ +import React, { useState } from "react"; +import { Button } from "react-bootstrap"; + +function ShoveBoxButton({ + position, + setPosition +}: { + position: number; + setPosition: (newPosition: number) => void; +}) { + return ( + + ); +} + +function MoveableBox(): JSX.Element { + const [position, setPosition] = useState(10); + return ( +
+ ); +} + +export function ShoveBox(): JSX.Element { + const box = MoveableBox(); + + return ( +
+ {/* The box is at: {box.position} +
+ + {box} +
*/} +
+ ); +} diff --git a/src/bad-components/TrackingNumbers.tsx b/src/bad-components/TrackingNumbers.tsx new file mode 100644 index 0000000000..910e273ed1 --- /dev/null +++ b/src/bad-components/TrackingNumbers.tsx @@ -0,0 +1,38 @@ +import React, { useState } from "react"; +import { Button } from "react-bootstrap"; + +export function TrackingNumbers(): JSX.Element { + const [latestNumber, setLatestNumber] = useState(0); + const [trackedNumbers, setTrackedNumbers] = useState([]); + + function stopTracking(targetNumber: number) { + const withoutNumber = trackedNumbers.filter( + (aNumber: number) => aNumber !== targetNumber + ); + setTrackedNumbers(withoutNumber); + } + + function trackNumber() { + setLatestNumber(1 + latestNumber); + const withNumber = [...trackedNumbers, latestNumber]; + setTrackedNumbers(withNumber); + } + + // Uncomment the below and fix the error! + return ( +
+ {/*
    + {trackedNumbers.map((trackedNumber: number) => ( +
  1. + Tracking {trackedNumber} ( + + ) +
  2. + ))} +
*/} + +
+ ); +} From 562f3067fd1fce48bd3ed2cb4cb6bfe1e24d65f5 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Thu, 24 Feb 2022 10:48:53 -0500 Subject: [PATCH 18/79] Another subtask, ChooseTeam --- src/App.tsx | 3 ++ src/bad-components/ChooseTeam.tsx | 54 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/bad-components/ChooseTeam.tsx diff --git a/src/App.tsx b/src/App.tsx index 98499aa554..d4ec176137 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ import { DoubleHalf } from "./bad-components/DoubleHalf"; import { ColoredBox } from "./bad-components/ColoredBox"; import { ShoveBox } from "./bad-components/ShoveBox"; import { TrackingNumbers } from "./bad-components/TrackingNumbers"; +import { ChooseTeam } from "./bad-components/ChooseTeam"; function App(): JSX.Element { return ( @@ -20,6 +21,8 @@ function App(): JSX.Element {
{/* */}
+ +

diff --git a/src/bad-components/ChooseTeam.tsx b/src/bad-components/ChooseTeam.tsx new file mode 100644 index 0000000000..f2c3cd49f0 --- /dev/null +++ b/src/bad-components/ChooseTeam.tsx @@ -0,0 +1,54 @@ +import React, { useState } from "react"; +import { Button, Row, Col } from "react-bootstrap"; + +const PEOPLE = [ + "Alan Turing", + "Grace Hopper", + "Ada Lovelace", + "Charles Babbage", + "Barbara Liskov", + "Margaret Hamilton" +]; + +export function ChooseTeam(): JSX.Element { + const [allOptions, setAllOptions] = useState(PEOPLE); + const [team, setTeam] = useState([]); + + function chooseMember() { + /* + if (!team.includes(newMember)) { + team.push(newMember); + } + */ + } + + function clearTeam() { + /* + team = []; + */ + } + + return ( +
+ + + {allOptions.map((option: string) => ( +
+ Add{" "} + +
+ ))} + + + Team: + {team.map((member: string) => ( +
  • {member}
  • + ))} + + +
    +
    + ); +} From 4a34f5fa59524db440c37c83f2edcbb1b640096c Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Thu, 24 Feb 2022 11:33:41 -0500 Subject: [PATCH 19/79] Oops order out of operations --- src/bad-components/ColoredBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bad-components/ColoredBox.tsx b/src/bad-components/ColoredBox.tsx index 622f77fd31..5e2a9f7ac8 100644 --- a/src/bad-components/ColoredBox.tsx +++ b/src/bad-components/ColoredBox.tsx @@ -7,7 +7,7 @@ const DEFAULT_COLOR_INDEX = 0; function ChangeColor(): JSX.Element { const [colorIndex, setColorIndex] = useState(DEFAULT_COLOR_INDEX); return ( - ); From 7327f4c2f93d210353eba03e002770d1d6503174 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Thu, 24 Feb 2022 11:41:03 -0500 Subject: [PATCH 20/79] Add headers to each subtask --- src/bad-components/ChooseTeam.tsx | 1 + src/bad-components/ColoredBox.tsx | 1 + src/bad-components/DoubleHalf.tsx | 1 + src/bad-components/ShoveBox.tsx | 1 + src/bad-components/TrackingNumbers.tsx | 1 + 5 files changed, 5 insertions(+) diff --git a/src/bad-components/ChooseTeam.tsx b/src/bad-components/ChooseTeam.tsx index f2c3cd49f0..c02334e661 100644 --- a/src/bad-components/ChooseTeam.tsx +++ b/src/bad-components/ChooseTeam.tsx @@ -30,6 +30,7 @@ export function ChooseTeam(): JSX.Element { return (
    +

    Choose Team

    {allOptions.map((option: string) => ( diff --git a/src/bad-components/ColoredBox.tsx b/src/bad-components/ColoredBox.tsx index 5e2a9f7ac8..0a5bb83d96 100644 --- a/src/bad-components/ColoredBox.tsx +++ b/src/bad-components/ColoredBox.tsx @@ -31,6 +31,7 @@ function ColorPreview(): JSX.Element { export function ColoredBox(): JSX.Element { return (
    +

    Colored Box

    The current color is: {COLORS[DEFAULT_COLOR_INDEX]}
    diff --git a/src/bad-components/DoubleHalf.tsx b/src/bad-components/DoubleHalf.tsx index a8376dbea2..f3871eeec6 100644 --- a/src/bad-components/DoubleHalf.tsx +++ b/src/bad-components/DoubleHalf.tsx @@ -13,6 +13,7 @@ function Halver(): JSX.Element { export function DoubleHalf(): JSX.Element { return (
    +

    Double Half

    The current value is: {dhValue} diff --git a/src/bad-components/ShoveBox.tsx b/src/bad-components/ShoveBox.tsx index d0b4ec0561..910aca490b 100644 --- a/src/bad-components/ShoveBox.tsx +++ b/src/bad-components/ShoveBox.tsx @@ -35,6 +35,7 @@ export function ShoveBox(): JSX.Element { return (
    +

    Shove Box

    {/* The box is at: {box.position}
    +

    Tracking Numbers

    {/*
      {trackedNumbers.map((trackedNumber: number) => (
    1. From cf7c212a6631e9ef7c9f9c4b4dfd5d3cac72acb5 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Thu, 24 Feb 2022 13:00:45 -0500 Subject: [PATCH 21/79] Make testing easier for these components --- src/bad-components/ColoredBox.tsx | 1 + src/bad-components/DoubleHalf.tsx | 4 +++- src/bad-components/ShoveBox.tsx | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bad-components/ColoredBox.tsx b/src/bad-components/ColoredBox.tsx index 0a5bb83d96..a3c3c3f077 100644 --- a/src/bad-components/ColoredBox.tsx +++ b/src/bad-components/ColoredBox.tsx @@ -16,6 +16,7 @@ function ChangeColor(): JSX.Element { function ColorPreview(): JSX.Element { return (

      Double Half

      - The current value is: {dhValue} +
      + The current value is: {dhValue} +
      diff --git a/src/bad-components/ShoveBox.tsx b/src/bad-components/ShoveBox.tsx index 910aca490b..7c55142636 100644 --- a/src/bad-components/ShoveBox.tsx +++ b/src/bad-components/ShoveBox.tsx @@ -17,6 +17,7 @@ function MoveableBox(): JSX.Element { const [position, setPosition] = useState(10); return (
      Date: Thu, 24 Feb 2022 13:01:04 -0500 Subject: [PATCH 22/79] Ugh this component is stupid, let's just forget about it for now --- src/App.tsx | 3 -- src/bad-components/TrackingNumbers.tsx | 39 -------------------------- 2 files changed, 42 deletions(-) delete mode 100644 src/bad-components/TrackingNumbers.tsx diff --git a/src/App.tsx b/src/App.tsx index d4ec176137..e09c26ec76 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,7 +9,6 @@ import { Counter } from "./components/Counter"; import { DoubleHalf } from "./bad-components/DoubleHalf"; import { ColoredBox } from "./bad-components/ColoredBox"; import { ShoveBox } from "./bad-components/ShoveBox"; -import { TrackingNumbers } from "./bad-components/TrackingNumbers"; import { ChooseTeam } from "./bad-components/ChooseTeam"; function App(): JSX.Element { @@ -27,8 +26,6 @@ function App(): JSX.Element {

      - -

      diff --git a/src/bad-components/TrackingNumbers.tsx b/src/bad-components/TrackingNumbers.tsx deleted file mode 100644 index 70a9a7c7de..0000000000 --- a/src/bad-components/TrackingNumbers.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useState } from "react"; -import { Button } from "react-bootstrap"; - -export function TrackingNumbers(): JSX.Element { - const [latestNumber, setLatestNumber] = useState(0); - const [trackedNumbers, setTrackedNumbers] = useState([]); - - function stopTracking(targetNumber: number) { - const withoutNumber = trackedNumbers.filter( - (aNumber: number) => aNumber !== targetNumber - ); - setTrackedNumbers(withoutNumber); - } - - function trackNumber() { - setLatestNumber(1 + latestNumber); - const withNumber = [...trackedNumbers, latestNumber]; - setTrackedNumbers(withNumber); - } - - // Uncomment the below and fix the error! - return ( -
      -

      Tracking Numbers

      - {/*
        - {trackedNumbers.map((trackedNumber: number) => ( -
      1. - Tracking {trackedNumber} ( - - ) -
      2. - ))} -
      */} - -
      - ); -} From 89053a45855b254fb0363d8f002ac5535524e77f Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Thu, 24 Feb 2022 13:03:58 -0500 Subject: [PATCH 23/79] Provide the tests for the bad components --- src/bad-components/ChooseTeam.test.tsx | 61 ++++++++++++++++++++++++++ src/bad-components/ColoredBox.test.tsx | 31 +++++++++++++ src/bad-components/DoubleHalf.test.tsx | 56 +++++++++++++++++++++++ src/bad-components/ShoveBox.test.tsx | 31 +++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 src/bad-components/ChooseTeam.test.tsx create mode 100644 src/bad-components/ColoredBox.test.tsx create mode 100644 src/bad-components/DoubleHalf.test.tsx create mode 100644 src/bad-components/ShoveBox.test.tsx diff --git a/src/bad-components/ChooseTeam.test.tsx b/src/bad-components/ChooseTeam.test.tsx new file mode 100644 index 0000000000..f11465a2d6 --- /dev/null +++ b/src/bad-components/ChooseTeam.test.tsx @@ -0,0 +1,61 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { ChooseTeam } from "./ChooseTeam"; + +describe("ChooseTeam Component tests", () => { + beforeEach(() => { + render(); + }); + test("The initial team is empty", () => { + const currentTeam = screen.queryAllByRole("listitem"); + expect(currentTeam).toHaveLength(0); + }); + test("There are 7 buttons.", () => { + const adders = screen.queryAllByRole("button"); + expect(adders).toHaveLength(7); + }); + test("Clicking first team member works", () => { + const first = screen.queryAllByRole("button")[0]; + first.click(); + const currentTeam = screen.queryAllByRole("listitem"); + expect(currentTeam).toHaveLength(1); + expect(currentTeam[0].textContent).toEqual(first.textContent); + }); + test("Clicking the third team member works", () => { + const third = screen.queryAllByRole("button")[2]; + third.click(); + const currentTeam = screen.queryAllByRole("listitem"); + expect(currentTeam).toHaveLength(1); + expect(currentTeam[0].textContent).toEqual(third.textContent); + }); + test("Clicking three team members works", () => { + const [, second, third, , fifth] = screen.queryAllByRole("button"); + third.click(); + second.click(); + fifth.click(); + const currentTeam = screen.queryAllByRole("listitem"); + expect(currentTeam).toHaveLength(3); + expect(currentTeam[0].textContent).toEqual(third.textContent); + expect(currentTeam[1].textContent).toEqual(second.textContent); + expect(currentTeam[2].textContent).toEqual(fifth.textContent); + }); + test("Clearing the team works", () => { + const [, second, third, fourth, fifth, , clear] = + screen.queryAllByRole("button"); + third.click(); + second.click(); + fifth.click(); + let currentTeam = screen.queryAllByRole("listitem"); + expect(currentTeam).toHaveLength(3); + expect(currentTeam[0].textContent).toEqual(third.textContent); + expect(currentTeam[1].textContent).toEqual(second.textContent); + expect(currentTeam[2].textContent).toEqual(fifth.textContent); + clear.click(); + currentTeam = screen.queryAllByRole("listitem"); + expect(currentTeam).toHaveLength(0); + fourth.click(); + currentTeam = screen.queryAllByRole("listitem"); + expect(currentTeam).toHaveLength(1); + expect(currentTeam[0].textContent).toEqual(fourth.textContent); + }); +}); diff --git a/src/bad-components/ColoredBox.test.tsx b/src/bad-components/ColoredBox.test.tsx new file mode 100644 index 0000000000..c17a13f66c --- /dev/null +++ b/src/bad-components/ColoredBox.test.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { ColoredBox } from "./ColoredBox"; + +describe("ColoredBox Component tests", () => { + beforeEach(() => { + render(); + }); + test("The ColoredBox is initially red.", () => { + const box = screen.getByTestId("colored-box"); + expect(box).toHaveStyle({ backgroundColor: "red" }); + }); + test("There is a button", () => { + expect(screen.getByRole("button")).toBeInTheDocument(); + }); + test("Clicking the button advances the color.", () => { + const nextColor = screen.getByRole("button"); + nextColor.click(); + expect(screen.getByTestId("colored-box")).toHaveStyle({ + backgroundColor: "blue" + }); + nextColor.click(); + expect(screen.getByTestId("colored-box")).toHaveStyle({ + backgroundColor: "green" + }); + nextColor.click(); + expect(screen.getByTestId("colored-box")).toHaveStyle({ + backgroundColor: "red" + }); + }); +}); diff --git a/src/bad-components/DoubleHalf.test.tsx b/src/bad-components/DoubleHalf.test.tsx new file mode 100644 index 0000000000..cbae5f68af --- /dev/null +++ b/src/bad-components/DoubleHalf.test.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { DoubleHalf } from "./DoubleHalf"; + +describe("DoubleHalf Component tests", () => { + beforeEach(() => { + render(); + }); + test("The DoubleHalf value is initially 10", () => { + expect(screen.getByText("10")).toBeInTheDocument(); + expect(screen.queryByText("20")).not.toBeInTheDocument(); + expect(screen.queryByText("5")).not.toBeInTheDocument(); + }); + test("There are Double and Halve buttons", () => { + expect( + screen.getByRole("button", { name: /Double/i }) + ).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: /Halve/i }) + ).toBeInTheDocument(); + }); + test("You can double the number.", () => { + const double = screen.getByRole("button", { name: /Double/i }); + double.click(); + expect(screen.getByText("20")).toBeInTheDocument(); + expect(screen.queryByText("10")).not.toBeInTheDocument(); + }); + test("You can halve the number.", () => { + const halve = screen.getByRole("button", { name: /Halve/i }); + halve.click(); + expect(screen.getByText("5")).toBeInTheDocument(); + expect(screen.queryByText("10")).not.toBeInTheDocument(); + }); + test("You can double AND halve the number.", () => { + const double = screen.getByRole("button", { name: /Double/i }); + const halve = screen.getByRole("button", { name: /Halve/i }); + double.click(); + expect(screen.getByText("20")).toBeInTheDocument(); + expect(screen.queryByText("10")).not.toBeInTheDocument(); + double.click(); + expect(screen.getByText("40")).toBeInTheDocument(); + expect(screen.queryByText("20")).not.toBeInTheDocument(); + halve.click(); + expect(screen.getByText("20")).toBeInTheDocument(); + expect(screen.queryByText("10")).not.toBeInTheDocument(); + halve.click(); + expect(screen.getByText("10")).toBeInTheDocument(); + expect(screen.queryByText("20")).not.toBeInTheDocument(); + halve.click(); + expect(screen.getByText("5")).toBeInTheDocument(); + expect(screen.queryByText("10")).not.toBeInTheDocument(); + halve.click(); + expect(screen.getByText("2.5")).toBeInTheDocument(); + expect(screen.queryByText("5")).not.toBeInTheDocument(); + }); +}); diff --git a/src/bad-components/ShoveBox.test.tsx b/src/bad-components/ShoveBox.test.tsx new file mode 100644 index 0000000000..2adec13d4e --- /dev/null +++ b/src/bad-components/ShoveBox.test.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { ShoveBox } from "./ShoveBox"; + +describe("ShoveBox Component tests", () => { + beforeEach(() => { + render(); + }); + test("The MoveableBox is initially nearby.", () => { + const box = screen.getByTestId("moveable-box"); + expect(box).toHaveStyle({ marginLeft: "10px" }); + }); + test("There is a button", () => { + expect(screen.getByRole("button")).toBeInTheDocument(); + }); + test("Clicking the button moves the box.", () => { + const shoveBox = screen.getByRole("button"); + shoveBox.click(); + expect(screen.getByTestId("moveable-box")).toHaveStyle({ + marginLeft: "14px" + }); + shoveBox.click(); + expect(screen.getByTestId("moveable-box")).toHaveStyle({ + marginLeft: "18px" + }); + shoveBox.click(); + expect(screen.getByTestId("moveable-box")).toHaveStyle({ + marginLeft: "22px" + }); + }); +}); From 7a207345d9e404afd04607811b89bb758de02905 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 24 Aug 2024 12:52:12 -0400 Subject: [PATCH 24/79] Include json test command here --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index cf6e1bc772..fc2b66a549 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "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", From 7fe9ca316fad2e694586e037fe601b85a2584c56 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Mon, 31 Jan 2022 16:37:54 -0500 Subject: [PATCH 25/79] Require Hello World in the document --- src/text.Test.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/text.Test.tsx diff --git a/src/text.Test.tsx b/src/text.Test.tsx new file mode 100644 index 0000000000..b32c330d3f --- /dev/null +++ b/src/text.Test.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import App from "./App"; + +test("renders the text 'Hello World' somewhere", () => { + render(); + const text = screen.getByText(/Hello World/); + expect(text).toBeInTheDocument(); +}); From b8b8878c873d4faa2fd5f04d656e23d66c7d6cef Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Mon, 31 Jan 2022 16:56:42 -0500 Subject: [PATCH 26/79] Include the task info --- public/tasks/task-first-branch.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 public/tasks/task-first-branch.md diff --git a/public/tasks/task-first-branch.md b/public/tasks/task-first-branch.md new file mode 100644 index 0000000000..94333338a0 --- /dev/null +++ b/public/tasks/task-first-branch.md @@ -0,0 +1,5 @@ +# Task - First Branch + +Version: 0.0.1 + +Pass a short test to have certain text on the page. From fbdebdec2006b01d3976bd9408037baf82eb5e56 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Mon, 31 Jan 2022 16:41:17 -0500 Subject: [PATCH 27/79] Rename text.Test.tsx to text.test.tsx --- src/{text.Test.tsx => text.test.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{text.Test.tsx => text.test.tsx} (100%) diff --git a/src/text.Test.tsx b/src/text.test.tsx similarity index 100% rename from src/text.Test.tsx rename to src/text.test.tsx From 2f0146c22beca5c5ac48603876f0fa8ea2e2e905 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Thu, 3 Feb 2022 14:10:55 -0500 Subject: [PATCH 28/79] Allow one or more instances of the Hello World text --- src/text.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/text.test.tsx b/src/text.test.tsx index b32c330d3f..f99a063e76 100644 --- a/src/text.test.tsx +++ b/src/text.test.tsx @@ -4,6 +4,6 @@ import App from "./App"; test("renders the text 'Hello World' somewhere", () => { render(); - const text = screen.getByText(/Hello World/); - expect(text).toBeInTheDocument(); + const texts = screen.getAllByText(/Hello World/); + expect(texts.length).toBeGreaterThanOrEqual(1); }); From ac36b32302a8ea2e66b4b954626c8e396e172075 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 29 Jan 2022 23:59:24 -0500 Subject: [PATCH 29/79] First set of tests --- public/tasks/task-html-css.md | 5 +++++ src/HtmlCss.test.tsx | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 public/tasks/task-html-css.md create mode 100644 src/HtmlCss.test.tsx diff --git a/public/tasks/task-html-css.md b/public/tasks/task-html-css.md new file mode 100644 index 0000000000..ebc0efcba5 --- /dev/null +++ b/public/tasks/task-html-css.md @@ -0,0 +1,5 @@ +# Task - HTML/CSS + +Version: 0.0.1 + +Add in some HTML and CSS, including a fancy looking button. diff --git a/src/HtmlCss.test.tsx b/src/HtmlCss.test.tsx new file mode 100644 index 0000000000..168ce37fde --- /dev/null +++ b/src/HtmlCss.test.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { render, screen } from "@testing-library/react"; +import App from "./App"; + +describe("Some HTML Elements are added.", () => { + test("There is a header", () => { + render(); + const header = screen.getByRole("heading"); + expect(header).toBeInTheDocument(); + }); +}); + +describe("Some basic CSS is added.", () => { + test("There is a floating red box", () => { + render(); + expect(true); + }); +}); + +describe("Some Bootstrap Elements are added", () => { + test("There is a bootstrap button", () => { + render(); + const button = screen.getByRole("button"); + expect(button).toBeInTheDocument(); + expect(button).toHaveClass("btn"); + expect(button).toHaveClass("btn-primary"); + }); +}); From d04739d1d2ec0c934c0ecf1dc05ac1289063627d Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sun, 30 Jan 2022 00:24:38 -0500 Subject: [PATCH 30/79] Some logging tests --- src/HtmlCss.test.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/HtmlCss.test.tsx b/src/HtmlCss.test.tsx index 168ce37fde..bf957616f9 100644 --- a/src/HtmlCss.test.tsx +++ b/src/HtmlCss.test.tsx @@ -1,6 +1,7 @@ import React from "react"; import { render, screen } from "@testing-library/react"; import App from "./App"; +import userEvent from "@testing-library/user-event"; describe("Some HTML Elements are added.", () => { test("There is a header", () => { @@ -18,11 +19,25 @@ describe("Some basic CSS is added.", () => { }); describe("Some Bootstrap Elements are added", () => { - test("There is a bootstrap button", () => { + test("There is one bootstrap button with the text 'Log Hello World'", () => { render(); - const button = screen.getByRole("button"); + const button = screen.getByRole("button", { name: /Log Hello World/i }); expect(button).toBeInTheDocument(); expect(button).toHaveClass("btn"); expect(button).toHaveClass("btn-primary"); }); + + test("Not clicking the bootstrap button does not logs 'Hello World!'", () => { + const consoleSpy = jest.spyOn(console, "log"); + render(); + expect(consoleSpy).not.toHaveBeenCalledWith("Hello World!"); + }); + + test("Clicking the bootstrap button logs 'Hello World!'", () => { + const consoleSpy = jest.spyOn(console, "log"); + render(); + const button = screen.getByRole("button", { name: /Log Hello World/i }); + userEvent.click(button); + expect(consoleSpy).toHaveBeenCalledWith("Hello World!"); + }); }); From b26100f543943eced73fdff33784861243c65386 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sun, 30 Jan 2022 00:47:43 -0500 Subject: [PATCH 31/79] More html tests --- src/HtmlCss.test.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/HtmlCss.test.tsx b/src/HtmlCss.test.tsx index bf957616f9..676c37f903 100644 --- a/src/HtmlCss.test.tsx +++ b/src/HtmlCss.test.tsx @@ -9,6 +9,20 @@ describe("Some HTML Elements are added.", () => { const header = screen.getByRole("heading"); expect(header).toBeInTheDocument(); }); + + test("There is an image with alt text", () => { + render(); + const image = screen.getByRole("image"); + expect(image).toBeInTheDocument(); + expect(image).toHaveAttribute("alt"); + }); + + test("There is a list with at least three elements", () => { + render(); + const list = screen.getByRole("list"); + expect(list).toBeInTheDocument(); + expect(list.children.length).toBeGreaterThanOrEqual(3); + }); }); describe("Some basic CSS is added.", () => { From 3bf4550a8f042dee28a57c06abec05dfce779519 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sun, 30 Jan 2022 00:55:24 -0500 Subject: [PATCH 32/79] Fix the image test --- src/HtmlCss.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HtmlCss.test.tsx b/src/HtmlCss.test.tsx index 676c37f903..79b7db2dda 100644 --- a/src/HtmlCss.test.tsx +++ b/src/HtmlCss.test.tsx @@ -12,7 +12,7 @@ describe("Some HTML Elements are added.", () => { test("There is an image with alt text", () => { render(); - const image = screen.getByRole("image"); + const image = screen.getByRole("img"); expect(image).toBeInTheDocument(); expect(image).toHaveAttribute("alt"); }); From 8dff2b64a2efc0b1b49703077965fe5e334eab1a Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Mon, 31 Jan 2022 16:31:58 -0500 Subject: [PATCH 33/79] Updated CSS tests, left a note about additional tests --- src/HtmlCss.test.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/HtmlCss.test.tsx b/src/HtmlCss.test.tsx index 79b7db2dda..379fc8f449 100644 --- a/src/HtmlCss.test.tsx +++ b/src/HtmlCss.test.tsx @@ -30,6 +30,14 @@ describe("Some basic CSS is added.", () => { render(); expect(true); }); + + test("The background color of the header area is different", () => { + render(); + const banner = screen.getByRole("banner"); + expect(banner).not.toHaveStyle({ + "background-color": "rgb(40, 44, 52)" + }); + }); }); describe("Some Bootstrap Elements are added", () => { From b66d4de909f74de4cba160a6fff05b078b9b47cc Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Mon, 31 Jan 2022 16:32:13 -0500 Subject: [PATCH 34/79] See previous commit message --- src/HtmlCss.test.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/HtmlCss.test.tsx b/src/HtmlCss.test.tsx index 379fc8f449..36767ad350 100644 --- a/src/HtmlCss.test.tsx +++ b/src/HtmlCss.test.tsx @@ -26,11 +26,6 @@ describe("Some HTML Elements are added.", () => { }); describe("Some basic CSS is added.", () => { - test("There is a floating red box", () => { - render(); - expect(true); - }); - test("The background color of the header area is different", () => { render(); const banner = screen.getByRole("banner"); @@ -63,3 +58,7 @@ describe("Some Bootstrap Elements are added", () => { expect(consoleSpy).toHaveBeenCalledWith("Hello World!"); }); }); + +/** + * Remember, there are additional tasks described on the page! + */ From 0a24364f67b1ee221ebe175d6c494d5eca6ad7dc Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 24 Aug 2024 13:10:09 -0400 Subject: [PATCH 35/79] Add in new css test --- src/HtmlCss.test.tsx | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/HtmlCss.test.tsx b/src/HtmlCss.test.tsx index 36767ad350..48b0a6df2d 100644 --- a/src/HtmlCss.test.tsx +++ b/src/HtmlCss.test.tsx @@ -30,7 +30,7 @@ describe("Some basic CSS is added.", () => { render(); const banner = screen.getByRole("banner"); expect(banner).not.toHaveStyle({ - "background-color": "rgb(40, 44, 52)" + "background-color": "rgb(40, 44, 52)", }); }); }); @@ -59,6 +59,25 @@ describe("Some Bootstrap Elements are added", () => { }); }); -/** - * Remember, there are additional tasks described on the page! - */ +describe("Some additional CSS was added", () => { + test("checks if any element has a background color of red", () => { + const { container } = render(); + // Get all elements in the rendered container + const elements = container.querySelectorAll("*"); + + // Check if any element has a background color of red + let foundRedBackground = false; + + elements.forEach((element) => { + const style = getComputedStyle(element); + if ( + style.backgroundColor === "red" || + style.backgroundColor === "rgb(255, 0, 0)" + ) { + foundRedBackground = true; + } + }); + + expect(foundRedBackground).toBe(true); + }); +}); From 4d43d7a5d133522b0a8d92e1cb3d7e4053a81992 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 24 Aug 2024 13:12:28 -0400 Subject: [PATCH 36/79] Add in points --- src/HtmlCss.test.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/HtmlCss.test.tsx b/src/HtmlCss.test.tsx index 48b0a6df2d..320cb97524 100644 --- a/src/HtmlCss.test.tsx +++ b/src/HtmlCss.test.tsx @@ -4,20 +4,20 @@ import App from "./App"; import userEvent from "@testing-library/user-event"; describe("Some HTML Elements are added.", () => { - test("There is a header", () => { + test("(2 pts) There is a header", () => { render(); const header = screen.getByRole("heading"); expect(header).toBeInTheDocument(); }); - test("There is an image with alt text", () => { + test("(2 pts) There is an image with alt text", () => { render(); const image = screen.getByRole("img"); expect(image).toBeInTheDocument(); expect(image).toHaveAttribute("alt"); }); - test("There is a list with at least three elements", () => { + test("(2 pts) There is a list with at least three elements", () => { render(); const list = screen.getByRole("list"); expect(list).toBeInTheDocument(); @@ -25,7 +25,7 @@ describe("Some HTML Elements are added.", () => { }); }); -describe("Some basic CSS is added.", () => { +describe("(2 pts) Some basic CSS is added.", () => { test("The background color of the header area is different", () => { render(); const banner = screen.getByRole("banner"); @@ -35,7 +35,7 @@ describe("Some basic CSS is added.", () => { }); }); -describe("Some Bootstrap Elements are added", () => { +describe("(2 pts) Some Bootstrap Elements are added", () => { test("There is one bootstrap button with the text 'Log Hello World'", () => { render(); const button = screen.getByRole("button", { name: /Log Hello World/i }); @@ -44,13 +44,13 @@ describe("Some Bootstrap Elements are added", () => { expect(button).toHaveClass("btn-primary"); }); - test("Not clicking the bootstrap button does not logs 'Hello World!'", () => { + test("(2 pts) Not clicking the bootstrap button does not logs 'Hello World!'", () => { const consoleSpy = jest.spyOn(console, "log"); render(); expect(consoleSpy).not.toHaveBeenCalledWith("Hello World!"); }); - test("Clicking the bootstrap button logs 'Hello World!'", () => { + test("(2 pts) Clicking the bootstrap button logs 'Hello World!'", () => { const consoleSpy = jest.spyOn(console, "log"); render(); const button = screen.getByRole("button", { name: /Log Hello World/i }); @@ -60,7 +60,7 @@ describe("Some Bootstrap Elements are added", () => { }); describe("Some additional CSS was added", () => { - test("checks if any element has a background color of red", () => { + test("(2 pts) checks if any element has a background color of red", () => { const { container } = render(); // Get all elements in the rendered container const elements = container.querySelectorAll("*"); From 83c4461f9dbe7d2a66c09eed18959565a302eea2 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 29 Jan 2022 23:23:45 -0500 Subject: [PATCH 37/79] Basic functions tests and stubs --- public/tasks/task-functions.md | 5 +++ src/functions.test.ts | 59 ++++++++++++++++++++++++++++++++++ src/functions.ts | 19 +++++++++++ 3 files changed, 83 insertions(+) create mode 100644 public/tasks/task-functions.md create mode 100644 src/functions.test.ts create mode 100644 src/functions.ts diff --git a/public/tasks/task-functions.md b/public/tasks/task-functions.md new file mode 100644 index 0000000000..36e7926bb7 --- /dev/null +++ b/public/tasks/task-functions.md @@ -0,0 +1,5 @@ +# Task - Functions + +Version: 0.0.1 + +Implement a bunch of functions that work on primitives. diff --git a/src/functions.test.ts b/src/functions.test.ts new file mode 100644 index 0000000000..e0bef414ea --- /dev/null +++ b/src/functions.test.ts @@ -0,0 +1,59 @@ +import { + add3, + fahrenheitToCelius, + shout, + isQuestion, + convertYesNo +} from "./functions"; + +test("Testing the basic functions", () => { + it("Testing the add3 function", () => { + expect(add3(1, 2, 3)).toBe(6); + expect(add3(9, 7, 4)).toBe(20); + expect(add3(6, -3, 9)).toBe(15); + expect(add3(10, 1, -9)).toBe(11); + expect(add3(-9, -100, -4)).toBe(0); + expect(add3(-1, -1, 1)).toBe(1); + }); + + it("Testing the fahrenheitToCelius function", () => { + expect(fahrenheitToCelius(32)).toBe(0); + expect(fahrenheitToCelius(-40)).toBe(40); + expect(fahrenheitToCelius(-22)).toBe(-30); + expect(fahrenheitToCelius(14)).toBe(-10); + expect(fahrenheitToCelius(68)).toBe(20); + expect(fahrenheitToCelius(86)).toBe(30); + expect(fahrenheitToCelius(212)).toBe(100); + }); + + it("Testing the shout function", () => { + expect(shout("Hello")).toBe("HELLO!"); + expect(shout("What?")).toBe("WHAT?!"); + expect(shout("oHo")).toBe("OHO!"); + expect(shout("AHHHH!!!")).toBe("AHHHH!!!!"); + expect(shout("")).toBe("!"); + expect(shout("Please go outside")).toBe("PLEASE GO OUTSIDE!"); + }); + + it("Testing the isQuestion function", () => { + expect(isQuestion("Is this a question?")).toBe(true); + expect(isQuestion("Who are you?")).toBe(true); + expect(isQuestion("WHAT ARE YOU !?")).toBe(true); + expect(isQuestion("WHAT IS THIS?!")).toBe(false); + expect(isQuestion("OH GOD!")).toBe(false); + expect(isQuestion("Oh nevermind, it's fine.")).toBe(false); + expect(isQuestion("")).toBe(false); + }); + + it("Testing the convertYesNo function", () => { + expect(convertYesNo("yes")).toBe(true); + expect(convertYesNo("YES")).toBe(true); + expect(convertYesNo("NO")).toBe(false); + expect(convertYesNo("no")).toBe(false); + expect(convertYesNo("Apple")).toBe(null); + expect(convertYesNo("Bananas")).toBe(null); + expect(convertYesNo("Nope")).toBe(null); + expect(convertYesNo("Yesterday")).toBe(null); + expect(convertYesNo("Maybe")).toBe(null); + }); +}); diff --git a/src/functions.ts b/src/functions.ts new file mode 100644 index 0000000000..03193e4212 --- /dev/null +++ b/src/functions.ts @@ -0,0 +1,19 @@ +export function fahrenheitToCelius(temperature: number): number { + return 0; +} + +export function add3(first: number, second: number, third: number): number { + return 0; +} + +export function shout(message: string): string { + return ""; +} + +export function isQuestion(message: string): boolean { + return true; +} + +export function convertYesNo(word: string): boolean | null { + return true; +} From a48653022ec3c8b7ce99cf49d98b041e20815420 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 29 Jan 2022 23:30:17 -0500 Subject: [PATCH 38/79] Fix test organization --- src/functions.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/functions.test.ts b/src/functions.test.ts index e0bef414ea..98c926bb6f 100644 --- a/src/functions.test.ts +++ b/src/functions.test.ts @@ -6,8 +6,8 @@ import { convertYesNo } from "./functions"; -test("Testing the basic functions", () => { - it("Testing the add3 function", () => { +describe("Testing the basic functions", () => { + test("Testing the add3 function", () => { expect(add3(1, 2, 3)).toBe(6); expect(add3(9, 7, 4)).toBe(20); expect(add3(6, -3, 9)).toBe(15); @@ -16,7 +16,7 @@ test("Testing the basic functions", () => { expect(add3(-1, -1, 1)).toBe(1); }); - it("Testing the fahrenheitToCelius function", () => { + test("Testing the fahrenheitToCelius function", () => { expect(fahrenheitToCelius(32)).toBe(0); expect(fahrenheitToCelius(-40)).toBe(40); expect(fahrenheitToCelius(-22)).toBe(-30); @@ -26,7 +26,7 @@ test("Testing the basic functions", () => { expect(fahrenheitToCelius(212)).toBe(100); }); - it("Testing the shout function", () => { + test("Testing the shout function", () => { expect(shout("Hello")).toBe("HELLO!"); expect(shout("What?")).toBe("WHAT?!"); expect(shout("oHo")).toBe("OHO!"); @@ -35,7 +35,7 @@ test("Testing the basic functions", () => { expect(shout("Please go outside")).toBe("PLEASE GO OUTSIDE!"); }); - it("Testing the isQuestion function", () => { + test("Testing the isQuestion function", () => { expect(isQuestion("Is this a question?")).toBe(true); expect(isQuestion("Who are you?")).toBe(true); expect(isQuestion("WHAT ARE YOU !?")).toBe(true); @@ -45,7 +45,7 @@ test("Testing the basic functions", () => { expect(isQuestion("")).toBe(false); }); - it("Testing the convertYesNo function", () => { + test("Testing the convertYesNo function", () => { expect(convertYesNo("yes")).toBe(true); expect(convertYesNo("YES")).toBe(true); expect(convertYesNo("NO")).toBe(false); From 9722564e99cecda5d50dd95524c94a76c4cda923 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 29 Jan 2022 23:39:22 -0500 Subject: [PATCH 39/79] Fix issue in fahrenheit conversion --- src/functions.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functions.test.ts b/src/functions.test.ts index 98c926bb6f..3eb9f4f3aa 100644 --- a/src/functions.test.ts +++ b/src/functions.test.ts @@ -18,7 +18,7 @@ describe("Testing the basic functions", () => { test("Testing the fahrenheitToCelius function", () => { expect(fahrenheitToCelius(32)).toBe(0); - expect(fahrenheitToCelius(-40)).toBe(40); + expect(fahrenheitToCelius(-40)).toBe(-40); expect(fahrenheitToCelius(-22)).toBe(-30); expect(fahrenheitToCelius(14)).toBe(-10); expect(fahrenheitToCelius(68)).toBe(20); From bd06d5d0e3ed264f7bffb4e8e4811d0efc170255 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Thu, 3 Feb 2022 14:27:08 -0500 Subject: [PATCH 40/79] Move around some of the functions --- src/functions.test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/functions.test.ts b/src/functions.test.ts index 3eb9f4f3aa..c496ac7e99 100644 --- a/src/functions.test.ts +++ b/src/functions.test.ts @@ -7,15 +7,6 @@ import { } from "./functions"; describe("Testing the basic functions", () => { - test("Testing the add3 function", () => { - expect(add3(1, 2, 3)).toBe(6); - expect(add3(9, 7, 4)).toBe(20); - expect(add3(6, -3, 9)).toBe(15); - expect(add3(10, 1, -9)).toBe(11); - expect(add3(-9, -100, -4)).toBe(0); - expect(add3(-1, -1, 1)).toBe(1); - }); - test("Testing the fahrenheitToCelius function", () => { expect(fahrenheitToCelius(32)).toBe(0); expect(fahrenheitToCelius(-40)).toBe(-40); @@ -26,6 +17,15 @@ describe("Testing the basic functions", () => { expect(fahrenheitToCelius(212)).toBe(100); }); + test("Testing the add3 function", () => { + expect(add3(1, 2, 3)).toBe(6); + expect(add3(9, 7, 4)).toBe(20); + expect(add3(6, -3, 9)).toBe(15); + expect(add3(10, 1, -9)).toBe(11); + expect(add3(-9, -100, -4)).toBe(0); + expect(add3(-1, -1, 1)).toBe(1); + }); + test("Testing the shout function", () => { expect(shout("Hello")).toBe("HELLO!"); expect(shout("What?")).toBe("WHAT?!"); From 4cd1900783f690690229b7c17cf9e81995f52b3a Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Thu, 3 Feb 2022 14:27:18 -0500 Subject: [PATCH 41/79] Explain what the actual functions require you to do --- src/functions.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/functions.ts b/src/functions.ts index 03193e4212..e614c81c0c 100644 --- a/src/functions.ts +++ b/src/functions.ts @@ -1,19 +1,41 @@ +/** + * 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 0; } +/** + * 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 { return 0; } +/** + * 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 ""; } +/** + * 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 true; } +/** + * 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 { return true; } From cf1d21a31d00c2e8dc8bb7c76f372b3e0adebfbe Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 24 Aug 2024 13:15:59 -0400 Subject: [PATCH 42/79] Update formatting --- src/functions.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functions.test.ts b/src/functions.test.ts index c496ac7e99..a082bfd61a 100644 --- a/src/functions.test.ts +++ b/src/functions.test.ts @@ -3,7 +3,7 @@ import { fahrenheitToCelius, shout, isQuestion, - convertYesNo + convertYesNo, } from "./functions"; describe("Testing the basic functions", () => { From e11693a366f61cdb442c6f6f5822bd49e2dd604f Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 24 Aug 2024 13:18:24 -0400 Subject: [PATCH 43/79] Add in points --- src/functions.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/functions.test.ts b/src/functions.test.ts index a082bfd61a..3d921f5d64 100644 --- a/src/functions.test.ts +++ b/src/functions.test.ts @@ -7,7 +7,7 @@ import { } from "./functions"; describe("Testing the basic functions", () => { - test("Testing the fahrenheitToCelius function", () => { + test("(3 pts) Testing the fahrenheitToCelius function", () => { expect(fahrenheitToCelius(32)).toBe(0); expect(fahrenheitToCelius(-40)).toBe(-40); expect(fahrenheitToCelius(-22)).toBe(-30); @@ -17,7 +17,7 @@ describe("Testing the basic functions", () => { expect(fahrenheitToCelius(212)).toBe(100); }); - test("Testing the add3 function", () => { + test("(3 pts) Testing the add3 function", () => { expect(add3(1, 2, 3)).toBe(6); expect(add3(9, 7, 4)).toBe(20); expect(add3(6, -3, 9)).toBe(15); @@ -26,7 +26,7 @@ describe("Testing the basic functions", () => { expect(add3(-1, -1, 1)).toBe(1); }); - test("Testing the shout function", () => { + test("(3 pts) Testing the shout function", () => { expect(shout("Hello")).toBe("HELLO!"); expect(shout("What?")).toBe("WHAT?!"); expect(shout("oHo")).toBe("OHO!"); @@ -35,7 +35,7 @@ describe("Testing the basic functions", () => { expect(shout("Please go outside")).toBe("PLEASE GO OUTSIDE!"); }); - test("Testing the isQuestion function", () => { + test("(3 pts) Testing the isQuestion function", () => { expect(isQuestion("Is this a question?")).toBe(true); expect(isQuestion("Who are you?")).toBe(true); expect(isQuestion("WHAT ARE YOU !?")).toBe(true); @@ -45,7 +45,7 @@ describe("Testing the basic functions", () => { expect(isQuestion("")).toBe(false); }); - test("Testing the convertYesNo function", () => { + test("(3 pts) Testing the convertYesNo function", () => { expect(convertYesNo("yes")).toBe(true); expect(convertYesNo("YES")).toBe(true); expect(convertYesNo("NO")).toBe(false); From 7cc4e3f20e61307e9f22eb466fe21871b3eefbd3 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Tue, 1 Feb 2022 14:51:32 -0500 Subject: [PATCH 44/79] First stab at array problems --- public/tasks/task-arrays.md | 5 +++ src/arrays.test.ts | 12 ++++++ src/arrays.ts | 84 +++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 public/tasks/task-arrays.md create mode 100644 src/arrays.test.ts create mode 100644 src/arrays.ts diff --git a/public/tasks/task-arrays.md b/public/tasks/task-arrays.md new file mode 100644 index 0000000000..c2fbf80f8d --- /dev/null +++ b/public/tasks/task-arrays.md @@ -0,0 +1,5 @@ +# Task - Arrays + +Version: 0.0.1 + +Implement functions that work with arrays immutably. diff --git a/src/arrays.test.ts b/src/arrays.test.ts new file mode 100644 index 0000000000..b812349d2f --- /dev/null +++ b/src/arrays.test.ts @@ -0,0 +1,12 @@ +import { bookEndList } from "./arrays"; + +describe("Testing the array functions", () => { + const NUMBERS_1 = [1, 2, 3]; + + test("Testing the bookEndList function", () => { + // Ensure that the original array was not changed + expect(bookEndList(NUMBERS_1)).not.toBe(NUMBERS_1); + // And that a correct new array was returned + expect(bookEndList(NUMBERS_1)).toEqual([1, 3]); + }); +}); diff --git a/src/arrays.ts b/src/arrays.ts new file mode 100644 index 0000000000..7604b40cdb --- /dev/null +++ b/src/arrays.ts @@ -0,0 +1,84 @@ +/** + * 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[] { + return numbers; +} + +/** + * 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; +} + +/** + * Consume an array of strings and convert them to integers. If + * the number cannot be parsed as an integer, convert it to "?" instead. + */ +export function stringsToIntegers(numbers: string[]): number[] { + return []; +} + +/** + * 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 "?" instead. + */ +// Remember, you can write functions as lambdas too! They work exactly the same. +export const removeDollars = (amounts: string[]): number[] => { + return []; +}; + +/** + * Consume an array of messages and return a new list of the messages. However, any + * string that ends in "!" should be made uppercase. + */ +export const shoutIfExclaiming = (messages: string[]): string[] => { + return []; +}; + +/** + * 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 0; +} + +/** + * 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 { + return false; +} + +/** + * 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". + */ +export function makeMath(addends: number[]): string { + return ""; +} + +/** + * 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 + * 0 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, 0] + */ +export function injectPositive(values: number[]): number[] { + return []; +} From f25333778032fc42866a278af6a3ce871f735150 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Tue, 1 Feb 2022 16:09:10 -0500 Subject: [PATCH 45/79] Add in the rest of the tests --- src/arrays.test.ts | 269 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 265 insertions(+), 4 deletions(-) diff --git a/src/arrays.test.ts b/src/arrays.test.ts index b812349d2f..0881d9fe8a 100644 --- a/src/arrays.test.ts +++ b/src/arrays.test.ts @@ -1,12 +1,273 @@ -import { bookEndList } from "./arrays"; +import { + allRGB, + bookEndList, + countShortWords, + injectPositive, + makeMath, + removeDollars, + shoutIfExclaiming, + stringsToIntegers, + tripleNumbers +} from "./arrays"; describe("Testing the array functions", () => { + ////////////////////////////////// + // bookEndList and tripleNumbers + const NUMBERS_1 = [1, 2, 3]; + const NUMBERS_2 = [100, 300, 200]; + const NUMBERS_3 = [5]; + const NUMBERS_4: number[] = []; + const NUMBERS_5 = [100, 199, 1, -5, 7, 3]; + const NUMBERS_6 = [-100, -200, 100, 200]; + const NUMBERS_7 = [199, 1, 550, 50, 200]; + + // Ensure that none of the arrays were changed mutably + // If you fail these, you aren't using map/filter/reduce/etc. properly! + afterEach(() => { + expect(NUMBERS_1).toEqual([1, 2, 3]); + expect(NUMBERS_2).toEqual([100, 300, 200]); + expect(NUMBERS_3).toEqual([5]); + expect(NUMBERS_4).toEqual([]); + expect(NUMBERS_5).toEqual([100, 199, 1, -5, 7, 3]); + expect(NUMBERS_6).toEqual([-100, -200, 100, 200]); + expect(NUMBERS_7).toEqual([199, 1, 550, 50, 200]); + }); test("Testing the bookEndList function", () => { - // Ensure that the original array was not changed - expect(bookEndList(NUMBERS_1)).not.toBe(NUMBERS_1); - // And that a correct new array was returned expect(bookEndList(NUMBERS_1)).toEqual([1, 3]); + expect(bookEndList(NUMBERS_2)).toEqual([100, 200]); + expect(bookEndList(NUMBERS_3)).toEqual([5, 5]); + expect(bookEndList(NUMBERS_4)).toEqual([]); + expect(bookEndList(NUMBERS_5)).toEqual([100, 3]); + expect(bookEndList(NUMBERS_6)).toEqual([-100, 200]); + }); + + test("Testing the tripleNumbers function", () => { + expect(tripleNumbers(NUMBERS_1)).toEqual([3, 6, 9]); + expect(tripleNumbers(NUMBERS_2)).toEqual([300, 900, 600]); + expect(tripleNumbers(NUMBERS_3)).toEqual([15]); + expect(tripleNumbers(NUMBERS_4)).toEqual([]); + expect(tripleNumbers(NUMBERS_5)).toEqual([300, 597, 3, -15, 21, 9]); + expect(tripleNumbers(NUMBERS_6)).toEqual([-300, -600, 300, 600]); + }); + + ////////////////////////////////// + // stringsToIntegers + + const VALUES_1 = ["1", "2", "3"]; + const VALUES_2 = ["100", "200", "300"]; + const VALUES_3 = ["5"]; + const VALUES_4: string[] = []; + const VALUES_5 = ["100", "?", "27", "$44"]; + const VALUES_6 = ["-1", "0", "1", "*1"]; + const VALUES_7 = ["apple", "banana", "cactus"]; + + // Ensure that none of the arrays were changed mutably + // If you fail these, you aren't using map/filter/reduce/etc. properly! + afterEach(() => { + expect(VALUES_1).toEqual(["1", "2", "3"]); + expect(VALUES_2).toEqual(["100", "200", "300"]); + expect(VALUES_3).toEqual(["5"]); + expect(VALUES_4).toEqual([]); + expect(VALUES_5).toEqual(["100", "?", "27", "$44"]); + expect(VALUES_6).toEqual(["-1", "0", "1", "*1"]); + expect(VALUES_7).toEqual(["apple", "banana", "cactus"]); + }); + + test("Testing the stringsToIntegers function", () => { + expect(stringsToIntegers(VALUES_1)).toEqual([1, 2, 3]); + expect(stringsToIntegers(VALUES_2)).toEqual([100, 200, 300]); + expect(stringsToIntegers(VALUES_3)).toEqual([5]); + expect(stringsToIntegers(VALUES_4)).toEqual([]); + expect(stringsToIntegers(VALUES_5)).toEqual([100, 0, 27, 0]); + expect(stringsToIntegers(VALUES_6)).toEqual([-1, 0, 1, 0]); + expect(stringsToIntegers(VALUES_7)).toEqual([0, 0, 0]); + }); + + ////////////////////////////////// + // removeDollars + + const AMOUNTS_1 = ["$1", "$2", "$3"]; + const AMOUNTS_2 = ["$100", "$200", "$300", "$400"]; + const AMOUNTS_3 = ["$5"]; + const AMOUNTS_4 = ["$"]; + const AMOUNTS_5 = ["100", "200", "$300", "$400"]; + const AMOUNTS_6: string[] = []; + const AMOUNTS_7 = ["100", "???", "7", "$233", "", "$"]; + const AMOUNTS_8 = ["$one", "two", "$three"]; + + // Ensure that none of the arrays were changed mutably + // If you fail these, you aren't using map/filter/reduce/etc. properly! + afterEach(() => { + expect(AMOUNTS_1).toEqual(["$1", "$2", "$3"]); + expect(AMOUNTS_2).toEqual(["$100", "$200", "$300", "$400"]); + expect(AMOUNTS_3).toEqual(["$5"]); + expect(AMOUNTS_4).toEqual(["$"]); + expect(AMOUNTS_5).toEqual(["100", "200", "$300", "$400"]); + expect(AMOUNTS_6).toEqual([]); + expect(AMOUNTS_7).toEqual(["100", "???", "7", "$233", "", "$"]); + expect(AMOUNTS_8).toEqual(["$one", "two", "$three"]); + }); + + test("Testing the removeDollars function", () => { + expect(removeDollars(AMOUNTS_1)).toEqual([1, 2, 3]); + expect(removeDollars(AMOUNTS_2)).toEqual([100, 200, 300, 400]); + expect(removeDollars(AMOUNTS_3)).toEqual([5]); + expect(removeDollars(AMOUNTS_4)).toEqual([0]); + expect(removeDollars(AMOUNTS_5)).toEqual([100, 200, 300, 400]); + expect(removeDollars(AMOUNTS_6)).toEqual([]); + expect(removeDollars(AMOUNTS_7)).toEqual([100, 0, 7, 233, 0, 0]); + expect(removeDollars(AMOUNTS_8)).toEqual([0, 0, 0]); + }); + + ////////////////////////////////// + // shoutIfExclaiming + + const MESSAGE_1 = ["Hello", "you", "are", "great!"]; + const MESSAGE_2 = ["oho!", "Oho!", "oHo!", "oHO!", "OHO!"]; + const MESSAGE_3 = ["Wait?", "What?", "Lo", "How?", "High!"]; + const MESSAGE_4 = ["??????"]; + const MESSAGE_5: string[] = ["This one is very long!"]; + const MESSAGE_6 = ["No", "Caps", "here.", "Right?"]; + + // Ensure that none of the arrays were changed mutably + // If you fail these, you aren't using map/filter/reduce/etc. properly! + afterEach(() => { + expect(MESSAGE_1).toEqual(["Hello", "you", "are", "great!"]); + expect(MESSAGE_2).toEqual(["oho!", "Oho!", "oHo!", "oHO!", "OHO!"]); + expect(MESSAGE_3).toEqual(["Wait?", "What?", "Lo", "How?", "High!"]); + expect(MESSAGE_4).toEqual(["??????"]); + expect(MESSAGE_5).toEqual(["This one is very long!"]); + expect(MESSAGE_6).toEqual(["No", "Caps", "here.", "Right?"]); + }); + + test("Testing the shoutIfExclaiming function", () => { + expect(shoutIfExclaiming(MESSAGE_1)).toEqual([ + "Hello", + "you", + "are", + "GREAT!" + ]); + expect(shoutIfExclaiming(MESSAGE_2)).toEqual([ + "OHO!", + "OHO!", + "OHO!", + "OHO!", + "OHO!" + ]); + expect(shoutIfExclaiming(MESSAGE_3)).toEqual(["Lo", "HIGH!"]); + expect(shoutIfExclaiming(MESSAGE_4)).toEqual([]); + expect(shoutIfExclaiming(MESSAGE_5)).toEqual([ + "THIS ONE IS VERY LONG!" + ]); + expect(shoutIfExclaiming(MESSAGE_6)).toEqual(["No", "Caps", "here."]); + }); + + ////////////////////////////////// + // countShortWords + + const WORDS_1 = ["the", "cat", "in", "the", "hat"]; + const WORDS_2 = ["one", "two", "three", "four", "five", "six", "seven"]; + const WORDS_3 = ["alpha", "beta", "gamma"]; + const WORDS_4 = ["Longest", "Words", "Possible"]; + const WORDS_5: string[] = []; + const WORDS_6 = ["", "", "", ""]; + + // Ensure that none of the arrays were changed mutably + // If you fail these, you aren't using map/filter/reduce/etc. properly! + afterEach(() => { + expect(WORDS_1).toEqual(["the", "cat", "in", "the", "hat"]); + expect(WORDS_2).toEqual([ + "one", + "two", + "three", + "four", + "five", + "six", + "seven" + ]); + expect(WORDS_3).toEqual(["alpha", "beta", "gamma"]); + expect(WORDS_4).toEqual(["Longest", "Words", "Possible"]); + expect(WORDS_5).toEqual([]); + expect(WORDS_6).toEqual(["", "", "", ""]); + }); + + test("Testing the countShortWords function", () => { + expect(countShortWords(WORDS_1)).toEqual(5); + expect(countShortWords(WORDS_2)).toEqual(3); + expect(countShortWords(WORDS_3)).toEqual(0); + expect(countShortWords(WORDS_4)).toEqual(0); + expect(countShortWords(WORDS_5)).toEqual(0); + expect(countShortWords(WORDS_6)).toEqual(4); + }); + + ////////////////////////////////// + // allRGB + + const COLORS_1 = ["red", "green", "blue"]; + const COLORS_2 = ["red", "red", "red"]; + const COLORS_3 = ["red", "red", "blue", "blue", "green", "red"]; + const COLORS_4 = ["purple", "orange", "violet"]; + const COLORS_5 = ["red", "blue", "yellow"]; + const COLORS_6 = ["green"]; + const COLORS_7 = ["red"]; + const COLORS_8 = ["kabluey"]; + const COLORS_9: string[] = []; + + // Ensure that none of the arrays were changed mutably + // If you fail these, you aren't using map/filter/reduce/etc. properly! + afterEach(() => { + expect(COLORS_1).toEqual(["red", "green", "blue"]); + expect(COLORS_2).toEqual(["red", "red", "red"]); + expect(COLORS_3).toEqual([ + "red", + "red", + "blue", + "blue", + "green", + "red" + ]); + expect(COLORS_4).toEqual(["purple", "orange", "violet"]); + expect(COLORS_5).toEqual(["red", "blue", "yellow"]); + expect(COLORS_6).toEqual(["green"]); + expect(COLORS_7).toEqual(["red"]); + expect(COLORS_8).toEqual(["kabluey"]); + expect(COLORS_9).toEqual([]); + }); + + test("Testing the allRGB function", () => { + expect(allRGB(COLORS_1)).toEqual(true); + expect(allRGB(COLORS_2)).toEqual(true); + expect(allRGB(COLORS_3)).toEqual(true); + expect(allRGB(COLORS_4)).toEqual(false); + expect(allRGB(COLORS_5)).toEqual(false); + expect(allRGB(COLORS_6)).toEqual(true); + expect(allRGB(COLORS_7)).toEqual(true); + expect(allRGB(COLORS_8)).toEqual(false); + expect(allRGB(COLORS_9)).toEqual(true); + }); + + ////////////////////////////////// + // makeMath + + test("Testing the makeMath function", () => { + expect(makeMath(NUMBERS_1)).toEqual("6=1+2+3"); + expect(makeMath(NUMBERS_2)).toEqual("600=100+300+200"); + expect(makeMath(NUMBERS_3)).toEqual("5=5"); + expect(makeMath(NUMBERS_4)).toEqual("0=0"); + expect(makeMath(NUMBERS_7)).toEqual("1000=199+1+550+50+200"); + }); + + ////////////////////////////////// + // injectPositive + test("Testing the tripleNumbers function", () => { + expect(injectPositive(NUMBERS_1)).toEqual([1, 2, 3, 6]); + expect(injectPositive(NUMBERS_2)).toEqual([100, 300, 200, 600]); + expect(injectPositive(NUMBERS_3)).toEqual([5, 5]); + expect(injectPositive(NUMBERS_4)).toEqual([0]); + expect(injectPositive(NUMBERS_5)).toEqual([100, 199, 1, -5, 300, 7, 3]); + expect(injectPositive(NUMBERS_6)).toEqual([-100, 0, -200, 100, 200]); + expect(injectPositive(NUMBERS_7)).toEqual([199, 1, 550, 50, 200, 1000]); }); }); From b8777b1873553a2e2780b67fd504486b9d16bd92 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Tue, 1 Feb 2022 16:09:25 -0500 Subject: [PATCH 46/79] Fix question text --- src/arrays.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/arrays.ts b/src/arrays.ts index 7604b40cdb..4a2ffe8e5b 100644 --- a/src/arrays.ts +++ b/src/arrays.ts @@ -18,7 +18,7 @@ export function tripleNumbers(numbers: number[]): number[] { /** * Consume an array of strings and convert them to integers. If - * the number cannot be parsed as an integer, convert it to "?" instead. + * the number cannot be parsed as an integer, convert it to 0 instead. */ export function stringsToIntegers(numbers: string[]): number[] { return []; @@ -28,7 +28,7 @@ export function stringsToIntegers(numbers: string[]): number[] { * 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 "?" instead. + * convert it to 0 instead. */ // Remember, you can write functions as lambdas too! They work exactly the same. export const removeDollars = (amounts: string[]): number[] => { @@ -37,7 +37,8 @@ export const removeDollars = (amounts: string[]): number[] => { /** * Consume an array of messages and return a new list of the messages. However, any - * string that ends in "!" should be made uppercase. + * string that ends in "!" should be made uppercase. Also, remove any strings that end + * in question marks ("?"). */ export const shoutIfExclaiming = (messages: string[]): string[] => { return []; @@ -65,6 +66,7 @@ export function allRGB(colors: string[]): boolean { * 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 { return ""; @@ -74,10 +76,10 @@ export function makeMath(addends: number[]): string { * 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 - * 0 to the list. + * 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, 0] + * And the array [1, 9, 7] would become [1, 9, 7, 17] */ export function injectPositive(values: number[]): number[] { return []; From f87771e7d8058f6c4fc6d8c6d036953f65b3a775 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Fri, 11 Feb 2022 14:24:17 -0500 Subject: [PATCH 47/79] Update arrays.test.ts --- src/arrays.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arrays.test.ts b/src/arrays.test.ts index 0881d9fe8a..3652078efa 100644 --- a/src/arrays.test.ts +++ b/src/arrays.test.ts @@ -261,7 +261,7 @@ describe("Testing the array functions", () => { ////////////////////////////////// // injectPositive - test("Testing the tripleNumbers function", () => { + test("Testing the injectPositive function", () => { expect(injectPositive(NUMBERS_1)).toEqual([1, 2, 3, 6]); expect(injectPositive(NUMBERS_2)).toEqual([100, 300, 200, 600]); expect(injectPositive(NUMBERS_3)).toEqual([5, 5]); From f0d316b36ae394d502e75849b5532b76ffdf7c68 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 24 Aug 2024 13:21:13 -0400 Subject: [PATCH 48/79] Add in points --- src/arrays.test.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/arrays.test.ts b/src/arrays.test.ts index 3652078efa..c2847517bd 100644 --- a/src/arrays.test.ts +++ b/src/arrays.test.ts @@ -7,7 +7,7 @@ import { removeDollars, shoutIfExclaiming, stringsToIntegers, - tripleNumbers + tripleNumbers, } from "./arrays"; describe("Testing the array functions", () => { @@ -34,7 +34,7 @@ describe("Testing the array functions", () => { expect(NUMBERS_7).toEqual([199, 1, 550, 50, 200]); }); - test("Testing the bookEndList function", () => { + test("(3 pts) Testing the bookEndList function", () => { expect(bookEndList(NUMBERS_1)).toEqual([1, 3]); expect(bookEndList(NUMBERS_2)).toEqual([100, 200]); expect(bookEndList(NUMBERS_3)).toEqual([5, 5]); @@ -43,7 +43,7 @@ describe("Testing the array functions", () => { expect(bookEndList(NUMBERS_6)).toEqual([-100, 200]); }); - test("Testing the tripleNumbers function", () => { + test("(3 pts) Testing the tripleNumbers function", () => { expect(tripleNumbers(NUMBERS_1)).toEqual([3, 6, 9]); expect(tripleNumbers(NUMBERS_2)).toEqual([300, 900, 600]); expect(tripleNumbers(NUMBERS_3)).toEqual([15]); @@ -75,7 +75,7 @@ describe("Testing the array functions", () => { expect(VALUES_7).toEqual(["apple", "banana", "cactus"]); }); - test("Testing the stringsToIntegers function", () => { + test("(3 pts) Testing the stringsToIntegers function", () => { expect(stringsToIntegers(VALUES_1)).toEqual([1, 2, 3]); expect(stringsToIntegers(VALUES_2)).toEqual([100, 200, 300]); expect(stringsToIntegers(VALUES_3)).toEqual([5]); @@ -110,7 +110,7 @@ describe("Testing the array functions", () => { expect(AMOUNTS_8).toEqual(["$one", "two", "$three"]); }); - test("Testing the removeDollars function", () => { + test("(3 pts) Testing the removeDollars function", () => { expect(removeDollars(AMOUNTS_1)).toEqual([1, 2, 3]); expect(removeDollars(AMOUNTS_2)).toEqual([100, 200, 300, 400]); expect(removeDollars(AMOUNTS_3)).toEqual([5]); @@ -142,24 +142,24 @@ describe("Testing the array functions", () => { expect(MESSAGE_6).toEqual(["No", "Caps", "here.", "Right?"]); }); - test("Testing the shoutIfExclaiming function", () => { + test("(3 pts) Testing the shoutIfExclaiming function", () => { expect(shoutIfExclaiming(MESSAGE_1)).toEqual([ "Hello", "you", "are", - "GREAT!" + "GREAT!", ]); expect(shoutIfExclaiming(MESSAGE_2)).toEqual([ "OHO!", "OHO!", "OHO!", "OHO!", - "OHO!" + "OHO!", ]); expect(shoutIfExclaiming(MESSAGE_3)).toEqual(["Lo", "HIGH!"]); expect(shoutIfExclaiming(MESSAGE_4)).toEqual([]); expect(shoutIfExclaiming(MESSAGE_5)).toEqual([ - "THIS ONE IS VERY LONG!" + "THIS ONE IS VERY LONG!", ]); expect(shoutIfExclaiming(MESSAGE_6)).toEqual(["No", "Caps", "here."]); }); @@ -185,7 +185,7 @@ describe("Testing the array functions", () => { "four", "five", "six", - "seven" + "seven", ]); expect(WORDS_3).toEqual(["alpha", "beta", "gamma"]); expect(WORDS_4).toEqual(["Longest", "Words", "Possible"]); @@ -193,7 +193,7 @@ describe("Testing the array functions", () => { expect(WORDS_6).toEqual(["", "", "", ""]); }); - test("Testing the countShortWords function", () => { + test("(3 pts) Testing the countShortWords function", () => { expect(countShortWords(WORDS_1)).toEqual(5); expect(countShortWords(WORDS_2)).toEqual(3); expect(countShortWords(WORDS_3)).toEqual(0); @@ -226,7 +226,7 @@ describe("Testing the array functions", () => { "blue", "blue", "green", - "red" + "red", ]); expect(COLORS_4).toEqual(["purple", "orange", "violet"]); expect(COLORS_5).toEqual(["red", "blue", "yellow"]); @@ -236,7 +236,7 @@ describe("Testing the array functions", () => { expect(COLORS_9).toEqual([]); }); - test("Testing the allRGB function", () => { + test("(3 pts) Testing the allRGB function", () => { expect(allRGB(COLORS_1)).toEqual(true); expect(allRGB(COLORS_2)).toEqual(true); expect(allRGB(COLORS_3)).toEqual(true); @@ -251,7 +251,7 @@ describe("Testing the array functions", () => { ////////////////////////////////// // makeMath - test("Testing the makeMath function", () => { + test("(3 pts) Testing the makeMath function", () => { expect(makeMath(NUMBERS_1)).toEqual("6=1+2+3"); expect(makeMath(NUMBERS_2)).toEqual("600=100+300+200"); expect(makeMath(NUMBERS_3)).toEqual("5=5"); @@ -261,7 +261,7 @@ describe("Testing the array functions", () => { ////////////////////////////////// // injectPositive - test("Testing the injectPositive function", () => { + test("(3 pts) Testing the injectPositive function", () => { expect(injectPositive(NUMBERS_1)).toEqual([1, 2, 3, 6]); expect(injectPositive(NUMBERS_2)).toEqual([100, 300, 200, 600]); expect(injectPositive(NUMBERS_3)).toEqual([5, 5]); From c2e556dece7ea7737c13bdd355ef3ebcee121e70 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 2 Feb 2022 13:12:40 -0500 Subject: [PATCH 49/79] 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 406ffb2b572cb14e885af2a2fddc8e9cc42c97dd Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sun, 6 Feb 2022 18:33:46 -0500 Subject: [PATCH 50/79] 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 9b9adb6f2ccbd1113a09cb8e13186d6d4f829928 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 24 Aug 2024 13:27:44 -0400 Subject: [PATCH 51/79] Fix formatting --- src/objects.test.ts | 70 ++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/src/objects.test.ts b/src/objects.test.ts index a9c76a334e..4d3117405d 100644 --- a/src/objects.test.ts +++ b/src/objects.test.ts @@ -9,7 +9,7 @@ import { renameQuestion, publishQuestion, addOption, - mergeQuestion + mergeQuestion, } from "./objects"; import testQuestionData from "./data/questions.json"; import backupQuestionData from "./data/questions.json"; @@ -25,7 +25,7 @@ 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, }: Record = backupQuestionData as Record< string, Question[] @@ -38,7 +38,7 @@ const [ BACKUP_ADDITION_QUESTION, BACKUP_LETTER_QUESTION, BACKUP_COLOR_QUESTION, - BACKUP_SHAPE_QUESTION + BACKUP_SHAPE_QUESTION, ] = BACKUP_SIMPLE_QUESTIONS; //////////////////////////////////////////// @@ -48,21 +48,25 @@ describe("Testing the object functions", () => { ////////////////////////////////// // makeBlankQuestion - test("Testing the makeBlankQuestion function", () => { + test("(3 pts) Testing the makeBlankQuestion function", () => { expect( - makeBlankQuestion(1, "Question 1", "multiple_choice_question") + makeBlankQuestion(1, "Question 1", "multiple_choice_question"), ).toEqual(BLANK_QUESTIONS[0]); expect( - makeBlankQuestion(47, "My New Question", "multiple_choice_question") + makeBlankQuestion( + 47, + "My New Question", + "multiple_choice_question", + ), ).toEqual(BLANK_QUESTIONS[1]); expect( - makeBlankQuestion(2, "Question 2", "short_answer_question") + makeBlankQuestion(2, "Question 2", "short_answer_question"), ).toEqual(BLANK_QUESTIONS[2]); }); /////////////////////////////////// // isCorrect - test("Testing the isCorrect function", () => { + test("(3 pts) 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); @@ -81,7 +85,7 @@ describe("Testing the object functions", () => { /////////////////////////////////// // isValid - test("Testing the isValid function", () => { + test("(3 pts) 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); @@ -104,7 +108,7 @@ describe("Testing the object functions", () => { /////////////////////////////////// // toShortForm - test("Testing the toShortForm function", () => { + test("(3 pts) 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"); @@ -114,7 +118,7 @@ describe("Testing the object functions", () => { /////////////////////////////////// // toMarkdown - test("Testing the toMarkdown function", () => { + test("(3 pts) Testing the toMarkdown function", () => { expect(toMarkdown(ADDITION_QUESTION)).toEqual(`# Addition What is 2+2?`); expect(toMarkdown(LETTER_QUESTION)).toEqual(`# Letters @@ -141,9 +145,9 @@ What shape can you make with one line? /////////////////////////////////// // renameQuestion - test("Testing the renameQuestion function", () => { + test("(3 pts) Testing the renameQuestion function", () => { expect( - renameQuestion(ADDITION_QUESTION, "My Addition Question") + renameQuestion(ADDITION_QUESTION, "My Addition Question"), ).toEqual({ id: 1, name: "My Addition Question", @@ -152,10 +156,10 @@ What shape can you make with one line? options: [], expected: "4", points: 1, - published: true + published: true, }); expect( - renameQuestion(SHAPE_QUESTION, "I COMPLETELY CHANGED THIS NAME") + renameQuestion(SHAPE_QUESTION, "I COMPLETELY CHANGED THIS NAME"), ).toEqual({ id: 9, name: "I COMPLETELY CHANGED THIS NAME", @@ -164,13 +168,13 @@ What shape can you make with one line? options: ["square", "triangle", "circle"], expected: "circle", points: 2, - published: false + published: false, }); }); /////////////////////////////////// // publishQuestion - test("Testing the publishQuestion function", () => { + test("(3 pts) Testing the publishQuestion function", () => { expect(publishQuestion(ADDITION_QUESTION)).toEqual({ id: 1, name: "Addition", @@ -179,7 +183,7 @@ What shape can you make with one line? options: [], expected: "4", points: 1, - published: false + published: false, }); expect(publishQuestion(LETTER_QUESTION)).toEqual({ id: 2, @@ -189,7 +193,7 @@ What shape can you make with one line? options: [], expected: "Z", points: 1, - published: true + published: true, }); expect(publishQuestion(publishQuestion(ADDITION_QUESTION))).toEqual({ id: 1, @@ -199,13 +203,13 @@ What shape can you make with one line? options: [], expected: "4", points: 1, - published: true + published: true, }); }); /////////////////////////////////// // duplicateQuestion - test("Testing the duplicateQuestion function", () => { + test("(3 pts) Testing the duplicateQuestion function", () => { expect(duplicateQuestion(9, ADDITION_QUESTION)).toEqual({ id: 9, name: "Copy of Addition", @@ -214,7 +218,7 @@ What shape can you make with one line? options: [], expected: "4", points: 1, - published: false + published: false, }); expect(duplicateQuestion(55, LETTER_QUESTION)).toEqual({ id: 55, @@ -224,13 +228,13 @@ What shape can you make with one line? options: [], expected: "Z", points: 1, - published: false + published: false, }); }); /////////////////////////////////// // addOption - test("Testing the addOption function", () => { + test("(3 pts) Testing the addOption function", () => { expect(addOption(SHAPE_QUESTION, "heptagon")).toEqual({ id: 9, name: "Shapes", @@ -239,7 +243,7 @@ What shape can you make with one line? options: ["square", "triangle", "circle", "heptagon"], expected: "circle", points: 2, - published: false + published: false, }); expect(addOption(COLOR_QUESTION, "squiggles")).toEqual({ id: 5, @@ -249,20 +253,20 @@ What shape can you make with one line? options: ["red", "apple", "firetruck", "squiggles"], expected: "red", points: 1, - published: true + published: true, }); }); /////////////////////////////////// // mergeQuestion - test("Testing the mergeQuestion function", () => { + test("(3 pts) Testing the mergeQuestion function", () => { expect( mergeQuestion( 192, "More Points Addition", ADDITION_QUESTION, - SHAPE_QUESTION - ) + SHAPE_QUESTION, + ), ).toEqual({ id: 192, name: "More Points Addition", @@ -271,7 +275,7 @@ What shape can you make with one line? options: [], expected: "4", points: 2, - published: false + published: false, }); expect( @@ -279,8 +283,8 @@ What shape can you make with one line? 99, "Less Points Shape", SHAPE_QUESTION, - ADDITION_QUESTION - ) + ADDITION_QUESTION, + ), ).toEqual({ id: 99, name: "Less Points Shape", @@ -289,7 +293,7 @@ What shape can you make with one line? options: ["square", "triangle", "circle"], expected: "circle", points: 1, - published: false + published: false, }); }); }); From 3660252c2f3f53f262fadb91e8d14d0eeffa6cd2 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 2 Feb 2022 13:12:40 -0500 Subject: [PATCH 52/79] 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 09d3d4f104a2cacab2641271c5c6cab55424efd1 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sun, 6 Feb 2022 18:33:46 -0500 Subject: [PATCH 53/79] 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 9a2402444e847b2c05ce0c9a6887534a249d7c46 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Tue, 8 Feb 2022 00:36:21 -0500 Subject: [PATCH 54/79] 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 879fe177e356794eedf9f893fe0e865e49f36eb7 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Tue, 8 Feb 2022 00:36:37 -0500 Subject: [PATCH 55/79] 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 4d29d2132a060f7f91420d71eea4e80ab72e7727 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 9 Feb 2022 13:20:35 -0500 Subject: [PATCH 56/79] 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 d71d9fcc94831dc1aea8d2aa847feeadeed6b9c4 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 9 Feb 2022 13:20:46 -0500 Subject: [PATCH 57/79] 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 c955718b2a52fe88f0f3b27b00b8fcb74e8be0ca Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 9 Feb 2022 13:21:43 -0500 Subject: [PATCH 58/79] 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 c574699cc746d22d95223bfc85d2e0cb8d5843e8 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Wed, 9 Feb 2022 13:22:01 -0500 Subject: [PATCH 59/79] 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 a368ad06a9847e4cb04fc2d544ff49997db54e90 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 19 Feb 2022 13:52:24 -0500 Subject: [PATCH 60/79] 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 304184e9c70c1ed35eff2a7e522c3e71d9204d17 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Tue, 1 Mar 2022 16:38:02 -0500 Subject: [PATCH 61/79] 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 1b76b8050daddd7c26e8cb372b2ad710974a66be Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 24 Aug 2024 13:33:40 -0400 Subject: [PATCH 62/79] Fix formatting --- src/nested.test.ts | 338 +++++++++++++++++++++++---------------------- 1 file changed, 173 insertions(+), 165 deletions(-) diff --git a/src/nested.test.ts b/src/nested.test.ts index 572a7a028d..7f52bfdf94 100644 --- a/src/nested.test.ts +++ b/src/nested.test.ts @@ -15,7 +15,7 @@ import { renameQuestionById, changeQuestionTypeById, editOption, - duplicateQuestionInArray + duplicateQuestionInArray, } from "./nested"; import testQuestionData from "./data/questions.json"; import backupQuestionData from "./data/questions.json"; @@ -25,7 +25,7 @@ const { SIMPLE_QUESTIONS, TRIVIA_QUESTIONS, EMPTY_QUESTIONS, - SIMPLE_QUESTIONS_2 + SIMPLE_QUESTIONS_2, }: Record = // Typecast the test data that we imported to be a record matching // strings to the question list @@ -37,7 +37,7 @@ const { SIMPLE_QUESTIONS: BACKUP_SIMPLE_QUESTIONS, TRIVIA_QUESTIONS: BACKUP_TRIVIA_QUESTIONS, EMPTY_QUESTIONS: BACKUP_EMPTY_QUESTIONS, - SIMPLE_QUESTIONS_2: BACKUP_SIMPLE_QUESTIONS_2 + SIMPLE_QUESTIONS_2: BACKUP_SIMPLE_QUESTIONS_2, }: Record = backupQuestionData as Record< string, Question[] @@ -51,7 +51,7 @@ const NEW_BLANK_QUESTION = { options: [], expected: "", points: 1, - published: false + published: false, }; const NEW_TRIVIA_QUESTION = { @@ -66,7 +66,7 @@ const NEW_TRIVIA_QUESTION = { options: ["Black, like my soul", "Blue again, we're tricky.", "#FFD200"], expected: "#FFD200",*/ points: 1, - published: false + published: false, }; //////////////////////////////////////////// @@ -76,7 +76,7 @@ describe("Testing the Question[] functions", () => { ////////////////////////////////// // getPublishedQuestions - test("Testing the getPublishedQuestions function", () => { + test("(3 pts) Testing the getPublishedQuestions function", () => { expect(getPublishedQuestions(BLANK_QUESTIONS)).toEqual([]); expect(getPublishedQuestions(SIMPLE_QUESTIONS)).toEqual([ { @@ -87,7 +87,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "4", points: 1, - published: true + published: true, }, { id: 5, @@ -97,12 +97,12 @@ describe("Testing the Question[] functions", () => { options: ["red", "apple", "firetruck"], expected: "red", points: 1, - published: true - } + published: true, + }, ]); expect(getPublishedQuestions(TRIVIA_QUESTIONS)).toEqual([]); expect(getPublishedQuestions(SIMPLE_QUESTIONS_2)).toEqual( - BACKUP_SIMPLE_QUESTIONS_2 + BACKUP_SIMPLE_QUESTIONS_2, ); expect(getPublishedQuestions(EMPTY_QUESTIONS)).toEqual([ { @@ -113,7 +113,7 @@ describe("Testing the Question[] functions", () => { options: ["correct", "it is", "not"], expected: "correct", points: 5, - published: true + published: true, }, { id: 2, @@ -123,7 +123,7 @@ describe("Testing the Question[] functions", () => { options: ["this", "one", "is", "not", "empty", "either"], expected: "one", points: 5, - published: true + published: true, }, { id: 3, @@ -133,7 +133,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 5, - published: true + published: true, }, { id: 4, @@ -143,21 +143,21 @@ describe("Testing the Question[] functions", () => { options: [], expected: "Even this one is not empty", points: 5, - published: true - } + published: true, + }, ]); }); - test("Testing the getNonEmptyQuestions functions", () => { + test("(3 pts) Testing the getNonEmptyQuestions functions", () => { expect(getNonEmptyQuestions(BLANK_QUESTIONS)).toEqual([]); expect(getNonEmptyQuestions(SIMPLE_QUESTIONS)).toEqual( - BACKUP_SIMPLE_QUESTIONS + BACKUP_SIMPLE_QUESTIONS, ); expect(getNonEmptyQuestions(TRIVIA_QUESTIONS)).toEqual( - BACKUP_TRIVIA_QUESTIONS + BACKUP_TRIVIA_QUESTIONS, ); expect(getNonEmptyQuestions(SIMPLE_QUESTIONS_2)).toEqual( - BACKUP_SIMPLE_QUESTIONS_2 + BACKUP_SIMPLE_QUESTIONS_2, ); expect(getNonEmptyQuestions(EMPTY_QUESTIONS)).toEqual([ { @@ -168,7 +168,7 @@ describe("Testing the Question[] functions", () => { options: ["correct", "it is", "not"], expected: "correct", points: 5, - published: true + published: true, }, { id: 2, @@ -178,7 +178,7 @@ describe("Testing the Question[] functions", () => { options: ["this", "one", "is", "not", "empty", "either"], expected: "one", points: 5, - published: true + published: true, }, { id: 3, @@ -188,7 +188,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 5, - published: true + published: true, }, { id: 4, @@ -198,12 +198,12 @@ describe("Testing the Question[] functions", () => { options: [], expected: "Even this one is not empty", points: 5, - published: true - } + published: true, + }, ]); }); - test("Testing the findQuestion function", () => { + test("(3 pts) Testing the findQuestion function", () => { expect(findQuestion(BLANK_QUESTIONS, 1)).toEqual(BLANK_QUESTIONS[0]); expect(findQuestion(BLANK_QUESTIONS, 47)).toEqual(BLANK_QUESTIONS[1]); expect(findQuestion(BLANK_QUESTIONS, 2)).toEqual(BLANK_QUESTIONS[2]); @@ -214,12 +214,12 @@ describe("Testing the Question[] functions", () => { 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] + SIMPLE_QUESTIONS_2[0], ); expect(findQuestion([], 0)).toEqual(null); }); - test("Testing the removeQuestion", () => { + test("(3 pts) Testing the removeQuestion", () => { expect(removeQuestion(BLANK_QUESTIONS, 1)).toEqual([ { id: 47, @@ -229,7 +229,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 2, @@ -239,8 +239,8 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false - } + published: false, + }, ]); expect(removeQuestion(BLANK_QUESTIONS, 47)).toEqual([ { @@ -251,7 +251,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 2, @@ -261,8 +261,8 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false - } + published: false, + }, ]); expect(removeQuestion(BLANK_QUESTIONS, 2)).toEqual([ { @@ -273,7 +273,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 47, @@ -283,8 +283,8 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false - } + published: false, + }, ]); expect(removeQuestion(SIMPLE_QUESTIONS, 9)).toEqual([ { @@ -295,7 +295,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "4", points: 1, - published: true + published: true, }, { id: 2, @@ -305,7 +305,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "Z", points: 1, - published: false + published: false, }, { id: 5, @@ -315,8 +315,8 @@ describe("Testing the Question[] functions", () => { options: ["red", "apple", "firetruck"], expected: "red", points: 1, - published: true - } + published: true, + }, ]); expect(removeQuestion(SIMPLE_QUESTIONS, 5)).toEqual([ { @@ -327,7 +327,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "4", points: 1, - published: true + published: true, }, { id: 2, @@ -337,7 +337,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "Z", points: 1, - published: false + published: false, }, { id: 9, @@ -347,45 +347,45 @@ describe("Testing the Question[] functions", () => { options: ["square", "triangle", "circle"], expected: "circle", points: 2, - published: false - } + published: false, + }, ]); }); - test("Testing the getNames function", () => { + test("(3 pts) Testing the getNames function", () => { expect(getNames(BLANK_QUESTIONS)).toEqual([ "Question 1", "My New Question", - "Question 2" + "Question 2", ]); expect(getNames(SIMPLE_QUESTIONS)).toEqual([ "Addition", "Letters", "Colors", - "Shapes" + "Shapes", ]); expect(getNames(TRIVIA_QUESTIONS)).toEqual([ "Mascot", "Motto", - "Goats" + "Goats", ]); expect(getNames(SIMPLE_QUESTIONS_2)).toEqual([ "Students", "Importance", "Sentience", "Danger", - "Listening" + "Listening", ]); expect(getNames(EMPTY_QUESTIONS)).toEqual([ "Empty 1", "Empty 2", "Empty 3", "Empty 4", - "Empty 5 (Actual)" + "Empty 5 (Actual)", ]); }); - test("Testing the sumPoints function", () => { + test("(3 pts) Testing the sumPoints function", () => { expect(sumPoints(BLANK_QUESTIONS)).toEqual(3); expect(sumPoints(SIMPLE_QUESTIONS)).toEqual(5); expect(sumPoints(TRIVIA_QUESTIONS)).toEqual(20); @@ -393,7 +393,7 @@ describe("Testing the Question[] functions", () => { expect(sumPoints(SIMPLE_QUESTIONS_2)).toEqual(300); }); - test("Testing the sumPublishedPoints function", () => { + test("(3 pts) Testing the sumPublishedPoints function", () => { expect(sumPublishedPoints(BLANK_QUESTIONS)).toEqual(0); expect(sumPublishedPoints(SIMPLE_QUESTIONS)).toEqual(2); expect(sumPublishedPoints(TRIVIA_QUESTIONS)).toEqual(0); @@ -401,7 +401,7 @@ describe("Testing the Question[] functions", () => { expect(sumPublishedPoints(SIMPLE_QUESTIONS_2)).toEqual(300); }); - test("Testing the toCSV function", () => { + test("(3 pts) Testing the toCSV function", () => { expect(toCSV(BLANK_QUESTIONS)).toEqual(`id,name,options,points,published 1,Question 1,0,1,false 47,My New Question,0,1,false @@ -432,40 +432,40 @@ describe("Testing the Question[] functions", () => { 1937,Listening,0,100,true`); }); - test("Testing the makeAnswers function", () => { + test("(3 pts) Testing the makeAnswers function", () => { expect(makeAnswers(BLANK_QUESTIONS)).toEqual([ { questionId: 1, correct: false, text: "", submitted: false }, { questionId: 47, correct: false, text: "", submitted: false }, - { questionId: 2, correct: false, text: "", submitted: false } + { 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 } + { 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 } + { 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 } + { 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 } + { questionId: 5, correct: false, text: "", submitted: false }, ]); }); - test("Testing the publishAll function", () => { + test("(3 pts) Testing the publishAll function", () => { expect(publishAll(BLANK_QUESTIONS)).toEqual([ { id: 1, @@ -475,7 +475,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: true + published: true, }, { id: 47, @@ -485,7 +485,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: true + published: true, }, { id: 2, @@ -495,8 +495,8 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: true - } + published: true, + }, ]); expect(publishAll(SIMPLE_QUESTIONS)).toEqual([ { @@ -507,7 +507,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "4", points: 1, - published: true + published: true, }, { id: 2, @@ -517,7 +517,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "Z", points: 1, - published: true + published: true, }, { id: 5, @@ -527,7 +527,7 @@ describe("Testing the Question[] functions", () => { options: ["red", "apple", "firetruck"], expected: "red", points: 1, - published: true + published: true, }, { id: 9, @@ -537,8 +537,8 @@ describe("Testing the Question[] functions", () => { options: ["square", "triangle", "circle"], expected: "circle", points: 2, - published: true - } + published: true, + }, ]); expect(publishAll(TRIVIA_QUESTIONS)).toEqual([ { @@ -549,7 +549,7 @@ describe("Testing the Question[] functions", () => { options: ["Bluey", "YoUDee", "Charles the Wonder Dog"], expected: "YoUDee", points: 7, - published: true + published: true, }, { id: 2, @@ -559,11 +559,11 @@ describe("Testing the Question[] functions", () => { options: [ "Knowledge is the light of the mind", "Just U Do it", - "Nothing, what's the motto with you?" + "Nothing, what's the motto with you?", ], expected: "Knowledge is the light of the mind", points: 3, - published: true + published: true, }, { id: 3, @@ -573,12 +573,12 @@ describe("Testing the Question[] functions", () => { options: [ "Zero, why would there be goats on the green?", "18420", - "Two" + "Two", ], expected: "Two", points: 10, - published: true - } + published: true, + }, ]); expect(publishAll(EMPTY_QUESTIONS)).toEqual([ { @@ -589,7 +589,7 @@ describe("Testing the Question[] functions", () => { options: ["correct", "it is", "not"], expected: "correct", points: 5, - published: true + published: true, }, { id: 2, @@ -599,7 +599,7 @@ describe("Testing the Question[] functions", () => { options: ["this", "one", "is", "not", "empty", "either"], expected: "one", points: 5, - published: true + published: true, }, { id: 3, @@ -609,7 +609,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 5, - published: true + published: true, }, { id: 4, @@ -619,7 +619,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "Even this one is not empty", points: 5, - published: true + published: true, }, { id: 5, @@ -629,13 +629,13 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 5, - published: true - } + published: true, + }, ]); expect(publishAll(SIMPLE_QUESTIONS_2)).toEqual(SIMPLE_QUESTIONS_2); }); - test("Testing the sameType function", () => { + test("(3 pts) Testing the sameType function", () => { expect(sameType([])).toEqual(true); expect(sameType(BLANK_QUESTIONS)).toEqual(false); expect(sameType(SIMPLE_QUESTIONS)).toEqual(false); @@ -644,29 +644,29 @@ describe("Testing the Question[] functions", () => { expect(sameType(SIMPLE_QUESTIONS_2)).toEqual(true); }); - test("Testing the addNewQuestion function", () => { + test("(3 pts) Testing the addNewQuestion function", () => { expect( - addNewQuestion([], 142, "A new question", "short_answer_question") + addNewQuestion([], 142, "A new question", "short_answer_question"), ).toEqual([NEW_BLANK_QUESTION]); expect( addNewQuestion( BLANK_QUESTIONS, 142, "A new question", - "short_answer_question" - ) + "short_answer_question", + ), ).toEqual([...BLANK_QUESTIONS, NEW_BLANK_QUESTION]); expect( addNewQuestion( TRIVIA_QUESTIONS, 449, "Colors", - "multiple_choice_question" - ) + "multiple_choice_question", + ), ).toEqual([...TRIVIA_QUESTIONS, NEW_TRIVIA_QUESTION]); }); - test("Testing the renameQuestionById function", () => { + test("(3 pts) Testing the renameQuestionById function", () => { expect(renameQuestionById(BLANK_QUESTIONS, 1, "New Name")).toEqual([ { id: 1, @@ -676,7 +676,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 47, @@ -686,7 +686,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 2, @@ -696,8 +696,8 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false - } + published: false, + }, ]); expect(renameQuestionById(BLANK_QUESTIONS, 47, "Another Name")).toEqual( [ @@ -709,7 +709,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 47, @@ -719,7 +719,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 2, @@ -729,9 +729,9 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false - } - ] + published: false, + }, + ], ); expect(renameQuestionById(SIMPLE_QUESTIONS, 5, "Colours")).toEqual([ { @@ -742,7 +742,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "4", points: 1, - published: true + published: true, }, { id: 2, @@ -752,7 +752,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "Z", points: 1, - published: false + published: false, }, { id: 5, @@ -762,7 +762,7 @@ describe("Testing the Question[] functions", () => { options: ["red", "apple", "firetruck"], expected: "red", points: 1, - published: true + published: true, }, { id: 9, @@ -772,21 +772,21 @@ describe("Testing the Question[] functions", () => { options: ["square", "triangle", "circle"], expected: "circle", points: 2, - published: false - } + published: false, + }, ]); }); - test("Test the changeQuestionTypeById function", () => { + test("(3 pts) Test the changeQuestionTypeById function", () => { expect( changeQuestionTypeById( BLANK_QUESTIONS, 1, - "multiple_choice_question" - ) + "multiple_choice_question", + ), ).toEqual(BLANK_QUESTIONS); expect( - changeQuestionTypeById(BLANK_QUESTIONS, 1, "short_answer_question") + changeQuestionTypeById(BLANK_QUESTIONS, 1, "short_answer_question"), ).toEqual([ { id: 1, @@ -796,7 +796,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 47, @@ -806,7 +806,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 2, @@ -816,11 +816,15 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false - } + published: false, + }, ]); expect( - changeQuestionTypeById(BLANK_QUESTIONS, 47, "short_answer_question") + changeQuestionTypeById( + BLANK_QUESTIONS, + 47, + "short_answer_question", + ), ).toEqual([ { id: 1, @@ -830,7 +834,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 47, @@ -840,7 +844,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 2, @@ -850,11 +854,15 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false - } + published: false, + }, ]); expect( - changeQuestionTypeById(TRIVIA_QUESTIONS, 3, "short_answer_question") + changeQuestionTypeById( + TRIVIA_QUESTIONS, + 3, + "short_answer_question", + ), ).toEqual([ { id: 1, @@ -864,7 +872,7 @@ describe("Testing the Question[] functions", () => { options: ["Bluey", "YoUDee", "Charles the Wonder Dog"], expected: "YoUDee", points: 7, - published: false + published: false, }, { id: 2, @@ -874,11 +882,11 @@ describe("Testing the Question[] functions", () => { options: [ "Knowledge is the light of the mind", "Just U Do it", - "Nothing, what's the motto with you?" + "Nothing, what's the motto with you?", ], expected: "Knowledge is the light of the mind", points: 3, - published: false + published: false, }, { id: 3, @@ -888,12 +896,12 @@ describe("Testing the Question[] functions", () => { options: [], expected: "Two", points: 10, - published: false - } + published: false, + }, ]); }); - test("Testing the editOption function", () => { + test("(3 pts) Testing the editOption function", () => { expect(editOption(BLANK_QUESTIONS, 1, -1, "NEW OPTION")).toEqual([ { id: 1, @@ -903,7 +911,7 @@ describe("Testing the Question[] functions", () => { options: ["NEW OPTION"], expected: "", points: 1, - published: false + published: false, }, { id: 47, @@ -913,7 +921,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 2, @@ -923,8 +931,8 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false - } + published: false, + }, ]); expect(editOption(BLANK_QUESTIONS, 47, -1, "Another option")).toEqual([ { @@ -935,7 +943,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 47, @@ -945,7 +953,7 @@ describe("Testing the Question[] functions", () => { options: ["Another option"], expected: "", points: 1, - published: false + published: false, }, { id: 2, @@ -955,8 +963,8 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false - } + published: false, + }, ]); expect(editOption(SIMPLE_QUESTIONS, 5, -1, "newspaper")).toEqual([ { @@ -967,7 +975,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "4", points: 1, - published: true + published: true, }, { id: 2, @@ -977,7 +985,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "Z", points: 1, - published: false + published: false, }, { id: 5, @@ -987,7 +995,7 @@ describe("Testing the Question[] functions", () => { options: ["red", "apple", "firetruck", "newspaper"], expected: "red", points: 1, - published: true + published: true, }, { id: 9, @@ -997,8 +1005,8 @@ describe("Testing the Question[] functions", () => { options: ["square", "triangle", "circle"], expected: "circle", points: 2, - published: false - } + published: false, + }, ]); expect(editOption(SIMPLE_QUESTIONS, 5, 0, "newspaper")).toEqual([ { @@ -1009,7 +1017,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "4", points: 1, - published: true + published: true, }, { id: 2, @@ -1019,7 +1027,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "Z", points: 1, - published: false + published: false, }, { id: 5, @@ -1029,7 +1037,7 @@ describe("Testing the Question[] functions", () => { options: ["newspaper", "apple", "firetruck"], expected: "red", points: 1, - published: true + published: true, }, { id: 9, @@ -1039,8 +1047,8 @@ describe("Testing the Question[] functions", () => { options: ["square", "triangle", "circle"], expected: "circle", points: 2, - published: false - } + published: false, + }, ]); expect(editOption(SIMPLE_QUESTIONS, 5, 2, "newspaper")).toEqual([ @@ -1052,7 +1060,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "4", points: 1, - published: true + published: true, }, { id: 2, @@ -1062,7 +1070,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "Z", points: 1, - published: false + published: false, }, { id: 5, @@ -1072,7 +1080,7 @@ describe("Testing the Question[] functions", () => { options: ["red", "apple", "newspaper"], expected: "red", points: 1, - published: true + published: true, }, { id: 9, @@ -1082,12 +1090,12 @@ describe("Testing the Question[] functions", () => { options: ["square", "triangle", "circle"], expected: "circle", points: 2, - published: false - } + published: false, + }, ]); }); - test("Testing the duplicateQuestionInArray function", () => { + test("(3 pts) Testing the duplicateQuestionInArray function", () => { expect(duplicateQuestionInArray(BLANK_QUESTIONS, 1, 27)).toEqual([ { id: 1, @@ -1097,7 +1105,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 27, @@ -1107,7 +1115,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 47, @@ -1117,7 +1125,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 2, @@ -1127,8 +1135,8 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false - } + published: false, + }, ]); expect(duplicateQuestionInArray(BLANK_QUESTIONS, 47, 19)).toEqual([ { @@ -1139,7 +1147,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 47, @@ -1149,7 +1157,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 19, @@ -1159,7 +1167,7 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false + published: false, }, { id: 2, @@ -1169,8 +1177,8 @@ describe("Testing the Question[] functions", () => { options: [], expected: "", points: 1, - published: false - } + published: false, + }, ]); expect(duplicateQuestionInArray(TRIVIA_QUESTIONS, 3, 111)).toEqual([ { @@ -1181,7 +1189,7 @@ describe("Testing the Question[] functions", () => { options: ["Bluey", "YoUDee", "Charles the Wonder Dog"], expected: "YoUDee", points: 7, - published: false + published: false, }, { id: 2, @@ -1191,11 +1199,11 @@ describe("Testing the Question[] functions", () => { options: [ "Knowledge is the light of the mind", "Just U Do it", - "Nothing, what's the motto with you?" + "Nothing, what's the motto with you?", ], expected: "Knowledge is the light of the mind", points: 3, - published: false + published: false, }, { id: 3, @@ -1205,11 +1213,11 @@ describe("Testing the Question[] functions", () => { options: [ "Zero, why would there be goats on the green?", "18420", - "Two" + "Two", ], expected: "Two", points: 10, - published: false + published: false, }, { id: 111, @@ -1219,12 +1227,12 @@ describe("Testing the Question[] functions", () => { options: [ "Zero, why would there be goats on the green?", "18420", - "Two" + "Two", ], expected: "Two", points: 10, - published: false - } + published: false, + }, ]); }); From 23314f383f4ac6257b7c904bfff21496bf743a68 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 24 Aug 2024 13:41:03 -0400 Subject: [PATCH 63/79] update point values for tests --- src/components/ChangeType.test.tsx | 10 +++++----- src/components/Counter.test.tsx | 10 +++++----- src/components/CycleHoliday.test.tsx | 8 ++++---- src/components/RevealAnswer.test.tsx | 8 ++++---- src/components/StartAttempt.test.tsx | 26 +++++++++++++------------- src/components/TwoDice.test.tsx | 16 ++++++++-------- 6 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/components/ChangeType.test.tsx b/src/components/ChangeType.test.tsx index 10b4f0dc3c..ef7ea4b66e 100644 --- a/src/components/ChangeType.test.tsx +++ b/src/components/ChangeType.test.tsx @@ -6,25 +6,25 @@ describe("ChangeType Component tests", () => { beforeEach(() => { render(); }); - test("The initial type is Short Answer", () => { + test("(1 pts) The initial type is Short Answer", () => { // We use `getByText` because the text MUST be there const typeText = screen.getByText(/Short Answer/i); expect(typeText).toBeInTheDocument(); }); - test("The initial type is not Multiple Choice", () => { + test("(1 pts) The initial type is not Multiple Choice", () => { // We use `queryByText` because the text might not be there const typeText = screen.queryByText(/Multiple Choice/i); expect(typeText).toBeNull(); }); - test("There is a button labeled Change Type", () => { + test("(1 pts) There is a button labeled Change Type", () => { const changeTypeButton = screen.getByRole("button", { name: /Change Type/i }); expect(changeTypeButton).toBeInTheDocument(); }); - test("Clicking the button changes the type.", () => { + test("(1 pts) Clicking the button changes the type.", () => { const changeTypeButton = screen.getByRole("button", { name: /Change Type/i }); @@ -37,7 +37,7 @@ describe("ChangeType Component tests", () => { expect(typeTextSA).toBeNull(); }); - test("Clicking the button twice keeps the type the same.", () => { + test("(1 pts) Clicking the button twice keeps the type the same.", () => { const changeTypeButton = screen.getByRole("button", { name: /Change Type/i }); diff --git a/src/components/Counter.test.tsx b/src/components/Counter.test.tsx index 7a37c46e38..1ee2d1ff32 100644 --- a/src/components/Counter.test.tsx +++ b/src/components/Counter.test.tsx @@ -6,30 +6,30 @@ describe("Counter Component tests", () => { beforeEach(() => { render(); }); - test("The initial value is 0", () => { + test("(1 pts) The initial value is 0", () => { // We use `getByText` because the text MUST be there const valueText = screen.getByText(/0/i); expect(valueText).toBeInTheDocument(); }); - test("The initial value is not 1", () => { + test("(1 pts) The initial value is not 1", () => { // We use `queryByText` because the text might not be there const valueText = screen.queryByText(/1/i); expect(valueText).toBeNull(); }); - test("There is a button named Add One", () => { + test("(1 pts) There is a button named Add One", () => { const addOneButton = screen.getByRole("button", { name: /Add One/i }); expect(addOneButton).toBeInTheDocument(); }); - test("Clicking the button once adds one", () => { + test("(1 pts) Clicking the button once adds one", () => { const addOneButton = screen.getByRole("button", { name: /Add One/i }); addOneButton.click(); const valueText = screen.getByText(/1/i); expect(valueText).toBeInTheDocument(); }); - test("Clicking the button twice adds two", () => { + test("(1 pts) Clicking the button twice adds two", () => { const addOneButton = screen.getByRole("button", { name: /Add One/i }); addOneButton.click(); addOneButton.click(); diff --git a/src/components/CycleHoliday.test.tsx b/src/components/CycleHoliday.test.tsx index 145e2cb3c8..a20389d03e 100644 --- a/src/components/CycleHoliday.test.tsx +++ b/src/components/CycleHoliday.test.tsx @@ -7,12 +7,12 @@ describe("CycleHoliday Component tests", () => { render(); }); - test("An initial holiday is displayed", () => { + test("(1 pts) An initial holiday is displayed", () => { const initialHoliday = screen.getByText(/Holiday: (.*)/i); expect(initialHoliday).toBeInTheDocument(); }); - test("There are two buttons", () => { + test("(1 pts) There are two buttons", () => { const alphabetButton = screen.getByRole("button", { name: /Alphabet/i }); @@ -23,7 +23,7 @@ describe("CycleHoliday Component tests", () => { expect(yearButton).toBeInTheDocument(); }); - test("Can cycle through 5 distinct holidays alphabetically", () => { + test("(1 pts) Can cycle through 5 distinct holidays alphabetically", () => { const alphabetButton = screen.getByRole("button", { name: /Alphabet/i }); @@ -38,7 +38,7 @@ describe("CycleHoliday Component tests", () => { expect(states[0]).toEqual(states[5]); }); - test("Can cycle through 5 distinct holidays by year", () => { + test("(1 pts) Can cycle through 5 distinct holidays by year", () => { const yearButton = screen.getByRole("button", { name: /Year/i }); diff --git a/src/components/RevealAnswer.test.tsx b/src/components/RevealAnswer.test.tsx index aa7996e964..438f4d5c28 100644 --- a/src/components/RevealAnswer.test.tsx +++ b/src/components/RevealAnswer.test.tsx @@ -6,17 +6,17 @@ describe("RevealAnswer Component tests", () => { beforeEach(() => { render(); }); - test("The answer '42' is not visible initially", () => { + test("(1 pts) The answer '42' is not visible initially", () => { const answerText = screen.queryByText(/42/); expect(answerText).toBeNull(); }); - test("There is a Reveal Answer button", () => { + test("(1 pts) There is a Reveal Answer button", () => { const revealButton = screen.getByRole("button", { name: /Reveal Answer/i }); expect(revealButton).toBeInTheDocument(); }); - test("Clicking Reveal Answer button reveals the '42'", () => { + test("(1 pts) Clicking Reveal Answer button reveals the '42'", () => { const revealButton = screen.getByRole("button", { name: /Reveal Answer/i }); @@ -24,7 +24,7 @@ describe("RevealAnswer Component tests", () => { const answerText = screen.getByText(/42/); expect(answerText).toBeInTheDocument(); }); - test("Clicking Reveal Answer button twice hides the '42'", () => { + test("(1 pts) Clicking Reveal Answer button twice hides the '42'", () => { const revealButton = screen.getByRole("button", { name: /Reveal Answer/i }); diff --git a/src/components/StartAttempt.test.tsx b/src/components/StartAttempt.test.tsx index 3d41c953cf..8e5b9667cb 100644 --- a/src/components/StartAttempt.test.tsx +++ b/src/components/StartAttempt.test.tsx @@ -27,36 +27,36 @@ describe("StartAttempt Component tests", () => { beforeEach(() => { render(); }); - test("The Number of attempts is displayed initially, without other numbers", () => { + test("(1 pts) The Number of attempts is displayed initially, without other numbers", () => { const attemptNumber = screen.getByText(/(\d+)/); expect(attemptNumber).toBeInTheDocument(); }); - test("The Number of attempts is more than 0", () => { + test("(1 pts) The Number of attempts is more than 0", () => { const attemptNumber = extractDigits(screen.getByText(/(\d+)/)); expect(attemptNumber).toBeGreaterThan(0); }); - test("The Number of attempts is less than 10", () => { + test("(1 pts) The Number of attempts is less than 10", () => { const attemptNumber = extractDigits(screen.getByText(/(\d+)/)); expect(attemptNumber).toBeLessThan(10); }); - test("There is an initially enabled Start Quiz button", () => { + test("(1 pts) There is an initially enabled Start Quiz button", () => { const startButton = screen.getByRole("button", { name: /Start Quiz/i }); expect(startButton).toBeInTheDocument(); expect(startButton).toBeEnabled(); }); - test("There is an initially disabled Stop Quiz button", () => { + test("(1 pts) There is an initially disabled Stop Quiz button", () => { const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); expect(stopButton).toBeInTheDocument(); expect(stopButton).toBeDisabled(); }); - test("There is an initially enabled Mulligan button", () => { + test("(1 pts) There is an initially enabled Mulligan button", () => { const mulliganButton = screen.getByRole("button", { name: /Mulligan/i }); expect(mulliganButton).toBeInTheDocument(); expect(mulliganButton).toBeEnabled(); }); - test("Clicking Mulligan increases attempts", () => { + test("(1 pts) Clicking Mulligan increases attempts", () => { const attemptNumber: number = extractDigits(screen.getByText(/(\d+)/)) || 0; const mulliganButton = screen.getByRole("button", { @@ -66,7 +66,7 @@ describe("StartAttempt Component tests", () => { const attemptNumberLater = extractDigits(screen.getByText(/(\d+)/)); expect(attemptNumber + 1).toEqual(attemptNumberLater); }); - test("Clicking Mulligan twice increases attempts by two", () => { + test("(1 pts) Clicking Mulligan twice increases attempts by two", () => { const attemptNumber: number = extractDigits(screen.getByText(/(\d+)/)) || 0; const mulliganButton = screen.getByRole("button", { @@ -77,7 +77,7 @@ describe("StartAttempt Component tests", () => { const attemptNumberLater = extractDigits(screen.getByText(/(\d+)/)); expect(attemptNumber + 2).toEqual(attemptNumberLater); }); - test("Clicking Start Quiz decreases attempts", () => { + test("(1 pts) Clicking Start Quiz decreases attempts", () => { const attemptNumber: number = extractDigits(screen.getByText(/(\d+)/)) || 0; const startButton = screen.getByRole("button", { @@ -88,7 +88,7 @@ describe("StartAttempt Component tests", () => { extractDigits(screen.getByText(/(\d+)/)) || 0; expect(attemptNumber - 1).toEqual(attemptNumberLater); }); - test("Clicking Start Quiz changes enabled buttons", () => { + test("(1 pts) Clicking Start Quiz changes enabled buttons", () => { // Given the buttons... const startButton = screen.getByRole("button", { name: /Start Quiz/i @@ -104,7 +104,7 @@ describe("StartAttempt Component tests", () => { expect(stopButton).toBeEnabled(); expect(mulliganButton).toBeDisabled(); }); - test("Clicking Start and Stop Quiz changes enabled buttons", () => { + test("(1 pts) Clicking Start and Stop Quiz changes enabled buttons", () => { // Given the buttons and initial attempt number... const startButton = screen.getByRole("button", { name: /Start Quiz/i @@ -121,7 +121,7 @@ describe("StartAttempt Component tests", () => { expect(stopButton).toBeDisabled(); expect(mulliganButton).toBeEnabled(); }); - test("Clicking Start, Stop, Mulligan sets attempts to original", () => { + test("(1 pts) Clicking Start, Stop, Mulligan sets attempts to original", () => { // Given the buttons and initial attempt number... const startButton = screen.getByRole("button", { name: /Start Quiz/i @@ -146,7 +146,7 @@ describe("StartAttempt Component tests", () => { extractDigits(screen.getByText(/(\d+)/)) || 0; expect(attemptNumber).toEqual(attemptNumberLatest); }); - test("Cannot click start quiz when out of attempts", () => { + test("(1 pts) Cannot click start quiz when out of attempts", () => { // Given the buttons and initial attempt number... const startButton = screen.getByRole("button", { name: /Start Quiz/i diff --git a/src/components/TwoDice.test.tsx b/src/components/TwoDice.test.tsx index 7996051096..d1e5f812dd 100644 --- a/src/components/TwoDice.test.tsx +++ b/src/components/TwoDice.test.tsx @@ -22,13 +22,13 @@ describe("TwoDice Component tests", () => { beforeEach(() => { render(); }); - test("There is a `left-die` and `right-die` testid", () => { + test("(1 pts) There is a `left-die` and `right-die` testid", () => { const leftDie = screen.getByTestId("left-die"); const rightDie = screen.getByTestId("right-die"); expect(leftDie).toBeInTheDocument(); expect(rightDie).toBeInTheDocument(); }); - test("The `left-die` and `right-die` are two different numbers", () => { + test("(1 pts) The `left-die` and `right-die` are two different numbers", () => { const leftDie = screen.getByTestId("left-die"); const rightDie = screen.getByTestId("right-die"); const leftNumber = extractDigits(leftDie); @@ -39,13 +39,13 @@ describe("TwoDice Component tests", () => { // Then they are two different numbers expect(leftNumber).not.toEqual(rightNumber); }); - test("There are two buttons present", () => { + test("(1 pts) There are two buttons present", () => { const leftButton = screen.getByRole("button", { name: /Roll Left/i }); const rightButton = screen.getByRole("button", { name: /Roll Right/i }); expect(leftButton).toBeInTheDocument(); expect(rightButton).toBeInTheDocument(); }); - test("Clicking left button changes first number", () => { + test("(1 pts) Clicking left button changes first number", () => { const leftButton = screen.getByRole("button", { name: /Roll Left/i }); leftButton.click(); leftButton.click(); @@ -57,7 +57,7 @@ describe("TwoDice Component tests", () => { expect(leftNumber).toEqual(5); }); // Clicking right button changes second number - test("Clicking right button changes second number", () => { + test("(1 pts) Clicking right button changes second number", () => { const rightButton = screen.getByRole("button", { name: /Roll Right/i }); rightButton.click(); rightButton.click(); @@ -69,7 +69,7 @@ describe("TwoDice Component tests", () => { expect(rightNumber).toEqual(5); }); // Rolling two different numbers does not win or lose the game - test("Rolling two different numbers does not win or lose the game", () => { + test("(1 pts) Rolling two different numbers does not win or lose the game", () => { // Given const leftButton = screen.getByRole("button", { name: /Roll Left/i }); const rightButton = screen.getByRole("button", { name: /Roll Right/i }); @@ -90,7 +90,7 @@ describe("TwoDice Component tests", () => { const loseText = screen.queryByText(/Lose/i); expect(loseText).toBeNull(); }); - test("Getting snake eyes loses the game", () => { + test("(1 pts) Getting snake eyes loses the game", () => { // Given const leftButton = screen.getByRole("button", { name: /Roll Left/i }); const rightButton = screen.getByRole("button", { name: /Roll Right/i }); @@ -114,7 +114,7 @@ describe("TwoDice Component tests", () => { const loseText = screen.getByText(/Lose/i); expect(loseText).toBeInTheDocument(); }); - test("Getting matching numbers wins the game", () => { + test("(1 pts) Getting matching numbers wins the game", () => { // Given const leftButton = screen.getByRole("button", { name: /Roll Left/i }); const rightButton = screen.getByRole("button", { name: /Roll Right/i }); From 82faaccfdddde466de5a4d0080af257364271684 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 24 Aug 2024 13:45:32 -0400 Subject: [PATCH 64/79] Fix react return value --- src/components/ChangeType.tsx | 2 +- src/components/Counter.tsx | 2 +- src/components/CycleHoliday.tsx | 2 +- src/components/RevealAnswer.tsx | 2 +- src/components/StartAttempt.tsx | 2 +- src/components/TwoDice.tsx | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/ChangeType.tsx b/src/components/ChangeType.tsx index 5608076f64..6e8f878020 100644 --- a/src/components/ChangeType.tsx +++ b/src/components/ChangeType.tsx @@ -2,6 +2,6 @@ import React, { useState } from "react"; import { Button } from "react-bootstrap"; import { QuestionType } from "../interfaces/question"; -export function ChangeType(): JSX.Element { +export function ChangeType(): React.JSX.Element { return
      Change Type
      ; } diff --git a/src/components/Counter.tsx b/src/components/Counter.tsx index 1987698ed1..2a546c1747 100644 --- a/src/components/Counter.tsx +++ b/src/components/Counter.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { Button } from "react-bootstrap"; -export function Counter(): JSX.Element { +export function Counter(): React.JSX.Element { const [value, setValue] = useState(0); return ( diff --git a/src/components/CycleHoliday.tsx b/src/components/CycleHoliday.tsx index 7c671f889f..47e6d76444 100644 --- a/src/components/CycleHoliday.tsx +++ b/src/components/CycleHoliday.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { Button } from "react-bootstrap"; -export function CycleHoliday(): JSX.Element { +export function CycleHoliday(): React.JSX.Element { return
      Cycle Holiday
      ; } diff --git a/src/components/RevealAnswer.tsx b/src/components/RevealAnswer.tsx index 07db6f62d2..a48c0a0961 100644 --- a/src/components/RevealAnswer.tsx +++ b/src/components/RevealAnswer.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { Button } from "react-bootstrap"; -export function RevealAnswer(): JSX.Element { +export function RevealAnswer(): React.JSX.Element { return
      Reveal Answer
      ; } diff --git a/src/components/StartAttempt.tsx b/src/components/StartAttempt.tsx index 0c0a85fdb6..dec4ae7dcd 100644 --- a/src/components/StartAttempt.tsx +++ b/src/components/StartAttempt.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { Button } from "react-bootstrap"; -export function StartAttempt(): JSX.Element { +export function StartAttempt(): React.JSX.Element { return
      Start Attempt
      ; } diff --git a/src/components/TwoDice.tsx b/src/components/TwoDice.tsx index 372502fe64..a257594d35 100644 --- a/src/components/TwoDice.tsx +++ b/src/components/TwoDice.tsx @@ -11,6 +11,6 @@ export function d6(): number { return 1 + Math.floor(Math.random() * 6); } -export function TwoDice(): JSX.Element { +export function TwoDice(): React.JSX.Element { return
      Two Dice
      ; } From cc7d4db27c04fce10f1974a3f179adaaffc739f2 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 24 Aug 2024 14:19:46 -0400 Subject: [PATCH 65/79] Update react tests to use async --- src/components/ChangeType.test.tsx | 24 ++++--- src/components/Counter.test.tsx | 18 ++++-- src/components/CycleHoliday.test.tsx | 22 ++++--- src/components/RevealAnswer.test.tsx | 24 ++++--- src/components/StartAttempt.test.tsx | 97 ++++++++++++++++++---------- src/components/TwoDice.test.tsx | 80 ++++++++++++++++------- 6 files changed, 175 insertions(+), 90 deletions(-) diff --git a/src/components/ChangeType.test.tsx b/src/components/ChangeType.test.tsx index ef7ea4b66e..0a126a9111 100644 --- a/src/components/ChangeType.test.tsx +++ b/src/components/ChangeType.test.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { act } from "react"; import { render, screen } from "@testing-library/react"; import { ChangeType } from "./ChangeType"; @@ -19,16 +19,18 @@ describe("ChangeType Component tests", () => { test("(1 pts) There is a button labeled Change Type", () => { const changeTypeButton = screen.getByRole("button", { - name: /Change Type/i + name: /Change Type/i, }); expect(changeTypeButton).toBeInTheDocument(); }); - test("(1 pts) Clicking the button changes the type.", () => { + test("(1 pts) Clicking the button changes the type.", async () => { const changeTypeButton = screen.getByRole("button", { - name: /Change Type/i + name: /Change Type/i, + }); + await act(async () => { + changeTypeButton.click(); }); - changeTypeButton.click(); // Should be Multiple Choice const typeTextMC = screen.getByText(/Multiple Choice/i); expect(typeTextMC).toBeInTheDocument(); @@ -37,12 +39,16 @@ describe("ChangeType Component tests", () => { expect(typeTextSA).toBeNull(); }); - test("(1 pts) Clicking the button twice keeps the type the same.", () => { + test("(1 pts) Clicking the button twice keeps the type the same.", async () => { const changeTypeButton = screen.getByRole("button", { - name: /Change Type/i + name: /Change Type/i, + }); + await act(async () => { + changeTypeButton.click(); + }); + await act(async () => { + changeTypeButton.click(); }); - changeTypeButton.click(); - changeTypeButton.click(); // Should be Short Answer const typeTextSA = screen.getByText(/Short Answer/i); expect(typeTextSA).toBeInTheDocument(); diff --git a/src/components/Counter.test.tsx b/src/components/Counter.test.tsx index 1ee2d1ff32..d08773f5c6 100644 --- a/src/components/Counter.test.tsx +++ b/src/components/Counter.test.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { act } from "react"; import { render, screen } from "@testing-library/react"; import { Counter } from "./Counter"; @@ -22,17 +22,23 @@ describe("Counter Component tests", () => { expect(addOneButton).toBeInTheDocument(); }); - test("(1 pts) Clicking the button once adds one", () => { + test("(1 pts) Clicking the button once adds one", async () => { const addOneButton = screen.getByRole("button", { name: /Add One/i }); - addOneButton.click(); + await act(async () => { + addOneButton.click(); + }); const valueText = screen.getByText(/1/i); expect(valueText).toBeInTheDocument(); }); - test("(1 pts) Clicking the button twice adds two", () => { + test("(1 pts) Clicking the button twice adds two", async () => { const addOneButton = screen.getByRole("button", { name: /Add One/i }); - addOneButton.click(); - addOneButton.click(); + await act(async () => { + addOneButton.click(); + }); + await act(async () => { + addOneButton.click(); + }); const valueText = screen.getByText(/2/i); expect(valueText).toBeInTheDocument(); }); diff --git a/src/components/CycleHoliday.test.tsx b/src/components/CycleHoliday.test.tsx index a20389d03e..4c1422f3df 100644 --- a/src/components/CycleHoliday.test.tsx +++ b/src/components/CycleHoliday.test.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { act } from "react"; import { render, screen } from "@testing-library/react"; import { CycleHoliday } from "./CycleHoliday"; @@ -14,39 +14,43 @@ describe("CycleHoliday Component tests", () => { test("(1 pts) There are two buttons", () => { const alphabetButton = screen.getByRole("button", { - name: /Alphabet/i + name: /Alphabet/i, }); const yearButton = screen.getByRole("button", { - name: /Year/i + name: /Year/i, }); expect(alphabetButton).toBeInTheDocument(); expect(yearButton).toBeInTheDocument(); }); - test("(1 pts) Can cycle through 5 distinct holidays alphabetically", () => { + test("(1 pts) Can cycle through 5 distinct holidays alphabetically", async () => { const alphabetButton = screen.getByRole("button", { - name: /Alphabet/i + name: /Alphabet/i, }); const initialHoliday = screen.getByText(/Holiday ?[:)-](.*)/i); const states: string[] = []; for (let i = 0; i < 6; i++) { states.push(initialHoliday.textContent || ""); - alphabetButton.click(); + await act(async () => { + alphabetButton.click(); + }); } const uniqueStates = states.filter((x, y) => states.indexOf(x) == y); expect(uniqueStates).toHaveLength(5); expect(states[0]).toEqual(states[5]); }); - test("(1 pts) Can cycle through 5 distinct holidays by year", () => { + test("(1 pts) Can cycle through 5 distinct holidays by year", async () => { const yearButton = screen.getByRole("button", { - name: /Year/i + name: /Year/i, }); const initialHoliday = screen.getByText(/Holiday ?[:)-](.*)/i); const states: string[] = []; for (let i = 0; i < 6; i++) { states.push(initialHoliday.textContent || ""); - yearButton.click(); + await act(async () => { + yearButton.click(); + }); } const uniqueStates = states.filter((x, y) => states.indexOf(x) == y); expect(uniqueStates).toHaveLength(5); diff --git a/src/components/RevealAnswer.test.tsx b/src/components/RevealAnswer.test.tsx index 438f4d5c28..f5ad250bf9 100644 --- a/src/components/RevealAnswer.test.tsx +++ b/src/components/RevealAnswer.test.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { act } from "react"; import { render, screen } from "@testing-library/react"; import { RevealAnswer } from "./RevealAnswer"; @@ -12,24 +12,30 @@ describe("RevealAnswer Component tests", () => { }); test("(1 pts) There is a Reveal Answer button", () => { const revealButton = screen.getByRole("button", { - name: /Reveal Answer/i + name: /Reveal Answer/i, }); expect(revealButton).toBeInTheDocument(); }); - test("(1 pts) Clicking Reveal Answer button reveals the '42'", () => { + test("(1 pts) Clicking Reveal Answer button reveals the '42'", async () => { const revealButton = screen.getByRole("button", { - name: /Reveal Answer/i + name: /Reveal Answer/i, + }); + await act(async () => { + revealButton.click(); }); - revealButton.click(); const answerText = screen.getByText(/42/); expect(answerText).toBeInTheDocument(); }); - test("(1 pts) Clicking Reveal Answer button twice hides the '42'", () => { + test("(1 pts) Clicking Reveal Answer button twice hides the '42'", async () => { const revealButton = screen.getByRole("button", { - name: /Reveal Answer/i + name: /Reveal Answer/i, + }); + await act(async () => { + revealButton.click(); + }); + await act(async () => { + revealButton.click(); }); - revealButton.click(); - revealButton.click(); const answerText = screen.queryByText(/42/); expect(answerText).toBeNull(); }); diff --git a/src/components/StartAttempt.test.tsx b/src/components/StartAttempt.test.tsx index 8e5b9667cb..74397290bb 100644 --- a/src/components/StartAttempt.test.tsx +++ b/src/components/StartAttempt.test.tsx @@ -1,5 +1,5 @@ -import React from "react"; -import { render, screen } from "@testing-library/react"; +import React, { act } from "react"; +import { render, screen, cleanup } from "@testing-library/react"; import { StartAttempt } from "./StartAttempt"; /*** @@ -27,6 +27,9 @@ describe("StartAttempt Component tests", () => { beforeEach(() => { render(); }); + afterEach(() => { + cleanup(); + }); test("(1 pts) The Number of attempts is displayed initially, without other numbers", () => { const attemptNumber = screen.getByText(/(\d+)/); expect(attemptNumber).toBeInTheDocument(); @@ -51,109 +54,129 @@ describe("StartAttempt Component tests", () => { }); test("(1 pts) There is an initially enabled Mulligan button", () => { const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i + name: /Mulligan/i, }); expect(mulliganButton).toBeInTheDocument(); expect(mulliganButton).toBeEnabled(); }); - test("(1 pts) Clicking Mulligan increases attempts", () => { + test("(1 pts) Clicking Mulligan increases attempts", async () => { const attemptNumber: number = extractDigits(screen.getByText(/(\d+)/)) || 0; const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i + name: /Mulligan/i, + }); + await act(async () => { + mulliganButton.click(); }); - mulliganButton.click(); const attemptNumberLater = extractDigits(screen.getByText(/(\d+)/)); expect(attemptNumber + 1).toEqual(attemptNumberLater); }); - test("(1 pts) Clicking Mulligan twice increases attempts by two", () => { + test("(1 pts) Clicking Mulligan twice increases attempts by two", async () => { const attemptNumber: number = extractDigits(screen.getByText(/(\d+)/)) || 0; const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i + name: /Mulligan/i, + }); + await act(async () => { + mulliganButton.click(); + }); + await act(async () => { + mulliganButton.click(); }); - mulliganButton.click(); - mulliganButton.click(); const attemptNumberLater = extractDigits(screen.getByText(/(\d+)/)); expect(attemptNumber + 2).toEqual(attemptNumberLater); }); - test("(1 pts) Clicking Start Quiz decreases attempts", () => { + test("(1 pts) Clicking Start Quiz decreases attempts", async () => { const attemptNumber: number = extractDigits(screen.getByText(/(\d+)/)) || 0; const startButton = screen.getByRole("button", { - name: /Start Quiz/i + name: /Start Quiz/i, + }); + await act(async () => { + startButton.click(); }); - startButton.click(); const attemptNumberLater = extractDigits(screen.getByText(/(\d+)/)) || 0; expect(attemptNumber - 1).toEqual(attemptNumberLater); }); - test("(1 pts) Clicking Start Quiz changes enabled buttons", () => { + test("(1 pts) Clicking Start Quiz changes enabled buttons", async () => { // Given the buttons... const startButton = screen.getByRole("button", { - name: /Start Quiz/i + name: /Start Quiz/i, }); const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i + name: /Mulligan/i, }); // When the start button is clicked - startButton.click(); + await act(async () => { + startButton.click(); + }); // Then the start is disabled, stop is enabled, and mulligan is disabled expect(startButton).toBeDisabled(); expect(stopButton).toBeEnabled(); expect(mulliganButton).toBeDisabled(); }); - test("(1 pts) Clicking Start and Stop Quiz changes enabled buttons", () => { + test("(1 pts) Clicking Start and Stop Quiz changes enabled buttons", async () => { // Given the buttons and initial attempt number... const startButton = screen.getByRole("button", { - name: /Start Quiz/i + name: /Start Quiz/i, }); const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i + name: /Mulligan/i, }); // When we click the start button and then the stop button - startButton.click(); - stopButton.click(); + await act(async () => { + startButton.click(); + }); + await act(async () => { + stopButton.click(); + }); // Then the start is enabled, stop is disabled, and mulligan is enabled expect(startButton).toBeEnabled(); expect(stopButton).toBeDisabled(); expect(mulliganButton).toBeEnabled(); }); - test("(1 pts) Clicking Start, Stop, Mulligan sets attempts to original", () => { + test("(1 pts) Clicking Start, Stop, Mulligan sets attempts to original", async () => { // Given the buttons and initial attempt number... const startButton = screen.getByRole("button", { - name: /Start Quiz/i + name: /Start Quiz/i, }); const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i + name: /Mulligan/i, }); const attemptNumber: number = extractDigits(screen.getByText(/(\d+)/)) || 0; // When we click the start button and then the stop button - startButton.click(); - stopButton.click(); + await act(async () => { + startButton.click(); + }); + await act(async () => { + stopButton.click(); + }); // Then the attempt is decreased const attemptNumberLater: number = extractDigits(screen.getByText(/(\d+)/)) || 0; expect(attemptNumber - 1).toEqual(attemptNumberLater); // And when we click the mulligan button - mulliganButton.click(); + await act(async () => { + mulliganButton.click(); + }); // Then the attempt is increased back to starting value const attemptNumberLatest: number = extractDigits(screen.getByText(/(\d+)/)) || 0; expect(attemptNumber).toEqual(attemptNumberLatest); }); - test("(1 pts) Cannot click start quiz when out of attempts", () => { + test("(1 pts) Cannot click start quiz when out of attempts", async () => { // Given the buttons and initial attempt number... const startButton = screen.getByRole("button", { - name: /Start Quiz/i + name: /Start Quiz/i, }); const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i + name: /Mulligan/i, }); let attemptNumber = extractDigits(screen.getByText(/(\d+)/)) || 0; const initialAttempt = attemptNumber; @@ -166,8 +189,12 @@ describe("StartAttempt Component tests", () => { expect(stopButton).toBeDisabled(); expect(mulliganButton).toBeEnabled(); // And when we Start and then immediately stop the quiz... - startButton.click(); - stopButton.click(); + await act(async () => { + startButton.click(); + }); + await act(async () => { + stopButton.click(); + }); // Then the number is going down, and doesn't go past 0 somehow attemptNumber = extractDigits(screen.getByText(/(\d+)/)) || 0; expect(attemptNumber).toBeGreaterThanOrEqual(0); @@ -183,7 +210,9 @@ describe("StartAttempt Component tests", () => { expect(stopButton).toBeDisabled(); expect(mulliganButton).toBeEnabled(); // And when we click the mulligan button - mulliganButton.click(); + await act(async () => { + mulliganButton.click(); + }); // Then the attempt is increased back to 1 const attemptNumberLatest: number = extractDigits(screen.getByText(/(\d+)/)) || 0; diff --git a/src/components/TwoDice.test.tsx b/src/components/TwoDice.test.tsx index d1e5f812dd..e5f9966deb 100644 --- a/src/components/TwoDice.test.tsx +++ b/src/components/TwoDice.test.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { act } from "react"; import { render, screen } from "@testing-library/react"; import { TwoDice } from "./TwoDice"; import { extractDigits } from "./StartAttempt.test"; @@ -45,11 +45,17 @@ describe("TwoDice Component tests", () => { expect(leftButton).toBeInTheDocument(); expect(rightButton).toBeInTheDocument(); }); - test("(1 pts) Clicking left button changes first number", () => { + test("(1 pts) Clicking left button changes first number", async () => { const leftButton = screen.getByRole("button", { name: /Roll Left/i }); - leftButton.click(); - leftButton.click(); - leftButton.click(); + await act(async () => { + leftButton.click(); + }); + await act(async () => { + leftButton.click(); + }); + await act(async () => { + leftButton.click(); + }); // Then the random function should be called 3 times expect(mathRandomFunction).toBeCalledTimes(3); // And the number to be 5 @@ -57,11 +63,17 @@ describe("TwoDice Component tests", () => { expect(leftNumber).toEqual(5); }); // Clicking right button changes second number - test("(1 pts) Clicking right button changes second number", () => { + test("(1 pts) Clicking right button changes second number", async () => { const rightButton = screen.getByRole("button", { name: /Roll Right/i }); - rightButton.click(); - rightButton.click(); - rightButton.click(); + await act(async () => { + rightButton.click(); + }); + await act(async () => { + rightButton.click(); + }); + await act(async () => { + rightButton.click(); + }); // Then the random function should be called 3 times expect(mathRandomFunction).toBeCalledTimes(3); // And the number to be 5 @@ -69,15 +81,19 @@ describe("TwoDice Component tests", () => { expect(rightNumber).toEqual(5); }); // Rolling two different numbers does not win or lose the game - test("(1 pts) Rolling two different numbers does not win or lose the game", () => { + test("(1 pts) Rolling two different numbers does not win or lose the game", async () => { // Given const leftButton = screen.getByRole("button", { name: /Roll Left/i }); const rightButton = screen.getByRole("button", { name: /Roll Right/i }); const leftDie = screen.getByTestId("left-die"); const rightDie = screen.getByTestId("right-die"); // When the left and right buttons are rolled once each - leftButton.click(); - rightButton.click(); + await act(async () => { + leftButton.click(); + }); + await act(async () => { + rightButton.click(); + }); // Then the numbers are not equal const leftNumber = extractDigits(leftDie); const rightNumber = extractDigits(rightDie); @@ -90,18 +106,28 @@ describe("TwoDice Component tests", () => { const loseText = screen.queryByText(/Lose/i); expect(loseText).toBeNull(); }); - test("(1 pts) Getting snake eyes loses the game", () => { + test("(1 pts) Getting snake eyes loses the game", async () => { // Given const leftButton = screen.getByRole("button", { name: /Roll Left/i }); const rightButton = screen.getByRole("button", { name: /Roll Right/i }); const leftDie = screen.getByTestId("left-die"); const rightDie = screen.getByTestId("right-die"); // When the left and right buttons are rolled once each - leftButton.click(); - rightButton.click(); - rightButton.click(); - rightButton.click(); - rightButton.click(); + await act(async () => { + leftButton.click(); + }); + await act(async () => { + rightButton.click(); + }); + await act(async () => { + rightButton.click(); + }); + await act(async () => { + rightButton.click(); + }); + await act(async () => { + rightButton.click(); + }); // Then the numbers are not equal const leftNumber = extractDigits(leftDie); const rightNumber = extractDigits(rightDie); @@ -114,17 +140,25 @@ describe("TwoDice Component tests", () => { const loseText = screen.getByText(/Lose/i); expect(loseText).toBeInTheDocument(); }); - test("(1 pts) Getting matching numbers wins the game", () => { + test("(1 pts) Getting matching numbers wins the game", async () => { // Given const leftButton = screen.getByRole("button", { name: /Roll Left/i }); const rightButton = screen.getByRole("button", { name: /Roll Right/i }); const leftDie = screen.getByTestId("left-die"); const rightDie = screen.getByTestId("right-die"); // When the left and right buttons are rolled once each - leftButton.click(); - leftButton.click(); - leftButton.click(); - rightButton.click(); + await act(async () => { + leftButton.click(); + }); + await act(async () => { + leftButton.click(); + }); + await act(async () => { + leftButton.click(); + }); + await act(async () => { + rightButton.click(); + }); // Then the numbers are not equal const leftNumber = extractDigits(leftDie); const rightNumber = extractDigits(rightDie); From c419dc919275541b287452c52aac7bf55c2b409c Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 24 Aug 2024 14:20:29 -0400 Subject: [PATCH 66/79] Fix linting --- src/components/ChangeType.test.tsx | 6 +++--- src/components/CycleHoliday.test.tsx | 8 ++++---- src/components/RevealAnswer.test.tsx | 6 +++--- src/components/StartAttempt.test.tsx | 24 ++++++++++++------------ 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/components/ChangeType.test.tsx b/src/components/ChangeType.test.tsx index 0a126a9111..f0ee545cc3 100644 --- a/src/components/ChangeType.test.tsx +++ b/src/components/ChangeType.test.tsx @@ -19,14 +19,14 @@ describe("ChangeType Component tests", () => { test("(1 pts) There is a button labeled Change Type", () => { const changeTypeButton = screen.getByRole("button", { - name: /Change Type/i, + name: /Change Type/i }); expect(changeTypeButton).toBeInTheDocument(); }); test("(1 pts) Clicking the button changes the type.", async () => { const changeTypeButton = screen.getByRole("button", { - name: /Change Type/i, + name: /Change Type/i }); await act(async () => { changeTypeButton.click(); @@ -41,7 +41,7 @@ describe("ChangeType Component tests", () => { test("(1 pts) Clicking the button twice keeps the type the same.", async () => { const changeTypeButton = screen.getByRole("button", { - name: /Change Type/i, + name: /Change Type/i }); await act(async () => { changeTypeButton.click(); diff --git a/src/components/CycleHoliday.test.tsx b/src/components/CycleHoliday.test.tsx index 4c1422f3df..ae364a0b5b 100644 --- a/src/components/CycleHoliday.test.tsx +++ b/src/components/CycleHoliday.test.tsx @@ -14,10 +14,10 @@ describe("CycleHoliday Component tests", () => { test("(1 pts) There are two buttons", () => { const alphabetButton = screen.getByRole("button", { - name: /Alphabet/i, + name: /Alphabet/i }); const yearButton = screen.getByRole("button", { - name: /Year/i, + name: /Year/i }); expect(alphabetButton).toBeInTheDocument(); expect(yearButton).toBeInTheDocument(); @@ -25,7 +25,7 @@ describe("CycleHoliday Component tests", () => { test("(1 pts) Can cycle through 5 distinct holidays alphabetically", async () => { const alphabetButton = screen.getByRole("button", { - name: /Alphabet/i, + name: /Alphabet/i }); const initialHoliday = screen.getByText(/Holiday ?[:)-](.*)/i); const states: string[] = []; @@ -42,7 +42,7 @@ describe("CycleHoliday Component tests", () => { test("(1 pts) Can cycle through 5 distinct holidays by year", async () => { const yearButton = screen.getByRole("button", { - name: /Year/i, + name: /Year/i }); const initialHoliday = screen.getByText(/Holiday ?[:)-](.*)/i); const states: string[] = []; diff --git a/src/components/RevealAnswer.test.tsx b/src/components/RevealAnswer.test.tsx index f5ad250bf9..6b2076ad1a 100644 --- a/src/components/RevealAnswer.test.tsx +++ b/src/components/RevealAnswer.test.tsx @@ -12,13 +12,13 @@ describe("RevealAnswer Component tests", () => { }); test("(1 pts) There is a Reveal Answer button", () => { const revealButton = screen.getByRole("button", { - name: /Reveal Answer/i, + name: /Reveal Answer/i }); expect(revealButton).toBeInTheDocument(); }); test("(1 pts) Clicking Reveal Answer button reveals the '42'", async () => { const revealButton = screen.getByRole("button", { - name: /Reveal Answer/i, + name: /Reveal Answer/i }); await act(async () => { revealButton.click(); @@ -28,7 +28,7 @@ describe("RevealAnswer Component tests", () => { }); test("(1 pts) Clicking Reveal Answer button twice hides the '42'", async () => { const revealButton = screen.getByRole("button", { - name: /Reveal Answer/i, + name: /Reveal Answer/i }); await act(async () => { revealButton.click(); diff --git a/src/components/StartAttempt.test.tsx b/src/components/StartAttempt.test.tsx index 74397290bb..fd326936e6 100644 --- a/src/components/StartAttempt.test.tsx +++ b/src/components/StartAttempt.test.tsx @@ -54,7 +54,7 @@ describe("StartAttempt Component tests", () => { }); test("(1 pts) There is an initially enabled Mulligan button", () => { const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i, + name: /Mulligan/i }); expect(mulliganButton).toBeInTheDocument(); expect(mulliganButton).toBeEnabled(); @@ -63,7 +63,7 @@ describe("StartAttempt Component tests", () => { const attemptNumber: number = extractDigits(screen.getByText(/(\d+)/)) || 0; const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i, + name: /Mulligan/i }); await act(async () => { mulliganButton.click(); @@ -75,7 +75,7 @@ describe("StartAttempt Component tests", () => { const attemptNumber: number = extractDigits(screen.getByText(/(\d+)/)) || 0; const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i, + name: /Mulligan/i }); await act(async () => { mulliganButton.click(); @@ -90,7 +90,7 @@ describe("StartAttempt Component tests", () => { const attemptNumber: number = extractDigits(screen.getByText(/(\d+)/)) || 0; const startButton = screen.getByRole("button", { - name: /Start Quiz/i, + name: /Start Quiz/i }); await act(async () => { startButton.click(); @@ -102,11 +102,11 @@ describe("StartAttempt Component tests", () => { test("(1 pts) Clicking Start Quiz changes enabled buttons", async () => { // Given the buttons... const startButton = screen.getByRole("button", { - name: /Start Quiz/i, + name: /Start Quiz/i }); const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i, + name: /Mulligan/i }); // When the start button is clicked await act(async () => { @@ -120,11 +120,11 @@ describe("StartAttempt Component tests", () => { test("(1 pts) Clicking Start and Stop Quiz changes enabled buttons", async () => { // Given the buttons and initial attempt number... const startButton = screen.getByRole("button", { - name: /Start Quiz/i, + name: /Start Quiz/i }); const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i, + name: /Mulligan/i }); // When we click the start button and then the stop button await act(async () => { @@ -141,11 +141,11 @@ describe("StartAttempt Component tests", () => { test("(1 pts) Clicking Start, Stop, Mulligan sets attempts to original", async () => { // Given the buttons and initial attempt number... const startButton = screen.getByRole("button", { - name: /Start Quiz/i, + name: /Start Quiz/i }); const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i, + name: /Mulligan/i }); const attemptNumber: number = extractDigits(screen.getByText(/(\d+)/)) || 0; @@ -172,11 +172,11 @@ describe("StartAttempt Component tests", () => { test("(1 pts) Cannot click start quiz when out of attempts", async () => { // Given the buttons and initial attempt number... const startButton = screen.getByRole("button", { - name: /Start Quiz/i, + name: /Start Quiz/i }); const stopButton = screen.getByRole("button", { name: /Stop Quiz/i }); const mulliganButton = screen.getByRole("button", { - name: /Mulligan/i, + name: /Mulligan/i }); let attemptNumber = extractDigits(screen.getByText(/(\d+)/)) || 0; const initialAttempt = attemptNumber; From 50a9c85dca9bc60f7dd875355f2c28c3988ca610 Mon Sep 17 00:00:00 2001 From: Austin Cory Bart Date: Sat, 24 Aug 2024 14:47:15 -0400 Subject: [PATCH 67/79] Update for new react --- src/bad-components/ChooseTeam.test.tsx | 54 ++++++++++++++++++-------- src/bad-components/ChooseTeam.tsx | 4 +- src/bad-components/ColoredBox.test.tsx | 26 ++++++++----- src/bad-components/ColoredBox.tsx | 14 ++++--- src/bad-components/DoubleHalf.test.tsx | 48 +++++++++++++++-------- src/bad-components/DoubleHalf.tsx | 26 ++++++++++--- src/bad-components/ShoveBox.test.tsx | 26 ++++++++----- src/bad-components/ShoveBox.tsx | 16 +++++--- 8 files changed, 144 insertions(+), 70 deletions(-) diff --git a/src/bad-components/ChooseTeam.test.tsx b/src/bad-components/ChooseTeam.test.tsx index f11465a2d6..66eee4be70 100644 --- a/src/bad-components/ChooseTeam.test.tsx +++ b/src/bad-components/ChooseTeam.test.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { act } from "react"; import { render, screen } from "@testing-library/react"; import { ChooseTeam } from "./ChooseTeam"; @@ -6,54 +6,74 @@ describe("ChooseTeam Component tests", () => { beforeEach(() => { render(); }); - test("The initial team is empty", () => { + test("(2 pts) The initial team is empty", () => { const currentTeam = screen.queryAllByRole("listitem"); expect(currentTeam).toHaveLength(0); }); - test("There are 7 buttons.", () => { + test("(2 pts) There are 7 buttons.", () => { const adders = screen.queryAllByRole("button"); expect(adders).toHaveLength(7); }); - test("Clicking first team member works", () => { + test("(2 pts) Clicking first team member works", async () => { const first = screen.queryAllByRole("button")[0]; - first.click(); + await act(async () => { + first.click(); + }); const currentTeam = screen.queryAllByRole("listitem"); expect(currentTeam).toHaveLength(1); expect(currentTeam[0].textContent).toEqual(first.textContent); }); - test("Clicking the third team member works", () => { + test("(2 pts) Clicking the third team member works", async () => { const third = screen.queryAllByRole("button")[2]; - third.click(); + await act(async () => { + third.click(); + }); const currentTeam = screen.queryAllByRole("listitem"); expect(currentTeam).toHaveLength(1); expect(currentTeam[0].textContent).toEqual(third.textContent); }); - test("Clicking three team members works", () => { + test("(2 pts) Clicking three team members works", async () => { const [, second, third, , fifth] = screen.queryAllByRole("button"); - third.click(); - second.click(); - fifth.click(); + await act(async () => { + third.click(); + }); + await act(async () => { + second.click(); + }); + await act(async () => { + fifth.click(); + }); const currentTeam = screen.queryAllByRole("listitem"); expect(currentTeam).toHaveLength(3); expect(currentTeam[0].textContent).toEqual(third.textContent); expect(currentTeam[1].textContent).toEqual(second.textContent); expect(currentTeam[2].textContent).toEqual(fifth.textContent); }); - test("Clearing the team works", () => { + test("(2 pts) Clearing the team works", async () => { const [, second, third, fourth, fifth, , clear] = screen.queryAllByRole("button"); - third.click(); - second.click(); - fifth.click(); + await act(async () => { + third.click(); + }); + await act(async () => { + second.click(); + }); + await act(async () => { + fifth.click(); + }); let currentTeam = screen.queryAllByRole("listitem"); expect(currentTeam).toHaveLength(3); expect(currentTeam[0].textContent).toEqual(third.textContent); expect(currentTeam[1].textContent).toEqual(second.textContent); expect(currentTeam[2].textContent).toEqual(fifth.textContent); - clear.click(); + await act(async () => { + clear.click(); + }); currentTeam = screen.queryAllByRole("listitem"); expect(currentTeam).toHaveLength(0); - fourth.click(); + await act(async () => { + fourth.click(); + }); currentTeam = screen.queryAllByRole("listitem"); expect(currentTeam).toHaveLength(1); expect(currentTeam[0].textContent).toEqual(fourth.textContent); diff --git a/src/bad-components/ChooseTeam.tsx b/src/bad-components/ChooseTeam.tsx index c02334e661..e73f600083 100644 --- a/src/bad-components/ChooseTeam.tsx +++ b/src/bad-components/ChooseTeam.tsx @@ -7,10 +7,10 @@ const PEOPLE = [ "Ada Lovelace", "Charles Babbage", "Barbara Liskov", - "Margaret Hamilton" + "Margaret Hamilton", ]; -export function ChooseTeam(): JSX.Element { +export function ChooseTeam(): React.JSX.Element { const [allOptions, setAllOptions] = useState(PEOPLE); const [team, setTeam] = useState([]); diff --git a/src/bad-components/ColoredBox.test.tsx b/src/bad-components/ColoredBox.test.tsx index c17a13f66c..5762afefb6 100644 --- a/src/bad-components/ColoredBox.test.tsx +++ b/src/bad-components/ColoredBox.test.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { act } from "react"; import { render, screen } from "@testing-library/react"; import { ColoredBox } from "./ColoredBox"; @@ -6,26 +6,32 @@ describe("ColoredBox Component tests", () => { beforeEach(() => { render(); }); - test("The ColoredBox is initially red.", () => { + test("(2 pts) The ColoredBox is initially red.", () => { const box = screen.getByTestId("colored-box"); expect(box).toHaveStyle({ backgroundColor: "red" }); }); - test("There is a button", () => { + test("(2 pts) There is a button", () => { expect(screen.getByRole("button")).toBeInTheDocument(); }); - test("Clicking the button advances the color.", () => { + test("(2 pts) Clicking the button advances the color.", async () => { const nextColor = screen.getByRole("button"); - nextColor.click(); + await act(async () => { + nextColor.click(); + }); expect(screen.getByTestId("colored-box")).toHaveStyle({ - backgroundColor: "blue" + backgroundColor: "blue", + }); + await act(async () => { + nextColor.click(); }); - nextColor.click(); expect(screen.getByTestId("colored-box")).toHaveStyle({ - backgroundColor: "green" + backgroundColor: "green", + }); + await act(async () => { + nextColor.click(); }); - nextColor.click(); expect(screen.getByTestId("colored-box")).toHaveStyle({ - backgroundColor: "red" + backgroundColor: "red", }); }); }); diff --git a/src/bad-components/ColoredBox.tsx b/src/bad-components/ColoredBox.tsx index a3c3c3f077..1fa2d770b2 100644 --- a/src/bad-components/ColoredBox.tsx +++ b/src/bad-components/ColoredBox.tsx @@ -4,16 +4,20 @@ import { Button } from "react-bootstrap"; export const COLORS = ["red", "blue", "green"]; const DEFAULT_COLOR_INDEX = 0; -function ChangeColor(): JSX.Element { +function ChangeColor(): React.JSX.Element { const [colorIndex, setColorIndex] = useState(DEFAULT_COLOR_INDEX); return ( - ); } -function ColorPreview(): JSX.Element { +function ColorPreview(): React.JSX.Element { return (
      ); } -export function ColoredBox(): JSX.Element { +export function ColoredBox(): React.JSX.Element { return (

      Colored Box

      diff --git a/src/bad-components/DoubleHalf.test.tsx b/src/bad-components/DoubleHalf.test.tsx index cbae5f68af..9b2a031acf 100644 --- a/src/bad-components/DoubleHalf.test.tsx +++ b/src/bad-components/DoubleHalf.test.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { act } from "react"; import { render, screen } from "@testing-library/react"; import { DoubleHalf } from "./DoubleHalf"; @@ -6,50 +6,66 @@ describe("DoubleHalf Component tests", () => { beforeEach(() => { render(); }); - test("The DoubleHalf value is initially 10", () => { + test("(2 pts) The DoubleHalf value is initially 10", () => { expect(screen.getByText("10")).toBeInTheDocument(); expect(screen.queryByText("20")).not.toBeInTheDocument(); expect(screen.queryByText("5")).not.toBeInTheDocument(); }); - test("There are Double and Halve buttons", () => { + test("(2 pts) There are Double and Halve buttons", () => { expect( - screen.getByRole("button", { name: /Double/i }) + screen.getByRole("button", { name: /Double/i }), ).toBeInTheDocument(); expect( - screen.getByRole("button", { name: /Halve/i }) + screen.getByRole("button", { name: /Halve/i }), ).toBeInTheDocument(); }); - test("You can double the number.", () => { + test("(2 pts) You can double the number.", async () => { const double = screen.getByRole("button", { name: /Double/i }); - double.click(); + await act(async () => { + double.click(); + }); expect(screen.getByText("20")).toBeInTheDocument(); expect(screen.queryByText("10")).not.toBeInTheDocument(); }); - test("You can halve the number.", () => { + test("(2 pts) You can halve the number.", async () => { const halve = screen.getByRole("button", { name: /Halve/i }); - halve.click(); + await act(async () => { + halve.click(); + }); expect(screen.getByText("5")).toBeInTheDocument(); expect(screen.queryByText("10")).not.toBeInTheDocument(); }); - test("You can double AND halve the number.", () => { + test("(2 pts) You can double AND halve the number.", async () => { const double = screen.getByRole("button", { name: /Double/i }); const halve = screen.getByRole("button", { name: /Halve/i }); - double.click(); + await act(async () => { + double.click(); + }); expect(screen.getByText("20")).toBeInTheDocument(); expect(screen.queryByText("10")).not.toBeInTheDocument(); - double.click(); + await act(async () => { + double.click(); + }); expect(screen.getByText("40")).toBeInTheDocument(); expect(screen.queryByText("20")).not.toBeInTheDocument(); - halve.click(); + await act(async () => { + halve.click(); + }); expect(screen.getByText("20")).toBeInTheDocument(); expect(screen.queryByText("10")).not.toBeInTheDocument(); - halve.click(); + await act(async () => { + halve.click(); + }); expect(screen.getByText("10")).toBeInTheDocument(); expect(screen.queryByText("20")).not.toBeInTheDocument(); - halve.click(); + await act(async () => { + halve.click(); + }); expect(screen.getByText("5")).toBeInTheDocument(); expect(screen.queryByText("10")).not.toBeInTheDocument(); - halve.click(); + await act(async () => { + halve.click(); + }); expect(screen.getByText("2.5")).toBeInTheDocument(); expect(screen.queryByText("5")).not.toBeInTheDocument(); }); diff --git a/src/bad-components/DoubleHalf.tsx b/src/bad-components/DoubleHalf.tsx index 5ae9cf4501..8b01352f59 100644 --- a/src/bad-components/DoubleHalf.tsx +++ b/src/bad-components/DoubleHalf.tsx @@ -2,15 +2,31 @@ import React, { useState } from "react"; import { Button } from "react-bootstrap"; import { dhValue, setDhValue } from "./DoubleHalfState"; -function Doubler(): JSX.Element { - return ; +function Doubler(): React.JSX.Element { + return ( + + ); } -function Halver(): JSX.Element { - return ; +function Halver(): React.JSX.Element { + return ( + + ); } -export function DoubleHalf(): JSX.Element { +export function DoubleHalf(): React.JSX.Element { return (

      Double Half

      diff --git a/src/bad-components/ShoveBox.test.tsx b/src/bad-components/ShoveBox.test.tsx index 2adec13d4e..e89abf2751 100644 --- a/src/bad-components/ShoveBox.test.tsx +++ b/src/bad-components/ShoveBox.test.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { act } from "react"; import { render, screen } from "@testing-library/react"; import { ShoveBox } from "./ShoveBox"; @@ -6,26 +6,32 @@ describe("ShoveBox Component tests", () => { beforeEach(() => { render(); }); - test("The MoveableBox is initially nearby.", () => { + test("(2 pts) The MoveableBox is initially nearby.", () => { const box = screen.getByTestId("moveable-box"); expect(box).toHaveStyle({ marginLeft: "10px" }); }); - test("There is a button", () => { + test("(2 pts) There is a button", () => { expect(screen.getByRole("button")).toBeInTheDocument(); }); - test("Clicking the button moves the box.", () => { + test("(2 pts) Clicking the button moves the box.", async () => { const shoveBox = screen.getByRole("button"); - shoveBox.click(); + await act(async () => { + shoveBox.click(); + }); expect(screen.getByTestId("moveable-box")).toHaveStyle({ - marginLeft: "14px" + marginLeft: "14px", + }); + await act(async () => { + shoveBox.click(); }); - shoveBox.click(); expect(screen.getByTestId("moveable-box")).toHaveStyle({ - marginLeft: "18px" + marginLeft: "18px", + }); + await act(async () => { + shoveBox.click(); }); - shoveBox.click(); expect(screen.getByTestId("moveable-box")).toHaveStyle({ - marginLeft: "22px" + marginLeft: "22px", }); }); }); diff --git a/src/bad-components/ShoveBox.tsx b/src/bad-components/ShoveBox.tsx index 7c55142636..45cdcc335d 100644 --- a/src/bad-components/ShoveBox.tsx +++ b/src/bad-components/ShoveBox.tsx @@ -3,17 +3,23 @@ import { Button } from "react-bootstrap"; function ShoveBoxButton({ position, - setPosition + setPosition, }: { position: number; setPosition: (newPosition: number) => void; }) { return ( - + ); } -function MoveableBox(): JSX.Element { +function MoveableBox(): React.JSX.Element { const [position, setPosition] = useState(10); return (
      ); } -export function ShoveBox(): JSX.Element { +export function ShoveBox(): React.JSX.Element { const box = MoveableBox(); return ( From 6c33d89635a781111fd8b2058c6d1556d9780191 Mon Sep 17 00:00:00 2001 From: ethfass Date: Thu, 29 Aug 2024 17:46:25 -0400 Subject: [PATCH 68/79] Added name to App --- src/App.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/App.tsx b/src/App.tsx index b77558eaac..0cf5aed5ee 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
      +

      Ethan Fassnacht - for credit

      Edit src/App.tsx and save. This page will automatically reload. From f59678929040d5707bc0e16f6f6485f3b304b89b Mon Sep 17 00:00:00 2001 From: ethfass Date: Fri, 30 Aug 2024 11:13:33 -0400 Subject: [PATCH 69/79] Added name to App --- src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 0cf5aed5ee..15fbfd904a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,7 @@ function App(): React.JSX.Element {

      UD CISC275 with React Hooks and TypeScript
      -

      Ethan Fassnacht - for credit

      +

      Ethan Fassnacht - name for credit

      Edit src/App.tsx and save. This page will automatically reload. From 5e4f4b9b14a1f20d2603d7a95ad510051c3e209a Mon Sep 17 00:00:00 2001 From: Ethan Fassnacht Date: Tue, 3 Sep 2024 11:52:23 -0400 Subject: [PATCH 70/79] got rid of quotations --- package-lock.json | 123 +++++++++++++++++++++------------------------- src/App.tsx | 1 + 2 files changed, 58 insertions(+), 66 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c3779f487..1e6922997e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2153,7 +2153,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" @@ -2166,7 +2166,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", @@ -3273,7 +3273,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.27.8" @@ -3436,7 +3436,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -3454,7 +3454,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3470,7 +3470,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -3487,7 +3487,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3500,14 +3500,14 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@jest/types/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -3517,7 +3517,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -3909,7 +3909,7 @@ "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@sinonjs/commons": { @@ -4222,7 +4222,6 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.10.4", @@ -4242,7 +4241,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -4258,7 +4256,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -4275,7 +4272,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4288,14 +4284,12 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/@testing-library/dom/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4305,7 +4299,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -4470,35 +4463,34 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, "license": "MIT" }, "node_modules/@types/babel__core": { @@ -4911,7 +4903,7 @@ "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -5508,7 +5500,7 @@ "version": "8.3.3", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -7210,7 +7202,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/cross-spawn": { @@ -7947,7 +7939,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -13021,7 +13013,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -13031,7 +13023,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -13949,7 +13941,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -13959,7 +13951,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -13994,7 +13986,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -14010,7 +14002,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -14027,7 +14019,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -14040,14 +14032,14 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jest-resolve/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -14057,7 +14049,7 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", @@ -14075,7 +14067,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -14518,7 +14510,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -14536,7 +14528,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -14552,7 +14544,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -14569,7 +14561,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -14582,14 +14574,14 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jest-util/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -14599,7 +14591,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -14612,7 +14604,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/types": "^29.6.3", @@ -14630,7 +14622,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -14646,7 +14638,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -14659,7 +14651,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -14676,7 +14668,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -14689,14 +14681,14 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jest-validate/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -14706,7 +14698,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.6.3", @@ -14721,7 +14713,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -14734,14 +14726,14 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/jest-validate/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -14850,7 +14842,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -14866,7 +14858,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -14876,7 +14868,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -15368,7 +15360,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, "license": "MIT", "bin": { "lz-string": "bin/bin.js" @@ -15402,7 +15393,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/makeerror": { @@ -20668,7 +20659,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -22588,7 +22579,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -22632,7 +22623,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/tsconfig-paths": { @@ -23056,7 +23047,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/v8-to-istanbul": { @@ -24118,7 +24109,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" diff --git a/src/App.tsx b/src/App.tsx index 15fbfd904a..9c31197d9d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ function App(): React.JSX.Element { UD CISC275 with React Hooks and TypeScript

      Ethan Fassnacht - name for credit

      +

      Hello World

      Edit src/App.tsx and save. This page will automatically reload. From fbd725630c558caee99f047cc3e306e719835f0c Mon Sep 17 00:00:00 2001 From: Ethan Fassnacht Date: Tue, 3 Sep 2024 18:20:23 -0400 Subject: [PATCH 71/79] changes for task 3 --- src/App.tsx | 42 +++++++++++++++++++++++++++++++++++++++++- src/doghat.jpg | Bin 0 -> 201444 bytes 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/doghat.jpg diff --git a/src/App.tsx b/src/App.tsx index 9c31197d9d..006a2917d8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,14 +1,54 @@ import React from "react"; import "./App.css"; +import doghat from "./doghat.jpg"; +import { Button, Container, Row, Col } from "react-bootstrap"; function App(): React.JSX.Element { return (

      -
      +

      This is a test

      + A picture of a dog with a hat +
      UD CISC275 with React Hooks and TypeScript
      +

      + + + +
      + + +
      + +
      +

      Ethan Fassnacht - name for credit

      Hello World

      +
        +
      1. this is thing1
      2. +
      3. second thing
      4. +
      5. thing number 3
      6. +
      7. the last thing, thing4
      8. +
      +

      Edit src/App.tsx and save. This page will automatically reload. diff --git a/src/doghat.jpg b/src/doghat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..df92ce5f763f47629a82895c76f52f969aba0150 GIT binary patch literal 201444 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 z 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 OzkwaYPmKHX{{I0JlW`0H literal 0 HcmV?d00001 From 700600310e8f4a587301fbfd75cd5128e5e9b04c Mon Sep 17 00:00:00 2001 From: Ethan Fassnacht Date: Fri, 6 Sep 2024 12:18:08 -0400 Subject: [PATCH 72/79] functions for task 4 --- src/functions.ts | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/functions.ts b/src/functions.ts index e614c81c0c..b65323ca22 100644 --- a/src/functions.ts +++ b/src/functions.ts @@ -4,7 +4,9 @@ * C = (F - 32) * 5/9 */ export function fahrenheitToCelius(temperature: number): number { - return 0; + temperature -= 32; + temperature *= 5 / 9; + return temperature; } /** @@ -12,7 +14,17 @@ export function fahrenheitToCelius(temperature: number): number { * if the number is greater than zero. */ export function add3(first: number, second: number, third: number): number { - return 0; + let goal: number = 0; + if (first > 0) { + goal += first; + } + if (second > 0) { + goal += second; + } + if (third > 0) { + goal += third; + } + return goal; } /** @@ -20,7 +32,7 @@ export function add3(first: number, second: number, third: number): number { * mark added to the end. */ export function shout(message: string): string { - return ""; + return message.toUpperCase() + "!"; } /** @@ -28,7 +40,7 @@ export function shout(message: string): string { * mark. Do not use an `if` statement in solving this question. */ export function isQuestion(message: string): boolean { - return true; + return message[message.length - 1] === "?"; } /** @@ -37,5 +49,11 @@ export function isQuestion(message: string): boolean { * upper or lower case), then return `false`. Otherwise, return `null`. */ export function convertYesNo(word: string): boolean | null { - return true; + if (word.toUpperCase() === "YES") { + return true; + } else if (word.toUpperCase() == "NO") { + return false; + } else { + return null; + } } From 41fa39ae13f2b4c13bb6c5edfe20dc6f82195077 Mon Sep 17 00:00:00 2001 From: Ethan Fassnacht Date: Tue, 10 Sep 2024 22:47:00 -0400 Subject: [PATCH 73/79] changes for task 4 --- src/arrays.ts | 81 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/src/arrays.ts b/src/arrays.ts index 4a2ffe8e5b..e908776293 100644 --- a/src/arrays.ts +++ b/src/arrays.ts @@ -5,7 +5,11 @@ * the number twice. */ export function bookEndList(numbers: number[]): number[] { - return numbers; + if (numbers.length === 0) { + return []; + } + let goal: number[] = [numbers[0], numbers[numbers.length - 1]]; + return goal; } /** @@ -13,7 +17,8 @@ export function bookEndList(numbers: number[]): number[] { * number has been tripled (multiplied by 3). */ export function tripleNumbers(numbers: number[]): number[] { - return numbers; + let goal: number[] = numbers.map((x: number): number => x * 3); + return goal; } /** @@ -21,7 +26,11 @@ export function tripleNumbers(numbers: number[]): number[] { * the number cannot be parsed as an integer, convert it to 0 instead. */ export function stringsToIntegers(numbers: string[]): number[] { - return []; + let goal: number[] = numbers.map((dupe: string): number => { + let goal = Number(dupe); + return isNaN(goal) ? 0 : goal; + }); + return goal; } /** @@ -32,7 +41,12 @@ export function stringsToIntegers(numbers: string[]): number[] { */ // Remember, you can write functions as lambdas too! They work exactly the same. export const removeDollars = (amounts: string[]): number[] => { - return []; + let goal: number[] = amounts.map((dupe: string): number => { + let temp = dupe[0] === "$" ? dupe.slice(1) : dupe; + let goal = parseInt(temp, 10); + return isNaN(goal) ? 0 : goal; + }); + return goal; }; /** @@ -41,7 +55,17 @@ export const removeDollars = (amounts: string[]): number[] => { * in question marks ("?"). */ export const shoutIfExclaiming = (messages: string[]): string[] => { - return []; + let goal: string[] = messages.map((dupe: string): string => { + if (dupe[dupe.length - 1] === "!") { + return dupe.toUpperCase(); + } else { + return dupe; + } + }); + goal = goal.filter( + (dupe: string): boolean => dupe[dupe.length - 1] !== "?", + ); + return goal; }; /** @@ -49,7 +73,10 @@ export const shoutIfExclaiming = (messages: string[]): string[] => { * 4 letters long. */ export function countShortWords(words: string[]): number { - return 0; + let goal: string[] = words.filter( + (dupe: string): boolean => dupe.length < 4, + ); + return goal.length; } /** @@ -58,7 +85,19 @@ export function countShortWords(words: string[]): number { * then return true. */ export function allRGB(colors: string[]): boolean { - return false; + let goal = false; + if (colors.length === 0) { + return true; + } + if ( + colors.every( + (dupe: string): boolean => + dupe === "red" || dupe === "green" || dupe === "blue", + ) + ) { + goal = true; + } + return goal; } /** @@ -69,7 +108,14 @@ export function allRGB(colors: string[]): boolean { * And the array [] would become "0=0". */ export function makeMath(addends: number[]): string { - return ""; + if (addends.length === 0) { + return "0=0"; + } + let dupe = addends.reduce((total, curr) => { + return total + curr; + }, 0); + let dupe2 = dupe.toString() + "="; + return dupe2 + addends.join("+"); } /** @@ -82,5 +128,22 @@ export function makeMath(addends: number[]): string { * And the array [1, 9, 7] would become [1, 9, 7, 17] */ export function injectPositive(values: number[]): number[] { - return []; + let finale = [...values]; + if (finale.length === 0) { + return [0]; + } + let goated = finale.reduce((total, curr) => { + return total + curr; + }, 0); + let first = finale.findIndex((dupe: number): boolean => dupe < 0); + if (first === -1) { + finale.push(goated); + } else { + let goal = finale + .slice(0, first) + .reduce((total, curr) => total + curr, 0); + + finale.splice(first + 1, 0, goal); + } + return finale; } From bdd3a69609bb3acd8c9a234cb375a658ddde3fa1 Mon Sep 17 00:00:00 2001 From: Ethan Fassnacht Date: Fri, 13 Sep 2024 18:57:19 -0400 Subject: [PATCH 74/79] obj task --- src/objects.ts | 65 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/src/objects.ts b/src/objects.ts index 3fd2072e5e..dec98970d5 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -8,9 +8,18 @@ import { Question, QuestionType } from "./interfaces/question"; export function makeBlankQuestion( id: number, name: string, - type: QuestionType + type: QuestionType, ): Question { - return {}; + return { + id: id, + name: name, + type: type, + body: "", + expected: "", + options: [], + points: 1, + published: false, + }; } /** @@ -21,7 +30,9 @@ export function makeBlankQuestion( * HINT: Look up the `trim` and `toLowerCase` functions. */ export function isCorrect(question: Question, answer: string): boolean { - return false; + return ( + question.expected.trim().toLowerCase() === answer.trim().toLowerCase() + ); } /** @@ -31,7 +42,11 @@ export function isCorrect(question: Question, answer: string): boolean { * be exactly one of the options. */ export function isValid(question: Question, answer: string): boolean { - return false; + if (question.type === "short_answer_question") { + return true; + } else { + return question.options.includes(answer); + } } /** @@ -41,7 +56,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 { - return ""; + let goal = ""; + goal = question.id + ": " + question.name.slice(0, 10); + return goal; } /** @@ -62,7 +79,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 { - return ""; + let goal = ""; + goal += "# " + question.name + "\n" + question.body; + if (question.type === "multiple_choice_question") { + for (const x of question.options) { + goal += "\n- " + x; + } + } + return goal; } /** @@ -70,7 +94,8 @@ export function toMarkdown(question: Question): string { * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - return question; + const goal = { ...question, name: newName }; + return goal; } /** @@ -79,7 +104,8 @@ 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; + const goal = { ...question, published: !question.published }; + return goal; } /** @@ -89,7 +115,13 @@ export function publishQuestion(question: Question): Question { * The `published` field should be reset to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - return oldQuestion; + const goal = { + ...oldQuestion, + name: "Copy of " + oldQuestion.name, + published: false, + id: id, + }; + return goal; } /** @@ -100,7 +132,9 @@ 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 newOps = [...question.options, newOption]; + const goal = { ...question, options: newOps }; + return goal; } /** @@ -115,7 +149,14 @@ export function mergeQuestion( id: number, name: string, contentQuestion: Question, - { points }: { points: number } + { points }: { points: number }, ): Question { - return contentQuestion; + const goal = { + ...contentQuestion, + id: id, + name: name, + points: points, + published: false, + }; + return goal; } From de71c529eb1985685c3066383862a6861f1b19ad Mon Sep 17 00:00:00 2001 From: Ethan Fassnacht Date: Tue, 17 Sep 2024 01:39:51 -0400 Subject: [PATCH 75/79] nested task --- src/nested.ts | 140 +++++++++++++++++++++++++++++++++++++++++-------- src/objects.ts | 61 +++++++++++++++++---- 2 files changed, 169 insertions(+), 32 deletions(-) diff --git a/src/nested.ts b/src/nested.ts index 562b6ca0df..78b6f1d727 100644 --- a/src/nested.ts +++ b/src/nested.ts @@ -6,7 +6,8 @@ import { Question, QuestionType } from "./interfaces/question"; * that are `published`. */ export function getPublishedQuestions(questions: Question[]): Question[] { - return []; + const goal = questions.filter((element: Question) => element.published); + return goal; } /** @@ -15,7 +16,15 @@ export function getPublishedQuestions(questions: Question[]): Question[] { * `expected`, and an empty array for its `options`. */ export function getNonEmptyQuestions(questions: Question[]): Question[] { - return []; + const goal = questions.filter( + (element: Question) => + !( + element.body === "" && + element.expected === "" && + element.options.length === 0 + ), + ); + return goal; } /*** @@ -24,9 +33,15 @@ export function getNonEmptyQuestions(questions: Question[]): Question[] { */ export function findQuestion( questions: Question[], - id: number + id: number, ): Question | null { - return null; + const goal = questions.find( + (element: Question): boolean => element.id === id, + ); + if (goal === undefined) { + return null; + } + return goal; } /** @@ -34,7 +49,8 @@ export function findQuestion( * with the given `id`. */ export function removeQuestion(questions: Question[], id: number): Question[] { - return []; + const goal = questions.filter((element: Question) => !(element.id === id)); + return goal; } /*** @@ -42,21 +58,29 @@ export function removeQuestion(questions: Question[], id: number): Question[] { * questions, as an array. */ export function getNames(questions: Question[]): string[] { - return []; + const goal = questions.map((element: Question) => element.name); + return goal; } /*** * Consumes an array of questions and returns the sum total of all their points added together. */ export function sumPoints(questions: Question[]): number { - return 0; + const goal = questions.reduce((total: number, current: Question) => { + return total + current.points; + }, 0); + return goal; } /*** * Consumes an array of questions and returns the sum total of the PUBLISHED questions. */ export function sumPublishedPoints(questions: Question[]): number { - return 0; + const goal = questions.filter((element: Question) => element.published); + const finale = goal.reduce((total: number, current: Question) => { + return total + current.points; + }, 0); + return finale; } /*** @@ -77,7 +101,14 @@ id,name,options,points,published * Check the unit tests for more examples! */ export function toCSV(questions: Question[]): string { - return ""; + let goal = "id,name,options,points,published"; + const finale = questions + .map( + (element: Question) => + `${element.id},${element.name},${element.options.length},${element.points},${element.published}`, + ) + .join("\n"); + return `${goal}\n${finale}`; } /** @@ -86,7 +117,15 @@ export function toCSV(questions: Question[]): string { * making the `text` an empty string, and using false for both `submitted` and `correct`. */ export function makeAnswers(questions: Question[]): Answer[] { - return []; + const goal = questions.map((element: Question) => { + return { + questionId: element.id, + text: "", + submitted: false, + correct: false, + }; + }); + return goal; } /*** @@ -94,7 +133,10 @@ export function makeAnswers(questions: Question[]): Answer[] { * each question is now published, regardless of its previous published status. */ export function publishAll(questions: Question[]): Question[] { - return []; + const goal = questions.map((element: Question) => { + return { ...element, published: true }; + }); + return goal; } /*** @@ -102,7 +144,13 @@ export function publishAll(questions: Question[]): Question[] { * 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; + if (questions.length === 0) { + return true; + } + const typing = questions[0].type; + return questions.every( + (element: Question): boolean => element.type === typing, + ); } /*** @@ -114,9 +162,19 @@ export function addNewQuestion( questions: Question[], id: number, name: string, - type: QuestionType + type: QuestionType, ): Question[] { - return []; + const goal: Question = { + id: id, + name: name, + type: type, + body: "", + expected: "", + options: [], + points: 1, + published: false, + }; + return [...questions, goal]; } /*** @@ -127,9 +185,15 @@ export function addNewQuestion( export function renameQuestionById( questions: Question[], targetId: number, - newName: string + newName: string, ): Question[] { - return []; + return questions.map((element: Question) => { + if (element.id === targetId) { + return { ...element, name: newName }; + } else { + return element; + } + }); } /*** @@ -142,9 +206,19 @@ export function renameQuestionById( export function changeQuestionTypeById( questions: Question[], targetId: number, - newQuestionType: QuestionType + newQuestionType: QuestionType, ): Question[] { - return []; + return questions.map((element: Question) => { + if (element.id === targetId) { + if (newQuestionType !== "multiple_choice_question") { + return { ...element, type: newQuestionType, options: [] }; + } else { + return { ...element, type: newQuestionType }; + } + } else { + return element; + } + }); } /** @@ -161,9 +235,21 @@ export function editOption( questions: Question[], targetId: number, targetOptionIndex: number, - newOption: string + newOption: string, ): Question[] { - return []; + return questions.map((element: Question) => { + if (element.id === targetId) { + if (targetOptionIndex === -1) { + return { ...element, options: [...element.options, newOption] }; + } else { + const goal = [...element.options]; + goal[targetOptionIndex] = newOption; + return { ...element, options: goal }; + } + } else { + return element; + } + }); } /*** @@ -175,7 +261,17 @@ export function editOption( export function duplicateQuestionInArray( questions: Question[], targetId: number, - newId: number + newId: number, ): Question[] { - return []; + const goal = questions.find((element: Question) => element.id === targetId); + if (!goal) { + return questions; + } + const newQuestion = { ...goal, id: newId, name: "Copy of " + goal.name }; + const indy = questions.findIndex( + (element: Question) => element.id === targetId, + ); + const firstHalf = questions.slice(0, indy + 1); + const secondHalf = questions.slice(indy + 1); + return [...firstHalf, newQuestion, ...secondHalf]; } diff --git a/src/objects.ts b/src/objects.ts index 6ad25808c2..dec98970d5 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -10,7 +10,16 @@ export function makeBlankQuestion( name: string, type: QuestionType, ): Question { - return {}; + return { + id: id, + name: name, + type: type, + body: "", + expected: "", + options: [], + points: 1, + published: false, + }; } /** @@ -21,7 +30,9 @@ export function makeBlankQuestion( * HINT: Look up the `trim` and `toLowerCase` functions. */ export function isCorrect(question: Question, answer: string): boolean { - return false; + return ( + question.expected.trim().toLowerCase() === answer.trim().toLowerCase() + ); } /** @@ -31,7 +42,11 @@ export function isCorrect(question: Question, answer: string): boolean { * be exactly one of the options. */ export function isValid(question: Question, answer: string): boolean { - return false; + if (question.type === "short_answer_question") { + return true; + } else { + return question.options.includes(answer); + } } /** @@ -41,7 +56,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 { - return ""; + let goal = ""; + goal = question.id + ": " + question.name.slice(0, 10); + return goal; } /** @@ -62,7 +79,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 { - return ""; + let goal = ""; + goal += "# " + question.name + "\n" + question.body; + if (question.type === "multiple_choice_question") { + for (const x of question.options) { + goal += "\n- " + x; + } + } + return goal; } /** @@ -70,7 +94,8 @@ export function toMarkdown(question: Question): string { * `newName`. */ export function renameQuestion(question: Question, newName: string): Question { - return question; + const goal = { ...question, name: newName }; + return goal; } /** @@ -79,7 +104,8 @@ 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; + const goal = { ...question, published: !question.published }; + return goal; } /** @@ -89,7 +115,13 @@ export function publishQuestion(question: Question): Question { * The `published` field should be reset to false. */ export function duplicateQuestion(id: number, oldQuestion: Question): Question { - return oldQuestion; + const goal = { + ...oldQuestion, + name: "Copy of " + oldQuestion.name, + published: false, + id: id, + }; + return goal; } /** @@ -100,7 +132,9 @@ 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 newOps = [...question.options, newOption]; + const goal = { ...question, options: newOps }; + return goal; } /** @@ -117,5 +151,12 @@ export function mergeQuestion( contentQuestion: Question, { points }: { points: number }, ): Question { - return contentQuestion; + const goal = { + ...contentQuestion, + id: id, + name: name, + points: points, + published: false, + }; + return goal; } From 9346b7ad30d56bbee0d210b023849dd801aa55b9 Mon Sep 17 00:00:00 2001 From: Ethan Fassnacht Date: Thu, 19 Sep 2024 11:46:34 -0400 Subject: [PATCH 76/79] got rid of old tests --- src/App.tsx | 1 + src/HtmlCss.test.tsx | 83 -------------------------------------------- src/text.test.tsx | 9 ----- 3 files changed, 1 insertion(+), 92 deletions(-) delete mode 100644 src/HtmlCss.test.tsx delete mode 100644 src/text.test.tsx diff --git a/src/App.tsx b/src/App.tsx index 805076820b..87067b8208 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -24,6 +24,7 @@ function App(): React.JSX.Element {


      +

      CISC275

      ); } diff --git a/src/HtmlCss.test.tsx b/src/HtmlCss.test.tsx deleted file mode 100644 index 320cb97524..0000000000 --- a/src/HtmlCss.test.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from "react"; -import { render, screen } from "@testing-library/react"; -import App from "./App"; -import userEvent from "@testing-library/user-event"; - -describe("Some HTML Elements are added.", () => { - test("(2 pts) There is a header", () => { - render(); - const header = screen.getByRole("heading"); - expect(header).toBeInTheDocument(); - }); - - test("(2 pts) There is an image with alt text", () => { - render(); - const image = screen.getByRole("img"); - expect(image).toBeInTheDocument(); - expect(image).toHaveAttribute("alt"); - }); - - test("(2 pts) There is a list with at least three elements", () => { - render(); - const list = screen.getByRole("list"); - expect(list).toBeInTheDocument(); - expect(list.children.length).toBeGreaterThanOrEqual(3); - }); -}); - -describe("(2 pts) Some basic CSS is added.", () => { - test("The background color of the header area is different", () => { - render(); - const banner = screen.getByRole("banner"); - expect(banner).not.toHaveStyle({ - "background-color": "rgb(40, 44, 52)", - }); - }); -}); - -describe("(2 pts) Some Bootstrap Elements are added", () => { - test("There is one bootstrap button with the text 'Log Hello World'", () => { - render(); - const button = screen.getByRole("button", { name: /Log Hello World/i }); - expect(button).toBeInTheDocument(); - expect(button).toHaveClass("btn"); - expect(button).toHaveClass("btn-primary"); - }); - - test("(2 pts) Not clicking the bootstrap button does not logs 'Hello World!'", () => { - const consoleSpy = jest.spyOn(console, "log"); - render(); - expect(consoleSpy).not.toHaveBeenCalledWith("Hello World!"); - }); - - test("(2 pts) Clicking the bootstrap button logs 'Hello World!'", () => { - const consoleSpy = jest.spyOn(console, "log"); - render(); - const button = screen.getByRole("button", { name: /Log Hello World/i }); - userEvent.click(button); - expect(consoleSpy).toHaveBeenCalledWith("Hello World!"); - }); -}); - -describe("Some additional CSS was added", () => { - test("(2 pts) checks if any element has a background color of red", () => { - const { container } = render(); - // Get all elements in the rendered container - const elements = container.querySelectorAll("*"); - - // Check if any element has a background color of red - let foundRedBackground = false; - - elements.forEach((element) => { - const style = getComputedStyle(element); - if ( - style.backgroundColor === "red" || - style.backgroundColor === "rgb(255, 0, 0)" - ) { - foundRedBackground = true; - } - }); - - expect(foundRedBackground).toBe(true); - }); -}); diff --git a/src/text.test.tsx b/src/text.test.tsx deleted file mode 100644 index f99a063e76..0000000000 --- a/src/text.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; -import { render, screen } from "@testing-library/react"; -import App from "./App"; - -test("renders the text 'Hello World' somewhere", () => { - render(); - const texts = screen.getAllByText(/Hello World/); - expect(texts.length).toBeGreaterThanOrEqual(1); -}); From 56c29b0d97b7555b814e7b81887aa856232cda8f Mon Sep 17 00:00:00 2001 From: Ethan Fassnacht Date: Thu, 19 Sep 2024 11:51:45 -0400 Subject: [PATCH 77/79] test8 --- src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 87067b8208..20fb5082ec 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -24,7 +24,7 @@ function App(): React.JSX.Element {
      -

      CISC275

      +

      Ethan Fassnacht - CISC275

      ); } From 25831d809abe92ee5fbf0ba3257dd92c274bdff6 Mon Sep 17 00:00:00 2001 From: Ethan Fassnacht Date: Thu, 19 Sep 2024 11:54:17 -0400 Subject: [PATCH 78/79] 15 --- src/components/CycleHoliday.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CycleHoliday.tsx b/src/components/CycleHoliday.tsx index f8b5376c51..c518390740 100644 --- a/src/components/CycleHoliday.tsx +++ b/src/components/CycleHoliday.tsx @@ -33,7 +33,7 @@ export function CycleHoliday(): React.JSX.Element { return ( - Holiday: {holiday} From a244ed126d3a5dc34a65d37668dc1a221a0d2ec3 Mon Sep 17 00:00:00 2001 From: Ethan Fassnacht Date: Thu, 19 Sep 2024 12:50:48 -0400 Subject: [PATCH 79/79] pls --- src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 20fb5082ec..87067b8208 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -24,7 +24,7 @@ function App(): React.JSX.Element {
      -

      Ethan Fassnacht - CISC275

      +

      CISC275

      ); }