Skip to the code
cf.

Render a list from data

EasyDOM

The page has an empty <ul id="todos">. Write renderTodos(list, todos): it replaces the list's contents with one <li> per todo, showing its text. A done todo's <li> gets the class done.

Text must appear as text, never as HTML. Calling it again with new data replaces what was there. Render into the list you're given rather than looking one up, so it works for any list.

renderTodos(document.getElementById("todos"), [
  { text: "Buy milk", done: true },
  { text: "Write tests", done: false },
]);
// <ul id="todos"><li class="done">Buy milk</li><li>Write tests</li></ul>

What the tests check

  1. adds one li per todo, in order
    const list = document.getElementById("todos");
    renderTodos(list, [{ text: "a", done: false }, { text: "b", done: false }]);
    expect([...list.children].map((li) => li.tagName)).toEqual(["LI", "LI"]);
    expect([...list.children].map((li) => li.textContent)).toEqual(["a", "b"]);
    
  2. marks done todos with the class done
    const list = document.getElementById("todos");
    renderTodos(list, [{ text: "a", done: true }, { text: "b", done: false }]);
    expect(list.children[0].classList.contains("done")).toBe(true);
    expect(list.children[1].classList.contains("done")).toBe(false);
    
  3. shows text as text, never as HTML
    const list = document.getElementById("todos");
    renderTodos(list, [{ text: "<img src=x onerror=alert(1)>", done: false }]);
    expect(list.querySelector("img")).toBeNull();
    expect(list.children[0].textContent).toBe("<img src=x onerror=alert(1)>");
    
  4. rendering again replaces the old items
    const list = document.getElementById("todos");
    renderTodos(list, [{ text: "a", done: false }, { text: "b", done: false }]);
    renderTodos(list, [{ text: "c", done: false }]);
    expect(list.children).toHaveLength(1);
    expect(list.textContent).toBe("c");
    
  5. an empty array empties the list
    const list = document.getElementById("todos");
    renderTodos(list, [{ text: "a", done: false }]);
    renderTodos(list, []);
    expect(list.children).toHaveLength(0);
    
  6. renders into whichever list it's given
    const other = document.createElement("ol");
    renderTodos(other, [{ text: "x", done: false }]);
    expect(other.children).toHaveLength(1);
    expect(other.textContent).toBe("x");
    expect(document.getElementById("todos").children).toHaveLength(0);
    

Learn more

solution.jsSaved in this browser

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).