The page has a form for adding todos, an empty list, and a counter:
<form id="new-todo">
<label for="todo-text">New todo</label>
<input id="todo-text" name="text" autocomplete="off">
<button>Add</button>
</form>
<ul id="todos"></ul>
<p id="todo-count">0 items left</p>
Write setupTodoList(form, list, counter) to make it work. Listen for the form's submit event, which fires whether the user presses Enter in the field or clicks Add. In the handler, call preventDefault() so the page doesn't reload, then add a todo with the field's text, trimmed, and clear the field. Ignore text that's empty after trimming. The field is form.elements.text.
Each todo is an <li> holding, in order, an <input type="checkbox">, a <span> with the text, and a <button type="button"> reading Delete. Give the checkbox an aria-label of the todo's text and the button an aria-label of Delete followed by the text, so screen reader users can tell the rows apart. Insert text as text, never as HTML.
Checking a todo's checkbox toggles the class done on its <li>. Clicking Delete removes its <li>. counter always shows how many todos aren't done: 0 items left, 1 item left, 2 items left.
setupTodoList(
document.getElementById("new-todo"),
document.getElementById("todos"),
document.getElementById("todo-count"),
);
// type "Buy milk" and press Enter:
// <li><input type="checkbox" aria-label="Buy milk"><span>Buy milk</span><button type="button" aria-label="Delete Buy milk">Delete</button></li>
// the counter reads "1 item left"
const form = document.getElementById("new-todo");
const list = document.getElementById("todos");
setupTodoList(form, list, document.getElementById("todo-count"));
document.getElementById("todo-text").value = "Buy milk";
fire(form, "submit");
expect(list.children).toHaveLength(1);
const item = list.children[0];
expect([...item.children].map((child) => child.tagName)).toEqual(["INPUT", "SPAN", "BUTTON"]);
expect(item.children[0].type).toBe("checkbox");
expect(item.children[1].textContent).toBe("Buy milk");
expect(item.children[2].textContent).toBe("Delete");
expect(item.children[2].type).toBe("button");
const form = document.getElementById("new-todo");
setupTodoList(form, document.getElementById("todos"), document.getElementById("todo-count"));
document.getElementById("todo-text").value = "Walk";
expect(fire(form, "submit").defaultPrevented).toBe(true);
const form = document.getElementById("new-todo");
const list = document.getElementById("todos");
const field = document.getElementById("todo-text");
setupTodoList(form, list, document.getElementById("todo-count"));
field.value = " Walk the dog ";
fire(form, "submit");
expect(list.querySelector("span").textContent).toBe("Walk the dog");
expect(field.value).toBe("");
field.value = " ";
fire(form, "submit");
expect(list.children).toHaveLength(1);
const form = document.getElementById("new-todo");
const list = document.getElementById("todos");
setupTodoList(form, list, document.getElementById("todo-count"));
document.getElementById("todo-text").value = "Buy milk";
fire(form, "submit");
expect(list.querySelector("input").getAttribute("aria-label")).toBe("Buy milk");
expect(list.querySelector("button").getAttribute("aria-label")).toBe("Delete Buy milk");
const form = document.getElementById("new-todo");
const list = document.getElementById("todos");
setupTodoList(form, list, document.getElementById("todo-count"));
document.getElementById("todo-text").value = "A";
fire(form, "submit");
const checkbox = list.querySelector("input");
fire(checkbox, "click");
expect(list.children[0].classList.contains("done")).toBe(true);
fire(checkbox, "click");
expect(list.children[0].classList.contains("done")).toBe(false);
const form = document.getElementById("new-todo");
const list = document.getElementById("todos");
const field = document.getElementById("todo-text");
setupTodoList(form, list, document.getElementById("todo-count"));
field.value = "A";
fire(form, "submit");
field.value = "B";
fire(form, "submit");
fire(list.children[0].querySelector("button"), "click");
expect([...list.querySelectorAll("span")].map((span) => span.textContent)).toEqual(["B"]);
const form = document.getElementById("new-todo");
const list = document.getElementById("todos");
const field = document.getElementById("todo-text");
const counter = document.getElementById("todo-count");
setupTodoList(form, list, counter);
field.value = "A";
fire(form, "submit");
field.value = "B";
fire(form, "submit");
expect(counter.textContent).toBe("2 items left");
fire(list.children[0].querySelector("input"), "click");
expect(counter.textContent).toBe("1 item left");
fire(list.children[1].querySelector("button"), "click");
expect(counter.textContent).toBe("0 items left");
fire(list.children[0].querySelector("input"), "click");
expect(counter.textContent).toBe("1 item left");
const form = document.getElementById("new-todo");
const list = document.getElementById("todos");
setupTodoList(form, list, document.getElementById("todo-count"));
document.getElementById("todo-text").value = "<img src=x onerror=alert(1)>";
fire(form, "submit");
expect(list.querySelector("img")).toBeNull();
expect(list.querySelector("span").textContent).toBe("<img src=x onerror=alert(1)>");
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 Todo list. 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.