Skip to the code
cf.

Closest common ancestor of two nodes

MediumDOM

Write commonAncestor(a, b), which returns the deepest node that contains both a and b. A node counts as containing itself: if a contains b the answer is a, and commonAncestor(a, a) is a.

a and b can be any nodes, text nodes included, and needn't be in the page: for two nodes in the same detached tree, the answer is in that tree. When they're in different trees, return null. Climb with parentNode, which, unlike parentElement, goes all the way up to document.

// <section id="s1"><p id="p1">One</p><p id="p2">Two</p></section>
commonAncestor(p1, p2);                            // section#s1
commonAncestor(p1.firstChild, p1);                 // p#p1
commonAncestor(document.head, document.body);      // <html>
commonAncestor(p1, document.createElement("div")); // null

What the tests check

  1. finds the parent of two siblings
    const result = commonAncestor(document.getElementById("p1"), document.getElementById("p2"));
    expect(result).toBe(document.getElementById("s1"));
    
  2. finds an ancestor several levels up
    const result = commonAncestor(document.getElementById("em1"), document.getElementById("p3"));
    expect(result).toBe(document.getElementById("main"));
    
  3. returns the node itself when given it twice
    const p1 = document.getElementById("p1");
    expect(commonAncestor(p1, p1)).toBe(p1);
    
  4. returns the outer node when one contains the other, in either order
    const s1 = document.getElementById("s1");
    const em1 = document.getElementById("em1");
    expect(commonAncestor(s1, em1)).toBe(s1);
    expect(commonAncestor(em1, s1)).toBe(s1);
    
  5. works with text nodes
    const p1 = document.getElementById("p1");
    const em1 = document.getElementById("em1");
    expect(commonAncestor(p1.firstChild, em1.firstChild)).toBe(p1);
    
  6. climbs all the way to document
    expect(commonAncestor(document.head, document.body)).toBe(document.documentElement);
    expect(commonAncestor(document, document.getElementById("p1"))).toBe(document);
    const note = document.createComment("before <html>");
    document.insertBefore(note, document.documentElement);
    expect(commonAncestor(note, document.body)).toBe(document);
    
  7. works inside a detached tree
    const div = document.createElement("div");
    const a = document.createElement("span");
    const b = document.createElement("span");
    div.append(a, b);
    expect(commonAncestor(a, b)).toBe(div);
    
  8. returns null for nodes in different trees
    const detached = document.createElement("div");
    expect(commonAncestor(document.getElementById("p1"), detached)).toBeNull();
    expect(commonAncestor(document.createElement("span"), detached)).toBeNull();
    

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