Fix invisible text in fragment context terms

Added logic to skip invisible text nodes when traversing
the DOM. Fixed traversal functions to be able to detect
block boundaries. Added helper class to keep track of text
inside the same block boundaries. Added tests.
diff --git a/src/fragment-generation-utils.js b/src/fragment-generation-utils.js
index e5525b3..2424ba1 100644
--- a/src/fragment-generation-utils.js
+++ b/src/fragment-generation-utils.js
@@ -267,9 +267,16 @@
   if (!walker) {
     return undefined;
   }
-  const map = createForwardOverrideMap(walker);
-  const origin = node;
 
+  const isFinished = new Set();
+  // if the range starts after the last child of an element node
+  // don't visit its subtree because it's not included in the range
+  if(range.startContainer.nodeType === Node.ELEMENT_NODE 
+    && range.startOffset === range.startContainer.childNodes.length) {
+    isFinished.add(range.startContainer);
+  }
+  const origin = node;
+  const textAccumulator = new BlockTextAccumulator(range, true);
   // tempRange monitors whether we've exhausted our search space yet.
   const tempRange = range.cloneRange();
   while (!tempRange.collapsed && node != null) {
@@ -281,17 +288,14 @@
     } else {
       tempRange.setStartBefore(node);
     }
+    // Add node to accumulator to keep track of text inside the current block boundaries
+    textAccumulator.appendNode(node);
 
-    // If |node| is a block node, then we've hit a block boundary.
-    if (isBlock(node)) {
-      const candidate = range.cloneRange();
-      candidate.setEnd(tempRange.startContainer, tempRange.startOffset);
-      const trimmed = candidate.toString().trim();
-      if (trimmed.length > 0) {
-        return trimmed;
+    // if the accumulator found a non empty block boundary we've got our search space
+    if (textAccumulator.textInBlock !== null) {
+      return textAccumulator.textInBlock;
       }
-    }
-    node = forwardTraverse(walker, map);
+    node = forwardTraverse(walker, isFinished);
   }
   return undefined;
 };
@@ -312,8 +316,16 @@
   if (!walker) {
     return undefined;
   }
-  const visited = new Set();
+  const isFinished = new Set();
+  // if the range ends before the first child of an element node
+  // don't visit its subtree because it's not included in the range
+  if(range.endContainer.nodeType === Node.ELEMENT_NODE 
+    && range.endOffset === 0) {
+    isFinished.add(range.endContainer);
+  }
+
   const origin = node;
+  const textAccumulator = new BlockTextAccumulator(range, false);
 
   // tempRange monitors whether we've exhausted our search space yet.
   const tempRange = range.cloneRange();
@@ -327,16 +339,15 @@
       tempRange.setEndAfter(node);
     }
 
-    // If |node| is a block node, then we've hit a block boundary.
-    if (isBlock(node)) {
-      const candidate = range.cloneRange();
-      candidate.setStart(tempRange.endContainer, tempRange.endOffset);
-      const trimmed = candidate.toString().trim();
-      if (trimmed.length > 0) {
-        return trimmed;
+    // Add node to accumulator to keep track of text inside the current block boundaries
+    textAccumulator.appendNode(node);
+
+    // if the accumulator found a non empty block boundary we've got our search space
+    if (textAccumulator.textInBlock !== null) {
+      return textAccumulator.textInBlock;
       }
-    }
-    node = backwardTraverse(walker, visited, origin);
+
+    node = backwardTraverse(walker, isFinished);
   }
   return undefined;
 };
@@ -954,6 +965,81 @@
 };
 
 /**
+ * Helper class to calculate the text in the first or last non empty block boundary in a given range
+ */
+const BlockTextAccumulator = class {
+  /**
+   * @param {Range} searchRange - the range for which the text in the last or first non emty block boundary will be calculated 
+   * @param {boolean} isForwardTraversal - true if searchRange if nodes in searchRange will be forward traversed  
+   */
+  constructor(searchRange, isForwardTraversal) {
+    this.searchRange = searchRange
+    this.insertFunc = isForwardTraversal ? Array.prototype.push : Array.prototype.unshift
+    this.textFound = false
+    this.textNodes = []
+    this.textInBlock = null
+  }
+  /**
+   * Adds the next node in the search space range traversal to the accumulator.
+   * The accumulator then will keep track of the text nodes in the range until a block boundary is found.
+   * Once a block boundary is found and the content of the text nodes in the boundary is non emtpy, the property
+   * textInBlock will be set with the content of the text nodes, trimmed of leading and trailing whitespaces.
+   * @param {Node} node - next node in the traversal of the searchRange  
+   */
+  appendNode(node) {
+    // if we already calculated the text in the block boundary just ignore any calls to append nodes
+    if (this.textInBlock !== null) {
+      return;
+    }
+    // we found a block boundary, check if there's text inside and set it to textInBlock or keep going to the next block boundary
+    if (isBlock(node)) {
+      if (this.textFound) {
+        // concatenate all the text nodes in the block boundary and trim any trailing and leading whitespaces
+        this.textInBlock = this.textNodes.map(textNode => textNode.textContent).join("").trim();
+      } else {
+        // discard the text nodes visited so far since they are empty and we'll continue searching in the next block boundary
+        this.textNodes = [];
+      }
+      return;
+    }
+
+    // ignore non text nodes
+    if (!isText(node)) return;
+
+    // get the part of node inside the search range. this is to avoid accumulating text that's not inside the range
+    const nodeToInsert = this.getNodeInterceptionWithRange(node);
+
+    // keep track of any text found in the block boundary.
+    this.textFound = this.textFound || nodeToInsert.textContent.trim() !== "";
+
+    this.insertFunc.call(this.textNodes, nodeToInsert);
+  }
+
+  /**
+   * Calculates the interception of a node with searchRange and returns a Text Node with the interception
+   * @param {Node} node - the node to intercept with searchRange
+   * @returns {Node} - node if node is fully within searchRange or a Text Node with the substring of the content of node inside the search range
+   */
+  getNodeInterceptionWithRange(node) {
+    let startOffset = null;
+    let endOffset = null;
+
+    if (node === this.searchRange.startContainer && this.searchRange.startOffset !== 0) {
+      startOffset = this.searchRange.startOffset;
+    }
+
+    if (node === this.searchRange.endContainer && this.searchRange.endOffset !== node.textContent.length) {
+      endOffset = this.searchRange.endOffset;
+    }
+    if (startOffset !== null || endOffset !== null) {
+      return document.createTextNode(node.textContent.substring(startOffset ?? 0, endOffset ?? node.textContent.length));
+    }
+
+    return node;
+  }
+};
+
+/**
  * @param {TextFragment} fragment - the candidate fragment
  * @return {boolean} - true iff the candidate fragment identifies exactly one
  *     portion of the document.
@@ -1033,12 +1119,12 @@
   if (!walker) {
     return false;
   }
-  const map = createForwardOverrideMap(walker);
+  const isFinished = new Set();
 
   while (!tempRange.collapsed && node != null) {
     if (isBlock(node)) return true;
     if (node != null) tempRange.setStartAfter(node);
-    node = forwardTraverse(walker, map);
+    node = forwardTraverse(walker, isFinished);
     checkTimeout();
   }
   return false;
@@ -1184,10 +1270,9 @@
     if (!walker) {
       return;
     }
-    const visited = new Set();
-    const origin = walker.currentNode;
+    const isFinished = new Set();
 
-    let node = backwardTraverse(walker, visited, origin);
+    let node = backwardTraverse(walker, isFinished);
     while (node != null) {
       const newOffset = findWordStartBoundInTextNode(node);
       if (newOffset !== -1) {
@@ -1210,7 +1295,7 @@
         return;
       }
 
-      node = backwardTraverse(walker, visited, origin);
+      node = backwardTraverse(walker, isFinished);
       // We should never get here; the walker should eventually hit a block node
       // or the root of the document. Collapse range so the caller can handle
       // this as an error.
@@ -1350,15 +1435,15 @@
   if (!backWalker) {
     return;
   }
-  const visited = new Set();
+  const isFinished = new Set();
   const origin = backWalker.currentNode;
-  let backNode = backwardTraverse(backWalker, visited, origin);
+  let backNode = backwardTraverse(backWalker, isFinished, origin);
   while (backNode != null && !isBlock(backNode)) {
     checkTimeout();
     if (backNode.nodeType === Node.TEXT_NODE) {
       preNodes.push(backNode);
     }
-    backNode = backwardTraverse(backWalker, visited, origin);
+    backNode = backwardTraverse(backWalker, isFinished, origin);
   };
   preNodes.reverse();
 
@@ -1386,17 +1471,19 @@
   if (!forwardWalker) {
     return;
   }
-  const overrideMap = createForwardOverrideMap(forwardWalker);
-  let forwardNode = forwardTraverse(forwardWalker, overrideMap);
+  // forward traverse from node after having visited its subtree
+  // to get text nodes after it until we find a block boundary
+  const isForwardFinished = new Set([node]);
+  let forwardNode = forwardTraverse(forwardWalker, isForwardFinished);
   while (forwardNode != null && !isBlock(forwardNode)) {
     checkTimeout();
     if (forwardNode.nodeType === Node.TEXT_NODE) {
       postNodes.push(forwardNode);
     }
-    forwardNode = forwardTraverse(forwardWalker, overrideMap);
+    forwardNode = forwardTraverse(forwardWalker, isForwardFinished);
   }
 
-  return {preNodes: preNodes, innerNodes: innerNodes, postNodes: postNodes};
+  return { preNodes: preNodes, innerNodes: innerNodes, postNodes: postNodes };
 };
 
 /**
@@ -1451,61 +1538,79 @@
 };
 
 /**
- * Performs traversal on a TreeWalker, using document order except when a node
- * has an entry in |overrideMap|, in which case navigation skips to the
- * indicated destination. This is useful for ensuring the ends of block
- * boundaries are found.
+ * Performs traversal on a TreeWalker, visiting each subtree in document order. 
+ * When visiting a subtree not already visited (its root not in finishedNodes ),
+ * first the root is emited then the subtree is traversed, then the root is emited again
+ * and then the next subtree in document order is visited. 
+ * 
+ * Subtree's roots are emited twice to signal the begining and ending of element nodes.
+ * This is useful for ensuring the ends of block boundaries are found. 
  * @param {TreeWalker} walker - the TreeWalker to be traversed
- * @param {Map<Node, Node>} overrideMap - maps nodes to the nodes which should
- *     follow them during traversal, if this differs from document order
- * @return {Node} - |walker|'s new current node, or null if the current node
- *     was unchanged (and thus, no further traversal is possible)
+ * @param {Set} finishedNodes - set of subtree roots already visited
+ * @return {Node} - next node in the traversal
  */
-const forwardTraverse = (walker, overrideMap) => {
-  if (overrideMap.has(walker.currentNode)) {
-    const override = overrideMap.get(walker.currentNode);
-    if (override != null) walker.currentNode = override;
-    return override;
+const forwardTraverse = (walker, finishedNodes) => {
+  // if current node's subtree is not already finished
+  // try to go first down the subtree
+  if (!finishedNodes.has(walker.currentNode)) {
+    const firstChild = walker.firstChild();
+    if (firstChild !== null) {
+      return firstChild;
+    }
   }
-  return walker.nextNode();
+
+  // if no subtree go to next sibling if any
+  const nextSibling = walker.nextSibling();
+  if (nextSibling !== null) {
+    return nextSibling;
+  }
+
+  // if no sibling go back to parent and mark it as finished
+  const parent = walker.parentNode();
+
+  if (parent !== null) {
+    finishedNodes.add(parent);
+  }
+
+  return parent;
 };
 
 /**
- * Performs backwards traversal on a TreeWalker, such that parent nodes are
- * encountered *before* their children (except when they are ancestors of the
- * starting node |origin|). This is useful for finding block boundaries.
+ * Performs backwards traversal on a TreeWalker, visiting each subtree in backwards document order. 
+ * When visiting a subtree not already visited (its root not in finishedNodes ),
+ * first the root is emited then the subtree is backward traversed, then the root is emited again
+ * and then the previous subtree in document order is visited. 
+ * 
+ * Subtree's roots are emited twice to signal the begining and ending of element nodes.
+ * This is useful for ensuring  block boundaries are found. 
  * @param {TreeWalker} walker - the TreeWalker to be traversed
- * @param {Set<Node>} visited - a set used to avoid repeat iterations. Should be
- *     empty the first time this method is called.
- * @param {Node} origin - the node where traversal started
- * @return {Node} - |walker|'s new current node, or null if
- *     the current node was unchanged (and thus, no further traversal is
- *     possible).
+ * @param {Set} finishedNodes - set of subtree roots already visited
+ * @return {Node} - next node in the backwards traversal
  */
-const backwardTraverse = (walker, visited, origin) => {
-  // Infinite loop to avoid recursion. Will terminate since visited set
-  // guarantees children of a node are only traversed once, and parent node
-  // will be null once the root of the walker is reached.
-  while (true) {
-    checkTimeout();
-    // The first time we visit a node, we traverse its children backwards,
-    // unless it's an ancestor of the starting node.
-    if (!visited.has(walker.currentNode) &&
-        !walker.currentNode.contains(origin)) {
-      visited.add(walker.currentNode);
-      if (walker.lastChild() != null) {
-        return walker.currentNode;
+const backwardTraverse = (walker, finishedNodes) => {
+  // if current node's subtree is not already finished
+  // try to go first down the subtree
+  if (!finishedNodes.has(walker.currentNode)) {
+    const lastChild = walker.lastChild();
+    if (lastChild !== null) {
+      return lastChild;
       }
     }
 
-    if (walker.previousSibling() != null) {
-      return walker.currentNode;
-    } else if (walker.parentNode() == null) {
-      return null;
-    } else if (!visited.has(walker.currentNode)) {
-      return walker.currentNode;
+  // if no subtree go to previous sibling if any
+  const previousSibling = walker.previousSibling();
+  if (previousSibling !== null) {
+    return previousSibling;
     }
+
+  // if no sibling go back to parent and mark it as finished
+  const parent = walker.parentNode();
+
+  if (parent !== null) {
+    finishedNodes.add(parent);
   }
+
+  return parent;
 };
 
 /**
@@ -1539,7 +1644,9 @@
     if (!walker) {
       return;
     }
-    const override = createForwardOverrideMap(walker);
+    // we'll traverse the dom after node's subtree to try to find 
+    // either a word or block boundary
+    const isFinished = new Set([node]);
 
     while (node != null) {
       checkTimeout();
@@ -1569,7 +1676,7 @@
         return;
       }
 
-      node = forwardTraverse(walker, override);
+      node = forwardTraverse(walker, isFinished);
     }
     // We should never get here; the walker should eventually hit a block node
     // or the root of the document. Collapse range so the caller can handle this
@@ -1581,7 +1688,7 @@
 /**
  * Helper to determine if a node is a block element or not.
  * @param {Node} node - the node to evaluate
- * @return {Boolean} true iff the node is an element classified as block-level
+ * @return {Boolean} - true if the node is an element classified as block-level
  */
 const isBlock = (node) => {
   return node.nodeType === Node.ELEMENT_NODE &&
@@ -1589,6 +1696,15 @@
        node.tagName === 'HTML' || node.tagName === 'BODY');
 };
 
+/**
+ * Helper to determine if a node is a Text Node or not
+ * @param {Node} node - the node to evaluate
+ * @returns {Boolean} - true if the node is a Text Node
+ */
+const isText = (node) => {
+  return node.nodeType === Node.TEXT_NODE;
+};
+
 export const forTesting = {
   backwardTraverse: backwardTraverse,
   containsBlockBoundary: containsBlockBoundary,
diff --git a/test/fragment-generation-utils-test.js b/test/fragment-generation-utils-test.js
index 781ad58..5060c88 100644
--- a/test/fragment-generation-utils-test.js
+++ b/test/fragment-generation-utils-test.js
@@ -357,27 +357,26 @@
     document.body.innerHTML = __html__['postorder-tree.html'];
     const walker = document.createTreeWalker(document.getElementById('l'));
     walker.currentNode = document.getElementById('b').firstChild;
-    const visited = generationUtils.forTesting.createForwardOverrideMap(walker);
+    const isFinished = new Set();
     const traversalOrder = [];
-    while (generationUtils.forTesting.forwardTraverse(walker, visited) !=
+    while (generationUtils.forTesting.forwardTraverse(walker, isFinished) !=
            null) {
       if (walker.currentNode.id != null) {
         traversalOrder.push(walker.currentNode.id);
       }
     }
     expect(traversalOrder).toEqual([
-      'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'
+      'b', 'c', 'd', 'e', 'c', 'f', 'g', 'h', 'i', 'h', 'j', 'k', 'k', 'j', 'g', 'l'
     ]);
 
     // Also check a simple case that was previously causing cycles
     document.body.innerHTML = __html__['non-word-boundaries.html'];
     const walker2 = document.createTreeWalker(document.body);
     walker2.currentNode = document.getElementById('em').firstChild;
-    const override =
-        generationUtils.forTesting.createForwardOverrideMap(walker2);
+    const isFinished2 = new Set();
     traversalOrder.splice(0);
     const seen = new Set();
-    while (generationUtils.forTesting.forwardTraverse(walker2, override) !=
+    while (generationUtils.forTesting.forwardTraverse(walker2, isFinished2) !=
            null) {
       expect(seen.has(walker2.currentNode)).toBeFalse();
 
@@ -403,13 +402,13 @@
     const visited = new Set();
     const traversalOrder = [];
     while (generationUtils.forTesting.backwardTraverse(
-               walker, visited, origin) != null) {
+               walker, visited) != null) {
       if (walker.currentNode.id != null) {
         traversalOrder.push(walker.currentNode.id);
       }
     }
     expect(traversalOrder).toEqual([
-      'k', 'j', 'h', 'i', 'g', 'f', 'c', 'e', 'd', 'b', 'l'
+      'k', 'j', 'h', 'i', 'h', 'g', 'f', 'c', 'e', 'd', 'c', 'b', 'b', 'f', 'l'
     ]);
   });
 
@@ -1044,4 +1043,235 @@
            .toEqual(generationUtils.GenerateFragmentStatus.SUCCESS);
        expect(output.fragment.textStart).toEqual('てこの崇高');
      });
+  // forwardTraverse tests
+  it('Given walker with non finished current node\n' +
+    'and current node has visible children\n' +
+    'When forwardTraverse is called\n' +
+    'Then the first visible child of current node is returned',
+    function () {
+      document.body.innerHTML = __html__['traversal_tests.html'];
+
+      const root = document.getElementById('1');
+      const firstChild = root.firstChild;
+
+      const walker = document.createTreeWalker(root);
+      walker.currentNode = root;
+      const finishedNodes = new Set();
+
+      expect(generationUtils.forTesting.forwardTraverse(walker, finishedNodes))
+        .toEqual(firstChild);
+
+      expect(walker.currentNode).toEqual(firstChild);
+    });
+
+  it('Given walker with non finished current node\n' +
+    'and current node has no visible children\n' +
+    'and current node has a visible next sibling\n' +
+    'When forwardTraverse is called\n' +
+    'Then the next visible sibling of current node is returned',
+    function () {
+      document.body.innerHTML = __html__['traversal_tests.html'];
+
+      const root = document.getElementById('1');
+      const currentNode = document.getElementById('2').firstChild;
+      const nextSibling = document.getElementById('3');
+
+      const walker = document.createTreeWalker(root);
+      walker.currentNode = currentNode;
+      const finishedNodes = new Set();
+
+      expect(generationUtils.forTesting.forwardTraverse(walker, finishedNodes))
+        .toEqual(nextSibling);
+
+      expect(walker.currentNode).toEqual(nextSibling);
+    });
+
+  it('Given walker with non finished current node\n' +
+    'and current node has no visible children\n' +
+    'and current node has no visible next siblings\n' +
+    'When forwardTraverse is called\n' +
+    'Then the parent of current node is returned\n' +
+    'and the parent of current node is marked as finished',
+    function () {
+      document.body.innerHTML = __html__['traversal_tests.html'];
+
+      const root = document.getElementById('1');
+      const parent = document.getElementById('3');
+      const currentNode = parent.lastChild;
+
+      const walker = document.createTreeWalker(root);
+      walker.currentNode = currentNode;
+      const finishedNodes = new Set();
+
+      expect(generationUtils.forTesting.forwardTraverse(walker, finishedNodes))
+        .toEqual(parent);
+
+      expect(walker.currentNode).toEqual(parent);
+
+      expect(finishedNodes.has(parent))
+        .toBeTrue();
+    });
+
+  it('Given walker with finished current node\n' +
+    'and current node has visible children\n' +
+    'and current node has no visible next sibling\n' +
+    'When forwardTraverse is called\n' +
+    'Then the parent of current node is returned\n' +
+    'and the parent of current node is marked as finished',
+    function () {
+      document.body.innerHTML = __html__['traversal_tests.html'];
+
+      const root = document.getElementById('1');
+      const currentNode = root.lastChild;
+
+      const walker = document.createTreeWalker(root);
+      walker.currentNode = currentNode;
+
+      const finishedNodes = new Set([currentNode]);
+
+      expect(generationUtils.forTesting.forwardTraverse(walker, finishedNodes))
+        .toEqual(root);
+
+      expect(walker.currentNode).toEqual(root);
+
+      expect(finishedNodes.has(root))
+        .toBeTrue();
+    });
+
+  it('Given walker with finished current node\n' +
+    'and current node has visible next sibling\n' +
+    'When forwardTraverse is called\n' +
+    'Then the next visible sibling of current node is returned',
+    function () {
+      document.body.innerHTML = __html__['traversal_tests.html'];
+
+      const parent = document.getElementById('2');
+      const currentNode = document.getElementById('3');
+      const nextSibling = document.getElementById('4');
+
+      const walker = document.createTreeWalker(parent);
+      walker.currentNode = currentNode;
+      const finishedNodes = new Set([currentNode]);
+
+      expect(generationUtils.forTesting.forwardTraverse(walker, finishedNodes))
+        .toEqual(nextSibling);
+
+      expect(walker.currentNode).toEqual(nextSibling);
+    });
+
+  // backwardTraverse tests
+  it('Given walker with non finished current node\n' +
+    'and current node has visible children\n' +
+    'When backwardTraverse is called\n' +
+    'Then the last visible child of current node is returned',
+    function () {
+      document.body.innerHTML = __html__['traversal_tests.html'];
+
+      const root = document.getElementById('2');
+      const lastChild = root.lastChild;
+
+      const walker = document.createTreeWalker(root);
+      walker.currentNode = root;
+      const finishedNodes = new Set();
+
+      expect(generationUtils.forTesting.backwardTraverse(walker, finishedNodes))
+        .toEqual(lastChild);
+
+      expect(walker.currentNode).toEqual(lastChild);
+    });
+
+  it('Given walker with non finished current node\n' +
+    'and current node has no visible children\n' +
+    'and current node has a visible previous sibling\n' +
+    'When backwardTraverse is called\n' +
+    'Then the previous visible sibling of current node is returned',
+    function () {
+      document.body.innerHTML = __html__['traversal_tests.html'];
+
+      const root = document.getElementById('1');
+      const currentNode = document.getElementById('2').lastChild;
+      const previousSibling = document.getElementById('4');
+
+      const walker = document.createTreeWalker(root);
+      walker.currentNode = currentNode;
+      const finishedNodes = new Set();
+
+      expect(generationUtils.forTesting.backwardTraverse(walker, finishedNodes))
+        .toEqual(previousSibling);
+
+      expect(walker.currentNode).toEqual(previousSibling);
+    });
+
+  it('Given walker with non finished current node\n' +
+    'and current node has no visible children\n' +
+    'and current node has no visible previous sibling\n' +
+    'When backwardTraverse is called\n' +
+    'Then the parent of current node is returned\n' +
+    'and the parent of current node is marked as finished',
+    function () {
+      document.body.innerHTML = __html__['traversal_tests.html'];
+
+      const root = document.getElementById('1');
+      const parent = document.getElementById('3');
+      const currentNode = parent.firstChild;
+
+      const walker = document.createTreeWalker(root);
+      walker.currentNode = currentNode;
+      const finishedNodes = new Set();
+
+      expect(generationUtils.forTesting.backwardTraverse(walker, finishedNodes))
+        .toEqual(parent);
+
+      expect(walker.currentNode).toEqual(parent);
+
+      expect(finishedNodes.has(parent))
+        .toBeTrue();
+    });
+
+  it('Given walker with finished current node\n' +
+    'and current node has visible children\n' +
+    'and current node has no visible previous sibling\n' +
+    'When backwardTraverse is called\n' +
+    'Then the parent of current node is returned\n' +
+    'and the parent of current node is marked as finished',
+    function () {
+      document.body.innerHTML = __html__['traversal_tests.html'];
+
+      const parent = document.getElementById('5');
+      const currentNode = document.getElementById('6');
+
+      const walker = document.createTreeWalker(parent);
+      walker.currentNode = currentNode;
+
+      const finishedNodes = new Set([currentNode]);
+
+      expect(generationUtils.forTesting.backwardTraverse(walker, finishedNodes))
+        .toEqual(parent);
+
+      expect(walker.currentNode).toEqual(parent);
+
+      expect(finishedNodes.has(parent))
+        .toBeTrue();
+    });
+
+  it('Given walker with finished current node\n' +
+    'and current node has visible previous sibling\n' +
+    'When backwardTraverse is called\n' +
+    'Then the previous visible sibling of current node is returned',
+    function () {
+      document.body.innerHTML = __html__['traversal_tests.html'];
+
+      const parent = document.getElementById('2');
+      const currentNode = document.getElementById('4');
+      const previousSibling = document.getElementById('3');
+
+      const walker = document.createTreeWalker(parent);
+      walker.currentNode = currentNode;
+      const finishedNodes = new Set([currentNode]);
+
+      expect(generationUtils.forTesting.backwardTraverse(walker, finishedNodes))
+        .toEqual(previousSibling);
+
+      expect(walker.currentNode).toEqual(previousSibling);
+    });
 });
diff --git a/test/traversal_tests.html b/test/traversal_tests.html
new file mode 100644
index 0000000..76a6cc1
--- /dev/null
+++ b/test/traversal_tests.html
@@ -0,0 +1,4 @@
+<div id="1">
+  <p id="2">Some text <span id="3">Text</span><span id="4">Text</span>End text</p>
+  <div id="5"><span id="6">Text</span></div>
+</div>