Skip to content
5 changes: 5 additions & 0 deletions public/tasks/task-components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Task - Components

Version: 0.0.1

Fix some components that are using state incorrectly.
12 changes: 12 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -15,6 +19,14 @@ function App(): React.JSX.Element {
<img src="logo192.png" alt="React Logo" />
</header>
<hr></hr>
{<DoubleHalf></DoubleHalf>}
<hr></hr>
<ChooseTeam></ChooseTeam>
<hr></hr>
<ColoredBox></ColoredBox>
<hr></hr>
<ShoveBox></ShoveBox>
<hr></hr>
<Counter></Counter>
<hr />
<RevealAnswer></RevealAnswer>
Expand Down
2 changes: 1 addition & 1 deletion src/HtmlCss.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<App />);
const header = screen.getByRole("heading");
const header = screen.getByRole("heading", { level: 1 });
expect(header).toBeInTheDocument();
});

Expand Down
81 changes: 81 additions & 0 deletions src/bad-components/ChooseTeam.test.tsx
Original file line number Diff line number Diff line change
@@ -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(<ChooseTeam />);
});
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);
});
});
54 changes: 54 additions & 0 deletions src/bad-components/ChooseTeam.tsx
Original file line number Diff line number Diff line change
@@ -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<string[]>(PEOPLE);
const [team, setTeam] = useState<string[]>([]);

function chooseMember(newMember: string) {
if (!team.includes(newMember)) {
setTeam([...team, newMember]);
}
}

function clearTeam() {
setTeam([]);
}

return (
<div>
<h3>Choose Team</h3>
<Row>
<Col>
{allOptions.map((option: string) => (
<div key={option} style={{ marginBottom: "4px" }}>
Add{" "}
<Button
onClick={() => chooseMember(option)}
size="sm"
>
{option}
</Button>
</div>
))}
</Col>
<Col>
<strong>Team:</strong>
{team.map((member: string) => (
<li key={member}>{member}</li>
))}
<Button onClick={clearTeam}>Clear Team</Button>
</Col>
</Row>
</div>
);
}
37 changes: 37 additions & 0 deletions src/bad-components/ColoredBox.test.tsx
Original file line number Diff line number Diff line change
@@ -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(<ColoredBox />);
});
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",
});
});
});
61 changes: 61 additions & 0 deletions src/bad-components/ColoredBox.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Button
onClick={() => {
setColorIndex((1 + colorIndex) % COLORS.length);
}}
>
Next Color
</Button>
);
}

function ColorPreview({
colorIndex,
}: {
colorIndex: number;
}): React.JSX.Element {
return (
<div
data-testid="colored-box"
style={{
width: "50px",
height: "50px",
backgroundColor: COLORS[colorIndex],
display: "inline-block",
verticalAlign: "bottom",
marginLeft: "5px",
}}
></div>
);
}

export function ColoredBox(): React.JSX.Element {
const [colorIndex, setColorIndex] = useState<number>(DEFAULT_COLOR_INDEX);

return (
<div>
<h3>Colored Box</h3>
<span>The current color is: {COLORS[colorIndex]}</span>
<div>
<ChangeColor
colorIndex={colorIndex}
setColorIndex={setColorIndex}
></ChangeColor>
<ColorPreview colorIndex={colorIndex}></ColorPreview>
</div>
</div>
);
}
72 changes: 72 additions & 0 deletions src/bad-components/DoubleHalf.test.tsx
Original file line number Diff line number Diff line change
@@ -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(<DoubleHalf />);
});
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();
});
});
46 changes: 46 additions & 0 deletions src/bad-components/DoubleHalf.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Button
onClick={() => {
setDhValue(2 * dhValue);
}}
>
Double
</Button>
);
}

function Halver({ dhValue, setDhValue }: DoubleHalfProps): React.JSX.Element {
return (
<Button
onClick={() => {
setDhValue(0.5 * dhValue);
}}
>
Halve
</Button>
);
}

export function DoubleHalf(): React.JSX.Element {
const [dhValue, setDhValue] = useState<number>(10);

return (
<div>
<h3>Double Half</h3>
<div>
The current value is: <span>{dhValue}</span>
</div>
<Doubler dhValue={dhValue} setDhValue={setDhValue}></Doubler>
<Halver dhValue={dhValue} setDhValue={setDhValue}></Halver>
</div>
);
}
Loading