Commit 0ccf0ae3 authored by David Tseng's avatar David Tseng Committed by Chromium LUCI CQ

Add move by word intent speech feedback support

R=akihiroota@chromium.org

Bug: none
Test: existing editing tests for word feedback. Added new unit-test-like suite for intent_handler character and line speech.
Change-Id: If32425cd35ea6504841fef8c7d3eb6245b609c3e
AX-Relnotes: n/a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2618558
Commit-Queue: David Tseng <dtseng@chromium.org>
Reviewed-by: default avatarAkihiro Ota <akihiroota@chromium.org>
Cr-Commit-Position: refs/heads/master@{#842164}
parent 3d1abe89
...@@ -453,6 +453,7 @@ if (is_chromeos_ash) { ...@@ -453,6 +453,7 @@ if (is_chromeos_ash) {
"background/cursors_test.js", "background/cursors_test.js",
"background/download_handler_test.js", "background/download_handler_test.js",
"background/editing/editing_test.js", "background/editing/editing_test.js",
"background/editing/intent_handler_test.js",
"background/keyboard_handler_test.js", "background/keyboard_handler_test.js",
"background/live_regions_test.js", "background/live_regions_test.js",
"background/locale_output_helper_test.js", "background/locale_output_helper_test.js",
......
...@@ -386,6 +386,11 @@ node if true. ...@@ -386,6 +386,11 @@ node if true.
return this.startContainerValue_; return this.startContainerValue_;
} }
/** @return {!cursors.Cursor} */
get startCursor() {
return this.start_;
}
/** @return {boolean} */ /** @return {boolean} */
hasCollapsedSelection() { hasCollapsedSelection() {
return this.start_.equals(this.end_); return this.start_.equals(this.end_);
......
...@@ -8,12 +8,18 @@ ...@@ -8,12 +8,18 @@
goog.provide('IntentHandler'); goog.provide('IntentHandler');
goog.require('constants');
goog.require('editing.EditableLine'); goog.require('editing.EditableLine');
goog.require('Output');
goog.scope(function() { goog.scope(function() {
const AutomationIntent = chrome.automation.AutomationIntent; const AutomationIntent = chrome.automation.AutomationIntent;
const Dir = constants.Dir;
const IntentCommandType = chrome.automation.IntentCommandType; const IntentCommandType = chrome.automation.IntentCommandType;
const IntentTextBoundaryType = chrome.automation.IntentTextBoundaryType; const IntentTextBoundaryType = chrome.automation.IntentTextBoundaryType;
const Movement = cursors.Movement;
const Range = cursors.Range;
const Unit = cursors.Unit;
/** /**
* A stateless class that turns intents into speech. * A stateless class that turns intents into speech.
...@@ -95,6 +101,26 @@ IntentHandler = class { ...@@ -95,6 +101,26 @@ IntentHandler = class {
cur.speakLine(prev); cur.speakLine(prev);
return true; return true;
case IntentTextBoundaryType.WORD_END:
case IntentTextBoundaryType.WORD_START:
const pos = cur.startCursor;
// When movement goes to the end of a word, we actually want to describe
// the word itself; this is considered the previous word so impacts the
// movement type below. We can give further context e.g. by saying "end
// of word", if we chose to be more verbose.
const shouldMoveToPreviousWord =
intent.textBoundary === IntentTextBoundaryType.WORD_END;
const start = pos.move(
Unit.WORD,
shouldMoveToPreviousWord ? Movement.DIRECTIONAL : Movement.BOUND,
Dir.BACKWARD);
const end = pos.move(Unit.WORD, Movement.BOUND, Dir.FORWARD);
new Output()
.withSpeech(new Range(start, end), null, Output.EventType.NAVIGATE)
.go();
return true;
// TODO: implement support. // TODO: implement support.
case IntentTextBoundaryType.FORMAT: case IntentTextBoundaryType.FORMAT:
case IntentTextBoundaryType.OBJECT: case IntentTextBoundaryType.OBJECT:
...@@ -108,8 +134,6 @@ IntentHandler = class { ...@@ -108,8 +134,6 @@ IntentHandler = class {
case IntentTextBoundaryType.SENTENCE_START: case IntentTextBoundaryType.SENTENCE_START:
case IntentTextBoundaryType.SENTENCE_START_OR_END: case IntentTextBoundaryType.SENTENCE_START_OR_END:
case IntentTextBoundaryType.WEB_PAGE: case IntentTextBoundaryType.WEB_PAGE:
case IntentTextBoundaryType.WORD_END:
case IntentTextBoundaryType.WORD_START:
case IntentTextBoundaryType.WORD_START_OR_END: case IntentTextBoundaryType.WORD_START_OR_END:
break; break;
} }
......
// Copyright 2021 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.
GEN_INCLUDE([
'../../testing/chromevox_next_e2e_test_base.js',
'../../../common/testing/assert_additions.js',
]);
/**
* Test fixture for intent handler tests.
* These tests are written to be as "unit" test like as possible e.g. mocking
* out classes not under test but it runs under a full extension test
* environment to get things like extension api literals.
*/
ChromeVoxIntentHandlerTest = class extends ChromeVoxNextE2ETest {
/** @override */
setUp() {
window.Dir = constants.Dir;
window.IntentTextBoundaryType = chrome.automation.IntentTextBoundaryType;
window.Movement = cursors.Movement;
window.Unit = cursors.Unit;
}
};
SYNC_TEST_F('ChromeVoxIntentHandlerTest', 'MoveByCharacter', function() {
let lastSpoken;
ChromeVox.tts.speak = (text) => lastSpoken = text;
const intent = {textBoundary: IntentTextBoundaryType.CHARACTER};
const move = IntentHandler.onMoveSelection.bind(null, intent);
move({text: 'hello', startOffset: 0});
assertEquals('h', lastSpoken);
move({text: 'hello', startOffset: 1});
assertEquals('e', lastSpoken);
move({text: 'hello', startOffset: 2});
assertEquals('l', lastSpoken);
move({text: 'hello', startOffset: 3});
assertEquals('l', lastSpoken);
move({text: 'hello', startOffset: 4});
assertEquals('o', lastSpoken);
move({text: 'hello', startOffset: 5});
assertEquals('\n', lastSpoken);
});
SYNC_TEST_F('ChromeVoxIntentHandlerTest', 'MoveByWord', function() {
let calls = [];
const fakeLine = new class {
constructor() {}
get startCursor() {
return new class {
move(...args) {
calls.push(['move', ...args]);
}
};
}
};
Output.prototype.withSpeech = function(...args) {
calls.push(['withSpeech', ...args]);
return this;
};
Output.prototype.go = function() {
calls.push(['go']);
};
let intent = {textBoundary: IntentTextBoundaryType.WORD_END};
IntentHandler.onMoveSelection(intent, fakeLine);
assertEquals(4, calls.length);
assertArraysEquals(
['move', Unit.WORD, Movement.DIRECTIONAL, Dir.BACKWARD], calls[0]);
assertArraysEquals(
['move', Unit.WORD, Movement.BOUND, Dir.FORWARD], calls[1]);
assertArraysEquals(
['withSpeech', {}, null, Output.EventType.NAVIGATE], calls[2]);
assertArraysEquals(['go'], calls[3]);
calls = [];
intent = {textBoundary: IntentTextBoundaryType.WORD_START};
IntentHandler.onMoveSelection(intent, fakeLine);
assertEquals(4, calls.length);
assertArraysEquals(
['move', Unit.WORD, Movement.BOUND, Dir.BACKWARD], calls[0]);
assertArraysEquals(
['move', Unit.WORD, Movement.BOUND, Dir.FORWARD], calls[1]);
assertArraysEquals(
['withSpeech', {}, null, Output.EventType.NAVIGATE], calls[2]);
assertArraysEquals(['go'], calls[3]);
calls = [];
intent = {textBoundary: IntentTextBoundaryType.WORD_START_OR_END};
IntentHandler.onMoveSelection(intent, fakeLine);
assertEquals(0, calls.length);
});
SYNC_TEST_F('ChromeVoxIntentHandlerTest', 'MoveByLine', function() {
const fakeLine = new class {
constructor() {
this.speakLineCount = 0;
}
speakLine() {
this.speakLineCount++;
}
get startCursor() {
return new class {
move() {}
};
}
};
Output.prototype.withSpeech = function() {
return this;
};
Output.prototype.go = function() {};
let intent = {textBoundary: IntentTextBoundaryType.LINE_END};
IntentHandler.onMoveSelection(intent, fakeLine);
assertEquals(1, fakeLine.speakLineCount);
intent = {textBoundary: IntentTextBoundaryType.LINE_START};
IntentHandler.onMoveSelection(intent, fakeLine);
assertEquals(2, fakeLine.speakLineCount);
intent = {textBoundary: IntentTextBoundaryType.LINE_START_OR_END};
IntentHandler.onMoveSelection(intent, fakeLine);
assertEquals(3, fakeLine.speakLineCount);
intent = {textBoundary: IntentTextBoundaryType.WORD_START};
IntentHandler.onMoveSelection(intent, fakeLine);
assertEquals(3, fakeLine.speakLineCount);
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment