| // Copyright 2020 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(['panel_test_base.js']); |
| GEN_INCLUDE(['../testing/mock_feedback.js']); |
| |
| /** |
| * Test fixture for the interactive tutorial. |
| */ |
| ChromeVoxTutorialTest = class extends ChromeVoxPanelTestBase { |
| assertActiveLessonIndex(expectedIndex) { |
| assertEquals(expectedIndex, this.getTutorial().activeLessonIndex); |
| } |
| |
| assertActiveScreen(expectedScreen) { |
| assertEquals(expectedScreen, this.getTutorial().activeScreen); |
| } |
| |
| async launchAndWaitForTutorial() { |
| new PanelCommand(PanelCommandType.TUTORIAL).send(); |
| await this.waitForTutorial(); |
| return new Promise(resolve => { |
| resolve(); |
| }); |
| } |
| |
| /** Waits for the tutorial to load. */ |
| async waitForTutorial() { |
| return new Promise(resolve => { |
| const doc = this.getPanelWindow().document; |
| if (doc.getElementById('chromevox-tutorial-container')) { |
| resolve(); |
| } else { |
| /** |
| * @param {Array<MutationRecord>} mutationsList |
| * @param {MutationObserver} observer |
| */ |
| const onMutation = (mutationsList, observer) => { |
| for (const mutation of mutationsList) { |
| if (mutation.type === 'childList') { |
| for (const node of mutation.addedNodes) { |
| if (node.id === 'chromevox-tutorial-container') { |
| // Once the tutorial has been added to the document, we need |
| // to wait for the lesson templates to load. |
| const panel = this.getPanel(); |
| if (panel.tutorialReadyForTesting_) { |
| resolve(); |
| } else { |
| panel.tutorial.addEventListener('readyfortesting', () => { |
| resolve(); |
| }); |
| } |
| observer.disconnect(); |
| } |
| } |
| } |
| } |
| }; |
| |
| const observer = new MutationObserver(onMutation); |
| observer.observe( |
| doc.body /* target */, {childList: true} /* options */); |
| } |
| }); |
| } |
| |
| getTutorial() { |
| return this.getPanel().tutorial; |
| } |
| |
| get simpleDoc() { |
| return ` |
| <p>Some web content</p> |
| `; |
| } |
| }; |
| |
| TEST_F('ChromeVoxTutorialTest', 'BasicTest', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| mockFeedback |
| .expectSpeech( |
| 'ChromeVox tutorial', 'Heading 1', |
| 'Press Search + Right Arrow, or Search + Left Arrow to browse' + |
| ' topics') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Quick orientation', 'Link') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Essential keys', 'Link') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Navigation', 'Link') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Command references', 'Link') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Sounds and settings', 'Link') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Resources', 'Link') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Exit tutorial', 'Button') |
| .replay(); |
| }); |
| }); |
| |
| // Tests that different lessons are shown when choosing an experience from the |
| // main menu. |
| // TODO(crbug.com/1193799): fix ax node errors causing console spew and |
| // breaking tests |
| TEST_F('ChromeVoxTutorialTest', 'DISABLED_LessonSetTest', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Quick orientation') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech(/Quick Orientation Tutorial, [0-9]+ Lessons/) |
| .expectSpeech( |
| 'Press Search + Right Arrow, or Search + Left Arrow to browse ' + |
| 'lessons for this topic') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Welcome to ChromeVox!') |
| .call(() => { |
| // Call from the tutorial directly, instead of navigating to and |
| // clicking on the main menu button. |
| tutorial.showMainMenu_(); |
| }) |
| .expectSpeech('ChromeVox tutorial') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Quick orientation') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Essential keys', 'Link') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) |
| .call(doCmd('nextObject')) |
| .expectSpeech('On, Off, and Stop') |
| .replay(); |
| }); |
| }); |
| |
| // Tests that a static lesson does not show the 'Practice area' button. |
| TEST_F('ChromeVoxTutorialTest', 'NoPracticeAreaTest', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Quick orientation') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Essential keys') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) |
| .call(() => { |
| tutorial.showLesson_(0); |
| }) |
| .expectSpeech( |
| 'On, Off, and Stop', 'Heading 1', |
| ' Press Search + Right Arrow, or Search + Left Arrow to navigate ' + |
| 'this lesson ') |
| .call(doCmd('nextButton')) |
| .expectSpeech('Next lesson') |
| .replay(); |
| }); |
| }); |
| |
| // Tests that an interactive lesson shows the 'Practice area' button. |
| TEST_F('ChromeVoxTutorialTest', 'HasPracticeAreaTest', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Quick orientation') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Essential keys') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Navigation') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech(/Navigation Tutorial, [0-9]+ Lessons/) |
| .call(() => { |
| tutorial.showLesson_(1); |
| }) |
| .expectSpeech('Jump Commands', 'Heading 1') |
| .call(doCmd('nextButton')) |
| .expectSpeech('Practice area') |
| .replay(); |
| }); |
| }); |
| |
| // Tests nudges given in the general tutorial context. |
| // The first three nudges should read the current item with full context. |
| // Afterward, general hints will be given about using ChromeVox. Lastly, |
| // we will give a hint for exiting the tutorial. |
| TEST_F('ChromeVoxTutorialTest', 'GeneralNudgesTest', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| const giveNudge = () => { |
| tutorial.giveNudge(); |
| }; |
| mockFeedback.expectSpeech('ChromeVox tutorial'); |
| for (let i = 0; i < 3; ++i) { |
| mockFeedback.call(giveNudge).expectSpeech( |
| 'ChromeVox tutorial', 'Heading 1'); |
| } |
| mockFeedback.call(giveNudge) |
| .expectSpeech('Hint: Hold Search and press the arrow keys to navigate.') |
| .call(giveNudge) |
| .expectSpeech( |
| 'Hint: Press Search + Space to activate the current item.') |
| .call(giveNudge) |
| .expectSpeech( |
| 'Hint: Press Escape if you would like to exit this tutorial.') |
| .replay(); |
| }); |
| }); |
| |
| // Tests nudges given in the practice area context. Note, each practice area |
| // can have different nudge messages; this test confirms that nudges given in |
| // the practice area differ from those given in the general tutorial context. |
| TEST_F('ChromeVoxTutorialTest', 'DISABLED_PracticeAreaNudgesTest', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| const giveNudge = () => { |
| tutorial.giveNudge(); |
| }; |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Quick orientation') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Essential keys') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Navigation') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech(/Navigation Tutorial, [0-9]+ Lessons/) |
| .call(() => { |
| tutorial.showLesson_(0); |
| }) |
| .expectSpeech('Basic Navigation', 'Heading 1') |
| .call(doCmd('nextButton')) |
| .expectSpeech('Practice area') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech(/Try using basic navigation to navigate/) |
| .call(giveNudge) |
| .expectSpeech( |
| 'Try pressing Search + left/right arrow. The search key is ' + |
| 'directly above the shift key') |
| .call(giveNudge) |
| .expectSpeech('Press Search + Space to activate the current item.') |
| .replay(); |
| }); |
| }); |
| |
| // Tests that the tutorial closes when the 'Exit tutorial' button is clicked. |
| TEST_F('ChromeVoxTutorialTest', 'ExitButtonTest', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(doCmd('previousButton')) |
| .expectSpeech('Exit tutorial') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech('Some web content') |
| .replay(); |
| }); |
| }); |
| |
| // Tests that the tutorial closes when Escape is pressed. |
| TEST_F('ChromeVoxTutorialTest', 'EscapeTest', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(() => { |
| // Press Escape. |
| tutorial.onKeyDown({ |
| key: 'Escape', |
| preventDefault: () => {}, |
| stopPropagation: () => {} |
| }); |
| }) |
| .expectSpeech('Some web content') |
| .replay(); |
| }); |
| }); |
| |
| // Tests that the main menu button navigates the user to the main menu screen. |
| TEST_F('ChromeVoxTutorialTest', 'MainMenuButton', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(this.assertActiveScreen.bind(this, 'main_menu')) |
| .call(doCmd('nextObject')) |
| .expectSpeech('Quick orientation') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Essential keys') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) |
| .call(this.assertActiveScreen.bind(this, 'lesson_menu')) |
| .call(doCmd('previousButton')) |
| .expectSpeech('Exit tutorial') |
| .call(doCmd('previousButton')) |
| .expectSpeech('Main menu') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech('ChromeVox tutorial') |
| .call(this.assertActiveScreen.bind(this, 'main_menu')) |
| .replay(); |
| }); |
| }); |
| |
| // Tests that the all lessons button navigates the user to the lesson menu |
| // screen. |
| // TODO(crbug.com/1193799): fix ax node errors causing console spew and |
| // breaking tests |
| TEST_F('ChromeVoxTutorialTest', 'DISABLED_AllLessonsButton', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(this.assertActiveScreen.bind(this, 'main_menu')) |
| .call(doCmd('nextObject')) |
| .expectSpeech('Quick orientation') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Essential keys') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) |
| .call(this.assertActiveScreen.bind(this, 'lesson_menu')) |
| .call(doCmd('nextObject')) |
| .expectSpeech('On, Off, and Stop') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech('On, Off, and Stop', 'Heading 1') |
| .call(this.assertActiveScreen.bind(this, 'lesson')) |
| .call(doCmd('nextButton')) |
| .expectSpeech('Next lesson') |
| .call(doCmd('nextButton')) |
| .expectSpeech('All lessons') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) |
| .call(this.assertActiveScreen.bind(this, 'lesson_menu')) |
| .replay(); |
| }); |
| }); |
| |
| // Tests that the next and previous lesson buttons navigate properly. |
| // TODO(crbug.com/1193799): fix ax node errors causing console spew and |
| // breaking tests |
| TEST_F('ChromeVoxTutorialTest', 'DISABLED_NextPreviousButtons', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(() => { |
| tutorial.curriculum = 'essential_keys'; |
| tutorial.showLesson_(0); |
| this.assertActiveLessonIndex(0); |
| this.assertActiveScreen('lesson'); |
| }) |
| .expectSpeech('On, Off, and Stop', 'Heading 1') |
| .call(doCmd('nextButton')) |
| .expectSpeech('Next lesson') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech('The ChromeVox modifier key', 'Heading 1') |
| .call(this.assertActiveLessonIndex.bind(this, 1)) |
| .call(doCmd('nextButton')) |
| .expectSpeech('Previous lesson') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech('On, Off, and Stop', 'Heading 1') |
| .call(this.assertActiveLessonIndex.bind(this, 0)) |
| .replay(); |
| }); |
| }); |
| |
| // Tests that the title of an interactive lesson is read when shown. |
| TEST_F('ChromeVoxTutorialTest', 'AutoReadTitle', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Quick orientation') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech(/Quick Orientation Tutorial, [0-9]+ Lessons/) |
| .call(doCmd('nextObject')) |
| .expectSpeech('Welcome to ChromeVox!') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech('Welcome to ChromeVox!') |
| .expectSpeech( |
| 'Welcome to the ChromeVox tutorial. To exit this tutorial at any ' + |
| 'time, press the Escape key on the top left corner of the ' + |
| 'keyboard. To turn off ChromeVox, hold Control and Alt, and ' + |
| `press Z. When you're ready, use the spacebar to move to the ` + |
| 'next lesson.') |
| .replay(); |
| }); |
| }); |
| |
| // Tests that we read a hint for navigating a lesson when it is shown. |
| TEST_F('ChromeVoxTutorialTest', 'LessonHint', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Quick orientation') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Essential keys') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech(/Essential Keys Tutorial, [0-9]+ Lessons/) |
| .call(() => { |
| tutorial.showLesson_(0); |
| }) |
| .expectSpeech('On, Off, and Stop', 'Heading 1') |
| .expectSpeech( |
| ' Press Search + Right Arrow, or Search + Left Arrow to navigate' + |
| ' this lesson ') |
| .replay(); |
| }); |
| }); |
| |
| // Tests for correct speech and earcons on the earcons lesson. |
| TEST_F('ChromeVoxTutorialTest', 'EarconLesson', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| const nextObjectAndExpectSpeechAndEarcon = (speech, earcon) => { |
| mockFeedback.call(doCmd('nextObject')) |
| .expectSpeech(speech) |
| .expectEarcon(earcon); |
| }; |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(() => { |
| // Show the lesson. |
| tutorial.curriculum = 'sounds_and_settings'; |
| tutorial.showLesson_(0); |
| }) |
| .expectSpeech('Sounds') |
| .call(doCmd('nextObject')) |
| .expectSpeech(new RegExp( |
| 'ChromeVox uses sounds to give you essential and additional ' + |
| 'information.')); |
| nextObjectAndExpectSpeechAndEarcon('A modal alert', Earcon.ALERT_MODAL); |
| nextObjectAndExpectSpeechAndEarcon( |
| 'A non modal alert', Earcon.ALERT_NONMODAL); |
| nextObjectAndExpectSpeechAndEarcon('A button', Earcon.BUTTON); |
| mockFeedback.replay(); |
| }); |
| }); |
| |
| // Tests that a lesson from the quick orientation blocks ChromeVox execution |
| // until the specified keystroke is pressed. |
| // TODO(crbug.com/1193799): fix ax node errors causing console spew and |
| // breaking tests |
| TEST_F( |
| 'ChromeVoxTutorialTest', 'DISABLED_QuickOrientationLessonTest', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| const keyboardHandler = ChromeVoxState.instance.keyboardHandler_; |
| |
| // Helper functions. For this test, activate commands by hooking into |
| // the BackgroundKeyboardHandler. This is necessary because |
| // UserActionMonitor intercepts key sequences before they are routed to |
| // CommandHandler. |
| const getRangeStartNode = () => { |
| return ChromeVoxState.instance.getCurrentRange().start.node; |
| }; |
| |
| const simulateKeyPress = (keyCode, opt_modifiers) => { |
| const keyEvent = TestUtils.createMockKeyEvent(keyCode, opt_modifiers); |
| keyboardHandler.onKeyDown(keyEvent); |
| keyboardHandler.onKeyUp(keyEvent); |
| }; |
| |
| let firstLessonNode; |
| await mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Quick orientation') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech(/Quick Orientation Tutorial, [0-9]+ Lessons/) |
| .call(doCmd('nextObject')) |
| .expectSpeech('Welcome to ChromeVox!') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech(/Welcome to the ChromeVox tutorial./) |
| .call(() => { |
| assertEquals(0, tutorial.activeLessonId); |
| firstLessonNode = getRangeStartNode(); |
| }) |
| .call(simulateKeyPress.bind( |
| this, KeyCode.RIGHT, {searchKeyHeld: true})) |
| .call(() => { |
| assertEquals(firstLessonNode, getRangeStartNode()); |
| assertEquals(0, tutorial.activeLessonId); |
| }) |
| .call(simulateKeyPress.bind( |
| this, KeyCode.LEFT, {searchKeyHeld: true})) |
| .call(() => { |
| assertEquals(firstLessonNode, getRangeStartNode()); |
| assertEquals(0, tutorial.activeLessonId); |
| }) |
| // Pressing space, which is the desired key sequence, should move us |
| // to the next lesson. |
| .call(simulateKeyPress.bind(this, KeyCode.SPACE, {})) |
| .expectSpeech('Essential Keys: Control') |
| .expectSpeech(/Let's start with a few keys you'll use regularly./) |
| .call(() => { |
| assertEquals(1, tutorial.activeLessonId); |
| assertNotEquals(firstLessonNode, getRangeStartNode()); |
| }) |
| // Pressing control, which is the desired key sequence, should move |
| // us to the next lesson. |
| .call(simulateKeyPress.bind(this, KeyCode.CONTROL, {})) |
| .expectSpeech('Essential Keys: Shift') |
| .call(() => { |
| assertEquals(2, tutorial.activeLessonId); |
| }) |
| .replay(); |
| }); |
| }); |
| |
| // Tests that tutorial nudges are restarted whenever the current range changes. |
| TEST_F('ChromeVoxTutorialTest', 'RestartNudges', function() { |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| let restart = false; |
| // Swap in below function to track when nudges get restarted. |
| tutorial.restartNudges = () => { |
| restart = true; |
| }; |
| const waitForRestartNudges = async () => { |
| return new Promise(resolve => { |
| let intervalId; |
| const nudgesRestarted = () => { |
| return restart; |
| }; |
| if (nudgesRestarted()) { |
| resolve(); |
| } else { |
| intervalId = setInterval(() => { |
| if (nudgesRestarted()) { |
| clearInterval(intervalId); |
| resolve(); |
| } |
| }, 500); |
| } |
| }); |
| }; |
| restart = false; |
| CommandHandler.onCommand('nextObject'); |
| await waitForRestartNudges(); |
| // Show a lesson. |
| tutorial.curriculum = 'essential_keys'; |
| tutorial.showLesson_(0); |
| restart = false; |
| CommandHandler.onCommand('nextObject'); |
| await waitForRestartNudges(); |
| restart = false; |
| CommandHandler.onCommand('nextObject'); |
| await waitForRestartNudges(); |
| }); |
| }); |
| |
| // Tests that the tutorial closes and ChromeVox navigates to a resource link. |
| TEST_F('ChromeVoxTutorialTest', 'ResourcesTest', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(() => { |
| tutorial.curriculum = 'resources'; |
| tutorial.showLesson_(0); |
| }) |
| .expectSpeech('Learn More') |
| .call(doCmd('nextObject')) |
| .expectSpeech(/Congratulations/) |
| .call(doCmd('nextObject')) |
| .expectSpeech('ChromeVox Command Reference', 'Link') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech('support.google.com') |
| .replay(); |
| }); |
| }); |
| |
| // Tests that choosing a curriculum with only 1 lesson automatically opens the |
| // lesson. |
| TEST_F('ChromeVoxTutorialTest', 'OnlyLessonTest', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Quick orientation') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Essential keys') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Navigation') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Command references') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Sounds and settings') |
| .call(doCmd('nextObject')) |
| .expectSpeech('Resources') |
| .call(doCmd('forceClickOnCurrentItem')) |
| .expectSpeech('Learn More', 'Heading 1') |
| .expectSpeech( |
| ' Press Search + Right Arrow, or Search + Left Arrow to' + |
| ' navigate this lesson ') |
| // The 'All lessons' button should be hidden since this is the only |
| // lesson for the curriculum. |
| .call(doCmd('nextButton')) |
| .expectSpeech('Main menu') |
| .call(doCmd('nextButton')) |
| .expectSpeech('Exit tutorial') |
| .replay(); |
| }); |
| }); |
| |
| // Tests that interactive mode and UserActionMonitor are properly set when |
| // showing different screens in the tutorial. |
| TEST_F('ChromeVoxTutorialTest', 'StartStopInteractiveMode', function() { |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| let userActionMonitorCreatedCount = 0; |
| let userActionMonitorDestroyedCount = 0; |
| let isUserActionMonitorActive = false; |
| |
| // Swap in functions below so we can track the number of times |
| // UserActionMonitor is created and destroyed. |
| ChromeVoxState.instance.createUserActionMonitor = (actions, callback) => { |
| userActionMonitorCreatedCount += 1; |
| isUserActionMonitorActive = true; |
| }; |
| ChromeVoxState.instance.destroyUserActionMonitor = () => { |
| userActionMonitorDestroyedCount += 1; |
| isUserActionMonitorActive = false; |
| }; |
| |
| // A helper to make assertions on four variables of interest. |
| const makeAssertions = (expectedVars) => { |
| assertEquals(expectedVars.createdCount, userActionMonitorCreatedCount); |
| assertEquals( |
| expectedVars.destroyedCount, userActionMonitorDestroyedCount); |
| assertEquals(expectedVars.interactiveMode, tutorial.interactiveMode_); |
| // Note: Interactive mode and UserActionMonitor should always be in |
| // sync in the context of the tutorial. |
| assertEquals(expectedVars.interactiveMode, isUserActionMonitorActive); |
| }; |
| |
| makeAssertions( |
| {createdCount: 0, destroyedCount: 0, interactiveMode: false}); |
| // Show the first lesson of the quick orientation, which is interactive. |
| tutorial.curriculum = 'quick_orientation'; |
| tutorial.showLesson_(0); |
| makeAssertions({createdCount: 1, destroyedCount: 0, interactiveMode: true}); |
| |
| // Move to the next lesson in the quick orientation. This lesson is also |
| // interactive, so UserActionMonitor should be destroyed and re-created. |
| tutorial.showNextLesson(); |
| makeAssertions({createdCount: 2, destroyedCount: 1, interactiveMode: true}); |
| |
| // Leave the quick orientation by navigating to the lesson menu. This should |
| // stop interactive mode and destroy UserActionMonitor. |
| tutorial.showLessonMenu_(); |
| makeAssertions( |
| {createdCount: 2, destroyedCount: 2, interactiveMode: false}); |
| }); |
| }); |
| |
| // Tests that gestures can be used in the tutorial to navigate. |
| TEST_F('ChromeVoxTutorialTest', 'Gestures', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(doGesture(Gesture.SWIPE_RIGHT1)) |
| .expectSpeech('Quick orientation', 'Link') |
| .call(doGesture(Gesture.SWIPE_RIGHT1)) |
| .expectSpeech('Essential keys', 'Link') |
| .call(doGesture(Gesture.SWIPE_LEFT1)) |
| .expectSpeech('Quick orientation', 'Link') |
| .call(doGesture(Gesture.SWIPE_LEFT2)) |
| .expectSpeech('Some web content') |
| .replay(); |
| }); |
| }); |
| |
| // Tests that touch orientation loads properly. Tests string content, but does |
| // not test interactivity of lessons. |
| // TODO(crbug.com/1193799): fix ax node errors causing console spew and |
| // breaking tests |
| TEST_F('ChromeVoxTutorialTest', 'DISABLED_TouchOrientation', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| mockFeedback.expectSpeech('ChromeVox tutorial') |
| .call(() => { |
| tutorial.curriculum = 'touch_orientation'; |
| tutorial.medium = 'touch'; |
| tutorial.showLesson_(0); |
| this.assertActiveLessonIndex(0); |
| this.assertActiveScreen('lesson'); |
| }) |
| .expectSpeech('ChromeVox touch tutorial') |
| .expectSpeech(/Welcome to the ChromeVox tutorial/) |
| .call(doGesture(Gesture.CLICK)) |
| .expectSpeech('Activate an item') |
| .expectSpeech(/To continue, double-tap now/) |
| .call(doGesture(Gesture.CLICK)) |
| .expectSpeech('Move to the next or previous item') |
| .call(() => { |
| // Jump to the penultimate lesson. |
| tutorial.showLesson_(6); |
| }) |
| .expectSpeech('Move to the next or previous section') |
| .expectSpeech(/swipe from left to right with four fingers/) |
| .call(doGesture(Gesture.SWIPE_RIGHT4)) |
| .expectSpeech(/swiping with four fingers from right to left/) |
| .call(doGesture(Gesture.SWIPE_LEFT4)) |
| .expectSpeech('Touch tutorial complete') |
| .replay(); |
| }); |
| }); |
| |
| TEST_F('ChromeVoxTutorialTest', 'GeneralTouchNudges', function() { |
| const mockFeedback = this.createMockFeedback(); |
| this.runWithLoadedTree(this.simpleDoc, async function(root) { |
| await this.launchAndWaitForTutorial(); |
| const tutorial = this.getTutorial(); |
| const giveNudge = () => { |
| tutorial.giveNudge(); |
| }; |
| mockFeedback.expectSpeech('ChromeVox tutorial'); |
| mockFeedback.call(() => { |
| tutorial.medium = 'touch'; |
| tutorial.initializeNudges('general'); |
| }); |
| for (let i = 0; i < 3; ++i) { |
| mockFeedback.call(giveNudge).expectSpeech( |
| 'ChromeVox tutorial', 'Heading 1'); |
| } |
| mockFeedback.call(giveNudge) |
| .expectSpeech('Hint: Swipe left or right with one finger to navigate.') |
| .call(giveNudge) |
| .expectSpeech( |
| 'Hint: Double-tap with one finger to activate the current item.') |
| .call(giveNudge) |
| .expectSpeech( |
| 'Hint: Swipe from right to left with two fingers if you would ' + |
| 'like to exit this tutorial.') |
| .replay(); |
| }); |
| }); |