chromium/ios/web/annotations/resources/annotations_test.ts

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview Test helpers for the annotations manager.
 */

import {gCrWeb} from '//ios/web/public/js_messaging/resources/gcrweb.js';
import {NON_TEXT_NODE_NAMES}
    from '//ios/web/annotations/resources/annotations_constants.js';

// Simpleton tags with no closing tags (only those used in tests).
const NO_END_TAGS_NODE_NAMES = new Set([
  'br'
]);

/**
 * Returns count of <chrome_annotation>s.
 */
function countAnnotations(): number {
  let nodes = document.querySelectorAll("chrome_annotation");
  return nodes.length;
}

/**
 * Simulate clicking annotation at given `index`.
 */
function clickAnnotation(index: number, viewport: boolean): boolean {
  let nodes = document.querySelectorAll("chrome_annotation");
  const decoration = nodes[index];
  if (decoration && decoration instanceof HTMLElement) {
    if (viewport) {
      const rect = decoration.getBoundingClientRect();
      const event = new MouseEvent('click', {
        bubbles: true,
        cancelable: true,
        clientX: rect.x + 1,
        clientY: rect.y + 1
      });
      decoration.parentElement!.dispatchEvent(event);
    } else {
      decoration.click();
    }
    return true;
  }
  return false;
}

/**
 * Returns first `maxChars` characters from the page text and tags.
 * @param maxChars - maximum number of characters to parse out.
 */
 function getPageTaggedText(maxChars: number): string {
  const parts: string[] = [];
  let length = 0;
  function traverse(node: Node) {
    if (length >= maxChars) return;
    if (node.nodeType === Node.ELEMENT_NODE) {
      // Reject non-text nodes such as scripts.
      if (NON_TEXT_NODE_NAMES.has(node.nodeName) &&
          node.nodeName !== 'CHROME_ANNOTATION') {
        return;
      }
      const element = node as Element;
      if (element.shadowRoot && element.shadowRoot != node) {
        traverse(element.shadowRoot);
        return;
      }

      let tagName = element.tagName.toLowerCase();
      parts.push('<' + tagName + '>');
      length += tagName.length + 2;
      if (node.hasChildNodes()) {
        for (let child of node.childNodes) {
          traverse(child);
        }
      }
      if (!NO_END_TAGS_NODE_NAMES.has(tagName)) {
        parts.push('</' + tagName + '>');
        length += tagName.length + 2;
      }
    } else if (node.nodeType === Node.TEXT_NODE && node.textContent) {
      parts.push(node.textContent);
      length += node.textContent.length;
    }
  }
  parts.push('<html>');
  traverse(document.body);
  parts.push('</html>');
  return ''.concat(...parts);
}

gCrWeb.annotationsTest = {
  getPageTaggedText,
  countAnnotations,
  clickAnnotation,
};