| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Include test fixture. |
| GEN_INCLUDE(['../../testing/chromevox_next_e2e_test_base.js', |
| '../../testing/assert_additions.js']); |
| |
| GEN_INCLUDE(['../../testing/mock_feedback.js']); |
| |
| /** |
| * Test fixture for editing tests. |
| * @constructor |
| * @extends {ChromeVoxNextE2ETest} |
| */ |
| function ChromeVoxEditingTest() { |
| ChromeVoxNextE2ETest.call(this); |
| window.RoleType = chrome.automation.RoleType; |
| } |
| |
| ChromeVoxEditingTest.prototype = { |
| __proto__: ChromeVoxNextE2ETest.prototype, |
| |
| /** |
| * @return {!MockFeedback} |
| */ |
| createMockFeedback: function() { |
| var mockFeedback = new MockFeedback(this.newCallback(), |
| this.newCallback.bind(this)); |
| mockFeedback.install(); |
| return mockFeedback; |
| }, |
| |
| press: function(keyCode, modifiers) { |
| return function() { |
| BackgroundKeyboardHandler.sendKeyPress(keyCode, modifiers); |
| }; |
| }, |
| |
| }; |
| |
| var doc = function() {/*! |
| <label for='singleLine'>singleLine</label> |
| <input type='text' id='singleLine' value='Single line field'><br> |
| <label for='textarea'>textArea</label> |
| <textarea id='textarea'> |
| Line 1
 |
| line 2
 |
| line 3 |
| </textarea> |
| */}; |
| |
| TEST_F('ChromeVoxEditingTest', 'Focus', function() { |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(doc, function(root) { |
| var singleLine = root.find({role: RoleType.TEXT_FIELD, |
| attributes: {name: 'singleLine'}}); |
| var textarea = root.find({role: RoleType.TEXT_FIELD, |
| attributes: {name: 'textArea'}}); |
| singleLine.focus(); |
| mockFeedback |
| .expectSpeech('singleLine', 'Single line field', 'Edit text') |
| .expectBraille('singleLine Single line field ed', |
| {startIndex: 11, endIndex: 11}) |
| .call(textarea.focus.bind(textarea)) |
| .expectSpeech('textArea', |
| 'Line 1\nline 2\nline 3', |
| 'Text area') |
| .expectBraille('textArea Line 1\nline 2\nline 3 mled', |
| {startIndex: 9, endIndex: 9}); |
| |
| mockFeedback.replay(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'Multiline', function() { |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(doc, function(root) { |
| var textarea = root.find({role: RoleType.TEXT_FIELD, |
| attributes: {name: 'textArea'}}); |
| textarea.focus(); |
| mockFeedback |
| .expectSpeech('textArea', |
| 'Line 1\nline 2\nline 3', |
| 'Text area') |
| .expectBraille('textArea Line 1\nline 2\nline 3 mled', |
| {startIndex: 9, endIndex: 9}) |
| .call(textarea.setSelection.bind(textarea, 1, 1)) |
| .expectSpeech('i') |
| .expectBraille('Line 1 mled', |
| {startIndex: 1, endIndex: 1}) |
| .call(textarea.setSelection.bind(textarea, 7, 7)) |
| .expectSpeech('line 2') |
| .expectBraille('line 2', |
| {startIndex: 0, endIndex: 0}) |
| .call(textarea.setSelection.bind(textarea, 7, 13)) |
| .expectSpeech('line 2', 'selected') |
| .expectBraille('line 2', |
| {startIndex: 0, endIndex: 6}); |
| |
| mockFeedback.replay(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'TextButNoSelectionChange', function() { |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree( |
| function() {/*! |
| <h1>Test doc</h1> |
| <input type='text' id='input' value='text1'> |
| <!-- We don't seem to get an event in js when the automation |
| setSelection function is called, so poll for the actual change. --> |
| |
| <script> |
| var timer; |
| var input = document.getElementById('input'); |
| function poll(e) { |
| if (input.selectionStart == 0) |
| return; |
| console.log('TIM' + 'ER'); |
| input.value = 'text2'; |
| window.clearInterval(timer); |
| } |
| timer = window.setInterval(poll, 200); |
| </script> |
| */}, |
| function(root) { |
| var input = root.find({role: RoleType.TEXT_FIELD}); |
| input.focus(); |
| mockFeedback |
| .expectSpeech('text1', 'Edit text') |
| .expectBraille('text1 ed', {startIndex: 0, endIndex: 0}) |
| .call(input.setSelection.bind(input, 5, 5)) |
| .expectBraille('text2 ed', {startIndex: 5, endIndex: 5}) |
| |
| mockFeedback.replay(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'RichTextMoveByLine', function() { |
| // Turn on rich text output settings. |
| localStorage['announceRichTextAttributes'] = 'true'; |
| |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(function() {/*! |
| <div role="textbox" contenteditable> |
| <h2>hello</h2> |
| <div><br></div> |
| <p>This is a <a href="#test">test</a> of rich text</p> |
| </div> |
| <button id="go">Go</button> |
| <script> |
| var dir = 'forward'; |
| var line = 0; |
| document.getElementById('go').addEventListener('click', function() { |
| var sel = getSelection(); |
| sel.modify('move', dir, 'line'); |
| if (dir == 'forward') |
| line++; |
| else |
| line--; |
| |
| if (line == 0) |
| dir = 'forward'; |
| if (line == 2) |
| dir = 'backward'; |
| }, true); |
| </script> |
| */}, function(root) { |
| var input = root.find({role: RoleType.TEXT_FIELD}); |
| var go = root.find({role: RoleType.BUTTON}); |
| var moveByLine = go.doDefault.bind(go); |
| this.listenOnce(input, 'focus', function() { |
| mockFeedback.call(moveByLine) |
| .expectSpeech('\n') |
| .expectBraille('\n') |
| .call(moveByLine) |
| .expectSpeech('This is a ', 'test', 'Link', ' of rich text') |
| .expectBraille('This is a test of rich text') |
| .call(moveByLine) |
| .expectSpeech('\n') |
| .expectBraille('\n') |
| .call(moveByLine) |
| .expectSpeech('hello', 'Heading 2') |
| .expectBraille('hello h2 mled') |
| .replay(); |
| }); |
| input.focus(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'RichTextMoveByCharacter', function() { |
| // Turn on rich text output settings. |
| localStorage['announceRichTextAttributes'] = 'true'; |
| |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(function() {/*! |
| <div role="textbox" contenteditable>This <b>is</b> a test.</div> |
| <button id="go">Go</button> |
| |
| <script> |
| var dir = 'forward'; |
| var char = 0; |
| document.getElementById('go').addEventListener('click', function() { |
| var sel = getSelection(); |
| sel.modify('move', dir, 'character'); |
| if (dir == 'forward') |
| char++; |
| else |
| char--; |
| |
| if (char == 0) |
| dir = 'forward'; |
| if (line == 16) |
| dir = 'backward'; |
| }, true); |
| </script> |
| */}, function(root) { |
| var input = root.find({role: RoleType.TEXT_FIELD}); |
| var go = root.find({role: RoleType.BUTTON}); |
| var moveByChar = go.doDefault.bind(go); |
| var lineText = 'This is a test. mled'; |
| |
| this.listenOnce(input, 'focus', function() { |
| mockFeedback.call(moveByChar) |
| .expectSpeech('h') |
| .expectBraille(lineText, { startIndex: 1, endIndex: 1 }) |
| .call(moveByChar) |
| .expectSpeech('i') |
| .expectBraille(lineText, { startIndex: 2, endIndex: 2 }) |
| .call(moveByChar) |
| .expectSpeech('s') |
| .expectBraille(lineText, { startIndex: 3, endIndex: 3 }) |
| .call(moveByChar) |
| .expectSpeech(' ') |
| .expectBraille(lineText, { startIndex: 4, endIndex: 4 }) |
| |
| .call(moveByChar) |
| .expectSpeech('i') |
| .expectSpeech('Bold') |
| .expectBraille(lineText, { startIndex: 5, endIndex: 5 }) |
| |
| .call(moveByChar) |
| .expectSpeech('s') |
| .expectBraille(lineText, { startIndex: 6, endIndex: 6 }) |
| |
| .call(moveByChar) |
| .expectSpeech(' ') |
| .expectSpeech('Not bold') |
| .expectBraille(lineText, { startIndex: 7, endIndex: 7 }) |
| |
| .call(moveByChar) |
| .expectSpeech('a') |
| .expectBraille(lineText, { startIndex: 8, endIndex: 8 }) |
| |
| .call(moveByChar) |
| .expectSpeech(' ') |
| .expectBraille(lineText, { startIndex: 9, endIndex: 9 }) |
| |
| .replay(); |
| }); |
| input.focus(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'RichTextMoveByCharacterAllAttributes', function() { |
| // Turn on rich text output settings. |
| localStorage['announceRichTextAttributes'] = 'true'; |
| |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(function() {/*! |
| <div role="textbox" contenteditable> |
| <p style="font-size:20px; font-family:times"> |
| <b style="color:#ff0000">Move</b> <i>through</i> <u style="font-family:georgia">text</u> |
| by <strike style="font-size:12px; color:#0000ff">character</strike> |
| <a href="#">test</a>! |
| </p> |
| </div> |
| |
| <button id="go">Go</button> |
| |
| <script> |
| document.getElementById('go').addEventListener('click', function() { |
| var sel = getSelection(); |
| sel.modify('move', 'forward', 'character'); |
| }, true); |
| </script> |
| */}, function(root) { |
| var input = root.find({role: RoleType.TEXT_FIELD}); |
| var go = root.find({role: RoleType.BUTTON}); |
| var moveByChar = go.doDefault.bind(go); |
| var lineText = 'Move through text by character test! mled'; |
| var lineOnLinkText = 'Move through text by character test lnk ! mled'; |
| |
| this.listenOnce(input, chrome.automation.EventType.FOCUS, function() { |
| mockFeedback.call(moveByChar) |
| .expectSpeech('o') |
| .expectSpeech('Size 20') |
| .expectSpeech('Red, 100% opacity.') |
| .expectSpeech('Bold') |
| .expectSpeech('Font times') |
| .expectBraille(lineText, { startIndex: 1, endIndex: 1 }) |
| .call(moveByChar) |
| .expectSpeech('v').expectBraille(lineText, { startIndex: 2, endIndex: 2 }) |
| .call(moveByChar) |
| .expectSpeech('e') |
| .expectBraille(lineText, { startIndex: 3, endIndex: 3 }) |
| .call(moveByChar) |
| .expectSpeech(' ') |
| .expectSpeech('Black, 100% opacity.') |
| .expectSpeech('Not bold') |
| .expectBraille(lineText, { startIndex: 4, endIndex: 4 }) |
| .call(moveByChar) |
| .expectSpeech('t') |
| .expectSpeech('Italic') |
| .expectBraille(lineText, { startIndex: 5, endIndex: 5 }) |
| .call(moveByChar) |
| .expectSpeech('h').expectBraille(lineText, { startIndex: 6, endIndex: 6 }) |
| .call(moveByChar) |
| .expectSpeech('r').expectBraille(lineText, { startIndex: 7, endIndex: 7 }) |
| .call(moveByChar) |
| .expectSpeech('o').expectBraille(lineText, { startIndex: 8, endIndex: 8 }) |
| .call(moveByChar) |
| .expectSpeech('u').expectBraille(lineText, { startIndex: 9, endIndex: 9 }) |
| .call(moveByChar) |
| .expectSpeech('g').expectBraille(lineText, { startIndex: 10, endIndex: 10 }) |
| .call(moveByChar) |
| .expectSpeech('h') |
| .expectBraille(lineText, { startIndex: 11, endIndex: 11 }) |
| .call(moveByChar) |
| .expectSpeech(' ') |
| .expectSpeech('Not italic') |
| .expectBraille(lineText, { startIndex: 12, endIndex: 12 }) |
| .call(moveByChar) |
| .expectSpeech('t') |
| .expectSpeech('Underline') |
| .expectSpeech('Font georgia') |
| .expectBraille(lineText, { startIndex: 13, endIndex: 13 }) |
| .call(moveByChar) |
| .expectSpeech('e').expectBraille(lineText, { startIndex: 14, endIndex: 14 }) |
| .call(moveByChar) |
| .expectSpeech('x').expectBraille(lineText, { startIndex: 15, endIndex: 15 }) |
| .call(moveByChar) |
| .expectSpeech('t') |
| .expectBraille(lineText, { startIndex: 16, endIndex: 16 }) |
| .call(moveByChar) |
| .expectSpeech(' ') |
| .expectSpeech('Not underline') |
| .expectSpeech('Font times') |
| .expectBraille(lineText, { startIndex: 17, endIndex: 17 }) |
| .call(moveByChar) |
| .expectSpeech('b').expectBraille(lineText, { startIndex: 18, endIndex: 18 }) |
| .call(moveByChar) |
| .expectSpeech('y').expectBraille(lineText, { startIndex: 19, endIndex: 19 }) |
| .call(moveByChar) |
| .expectSpeech(' ').expectBraille(lineText, { startIndex: 20, endIndex: 20 }) |
| .call(moveByChar) |
| .expectSpeech('c') |
| .expectSpeech('Size 12') |
| .expectSpeech('Blue, 100% opacity.') |
| .expectSpeech('Line through') |
| .expectBraille(lineText, { startIndex: 21, endIndex: 21 }) |
| .call(moveByChar) |
| .expectSpeech('h').expectBraille(lineText, { startIndex: 22, endIndex: 22 }) |
| .call(moveByChar) |
| .expectSpeech('a').expectBraille(lineText, { startIndex: 23, endIndex: 23 }) |
| .call(moveByChar) |
| .expectSpeech('r').expectBraille(lineText, { startIndex: 24, endIndex: 24 }) |
| .call(moveByChar) |
| .expectSpeech('a').expectBraille(lineText, { startIndex: 25, endIndex: 25 }) |
| .call(moveByChar) |
| .expectSpeech('c').expectBraille(lineText, { startIndex: 26, endIndex: 26 }) |
| .call(moveByChar) |
| .expectSpeech('t').expectBraille(lineText, { startIndex: 27, endIndex: 27 }) |
| .call(moveByChar) |
| .expectSpeech('e').expectBraille(lineText, { startIndex: 28, endIndex: 28 }) |
| .call(moveByChar) |
| .expectSpeech('r') |
| .expectBraille(lineText, { startIndex: 29, endIndex: 29 }) |
| .call(moveByChar) |
| .expectSpeech(' ') |
| .expectSpeech('Size 20') |
| .expectSpeech('Black, 100% opacity.') |
| .expectSpeech('Not line through') |
| .expectBraille(lineText, { startIndex: 30, endIndex: 30 }) |
| .call(moveByChar) |
| .expectSpeech('t') |
| .expectSpeech('Blue, 100% opacity.') |
| .expectSpeech('Link') |
| .expectSpeech('Underline') |
| .expectBraille(lineOnLinkText, { startIndex: 31, endIndex: 31 }) |
| .call(moveByChar) |
| .expectSpeech('e').expectBraille(lineOnLinkText, { startIndex: 32, endIndex: 32 }) |
| .call(moveByChar) |
| .expectSpeech('s').expectBraille(lineOnLinkText, { startIndex: 33, endIndex: 33 }) |
| .call(moveByChar) |
| .expectSpeech('t').expectBraille(lineOnLinkText, { startIndex: 34, endIndex: 34 }) |
| .call(moveByChar) |
| .expectSpeech('!') |
| .expectSpeech('Black, 100% opacity.') |
| .expectSpeech('Not link') |
| .expectSpeech('Not underline') |
| .expectBraille(lineText, {startIndex: 35, endIndex: 35 }) |
| |
| .replay(); |
| }); |
| input.focus(); |
| }); |
| }); |
| |
| // Tests specifically for cursor workarounds. |
| TEST_F('ChromeVoxEditingTest', 'RichTextMoveByCharacterNodeWorkaround', function() { |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(function() {/*! |
| <div role="textbox" contenteditable>hello <b>world</b></div> |
| <button id="go">Go</button> |
| |
| <script> |
| document.getElementById('go').addEventListener('click', function() { |
| var sel = getSelection(); |
| sel.modify('move', 'forward', 'character'); |
| }, true); |
| </script> |
| */}, function(root) { |
| var input = root.find({role: RoleType.TEXT_FIELD}); |
| var go = root.find({role: RoleType.BUTTON}); |
| var moveByChar = go.doDefault.bind(go); |
| var lineText = 'hello world mled'; |
| |
| this.listenOnce(input, 'focus', function() { |
| mockFeedback.call(moveByChar) |
| .expectSpeech('e') |
| .expectBraille(lineText, { startIndex: 1, endIndex: 1 }) |
| .call(moveByChar) |
| .expectSpeech('l') |
| .expectBraille(lineText, { startIndex: 2, endIndex: 2 }) |
| .call(moveByChar) |
| .expectSpeech('l') |
| .expectBraille(lineText, { startIndex: 3, endIndex: 3 }) |
| .call(moveByChar) |
| .expectSpeech('o') |
| .expectBraille(lineText, { startIndex: 4, endIndex: 4 }) |
| .call(moveByChar) |
| .expectSpeech(' ') |
| .expectBraille(lineText, { startIndex: 5, endIndex: 5 }) |
| .call(moveByChar) |
| .expectSpeech('w') |
| .expectBraille(lineText, { startIndex: 6, endIndex: 6 }) |
| .replay(); |
| }); |
| input.focus(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'RichTextMoveByCharacterEndOfLine', function() { |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(function() {/*! |
| <div role="textbox" contenteditable>Test</div> |
| <button id="go">Go</button> |
| |
| <script> |
| document.getElementById('go').addEventListener('click', function() { |
| var sel = getSelection(); |
| sel.modify('move', 'forward', 'character'); |
| }, true); |
| </script> |
| */}, function(root) { |
| var input = root.find({role: RoleType.TEXT_FIELD}); |
| var go = root.find({role: RoleType.BUTTON}); |
| var moveByChar = go.doDefault.bind(go); |
| var lineText = 'Test mled'; |
| |
| this.listenOnce(input, 'focus', function() { |
| mockFeedback.call(moveByChar) |
| .expectSpeech('e') |
| .expectBraille(lineText, { startIndex: 1, endIndex: 1 }) |
| .call(moveByChar) |
| .expectSpeech('s') |
| .expectBraille(lineText, { startIndex: 2, endIndex: 2 }) |
| .call(moveByChar) |
| .expectSpeech('t') |
| .expectBraille(lineText, { startIndex: 3, endIndex: 3 }) |
| .call(moveByChar) |
| .expectSpeech('\n') |
| .expectBraille(lineText, { startIndex: 4, endIndex: 4 }) |
| |
| .replay(); |
| }); |
| input.focus(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'RichTextLinkOutput', function() { |
| // Turn on rich text output settings. |
| localStorage['announceRichTextAttributes'] = 'true'; |
| |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(function() {/*! |
| <div role="textbox" contenteditable>a <a href="#">test</a></div> |
| <button id="go">Go</button> |
| <script> |
| document.getElementById('go').addEventListener('click', function() { |
| var sel = getSelection(); |
| sel.modify('move', 'forward', 'character'); |
| }, true); |
| </script> |
| */}, function(root) { |
| var input = root.find({role: RoleType.TEXT_FIELD}); |
| var go = root.find({role: RoleType.BUTTON}); |
| var moveByChar = go.doDefault.bind(go); |
| var lineText = 'a test mled'; |
| var lineOnLinkText = 'a test lnk mled'; |
| |
| this.listenOnce(input, 'focus', function() { |
| mockFeedback.call(moveByChar) |
| .expectSpeech(' ') |
| .expectBraille(lineText, { startIndex: 1, endIndex: 1 }) |
| .call(moveByChar) |
| .expectSpeech('t') |
| .expectSpeech('Blue, 100% opacity.') |
| .expectSpeech('Link') |
| .expectSpeech('Underline') |
| .expectBraille(lineOnLinkText, { startIndex: 2, endIndex: 2 }) |
| .call(moveByChar) |
| .expectSpeech('e') |
| .expectBraille(lineOnLinkText, { startIndex: 3, endIndex: 3 }) |
| .call(moveByChar) |
| .expectSpeech('s') |
| .expectBraille(lineOnLinkText, { startIndex: 4, endIndex: 4 }) |
| .call(moveByChar) |
| .expectSpeech('t') |
| .expectBraille(lineOnLinkText, { startIndex: 5, endIndex: 5 }) |
| |
| .replay(); |
| }); |
| input.focus(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'RichTextExtendByCharacter', function() { |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(function() {/*! |
| <div role="textbox" contenteditable>Te<br>st</div> |
| <button id="go">Go</button> |
| |
| <script> |
| document.getElementById('go').addEventListener('click', function() { |
| var sel = getSelection(); |
| sel.modify('extend', 'forward', 'character'); |
| }, true); |
| </script> |
| */}, function(root) { |
| var input = root.find({role: RoleType.TEXT_FIELD}); |
| var go = root.find({role: RoleType.BUTTON}); |
| var moveByChar = go.doDefault.bind(go); |
| |
| this.listenOnce(input, 'focus', function() { |
| mockFeedback.call(moveByChar) |
| .expectSpeech('T', 'selected') |
| .call(moveByChar) |
| .expectSpeech('e', 'added to selection') |
| .call(moveByChar) |
| .expectSpeech('selected') |
| .call(moveByChar) |
| // This gets described by the line logic in EditableLine. |
| .expectSpeech('s', 'selected') |
| .call(moveByChar) |
| .expectSpeech('t', 'added to selection') |
| |
| .replay(); |
| }); |
| input.focus(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'RichTextImageByCharacter', function() { |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(function() {/*! |
| <p contenteditable> |
| <img alt="dog"> is a <img alt="cat"> test |
| </p> |
| <button id="go">Go</button> |
| <script> |
| document.getElementById('go').addEventListener('click', function() { |
| var sel = getSelection(); |
| sel.modify('move', 'forward', 'character'); |
| }, true); |
| </script> |
| */}, function(root) { |
| var input = root.find({role: RoleType.PARAGRAPH}); |
| var go = root.find({role: RoleType.BUTTON}); |
| var moveByChar = go.doDefault.bind(go); |
| |
| this.listenOnce(input, 'focus', function() { |
| var lineText = 'dog is a cat test mled'; |
| var lineOnCatText = 'dog is a cat img test mled'; |
| mockFeedback |
| // This is initial output from focusing the contenteditable (which has |
| // no role). |
| .expectSpeech('dog', 'Image', ' is a ', 'cat', 'Image', ' test') |
| .expectBraille('dog img is a cat img test') |
| .clearPendingOutput() |
| .call(moveByChar) |
| // This is actually wrong; should say space. |
| .expectSpeech('dog') |
| .expectBraille(lineText, {startIndex: 3, endIndex: 3}) |
| .call(moveByChar) |
| .expectSpeech('i') |
| .expectBraille(lineText, {startIndex: 4, endIndex: 4}) |
| .call(moveByChar) |
| .expectSpeech('s') |
| .expectBraille(lineText, {startIndex: 5, endIndex: 5}) |
| .call(moveByChar) |
| .expectSpeech(' ') |
| .expectBraille(lineText, {startIndex: 6, endIndex: 6}) |
| .call(moveByChar) |
| .expectSpeech('a') |
| .expectBraille(lineText, {startIndex: 7, endIndex: 7}) |
| .call(moveByChar) |
| .expectSpeech(' ') |
| .expectBraille(lineText, {startIndex: 8, endIndex: 8}) |
| .clearPendingOutput() |
| .call(moveByChar) |
| .expectSpeech('cat', 'Image') |
| .expectBraille(lineOnCatText, {startIndex: 9, endIndex: 9}) |
| |
| // Unfortunately, the node offset being wrong here means there's no |
| // output for the next character move. Fix Once node offsets get fixed |
| // in Blink. |
| |
| .replay(); |
| }); |
| input.focus(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'RichTextSelectByLine', function() { |
| var mockFeedback = this.createMockFeedback(); |
| // Use digit strings like "11111" and "22222" because the character widths |
| // of digits are always the same. This means the test can move down one line |
| // middle of "11111" and reliably hit a given character position in "22222", |
| // regardless of font configuration. https://crbug.com/898213 |
| this.runWithLoadedTree(function() {/*! |
| <div> |
| <button id="go">Go</button> |
| </div> |
| <p contenteditable> |
| 11111 line<br> |
| 22222 line<br> |
| 33333 line<br> |
| </p> |
| <script> |
| var commands = [ |
| ['extend', 'forward', 'character'], |
| ['extend', 'forward', 'character'], |
| |
| ['extend', 'forward', 'line'], |
| ['extend', 'forward', 'line'], |
| |
| ['extend', 'backward', 'line'], |
| ['extend', 'backward', 'line'], |
| |
| ['extend', 'forward', 'documentBoundary'], |
| |
| ['move', 'forward', 'character'], |
| ['move', 'backward', 'character'], |
| ['move', 'backward', 'character'], |
| |
| ['extend', 'backward', 'line'], |
| ['extend', 'backward', 'line'], |
| |
| ['extend', 'forward', 'line'], |
| ]; |
| document.getElementById('go').addEventListener('click', function() { |
| var sel = getSelection(); |
| sel.modify.apply(sel, commands.shift()); |
| }, true); |
| </script> |
| */}, function(root) { |
| var input = root.find({role: RoleType.PARAGRAPH}); |
| var go = root.find({role: RoleType.BUTTON}); |
| var move = go.doDefault.bind(go); |
| |
| this.listenOnce(input, 'focus', function() { |
| |
| // By character. |
| mockFeedback.call(move) |
| .expectSpeech('1', 'selected') |
| .expectBraille('11111 line\nmled', {startIndex: 0, endIndex: 1}) |
| .call(move) |
| .expectSpeech('1', 'added to selection') |
| .expectBraille('11111 line\nmled', {startIndex: 0, endIndex: 2}) |
| |
| // Forward selection. |
| |
| // Growing. |
| |
| // By line (notice the partial selections from the first and second |
| // lines). |
| .call(move) |
| .expectSpeech('111 line \n 22', 'selected') |
| .expectBraille('22222 line\n', {startIndex: 0, endIndex: 2}) |
| |
| .call(move) |
| .expectSpeech('222 line \n 33', 'selected') |
| .expectBraille('33333 line\n', {startIndex: 0, endIndex: 2}) |
| |
| // Shrinking. |
| .call(move) |
| .expectSpeech('222 line \n 33', 'unselected') |
| .expectBraille('22222 line\n', {startIndex: 0, endIndex: 2}) |
| |
| .call(move) |
| .expectSpeech('11', 'selected') |
| .expectBraille('11111 line\nmled', {startIndex: 0, endIndex: 2}) |
| |
| // Document boundary. |
| .call(move) |
| .expectSpeech('111 line \n 22222 line \n 33333 line \n \n', |
| 'selected') |
| .expectBraille('33333 line\n', {startIndex: 0, endIndex: 10}) |
| |
| // The script repositions the caret to the 'n' of the third line. |
| .call(move) |
| .expectSpeech('33333 line') |
| .expectBraille('33333 line\n', {startIndex: 10, endIndex: 10}) |
| .call(move) |
| .expectSpeech('e') |
| .expectBraille('33333 line\n', {startIndex: 9, endIndex: 9}) |
| .call(move) |
| .expectSpeech('n') |
| .expectBraille('33333 line\n', {startIndex: 8, endIndex: 8}) |
| |
| // Backward selection. |
| |
| // Growing. |
| .call(move) |
| .expectSpeech('ne \n 33333 li', 'selected') |
| .expectBraille('22222 line\n', {startIndex: 8, endIndex: 11}) |
| |
| .call(move) |
| .expectSpeech('ne \n 22222 li', 'selected') |
| .expectBraille('11111 line\n', {startIndex: 8, endIndex: 11}) |
| |
| // Shrinking. |
| .call(move) |
| .expectSpeech('ne \n 22222 li', 'unselected') |
| .expectBraille('22222 line\n', {startIndex: 8, endIndex: 11}) |
| |
| .replay(); |
| }); |
| input.focus(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'EditableLineOneStaticText', function() { |
| this.runWithLoadedTree(function() {/*! |
| <p contenteditable style="word-spacing:100000px">this is a test</p> |
| */}, function(root) { |
| var staticText = root.find({role: RoleType.STATIC_TEXT}); |
| |
| var e = new editing.EditableLine(staticText, 0, staticText, 0); |
| assertEquals('this ', e.text); |
| |
| assertEquals(0, e.startOffset); |
| assertEquals(0, e.endOffset); |
| assertEquals(0, e.localStartOffset); |
| assertEquals(0, e.localEndOffset); |
| |
| assertEquals(0, e.containerStartOffset); |
| assertEquals(4, e.containerEndOffset); |
| |
| e = new editing.EditableLine(staticText, 1, staticText, 1); |
| assertEquals('this ', e.text); |
| |
| assertEquals(1, e.startOffset); |
| assertEquals(1, e.endOffset); |
| assertEquals(1, e.localStartOffset); |
| assertEquals(1, e.localEndOffset); |
| |
| assertEquals(0, e.containerStartOffset); |
| assertEquals(4, e.containerEndOffset); |
| |
| e = new editing.EditableLine(staticText, 5, staticText, 5); |
| assertEquals('is ', e.text); |
| |
| assertEquals(0, e.startOffset); |
| assertEquals(0, e.endOffset); |
| assertEquals(5, e.localStartOffset); |
| assertEquals(5, e.localEndOffset); |
| |
| assertEquals(0, e.containerStartOffset); |
| assertEquals(2, e.containerEndOffset); |
| |
| e = new editing.EditableLine(staticText, 7, staticText, 7); |
| assertEquals('is ', e.text); |
| |
| assertEquals(2, e.startOffset); |
| assertEquals(2, e.endOffset); |
| assertEquals(7, e.localStartOffset); |
| assertEquals(7, e.localEndOffset); |
| |
| assertEquals(0, e.containerStartOffset); |
| assertEquals(2, e.containerEndOffset); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'EditableLineTwoStaticTexts', function() { |
| this.runWithLoadedTree(function() {/*! |
| <p contenteditable>hello <b>world</b></p> |
| */}, function(root) { |
| var text = root.find({role: RoleType.STATIC_TEXT}); |
| var bold = text.nextSibling; |
| |
| var e = new editing.EditableLine(text, 0, text, 0); |
| assertEquals('hello world', e.text); |
| |
| assertEquals(0, e.startOffset); |
| assertEquals(0, e.endOffset); |
| assertEquals(0, e.localStartOffset); |
| assertEquals(0, e.localEndOffset); |
| |
| assertEquals(0, e.containerStartOffset); |
| assertEquals(5, e.containerEndOffset); |
| |
| e = new editing.EditableLine(text, 5, text, 5); |
| assertEquals('hello world', e.text); |
| |
| assertEquals(5, e.startOffset); |
| assertEquals(5, e.endOffset); |
| assertEquals(5, e.localStartOffset); |
| assertEquals(5, e.localEndOffset); |
| |
| assertEquals(0, e.containerStartOffset); |
| assertEquals(5, e.containerEndOffset); |
| |
| e = new editing.EditableLine(bold, 0, bold, 0); |
| assertEquals('hello world', e.text); |
| |
| assertEquals(6, e.startOffset); |
| assertEquals(6, e.endOffset); |
| assertEquals(0, e.localStartOffset); |
| assertEquals(0, e.localEndOffset); |
| |
| assertEquals(6, e.containerStartOffset); |
| assertEquals(10, e.containerEndOffset); |
| |
| e = new editing.EditableLine(bold, 4, bold, 4); |
| assertEquals('hello world', e.text); |
| |
| assertEquals(10, e.startOffset); |
| assertEquals(10, e.endOffset); |
| assertEquals(4, e.localStartOffset); |
| assertEquals(4, e.localEndOffset); |
| |
| assertEquals(6, e.containerStartOffset); |
| assertEquals(10, e.containerEndOffset); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'EditableLineEquality', function() { |
| this.runWithLoadedTree(function() {/*! |
| <div contenteditable role="textbox"> |
| <p style="word-spacing:100000px">this is a test</p> |
| <p>hello <b>world</b></p> |
| </div> |
| */}, function(root) { |
| var thisIsATest = root.findAll({role: RoleType.PARAGRAPH})[0].firstChild; |
| var hello = root.findAll({role: RoleType.PARAGRAPH})[1].firstChild; |
| var world = root.findAll({role: RoleType.PARAGRAPH})[1].lastChild; |
| |
| // The same position -- sanity check. |
| var e1 = new editing.EditableLine(thisIsATest, 0, thisIsATest, 0); |
| assertEquals('this ', e1.text); |
| assertTrue(e1.isSameLine(e1)); |
| |
| // Offset into the same soft line. |
| var e2 = new editing.EditableLine(thisIsATest, 1, thisIsATest, 1); |
| assertTrue(e1.isSameLine(e2)); |
| |
| // Boundary. |
| e2 = new editing.EditableLine(thisIsATest, 4, thisIsATest, 4); |
| assertTrue(e1.isSameLine(e2)); |
| |
| // Offsets into different soft lines. |
| e2 = new editing.EditableLine(thisIsATest, 5, thisIsATest, 5); |
| assertEquals('is ', e2.text); |
| assertFalse(e1.isSameLine(e2)); |
| |
| // Sanity check; second soft line. |
| assertTrue(e2.isSameLine(e2)); |
| |
| // Different offsets into second soft line. |
| e1 = new editing.EditableLine(thisIsATest, 6, thisIsATest, 6); |
| assertTrue(e1.isSameLine(e2)); |
| |
| // Boundary. |
| e1 = new editing.EditableLine(thisIsATest, 7, thisIsATest, 7); |
| assertTrue(e1.isSameLine(e2)); |
| |
| // Third line. |
| e1 = new editing.EditableLine(thisIsATest, 8, thisIsATest, 8); |
| assertEquals('a ', e1.text); |
| assertFalse(e1.isSameLine(e2)); |
| |
| // Last line. |
| e2 = new editing.EditableLine(thisIsATest, 10, thisIsATest, 10); |
| assertEquals('test', e2.text); |
| assertFalse(e1.isSameLine(e2)); |
| |
| // Boundary. |
| e1 = new editing.EditableLine(thisIsATest, 13, thisIsATest, 13); |
| assertTrue(e1.isSameLine(e2)); |
| |
| // Cross into new paragraph. |
| e2 = new editing.EditableLine(hello, 0, hello, 0); |
| assertEquals('hello world', e2.text); |
| assertFalse(e1.isSameLine(e2)); |
| |
| // On same node, with multi-static text line. |
| e1 = new editing.EditableLine(hello, 1, hello, 1); |
| assertTrue(e1.isSameLine(e2)); |
| |
| // On same node, with multi-static text line; boundary. |
| e1 = new editing.EditableLine(hello, 5, hello, 5); |
| assertTrue(e1.isSameLine(e2)); |
| |
| // On different node, with multi-static text line. |
| e1 = new editing.EditableLine(world, 1, world, 1); |
| assertTrue(e1.isSameLine(e2)); |
| |
| // Another mix of lines. |
| e2 = new editing.EditableLine(thisIsATest, 9, thisIsATest, 9); |
| assertFalse(e1.isSameLine(e2)); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'EditableLineStrictEquality', function() { |
| this.runWithLoadedTree(function() {/*! |
| <div contenteditable role="textbox"> |
| <p style="word-spacing:100000px">this is a test</p> |
| <p>hello <b>world</b></p> |
| </div> |
| */}, function(root) { |
| var thisIsATest = root.findAll({role: RoleType.PARAGRAPH})[0].firstChild; |
| var hello = root.findAll({role: RoleType.PARAGRAPH})[1].firstChild; |
| var world = root.findAll({role: RoleType.PARAGRAPH})[1].lastChild; |
| |
| // The same position -- sanity check. |
| var e1 = new editing.EditableLine(thisIsATest, 0, thisIsATest, 0); |
| assertEquals('this ', e1.text); |
| assertTrue(e1.isSameLineAndSelection(e1)); |
| |
| // Offset into the same soft line. |
| var e2 = new editing.EditableLine(thisIsATest, 1, thisIsATest, 1); |
| assertFalse(e1.isSameLineAndSelection(e2)); |
| |
| // Boundary. |
| e2 = new editing.EditableLine(thisIsATest, 4, thisIsATest, 4); |
| assertFalse(e1.isSameLineAndSelection(e2)); |
| |
| // Offsets into different soft lines. |
| e2 = new editing.EditableLine(thisIsATest, 5, thisIsATest, 5); |
| assertEquals('is ', e2.text); |
| assertFalse(e1.isSameLineAndSelection(e2)); |
| |
| // Sanity check; second soft line. |
| assertTrue(e2.isSameLineAndSelection(e2)); |
| |
| // Different offsets into second soft line. |
| e1 = new editing.EditableLine(thisIsATest, 6, thisIsATest, 6); |
| assertFalse(e1.isSameLineAndSelection(e2)); |
| |
| // Boundary. |
| e1 = new editing.EditableLine(thisIsATest, 7, thisIsATest, 7); |
| assertFalse(e1.isSameLineAndSelection(e2)); |
| |
| // Cross into new paragraph. |
| e2 = new editing.EditableLine(hello, 0, hello, 0); |
| assertEquals('hello world', e2.text); |
| assertFalse(e1.isSameLineAndSelection(e2)); |
| |
| // On same node, with multi-static text line. |
| e1 = new editing.EditableLine(hello, 1, hello, 1); |
| assertFalse(e1.isSameLineAndSelection(e2)); |
| |
| // On same node, with multi-static text line; boundary. |
| e1 = new editing.EditableLine(hello, 5, hello, 5); |
| assertFalse(e1.isSameLineAndSelection(e2)); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'EditableLineBaseLineAnchorOrFocus', function() { |
| this.runWithLoadedTree(function() {/*! |
| <div contenteditable role="textbox"> |
| <p style="word-spacing:100000px">this is a test</p> |
| <p>hello <b>world</b></p> |
| </div> |
| */}, function(root) { |
| var thisIsATest = root.findAll({role: RoleType.PARAGRAPH})[0].firstChild; |
| var hello = root.findAll({role: RoleType.PARAGRAPH})[1].firstChild; |
| var world = root.findAll({role: RoleType.PARAGRAPH})[1].lastChild; |
| |
| // The same position -- sanity check. |
| var e1 = new editing.EditableLine(thisIsATest, 0, thisIsATest, 0, true); |
| assertEquals('this ', e1.text); |
| |
| // Offsets into different soft lines; base on focus (default). |
| e1 = new editing.EditableLine(thisIsATest, 0, thisIsATest, 6); |
| assertEquals('is ', e1.text); |
| // Notice that the offset is truncated at the beginning of the line. |
| assertEquals(0, e1.startOffset); |
| // Notice that the end offset is properly retained. |
| assertEquals(1, e1.endOffset); |
| |
| // Offsets into different soft lines; base on anchor. |
| e1 = new editing.EditableLine(thisIsATest, 0, thisIsATest, 6, true); |
| assertEquals('this ', e1.text); |
| assertEquals(0, e1.startOffset); |
| // Notice that the end offset is truncated up to the end of line. |
| assertEquals(5, e1.endOffset); |
| |
| // Across paragraph selection with base line on focus. |
| e1 = new editing.EditableLine(thisIsATest, 5, hello, 2); |
| assertEquals('hello world', e1.text); |
| assertEquals(0, e1.startOffset); |
| assertEquals(2, e1.endOffset); |
| |
| // Across paragraph selection with base line on anchor. |
| e1 = new editing.EditableLine(thisIsATest, 5, hello, 2, true); |
| assertEquals('is ', e1.text); |
| assertEquals(0, e1.startOffset); |
| assertEquals(3, e1.endOffset); |
| }) |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'IsValidLine', function() { |
| this.runWithLoadedTree(function() {/*! |
| <div contenteditable role="textbox"> |
| <p style="word-spacing:100000px">this is a test</p> |
| <p>end</p> |
| </div> |
| */}, function(root) { |
| // Each word is on its own line, but parented by a static text. |
| var text, endText; |
| [text, endText] = root.findAll({role: RoleType.STATIC_TEXT}); |
| |
| // The EditableLine object automatically adjusts to surround the line no |
| // matter what the input is. |
| var line = new editing.EditableLine(text, 0, text, 0); |
| assertTrue(line.isValidLine()); |
| |
| // During the course of editing operations, this line may become |
| // invalidted. For example, if a user starts typing into the line, the |
| // bounding nodes might change. |
| // Simulate that here by modifying private state. |
| |
| // This puts the line at offset 8 (|this is a|). |
| line.localLineStartContainerOffset_ = 0; |
| line.localLineEndContainerOffset_ = 8; |
| assertFalse(line.isValidLine()); |
| |
| // This puts us in the first line. |
| line.localLineStartContainerOffset_ = 0; |
| line.localLineEndContainerOffset_ = 4; |
| assertTrue(line.isValidLine()); |
| |
| // This is still fine (for our purposes) because the line is still intact. |
| line.localLineStartContainerOffset_ = 0; |
| line.localLineEndContainerOffset_ = 2; |
| assertTrue(line.isValidLine()); |
| |
| // The line has changed. The end has been moved for some reason. |
| line.lineEndContainer_ = endText; |
| assertFalse(line.isValidLine()); |
| }) |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'TelTrimsWhitespace', function() { |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(function() {/*! |
| <div id="go"></div> |
| <input id="input" type="tel"></input> |
| <script> |
| var data = [ |
| '6 ', |
| '60 ', |
| '601 ', |
| '60 ' |
| ]; |
| var go = document.getElementById('go'); |
| var input = document.getElementById('input'); |
| var index = 0; |
| go.addEventListener('click', function() { |
| input.value = data[index]; |
| index++; |
| input.selectionStart = index; |
| input.selectionEnd = index; |
| }, true); |
| </script> |
| */}, function(root) { |
| var input = root.find({role: RoleType.TEXT_FIELD}); |
| var go = root.find({role: RoleType.GENERIC_CONTAINER}); |
| var enterKey = go.doDefault.bind(go); |
| this.listenOnce(input, 'focus', function() { |
| mockFeedback.call(enterKey) |
| .expectSpeech('6') |
| .call(enterKey) |
| .expectSpeech('0') |
| .call(enterKey) |
| .expectSpeech('1') |
| |
| // Deletion. |
| .call(enterKey) |
| .expectSpeech('1') |
| .replay(); |
| }); |
| input.focus(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'BackwardWordDelete', function() { |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(function() {/*! |
| <div |
| style='max-width: 5px; overflow-wrap: normal' |
| contenteditable |
| role="textbox"> |
| this is a test |
| </div> |
| */}, function(root) { |
| var input = root.find({role: RoleType.TEXT_FIELD}); |
| this.listenOnce(input, 'focus', function() { |
| mockFeedback.call(this.press(35 /* end */, {ctrl: true})) |
| .call(this.press(8 /* backspace */, {ctrl: true})) |
| .expectSpeech('test, deleted') |
| .expectBraille('a\u00a0', {startIndex: 2, endIndex: 2}) |
| .call(this.press(8 /* backspace */, {ctrl: true})) |
| .expectSpeech('a , deleted') |
| .expectBraille('is\u00a0', {startIndex: 3, endIndex: 3}) |
| .call(this.press(8 /* backspace */, {ctrl: true})) |
| .expectSpeech('is , deleted') |
| .expectBraille('this\u00a0mled', {startIndex: 5, endIndex: 5}) |
| .call(this.press(8 /* backspace */, {ctrl: true})) |
| .expectSpeech('this , deleted') |
| .expectBraille(' ed mled', {startIndex: 0, endIndex: 0}) |
| .replay(); |
| }); |
| input.focus(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxEditingTest', 'BackwardWordDeleteAcrossParagraphs', function() { |
| var mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(function() {/*! |
| <div |
| style='max-width: 5px; overflow-wrap: normal' |
| contenteditable |
| role="textbox"> |
| <p>first line</p> |
| <p>second line</p> |
| </div> |
| */}, function(root) { |
| var input = root.find({role: RoleType.TEXT_FIELD}); |
| this.listenOnce(input, 'focus', function() { |
| mockFeedback.call(this.press(35 /* end */, {ctrl: true})) |
| .expectSpeech('line') |
| .call(this.press(8 /* backspace */, {ctrl: true})) |
| .expectSpeech('line, deleted') |
| .call(this.press(8 /* backspace */, {ctrl: true})) |
| .expectSpeech('second , deleted') |
| .call(this.press(8 /* backspace */, {ctrl: true})) |
| .expectSpeech('first\u00a0') |
| .call(this.press(8 /* backspace */, {ctrl: true})) |
| .expectSpeech('first , deleted') |
| .replay(); |
| }); |
| input.focus(); |
| }); |
| }); |