Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 68 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,78 @@
import React from "react";
import "./App.css";
import {
createQuiz,
updateDifficulty,
addQuestion,
createStudent,
addScore,
calculateAverage,
createStudentRecord,
} from "./objects";

function App() {
console.log(
"Created Quiz:",
createQuiz(
"JavaScript Basics",
["What is a closure?", "What is 'this'?"],
"medium",
),
);

console.log(
"Updated Quiz Difficulty:",
updateDifficulty(
createQuiz(
"JavaScript Basics",
["What is a closure?", "What is 'this'?"],
"medium",
),
"hard",
),
);

console.log(
"Quiz with New Question:",
addQuestion(
createQuiz(
"JavaScript Basics",
["What is a closure?", "What is 'this'?"],
"medium",
),
"Explain event delegation.",
),
);

console.log(
"Created Student:",
createStudent("001", "Daniah", [85, 90, 78]),
);

console.log(
"Student with New Score:",
addScore(createStudent("001", "Daniah", [85, 90, 78]), 95),
);

console.log(
"Student Average Score:",
calculateAverage(createStudent("001", "Daniah", [85, 90, 78])),
);

console.log(
"Student Record:",
createStudentRecord([
createStudent("001", "Daniah", [85, 90, 78]),
createStudent("002", "Kejae", [88, 92, 80]),
]),
);

function App(): React.JSX.Element {
return (
<div className="App">
<header className="App-header">
UD CISC275 with React Hooks and TypeScript
CISC275 - Task 6 - Objects in TypeScript
</header>
<p>
Edit <code>src/App.tsx</code> and save. This page will
automatically reload.
</p>
<p>Check the console for function outputs.</p>
</div>
);
}
Expand Down
163 changes: 98 additions & 65 deletions src/objects.ts
Original file line number Diff line number Diff line change
@@ -1,121 +1,154 @@
import { Question, QuestionType } from "./interfaces/question";
export function createQuiz(
title: string,
questions: string[],
difficulty: string,
): { title: string; questions: string[]; difficulty: string } {
return { title, questions, difficulty };
}

export function updateDifficulty(
quiz: { title: string; questions: string[]; difficulty: string },
newDifficulty: string,
) {
return { ...quiz, difficulty: newDifficulty };
}

export function addQuestion(
quiz: { title: string; questions: string[]; difficulty: string },
newQuestion: string,
) {
return { ...quiz, questions: [...quiz.questions, newQuestion] };
}

export function createStudent(
id: string,
name: string,
scores: number[],
): { id: string; name: string; scores: number[] } {
return { id, name, scores };
}

export function addScore(
student: { id: string; name: string; scores: number[] },
newScore: number,
) {
return { ...student, scores: [...student.scores, newScore] };
}

export function calculateAverage(student: {
id: string;
name: string;
scores: number[];
}) {
const total = student.scores.reduce((sum, score) => sum + score, 0);
return student.scores.length > 0 ? total / student.scores.length : 0;
}

export function createStudentRecord(
students: { id: string; name: string; scores: number[] }[],
): Record<string, { id: string; name: string; scores: number[] }> {
return Object.fromEntries(students.map((student) => [student.id, student]));
}

/**
* Create a new blank question with the given `id`, `name`, and `type. The `body` and
* `expected` should be empty strings, the `options` should be an empty list, the `points`
* should default to 1, and `published` should default to false.
*/
export function makeBlankQuestion(
id: number,
name: string,
type: QuestionType
type: QuestionType,
): Question {
return {};
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.
* Check if the provided `answer` is correct.
* The `answer` must match `expected`, ignoring case and extra spaces.
*/
export function isCorrect(question: Question, answer: string): boolean {
return false;
return (
question.expected.trim().toLowerCase() === answer.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.
* Determine if an answer is valid:
* - Any answer is valid for a `short_answer_question`.
* - The answer must be one of the options for a `multiple_choice_question`.
*/
export function isValid(question: Question, answer: string): boolean {
return false;
return question.type === "short_answer_question" ?
true
: 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".
* Convert a question into a short form: `"id: first 10 chars of name"`
*/
export function toShortForm(question: Question): string {
return "";
return `${question.id}: ${question.name.substring(0, 10)}`;
}

/**
* Consumes a question and returns a formatted string representation as follows:
* - The first line should be a hash sign, a space, and then the `name`
* - The second line should be the `body`
* - If the question is a `multiple_choice_question`, then the following lines
* need to show each option on its line, preceded by a dash and space.
*
* The example below might help, but don't include the border!
* ----------Example-------------
* |# Name |
* |The body goes here! |
* |- Option 1 |
* |- Option 2 |
* |- Option 3 |
* ------------------------------
* Check the unit tests for more examples of what this looks like!
* Convert a question into a Markdown-like format.
*/
export function toMarkdown(question: Question): string {
return "";
let markdown = `# ${question.name}\n${question.body}`;
if (question.type === "multiple_choice_question") {
markdown += `\n${question.options.map((option) => `- ${option}`).join("\n")}`;
}
return markdown;
}

/**
* Return a new version of the given question, except the name should now be
* `newName`.
* Return a new question with a modified name.
*/
export function renameQuestion(question: Question, newName: string): Question {
return 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.
* Toggle the `published` status of a question.
*/
export function publishQuestion(question: Question): Question {
return 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.
* Duplicate a question, setting the name as `"Copy of ORIGINAL NAME"` and `published` to `false`.
*/
export function duplicateQuestion(id: number, oldQuestion: Question): Question {
return oldQuestion;
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.
* Add a new option to the `options` list while maintaining immutability.
*/
export function addOption(question: Question, newOption: string): Question {
return 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!
* Merge two questions: `contentQuestion` provides content, `points` comes from the second argument.
* The new question is unpublished.
*/
export function mergeQuestion(
id: number,
name: string,
contentQuestion: Question,
{ points }: { points: number }
{ points }: { points: number },
): Question {
return contentQuestion;
return { ...contentQuestion, id, name, points, published: false };
}