/* This Source Code Form is subject to the terms of the Mozilla Public
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/* import-globals-from ../../../mochitest/text.js */
loadScripts({ name: "text.js", dir: MOCHITESTS_DIR });

/* eslint-disable camelcase */
const SupportedTextSelection_None = 0;
const SupportedTextSelection_Multiple = 2;
/* eslint-enable camelcase */

/**
 * Test the Text pattern's DocumentRange property. This also tests where the
 * Text pattern is exposed.
 */
addUiaTask(
  `
<div><input id="input" value="input"></div>
<textarea id="textarea">textarea</textarea>
<div id="contentEditable" contenteditable><p>content</p><p>editable</p></div>
<p id="p">p</p>
<a id="link" href="#">link</a>
  `,
  async function testTextDocumentRange() {
    await definePyVar("doc", `getDocUia()`);
    await definePyVar("pattern", `getUiaPattern(doc, "Text")`);
    ok(await runPython(`bool(pattern)`), "doc has Text pattern");
    // The IA2 -> UIA proxy adds spaces between elements that don't exist.
    if (gIsUiaEnabled) {
      is(
        await runPython(`pattern.DocumentRange.GetText(-1)`),
        "inputtextareacontenteditableplink",
        "document DocumentRange Text correct"
      );
    }

    await assignPyVarToUiaWithId("input");
    await definePyVar("pattern", `getUiaPattern(input, "Text")`);
    ok(await runPython(`bool(pattern)`), "input has Text pattern");
    is(
      await runPython(`pattern.DocumentRange.GetText(-1)`),
      "input",
      "input DocumentRange Text correct"
    );

    await assignPyVarToUiaWithId("textarea");
    await definePyVar("pattern", `getUiaPattern(textarea, "Text")`);
    ok(await runPython(`bool(pattern)`), "textarea has Text pattern");
    is(
      await runPython(`pattern.DocumentRange.GetText(-1)`),
      "textarea",
      "textarea DocumentRange Text correct"
    );

    // The IA2 -> UIA proxy doesn't expose the Text pattern on contentEditables
    // without role="textbox".
    if (gIsUiaEnabled) {
      await assignPyVarToUiaWithId("contentEditable");
      await definePyVar("pattern", `getUiaPattern(contentEditable, "Text")`);
      ok(await runPython(`bool(pattern)`), "contentEditable has Text pattern");
      is(
        await runPython(`pattern.DocumentRange.GetText(-1)`),
        "contenteditable",
        "contentEditable DocumentRange Text correct"
      );
    }

    await testPatternAbsent("p", "Text");
    // The IA2 -> UIA proxy doesn't expose the Text pattern on this text leaf.
    if (gIsUiaEnabled) {
      await runPython(`
        global pLeaf
        p = findUiaByDomId(doc, "p")
        pLeaf = uiaClient.RawViewWalker.GetFirstChildElement(p)
      `);
      await definePyVar("pattern", `getUiaPattern(pLeaf, "Text")`);
      ok(await runPython(`bool(pattern)`), "pLeaf has Text pattern");
      is(
        await runPython(`pattern.DocumentRange.GetText(-1)`),
        "p",
        "pLeaf DocumentRange Text correct"
      );
    }

    await testPatternAbsent("link", "Text");
    // The IA2 -> UIA proxy doesn't expose this text leaf at all.
    if (gIsUiaEnabled) {
      await runPython(`
        global linkLeaf
        link = findUiaByDomId(doc, "link")
        linkLeaf = uiaClient.RawViewWalker.GetFirstChildElement(link)
      `);
      await definePyVar("pattern", `getUiaPattern(linkLeaf, "Text")`);
      ok(await runPython(`bool(pattern)`), "linkLeaf has Text pattern");
      is(
        await runPython(`pattern.DocumentRange.GetText(-1)`),
        "link",
        "linkLeaf DocumentRange Text correct"
      );
    }
  }
);

/**
 * Test the TextRange pattern's GetText method.
 */
addUiaTask(
  `<div id="editable" contenteditable role="textbox">a <span>b</span>`,
  async function testTextRangeGetText() {
    await runPython(`
      doc = getDocUia()
      editable = findUiaByDomId(doc, "editable")
      text = getUiaPattern(editable, "Text")
      global range
      range = text.DocumentRange
    `);
    is(await runPython(`range.GetText(-1)`), "a b", "GetText(-1) correct");
    is(await runPython(`range.GetText(0)`), "", "GetText(0) correct");
    is(await runPython(`range.GetText(1)`), "a", "GetText(1) correct");
    is(await runPython(`range.GetText(2)`), "a ", "GetText(2) correct");
    is(await runPython(`range.GetText(3)`), "a b", "GetText(3) correct");
    is(await runPython(`range.GetText(4)`), "a b", "GetText(4) correct");
  }
);

/**
 * Test the TextRange pattern's Clone method.
 */
addUiaTask(
  `<input id="input" type="text" value="testing">`,
  async function testTextRangeClone() {
    await runPython(`
      doc = getDocUia()
      input = findUiaByDomId(doc, "input")
      text = getUiaPattern(input, "Text")
      global origRange
      origRange = text.DocumentRange
    `);
    is(
      await runPython(`origRange.GetText(-1)`),
      "testing",
      "origRange text correct"
    );
    await runPython(`
      global clonedRange
      clonedRange = origRange.Clone()
    `);
    is(
      await runPython(`clonedRange.GetText(-1)`),
      "testing",
      "clonedRange text correct"
    );

    // Test that modifying clonedRange doesn't impact origRange.
    info("Collapsing clonedRange to start");
    await runPython(
      `clonedRange.MoveEndpointByRange(TextPatternRangeEndpoint_End, clonedRange, TextPatternRangeEndpoint_Start)`
    );
    is(
      await runPython(`clonedRange.GetText(-1)`),
      "",
      "clonedRange text correct"
    );
    is(
      await runPython(`origRange.GetText(-1)`),
      "testing",
      "origRange text correct"
    );
  }
);

/**
 * Test the TextRange pattern's Compare method.
 */
addUiaTask(
  `<input id="input" type="text" value="testing">`,
  async function testTextRangeCompare() {
    await runPython(`
      doc = getDocUia()
      input = findUiaByDomId(doc, "input")
      text = getUiaPattern(input, "Text")
      global range1, range2
      range1 = text.DocumentRange
      range2 = text.DocumentRange
    `);
    ok(
      await runPython(`range1.Compare(range2)`),
      "range1 Compare range2 correct"
    );
    ok(
      await runPython(`range2.Compare(range1)`),
      "range2 Compare range1 correct"
    );
    info("Collapsing range2 to start");
    await runPython(
      `range2.MoveEndpointByRange(TextPatternRangeEndpoint_End, range2, TextPatternRangeEndpoint_Start)`
    );
    ok(
      !(await runPython(`range1.Compare(range2)`)),
      "range1 Compare range2 correct"
    );
    ok(
      !(await runPython(`range2.Compare(range1)`)),
      "range2 Compare range1 correct"
    );
  }
);

/**
 * Test the TextRange pattern's CompareEndpoints method.
 */
addUiaTask(
  `
<p>before</p>
<div><input id="input" type="text" value="input"></div>
<p>after</p>
  `,
  async function testTextRangeCompareEndpoints() {
    await runPython(`
      global doc, range1, range2
      doc = getDocUia()
      input = findUiaByDomId(doc, "input")
      text = getUiaPattern(input, "Text")
      range1 = text.DocumentRange
      range2 = text.DocumentRange
    `);
    is(
      await runPython(
        `range1.CompareEndpoints(TextPatternRangeEndpoint_Start, range1, TextPatternRangeEndpoint_Start)`
      ),
      0,
      "Compare range1 start to range1 start correct"
    );
    is(
      await runPython(
        `range1.CompareEndpoints(TextPatternRangeEndpoint_End, range1, TextPatternRangeEndpoint_End)`
      ),
      0,
      "Compare range1 end to range1 end correct"
    );
    is(
      await runPython(
        `range1.CompareEndpoints(TextPatternRangeEndpoint_Start, range1, TextPatternRangeEndpoint_End)`
      ),
      -1,
      "Compare range1 start to range1 end correct"
    );
    is(
      await runPython(
        `range1.CompareEndpoints(TextPatternRangeEndpoint_End, range1, TextPatternRangeEndpoint_Start)`
      ),
      1,
      "Compare range1 end to range1 start correct"
    );
    // Compare different ranges.
    is(
      await runPython(
        `range1.CompareEndpoints(TextPatternRangeEndpoint_Start, range2, TextPatternRangeEndpoint_Start)`
      ),
      0,
      "Compare range1 start to range2 start correct"
    );
    is(
      await runPython(
        `range1.CompareEndpoints(TextPatternRangeEndpoint_End, range2, TextPatternRangeEndpoint_End)`
      ),
      0,
      "Compare range1 end to range2 end correct"
    );
    is(
      await runPython(
        `range1.CompareEndpoints(TextPatternRangeEndpoint_Start, range2, TextPatternRangeEndpoint_End)`
      ),
      -1,
      "Compare range1 start to range2 end correct"
    );
    is(
      await runPython(
        `range1.CompareEndpoints(TextPatternRangeEndpoint_End, range2, TextPatternRangeEndpoint_Start)`
      ),
      1,
      "Compare range1 end to range2 start correct"
    );
    // Compare ranges created using different elements.
    await definePyVar("range3", `getUiaPattern(doc, "Text").DocumentRange`);
    is(
      await runPython(
        `range1.CompareEndpoints(TextPatternRangeEndpoint_Start, range3, TextPatternRangeEndpoint_Start)`
      ),
      1,
      "Compare range1 start to range3 start correct"
    );
    is(
      await runPython(
        `range1.CompareEndpoints(TextPatternRangeEndpoint_End, range3, TextPatternRangeEndpoint_End)`
      ),
      -1,
      "Compare range1 end to range3 end correct"
    );
  }
);

/**
 * Test the TextRange pattern's ExpandToEnclosingUnit method.
 */
addUiaTask(
  `
<p>before</p>
<div><textarea id="textarea" cols="5">ab cd ef gh</textarea></div>
<div>after <input id="input" value="input"></div>
  `,
  async function testTextRangeExpandToEnclosingUnit() {
    info("Getting DocumentRange from textarea");
    await runPython(`
      global doc, range
      doc = getDocUia()
      textarea = findUiaByDomId(doc, "textarea")
      text = getUiaPattern(textarea, "Text")
      range = text.DocumentRange
    `);
    is(
      await runPython(`range.GetText(-1)`),
      "ab cd ef gh",
      "range text correct"
    );
    // Expand should shrink the range because it's too big.
    info("Expanding to character");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Character)`);
    is(await runPython(`range.GetText(-1)`), "a", "range text correct");
    info("Collapsing to end");
    await runPython(
      `range.MoveEndpointByRange(TextPatternRangeEndpoint_Start, range, TextPatternRangeEndpoint_End)`
    );
    is(await runPython(`range.GetText(-1)`), "", "range text correct");
    // range is now collapsed at "b".
    info("Expanding to character");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Character)`);
    is(await runPython(`range.GetText(-1)`), "b", "range text correct");
    info("Expanding to word");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Word)`);
    is(await runPython(`range.GetText(-1)`), "ab ", "range text correct");
    info("Collapsing to end");
    await runPython(
      `range.MoveEndpointByRange(TextPatternRangeEndpoint_Start, range, TextPatternRangeEndpoint_End)`
    );
    // range is now collapsed at "c".
    info("Expanding to word");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Word)`);
    is(await runPython(`range.GetText(-1)`), "cd ", "range text correct");
    info("Expanding to line");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Line)`);
    is(await runPython(`range.GetText(-1)`), "ab cd ", "range text correct");
    info("Collapsing to end");
    await runPython(
      `range.MoveEndpointByRange(TextPatternRangeEndpoint_Start, range, TextPatternRangeEndpoint_End)`
    );
    // range is now collapsed at "e".
    info("Expanding to line");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Line)`);
    // The IA2 -> UIA proxy gets most things below this wrong.
    if (!gIsUiaEnabled) {
      return;
    }
    is(await runPython(`range.GetText(-1)`), "ef gh", "range text correct");
    info("Expanding to document");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Document)`);
    is(
      await runPython(`range.GetText(-1)`),
      "beforeab cd ef ghafter input",
      "range text correct"
    );

    // Test expanding to a line which crosses elements.
    info("Getting DocumentRange from input");
    await runPython(`
      input = findUiaByDomId(doc, "input")
      text = getUiaPattern(input, "Text")
      global range
      range = text.DocumentRange
    `);
    info("Expanding to line");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Line)`);
    is(
      await runPython(`range.GetText(-1)`),
      "after input",
      "range text correct"
    );
    info("Collapsing to end");
    await runPython(
      `range.MoveEndpointByRange(TextPatternRangeEndpoint_Start, range, TextPatternRangeEndpoint_End)`
    );
    // range is now collapsed at the end of the document.
    info("Expanding to line");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Line)`);
    is(
      await runPython(`range.GetText(-1)`),
      "after input",
      "range text correct"
    );
  }
);

/**
 * Test the Format TextUnit. Exercises ExpandToEnclosingUnit, Move, and
 * MoveEndpointByUnit. Tested here separately since the setup and implementation
 * is somewhat different from other TextUnits.
 */
addUiaTask(
  `
<div id="bold-container">a <b>bcd</b> ef</div>
<div id="container-container">a <span tabindex="0">bcd</span> ef</div>
<textarea id="textarea" spellcheck="true">test tset test</textarea>
`,
  async function testTextRangeMove(browser, docAcc) {
    info("Constructing range on bold text run");
    await runPython(`
      global doc, docText, range
      doc = getDocUia()
      docText = getUiaPattern(doc, "Text")
      boldContainerAcc = findUiaByDomId(doc, "bold-container")
      range = docText.RangeFromChild(boldContainerAcc)
    `);
    is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");
    info("Moving to bold text run");
    is(
      await runPython(`range.Move(TextUnit_Format, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");

    // Testing ExpandToEnclosingUnit (on formatting boundaries)
    info("Expanding to character (shrinking the range)");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Character)`);
    is(await runPython(`range.GetText(-1)`), "b", "range text correct");
    info("Expanding to Format");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Format)`);
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");

    info("Making range larger than the Format unit");
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Character, 1)`
      ),
      1,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd ", "range text correct");

    info("Expanding to Format (shrinking the range)");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Format)`);
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");

    // Testing Move (on formatting boundaries)
    info("Moving 1 Format unit");
    is(
      await runPython(`range.Move(TextUnit_Format, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), " ef", "range text correct");
    info("Moving -3 Format units (but only -2 are left)");
    is(
      await runPython(`range.Move(TextUnit_Format, -3)`),
      -2,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "a ", "range text correct");

    // Testing MoveEndpointByUnit (on formatting boundaries)
    info("Moving end 1 Format unit");
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Format, 1)`
      ),
      1,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "a bcd", "range text correct");
    info("Moving start 1 Format unit");
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Format, 1)`
      ),
      1,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");

    // Testing above three methods on text runs defined by container boundaries
    info("Constructing range on text run defined by container boundaries");
    await runPython(`
      global doc, docText, range
      containerContainer = findUiaByDomId(doc, "container-container")
      range = docText.RangeFromChild(containerContainer)
    `);
    is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");
    info("Expanding to Format");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Format)`);
    is(await runPython(`range.GetText(-1)`), "a ", "range text correct");
    info("Moving 1 Format unit");
    is(
      await runPython(`range.Move(TextUnit_Format, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");
    info("Moving start -1 Format unit");
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Format, -1)`
      ),
      -1,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "a bcd", "range text correct");

    // Trigger spelling errors so we can test text offset attributes
    const textarea = findAccessibleChildByID(docAcc, "textarea");
    textarea.takeFocus();
    await waitForEvent(EVENT_TEXT_ATTRIBUTE_CHANGED);

    // Testing above three methods on text offset attributes
    info("Constructing range on italic text run");
    await runPython(`
      global doc, docText, range
      textarea = findUiaByDomId(doc, "textarea")
      range = docText.RangeFromChild(textarea)
    `);
    is(
      await runPython(`range.GetText(-1)`),
      "test tset test",
      "range text correct"
    );
    info("Expanding to Format");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Format)`);
    is(await runPython(`range.GetText(-1)`), "test ", "range text correct");
    info("Moving 1 Format unit");
    is(
      await runPython(`range.Move(TextUnit_Format, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "tset", "range text correct");
    info("Moving start -1 Format unit");
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Format, -1)`
      ),
      -1,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "test tset", "range text correct");
  }
);

/**
 * Test the GetAttributeValue method. Verify the behavior of various UIA
 * Attribute IDs.
 */
addUiaTask(
  `
<div id="font-weight-container">a <span tabindex="0"><b>bcd</b></span><b> ef</b></div>
<div id="font-size-container">a <span style="font-size:20px">bcd</span> ef</div>
<div id="font-family-container">a <span style="font-family:Arial">bcd</span> ef</div>
<div id="italic-container">a <span style="font-style:italic">bcd</span> ef</div>
<div id="subscript-container">a <sub>bcd</sub> ef</div>
<div id="superscript-container">a <sup>bcd</sup> ef</div>
<div id="not-hidden-container">a bcd ef</div>
<div id="readonly-container">a <span contenteditable="true">bcd</span> ef</div>
<input id="text-input"/>
<div id="spelling-error-container">a <span aria-invalid="spelling">bcd</span> ef</div>
<div id="grammar-error-container">a <span aria-invalid="grammar">bcd</span> ef</div>
<div id="data-validation-error-container">a <span aria-invalid="true">bcd</span> ef</div>
<div id="highlight-container">a highlighted phrase ef</div>
<div id="heading-container">ab<h3>h3</h3>cd</div>
<div id="blockquote-container">ab<blockquote>quote</blockquote>cd</div>
<div id="emphasis-container">ab<em>emph</em>cd</div>
`,
  async function testTextRangeGetAttributeValue() {
    // ================== UIA_FontWeightAttributeId ==================
    info("Constructing range on bold text run");
    await runPython(`
      global doc, docText, range
      doc = getDocUia()
      docText = getUiaPattern(doc, "Text")
      fontWeightContainerAcc = findUiaByDomId(doc, "font-weight-container")
      range = docText.RangeFromChild(fontWeightContainerAcc)
    `);
    is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");

    info("checking mixed font weights");
    ok(
      await runPython(`
        val = range.GetAttributeValue(UIA_FontWeightAttributeId)
        return val == uiaClient.ReservedMixedAttributeValue
      `),
      "FontWeight correct (mixed)"
    );

    info("Moving to bold text run");
    is(
      await runPython(`range.Move(TextUnit_Format, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");

    info("checking FontWeight");
    is(
      await runPython(`range.GetAttributeValue(UIA_FontWeightAttributeId)`),
      700,
      "FontWeight correct"
    );

    info("Moving end 1 Format unit");
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Format, 1)`
      ),
      1,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd ef", "range text correct");
    info(
      "checking font weight (across equivalent container-separated Format runs)"
    );
    is(
      await runPython(`range.GetAttributeValue(UIA_FontWeightAttributeId)`),
      700,
      "FontWeight correct"
    );

    // ================== UIA_FontSizeAttributeId ==================
    await runPython(`
      global range
      fontSizeContainerAcc = findUiaByDomId(doc, "font-size-container")
      range = docText.RangeFromChild(fontSizeContainerAcc)
    `);
    is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");
    info("checking mixed font weights");
    ok(
      await runPython(`
        val = range.GetAttributeValue(UIA_FontSizeAttributeId)
        return val == uiaClient.ReservedMixedAttributeValue
      `),
      "FontSize correct (mixed)"
    );
    info("Moving to increased font-size text run");
    is(
      await runPython(`range.Move(TextUnit_Format, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");
    info("checking FontSize");
    is(
      await runPython(`range.GetAttributeValue(UIA_FontSizeAttributeId)`),
      15,
      "FontSize correct"
    );

    // ================== UIA_FontNameAttributeId ==================
    await runPython(`
      global range
      fontFamilyContainerAcc = findUiaByDomId(doc, "font-family-container")
      range = docText.RangeFromChild(fontFamilyContainerAcc)
    `);
    is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");
    info("checking mixed font families");
    ok(
      await runPython(`
        val = range.GetAttributeValue(UIA_FontNameAttributeId)
        return val == uiaClient.ReservedMixedAttributeValue
      `),
      "FontName correct (mixed)"
    );
    info("Moving to sans-serif font-family text run");
    is(
      await runPython(`range.Move(TextUnit_Format, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");
    info("checking FontName");
    is(
      await runPython(`range.GetAttributeValue(UIA_FontNameAttributeId)`),
      "Arial",
      "FontName correct"
    );

    // ================== UIA_IsItalicAttributeId ==================
    await runPython(`
      global range
      italicContainerAcc = findUiaByDomId(doc, "italic-container")
      range = docText.RangeFromChild(italicContainerAcc)
    `);
    is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");
    info("checking mixed IsItalic properties");
    ok(
      await runPython(`
        val = range.GetAttributeValue(UIA_IsItalicAttributeId)
        return val == uiaClient.ReservedMixedAttributeValue
      `),
      "IsItalic correct (mixed)"
    );
    info("Moving to italic text run");
    is(
      await runPython(`range.Move(TextUnit_Format, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");
    info("checking IsItalic");
    is(
      await runPython(`range.GetAttributeValue(UIA_IsItalicAttributeId)`),
      true,
      "IsItalic correct"
    );

    // ================== UIA_IsSubscriptAttributeId ==================
    await runPython(`
      global range
      subscriptContainerAcc = findUiaByDomId(doc, "subscript-container")
      range = docText.RangeFromChild(subscriptContainerAcc)
    `);
    is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");
    info("checking mixed IsSubscript properties");
    ok(
      await runPython(`
        val = range.GetAttributeValue(UIA_IsSubscriptAttributeId)
        return val == uiaClient.ReservedMixedAttributeValue
      `),
      "IsSubscript correct (mixed)"
    );
    info("Moving to subscript text run");
    is(
      await runPython(`range.Move(TextUnit_Format, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");
    info("checking IsSubscript");
    is(
      await runPython(`range.GetAttributeValue(UIA_IsSubscriptAttributeId)`),
      true,
      "IsSubscript correct"
    );

    // ================== UIA_IsSuperscriptAttributeId ==================
    await runPython(`
      global range
      superscriptContainerAcc = findUiaByDomId(doc, "superscript-container")
      range = docText.RangeFromChild(superscriptContainerAcc)
    `);
    is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");
    info("checking mixed IsSuperscript properties");
    ok(
      await runPython(`
        val = range.GetAttributeValue(UIA_IsSuperscriptAttributeId)
        return val == uiaClient.ReservedMixedAttributeValue
      `),
      "IsSuperscript correct (mixed)"
    );
    info("Moving to superscript text run");
    is(
      await runPython(`range.Move(TextUnit_Format, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");
    info("checking IsSuperscript");
    is(
      await runPython(`range.GetAttributeValue(UIA_IsSuperscriptAttributeId)`),
      true,
      "IsSuperscript correct"
    );

    // ================== UIA_IsHiddenAttributeId ==================
    // Testing the "true" case is not really possible since these Accessible
    // nodes are not present in the tree. Verify the "false" case.
    await runPython(`
      global range
      notHiddenContainerAcc = findUiaByDomId(doc, "not-hidden-container")
      range = docText.RangeFromChild(notHiddenContainerAcc)
    `);
    is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");
    info("checking mixed IsHidden properties");
    ok(
      await runPython(`
        val = range.GetAttributeValue(UIA_IsHiddenAttributeId)
        return val != uiaClient.ReservedMixedAttributeValue
      `),
      "IsHidden correct (not mixed)"
    );

    // ================== UIA_IsReadOnlyAttributeId ==================
    await runPython(`
      global range
      readonlyContainerAcc = findUiaByDomId(doc, "readonly-container")
      range = docText.RangeFromChild(readonlyContainerAcc)
    `);
    is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");
    info("checking mixed ReadOnly properties");
    ok(
      await runPython(`
        val = range.GetAttributeValue(UIA_IsReadOnlyAttributeId)
        return val == uiaClient.ReservedMixedAttributeValue
      `),
      "ReadOnly correct (mixed)"
    );
    info("Moving to editable text run");
    is(
      await runPython(`range.Move(TextUnit_Format, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");
    info("checking IsReadOnly");
    is(
      await runPython(`range.GetAttributeValue(UIA_IsReadOnlyAttributeId)`),
      false,
      "IsReadOnly correct"
    );

    // Verify that text inputs are not read-only by default.
    await runPython(`
      global range
      textInputAcc = findUiaByDomId(doc, "text-input")
      range = docText.RangeFromChild(textInputAcc)
    `);
    info("checking IsReadOnly");
    is(
      await runPython(`range.GetAttributeValue(UIA_IsReadOnlyAttributeId)`),
      false,
      "IsReadOnly correct for text input"
    );

    // ================== UIA_AnnotationTypesAttributeId - AnnotationType_SpellingError ==================
    await runPython(`
      global range
      spellingErrorContainerAcc = findUiaByDomId(doc, "spelling-error-container")
      range = docText.RangeFromChild(spellingErrorContainerAcc)
    `);
    is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");
    info("checking mixed SpellingError properties");
    ok(
      await runPython(`
        val = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
        return val == uiaClient.ReservedMixedAttributeValue
      `),
      "SpellingError correct (mixed)"
    );
    info('Moving to aria-invalid="spelling" text run');
    is(
      await runPython(`range.Move(TextUnit_Format, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");
    info("checking SpellingError");
    ok(
      await runPython(`
        annotations = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
        return annotations == (AnnotationType_SpellingError,)
      `),
      "SpellingError correct"
    );

    // ================== UIA_AnnotationTypesAttributeId - AnnotationType_GrammarError ==================
    await runPython(`
      global range
      grammarErrorContainerAcc = findUiaByDomId(doc, "grammar-error-container")
      range = docText.RangeFromChild(grammarErrorContainerAcc)
    `);
    is(await runPython(`range.GetText(-1)`), "a bcd ef", "range text correct");
    info("checking mixed GrammarError properties");
    ok(
      await runPython(`
        val = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
        return val == uiaClient.ReservedMixedAttributeValue
      `),
      "GrammarError correct (mixed)"
    );
    info('Moving to aria-invalid="grammar" text run');
    is(
      await runPython(`range.Move(TextUnit_Format, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");
    info("checking GrammarError");
    ok(
      await runPython(`
        annotations = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
        return annotations == (AnnotationType_GrammarError,)
      `),
      "GrammarError correct"
    );

    // ================== UIA_AnnotationTypesAttributeId - AnnotationType_DataValidationError ==================
    // The IA2 -> UIA bridge does not work for aria-invalid=true or highlights.
    if (gIsUiaEnabled) {
      await runPython(`
      global range
      dataValidationErrorContainerAcc = findUiaByDomId(doc, "data-validation-error-container")
      range = docText.RangeFromChild(dataValidationErrorContainerAcc)
    `);
      is(
        await runPython(`range.GetText(-1)`),
        "a bcd ef",
        "range text correct"
      );
      info("checking mixed DataValidationError properties");
      ok(
        await runPython(`
        val = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
        return val == uiaClient.ReservedMixedAttributeValue
      `),
        "DataValidationError correct (mixed)"
      );
      info('Moving to aria-invalid="true" text run');
      is(
        await runPython(`range.Move(TextUnit_Format, 1)`),
        1,
        "Move return correct"
      );
      is(await runPython(`range.GetText(-1)`), "bcd", "range text correct");
      info("checking DataValidationError");
      ok(
        await runPython(`
        annotations = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
        return annotations == (AnnotationType_DataValidationError,)
      `),
        "DataValidationError correct"
      );

      // ================== UIA_AnnotationTypesAttributeId - AnnotationType_Highlighted ==================
      await runPython(`
      global range
      highlightContainerAcc = findUiaByDomId(doc, "highlight-container")
      range = docText.RangeFromChild(highlightContainerAcc)
    `);
      is(
        await runPython(`range.GetText(-1)`),
        "a highlighted phrase ef",
        "range text correct"
      );
      info("checking mixed Highlighted properties");
      ok(
        await runPython(`
        val = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
        return val == uiaClient.ReservedMixedAttributeValue
      `),
        "Highlighted correct (mixed)"
      );
      info("Moving to highlighted text run");
      is(
        await runPython(`range.Move(TextUnit_Format, 1)`),
        1,
        "Move return correct"
      );
      is(
        await runPython(`range.GetText(-1)`),
        "highlighted phrase",
        "range text correct"
      );
      info("checking Highlighted");
      ok(
        await runPython(`
        annotations = range.GetAttributeValue(UIA_AnnotationTypesAttributeId)
        return annotations == (AnnotationType_Highlighted,)
      `),
        "Highlighted correct"
      );
    }

    // The IA2 -> UIA bridge does not work correctly here.
    if (gIsUiaEnabled) {
      // ================== UIA_StyleIdAttributeId - StyleId_Heading* ==================
      await runPython(`
        global range
        headingContainerAcc = findUiaByDomId(doc, "heading-container")
        range = docText.RangeFromChild(headingContainerAcc)
      `);
      is(await runPython(`range.GetText(-1)`), "abh3cd", "range text correct");
      info("checking mixed StyleId properties");
      ok(
        await runPython(`
          val = range.GetAttributeValue(UIA_StyleIdAttributeId)
          return val == uiaClient.ReservedMixedAttributeValue
        `),
        "StyleId correct (mixed)"
      );
      info("Moving to h3 text run");
      is(
        await runPython(`range.Move(TextUnit_Format, 1)`),
        1,
        "Move return correct"
      );
      is(await runPython(`range.GetText(-1)`), "h3", "range text correct");
      info("checking StyleId");
      ok(
        await runPython(`
          styleId = range.GetAttributeValue(UIA_StyleIdAttributeId)
          return styleId == StyleId_Heading3
        `),
        "StyleId correct"
      );

      // ================== UIA_StyleIdAttributeId - StyleId_Quote ==================
      await runPython(`
        global range
        blockquoteContainerAcc = findUiaByDomId(doc, "blockquote-container")
        range = docText.RangeFromChild(blockquoteContainerAcc)
      `);
      is(
        await runPython(`range.GetText(-1)`),
        "abquotecd",
        "range text correct"
      );
      info("checking mixed StyleId properties");
      ok(
        await runPython(`
          val = range.GetAttributeValue(UIA_StyleIdAttributeId)
          return val == uiaClient.ReservedMixedAttributeValue
        `),
        "StyleId correct (mixed)"
      );
      info("Moving to blockquote text run");
      is(
        await runPython(`range.Move(TextUnit_Format, 1)`),
        1,
        "Move return correct"
      );
      is(await runPython(`range.GetText(-1)`), "quote", "range text correct");
      info("checking StyleId");
      ok(
        await runPython(`
          styleId = range.GetAttributeValue(UIA_StyleIdAttributeId)
          return styleId == StyleId_Quote
        `),
        "StyleId correct"
      );

      // ================== UIA_StyleIdAttributeId - StyleId_Emphasis ==================
      await runPython(`
        global range
        emphasisContainerAcc = findUiaByDomId(doc, "emphasis-container")
        range = docText.RangeFromChild(emphasisContainerAcc)
      `);
      is(
        await runPython(`range.GetText(-1)`),
        "abemphcd",
        "range text correct"
      );
      info("checking mixed StyleId properties");
      ok(
        await runPython(`
          val = range.GetAttributeValue(UIA_StyleIdAttributeId)
          return val == uiaClient.ReservedMixedAttributeValue
        `),
        "StyleId correct (mixed)"
      );
      info("Moving to emphasized text run");
      is(
        await runPython(`range.Move(TextUnit_Format, 1)`),
        1,
        "Move return correct"
      );
      is(await runPython(`range.GetText(-1)`), "emph", "range text correct");
      info("checking StyleId");
      ok(
        await runPython(`
          styleId = range.GetAttributeValue(UIA_StyleIdAttributeId)
          return styleId == StyleId_Emphasis
        `),
        "StyleId correct"
      );
    }
  },
  { urlSuffix: "#:~:text=highlighted%20phrase" }
);

/**
 * Test the GetAttributeValue method when backspacing a character at the end of
 * a document.
 */
addUiaTask(
  `a<input id="input" value="bc">`,
  async function testTextRangeGetAttributeValueBackspaceAtDocEnd(
    browser,
    docAcc
  ) {
    const input = findAccessibleChildByID(docAcc, "input");
    info("Focusing input");
    let moved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
    input.takeFocus();
    await moved;
    info("Pressing end");
    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
    EventUtils.synthesizeKey("KEY_End");
    await moved;
    await runPython(`
      global doc, range
      doc = getDocUia()
      input = findUiaByDomId(doc, "input")
      text = getUiaPattern(input, "Text")
      range = text.GetSelection().GetElement(0)
    `);
    // `range` is collapsed at the end of the input, after "c".
    is(
      await runPython(`range.GetAttributeValue(UIA_IsReadOnlyAttributeId)`),
      false,
      "IsReadOnly correct"
    );
    info("Backspacing c");
    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
    EventUtils.synthesizeKey("KEY_Backspace");
    await moved;
    is(
      await runPython(`range.GetAttributeValue(UIA_IsReadOnlyAttributeId)`),
      false,
      "IsReadOnly correct"
    );
  }
);

/**
 * Test the TextRange pattern's Move method.
 */
addUiaTask(
  `
<p>ab</p>
<textarea id="textarea">cd ef gh</textarea>
<p>ij</p>
  `,
  async function testTextRangeMove() {
    await runPython(`
      doc = getDocUia()
      textarea = findUiaByDomId(doc, "textarea")
      text = getUiaPattern(textarea, "Text")
      global range
      range = text.DocumentRange
    `);
    is(await runPython(`range.GetText(-1)`), "cd ef gh", "range text correct");
    info("Moving 1 word");
    is(
      await runPython(`range.Move(TextUnit_Word, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "ef ", "range text correct");
    info("Moving 3 words");
    // There are only 2 words after.
    is(
      await runPython(`range.Move(TextUnit_Word, 3)`),
      2,
      "Move return correct"
    );
    // The IA2 -> UIA proxy gets most things below this wrong.
    if (!gIsUiaEnabled) {
      return;
    }
    is(await runPython(`range.GetText(-1)`), "ij", "range text correct");
    info("Moving -5 words");
    // There are only 4 words before.
    is(
      await runPython(`range.Move(TextUnit_Word, -5)`),
      -4,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "ab", "range text correct");
    info("Moving 1 word");
    is(
      await runPython(`range.Move(TextUnit_Word, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "cd ", "range text correct");
    info("Moving 1 character");
    is(
      await runPython(`range.Move(TextUnit_Character, 1)`),
      1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "d", "range text correct");
    // When the range is not collapsed, Move moves backward to the start of the
    // unit before moving to the requested unit.
    info("Moving -1 word");
    is(
      await runPython(`range.Move(TextUnit_Word, -1)`),
      -1,
      "Move return correct"
    );
    is(await runPython(`range.GetText(-1)`), "ab", "range text correct");
    info("Collapsing to start");
    await runPython(
      `range.MoveEndpointByRange(TextPatternRangeEndpoint_End, range, TextPatternRangeEndpoint_Start)`
    );
    is(await runPython(`range.GetText(-1)`), "", "range text correct");
    // range is now collapsed at "a".
    info("Moving 1 word");
    is(
      await runPython(`range.Move(TextUnit_Word, 1)`),
      1,
      "Move return correct"
    );
    // range is now collapsed at "c".
    is(await runPython(`range.GetText(-1)`), "", "range text correct");
    info("Expanding to character");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Character)`);
    is(await runPython(`range.GetText(-1)`), "c", "range text correct");
    info("Collapsing to end");
    await runPython(
      `range.MoveEndpointByRange(TextPatternRangeEndpoint_Start, range, TextPatternRangeEndpoint_End)`
    );
    // range is now collapsed at "d".
    // When the range is collapsed, Move does *not* first move back to the start
    // of the unit.
    info("Moving -1 word");
    is(
      await runPython(`range.Move(TextUnit_Word, -1)`),
      -1,
      "Move return correct"
    );
    // range is collapsed at "c".
    is(await runPython(`range.GetText(-1)`), "", "range text correct");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Word)`);
    is(await runPython(`range.GetText(-1)`), "cd ", "range text correct");
  }
);

/**
 * Test the TextRange pattern's MoveEndpointByRange method.
 */
addUiaTask(
  `
<p>ab</p>
<div><textarea id="textarea">cd ef gh</textarea></div>
<p>ij</p>
  `,
  async function testTextRangeMoveEndpointByRange() {
    await runPython(`
      global doc, taRange, range
      doc = getDocUia()
      textarea = findUiaByDomId(doc, "textarea")
      text = getUiaPattern(textarea, "Text")
      taRange = text.DocumentRange
      range = text.DocumentRange
    `);
    is(await runPython(`range.GetText(-1)`), "cd ef gh", "range text correct");
    info("Expanding to character");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Character)`);
    is(await runPython(`range.GetText(-1)`), "c", "range text correct");
    is(
      await runPython(
        `range.CompareEndpoints(TextPatternRangeEndpoint_Start, range, TextPatternRangeEndpoint_End)`
      ),
      -1,
      "start < end"
    );
    info("Moving end to start");
    await runPython(
      `range.MoveEndpointByRange(TextPatternRangeEndpoint_End, range, TextPatternRangeEndpoint_Start)`
    );
    is(
      await runPython(
        `range.CompareEndpoints(TextPatternRangeEndpoint_Start, range, TextPatternRangeEndpoint_End)`
      ),
      0,
      "start == end"
    );
    info("Moving range end to textarea end");
    await runPython(
      `range.MoveEndpointByRange(TextPatternRangeEndpoint_End, taRange, TextPatternRangeEndpoint_End)`
    );
    is(await runPython(`range.GetText(-1)`), "cd ef gh", "range text correct");
    info("Expanding to character");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Character)`);
    is(await runPython(`range.GetText(-1)`), "c", "range text correct");
    info("Moving range start to textarea end");
    await runPython(
      `range.MoveEndpointByRange(TextPatternRangeEndpoint_Start, taRange, TextPatternRangeEndpoint_End)`
    );
    is(
      await runPython(
        `range.CompareEndpoints(TextPatternRangeEndpoint_Start, taRange, TextPatternRangeEndpoint_End)`
      ),
      0,
      "range start == textarea end"
    );
    is(
      await runPython(
        `range.CompareEndpoints(TextPatternRangeEndpoint_End, taRange, TextPatternRangeEndpoint_End)`
      ),
      0,
      "range end == textarea end"
    );
    info("Moving range end to textarea start");
    await runPython(
      `range.MoveEndpointByRange(TextPatternRangeEndpoint_End, taRange, TextPatternRangeEndpoint_Start)`
    );
    is(
      await runPython(
        `range.CompareEndpoints(TextPatternRangeEndpoint_Start, taRange, TextPatternRangeEndpoint_Start)`
      ),
      0,
      "range start == textarea start"
    );
    is(
      await runPython(
        `range.CompareEndpoints(TextPatternRangeEndpoint_End, taRange, TextPatternRangeEndpoint_Start)`
      ),
      0,
      "range end == textarea start"
    );
    await definePyVar("docRange", `getUiaPattern(doc, "Text").DocumentRange`);
    info("Moving range start to document start");
    await runPython(
      `range.MoveEndpointByRange(TextPatternRangeEndpoint_Start, docRange, TextPatternRangeEndpoint_Start)`
    );
    info("Moving range end to document end");
    await runPython(
      `range.MoveEndpointByRange(TextPatternRangeEndpoint_End, docRange, TextPatternRangeEndpoint_End)`
    );
    is(
      await runPython(
        `range.CompareEndpoints(TextPatternRangeEndpoint_Start, docRange, TextPatternRangeEndpoint_Start)`
      ),
      0,
      "range start == document start"
    );
    is(
      await runPython(
        `range.CompareEndpoints(TextPatternRangeEndpoint_End, docRange, TextPatternRangeEndpoint_End)`
      ),
      0,
      "range end == document end"
    );
  }
);

/**
 * Test the TextRange pattern's MoveEndpointByUnit method.
 */
addUiaTask(
  `
<p>ab</p>
<textarea id="textarea">cd ef gh</textarea>
<p>ij</p>
  `,
  async function testTextRangeMoveEndpointByUnit() {
    await runPython(`
      doc = getDocUia()
      textarea = findUiaByDomId(doc, "textarea")
      text = getUiaPattern(textarea, "Text")
      global range
      range = text.DocumentRange
    `);
    is(await runPython(`range.GetText(-1)`), "cd ef gh", "range text correct");
    info("Moving end -1 word");
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Word, -1)`
      ),
      -1,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "cd ef ", "range text correct");
    info("Moving end -4 words");
    // There are only 3 words before.
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Word, -4)`
      ),
      -3,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "", "range text correct");
    info("Moving start 1 word");
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Word, 1)`
      ),
      1,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "", "range text correct");
    info("Moving end 1 character");
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Character, 1)`
      ),
      1,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "c", "range text correct");
    info("Moving start 5 words");
    // There are only 4 word boundaries after.
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Word, 5)`
      ),
      4,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "", "range text correct");
    info("Moving end -1 word");
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Word, -1)`
      ),
      -1,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "", "range text correct");
    info("Moving end 1 character");
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Character, 1)`
      ),
      1,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "i", "range text correct");
  }
);

/**
 * Test the Text pattern's SupportedTextSelection property.
 */
addUiaTask(
  `
<style>
body {
  user-select: none;
}
</style>
<input id="input">
<p id="p">p</p>
  `,
  async function testTextSupportedTextSelection() {
    let result = await runPython(`
      global doc
      doc = getDocUia()
      input = findUiaByDomId(doc, "input")
      text = getUiaPattern(input, "Text")
      return text.SupportedTextSelection
    `);
    is(
      result,
      SupportedTextSelection_Multiple,
      "input SupportedTextSelection correct"
    );
    if (gIsUiaEnabled) {
      // The IA2 -> UIA proxy doesn't expose the Text pattern on this text leaf.
      is(
        await runPython(`
          p = findUiaByDomId(doc, "p")
          pLeaf = uiaClient.RawViewWalker.GetFirstChildElement(p)
          text = getUiaPattern(pLeaf, "Text")
          return text.SupportedTextSelection
        `),
        SupportedTextSelection_None,
        "pLeaf SupportedTextSelection correct"
      );
      // The IA2 -> UIA proxy doesn't understand that text isn't selectable in
      // this document.
      is(
        await runPython(`getUiaPattern(doc, "Text").SupportedTextSelection`),
        SupportedTextSelection_None,
        "doc SupportedTextSelection correct"
      );
    }
  }
);

/**
 * Test the Text pattern's SupportedTextSelection property on a document with a
 * selectable body.
 */
addUiaTask(
  `<p id="p">p</p>`,
  async function testTextSupportedTextSelectionSelectableBody() {
    is(
      await runPython(`
        global doc
        doc = getDocUia()
        text = getUiaPattern(doc, "Text")
        return text.SupportedTextSelection
      `),
      SupportedTextSelection_Multiple,
      "doc SupportedTextSelection correct"
    );
    // The IA2 -> UIA proxy doesn't expose the Text pattern on this text leaf.
    if (gIsUiaEnabled) {
      is(
        await runPython(`
          p = findUiaByDomId(doc, "p")
          pLeaf = uiaClient.RawViewWalker.GetFirstChildElement(p)
          text = getUiaPattern(pLeaf, "Text")
          return text.SupportedTextSelection
        `),
        SupportedTextSelection_Multiple,
        "pLeaf SupportedTextSelection correct"
      );
    }
  }
);

/**
 * Test the Text pattern's GetSelection method with the caret.
 */
addUiaTask(
  `<textarea id="textarea" cols="2">ab cd</textarea>`,
  async function testTextGetSelectionCaret(browser, docAcc) {
    await runPython(`
      doc = getDocUia()
      textarea = findUiaByDomId(doc, "textarea")
      global text
      text = getUiaPattern(textarea, "Text")
    `);
    is(await runPython(`text.GetSelection().Length`), 0, "No selection");
    info("Focusing textarea");
    const textarea = findAccessibleChildByID(docAcc, "textarea", [
      nsIAccessibleText,
    ]);
    let moved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    textarea.takeFocus();
    await moved;
    is(await runPython(`text.GetSelection().Length`), 1, "1 selection");
    await definePyVar("range", `text.GetSelection().GetElement(0)`);
    ok(await runPython(`bool(range)`), "Got selection range 0");
    info("Expanding to character");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Character)`);
    is(await runPython(`range.GetText(-1)`), "a", "range text correct");

    info("Pressing ArrowRight");
    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    EventUtils.synthesizeKey("KEY_ArrowRight");
    await moved;
    is(await runPython(`text.GetSelection().Length`), 1, "1 selection");
    await definePyVar("range", `text.GetSelection().GetElement(0)`);
    ok(await runPython(`bool(range)`), "Got selection range 0");
    info("Expanding to character");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Character)`);
    is(await runPython(`range.GetText(-1)`), "b", "range text correct");

    // The IA2 -> UIA proxy doesn't handle the insertion point at the end of a
    // line correctly.
    if (!gIsUiaEnabled) {
      return;
    }

    // Test the insertion point at the end of a wrapped line.
    info("Pressing End");
    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    EventUtils.synthesizeKey("KEY_End");
    await moved;
    is(await runPython(`text.GetSelection().Length`), 1, "1 selection");
    await definePyVar("range", `text.GetSelection().GetElement(0)`);
    ok(await runPython(`bool(range)`), "Got selection range 0");
    info("Expanding to character");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Character)`);
    is(await runPython(`range.GetText(-1)`), "", "range text correct");
    info("Moving end 1 character");
    await runPython(
      `range.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Character, 1)`
    );
    is(await runPython(`range.GetText(-1)`), "c", "range text correct");
    info("Expanding to line at caret");
    await definePyVar("range", `text.GetSelection().GetElement(0)`);
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Line)`);
    is(await runPython(`range.GetText(-1)`), "ab ", "range text correct");

    // Test the insertion point at the end of the textarea.
    info("Pressing Ctrl+End");
    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    EventUtils.synthesizeKey("KEY_End", { ctrlKey: true });
    await moved;
    is(await runPython(`text.GetSelection().Length`), 1, "1 selection");
    await definePyVar("range", `text.GetSelection().GetElement(0)`);
    ok(await runPython(`bool(range)`), "Got selection range 0");
    info("Expanding to character");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Character)`);
    is(await runPython(`range.GetText(-1)`), "", "range text correct");
    info("Expanding to line");
    await definePyVar("range", `text.GetSelection().GetElement(0)`);
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Line)`);
    is(await runPython(`range.GetText(-1)`), "cd", "range text correct");

    info("Clicking mouse at b");
    // BrowserTestUtils.synthesizeMouseAtPoint takes coordinates relative to the document.
    const docX = {};
    const docY = {};
    docAcc.getBounds(docX, docY, {}, {});
    let charX = {};
    let charY = {};
    textarea.getCharacterExtents(
      1,
      charX,
      charY,
      {},
      {},
      COORDTYPE_SCREEN_RELATIVE
    );
    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    await BrowserTestUtils.synthesizeMouseAtPoint(
      charX.value - docX.value,
      charY.value - docY.value,
      {},
      docAcc.browsingContext
    );
    await moved;
    is(await runPython(`text.GetSelection().Length`), 1, "1 selection");
    await definePyVar("range", `text.GetSelection().GetElement(0)`);
    ok(await runPython(`bool(range)`), "Got selection range 0");
    info("Expanding to character");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Character)`);
    is(await runPython(`range.GetText(-1)`), "b", "range text correct");
  }
);

/**
 * Test the Text pattern's GetSelection method with selection.
 */
addUiaTask(
  `<textarea id="textarea" cols="3">ab cd</textarea>`,
  async function testTextGetSelectionSelection(browser, docAcc) {
    await runPython(`
      doc = getDocUia()
      textarea = findUiaByDomId(doc, "textarea")
      global text
      text = getUiaPattern(textarea, "Text")
    `);
    is(await runPython(`text.GetSelection().Length`), 0, "No selection");
    info("Focusing textarea");
    const textarea = findAccessibleChildByID(docAcc, "textarea", [
      nsIAccessibleText,
    ]);
    let moved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
    textarea.takeFocus();
    await moved;
    is(await runPython(`text.GetSelection().Length`), 1, "1 selection");
    await definePyVar("range", `text.GetSelection().GetElement(0)`);
    ok(await runPython(`bool(range)`), "Got selection range 0");
    is(await runPython(`range.GetText(-1)`), "", "range text correct");

    info("Selecting ab");
    moved = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, textarea);
    textarea.addSelection(0, 2);
    await moved;
    is(await runPython(`text.GetSelection().Length`), 1, "1 selection");
    await definePyVar("range", `text.GetSelection().GetElement(0)`);
    ok(await runPython(`bool(range)`), "Got selection range 0");
    is(await runPython(`range.GetText(-1)`), "ab", "range text correct");

    info("Adding cd to selection");
    moved = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, textarea);
    textarea.addSelection(3, 5);
    await moved;
    is(await runPython(`text.GetSelection().Length`), 2, "2 selections");
    await definePyVar("range", `text.GetSelection().GetElement(0)`);
    ok(await runPython(`bool(range)`), "Got selection range 0");
    is(await runPython(`range.GetText(-1)`), "ab", "range text correct");
    await definePyVar("range", `text.GetSelection().GetElement(1)`);
    ok(await runPython(`bool(range)`), "Got selection range 1");
    is(await runPython(`range.GetText(-1)`), "cd", "range text correct");
  }
);

/**
 * Test the Text pattern's TextSelectionChanged event.
 */
addUiaTask(
  `
<input id="input" value="abc">
<div id="editable" contenteditable role="textbox"><p>de</p><p>f</p></div>
  `,
  async function testTextTextSelectionChanged(browser) {
    info("Focusing input");
    await setUpWaitForUiaEvent("Text_TextSelectionChanged", "input");
    await invokeContentTask(browser, [], () => {
      content.document.getElementById("input").focus();
    });
    await waitForUiaEvent();
    ok(true, "input got TextSelectionChanged event");
    info("Moving caret to b");
    await setUpWaitForUiaEvent("Text_TextSelectionChanged", "input");
    await invokeContentTask(browser, [], () => {
      content.document.getElementById("input").setSelectionRange(1, 1);
    });
    await waitForUiaEvent();
    ok(true, "input got TextSelectionChanged event");
    info("Selecting bc");
    await setUpWaitForUiaEvent("Text_TextSelectionChanged", "input");
    await invokeContentTask(browser, [], () => {
      content.document.getElementById("input").setSelectionRange(1, 3);
    });
    await waitForUiaEvent();
    ok(true, "input got TextSelectionChanged event");

    info("Focusing editable");
    await setUpWaitForUiaEvent("Text_TextSelectionChanged", "editable");
    await invokeContentTask(browser, [], () => {
      content._editable = content.document.getElementById("editable");
      content._editable.focus();
    });
    await waitForUiaEvent();
    ok(true, "editable got TextSelectionChanged event");
    info("Moving caret to e");
    await setUpWaitForUiaEvent("Text_TextSelectionChanged", "editable");
    await invokeContentTask(browser, [], () => {
      content._de = content._editable.firstChild.firstChild;
      content.getSelection().setBaseAndExtent(content._de, 1, content._de, 1);
    });
    await waitForUiaEvent();
    ok(true, "editable got TextSelectionChanged event");
    info("Selecting ef");
    await setUpWaitForUiaEvent("Text_TextSelectionChanged", "editable");
    await invokeContentTask(browser, [], () => {
      const f = content._editable.children[1].firstChild;
      content.getSelection().setBaseAndExtent(content._de, 1, f, 1);
    });
    await waitForUiaEvent();
    ok(true, "editable got TextSelectionChanged event");
  }
);

/**
 * Test the Text pattern's TextChanged event.
 */
addUiaTask(
  `
<input id="input" value="abc">
<div id="editable" contenteditable role="textbox">
  <p id="de">de</p>
  <p>f</p>
</div>
  `,
  async function testTextTextChanged(browser) {
    info("Focusing input");
    let moved = waitForEvent(EVENT_TEXT_CARET_MOVED, "input");
    await invokeContentTask(browser, [], () => {
      content.document.getElementById("input").focus();
    });
    await moved;
    info("Deleting a");
    await setUpWaitForUiaEvent("Text_TextChanged", "input");
    await invokeContentTask(browser, [], () => {
      content.document.execCommand("forwardDelete");
    });
    await waitForUiaEvent();
    ok(true, "input got TextChanged event");
    info("Inserting a");
    await setUpWaitForUiaEvent("Text_TextChanged", "input");
    await invokeContentTask(browser, [], () => {
      content.document.execCommand("insertText", false, "a");
    });
    await waitForUiaEvent();
    ok(true, "input got TextChanged event");

    info("Focusing editable");
    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, "de");
    await invokeContentTask(browser, [], () => {
      content.document.getElementById("editable").focus();
    });
    await moved;
    info("Deleting a");
    await setUpWaitForUiaEvent("Text_TextChanged", "editable");
    await invokeContentTask(browser, [], () => {
      content.document.execCommand("forwardDelete");
    });
    await waitForUiaEvent();
    ok(true, "editable got TextChanged event");
    info("Inserting a");
    await setUpWaitForUiaEvent("Text_TextChanged", "editable");
    await invokeContentTask(browser, [], () => {
      content.document.execCommand("insertText", false, "a");
    });
    await waitForUiaEvent();
    ok(true, "editable got TextChanged event");
  }
);

/**
 * Test the TextRange pattern's GetEnclosingElement method.
 */
addUiaTask(
  `
<div id="editable" contenteditable role="textbox">
  ab
  <mark id="cdef"><span>cd</span> <a id="ef" href="/">ef</a></mark>
  <a href="/"><img id="g" src="https://example.com/a11y/accessible/tests/mochitest/moz.png" alt="g"></a>
  <p><button id="h">h</button></p>
</div>
  `,
  async function testTextRangeGetEnclosingElement() {
    info("Getting editable DocumentRange");
    await runPython(`
      doc = getDocUia()
      editable = findUiaByDomId(doc, "editable")
      text = getUiaPattern(editable, "Text")
      global range
      range = text.DocumentRange
    `);
    is(
      await runPython(`range.GetEnclosingElement().CurrentAutomationId`),
      "editable",
      "EnclosingElement is editable"
    );
    info("Expanding to word");
    await runPython(`range.ExpandToEnclosingUnit(TextUnit_Word)`);
    // Range is now "ab ".
    // The IA2 -> UIA proxy gets this wrong.
    if (gIsUiaEnabled) {
      is(
        await runPython(`range.GetEnclosingElement().CurrentName`),
        "ab ",
        "EnclosingElement is ab text leaf"
      );
    }
    info("Moving 1 word");
    await runPython(`range.Move(TextUnit_Word, 1)`);
    // Range is now "cd ".
    // The "cd" text leaf doesn't include the space, so the enclosing element is
    // its parent.
    is(
      await runPython(`range.GetEnclosingElement().CurrentAutomationId`),
      "cdef",
      "EnclosingElement is cdef"
    );
    info("Moving end -1 character");
    await runPython(
      `range.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Character, -1)`
    );
    // Range is now "cd".
    // The IA2 -> UIA proxy gets this wrong.
    if (gIsUiaEnabled) {
      is(
        await runPython(`range.GetEnclosingElement().CurrentName`),
        "cd",
        "EnclosingElement is cd text leaf"
      );
    }
    info("Moving 1 word");
    await runPython(`range.Move(TextUnit_Word, 1)`);
    // Range is now "ef ".
    // Neither the "ef" text leaf/link nor the "cdef" mark include the trailing
    // space, so the enclosing element is cdef's parent.
    is(
      await runPython(`range.GetEnclosingElement().CurrentAutomationId`),
      "editable",
      "EnclosingElement is editable"
    );
    info("Moving end -1 character");
    await runPython(
      `range.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Character, -1)`
    );
    // Range is now "ef". The innermost element is the text leaf, but "ef" is a
    // link and that's what Narrator wants.
    is(
      await runPython(`range.GetEnclosingElement().CurrentAutomationId`),
      "ef",
      "EnclosingElement is ef"
    );
    // The IA2 -> UIA proxy gets the rest of this wrong.
    if (!gIsUiaEnabled) {
      return;
    }
    info("Moving 1 word");
    await runPython(`range.Move(TextUnit_Word, 1)`);
    // Range is now the embedded object character for the img (g).
    is(
      await runPython(`range.GetEnclosingElement().CurrentAutomationId`),
      "g",
      "EnclosingElement is g"
    );
    info("Moving 1 word");
    await runPython(`range.Move(TextUnit_Word, 1)`);
    // Range is now "h". "h" is a button and buttons prune their children, so
    // UIA doesn't see the text leaf.
    is(
      await runPython(`range.GetEnclosingElement().CurrentAutomationId`),
      "h",
      "EnclosingElement is h"
    );
  }
);

/**
 * Test the TextRange pattern's GetChildren method.
 */
addUiaTask(
  `<div id="editable" contenteditable role="textbox">ab <span id="cdef" role="button"><span>cd</span> <a id="ef" href="/">ef</a> </span><img id="g" src="https://example.com/a11y/accessible/tests/mochitest/moz.png" alt="g"></div>`,
  async function testTextRangeGetChildren() {
    info("Getting editable DocumentRange");
    await runPython(`
      doc = getDocUia()
      editable = findUiaByDomId(doc, "editable")
      text = getUiaPattern(editable, "Text")
      global r
      r = text.DocumentRange
    `);
    await isUiaElementArray(
      `r.GetChildren()`,
      ["cdef", "g"],
      "Children are correct"
    );
    info("Expanding to word");
    await runPython(`r.ExpandToEnclosingUnit(TextUnit_Word)`);
    // Range is now "ab ".
    await isUiaElementArray(`r.GetChildren()`, [], "Children are correct");
    info("Moving 1 word");
    await runPython(`r.Move(TextUnit_Word, 1)`);
    // Range is now "cd ".
    await isUiaElementArray(`r.GetChildren()`, [], "Children are correct");
    info("Moving 1 word");
    await runPython(`r.Move(TextUnit_Word, 1)`);
    // Range is now "ef ". The range includes the link but is not completely
    // enclosed by the link.
    await isUiaElementArray(`r.GetChildren()`, ["ef"], "Children are correct");
    info("Moving end -1 character");
    await runPython(
      `r.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Character, -1)`
    );
    // Range is now "ef". The range encloses the link, so there are no children.
    await isUiaElementArray(`r.GetChildren()`, [], "Children are correct");
    info("Moving 1 word");
    await runPython(`r.Move(TextUnit_Word, 1)`);
    // Range is now the embedded object character for the img (g). The range is
    // completely enclosed by the image.
    // The IA2 -> UIA proxy gets this wrong.
    if (gIsUiaEnabled) {
      await isUiaElementArray(`r.GetChildren()`, [], "Children are correct");
    }
  }
);

/**
 * Test the Text pattern's RangeFromChild method.
 */
addUiaTask(
  `<div id="editable" contenteditable role="textbox">ab <mark id="cdef"><span>cd</span> <a id="ef" href="/">ef</a></mark> <img id="g" src="https://example.com/a11y/accessible/tests/mochitest/moz.png" alt="g"></div>`,
  async function testTextRangeFromChild() {
    await runPython(`
      global doc, docText, editable, edText
      doc = getDocUia()
      docText = getUiaPattern(doc, "Text")
      editable = findUiaByDomId(doc, "editable")
      edText = getUiaPattern(editable, "Text")
    `);
    is(
      await runPython(`docText.RangeFromChild(editable).GetText(-1)`),
      `ab cd ef ${kEmbedChar}`,
      "doc returned correct range for editable"
    );
    await testPythonRaises(
      `edText.RangeFromChild(editable)`,
      "editable correctly failed to return range for editable"
    );
    is(
      await runPython(`docText.RangeFromChild(editable).GetText(-1)`),
      `ab cd ef ${kEmbedChar}`,
      "doc returned correct range for editable"
    );
    let text = await runPython(`
      ab = uiaClient.RawViewWalker.GetFirstChildElement(editable)
      range = docText.RangeFromChild(ab)
      return range.GetText(-1)
    `);
    is(text, "ab ", "doc returned correct range for ab");
    text = await runPython(`
      global cdef
      cdef = findUiaByDomId(doc, "cdef")
      range = docText.RangeFromChild(cdef)
      return range.GetText(-1)
    `);
    is(text, "cd ef", "doc returned correct range for cdef");
    text = await runPython(`
      cd = uiaClient.RawViewWalker.GetFirstChildElement(cdef)
      range = docText.RangeFromChild(cd)
      return range.GetText(-1)
    `);
    is(text, "cd", "doc returned correct range for cd");
    text = await runPython(`
      global efLink
      efLink = findUiaByDomId(doc, "ef")
      range = docText.RangeFromChild(efLink)
      return range.GetText(-1)
    `);
    is(text, "ef", "doc returned correct range for ef link");
    text = await runPython(`
      efLeaf = uiaClient.RawViewWalker.GetFirstChildElement(efLink)
      range = docText.RangeFromChild(efLeaf)
      return range.GetText(-1)
    `);
    is(text, "ef", "doc returned correct range for ef leaf");
    text = await runPython(`
      g = findUiaByDomId(doc, "g")
      range = docText.RangeFromChild(g)
      return range.GetText(-1)
    `);
    is(text, kEmbedChar, "doc returned correct range for g");
  },
  // The IA2 -> UIA proxy has too many quirks/bugs here.
  { uiaEnabled: true, uiaDisabled: false }
);

/**
 * Test the Text pattern's RangeFromPoint method.
 */
addUiaTask(
  `<div id="test">a <span>b </span>c</div>`,
  async function testTextRangeFromPoint(browser, docAcc) {
    const acc = findAccessibleChildByID(docAcc, "test", [nsIAccessibleText]);
    await runPython(`
      global doc, docText
      doc = getDocUia()
      docText = getUiaPattern(doc, "Text")
    `);

    // Walk through every offset in the accessible and hit test each. Verify
    // that the returned range is empty, and that it hit the right character.
    for (let offset = 0; offset < acc.characterCount; ++offset) {
      const x = {};
      const y = {};
      acc.getCharacterExtents(offset, x, y, {}, {}, COORDTYPE_SCREEN_RELATIVE);
      await runPython(`
        global range
        range = docText.RangeFromPoint(POINT(${x.value}, ${y.value}))`);
      is(
        await runPython(`range.GetText(-1)`),
        ``,
        "doc returned correct empty range"
      );
      await runPython(`range.ExpandToEnclosingUnit(TextUnit_Character)`);
      const charAtOffset = acc.getCharacterAtOffset(offset);
      is(
        await runPython(`range.GetText(-1)`),
        `${charAtOffset}`,
        "doc returned correct range"
      );
    }

    // An arbitrary invalid point should cause an invalid argument error.
    await testPythonRaises(
      `docText.RangeFromPoint(POINT(9999999999, 9999999999))`,
      "no text leaves at invalid point"
    );
  },
  { uiaEnabled: true, uiaDisabled: true }
);

/**
 * Test the TextRange pattern's GetBoundingRectangles method.
 */
addUiaTask(
  `
<div id="test"><p id="line1">abc</p><p id="line2">d</p><p id="line3"></p></div>
<div id="offscreen" style="position:absolute; left:200vw;">xyz</div>
  `,
  async function testTextRangeGetBoundingRectangles(browser, docAcc) {
    const line1 = findAccessibleChildByID(docAcc, "line1", [nsIAccessibleText]);
    const line2 = findAccessibleChildByID(docAcc, "line2", [nsIAccessibleText]);

    const lineRects = await runPython(`
      global doc, docText, testAcc, range
      doc = getDocUia()
      docText = getUiaPattern(doc, "Text")
      testAcc = findUiaByDomId(doc, "test")
      range = docText.RangeFromChild(testAcc)
      return range.GetBoundingRectangles()
    `);

    is(lineRects.length, 8, "GetBoundingRectangles returned two rectangles");
    const firstLineRect = [
      lineRects[0],
      lineRects[1],
      lineRects[2],
      lineRects[3],
    ];
    const secondLineRect = [
      lineRects[4],
      lineRects[5],
      lineRects[6],
      lineRects[7],
    ];
    testTextBounds(line1, 0, -1, firstLineRect, COORDTYPE_SCREEN_RELATIVE);
    testTextBounds(line2, 0, -1, secondLineRect, COORDTYPE_SCREEN_RELATIVE);
    // line3 has no rectangle - GetBoundingRectangles shouldn't return anything for empty lines.

    // GetBoundingRectangles shouldn't return anything for offscreen lines.
    const offscreenRects = await runPython(`
      global offscreenAcc, range
      offscreenAcc = findUiaByDomId(doc, "offscreen")
      range = docText.RangeFromChild(offscreenAcc)
      return range.GetBoundingRectangles()
    `);
    is(
      offscreenRects.length,
      0,
      "GetBoundingRectangles returned no rectangles"
    );
  },
  { uiaEnabled: true, uiaDisabled: true, chrome: true }
);

/**
 * Test char bounds with the TextRange pattern's GetBoundingRectangles method.
 */
addUiaTask(
  `<div id="test">abc</div>`,
  async function testTextRangeGetBoundingRectanglesChar(browser, docAcc) {
    const testAcc = findAccessibleChildByID(docAcc, "test", [
      nsIAccessibleText,
    ]);
    const charX = {};
    const charY = {};
    const charW = {};
    const charH = {};
    testAcc.getCharacterExtents(
      0,
      charX,
      charY,
      charW,
      charH,
      COORDTYPE_SCREEN_RELATIVE
    );

    await runPython(`
      global doc, docText, testAcc, range
      doc = getDocUia()
      docText = getUiaPattern(doc, "Text")
      testAcc = findUiaByDomId(doc, "test")
      range = docText.RangeFromChild(testAcc)
      range.ExpandToEnclosingUnit(TextUnit_Character)
    `);
    is(await runPython(`range.GetText(-1)`), "a", "range text correct");

    const uiaRect = await runPython(`range.GetBoundingRectangles()`);
    is(uiaRect.length, 4, "GetBoundingRectangles returned one rectangle");
    is(uiaRect[0], charX.value, "UIA char rect X matches core char rect X");
    is(uiaRect[1], charY.value, "UIA char rect Y matches core char rect Y");
    is(uiaRect[2], charW.value, "UIA char rect W matches core char rect W");
    is(uiaRect[3], charH.value, "UIA char rect H matches core char rect H");
  },
  { uiaEnabled: true, uiaDisabled: true, chrome: true }
);

/**
 * Test special case line bounds with the TextRange pattern's
 * GetBoundingRectangles method.
 */
addUiaTask(
  `
<div><span id="line-break" tabindex="-1">ABC<br/></span>DEF</div>
<p style="width: 1px;"><span id="wrapping" tabindex="-1">ABC</span> DEF</p>
  `,
  async function testTextRangeGetBoundingRectanglesLine(browser, docAcc) {
    const lineBreakAcc = findAccessibleChildByID(docAcc, "line-break", [
      nsIAccessibleText,
    ]);
    const wrappingAcc = findAccessibleChildByID(docAcc, "wrapping", [
      nsIAccessibleText,
    ]);

    let lineRects = await runPython(`
      global doc, docText, testAcc
      doc = getDocUia()
      docText = getUiaPattern(doc, "Text")
      testAcc = findUiaByDomId(doc, "line-break")
      range = docText.RangeFromChild(testAcc)
      return range.GetBoundingRectangles()
    `);

    is(lineRects.length, 4, "GetBoundingRectangles returned one rectangle");
    const lineBreakLineRect = [
      lineRects[0],
      lineRects[1],
      lineRects[2],
      lineRects[3],
    ];
    testTextBounds(
      lineBreakAcc,
      0,
      -1,
      lineBreakLineRect,
      COORDTYPE_SCREEN_RELATIVE
    );

    lineRects = await runPython(`
      global doc, docText, testAcc
      testAcc = findUiaByDomId(doc, "wrapping")
      range = docText.RangeFromChild(testAcc)
      return range.GetBoundingRectangles()
    `);
    is(lineRects.length, 4, "GetBoundingRectangles returned one rectangle");
    const wrappingLineRect = [
      lineRects[0],
      lineRects[1],
      lineRects[2],
      lineRects[3],
    ];
    testTextBounds(
      wrappingAcc,
      0,
      -1,
      wrappingLineRect,
      COORDTYPE_SCREEN_RELATIVE
    );
  },
  { uiaEnabled: true, uiaDisabled: true, chrome: true }
);

/**
 * Test the Text pattern's GetBoundingRectangles method with the caret.
 */
addUiaTask(
  `
<div id="editable" contenteditable role="textbox">
  <div id="ce0">a</div>
  <div id="ce1"><br></div>
  <div id="ce2">b</div>
</div>
  `,
  async function testTextRangeGetBoundingRectanglesCaret(browser, docAcc) {
    info("Focusing editable");
    const editable = findAccessibleChildByID(docAcc, "editable");
    const ce0 = findAccessibleChildByID(docAcc, "ce0");
    let moved = waitForEvent(EVENT_TEXT_CARET_MOVED, ce0);
    editable.takeFocus();
    await moved;
    await runPython(`
      global text
      doc = getDocUia()
      editable = findUiaByDomId(doc, "editable")
      text = getUiaPattern(editable, "Text")
    `);
    let uiaRects = await runPython(
      `text.GetSelection().GetElement(0).GetBoundingRectangles()`
    );
    testTextPos(ce0, 0, [uiaRects[0], uiaRects[1]], COORDTYPE_SCREEN_RELATIVE);

    info("ArrowRight to end of line");
    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, ce0);
    EventUtils.synthesizeKey("KEY_ArrowRight");
    await moved;
    uiaRects = await runPython(
      `text.GetSelection().GetElement(0).GetBoundingRectangles()`
    );
    // Bug 1966812: We would ideally return a rect here.
    is(uiaRects.length, 0, "GetBoundingRectangles returned nothing");

    info("ArrowRight to line feed on blank line");
    const ce1 = findAccessibleChildByID(docAcc, "ce1");
    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, ce1);
    EventUtils.synthesizeKey("KEY_ArrowRight");
    await moved;
    uiaRects = await runPython(
      `text.GetSelection().GetElement(0).GetBoundingRectangles()`
    );
    testTextPos(ce1, 0, [uiaRects[0], uiaRects[1]], COORDTYPE_SCREEN_RELATIVE);

    info("ArrowRight to b");
    const ce2 = findAccessibleChildByID(docAcc, "ce2");
    moved = waitForEvent(EVENT_TEXT_CARET_MOVED, ce2);
    EventUtils.synthesizeKey("KEY_ArrowRight");
    await moved;
    uiaRects = await runPython(
      `text.GetSelection().GetElement(0).GetBoundingRectangles()`
    );
    testTextPos(ce2, 0, [uiaRects[0], uiaRects[1]], COORDTYPE_SCREEN_RELATIVE);
  },
  // The IA2 -> UIA proxy doesn't support this.
  { uiaEnabled: true, uiaDisabled: false }
);

/**
 * Test the TextRange pattern's ScrollIntoView method.
 */
addUiaTask(
  `
<style>
  body {
    margin: 0;
  }
</style>
<p>p1</p>
<hr style="height: 200vh;">
<p id="p2">p2</p>
<hr style="height: 200vh;">
<p>p3</p>
  `,
  async function testTextRangeScrollIntoView(browser, docAcc) {
    const [docLeft, docTop, , docBottom] = await runPython(`
      global doc
      doc = getDocUia()
      rect = doc.CurrentBoundingRectangle
      return (rect.left, rect.top, rect.right, rect.bottom)
    `);

    info("Scrolling p2 to top");
    let scrolled = waitForEvent(EVENT_SCROLLING_END, docAcc);
    await runPython(`
      global docText, p2, range
      docText = getUiaPattern(doc, "Text")
      p2 = findUiaByDomId(doc, "p2")
      range = docText.RangeFromChild(p2)
      range.ScrollIntoView(True)
    `);
    await scrolled;
    let [left, top, , height] = await runPython(
      `range.GetBoundingRectangles()`
    );
    is(left, docLeft, "range is at left of document");
    is(top, docTop, "range is at top of document");

    info("Scrolling p2 to bottom");
    scrolled = waitForEvent(EVENT_SCROLLING_END, docAcc);
    await runPython(`
      range.ScrollIntoView(False)
    `);
    await scrolled;
    [left, top, , height] = await runPython(`range.GetBoundingRectangles()`);
    is(left, docLeft, "range is at left of document");
    is(top + height, docBottom, "range is at bottom of document");
  }
);

/**
 * Test the TextRange pattern's Select method.
 */
addUiaTask(
  `
<p id="p">ab<a id="link" href="/">c</a></p>
<input id="input" type="text" value="ab">
<div id="contenteditable" contenteditable role="textbox">ab</div>
  `,
  async function testTextRangeSelect(browser, docAcc) {
    info("Moving caret to b in p");
    const p = findAccessibleChildByID(docAcc, "p", [nsIAccessibleText]);
    let moved = waitForEvent(EVENT_TEXT_CARET_MOVED, p);
    await runPython(`
      global doc
      doc = getDocUia()
      p = findUiaByDomId(doc, "p")
      textChild = getUiaPattern(p, "TextChild")
      global pbRange
      pbRange = textChild.TextRange
      # Encompass "b".
      pbRange.Move(TextUnit_Character, 1)
      # Collapse.
      pbRange.MoveEndpointByRange(TextPatternRangeEndpoint_End, pbRange, TextPatternRangeEndpoint_Start)
      pbRange.Select()
    `);
    await moved;
    testTextSelectionCount(p, 0);
    is(p.caretOffset, 1, "caret at 1");
    // When using the IA2 -> UIA proxy, the focus changes when moving the caret,
    // but this isn't what UIA clients want.
    if (gIsUiaEnabled) {
      info("Moving caret to c in link");
      const link = findAccessibleChildByID(docAcc, "link", [nsIAccessibleText]);
      moved = waitForEvents({
        expected: [[EVENT_TEXT_CARET_MOVED, link]],
        unexpected: [[EVENT_FOCUS, link]],
      });
      await runPython(`
        link = findUiaByDomId(doc, "link")
        textChild = getUiaPattern(link, "TextChild")
        range = textChild.TextRange
        # Collapse to "a".
        range.MoveEndpointByRange(TextPatternRangeEndpoint_End, range, TextPatternRangeEndpoint_Start)
        range.Select()
      `);
      await moved;
      testTextSelectionCount(link, 0);
      is(p.caretOffset, 2, "p caret at 2");
      is(link.caretOffset, 0, "link caret at 0");
      info("Focusing link");
      moved = waitForEvent(EVENT_FOCUS, link);
      link.takeFocus();
      await moved;
      info("Moving caret back to b in p");
      moved = waitForEvents({
        expected: [[EVENT_TEXT_CARET_MOVED, p]],
        unexpected: [[EVENT_FOCUS, docAcc]],
      });
      await runPython(`
        pbRange.Select()
      `);
      await moved;
      testTextSelectionCount(p, 0);
      is(p.caretOffset, 1, "p caret at 1");
    }

    // <input> and contentEditable should behave the same.
    for (const id of ["input", "contenteditable"]) {
      info(`Focusing ${id}`);
      const acc = findAccessibleChildByID(docAcc, id, [nsIAccessibleText]);
      moved = waitForEvents([
        [EVENT_FOCUS, acc],
        [EVENT_TEXT_CARET_MOVED, acc],
      ]);
      acc.takeFocus();
      await moved;

      info("Selecting a");
      moved = waitForEvents([
        [EVENT_TEXT_SELECTION_CHANGED, acc],
        [EVENT_TEXT_CARET_MOVED, acc],
      ]);
      await runPython(`
        acc = findUiaByDomId(doc, "${id}")
        text = getUiaPattern(acc, "Text")
        global range
        range = text.DocumentRange
        range.ExpandToEnclosingUnit(TextUnit_Character)
        range.Select()
      `);
      await moved;
      testTextSelectionCount(acc, 1);
      testTextGetSelection(acc, 0, 1, 0);

      info("Moving caret to b");
      moved = waitForEvent(EVENT_TEXT_CARET_MOVED, acc);
      await runPython(`
        # Collapse to b.
        range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, 1)
        range.Select()
      `);
      await moved;
      testTextSelectionCount(acc, 0);
      is(acc.caretOffset, 1, "caret at 1");
    }
  }
);

/**
 * Test the TextRange pattern's AddToSelection method.
 */
addUiaTask(
  `
<input id="input" type="text" value="abc">
<div id="contenteditable" contenteditable role="textbox">abc</div>
  `,
  async function testTextRangeAddToSelection(browser, docAcc) {
    // <input> and contentEditable should behave the same.
    for (const id of ["input", "contenteditable"]) {
      info(`Focusing ${id}`);
      const acc = findAccessibleChildByID(docAcc, id, [nsIAccessibleText]);
      let moved = waitForEvents([
        [EVENT_FOCUS, acc],
        [EVENT_TEXT_CARET_MOVED, acc],
      ]);
      acc.takeFocus();
      await moved;

      info("Adding a to selection");
      moved = waitForEvents([
        [EVENT_TEXT_SELECTION_CHANGED, acc],
        [EVENT_TEXT_CARET_MOVED, acc],
      ]);
      await runPython(`
        doc = getDocUia()
        acc = findUiaByDomId(doc, "${id}")
        text = getUiaPattern(acc, "Text")
        global range
        range = text.DocumentRange
        range.ExpandToEnclosingUnit(TextUnit_Character)
        range.AddToSelection()
      `);
      await moved;
      testTextSelectionCount(acc, 1);
      testTextGetSelection(acc, 0, 1, 0);

      info("Adding c to selection");
      moved = waitForEvent(EVENT_TEXT_CARET_MOVED, acc);
      await runPython(`
        # Move start to c.
        range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, 2)
        range.ExpandToEnclosingUnit(TextUnit_Character)
        range.AddToSelection()
      `);
      await moved;
      testTextSelectionCount(acc, 2);
      testTextGetSelection(acc, 0, 1, 0);
      testTextGetSelection(acc, 2, 3, 1);
    }
  }
);

/**
 * Test the TextRange pattern's RemoveFromSelection method.
 */
addUiaTask(
  `
<input id="input" type="text" value="abc">
<div id="contenteditable" contenteditable role="textbox">abc</div>
  `,
  async function testTextRangeRemoveFromSelection(browser, docAcc) {
    // <input> and contentEditable should behave the same.
    for (const id of ["input", "contenteditable"]) {
      info(`Focusing ${id}`);
      const acc = findAccessibleChildByID(docAcc, id, [nsIAccessibleText]);
      let moved = waitForEvents([
        [EVENT_FOCUS, acc],
        [EVENT_TEXT_CARET_MOVED, acc],
      ]);
      acc.takeFocus();
      await moved;

      info("Adding a to selection");
      moved = waitForEvents([
        [EVENT_TEXT_SELECTION_CHANGED, acc],
        [EVENT_TEXT_CARET_MOVED, acc],
      ]);
      acc.addSelection(0, 1);
      await moved;
      info("Adding c to selection");
      moved = waitForEvents([
        [EVENT_TEXT_SELECTION_CHANGED, acc],
        [EVENT_TEXT_CARET_MOVED, acc],
      ]);
      acc.addSelection(2, 3);
      await moved;

      info("Removing a from selection");
      moved = waitForEvents([
        [EVENT_TEXT_SELECTION_CHANGED, acc],
        [EVENT_TEXT_CARET_MOVED, acc],
      ]);
      await runPython(`
        doc = getDocUia()
        acc = findUiaByDomId(doc, "${id}")
        text = getUiaPattern(acc, "Text")
        global range
        range = text.DocumentRange
        range.ExpandToEnclosingUnit(TextUnit_Character)
        range.RemoveFromSelection()
      `);
      await moved;
      testTextSelectionCount(acc, 1);
      testTextGetSelection(acc, 2, 3, 0);

      info("Removing b from selection even though it isn't selected");
      await runPython(`
        # Move start to b.
        range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, 1)
        range.ExpandToEnclosingUnit(TextUnit_Character)
      `);
      await testPythonRaises(
        `range.RemoveFromSelection()`,
        "RemoveFromSelection failed"
      );

      info("Removing c from selection");
      moved = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, acc);
      await runPython(`
        # Move start to c.
        range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, 1)
        range.ExpandToEnclosingUnit(TextUnit_Character)
        range.RemoveFromSelection()
      `);
      await moved;
      testTextSelectionCount(acc, 0);
    }
  },
  // The IA2 -> UIA proxy doesn't support RemoveFromSelection correctly.
  { uiaEnabled: true, uiaDisabled: false }
);

/**
 * Test the TextRange pattern's FindAttribute method.
 */
addUiaTask(
  `
<div id="font-weight-container">a <span tabindex="0"><b>bcd</b></span><b> ef</b> ghi</div>
  `,
  async function testTextRangeFindAttribute(_browser, _docAcc) {
    info("Constructing range on bold text run");
    await runPython(`
      global doc, docText, range, fontWeightContainerAcc
      doc = getDocUia()
      docText = getUiaPattern(doc, "Text")
      fontWeightContainerAcc = findUiaByDomId(doc, "font-weight-container")
      range = docText.RangeFromChild(fontWeightContainerAcc)
    `);
    is(
      await runPython(`range.GetText(-1)`),
      "a bcd ef ghi",
      "range text correct"
    );

    info("Finding first font-weight 400 text range");
    await runPython(`
      global subrange
      subrange = range.FindAttribute(UIA_FontWeightAttributeId, 400, False)
    `);
    is(await runPython(`subrange.GetText(-1)`), "a ", "range text correct");

    info("Finding first font-weight 700 text range");
    await runPython(`
      global subrange
      subrange = range.FindAttribute(UIA_FontWeightAttributeId, 700, False)
    `);
    is(await runPython(`subrange.GetText(-1)`), "bcd ef", "range text correct");

    info("Finding last font-weight 700 text range");
    await runPython(`
      global subrange
      subrange = range.FindAttribute(UIA_FontWeightAttributeId, 700, True)
    `);
    is(await runPython(`subrange.GetText(-1)`), "bcd ef", "range text correct");

    info("Finding last font-weight 400 text range");
    await runPython(`
      global subrange
      subrange = range.FindAttribute(UIA_FontWeightAttributeId, 400, True)
    `);
    is(await runPython(`subrange.GetText(-1)`), " ghi", "range text correct");

    // The IA2 -> UIA proxy gets things below this wrong.
    if (!gIsUiaEnabled) {
      return;
    }
    info("Moving range to the middle of a text attribute run");
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_Start, TextUnit_Character, 4)`
      ),
      4,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "cd ef ghi", "range text correct");

    info(
      "Finding first font-weight 700 text range (range starts in middle of text attribute run)"
    );
    await runPython(`
      global subrange
      subrange = range.FindAttribute(UIA_FontWeightAttributeId, 700, False)
    `);
    is(await runPython(`subrange.GetText(-1)`), "cd ef", "range text correct");

    await runPython(`
      global range
      range = docText.RangeFromChild(fontWeightContainerAcc)
    `);
    is(
      await runPython(`range.GetText(-1)`),
      "a bcd ef ghi",
      "range text correct"
    );
    is(
      await runPython(
        `range.MoveEndpointByUnit(TextPatternRangeEndpoint_End, TextUnit_Character, -5)`
      ),
      -5,
      "MoveEndpointByUnit return correct"
    );
    is(await runPython(`range.GetText(-1)`), "a bcd e", "range text correct");

    info(
      "Finding last font-weight 700 text range (range ends in middle of text attribute run)"
    );
    await runPython(`
      global subrange
      subrange = range.FindAttribute(UIA_FontWeightAttributeId, 700, True)
    `);
    is(await runPython(`subrange.GetText(-1)`), "bcd e", "range text correct");

    info("Collapsing range at start");
    await runPython(`
      global subrange
      subrange = range.Clone()
      subrange.MoveEndpointByRange(TextPatternRangeEndpoint_End, subrange, TextPatternRangeEndpoint_Start)
    `);
    is(await runPython(`subrange.GetText(-1)`), "", "subrange text correct");
    info("Finding last font-weight 400 text range on collapsed range");
    await runPython(`
      global subrange
      subrange = subrange.FindAttribute(UIA_FontWeightAttributeId, 400, True)
    `);
    is(await runPython(`subrange.GetText(-1)`), "", "subrange text correct");
  },
  { uiaEnabled: true, uiaDisabled: true }
);

/**
 * Test the Text pattern's GetVisibleRanges method.
 */
addUiaTask(
  `
<div id="div">
  <p>line1</p>
  <p><strong id="strong">line</strong>2</p>
  <p style="position: absolute; left: -10000px; width: 1px;">line3</p>
  <p>line4</p>
</div>
<!-- We use 0.5lh so the second line is definitely fully scrolled out.
     With 1lh, it could be partially visible and thus included. -->
<textarea id="textarea" style="height: 0.5lh;">line5
line6
line7</textarea>
<hr aria-hidden="true" style="height: 100vh;">
<p>line8</p>
  `,
  async function testTextGetVisibleRanges() {
    await runPython(`
      global doc, docText, ranges
      doc = getDocUia()
      docText = getUiaPattern(doc, "Text")
      ranges = docText.GetVisibleRanges()
    `);
    // XXX This should be 4 once we fix the scrolling case below.
    is(
      await runPython(`ranges.Length`),
      6,
      "doc has correct number of visible ranges"
    );
    is(
      await runPython(`ranges.GetElement(0).GetText(-1)`),
      "line1",
      "range 0 text correct"
    );
    is(
      await runPython(`ranges.GetElement(1).GetText(-1)`),
      "line2",
      "range 1 text correct"
    );
    // line3 is off-screen and thus not visible.
    is(
      await runPython(`ranges.GetElement(2).GetText(-1)`),
      "line4",
      "range 2 text correct"
    );
    is(
      await runPython(`ranges.GetElement(3).GetText(-1)`),
      "line5\n",
      "range 3 text correct"
    );
    // XXX line6 and line7 are scrolled off screen by the textarea, but we
    // incorrectly return them for now (ranges 4 and 5).
    // line8 is scrolled off screen by the document.

    await runPython(`
      textarea = findUiaByDomId(doc, "textarea")
      textareaText = getUiaPattern(textarea, "Text")
      global ranges
      ranges = textareaText.GetVisibleRanges()
    `);
    is(
      await runPython(`ranges.Length`),
      1,
      "textarea has correct number of visible ranges"
    );
    is(
      await runPython(`ranges.GetElement(0).GetText(-1)`),
      "line5\n",
      "range 0 text correct"
    );
    // line6 and line7 are scrolled off screen by the textarea.

    await runPython(`
      strong = findUiaByDomId(doc, "strong")
      strongLeaf = uiaClient.RawViewWalker.GetFirstChildElement(strong)
      strongText = getUiaPattern(strongLeaf, "Text")
      global ranges
      ranges = strongText.GetVisibleRanges()
    `);
    is(
      await runPython(`ranges.Length`),
      1,
      "strong leaf has correct number of visible ranges"
    );
    is(
      await runPython(`ranges.GetElement(0).GetText(-1)`),
      "line",
      "range 0 text correct"
    );
  },
  // The IA2 -> UIA proxy doesn't support GetVisibleRanges.
  { uiaEnabled: true, uiaDisabled: false }
);

/**
 * Test the TextRange pattern's FindText method.
 */
addUiaTask(
  `<div id="container"><b>abc</b>TEST<div id="inner">def</div>TEST<p>ghi</p></div>`,
  async function testTextRangeFromChild() {
    await runPython(`
      global doc, docText, container, range
      doc = getDocUia()
      docText = getUiaPattern(doc, "Text")
      container = findUiaByDomId(doc, "container")
      range = docText.RangeFromChild(container)
    `);
    // The IA2 -> UIA bridge inserts a space at the end of the text.
    if (gIsUiaEnabled) {
      is(
        await runPython(`range.GetText(-1)`),
        `abcTESTdefTESTghi`,
        "doc returned correct range for container"
      );
    }
    info("Finding 'abc', searching from the start");
    await runPython(`
      global subrange
      subrange = range.FindText("abc", False, False)
      `);
    is(await runPython(`subrange.GetText(-1)`), "abc", "range text correct");

    info("Finding 'abc', searching from the end");
    await runPython(`
      global subrange
      subrange = range.FindText("abc", True, False)
      `);
    is(await runPython(`subrange.GetText(-1)`), "abc", "range text correct");

    info("Finding 'ghi', searching from the start");
    await runPython(`
      global subrange
      subrange = range.FindText("ghi", False, False)
      `);
    is(await runPython(`subrange.GetText(-1)`), "ghi", "range text correct");

    info("Finding 'ghi', searching from the end");
    await runPython(`
      global subrange
      subrange = range.FindText("ghi", True, False)
      `);
    is(await runPython(`subrange.GetText(-1)`), "ghi", "range text correct");

    info("Finding 'TEST', searching from the start");
    await runPython(`
      global subrange
      subrange = range.FindText("TEST", False, False)
      `);
    is(await runPython(`subrange.GetText(-1)`), "TEST", "range text correct");
    info("Finding 'TEST', searching from the end");
    await runPython(`
      global subrange2
      subrange2 = range.FindText("TEST", True, False)
      `);
    is(await runPython(`subrange2.GetText(-1)`), "TEST", "range text correct");
    ok(
      !(await runPython(`subrange.compare(subrange2)`)),
      "ranges are not equal"
    );

    info("Finding 'test', searching from the start, case-sensitive");
    await runPython(`
      global subrange
      subrange = range.FindText("test", False, False)
      `);
    ok(await runPython(`not subrange`), "range not found");
    info("Finding 'test', searching from the start, case-insensitive");
    await runPython(`
      global subrange
      subrange = range.FindText("test", False, True)
      `);
    is(await runPython(`subrange.GetText(-1)`), "TEST", "range text correct");
  },
  { uiaEnabled: true, uiaDisabled: true }
);

const textChildSnippet = `
<p id="p">p</p>
<a id="a" href="/">a</a>
<img id="img" src="https://example.com/a11y/accessible/tests/mochitest/moz.png" alt="img">
<div id="textbox" contenteditable role="textbox">textboxLeaf
  <p id="textboxP">textboxP</p>
</div>
`;

/**
 * Test the TextChild pattern's TextContainer property.
 */
addUiaTask(textChildSnippet, async function testTextChildTextContainer() {
  ok(
    await runPython(`
        global doc, p
        doc = getDocUia()
        p = findUiaByDomId(doc, "p")
        tc = getUiaPattern(p, "TextChild")
        return uiaClient.CompareElements(tc.TextContainer, doc)
      `),
    "p TextContainer is doc"
  );
  // The IA2 -> UIA proxy doesn't support the TextChild pattern on text
  // leaves.
  if (gIsUiaEnabled) {
    ok(
      await runPython(`
        pLeaf = uiaClient.RawViewWalker.GetFirstChildElement(p)
        tc = getUiaPattern(pLeaf, "TextChild")
        return uiaClient.CompareElements(tc.TextContainer, doc)
      `),
      "p leaf TextContainer is doc"
    );
  }
  ok(
    await runPython(`
        a = findUiaByDomId(doc, "a")
        tc = getUiaPattern(a, "TextChild")
        return uiaClient.CompareElements(tc.TextContainer, doc)
      `),
    "a TextContainer is doc"
  );
  ok(
    await runPython(`
        img = findUiaByDomId(doc, "img")
        tc = getUiaPattern(img, "TextChild")
        return uiaClient.CompareElements(tc.TextContainer, doc)
      `),
    "img TextContainer is doc"
  );
  ok(
    await runPython(`
        global textbox
        textbox = findUiaByDomId(doc, "textbox")
        tc = getUiaPattern(textbox, "TextChild")
        return uiaClient.CompareElements(tc.TextContainer, doc)
      `),
    "textbox TextContainer is doc"
  );
  // The IA2 -> UIA proxy doesn't support the TextChild pattern on text
  // leaves.
  if (gIsUiaEnabled) {
    ok(
      await runPython(`
        textboxLeaf = uiaClient.RawViewWalker.GetFirstChildElement(textbox)
        tc = getUiaPattern(textboxLeaf, "TextChild")
        return uiaClient.CompareElements(tc.TextContainer, textbox)
      `),
      "textbox leaf  TextContainer is textbox"
    );
  }
  ok(
    await runPython(`
        textboxP = findUiaByDomId(doc, "textboxP")
        tc = getUiaPattern(textboxP, "TextChild")
        return uiaClient.CompareElements(tc.TextContainer, textbox)
      `),
    "textboxP TextContainer is textbox"
  );
});

/**
 * Test the TextChild pattern's TextRange property.
 */
addUiaTask(textChildSnippet, async function testTextChildTextRange() {
  is(
    await runPython(`
        global doc, p
        doc = getDocUia()
        p = findUiaByDomId(doc, "p")
        tc = getUiaPattern(p, "TextChild")
        return tc.TextRange.GetText(-1)
      `),
    "p",
    "p text correct"
  );
  // The IA2 -> UIA proxy doesn't support the TextChild pattern on text
  // leaves.
  if (gIsUiaEnabled) {
    is(
      await runPython(`
        pLeaf = uiaClient.RawViewWalker.GetFirstChildElement(p)
        tc = getUiaPattern(pLeaf, "TextChild")
        return tc.TextRange.GetText(-1)
      `),
      "p",
      "p leaf  text correct"
    );
  }
  is(
    await runPython(`
        a = findUiaByDomId(doc, "a")
        tc = getUiaPattern(a, "TextChild")
        return tc.TextRange.GetText(-1)
      `),
    "a",
    "a text correct"
  );
  if (gIsUiaEnabled) {
    // The IA2 -> UIA proxy doesn't expose an embedded object character for
    // images.
    is(
      await runPython(`
        img = findUiaByDomId(doc, "img")
        tc = getUiaPattern(img, "TextChild")
        return tc.TextRange.GetText(-1)
      `),
      kEmbedChar,
      "img text correct"
    );
    // The IA2 -> UIA proxy adds spaces between elements that don't exist.
    is(
      await runPython(`
        global textbox
        textbox = findUiaByDomId(doc, "textbox")
        tc = getUiaPattern(textbox, "TextChild")
        return tc.TextRange.GetText(-1)
      `),
      "textboxLeaf textboxP",
      "textbox text correct"
    );
    // The IA2 -> UIA proxy doesn't support the TextChild pattern on text
    // leaves.
    is(
      await runPython(`
        textboxLeaf = uiaClient.RawViewWalker.GetFirstChildElement(textbox)
        tc = getUiaPattern(textboxLeaf, "TextChild")
        return tc.TextRange.GetText(-1)
      `),
      "textboxLeaf ",
      "textbox leaf  text correct"
    );
  }
  is(
    await runPython(`
        textboxP = findUiaByDomId(doc, "textboxP")
        tc = getUiaPattern(textboxP, "TextChild")
        return tc.TextRange.GetText(-1)
      `),
    "textboxP",
    "textboxP text correct"
  );
});
