The tea shop's admin page uploads product photos, and the server takes at most three at a time. Build App, a simulation of that: an Add button, and a list of progress bars that each fill from 0 to 100 over 2 seconds.
Each click on Add adds a bar at the end: an element with role="progressbar", aria-label Bar 1, Bar 2, and so on, aria-valuemin="0", aria-valuemax="100", and aria-valuenow with its value, starting at 0. Draw it however you like (a div whose width is the value as a percentage works).
Every 100 ms, each filling bar goes up by 5, so a bar is full (100) 2 seconds after it starts, and then stays at 100. At most three bars fill at once: the first three bars that aren't full yet, in the order they were added. The others wait at 0, and the earliest waiting bar starts on the first 100 ms step after a filling one becomes full. A bar added while fewer than three are filling starts on the next step.
Run no timer while there's nothing to fill (no bars, or every bar full), and leave none running when the component is removed. It must fill at the same pace in Strict Mode, which runs state updater functions twice in development.
// Add clicked 4 times, 1 second later:
<div role="progressbar" aria-label="Bar 1" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50"></div>
<div role="progressbar" aria-label="Bar 2" ... aria-valuenow="50"></div>
<div role="progressbar" aria-label="Bar 3" ... aria-valuenow="50"></div>
<div role="progressbar" aria-label="Bar 4" ... aria-valuenow="0"></div>
const values = () => screen.queryAllByRole("progressbar").map((bar) => Number(bar.getAttribute("aria-valuenow")));
const add = (times = 1) => Array.from({ length: times }).reduce((done) => done.then(() => user.click(screen.getByRole("button", { name: "Add" }))), Promise.resolve());
const clock = useFakeTimers();
render(<App />);
expect(screen.getByRole("button", { name: "Add" })).toBeInTheDocument();
expect(values()).toEqual([]);
expect(clock.pending()).toBe(0);
const values = () => screen.queryAllByRole("progressbar").map((bar) => Number(bar.getAttribute("aria-valuenow")));
const add = (times = 1) => Array.from({ length: times }).reduce((done) => done.then(() => user.click(screen.getByRole("button", { name: "Add" }))), Promise.resolve());
useFakeTimers();
render(<App />);
await add(2);
const bar = screen.getByRole("progressbar", { name: "Bar 1" });
expect(bar).toHaveAttribute("aria-valuenow", "0");
expect(bar).toHaveAttribute("aria-valuemin", "0");
expect(bar).toHaveAttribute("aria-valuemax", "100");
expect(screen.getByRole("progressbar", { name: "Bar 2" })).toBeInTheDocument();
const values = () => screen.queryAllByRole("progressbar").map((bar) => Number(bar.getAttribute("aria-valuenow")));
const add = (times = 1) => Array.from({ length: times }).reduce((done) => done.then(() => user.click(screen.getByRole("button", { name: "Add" }))), Promise.resolve());
const clock = useFakeTimers();
render(<App />);
await add();
await clock.advance(100);
expect(values()).toEqual([5]);
await clock.advance(900);
expect(values()).toEqual([50]);
await clock.advance(1000);
expect(values()).toEqual([100]);
await clock.advance(1000);
expect(values()).toEqual([100]);
const values = () => screen.queryAllByRole("progressbar").map((bar) => Number(bar.getAttribute("aria-valuenow")));
const add = (times = 1) => Array.from({ length: times }).reduce((done) => done.then(() => user.click(screen.getByRole("button", { name: "Add" }))), Promise.resolve());
const clock = useFakeTimers();
render(<App />);
await add(5);
await clock.advance(1000);
expect(values()).toEqual([50, 50, 50, 0, 0]);
const values = () => screen.queryAllByRole("progressbar").map((bar) => Number(bar.getAttribute("aria-valuenow")));
const add = (times = 1) => Array.from({ length: times }).reduce((done) => done.then(() => user.click(screen.getByRole("button", { name: "Add" }))), Promise.resolve());
const clock = useFakeTimers();
render(<App />);
await add(4);
await clock.advance(2000);
expect(values()).toEqual([100, 100, 100, 0]);
await clock.advance(100);
expect(values()).toEqual([100, 100, 100, 5]);
await clock.advance(1900);
expect(values()).toEqual([100, 100, 100, 100]);
const values = () => screen.queryAllByRole("progressbar").map((bar) => Number(bar.getAttribute("aria-valuenow")));
const add = (times = 1) => Array.from({ length: times }).reduce((done) => done.then(() => user.click(screen.getByRole("button", { name: "Add" }))), Promise.resolve());
const clock = useFakeTimers();
render(<App />);
await add();
await clock.advance(1000);
await add();
await clock.advance(1000);
expect(values()).toEqual([100, 50]);
const values = () => screen.queryAllByRole("progressbar").map((bar) => Number(bar.getAttribute("aria-valuenow")));
const add = (times = 1) => Array.from({ length: times }).reduce((done) => done.then(() => user.click(screen.getByRole("button", { name: "Add" }))), Promise.resolve());
const clock = useFakeTimers();
render(<App />);
await add(3);
await clock.advance(1000);
await add(2);
await clock.advance(1000);
expect(values()).toEqual([100, 100, 100, 0, 0]);
await clock.advance(500);
expect(values()).toEqual([100, 100, 100, 25, 25]);
const values = () => screen.queryAllByRole("progressbar").map((bar) => Number(bar.getAttribute("aria-valuenow")));
const add = (times = 1) => Array.from({ length: times }).reduce((done) => done.then(() => user.click(screen.getByRole("button", { name: "Add" }))), Promise.resolve());
const clock = useFakeTimers();
render(<App />);
await add(2);
await clock.advance(2000);
expect(clock.pending()).toBe(0);
await add();
await clock.advance(1000);
expect(values()).toEqual([100, 100, 50]);
const values = () => screen.queryAllByRole("progressbar").map((bar) => Number(bar.getAttribute("aria-valuenow")));
const add = (times = 1) => Array.from({ length: times }).reduce((done) => done.then(() => user.click(screen.getByRole("button", { name: "Add" }))), Promise.resolve());
const clock = useFakeTimers();
render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
await add(4);
await clock.advance(1000);
expect(values()).toEqual([50, 50, 50, 0]);
const values = () => screen.queryAllByRole("progressbar").map((bar) => Number(bar.getAttribute("aria-valuenow")));
const add = (times = 1) => Array.from({ length: times }).reduce((done) => done.then(() => user.click(screen.getByRole("button", { name: "Add" }))), Promise.resolve());
const clock = useFakeTimers();
const { unmount } = render(<App />);
await add(4);
await clock.advance(500);
unmount();
expect(clock.pending()).toBe(0);
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with Progress bars with a queue. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.