blob: 91aab23a9eb3a35b137a457f9cb562d30a78d0a0 [file] [log] [blame]
// 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&#xa;
line 2&#xa;
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();
});
});