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 { React Logo
+ {} +
+ +
+ +
+ +

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} +
    + +
    +
    +
    + ); +}