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 24b6c6b434..2a0d8de397 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 { ChooseTeam } from "./bad-components/ChooseTeam";
function App(): React.JSX.Element {
return (
@@ -15,6 +19,14 @@ function App(): React.JSX.Element {
+ {}
+
+
+
+
+
+
+
diff --git a/src/HtmlCss.test.tsx b/src/HtmlCss.test.tsx
index 320cb97524..4ef6ef2fdf 100644
--- a/src/HtmlCss.test.tsx
+++ b/src/HtmlCss.test.tsx
@@ -6,7 +6,7 @@ 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");
+ const header = screen.getByRole("heading", { level: 1 });
expect(header).toBeInTheDocument();
});
diff --git a/src/bad-components/ChooseTeam.test.tsx b/src/bad-components/ChooseTeam.test.tsx
new file mode 100644
index 0000000000..66eee4be70
--- /dev/null
+++ b/src/bad-components/ChooseTeam.test.tsx
@@ -0,0 +1,81 @@
+import React, { act } from "react";
+import { render, screen } from "@testing-library/react";
+import { ChooseTeam } from "./ChooseTeam";
+
+describe("ChooseTeam Component tests", () => {
+ beforeEach(() => {
+ render();
+ });
+ test("(2 pts) The initial team is empty", () => {
+ const currentTeam = screen.queryAllByRole("listitem");
+ expect(currentTeam).toHaveLength(0);
+ });
+ test("(2 pts) There are 7 buttons.", () => {
+ const adders = screen.queryAllByRole("button");
+ expect(adders).toHaveLength(7);
+ });
+ test("(2 pts) Clicking first team member works", async () => {
+ const first = screen.queryAllByRole("button")[0];
+ await act(async () => {
+ first.click();
+ });
+ const currentTeam = screen.queryAllByRole("listitem");
+ expect(currentTeam).toHaveLength(1);
+ expect(currentTeam[0].textContent).toEqual(first.textContent);
+ });
+ test("(2 pts) Clicking the third team member works", async () => {
+ const third = screen.queryAllByRole("button")[2];
+ await act(async () => {
+ third.click();
+ });
+ const currentTeam = screen.queryAllByRole("listitem");
+ expect(currentTeam).toHaveLength(1);
+ expect(currentTeam[0].textContent).toEqual(third.textContent);
+ });
+ test("(2 pts) Clicking three team members works", async () => {
+ const [, second, third, , fifth] = screen.queryAllByRole("button");
+ 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("(2 pts) Clearing the team works", async () => {
+ const [, second, third, fourth, fifth, , clear] =
+ screen.queryAllByRole("button");
+ 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);
+ await act(async () => {
+ clear.click();
+ });
+ currentTeam = screen.queryAllByRole("listitem");
+ expect(currentTeam).toHaveLength(0);
+ 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
new file mode 100644
index 0000000000..9745a392d9
--- /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(): React.JSX.Element {
+ const [allOptions, setAllOptions] = useState(PEOPLE);
+ const [team, setTeam] = useState([]);
+
+ function chooseMember(newMember: string) {
+ if (!team.includes(newMember)) {
+ setTeam([...team, newMember]);
+ }
+ }
+
+ function clearTeam() {
+ setTeam([]);
+ }
+
+ return (
+
+
Choose Team
+
+
+ {allOptions.map((option: string) => (
+
+ Add{" "}
+
+
+ ))}
+
+
+ Team:
+ {team.map((member: string) => (
+ {member}
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/bad-components/ColoredBox.test.tsx b/src/bad-components/ColoredBox.test.tsx
new file mode 100644
index 0000000000..5762afefb6
--- /dev/null
+++ b/src/bad-components/ColoredBox.test.tsx
@@ -0,0 +1,37 @@
+import React, { act } from "react";
+import { render, screen } from "@testing-library/react";
+import { ColoredBox } from "./ColoredBox";
+
+describe("ColoredBox Component tests", () => {
+ beforeEach(() => {
+ render();
+ });
+ test("(2 pts) The ColoredBox is initially red.", () => {
+ const box = screen.getByTestId("colored-box");
+ expect(box).toHaveStyle({ backgroundColor: "red" });
+ });
+ test("(2 pts) There is a button", () => {
+ expect(screen.getByRole("button")).toBeInTheDocument();
+ });
+ test("(2 pts) Clicking the button advances the color.", async () => {
+ const nextColor = screen.getByRole("button");
+ await act(async () => {
+ nextColor.click();
+ });
+ expect(screen.getByTestId("colored-box")).toHaveStyle({
+ backgroundColor: "blue",
+ });
+ await act(async () => {
+ nextColor.click();
+ });
+ expect(screen.getByTestId("colored-box")).toHaveStyle({
+ backgroundColor: "green",
+ });
+ await act(async () => {
+ nextColor.click();
+ });
+ expect(screen.getByTestId("colored-box")).toHaveStyle({
+ backgroundColor: "red",
+ });
+ });
+});
diff --git a/src/bad-components/ColoredBox.tsx b/src/bad-components/ColoredBox.tsx
new file mode 100644
index 0000000000..f737916a95
--- /dev/null
+++ b/src/bad-components/ColoredBox.tsx
@@ -0,0 +1,61 @@
+import React, { useState } from "react";
+import { Button } from "react-bootstrap";
+
+export const COLORS = ["red", "blue", "green"];
+const DEFAULT_COLOR_INDEX = 0;
+
+function ChangeColor({
+ colorIndex,
+ setColorIndex,
+}: {
+ colorIndex: number;
+ setColorIndex: (index: number) => void;
+}): React.JSX.Element {
+ return (
+
+ );
+}
+
+function ColorPreview({
+ colorIndex,
+}: {
+ colorIndex: number;
+}): React.JSX.Element {
+ return (
+
+ );
+}
+
+export function ColoredBox(): React.JSX.Element {
+ const [colorIndex, setColorIndex] = useState(DEFAULT_COLOR_INDEX);
+
+ return (
+
+
Colored Box
+
The current color is: {COLORS[colorIndex]}
+
+
+
+
+
+ );
+}
diff --git a/src/bad-components/DoubleHalf.test.tsx b/src/bad-components/DoubleHalf.test.tsx
new file mode 100644
index 0000000000..9b2a031acf
--- /dev/null
+++ b/src/bad-components/DoubleHalf.test.tsx
@@ -0,0 +1,72 @@
+import React, { act } from "react";
+import { render, screen } from "@testing-library/react";
+import { DoubleHalf } from "./DoubleHalf";
+
+describe("DoubleHalf Component tests", () => {
+ beforeEach(() => {
+ render();
+ });
+ 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("(2 pts) There are Double and Halve buttons", () => {
+ expect(
+ screen.getByRole("button", { name: /Double/i }),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole("button", { name: /Halve/i }),
+ ).toBeInTheDocument();
+ });
+ test("(2 pts) You can double the number.", async () => {
+ const double = screen.getByRole("button", { name: /Double/i });
+ await act(async () => {
+ double.click();
+ });
+ expect(screen.getByText("20")).toBeInTheDocument();
+ expect(screen.queryByText("10")).not.toBeInTheDocument();
+ });
+ test("(2 pts) You can halve the number.", async () => {
+ const halve = screen.getByRole("button", { name: /Halve/i });
+ await act(async () => {
+ halve.click();
+ });
+ expect(screen.getByText("5")).toBeInTheDocument();
+ expect(screen.queryByText("10")).not.toBeInTheDocument();
+ });
+ 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 });
+ await act(async () => {
+ double.click();
+ });
+ expect(screen.getByText("20")).toBeInTheDocument();
+ expect(screen.queryByText("10")).not.toBeInTheDocument();
+ await act(async () => {
+ double.click();
+ });
+ expect(screen.getByText("40")).toBeInTheDocument();
+ expect(screen.queryByText("20")).not.toBeInTheDocument();
+ await act(async () => {
+ halve.click();
+ });
+ expect(screen.getByText("20")).toBeInTheDocument();
+ expect(screen.queryByText("10")).not.toBeInTheDocument();
+ await act(async () => {
+ halve.click();
+ });
+ expect(screen.getByText("10")).toBeInTheDocument();
+ expect(screen.queryByText("20")).not.toBeInTheDocument();
+ await act(async () => {
+ halve.click();
+ });
+ expect(screen.getByText("5")).toBeInTheDocument();
+ expect(screen.queryByText("10")).not.toBeInTheDocument();
+ 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
new file mode 100644
index 0000000000..6da4aa3e53
--- /dev/null
+++ b/src/bad-components/DoubleHalf.tsx
@@ -0,0 +1,46 @@
+import React, { useState } from "react";
+import { Button } from "react-bootstrap";
+
+interface DoubleHalfProps {
+ dhValue: number;
+ setDhValue: (value: number) => void;
+}
+
+function Doubler({ dhValue, setDhValue }: DoubleHalfProps): React.JSX.Element {
+ return (
+
+ );
+}
+
+function Halver({ dhValue, setDhValue }: DoubleHalfProps): React.JSX.Element {
+ return (
+
+ );
+}
+
+export function DoubleHalf(): React.JSX.Element {
+ const [dhValue, setDhValue] = useState(10);
+
+ return (
+
+
Double Half
+
+ The current value is: {dhValue}
+
+
+
+
+ );
+}
diff --git a/src/bad-components/ShoveBox.test.tsx b/src/bad-components/ShoveBox.test.tsx
new file mode 100644
index 0000000000..e89abf2751
--- /dev/null
+++ b/src/bad-components/ShoveBox.test.tsx
@@ -0,0 +1,37 @@
+import React, { act } from "react";
+import { render, screen } from "@testing-library/react";
+import { ShoveBox } from "./ShoveBox";
+
+describe("ShoveBox Component tests", () => {
+ beforeEach(() => {
+ render();
+ });
+ test("(2 pts) The MoveableBox is initially nearby.", () => {
+ const box = screen.getByTestId("moveable-box");
+ expect(box).toHaveStyle({ marginLeft: "10px" });
+ });
+ test("(2 pts) There is a button", () => {
+ expect(screen.getByRole("button")).toBeInTheDocument();
+ });
+ test("(2 pts) Clicking the button moves the box.", async () => {
+ const shoveBox = screen.getByRole("button");
+ await act(async () => {
+ shoveBox.click();
+ });
+ expect(screen.getByTestId("moveable-box")).toHaveStyle({
+ marginLeft: "14px",
+ });
+ await act(async () => {
+ shoveBox.click();
+ });
+ expect(screen.getByTestId("moveable-box")).toHaveStyle({
+ marginLeft: "18px",
+ });
+ await act(async () => {
+ shoveBox.click();
+ });
+ expect(screen.getByTestId("moveable-box")).toHaveStyle({
+ marginLeft: "22px",
+ });
+ });
+});
diff --git a/src/bad-components/ShoveBox.tsx b/src/bad-components/ShoveBox.tsx
new file mode 100644
index 0000000000..4f01c4ef3a
--- /dev/null
+++ b/src/bad-components/ShoveBox.tsx
@@ -0,0 +1,49 @@
+import React, { useState } from "react";
+import { Button } from "react-bootstrap";
+
+function ShoveBoxButton({
+ position,
+ setPosition,
+}: {
+ position: number;
+ setPosition: (newPosition: number) => void;
+}) {
+ return (
+
+ );
+}
+
+export function ShoveBox(): React.JSX.Element {
+ const [position, setPosition] = useState(10);
+
+ return (
+
+
Shove Box
+
The box is at: {position}
+
+
+ );
+}