Edit src/App.tsx and save. This page will
automatically reload.
+ Hello World
+
Created by Brendon
+
+
+ {/* Step 2: Add image with alt text*/}
+
Check this picture out:
+
+
+
+
+ {/* Step 3: Add a list of 3 items*/}
+
+
Computer
+
Github
+
Discord
+
+
+
+
+ {/* Step 4: Change the background color of the header area */}
+ {/* Changed to vibrant green in the App.css file */}
+
+
+
+
+ {/* Step 5: Add a bootstrap button with the text Log Hello World */}
+ {/* Make the button log Hello World! when clicked */}
+
+
+
+
+
+ {/* Step 6: Put a red-filled rectangle in each column using a div tag with width, height, and backgroundColor styles */}
+
+ return (
+
+
+
+
+
+ );
);
}
diff --git a/src/HtmlCss.test.tsx b/src/HtmlCss.test.tsx
new file mode 100644
index 0000000000..84d76db400
--- /dev/null
+++ b/src/HtmlCss.test.tsx
@@ -0,0 +1,83 @@
+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 heading", () => {
+ render();
+ const heading = screen.getAllByRole("heading");
+ expect(heading[0]).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/arrays.test.ts b/src/arrays.test.ts
new file mode 100644
index 0000000000..c2847517bd
--- /dev/null
+++ b/src/arrays.test.ts
@@ -0,0 +1,273 @@
+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("(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]);
+ expect(bookEndList(NUMBERS_4)).toEqual([]);
+ expect(bookEndList(NUMBERS_5)).toEqual([100, 3]);
+ expect(bookEndList(NUMBERS_6)).toEqual([-100, 200]);
+ });
+
+ 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]);
+ 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("(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]);
+ 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("(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]);
+ 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("(3 pts) 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("(3 pts) 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("(3 pts) 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("(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");
+ expect(makeMath(NUMBERS_4)).toEqual("0=0");
+ expect(makeMath(NUMBERS_7)).toEqual("1000=199+1+550+50+200");
+ });
+
+ //////////////////////////////////
+ // injectPositive
+ 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]);
+ 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]);
+ });
+});
diff --git a/src/arrays.ts b/src/arrays.ts
new file mode 100644
index 0000000000..a8e96ef0eb
--- /dev/null
+++ b/src/arrays.ts
@@ -0,0 +1,111 @@
+/**
+ * NO FOR LOOPS WHILE LOOPS OR RECURSIONR
+ * 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.length === 0 ? []
+ : numbers.length === 1 ? [numbers[0], numbers[0]]
+ : [numbers[0], numbers[numbers.length - 1]]
+ );
+}
+
+/**
+ * Consume an array of numbers, and return a new array where each
+ * number has been tripled (multiplied by 3).
+ */
+export function tripleNumbers(numbers: number[]): number[] {
+ return numbers.map((num: number): number => num * 3);
+}
+
+/**
+ * Consume an array of strings and convert them to integers. If
+ * the number cannot be parsed as an integer, convert it to 0 instead.
+ */
+export function stringsToIntegers(numbers: string[]): number[] {
+ return numbers.map((str: string): number => {
+ const parsed = parseInt(str, 10);
+ return isNaN(parsed) ? 0 : parsed;
+ });
+}
+
+/**
+ * Consume an array of strings and return them as numbers. Note that
+ * the strings MAY have "$" symbols at the beginning, in which case
+ * those should be removed. If the result cannot be parsed as an integer,
+ * convert it to 0 instead.
+ */
+// Remember, you can write functions as lambdas too! They work exactly the same.
+export const removeDollars = (amounts: string[]): number[] => {
+ return amounts.map((str: string): number => {
+ const cleaned = str.startsWith("$") ? str.slice(1) : str;
+ const parsed = parseInt(cleaned, 10);
+ return isNaN(parsed) ? 0 : parsed;
+ });
+};
+
+/**
+ * Consume an array of messages and return a new list of the messages. However, any
+ * string that ends in "!" should be made uppercase. Also, remove any strings that end
+ * in question marks ("?").
+ */
+export const shoutIfExclaiming = (messages: string[]): string[] => {
+ return messages
+ .filter((msg: string): boolean => !msg.endsWith("?"))
+ .map((msg: string): string =>
+ msg.endsWith("!") ? msg.toUpperCase() : msg,
+ );
+};
+
+/**
+ * Consumes an array of words and returns the number of words that are LESS THAN
+ * 4 letters long.
+ */
+export function countShortWords(words: string[]): number {
+ return words.filter((word: string): boolean => word.length < 4).length;
+}
+
+/**
+ * Consumes an array of colors (e.g., 'red', 'purple') and returns true if ALL
+ * the colors are either 'red', 'blue', or 'green'. If an empty list is given,
+ * then return true.
+ */
+export function allRGB(colors: string[]): boolean {
+ return colors.every((color: string): boolean =>
+ ["red", "blue", "green"].includes(color),
+ );
+}
+
+/**
+ * Consumes an array of numbers, and produces a string representation of the
+ * numbers being added together along with their actual sum.
+ *
+ * For instance, the array [1, 2, 3] would become "6=1+2+3".
+ * And the array [] would become "0=0".
+ */
+export function makeMath(addends: number[]): string {
+ const sum = addends.reduce((total, num) => total + num, 0);
+ const expression = addends.length > 0 ? addends.join("+") : "0";
+ return `${sum}=${expression}`;
+}
+
+/**
+ * Consumes an array of numbers and produces a new array of the same numbers,
+ * with one difference. After the FIRST negative number, insert the sum of all
+ * previous numbers in the list. If there are no negative numbers, then append
+ * the sum to the list.
+ *
+ * For instance, the array [1, 9, -5, 7] would become [1, 9, -5, 10, 7]
+ * And the array [1, 9, 7] would become [1, 9, 7, 17]
+ */
+export function injectPositive(values: number[]): number[] {
+ const index = values.findIndex((num) => num < 0);
+ const insertIndex = index === -1 ? values.length : index + 1;
+ const sum = values
+ .slice(0, index === -1 ? values.length : index)
+ .reduce((acc, num) => acc + num, 0);
+ return [...values.slice(0, insertIndex), sum, ...values.slice(insertIndex)];
+}
diff --git a/src/data/questions.json b/src/data/questions.json
new file mode 100644
index 0000000000..0411f30afe
--- /dev/null
+++ b/src/data/questions.json
@@ -0,0 +1,220 @@
+{
+ "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
+ }
+ ],
+ "TRIVIA_QUESTIONS": [
+ {
+ "id": 1,
+ "name": "Mascot",
+ "body": "What is the name of the UD Mascot?",
+ "type": "multiple_choice_question",
+ "options": ["Bluey", "YoUDee", "Charles the Wonder Dog"],
+ "expected": "YoUDee",
+ "points": 7,
+ "published": false
+ },
+ {
+ "id": 2,
+ "name": "Motto",
+ "body": "What is the University of Delaware's motto?",
+ "type": "multiple_choice_question",
+ "options": [
+ "Knowledge is the light of the mind",
+ "Just U Do it",
+ "Nothing, what's the motto with you?"
+ ],
+ "expected": "Knowledge is the light of the mind",
+ "points": 3,
+ "published": false
+ },
+ {
+ "id": 3,
+ "name": "Goats",
+ "body": "How many goats are there usually on the Green?",
+ "type": "multiple_choice_question",
+ "options": [
+ "Zero, why would there be goats on the green?",
+ "18420",
+ "Two"
+ ],
+ "expected": "Two",
+ "points": 10,
+ "published": false
+ }
+ ],
+ "EMPTY_QUESTIONS": [
+ {
+ "id": 1,
+ "name": "Empty 1",
+ "body": "This question is not empty, right?",
+ "type": "multiple_choice_question",
+ "options": ["correct", "it is", "not"],
+ "expected": "correct",
+ "points": 5,
+ "published": true
+ },
+ {
+ "id": 2,
+ "name": "Empty 2",
+ "body": "",
+ "type": "multiple_choice_question",
+ "options": ["this", "one", "is", "not", "empty", "either"],
+ "expected": "one",
+ "points": 5,
+ "published": true
+ },
+ {
+ "id": 3,
+ "name": "Empty 3",
+ "body": "This questions is not empty either!",
+ "type": "short_answer_question",
+ "options": [],
+ "expected": "",
+ "points": 5,
+ "published": true
+ },
+ {
+ "id": 4,
+ "name": "Empty 4",
+ "body": "",
+ "type": "short_answer_question",
+ "options": [],
+ "expected": "Even this one is not empty",
+ "points": 5,
+ "published": true
+ },
+ {
+ "id": 5,
+ "name": "Empty 5 (Actual)",
+ "body": "",
+ "type": "short_answer_question",
+ "options": [],
+ "expected": "",
+ "points": 5,
+ "published": false
+ }
+ ],
+ "SIMPLE_QUESTIONS_2": [
+ {
+ "id": 478,
+ "name": "Students",
+ "body": "How many students are taking CISC275 this semester?",
+ "type": "short_answer_question",
+ "options": [],
+ "expected": "90",
+ "points": 53,
+ "published": true
+ },
+ {
+ "id": 1937,
+ "name": "Importance",
+ "body": "On a scale of 1 to 10, how important is this quiz for them?",
+ "type": "short_answer_question",
+ "options": [],
+ "expected": "10",
+ "points": 47,
+ "published": true
+ },
+ {
+ "id": 479,
+ "name": "Sentience",
+ "body": "Is it technically possible for this quiz to become sentient?",
+ "type": "short_answer_question",
+ "options": [],
+ "expected": "Yes",
+ "points": 40,
+ "published": true
+ },
+ {
+ "id": 777,
+ "name": "Danger",
+ "body": "If this quiz became sentient, would it pose a danger to others?",
+ "type": "short_answer_question",
+ "options": [],
+ "expected": "Yes",
+ "points": 60,
+ "published": true
+ },
+ {
+ "id": 1937,
+ "name": "Listening",
+ "body": "Is this quiz listening to us right now?",
+ "type": "short_answer_question",
+ "options": [],
+ "expected": "Yes",
+ "points": 100,
+ "published": true
+ }
+ ]
+}
diff --git a/src/functions.test.ts b/src/functions.test.ts
new file mode 100644
index 0000000000..3d921f5d64
--- /dev/null
+++ b/src/functions.test.ts
@@ -0,0 +1,59 @@
+import {
+ add3,
+ fahrenheitToCelius,
+ shout,
+ isQuestion,
+ convertYesNo,
+} from "./functions";
+
+describe("Testing the basic functions", () => {
+ test("(3 pts) 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);
+ });
+
+ 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);
+ expect(add3(10, 1, -9)).toBe(11);
+ expect(add3(-9, -100, -4)).toBe(0);
+ expect(add3(-1, -1, 1)).toBe(1);
+ });
+
+ test("(3 pts) 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!");
+ });
+
+ 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);
+ 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);
+ });
+
+ test("(3 pts) 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..e1e647b742
--- /dev/null
+++ b/src/functions.ts
@@ -0,0 +1,50 @@
+/**
+ * Consumes a single temperature in Fahrenheit (a number) and converts to Celsius
+ * using this formula:
+ * C = (F - 32) * 5/9
+ */
+export function fahrenheitToCelius(temperature: number): number {
+ return ((temperature - 32) * 5) / 9;
+}
+
+/**
+ * Consumes three numbers and produces their sum. BUT you should only add a number
+ * if the number is greater than zero.
+ */
+export function add3(first: number, second: number, third: number): number {
+ let sum = 0;
+ if (first > 0) sum += first;
+ if (second > 0) sum += second;
+ if (third > 0) sum += third;
+
+ return sum;
+}
+
+/**
+ * Consumes a string and produces the same string in UPPERCASE and with an exclamation
+ * mark added to the end.
+ */
+export function shout(message: string): string {
+ return message.toUpperCase() + "!";
+}
+
+/**
+ * Consumes a string (a message) and returns a boolean if the string ends in a question
+ * mark. Do not use an `if` statement in solving this question.
+ */
+export function isQuestion(message: string): boolean {
+ return message.endsWith("?");
+}
+
+/**
+ * Consumes a word (a string) and returns either `true`, `false`, or `null`. If the string
+ * is "yes" (upper or lower case), then return `true`. If the string is "no" (again, either
+ * upper or lower case), then return `false`. Otherwise, return `null`.
+ */
+export function convertYesNo(word: string): boolean | null {
+ return (
+ word === "yes" || word === "YES" ? true
+ : word === "no" || word === "NO" ? false
+ : null
+ );
+}
diff --git a/src/interfaces/answer.ts b/src/interfaces/answer.ts
new file mode 100644
index 0000000000..743ee8dff9
--- /dev/null
+++ b/src/interfaces/answer.ts
@@ -0,0 +1,13 @@
+/***
+ * A representation of a students' answer in a quizzing game
+ */
+export interface Answer {
+ /** The ID of the question being answered. */
+ questionId: number;
+ /** The text that the student entered for their answer. */
+ text: string;
+ /** Whether or not the student has submitted this answer. */
+ submitted: boolean;
+ /** Whether or not the students' answer matched the expected. */
+ correct: boolean;
+}
diff --git a/src/interfaces/question.ts b/src/interfaces/question.ts
new file mode 100644
index 0000000000..5def48f2f7
--- /dev/null
+++ b/src/interfaces/question.ts
@@ -0,0 +1,22 @@
+/** QuestionType influences how a question is asked and what kinds of answers are possible */
+export type QuestionType = "multiple_choice_question" | "short_answer_question";
+
+/** A representation of a Question in a quizzing application */
+export interface Question {
+ /** A unique identifier for the question */
+ id: number;
+ /** The human-friendly title of the question */
+ name: string;
+ /** The instructions and content of the Question */
+ body: string;
+ /** The kind of Question; influences how the user answers and what options are displayed */
+ type: QuestionType;
+ /** The possible answers for a Question (for Multiple Choice questions) */
+ options: string[];
+ /** The actually correct answer expected */
+ expected: string;
+ /** How many points this question is worth, roughly indicating its importance and difficulty */
+ points: number;
+ /** Whether or not this question is ready to display to students */
+ published: boolean;
+}
diff --git a/src/nested.test.ts b/src/nested.test.ts
new file mode 100644
index 0000000000..61b0282318
--- /dev/null
+++ b/src/nested.test.ts
@@ -0,0 +1,919 @@
+import { Question } from "./interfaces/question";
+import {
+ getPublishedQuestions,
+ getNonEmptyQuestions,
+ findQuestion,
+ removeQuestion,
+ getNames,
+ makeAnswers,
+ publishAll,
+ addNewQuestion,
+ renameQuestionById,
+ editOption,
+} from "./nested";
+import testQuestionData from "./data/questions.json";
+import backupQuestionData from "./data/questions.json";
+
+const {
+ BLANK_QUESTIONS,
+ SIMPLE_QUESTIONS,
+ TRIVIA_QUESTIONS,
+ EMPTY_QUESTIONS,
+ SIMPLE_QUESTIONS_2,
+}: Record =
+ // Typecast the test data that we imported to be a record matching
+ // strings to the question list
+ testQuestionData as Record;
+
+// We have backup versions of the data to make sure all changes are immutable
+const {
+ BLANK_QUESTIONS: BACKUP_BLANK_QUESTIONS,
+ SIMPLE_QUESTIONS: BACKUP_SIMPLE_QUESTIONS,
+ TRIVIA_QUESTIONS: BACKUP_TRIVIA_QUESTIONS,
+ EMPTY_QUESTIONS: BACKUP_EMPTY_QUESTIONS,
+ SIMPLE_QUESTIONS_2: BACKUP_SIMPLE_QUESTIONS_2,
+}: Record = backupQuestionData as Record<
+ string,
+ Question[]
+>;
+
+const NEW_BLANK_QUESTION = {
+ id: 142,
+ name: "A new question",
+ body: "",
+ type: "short_answer_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+};
+
+const NEW_TRIVIA_QUESTION = {
+ id: 449,
+ name: "Colors",
+ body: "",
+ type: "multiple_choice_question",
+ options: [],
+ expected: "",
+ /*body: "The official colors of UD are Blue and ...?",
+ type: "multiple_choice_question",
+ options: ["Black, like my soul", "Blue again, we're tricky.", "#FFD200"],
+ expected: "#FFD200",*/
+ points: 1,
+ published: false,
+};
+
+////////////////////////////////////////////
+// Actual tests
+
+describe("Testing the Question[] functions", () => {
+ //////////////////////////////////
+ // getPublishedQuestions
+
+ test("(3 pts) Testing the getPublishedQuestions function", () => {
+ expect(getPublishedQuestions(BLANK_QUESTIONS)).toEqual([]);
+ expect(getPublishedQuestions(SIMPLE_QUESTIONS)).toEqual([
+ {
+ id: 1,
+ name: "Addition",
+ body: "What is 2+2?",
+ type: "short_answer_question",
+ options: [],
+ expected: "4",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 5,
+ name: "Colors",
+ body: "Which of these is a color?",
+ type: "multiple_choice_question",
+ options: ["red", "apple", "firetruck"],
+ expected: "red",
+ points: 1,
+ published: true,
+ },
+ ]);
+ expect(getPublishedQuestions(TRIVIA_QUESTIONS)).toEqual([]);
+ expect(getPublishedQuestions(SIMPLE_QUESTIONS_2)).toEqual(
+ BACKUP_SIMPLE_QUESTIONS_2,
+ );
+ expect(getPublishedQuestions(EMPTY_QUESTIONS)).toEqual([
+ {
+ id: 1,
+ name: "Empty 1",
+ body: "This question is not empty, right?",
+ type: "multiple_choice_question",
+ options: ["correct", "it is", "not"],
+ expected: "correct",
+ points: 5,
+ published: true,
+ },
+ {
+ id: 2,
+ name: "Empty 2",
+ body: "",
+ type: "multiple_choice_question",
+ options: ["this", "one", "is", "not", "empty", "either"],
+ expected: "one",
+ points: 5,
+ published: true,
+ },
+ {
+ id: 3,
+ name: "Empty 3",
+ body: "This questions is not empty either!",
+ type: "short_answer_question",
+ options: [],
+ expected: "",
+ points: 5,
+ published: true,
+ },
+ {
+ id: 4,
+ name: "Empty 4",
+ body: "",
+ type: "short_answer_question",
+ options: [],
+ expected: "Even this one is not empty",
+ points: 5,
+ published: true,
+ },
+ ]);
+ });
+
+ test("(3 pts) Testing the getNonEmptyQuestions functions", () => {
+ expect(getNonEmptyQuestions(BLANK_QUESTIONS)).toEqual([]);
+ expect(getNonEmptyQuestions(SIMPLE_QUESTIONS)).toEqual(
+ BACKUP_SIMPLE_QUESTIONS,
+ );
+ expect(getNonEmptyQuestions(TRIVIA_QUESTIONS)).toEqual(
+ BACKUP_TRIVIA_QUESTIONS,
+ );
+ expect(getNonEmptyQuestions(SIMPLE_QUESTIONS_2)).toEqual(
+ BACKUP_SIMPLE_QUESTIONS_2,
+ );
+ expect(getNonEmptyQuestions(EMPTY_QUESTIONS)).toEqual([
+ {
+ id: 1,
+ name: "Empty 1",
+ body: "This question is not empty, right?",
+ type: "multiple_choice_question",
+ options: ["correct", "it is", "not"],
+ expected: "correct",
+ points: 5,
+ published: true,
+ },
+ {
+ id: 2,
+ name: "Empty 2",
+ body: "",
+ type: "multiple_choice_question",
+ options: ["this", "one", "is", "not", "empty", "either"],
+ expected: "one",
+ points: 5,
+ published: true,
+ },
+ {
+ id: 3,
+ name: "Empty 3",
+ body: "This questions is not empty either!",
+ type: "short_answer_question",
+ options: [],
+ expected: "",
+ points: 5,
+ published: true,
+ },
+ {
+ id: 4,
+ name: "Empty 4",
+ body: "",
+ type: "short_answer_question",
+ options: [],
+ expected: "Even this one is not empty",
+ points: 5,
+ published: true,
+ },
+ ]);
+ });
+
+ test("(3 pts) Testing the findQuestion function", () => {
+ expect(findQuestion(BLANK_QUESTIONS, 1)).toEqual(BLANK_QUESTIONS[0]);
+ expect(findQuestion(BLANK_QUESTIONS, 47)).toEqual(BLANK_QUESTIONS[1]);
+ expect(findQuestion(BLANK_QUESTIONS, 2)).toEqual(BLANK_QUESTIONS[2]);
+ expect(findQuestion(BLANK_QUESTIONS, 3)).toEqual(null);
+ expect(findQuestion(SIMPLE_QUESTIONS, 1)).toEqual(SIMPLE_QUESTIONS[0]);
+ expect(findQuestion(SIMPLE_QUESTIONS, 2)).toEqual(SIMPLE_QUESTIONS[1]);
+ expect(findQuestion(SIMPLE_QUESTIONS, 5)).toEqual(SIMPLE_QUESTIONS[2]);
+ expect(findQuestion(SIMPLE_QUESTIONS, 9)).toEqual(SIMPLE_QUESTIONS[3]);
+ expect(findQuestion(SIMPLE_QUESTIONS, 6)).toEqual(null);
+ expect(findQuestion(SIMPLE_QUESTIONS_2, 478)).toEqual(
+ SIMPLE_QUESTIONS_2[0],
+ );
+ expect(findQuestion([], 0)).toEqual(null);
+ });
+
+ test("(3 pts) Testing the removeQuestion", () => {
+ expect(removeQuestion(BLANK_QUESTIONS, 1)).toEqual([
+ {
+ id: 47,
+ name: "My New Question",
+ body: "",
+ type: "multiple_choice_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 2,
+ name: "Question 2",
+ body: "",
+ type: "short_answer_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ ]);
+ expect(removeQuestion(BLANK_QUESTIONS, 47)).toEqual([
+ {
+ id: 1,
+ name: "Question 1",
+ body: "",
+ type: "multiple_choice_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 2,
+ name: "Question 2",
+ body: "",
+ type: "short_answer_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ ]);
+ expect(removeQuestion(BLANK_QUESTIONS, 2)).toEqual([
+ {
+ id: 1,
+ name: "Question 1",
+ body: "",
+ type: "multiple_choice_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 47,
+ name: "My New Question",
+ body: "",
+ type: "multiple_choice_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ ]);
+ expect(removeQuestion(SIMPLE_QUESTIONS, 9)).toEqual([
+ {
+ id: 1,
+ name: "Addition",
+ body: "What is 2+2?",
+ type: "short_answer_question",
+ options: [],
+ expected: "4",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 2,
+ name: "Letters",
+ body: "What is the last letter of the English alphabet?",
+ type: "short_answer_question",
+ options: [],
+ expected: "Z",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 5,
+ name: "Colors",
+ body: "Which of these is a color?",
+ type: "multiple_choice_question",
+ options: ["red", "apple", "firetruck"],
+ expected: "red",
+ points: 1,
+ published: true,
+ },
+ ]);
+ expect(removeQuestion(SIMPLE_QUESTIONS, 5)).toEqual([
+ {
+ id: 1,
+ name: "Addition",
+ body: "What is 2+2?",
+ type: "short_answer_question",
+ options: [],
+ expected: "4",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 2,
+ name: "Letters",
+ body: "What is the last letter of the English alphabet?",
+ type: "short_answer_question",
+ options: [],
+ expected: "Z",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 9,
+ name: "Shapes",
+ body: "What shape can you make with one line?",
+ type: "multiple_choice_question",
+ options: ["square", "triangle", "circle"],
+ expected: "circle",
+ points: 2,
+ published: false,
+ },
+ ]);
+ });
+
+ test("(3 pts) Testing the getNames function", () => {
+ expect(getNames(BLANK_QUESTIONS)).toEqual([
+ "Question 1",
+ "My New Question",
+ "Question 2",
+ ]);
+ expect(getNames(SIMPLE_QUESTIONS)).toEqual([
+ "Addition",
+ "Letters",
+ "Colors",
+ "Shapes",
+ ]);
+ expect(getNames(TRIVIA_QUESTIONS)).toEqual([
+ "Mascot",
+ "Motto",
+ "Goats",
+ ]);
+ expect(getNames(SIMPLE_QUESTIONS_2)).toEqual([
+ "Students",
+ "Importance",
+ "Sentience",
+ "Danger",
+ "Listening",
+ ]);
+ expect(getNames(EMPTY_QUESTIONS)).toEqual([
+ "Empty 1",
+ "Empty 2",
+ "Empty 3",
+ "Empty 4",
+ "Empty 5 (Actual)",
+ ]);
+ });
+
+ test("(3 pts) Testing the makeAnswers function", () => {
+ expect(makeAnswers(BLANK_QUESTIONS)).toEqual([
+ { questionId: 1, correct: false, text: "", submitted: false },
+ { questionId: 47, correct: false, text: "", submitted: false },
+ { questionId: 2, correct: false, text: "", submitted: false },
+ ]);
+ expect(makeAnswers(SIMPLE_QUESTIONS)).toEqual([
+ { questionId: 1, correct: false, text: "", submitted: false },
+ { questionId: 2, correct: false, text: "", submitted: false },
+ { questionId: 5, correct: false, text: "", submitted: false },
+ { questionId: 9, correct: false, text: "", submitted: false },
+ ]);
+ expect(makeAnswers(TRIVIA_QUESTIONS)).toEqual([
+ { questionId: 1, correct: false, text: "", submitted: false },
+ { questionId: 2, correct: false, text: "", submitted: false },
+ { questionId: 3, correct: false, text: "", submitted: false },
+ ]);
+ expect(makeAnswers(SIMPLE_QUESTIONS_2)).toEqual([
+ { questionId: 478, correct: false, text: "", submitted: false },
+ { questionId: 1937, correct: false, text: "", submitted: false },
+ { questionId: 479, correct: false, text: "", submitted: false },
+ { questionId: 777, correct: false, text: "", submitted: false },
+ { questionId: 1937, correct: false, text: "", submitted: false },
+ ]);
+ expect(makeAnswers(EMPTY_QUESTIONS)).toEqual([
+ { questionId: 1, correct: false, text: "", submitted: false },
+ { questionId: 2, correct: false, text: "", submitted: false },
+ { questionId: 3, correct: false, text: "", submitted: false },
+ { questionId: 4, correct: false, text: "", submitted: false },
+ { questionId: 5, correct: false, text: "", submitted: false },
+ ]);
+ });
+
+ test("(3 pts) Testing the publishAll function", () => {
+ expect(publishAll(BLANK_QUESTIONS)).toEqual([
+ {
+ id: 1,
+ name: "Question 1",
+ body: "",
+ type: "multiple_choice_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 47,
+ name: "My New Question",
+ body: "",
+ type: "multiple_choice_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 2,
+ name: "Question 2",
+ body: "",
+ type: "short_answer_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: true,
+ },
+ ]);
+ expect(publishAll(SIMPLE_QUESTIONS)).toEqual([
+ {
+ id: 1,
+ name: "Addition",
+ body: "What is 2+2?",
+ type: "short_answer_question",
+ options: [],
+ expected: "4",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 2,
+ name: "Letters",
+ body: "What is the last letter of the English alphabet?",
+ type: "short_answer_question",
+ options: [],
+ expected: "Z",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 5,
+ name: "Colors",
+ body: "Which of these is a color?",
+ type: "multiple_choice_question",
+ options: ["red", "apple", "firetruck"],
+ expected: "red",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 9,
+ name: "Shapes",
+ body: "What shape can you make with one line?",
+ type: "multiple_choice_question",
+ options: ["square", "triangle", "circle"],
+ expected: "circle",
+ points: 2,
+ published: true,
+ },
+ ]);
+ expect(publishAll(TRIVIA_QUESTIONS)).toEqual([
+ {
+ id: 1,
+ name: "Mascot",
+ body: "What is the name of the UD Mascot?",
+ type: "multiple_choice_question",
+ options: ["Bluey", "YoUDee", "Charles the Wonder Dog"],
+ expected: "YoUDee",
+ points: 7,
+ published: true,
+ },
+ {
+ id: 2,
+ name: "Motto",
+ body: "What is the University of Delaware's motto?",
+ type: "multiple_choice_question",
+ options: [
+ "Knowledge is the light of the mind",
+ "Just U Do it",
+ "Nothing, what's the motto with you?",
+ ],
+ expected: "Knowledge is the light of the mind",
+ points: 3,
+ published: true,
+ },
+ {
+ id: 3,
+ name: "Goats",
+ body: "How many goats are there usually on the Green?",
+ type: "multiple_choice_question",
+ options: [
+ "Zero, why would there be goats on the green?",
+ "18420",
+ "Two",
+ ],
+ expected: "Two",
+ points: 10,
+ published: true,
+ },
+ ]);
+ expect(publishAll(EMPTY_QUESTIONS)).toEqual([
+ {
+ id: 1,
+ name: "Empty 1",
+ body: "This question is not empty, right?",
+ type: "multiple_choice_question",
+ options: ["correct", "it is", "not"],
+ expected: "correct",
+ points: 5,
+ published: true,
+ },
+ {
+ id: 2,
+ name: "Empty 2",
+ body: "",
+ type: "multiple_choice_question",
+ options: ["this", "one", "is", "not", "empty", "either"],
+ expected: "one",
+ points: 5,
+ published: true,
+ },
+ {
+ id: 3,
+ name: "Empty 3",
+ body: "This questions is not empty either!",
+ type: "short_answer_question",
+ options: [],
+ expected: "",
+ points: 5,
+ published: true,
+ },
+ {
+ id: 4,
+ name: "Empty 4",
+ body: "",
+ type: "short_answer_question",
+ options: [],
+ expected: "Even this one is not empty",
+ points: 5,
+ published: true,
+ },
+ {
+ id: 5,
+ name: "Empty 5 (Actual)",
+ body: "",
+ type: "short_answer_question",
+ options: [],
+ expected: "",
+ points: 5,
+ published: true,
+ },
+ ]);
+ expect(publishAll(SIMPLE_QUESTIONS_2)).toEqual(SIMPLE_QUESTIONS_2);
+ });
+
+ test("(3 pts) Testing the addNewQuestion function", () => {
+ expect(
+ addNewQuestion([], 142, "A new question", "short_answer_question"),
+ ).toEqual([NEW_BLANK_QUESTION]);
+ expect(
+ addNewQuestion(
+ BLANK_QUESTIONS,
+ 142,
+ "A new question",
+ "short_answer_question",
+ ),
+ ).toEqual([...BLANK_QUESTIONS, NEW_BLANK_QUESTION]);
+ expect(
+ addNewQuestion(
+ TRIVIA_QUESTIONS,
+ 449,
+ "Colors",
+ "multiple_choice_question",
+ ),
+ ).toEqual([...TRIVIA_QUESTIONS, NEW_TRIVIA_QUESTION]);
+ });
+
+ test("(3 pts) Testing the renameQuestionById function", () => {
+ expect(renameQuestionById(BLANK_QUESTIONS, 1, "New Name")).toEqual([
+ {
+ id: 1,
+ name: "New Name",
+ body: "",
+ type: "multiple_choice_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 47,
+ name: "My New Question",
+ body: "",
+ type: "multiple_choice_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 2,
+ name: "Question 2",
+ body: "",
+ type: "short_answer_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ ]);
+ expect(renameQuestionById(BLANK_QUESTIONS, 47, "Another Name")).toEqual(
+ [
+ {
+ id: 1,
+ name: "Question 1",
+ body: "",
+ type: "multiple_choice_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 47,
+ name: "Another Name",
+ body: "",
+ type: "multiple_choice_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 2,
+ name: "Question 2",
+ body: "",
+ type: "short_answer_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ ],
+ );
+ expect(renameQuestionById(SIMPLE_QUESTIONS, 5, "Colours")).toEqual([
+ {
+ id: 1,
+ name: "Addition",
+ body: "What is 2+2?",
+ type: "short_answer_question",
+ options: [],
+ expected: "4",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 2,
+ name: "Letters",
+ body: "What is the last letter of the English alphabet?",
+ type: "short_answer_question",
+ options: [],
+ expected: "Z",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 5,
+ name: "Colours",
+ body: "Which of these is a color?",
+ type: "multiple_choice_question",
+ options: ["red", "apple", "firetruck"],
+ expected: "red",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 9,
+ name: "Shapes",
+ body: "What shape can you make with one line?",
+ type: "multiple_choice_question",
+ options: ["square", "triangle", "circle"],
+ expected: "circle",
+ points: 2,
+ published: false,
+ },
+ ]);
+ });
+
+ test("(3 pts) Testing the editOption function", () => {
+ expect(editOption(BLANK_QUESTIONS, 1, -1, "NEW OPTION")).toEqual([
+ {
+ id: 1,
+ name: "Question 1",
+ body: "",
+ type: "multiple_choice_question",
+ options: ["NEW OPTION"],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 47,
+ name: "My New Question",
+ body: "",
+ type: "multiple_choice_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 2,
+ name: "Question 2",
+ body: "",
+ type: "short_answer_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ ]);
+ expect(editOption(BLANK_QUESTIONS, 47, -1, "Another option")).toEqual([
+ {
+ id: 1,
+ name: "Question 1",
+ body: "",
+ type: "multiple_choice_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 47,
+ name: "My New Question",
+ body: "",
+ type: "multiple_choice_question",
+ options: ["Another option"],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 2,
+ name: "Question 2",
+ body: "",
+ type: "short_answer_question",
+ options: [],
+ expected: "",
+ points: 1,
+ published: false,
+ },
+ ]);
+ expect(editOption(SIMPLE_QUESTIONS, 5, -1, "newspaper")).toEqual([
+ {
+ id: 1,
+ name: "Addition",
+ body: "What is 2+2?",
+ type: "short_answer_question",
+ options: [],
+ expected: "4",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 2,
+ name: "Letters",
+ body: "What is the last letter of the English alphabet?",
+ type: "short_answer_question",
+ options: [],
+ expected: "Z",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 5,
+ name: "Colors",
+ body: "Which of these is a color?",
+ type: "multiple_choice_question",
+ options: ["red", "apple", "firetruck", "newspaper"],
+ expected: "red",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 9,
+ name: "Shapes",
+ body: "What shape can you make with one line?",
+ type: "multiple_choice_question",
+ options: ["square", "triangle", "circle"],
+ expected: "circle",
+ points: 2,
+ published: false,
+ },
+ ]);
+ expect(editOption(SIMPLE_QUESTIONS, 5, 0, "newspaper")).toEqual([
+ {
+ id: 1,
+ name: "Addition",
+ body: "What is 2+2?",
+ type: "short_answer_question",
+ options: [],
+ expected: "4",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 2,
+ name: "Letters",
+ body: "What is the last letter of the English alphabet?",
+ type: "short_answer_question",
+ options: [],
+ expected: "Z",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 5,
+ name: "Colors",
+ body: "Which of these is a color?",
+ type: "multiple_choice_question",
+ options: ["newspaper", "apple", "firetruck"],
+ expected: "red",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 9,
+ name: "Shapes",
+ body: "What shape can you make with one line?",
+ type: "multiple_choice_question",
+ options: ["square", "triangle", "circle"],
+ expected: "circle",
+ points: 2,
+ published: false,
+ },
+ ]);
+
+ expect(editOption(SIMPLE_QUESTIONS, 5, 2, "newspaper")).toEqual([
+ {
+ id: 1,
+ name: "Addition",
+ body: "What is 2+2?",
+ type: "short_answer_question",
+ options: [],
+ expected: "4",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 2,
+ name: "Letters",
+ body: "What is the last letter of the English alphabet?",
+ type: "short_answer_question",
+ options: [],
+ expected: "Z",
+ points: 1,
+ published: false,
+ },
+ {
+ id: 5,
+ name: "Colors",
+ body: "Which of these is a color?",
+ type: "multiple_choice_question",
+ options: ["red", "apple", "newspaper"],
+ expected: "red",
+ points: 1,
+ published: true,
+ },
+ {
+ id: 9,
+ name: "Shapes",
+ body: "What shape can you make with one line?",
+ type: "multiple_choice_question",
+ options: ["square", "triangle", "circle"],
+ expected: "circle",
+ points: 2,
+ published: false,
+ },
+ ]);
+ });
+
+ afterEach(() => {
+ expect(BLANK_QUESTIONS).toEqual(BACKUP_BLANK_QUESTIONS);
+ expect(SIMPLE_QUESTIONS).toEqual(BACKUP_SIMPLE_QUESTIONS);
+ expect(TRIVIA_QUESTIONS).toEqual(BACKUP_TRIVIA_QUESTIONS);
+ expect(SIMPLE_QUESTIONS_2).toEqual(BACKUP_SIMPLE_QUESTIONS_2);
+ expect(EMPTY_QUESTIONS).toEqual(BACKUP_EMPTY_QUESTIONS);
+ });
+});
diff --git a/src/nested.ts b/src/nested.ts
new file mode 100644
index 0000000000..cddb850dc7
--- /dev/null
+++ b/src/nested.ts
@@ -0,0 +1,280 @@
+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 questions
+ .filter((question: Question): boolean => question.published)
+ .map((question: Question): Question => {
+ const copiedOptions: string[] = question.options.map(
+ (option: string): string => option,
+ );
+
+ return {
+ id: question.id,
+ name: question.name,
+ type: question.type,
+ body: question.body,
+ expected: question.expected,
+ options: copiedOptions,
+ points: question.points,
+ published: question.published,
+ };
+ });
+}
+
+/**
+ * Consumes an array of questions and returns a new array of only the questions that are
+ * considered "non-empty". An empty question has an empty string for its `body` and
+ * `expected`, and an empty array for its `options`.
+ */
+export function getNonEmptyQuestions(questions: Question[]): Question[] {
+ return questions
+ .filter(
+ (question: Question): boolean =>
+ question.body !== "" ||
+ question.expected !== "" ||
+ question.options.length > 0,
+ )
+ .map((question: Question): Question => {
+ const copiedOptions: string[] = question.options.map(
+ (option: string): string => option,
+ );
+
+ return {
+ id: question.id,
+ name: question.name,
+ type: question.type,
+ body: question.body,
+ expected: question.expected,
+ options: copiedOptions,
+ points: question.points,
+ published: question.published,
+ };
+ });
+}
+
+/***
+ * Consumes an array of questions and returns the question with the given `id`. If the
+ * question is not found, return `null` instead.
+ */
+export function findQuestion(
+ questions: Question[],
+ id: number,
+): Question | null {
+ const found = questions.find(
+ (question: Question): boolean => question.id === id,
+ );
+
+ if (!found) {
+ return null;
+ }
+
+ const copiedOptions: string[] = found.options.map(
+ (option: string): string => option,
+ );
+
+ const copiedQuestion: Question = {
+ id: found.id,
+ name: found.name,
+ type: found.type,
+ body: found.body,
+ expected: found.expected,
+ options: copiedOptions,
+ points: found.points,
+ published: found.published,
+ };
+
+ return copiedQuestion;
+}
+
+/**
+ * Consumes an array of questions and returns a new array that does not contain the question
+ * with the given `id`.
+ * Hint: use filter
+ */
+export function removeQuestion(questions: Question[], id: number): Question[] {
+ return questions
+ .filter((question: Question): boolean => question.id !== id)
+ .map((question: Question): Question => {
+ const copiedOptions: string[] = question.options.map(
+ (option: string): string => option,
+ );
+
+ return {
+ id: question.id,
+ name: question.name,
+ type: question.type,
+ body: question.body,
+ expected: question.expected,
+ options: copiedOptions,
+ points: question.points,
+ published: question.published,
+ };
+ });
+}
+
+/***
+ * Consumes an array of questions and returns a new array containing just the names of the
+ * questions, as an array.
+ * Do not modify the input array.
+ */
+export function getNames(questions: Question[]): string[] {
+ return questions.map((question: Question): string => question.name);
+}
+
+/**
+ * Consumes an array of Questions and produces a corresponding array of
+ * Answers. Each Question gets its own Answer, copying over the `id` as the `questionId`,
+ * making the `text` an empty string, and using false for both `submitted` and `correct`.
+ */
+
+export function makeAnswers(questions: Question[]): Answer[] {
+ return questions.map((question: Question): Answer => {
+ return {
+ questionId: question.id,
+ text: "",
+ submitted: false,
+ correct: false,
+ };
+ });
+}
+
+/***
+ * Consumes an array of Questions and produces a new array of questions, where
+ * each question is now published, regardless of its previous published status.
+ * Hint: as usual, do not modify the input questions array
+ */
+export function publishAll(questions: Question[]): Question[] {
+ return questions.map((question: Question): Question => {
+ const copiedOptions: string[] = question.options.map(
+ (option: string): string => option,
+ );
+
+ return {
+ id: question.id,
+ name: question.name,
+ type: question.type,
+ body: question.body,
+ expected: question.expected,
+ options: copiedOptions,
+ points: question.points,
+ published: true,
+ };
+ });
+}
+
+/***
+ * 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.
+ * Hint: as usual, do not modify the input questions array
+ */
+
+import { makeBlankQuestion } from "./objects"; // Needed when re using the makeBlankQuestion from objects file (mental note)
+
+export function addNewQuestion(
+ questions: Question[],
+ id: number,
+ name: string,
+ type: QuestionType,
+): Question[] {
+ // Deep copy each question to avoid reference sharing
+ const copiedQuestions: Question[] = questions.map(
+ (question: Question): Question => {
+ return {
+ ...question,
+ options: [...question.options],
+ };
+ },
+ );
+
+ // Create a new blank question using the provided helper
+ const newBlankQuestion: Question = makeBlankQuestion(id, name, type);
+
+ // Return a new array with all copied questions + the new blank one
+ return [...copiedQuestions, newBlankQuestion];
+}
+
+/***
+ * 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`.
+ * Hint: as usual, do not modify the input questions array,
+ * to make a new copy of a question with some changes, use the ... operator
+ */
+export function renameQuestionById(
+ questions: Question[],
+ targetId: number,
+ newName: string,
+): Question[] {
+ return questions.map((question: Question): Question => {
+ const copiedOptions: string[] = question.options.map(
+ (option: string): string => option,
+ );
+
+ if (question.id === targetId) {
+ return {
+ ...question,
+ name: newName,
+ options: copiedOptions,
+ };
+ } else {
+ return {
+ ...question,
+ options: copiedOptions,
+ };
+ }
+ });
+}
+
+/**
+ * Consumes an array of Questions and produces a new array of Questions, where all
+ * the Questions are the same EXCEPT for the one with the given `targetId`. That
+ * Question should be the same EXCEPT that its `option` array should have a new element.
+ * If the `targetOptionIndex` is -1, the `newOption` should be added to the end of the list.
+ * Otherwise, it should *replace* the existing element at the `targetOptionIndex`.
+ *
+ * Remember, if a function starts getting too complicated, think about how a helper function
+ * can make it simpler! Break down complicated tasks into little pieces.
+ *
+ * Hint: you need to use the ... operator for both the question and the options array
+ */
+export function editOption(
+ questions: Question[],
+ targetId: number,
+ targetOptionIndex: number,
+ newOption: string,
+): Question[] {
+ return questions.map((question: Question): Question => {
+ const copiedOptions: string[] = question.options.map(
+ (option: string): string => option,
+ );
+
+ if (question.id !== targetId) {
+ return {
+ ...question,
+ options: copiedOptions,
+ };
+ }
+
+ let updatedOptions: string[];
+
+ if (targetOptionIndex === -1) {
+ updatedOptions = [...copiedOptions, newOption];
+ } else {
+ updatedOptions = copiedOptions.map(
+ (option: string, index: number): string =>
+ index === targetOptionIndex ? newOption : option,
+ );
+ }
+
+ return {
+ ...question,
+ options: updatedOptions,
+ };
+ });
+}
diff --git a/src/objects.test.ts b/src/objects.test.ts
new file mode 100644
index 0000000000..4d3117405d
--- /dev/null
+++ b/src/objects.test.ts
@@ -0,0 +1,299 @@
+import { Question } from "./interfaces/question";
+import {
+ makeBlankQuestion,
+ isCorrect,
+ 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("(3 pts) 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("(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);
+ 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("(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);
+ 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("(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");
+ expect(toShortForm(SHAPE_QUESTION)).toEqual("9: Shapes");
+ expect(toShortForm(BLANK_QUESTIONS[1])).toEqual("47: My New Que");
+ });
+
+ ///////////////////////////////////
+ // toMarkdown
+ test("(3 pts) 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("(3 pts) 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("(3 pts) 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("(3 pts) 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("(3 pts) 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("(3 pts) 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..2b650eafb3
--- /dev/null
+++ b/src/objects.ts
@@ -0,0 +1,167 @@
+import { Question, QuestionType } from "./interfaces/question";
+
+/**
+ * Create a new blank question with the given `id`, `name`, and `type. The `body` and
+ * `expected` should be empty strings, the `options` should be an empty list, the `points`
+ * should default to 1, and `published` should default to false.
+ */
+export function makeBlankQuestion(
+ id: number,
+ name: string,
+ type: QuestionType,
+): Question {
+ return {
+ id,
+ name,
+ type,
+ body: "",
+ expected: "",
+ options: [],
+ points: 1,
+ published: false,
+ };
+}
+
+
+/**
+ * Consumes a question and a potential `answer`, and returns whether or not
+ * the `answer` is correct. You should check that the `answer` is equal to
+ * the `expected`, ignoring capitalization and trimming any whitespace.
+ *
+ * HINT: Look up the `trim` and `toLowerCase` functions.
+ */
+export function isCorrect(question: Question, answer: string): boolean {
+ return (
+ answer.trim().toLowerCase() === question.expected.trim().toLowerCase()
+ );
+}
+
+/**
+ * Consumes a question and a potential `answer`, and returns whether or not
+ * the `answer` is valid (but not necessarily correct). For a `short_answer_question`,
+ * any answer is valid. But for a `multiple_choice_question`, the `answer` must
+ * be exactly one of the options.
+ */
+export function isValid(question: Question, answer: string): boolean {
+ if (question.type === "short_answer_question") {
+ return true;
+ }
+ return question.options.includes(answer);
+}
+
+/**
+ * Consumes a question and produces a string representation combining the
+ * `id` and first 10 characters of the `name`. The two strings should be
+ * separated by ": ". So for example, the question with id 9 and the
+ * name "My First Question" would become "9: My First Q".
+ */
+export function toShortForm(question: Question): string {
+ return `${question.id}: ${question.name.slice(0, 10)}`;
+}
+
+/**
+ * Consumes a question and returns a formatted string representation as follows:
+ * - The first line should be a hash sign, a space, and then the `name`
+ * - The second line should be the `body`
+ * - If the question is a `multiple_choice_question`, then the following lines
+ * need to show each option on its line, preceded by a dash and space.
+ *
+ * The example below might help, but don't include the border!
+ * ----------Example-------------
+ * |# Name |
+ * |The body goes here! |
+ * |- Option 1 |
+ * |- Option 2 |
+ * |- Option 3 |
+ * ------------------------------
+ * Check the unit tests for more examples of what this looks like!
+ */
+export function toMarkdown(question: Question): string {
+ const header = `# ${question.name}`;
+ const body = question.body;
+
+ const options =
+ question.type === "multiple_choice_question" ?
+ question.options.map((opt) => `- ${opt}`).join("\n")
+ : "";
+
+ return [header, body, options].filter((line) => line !== "").join("\n");
+}
+
+/**
+ * Return a new version of the given question, except the name should now be
+ * `newName`.
+ */
+export function renameQuestion(question: Question, newName: string): Question {
+ return {
+ ...question,
+ name: newName,
+ };
+}
+
+/**
+ * Return a new version of the given question, except the `published` field
+ * should be inverted. If the question was not published, now it should be
+ * published; if it was published, now it should be not published.
+ */
+export function publishQuestion(question: Question): Question {
+ return {
+ ...question,
+ published: !question.published,
+ };
+}
+
+/**
+ * Create a new question based on the old question, copying over its `body`, `type`,
+ * `options`, `expected`, and `points` without changes. The `name` should be copied
+ * over as "Copy of ORIGINAL NAME" (e.g., so "Question 1" would become "Copy of Question 1").
+ * The `published` field should be reset to false.
+ */
+export function duplicateQuestion(id: number, oldQuestion: Question): Question {
+ return {
+ ...oldQuestion,
+ id,
+ name: `Copy of ${oldQuestion.name}`,
+ published: false,
+ };
+}
+
+/**
+ * Return a new version of the given question, with the `newOption` added to
+ * the list of existing `options`. Remember that the new Question MUST have
+ * its own separate copy of the `options` list, rather than the same reference
+ * to the original question's list!
+ * Check out the subsection about "Nested Fields" for more information.
+ */
+export function addOption(question: Question, newOption: string): Question {
+ return {
+ ...question,
+ options: [...question.options, newOption],
+ };
+}
+
+/**
+ * Consumes an id, name, and two questions, and produces a new question.
+ * The new question will use the `body`, `type`, `options`, and `expected` of the
+ * `contentQuestion`. The second question will provide the `points`.
+ * The `published` status should be set to false.
+ * Notice that the second Question is provided as just an object with a `points`
+ * field; but the function call would be the same as if it were a `Question` type!
+ */
+export function mergeQuestion(
+ id: number,
+ name: string,
+ contentQuestion: Question,
+ { points }: { points: number },
+): Question {
+ return {
+ id,
+ name,
+ body: contentQuestion.body,
+ type: contentQuestion.type,
+ options: [...contentQuestion.options],
+ expected: contentQuestion.expected,
+ points,
+ published: false,
+ };
+}
diff --git a/src/text.test.tsx b/src/text.test.tsx
new file mode 100644
index 0000000000..f99a063e76
--- /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 texts = screen.getAllByText(/Hello World/);
+ expect(texts.length).toBeGreaterThanOrEqual(1);
+});