Staff at the tea shop write tasting notes for new teas, and they shouldn't lose a note if the tab closes. Build App: a <textarea> labelled Tasting note that saves what's in it to localStorage as you type.
Save under the key tea-note-draft, 500 ms after the typing stops: every change starts the wait again, so a long burst of typing is saved once, at the end. An empty note is saved too, as an empty string.
Under the box, a <p role="status"> says how it's going: Saving… (with the single … character) from a change until it's saved, then Saved. Before the first change it's empty.
When App appears, the box starts with the saved draft, or empty if there isn't one. Loading a draft isn't a change: it doesn't save and the status stays empty. When App leaves the page, no timer is left running.
localStorage.setItem("tea-note-draft", "Malty, with honey.");
<App />
// renders, half a second after typing " Long finish.":
<label for="...">Tasting note</label>
<textarea id="...">Malty, with honey. Long finish.</textarea>
<p role="status">Saved</p>
render(<App />);
expect(screen.getByLabelText("Tasting note")).toHaveValue("");
expect(screen.getByRole("status").textContent).toBe("");
localStorage.setItem("tea-note-draft", "Malty, with honey.");
render(<App />);
expect(screen.getByLabelText("Tasting note")).toHaveValue("Malty, with honey.");
const clock = useFakeTimers();
localStorage.setItem("tea-note-draft", "Malty, with honey.");
render(<App />);
await clock.advance(1000);
expect(screen.getByRole("status").textContent).toBe("");
expect(clock.pending()).toBe(0);
useFakeTimers();
render(<App />);
await user.type(screen.getByLabelText("Tasting note"), "Grassy");
expect(screen.getByRole("status").textContent).toBe("Saving…");
expect(localStorage.getItem("tea-note-draft")).toBe(null);
const clock = useFakeTimers();
render(<App />);
await user.type(screen.getByLabelText("Tasting note"), "Grassy");
await clock.advance(499);
expect(localStorage.getItem("tea-note-draft")).toBe(null);
await clock.advance(1);
expect(localStorage.getItem("tea-note-draft")).toBe("Grassy");
expect(screen.getByRole("status").textContent).toBe("Saved");
const clock = useFakeTimers();
render(<App />);
const note = screen.getByLabelText("Tasting note");
await user.type(note, "Gra");
await clock.advance(400);
await user.type(note, "ssy");
await clock.advance(400);
expect(localStorage.getItem("tea-note-draft")).toBe(null);
expect(screen.getByRole("status").textContent).toBe("Saving…");
await clock.advance(100);
expect(localStorage.getItem("tea-note-draft")).toBe("Grassy");
const clock = useFakeTimers();
render(<App />);
const note = screen.getByLabelText("Tasting note");
await user.type(note, "Grassy");
await clock.advance(500);
await user.type(note, ", sweet");
expect(screen.getByRole("status").textContent).toBe("Saving…");
await clock.advance(500);
expect(localStorage.getItem("tea-note-draft")).toBe("Grassy, sweet");
expect(screen.getByRole("status").textContent).toBe("Saved");
const clock = useFakeTimers();
localStorage.setItem("tea-note-draft", "Too bitter");
render(<App />);
await user.clear(screen.getByLabelText("Tasting note"));
await clock.advance(500);
expect(localStorage.getItem("tea-note-draft")).toBe("");
expect(screen.getByRole("status").textContent).toBe("Saved");
const clock = useFakeTimers();
const { unmount } = render(<App />);
await user.type(screen.getByLabelText("Tasting note"), "Grassy");
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 Autosave a draft. 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.