chromium/chrome/browser/ash/crostini/crostini_unsupported_action_notifier_unittest.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/crostini/crostini_unsupported_action_notifier.h"

#include <memory>
#include <string>
#include <tuple>

#include "ash/constants/ash_features.h"
#include "base/test/scoped_feature_list.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/tablet_state.h"

namespace crostini {

using ::testing::_;
using ::testing::Bool;
using ::testing::Combine;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::Truly;

class MockDelegate : public CrostiniUnsupportedActionNotifier::Delegate {
 public:
  MOCK_METHOD(bool, IsInTabletMode, (), (override));
  MOCK_METHOD(bool, IsFocusedWindowCrostini, (), (override));
  MOCK_METHOD(bool, IsVirtualKeyboardVisible, (), (override));
  MOCK_METHOD(void, ShowToast, (ash::ToastData toast_data), (override));
  MOCK_METHOD(base::TimeDelta, ToastTimeout, (), (override));
  MOCK_METHOD(void,
              AddFocusObserver,
              (aura::client::FocusChangeObserver * observer),
              (override));
  MOCK_METHOD(void,
              RemoveFocusObserver,
              (aura::client::FocusChangeObserver * observer),
              (override));
  MOCK_METHOD(void,
              AddDisplayObserver,
              (display::DisplayObserver * observer),
              (override));
  MOCK_METHOD(void,
              RemoveDisplayObserver,
              (display::DisplayObserver * observer),
              (override));
  MOCK_METHOD(void,
              AddKeyboardControllerObserver,
              (ash::KeyboardControllerObserver * observer),
              (override));
  MOCK_METHOD(void,
              RemoveKeyboardControllerObserver,
              (ash::KeyboardControllerObserver * observer),
              (override));
};

class CrostiniUnsupportedActionNotifierTest
    : public testing::TestWithParam<std::tuple<bool, bool, bool, bool>> {
 public:
  CrostiniUnsupportedActionNotifierTest()
      : notifier(std::make_unique<NiceMock<MockDelegate>>()) {}
  ~CrostiniUnsupportedActionNotifierTest() override = default;

  MockDelegate& get_delegate() {
    auto* ptr = notifier.get_delegate_for_testing();
    DCHECK(ptr);

    // Our delegate is always a mock delegate in these tests, but we don't have
    // RTTI so have to use a static cast.
    return static_cast<NiceMock<MockDelegate>&>(*ptr);
  }

  bool is_tablet_mode() const { return std::get<0>(GetParam()); }
  bool is_crostini_focused() const { return std::get<1>(GetParam()); }
  bool is_vk_visible() const { return std::get<2>(GetParam()); }

  static bool IsVKToast(const ash::ToastData& data) {
    return data.id.compare(0, 2, "VK") == 0;
  }

  void SetExpectations(bool show_tablet_toast, bool show_vk_toast) {
    // We show the same toast for tablet mode and manually triggered virtual
    // keyboard, so showing either counts as showing both.
    int num_tablet_toasts = (show_tablet_toast || show_vk_toast) ? 1 : 0;

    EXPECT_CALL(get_delegate(), IsInTabletMode)
        .WillRepeatedly(Return(is_tablet_mode()));
    EXPECT_CALL(get_delegate(), IsVirtualKeyboardVisible)
        .WillRepeatedly(Return(is_vk_visible()));
    EXPECT_CALL(get_delegate(), IsFocusedWindowCrostini)
        .WillRepeatedly(Return(is_crostini_focused()));

    EXPECT_CALL(get_delegate(), ShowToast(Truly(IsVKToast)))
        .Times(num_tablet_toasts);
  }

  base::test::ScopedFeatureList scoped_feature_list_;
  CrostiniUnsupportedActionNotifier notifier;
};

TEST_P(CrostiniUnsupportedActionNotifierTest,
       ToastShownOnceOnlyWhenEnteringTabletMode) {
  bool show_tablet_toast = is_tablet_mode() && is_crostini_focused();
  // Since tablet and vk toasts are the same we can trigger the toast
  // OnTabletModeStarted even if not in tablet mode.
  bool show_vk_toast = is_vk_visible() && is_crostini_focused();

  SetExpectations(show_tablet_toast, show_vk_toast);

  notifier.OnDisplayTabletStateChanged(display::TabletState::kInTabletMode);
  notifier.OnDisplayTabletStateChanged(display::TabletState::kInTabletMode);
}

TEST_P(CrostiniUnsupportedActionNotifierTest,
       ToastShownOnceOnlyWhenShowingVirtualKeyboard) {
  // Since tablet and vk toasts are the same we can trigger the toast
  // OnKeyboardVisibilityChanged even if the virtual keyboard isn't visible.
  bool show_tablet_toast = is_tablet_mode() && is_crostini_focused();
  bool show_vk_toast = is_vk_visible() && is_crostini_focused();

  SetExpectations(show_tablet_toast, show_vk_toast);

  notifier.OnKeyboardVisibilityChanged(true);
  notifier.OnKeyboardVisibilityChanged(false);
  notifier.OnKeyboardVisibilityChanged(true);
}

TEST_P(CrostiniUnsupportedActionNotifierTest,
       ToastsShownOnceOnlyWhenFocusingCrostiniApp) {
  bool show_tablet_toast = is_tablet_mode() && is_crostini_focused();
  bool show_vk_toast = is_vk_visible() && is_crostini_focused();

  SetExpectations(show_tablet_toast, show_vk_toast);

  notifier.OnWindowFocused({}, {});
  notifier.OnWindowFocused({}, {});
}

INSTANTIATE_TEST_SUITE_P(CrostiniUnsupportedActionNotifierTestCombination,
                         CrostiniUnsupportedActionNotifierTest,
                         Combine(Bool(), Bool(), Bool(), Bool()));

}  // namespace crostini