dom.ts•16.3 kB
import { searchHTML, printElement } from "../../src/tools/dom";
// Helper function to create mock shadow DOM
function createMockShadowDOM(
  hostElement: Element,
  innerHTML: string = "",
): ShadowRoot {
  const shadowRoot = hostElement.attachShadow({ mode: "open" });
  shadowRoot.innerHTML = innerHTML;
  return shadowRoot;
}
// Mock Puppeteer Page for testing
const createMockPage = (evaluateResult: any) => ({
  evaluate: jest.fn().mockImplementation((fn, ...args) => {
    return Promise.resolve(fn(...args));
  }),
});
// Mock console methods to reduce noise in tests
const originalConsole = global.console;
beforeAll(() => {
  global.console = {
    ...originalConsole,
    log: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
    info: jest.fn(),
    debug: jest.fn(),
  };
});
afterAll(() => {
  global.console = originalConsole;
});
describe("DOM Tools", () => {
  beforeEach(() => {
    // Clear the DOM before each test
    document.body.innerHTML = "";
    document.head.innerHTML = "";
  });
  describe("searchHTML", () => {
    test("should find elements containing text content", async () => {
      document.body.innerHTML = `
        <div class="container">
          <h1>Welcome</h1>
          <p>Option 1 is available</p>
          <p>Option 2 is not</p>
        </div>
      `;
      const mockPage = createMockPage(null);
      const result = await searchHTML(mockPage as any, "Option 1");
      expect(result).toContain("Option 1 is available");
      expect(result).toContain('<div class="container">');
      expect(result).not.toContain("Option 2 is not");
    });
    test("should find elements by attribute values", async () => {
      document.body.innerHTML = `
        <div class="main">
          <select class="test-select" id="test-select">
            <option value="">Select an option</option>
            <option value="option1">Choice 1</option>
            <option value="option2">Choice 2</option>
          </select>
        </div>
      `;
      const mockPage = createMockPage(null);
      const result = await searchHTML(mockPage as any, "option1");
      expect(result).toContain('value="option1"');
      expect(result).toContain('<select class="test-select" id="test-select">');
    });
    test("should handle nested elements correctly", async () => {
      document.body.innerHTML = `
        <div class="outer">
          <div class="middle">
            <div class="inner">Target text</div>
          </div>
          <div class="sibling">Other content</div>
        </div>
      `;
      const mockPage = createMockPage(null);
      const result = await searchHTML(mockPage as any, "Target text");
      expect(result).toContain('<div class="outer">');
      expect(result).toContain('<div class="middle">');
      expect(result).toContain('<div class="inner">');
      expect(result).toContain("Target text");
      expect(result).toContain("[...]"); // Should show [...] for sibling without target
    });
    test("should skip script, style, and svg elements", async () => {
      document.body.innerHTML = `
        <div>
          <p>Visible content with target</p>
          <script>console.log('target should not be found');</script>
          <style>.target { color: red; }</style>
          <svg><text>target in svg</text></svg>
        </div>
      `;
      const mockPage = createMockPage(null);
      const result = await searchHTML(mockPage as any, "target");
      expect(result).toContain("Visible content with target");
      expect(result).not.toContain("console.log");
      expect(result).not.toContain("color: red");
      expect(result).not.toContain("target in svg");
    });
    test("should handle shadow DOM content", async () => {
      const hostElement = document.createElement("div");
      hostElement.id = "shadow-host";
      document.body.appendChild(hostElement);
      const shadowRoot = createMockShadowDOM(
        hostElement,
        `
        <div class="shadow-content">
          <p>Shadow DOM target text</p>
        </div>
      `,
      );
      const mockPage = createMockPage(null);
      const result = await searchHTML(mockPage as any, "Shadow DOM target");
      expect(result).toContain("shadow-content");
      expect(result).toContain("Shadow DOM target text");
    });
    test("should handle elements without target content", async () => {
      document.body.innerHTML = `
        <div class="container">
          <div class="has-target">Found it!</div>
          <div class="no-target">Nothing here</div>
          <div class="also-no-target">Still nothing</div>
        </div>
      `;
      const mockPage = createMockPage(null);
      const result = await searchHTML(mockPage as any, "Found it");
      expect(result).toContain("Found it!");
      expect(result).toContain("[...]"); // Should show [...] for elements without target
    });
    test("should be case insensitive", async () => {
      document.body.innerHTML = `
        <div>
          <p>UPPERCASE TARGET</p>
          <p>lowercase target</p>
          <p>MiXeD CaSe TaRgEt</p>
        </div>
      `;
      const mockPage = createMockPage(null);
      const result = await searchHTML(mockPage as any, "target");
      expect(result).toContain("UPPERCASE TARGET");
      expect(result).toContain("lowercase target");
      expect(result).toContain("MiXeD CaSe TaRgEt");
    });
    test("should handle empty body", async () => {
      document.body.innerHTML = "";
      const mockPage = createMockPage(null);
      const result = await searchHTML(mockPage as any, "anything");
      expect(result).toBe("<body>\n</body>");
    });
    test("should preserve proper indentation", async () => {
      document.body.innerHTML = `
        <div class="level1">
          <div class="level2">
            <div class="level3">Target content</div>
          </div>
        </div>
      `;
      const mockPage = createMockPage(null);
      const result = await searchHTML(mockPage as any, "Target content");
      const lines = result.split("\n");
      expect(lines[1]).toMatch(/^  <div/); // Level 1 - 2 spaces
      expect(lines[2]).toMatch(/^    <div/); // Level 2 - 4 spaces
      expect(lines[3]).toMatch(/^      <div/); // Level 3 - 6 spaces
    });
  });
  describe("printElement", () => {
    test("should return full HTML for matching element", async () => {
      document.body.innerHTML = `
        <select class="test-select" id="test-select">
          <option value="">Select an option</option>
          <option value="option1">Option 1</option>
          <option value="option2">Option 2</option>
          <option value="option3">Option 3</option>
        </select>
      `;
      const mockPage = createMockPage(null);
      const result = await printElement(mockPage as any, ".test-select");
      expect(result).toContain('<select class="test-select" id="test-select">');
      expect(result).toContain('<option value="">Select an option</option>');
      expect(result).toContain('<option value="option1">Option 1</option>');
      expect(result).toContain('<option value="option2">Option 2</option>');
      expect(result).toContain('<option value="option3">Option 3</option>');
      expect(result).toContain("</select>");
    });
    test("should handle nested elements", async () => {
      document.body.innerHTML = `
        <div class="container">
          <div class="header">
            <h1>Title</h1>
            <p>Description</p>
          </div>
          <div class="content">
            <span>Content text</span>
          </div>
        </div>
      `;
      const mockPage = createMockPage(null);
      const result = await printElement(mockPage as any, ".container");
      expect(result).toContain('<div class="container">');
      expect(result).toContain('<div class="header">');
      expect(result).toContain("<h1>Title</h1>");
      expect(result).toContain("<p>Description</p>");
      expect(result).toContain('<div class="content">');
      expect(result).toContain("<span>Content text</span>");
    });
    test("should handle shadow DOM content", async () => {
      const hostElement = document.createElement("div");
      hostElement.className = "shadow-host";
      document.body.appendChild(hostElement);
      const shadowRoot = createMockShadowDOM(
        hostElement,
        `
        <div class="shadow-content">
          <h4>Shadow Title</h4>
          <p>Shadow paragraph</p>
          <button class="shadow-button">Shadow Button</button>
        </div>
      `,
      );
      const mockPage = createMockPage(null);
      const result = await printElement(mockPage as any, ".shadow-host");
      expect(result).toContain('<div class="shadow-host">');
      expect(result).toContain('<div class="shadow-content">');
      expect(result).toContain("<h4>Shadow Title</h4>");
      expect(result).toContain("<p>Shadow paragraph</p>");
      expect(result).toContain(
        '<button class="shadow-button">Shadow Button</button>',
      );
    });
    test("should handle elements with attributes", async () => {
      document.body.innerHTML = `
        <input
          type="text"
          id="test-input"
          class="form-control"
          placeholder="Enter text"
          value="default value"
          data-testid="input-field"
        />
      `;
      const mockPage = createMockPage(null);
      const result = await printElement(mockPage as any, "#test-input");
      expect(result).toContain('type="text"');
      expect(result).toContain('id="test-input"');
      expect(result).toContain('class="form-control"');
      expect(result).toContain('placeholder="Enter text"');
      expect(result).toContain('value="default value"');
      expect(result).toContain('data-testid="input-field"');
    });
    test("should handle self-closing elements", async () => {
      document.body.innerHTML = `
        <div>
          <img src="test.jpg" alt="Test image" />
          <br />
          <input type="text" />
        </div>
      `;
      const mockPage = createMockPage(null);
      const result = await printElement(mockPage as any, "div");
      expect(result).toContain('<img src="test.jpg" alt="Test image"></img>');
      expect(result).toContain("<br></br>");
      expect(result).toContain('<input type="text"></input>');
    });
    test("should throw error for non-existent element", async () => {
      document.body.innerHTML = `<div>Some content</div>`;
      const mockPage = {
        evaluate: jest.fn().mockImplementation((fn, selector) => {
          // Simulate the actual browser behavior
          const element = document.querySelector(selector);
          if (!element) {
            throw new Error(`Element not found: ${selector}`);
          }
          return fn(selector);
        }),
      };
      await expect(
        printElement(mockPage as any, ".non-existent"),
      ).rejects.toThrow("Element not found: .non-existent");
    });
    test("should preserve proper indentation in nested elements", async () => {
      document.body.innerHTML = `
        <div class="level1">
          <div class="level2">
            <div class="level3">
              <span>Deep content</span>
            </div>
          </div>
        </div>
      `;
      const mockPage = createMockPage(null);
      const result = await printElement(mockPage as any, ".level1");
      const lines = result.split("\n");
      expect(lines[0]).toMatch(/^<div/); // Level 1 - no indent
      expect(lines[1]).toMatch(/^  <div/); // Level 2 - 2 spaces
      expect(lines[2]).toMatch(/^    <div/); // Level 3 - 4 spaces
      expect(lines[3]).toMatch(/^      <span/); // Span - 6 spaces
    });
    test("should handle mixed content (text and elements)", async () => {
      document.body.innerHTML = `
        <div class="mixed">
          Some text before
          <span>inline element</span>
          some text after
          <div>block element</div>
          final text
        </div>
      `;
      const mockPage = createMockPage(null);
      const result = await printElement(mockPage as any, ".mixed");
      expect(result).toContain("Some text before");
      expect(result).toContain("<span>inline element</span>");
      expect(result).toContain("some text after");
      expect(result).toContain("<div>block element</div>");
      expect(result).toContain("final text");
    });
    test("should handle elements with no content", async () => {
      document.body.innerHTML = `<div class="empty"></div>`;
      const mockPage = createMockPage(null);
      const result = await printElement(mockPage as any, ".empty");
      expect(result).toBe('<div class="empty"></div>');
    });
  });
  describe("Integration tests", () => {
    test("should work with realistic select element structure", async () => {
      document.body.innerHTML = `
        <body>
          <div class="main-content">
            <h1>Test Page</h1>
            <div class="test-elements">
              <h2>Form Elements</h2>
              <select class="test-select" id="test-select">
                <option value="">Select an option</option>
                <option value="option1">Option 1</option>
                <option value="option2">Option 2</option>
                <option value="option3">Option 3</option>
              </select>
            </div>
          </div>
        </body>
      `;
      const mockPage = createMockPage(null);
      // Test searchHTML
      const searchResult = await searchHTML(mockPage as any, "Option 1");
      expect(searchResult).toContain("<body>");
      expect(searchResult).toContain('<div class="main-content">');
      expect(searchResult).toContain('<div class="test-elements">');
      expect(searchResult).toContain(
        '<select class="test-select" id="test-select">',
      );
      expect(searchResult).toContain(
        '<option value="option1">Option 1</option>',
      );
      expect(searchResult).toContain("[...]"); // Should contain abbreviated sections
      // Test printElement
      const printResult = await printElement(mockPage as any, ".test-select");
      expect(printResult).toContain(
        '<select class="test-select" id="test-select">',
      );
      expect(printResult).toContain(
        '<option value="">Select an option</option>',
      );
      expect(printResult).toContain(
        '<option value="option1">Option 1</option>',
      );
      expect(printResult).toContain(
        '<option value="option2">Option 2</option>',
      );
      expect(printResult).toContain(
        '<option value="option3">Option 3</option>',
      );
      expect(printResult).toContain("</select>");
    });
    test("should handle complex shadow DOM scenarios", async () => {
      // Create main content
      document.body.innerHTML = `
        <div class="main">
          <div class="shadow-host" id="shadow-host">
            <p>This div contains a shadow DOM.</p>
          </div>
        </div>
      `;
      const shadowHost = document.getElementById("shadow-host")!;
      const shadowRoot = createMockShadowDOM(
        shadowHost,
        `
        <div class="shadow-wrapper">
          <h4>Shadow Content</h4>
          <p>Shadow paragraph with target keyword</p>
          <button class="shadow-btn">Shadow Button</button>
        </div>
      `,
      );
      const mockPage = createMockPage(null);
      // Test searchHTML finds shadow DOM content
      const searchResult = await searchHTML(mockPage as any, "target keyword");
      expect(searchResult).toContain("shadow-wrapper");
      expect(searchResult).toContain("Shadow paragraph with target keyword");
      // Test printElement includes shadow DOM
      const printResult = await printElement(mockPage as any, ".shadow-host");
      expect(printResult).toContain(
        '<div class="shadow-host" id="shadow-host">',
      );
      expect(printResult).toContain('<div class="shadow-wrapper">');
      expect(printResult).toContain("<h4>Shadow Content</h4>");
      expect(printResult).toContain(
        "<p>Shadow paragraph with target keyword</p>",
      );
      expect(printResult).toContain(
        '<button class="shadow-btn">Shadow Button</button>',
      );
    });
  });
});