chromium/chrome/browser/resources/chromeos/accessibility/chromevox/panel/tutorial_test.js

// Copyright 2020 The Chromium Authors
// 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 {
  /** @override */
  async setUpDeferred() {
    await super.setUpDeferred();
    globalThis.Gesture = chrome.accessibilityPrivate.Gesture;
  }

  assertActiveLessonIndex(expectedIndex) {
    assertEquals(expectedIndex, this.getTutorial().activeLessonIndex);
  }

  assertActiveScreen(expectedScreen) {
    assertEquals(expectedScreen, this.getTutorial().activeScreen);
  }

  async launchAndWaitForTutorial() {
    new PanelCommand(PanelCommandType.TUTORIAL).send();
    await this.waitForTutorial();
  }

  /** 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.instance.tutorialReadyForTesting_) {
                    resolve();
                  } else {
                    panel.instance.tutorial_.addEventListener(
                        'readyfortesting', () => resolve());
                  }
                  observer.disconnect();
                }
              }
            }
          }
        };

        const observer = new MutationObserver(onMutation);
        observer.observe(
            doc.body /* target */, {childList: true} /* options */);
      }
    });
  }

  getTutorial() {
    return this.getPanel().instance.tutorial_;
  }

  disableRestartNudges() {
    this.getPanel().instance.tutorial_.restartNudges = null;
  }

  get simpleDoc() {
    return `
      <p>Some web content</p>
    `;
  }
};

// TODO(crbug.com/40941587): Flaky on ChromeOS.
AX_TEST_F('ChromeVoxTutorialTest', 'DISABLED_BasicTest', async function() {
  const mockFeedback = this.createMockFeedback();
  const root = await this.runWithLoadedTree(this.simpleDoc);
  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');
  await mockFeedback.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
AX_TEST_F('ChromeVoxTutorialTest', 'DISABLED_LessonSetTest', async function() {
  const mockFeedback = this.createMockFeedback();
  const root = await this.runWithLoadedTree(this.simpleDoc);
  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');
  await mockFeedback.replay();
});

// Tests that a static lesson does not show the 'Practice area' button.
// TODO(crbug.com/1193799): fix ax node errors causing console spew and
// breaking tests
AX_TEST_F(
    'ChromeVoxTutorialTest', 'DISABLED_NoPracticeAreaTest', async function() {
      const mockFeedback = this.createMockFeedback();
      const root = await this.runWithLoadedTree(this.simpleDoc);
      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');
      await mockFeedback.replay();
    });

// Tests that an interactive lesson shows the 'Practice area' button.
// TODO(crbug.com/1193799): fix ax node errors causing console spew and
// breaking tests
AX_TEST_F(
    'ChromeVoxTutorialTest', 'DISABLED_HasPracticeAreaTest', async function() {
      const mockFeedback = this.createMockFeedback();
      const root = await this.runWithLoadedTree(this.simpleDoc);
      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');
      await mockFeedback.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.
AX_TEST_F('ChromeVoxTutorialTest', 'GeneralNudgesTest', async function() {
  const mockFeedback = this.createMockFeedback();
  const root = await this.runWithLoadedTree(this.simpleDoc);
  await this.launchAndWaitForTutorial();
  this.disableRestartNudges();
  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.');
  await mockFeedback.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.
AX_TEST_F(
    'ChromeVoxTutorialTest', 'DISABLED_PracticeAreaNudgesTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      const root = await this.runWithLoadedTree(this.simpleDoc);
      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.');
      await mockFeedback.replay();
    });

// Tests that the tutorial closes when the 'Exit tutorial' button is clicked.
// TODO(crbug.com/1332510): Failing on ChromeOS.
AX_TEST_F('ChromeVoxTutorialTest', 'DISABLED_ExitButtonTest', async function() {
  const mockFeedback = this.createMockFeedback();
  const root = await this.runWithLoadedTree(this.simpleDoc);
  await this.launchAndWaitForTutorial();
  const tutorial = this.getTutorial();
  mockFeedback.expectSpeech('ChromeVox tutorial')
      .call(doCmd('previousButton'))
      .expectSpeech('Exit tutorial')
      .call(doCmd('forceClickOnCurrentItem'))
      .expectSpeech('Some web content');
  await mockFeedback.replay();
});

// Tests that the tutorial closes when Escape is pressed.
// TODO(crbug.com/1332510): Failing on ChromeOS.
AX_TEST_F('ChromeVoxTutorialTest', 'DISABLED_EscapeTest', async function() {
  const mockFeedback = this.createMockFeedback();
  const root = await this.runWithLoadedTree(this.simpleDoc);
  await this.launchAndWaitForTutorial();
  const tutorial = this.getTutorial();
  mockFeedback.expectSpeech('ChromeVox tutorial')
      .call(() => {
        // Press Escape.
        tutorial.onKeyDown({
          key: 'Escape',
          preventDefault: () => {},
          stopPropagation: () => {},
        });
      })
      .expectSpeech('Some web content');
  await mockFeedback.replay();
});

// Tests that the main menu button navigates the user to the main menu screen.
// TODO(crbug.com/1193799): fix ax node errors causing console spew and
// breaking tests
AX_TEST_F('ChromeVoxTutorialTest', 'DISABLED_MainMenuButton', async function() {
  const mockFeedback = this.createMockFeedback();
  const root = await this.runWithLoadedTree(this.simpleDoc);
  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'));
  await mockFeedback.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
AX_TEST_F(
    'ChromeVoxTutorialTest', 'DISABLED_AllLessonsButton', async function() {
      const mockFeedback = this.createMockFeedback();
      const root = await this.runWithLoadedTree(this.simpleDoc);
      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'));
      await mockFeedback.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
AX_TEST_F(
    'ChromeVoxTutorialTest', 'DISABLED_NextPreviousButtons', async function() {
      const mockFeedback = this.createMockFeedback();
      const root = await this.runWithLoadedTree(this.simpleDoc);
      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));
      await mockFeedback.replay();
    });

// Tests that the title of an interactive lesson is read when shown.
AX_TEST_F('ChromeVoxTutorialTest', 'AutoReadTitle', async function() {
  const mockFeedback = this.createMockFeedback();
  const root = await this.runWithLoadedTree(this.simpleDoc);
  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(() => {
        tutorial.showFirstLesson_();
      })
      .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.');
  await mockFeedback.replay();
});

// Tests that we read a hint for navigating a lesson when it is shown.
// TODO(crbug.com/1193799): fix ax node errors causing console spew and
// breaking tests
AX_TEST_F('ChromeVoxTutorialTest', 'DISABLED_LessonHint', async function() {
  const mockFeedback = this.createMockFeedback();
  const root = await this.runWithLoadedTree(this.simpleDoc);
  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');
  await mockFeedback.replay();
});

// Tests for correct speech and earcons on the earcons lesson.
AX_TEST_F('ChromeVoxTutorialTest', 'EarconLesson', async function() {
  const mockFeedback = this.createMockFeedback();
  const root = await this.runWithLoadedTree(this.simpleDoc);
  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', EarconId.ALERT_MODAL);
  nextObjectAndExpectSpeechAndEarcon(
      'A non modal alert', EarconId.ALERT_NONMODAL);
  // TODO(anastasi): Identify why the button is not present in the tutorial.
  // nextObjectAndExpectSpeechAndEarcon('A button', EarconId.BUTTON);
  await 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
AX_TEST_F(
    'ChromeVoxTutorialTest', 'DISABLED_QuickOrientationLessonTest',
    async function() {
      const mockFeedback = this.createMockFeedback();
      const root = await this.runWithLoadedTree(this.simpleDoc);
      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
      // ForcedActionPath intercepts key sequences before they are routed to
      // CommandHandler.
      const getRangeStartNode = () => ChromeVoxRange.current.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);
          });
      await mockFeedback.replay();
    });

// Tests that tutorial nudges are restarted whenever the current range changes.
AX_TEST_F('ChromeVoxTutorialTest', 'RestartNudges', async function() {
  const root = await this.runWithLoadedTree(this.simpleDoc);
  await this.launchAndWaitForTutorial();
  const tutorial = this.getTutorial();
  // Swap in below function to track when nudges get restarted.
  const reset = () => new Promise(resolve => tutorial.restartNudges = resolve);

  let nudgesHaveRestarted = reset();
  CommandHandlerInterface.instance.onCommand('nextObject');
  await nudgesHaveRestarted;

  // Show a lesson.
  tutorial.curriculum = 'essential_keys';
  tutorial.showLesson_(0);
  nudgesHaveRestarted = reset();
  CommandHandlerInterface.instance.onCommand('nextObject');
  await nudgesHaveRestarted;

  nudgesHaveRestarted = reset();
  CommandHandlerInterface.instance.onCommand('nextObject');
  await nudgesHaveRestarted;
});

// Tests that the tutorial closes and ChromeVox navigates to a resource link.
//
// Flaky. See crbug.com/336702956.
AX_TEST_F('ChromeVoxTutorialTest', 'DISABLED_ResourcesTest', async function() {
  const mockFeedback = this.createMockFeedback();
  const root = await this.runWithLoadedTree(this.simpleDoc);
  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'))
      // Expect the support page to be pulled up; it is read differently
      // depending on if this browsertest's browser has network access.
      .expectSpeech(/(support.google.com)|(Chromebook Help)/);
  await mockFeedback.replay();
});

// Tests that choosing a curriculum with only 1 lesson automatically opens the
// lesson.
AX_TEST_F('ChromeVoxTutorialTest', 'OnlyLessonTest', async function() {
  const mockFeedback = this.createMockFeedback();
  const root = await this.runWithLoadedTree(this.simpleDoc);
  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');
  await mockFeedback.replay();
});

// Tests that interactive mode and ForcedActionPath are properly set when
// showing different screens in the tutorial.
AX_TEST_F(
    'ChromeVoxTutorialTest', 'StartStopInteractiveMode', async function() {
      const root = await this.runWithLoadedTree(this.simpleDoc);
      await this.launchAndWaitForTutorial();
      const tutorial = this.getTutorial();
      let userActionMonitorCreatedCount = 0;
      let userActionMonitorDestroyedCount = 0;
      let isForcedActionPathActive = false;
      // Expose the correct BackgroundBridge so we can override the functions
      this.getPanel().exportBackgroundBridgeForTesting();
      // Swap in functions below so we can track the number of times
      // ForcedActionPath is created and destroyed.
      this.getPanelWindow().BackgroundBridge.ForcedActionPath.listenFor =
          () => {
            userActionMonitorCreatedCount += 1;
            isForcedActionPathActive = true;
          };
      this.getPanelWindow().BackgroundBridge.ForcedActionPath.stopListening =
          () => {
            userActionMonitorDestroyedCount += 1;
            isForcedActionPathActive = 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 ForcedActionPath should always be in
        // sync in the context of the tutorial.
        assertEquals(expectedVars.interactiveMode, isForcedActionPathActive);
      };

      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 ForcedActionPath 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 ForcedActionPath.
      tutorial.showLessonMenu_();
      makeAssertions(
          {createdCount: 2, destroyedCount: 2, interactiveMode: false});
    });

// Tests that gestures can be used in the tutorial to navigate.
// TODO(crbug.com/1332510): Failing on ChromeOS.
AX_TEST_F('ChromeVoxTutorialTest', 'DISABLED_Gestures', async function() {
  const mockFeedback = this.createMockFeedback();
  const root = await this.runWithLoadedTree(this.simpleDoc);
  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');
  await mockFeedback.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
AX_TEST_F(
    'ChromeVoxTutorialTest', 'DISABLED_TouchOrientation', async function() {
      const mockFeedback = this.createMockFeedback();
      const root = await this.runWithLoadedTree(this.simpleDoc);
      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');
      await mockFeedback.replay();
    });

AX_TEST_F('ChromeVoxTutorialTest', 'GeneralTouchNudges', async function() {
  const mockFeedback = this.createMockFeedback();
  const root = await this.runWithLoadedTree(this.simpleDoc);
  await this.launchAndWaitForTutorial();
  this.disableRestartNudges();
  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.');
  await mockFeedback.replay();
});