summaryrefslogtreecommitdiffstats
path: root/accessible/tests/browser/text/browser_text_spelling.js
blob: 14c5c16be4455c8c2188b76dfb1f78a7365dcee8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/* 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 */
/* import-globals-from ../../mochitest/attributes.js */
loadScripts(
  { name: "text.js", dir: MOCHITESTS_DIR },
  { name: "attributes.js", dir: MOCHITESTS_DIR }
);

const boldAttrs = { "font-weight": "700" };

/*
 * Given a text accessible and a list of ranges
 * check if those ranges match the misspelled ranges in the accessible.
 */
function misspelledRangesMatch(acc, ranges) {
  let offset = 0;
  let expectedRanges = [...ranges];
  let charCount = acc.characterCount;
  while (offset < charCount) {
    let start = {};
    let end = {};
    let attributes = acc.getTextAttributes(false, offset, start, end);
    offset = end.value;
    try {
      if (attributes.getStringProperty("invalid") == "spelling") {
        let expected = expectedRanges.shift();
        if (
          !expected ||
          expected[0] != start.value ||
          expected[1] != end.value
        ) {
          return false;
        }
      }
    } catch (err) {}
  }

  return !expectedRanges.length;
}

/*
 * Returns a promise that resolves after a text attribute changed event
 * brings us to a state where the misspelled ranges match.
 */
async function waitForMisspelledRanges(acc, ranges) {
  await waitForEvent(EVENT_TEXT_ATTRIBUTE_CHANGED);
  await untilCacheOk(
    () => misspelledRangesMatch(acc, ranges),
    `Misspelled ranges match: ${JSON.stringify(ranges)}`
  );
}

/**
 * Test spelling errors.
 */
addAccessibleTask(
  `
<textarea id="textarea" spellcheck="true">test tset tset test</textarea>
<div contenteditable id="editable" spellcheck="true">plain<span> ts</span>et <b>bold</b></div>
  `,
  async function (browser, docAcc) {
    const textarea = findAccessibleChildByID(docAcc, "textarea", [
      nsIAccessibleText,
    ]);
    info("Focusing textarea");
    let spellingChanged = waitForMisspelledRanges(textarea, [
      [5, 9],
      [10, 14],
    ]);
    textarea.takeFocus();
    await spellingChanged;

    // Test removal of a spelling error.
    info('textarea: Changing first "tset" to "test"');
    // setTextRange fires multiple EVENT_TEXT_ATTRIBUTE_CHANGED, so replace by
    // selecting and typing instead.
    spellingChanged = waitForMisspelledRanges(textarea, [[10, 14]]);
    await invokeContentTask(browser, [], () => {
      content.document.getElementById("textarea").setSelectionRange(5, 9);
    });
    EventUtils.sendString("test");
    // Move the cursor to trigger spell check.
    EventUtils.synthesizeKey("KEY_ArrowRight");
    await spellingChanged;

    // Test addition of a spelling error.
    info('textarea: Changing it back to "tset"');
    spellingChanged = waitForMisspelledRanges(textarea, [
      [5, 9],
      [10, 14],
    ]);
    await invokeContentTask(browser, [], () => {
      content.document.getElementById("textarea").setSelectionRange(5, 9);
    });
    EventUtils.sendString("tset");
    EventUtils.synthesizeKey("KEY_ArrowRight");
    await spellingChanged;

    // Ensure that changing the text without changing any spelling errors
    // correctly updates offsets.
    info('textarea: Changing first "test" to "the"');
    // Spelling errors don't change, so we won't get
    // EVENT_TEXT_ATTRIBUTE_CHANGED. We change the text, wait for the insertion
    // and then select a character so we know when the change is done.
    let inserted = waitForEvent(EVENT_TEXT_INSERTED, textarea);
    await invokeContentTask(browser, [], () => {
      content.document.getElementById("textarea").setSelectionRange(0, 4);
    });
    EventUtils.sendString("the");
    await inserted;
    let selected = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, textarea);
    EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true });
    await selected;
    const expectedRanges = [
      [4, 8],
      [9, 13],
    ];
    await untilCacheOk(
      () => misspelledRangesMatch(textarea, expectedRanges),
      `Misspelled ranges match: ${JSON.stringify(expectedRanges)}`
    );

    const editable = findAccessibleChildByID(docAcc, "editable", [
      nsIAccessibleText,
    ]);
    info("Focusing editable");
    spellingChanged = waitForMisspelledRanges(editable, [[6, 10]]);
    editable.takeFocus();
    await spellingChanged;
    // Test normal text and spelling errors crossing text nodes.
    testTextAttrs(editable, 0, {}, {}, 0, 6, true); // "plain "
    // Ensure we detect the spelling error even though there is a style change
    // after it.
    testTextAttrs(editable, 6, { invalid: "spelling" }, {}, 6, 10, true); // "tset"
    testTextAttrs(editable, 10, {}, {}, 10, 11, true); // " "
    // Ensure a style change is still detected in the presence of a spelling
    // error.
    testTextAttrs(editable, 11, boldAttrs, {}, 11, 15, true); // "bold"
  },
  {
    chrome: true,
    topLevel: true,
    iframe: true,
    remoteIframe: true,
  }
);