chromium/ash/shelf/assistant_overlay_unittest.cc

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

#include "ash/shelf/assistant_overlay.h"

#include <memory>
#include <string>
#include <vector>

#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/assistant/assistant_state.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/home_button.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shelf/shelf_view_test_api.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chromeos/ash/services/assistant/public/cpp/assistant_enums.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest-param-test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/layer.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/gesture_event_details.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/view.h"
#include "ui/wm/core/window_util.h"

namespace ash {
namespace {

constexpr base::TimeDelta kAssistantAnimationDelay = base::Milliseconds(200);
constexpr char kAssistantOverlayClassName[] = "AssistantOverlay";

enum TestVariant { kClamshell, kTablet, kTabletWithBackButton };

class AssistantOverlayTest : public AshTestBase,
                             public testing::WithParamInterface<TestVariant> {
 public:
  AssistantOverlayTest()
      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

  void SetUp() override {
    // In Tablet mode, home button is shown if a board has kAshEnableTabletMode
    // and kAccessibilityTabletModeShelfNavigationButtonsEnabled is true.
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        switches::kAshEnableTabletMode);

    AshTestBase::SetUp();

    // Enable Assistant
    Shell::Get()->session_controller()->GetPrimaryUserPrefService()->SetBoolean(
        assistant::prefs::kAssistantEnabled, true);
    AssistantState* assistant_state = AssistantState::Get();
    assistant_state->NotifyFeatureAllowed(
        assistant::AssistantAllowedState::ALLOWED);
    assistant_state->NotifyStatusChanged(assistant::AssistantStatus::READY);

    const TestVariant test_variant = GetParam();
    switch (test_variant) {
      case kClamshell:
        ASSERT_FALSE(Shell::Get()->IsInTabletMode());
        break;
      case kTablet:
        Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean(
            prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled, true);
        Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
        ASSERT_FALSE(ShelfConfig::Get()->is_in_app());
        break;
      case kTabletWithBackButton:
        Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean(
            prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled, true);
        Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
        // A back button is shown if there is an active window.
        CreateTestWindow();
        ActivateTestWindow();
        ASSERT_TRUE(ShelfConfig::Get()->is_in_app());
        break;
    }
  }

  void TearDown() override {
    window_.reset();

    AshTestBase::TearDown();
  }

 protected:
  const views::View* GetAssistantOverlay() {
    for (const views::View* child : home_button()->children()) {
      if (std::string(child->GetClassName()) == kAssistantOverlayClassName) {
        return child;
      }
    }

    return nullptr;
  }

  HomeButton* home_button() {
    return GetPrimaryShelf()
        ->shelf_widget()
        ->navigation_widget()
        ->GetHomeButton();
  }

  std::vector<gfx::Rect> TakeClipRectSnapshot(const ui::Layer* layer) {
    std::vector<gfx::Rect> clip_rects;
    while (layer) {
      clip_rects.push_back(layer->clip_rect());
      layer = layer->parent();
    }
    return clip_rects;
  }

  void SendGestureEventToHomeButton(ui::EventType type) {
    ui::GestureEvent event(0, 0, ui::EF_NONE, base::TimeTicks(),
                           ui::GestureEventDetails(type));
    home_button()->OnGestureEvent(&event);
  }

  void ActivateTestWindow() { wm::ActivateWindow(window_.get()); }

 private:
  void CreateTestWindow() {
    window_ = AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 400, 400));
  }

  std::unique_ptr<aura::Window> window_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         AssistantOverlayTest,
                         testing::Values(TestVariant::kClamshell,
                                         TestVariant::kTablet,
                                         TestVariant::kTabletWithBackButton));

// AssistantOverlay renders burst animation which goes outside of its view size.
// Make sure that it's not clipped by an ancestor layer.
//
// For long press, events and expectations are follows:
// - EventType::kGestureTapDown: Ripple animation starts
// - EventType::kGestureLongPress: Burst animation starts (clip rect will be
// restored
//   after the animation)
// - EventType::kGestureTapCancel: Nothing should happen
// - EventType::kGestureLongTap: Nothing should happen
TEST_P(AssistantOverlayTest, BurstAnimationWithLongPress) {
  const views::View* assistant_overlay = GetAssistantOverlay();
  ASSERT_THAT(assistant_overlay, testing::NotNull());

  const ui::Layer* assistant_overlay_layer = assistant_overlay->layer();

  std::vector<gfx::Rect> clip_rect_snapshot_before =
      TakeClipRectSnapshot(assistant_overlay_layer);

  SendGestureEventToHomeButton(ui::EventType::kGestureTapDown);

  // HomeButtonController delays assistant animation for
  // kAssistantAnimationDelay.
  task_environment()->FastForwardBy(kAssistantAnimationDelay);

  // Confirm that no clip rect is set in ancestor layers.
  std::vector<gfx::Rect> clip_rects_during_animation =
      TakeClipRectSnapshot(assistant_overlay_layer);
  for (const gfx::Rect& clip_rect : clip_rects_during_animation) {
    EXPECT_TRUE(clip_rect.IsEmpty());
  }

  // AssistantOverlay starts burst animation with EventType::kGestureLongPress.
  SendGestureEventToHomeButton(ui::EventType::kGestureLongPress);

  // Burst animation ends immediately in this test case. Confirm that clip rect
  // is restored now.
  EXPECT_EQ(TakeClipRectSnapshot(assistant_overlay_layer),
            clip_rect_snapshot_before);

  SendGestureEventToHomeButton(ui::EventType::kGestureTapCancel);
  SendGestureEventToHomeButton(ui::EventType::kGestureLongTap);
  // Confirm that nothing should happen with the following TAP_CANCEL and
  // LONG_TAP events.
  EXPECT_EQ(TakeClipRectSnapshot(assistant_overlay_layer),
            clip_rect_snapshot_before);
}

// AssistantOverlay renders a ripple animation with a tap, which goes beyond the
// size of home button.
TEST_P(AssistantOverlayTest, RippleAnimationWithTap) {
  const views::View* assistant_overlay = GetAssistantOverlay();
  ASSERT_THAT(assistant_overlay, testing::NotNull());

  const ui::Layer* assistant_overlay_layer = assistant_overlay->layer();

  std::vector<gfx::Rect> clip_rect_snapshot_before =
      TakeClipRectSnapshot(assistant_overlay_layer);

  SendGestureEventToHomeButton(ui::EventType::kGestureTapDown);

  // HomeButtonController delays assistant animation for
  // kAssistantAnimationDelay.
  task_environment()->FastForwardBy(kAssistantAnimationDelay);

  // Confirm that no clip rect is set in ancestor layers.
  std::vector<gfx::Rect> clip_rects_during_animation =
      TakeClipRectSnapshot(assistant_overlay_layer);
  for (const gfx::Rect& clip_rect : clip_rects_during_animation) {
    EXPECT_TRUE(clip_rect.IsEmpty());
  }

  SendGestureEventToHomeButton(ui::EventType::kGestureTap);

  // The above tap will de-activate test window and hides back button.
  // Re-activate the window to show a back button. Clip rect can be different if
  // no back button is shown.
  if (GetParam() == kTabletWithBackButton)
    ActivateTestWindow();

  EXPECT_EQ(TakeClipRectSnapshot(assistant_overlay_layer),
            clip_rect_snapshot_before);
}

}  // namespace
}  // namespace ash