chromium/chromecast/graphics/gestures/multiple_tap_detector_test.cc

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

#include "chromecast/graphics/gestures/multiple_tap_detector.h"

#include "base/run_loop.h"
#include "base/test/simple_test_tick_clock.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/aura/test/aura_test_base.h"
#include "ui/aura/window.h"
#include "ui/events/event_utils.h"
#include "ui/events/gesture_detection/gesture_configuration.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/test/events_test_utils.h"

using testing::_;
using testing::Eq;

namespace chromecast {

namespace {
constexpr gfx::Point kTestTapLocation(50, 50);
constexpr int kTapLengthMs = 50;
}  // namespace

class MockMultipleTapDetectorDelegate : public MultipleTapDetectorDelegate {
 public:
  ~MockMultipleTapDetectorDelegate() override = default;
  MOCK_METHOD1(OnTripleTap, void(const gfx::Point&));
  MOCK_METHOD1(OnDoubleTap, void(const gfx::Point&));
};

class MultipleTapDetectorTest : public aura::test::AuraTestBase {
 public:
  ~MultipleTapDetectorTest() override = default;

  void SetUp() override {
    aura::test::AuraTestBase::SetUp();

    triple_tap_delegate_ = std::make_unique<MockMultipleTapDetectorDelegate>();
    triple_tap_detector_ = std::make_unique<MultipleTapDetector>(
        root_window(), triple_tap_delegate_.get());

    generator_.reset(new ui::test::EventGenerator(root_window()));

    // Tests fail if time is ever 0.
    simulated_clock_.Advance(base::Milliseconds(10));
    // ui takes ownership of the tick clock.
    ui::SetEventTickClockForTesting(&simulated_clock_);
  }

  void TearDown() override {
    ui::SetEventTickClockForTesting(nullptr);
    triple_tap_detector_.reset();
    aura::test::AuraTestBase::TearDown();
  }

  // Pause the minimum amount of tap to trigger a double tap.
  void DoubleTapPause() {
    simulated_clock_.Advance(gesture_detector_config_.double_tap_min_time);
  }

  // Pause just past the maximum amount of time to trigger a double tap.
  void TooLongPause() {
    simulated_clock_.Advance(gesture_detector_config_.double_tap_timeout);
    simulated_clock_.Advance(base::Milliseconds(1));
    triple_tap_detector_->triple_tap_timer_.Stop();
    triple_tap_detector_->OnTapIntervalTimerFired();
  }

  // Simulate a tap event.
  void Tap(const gfx::Point& tap_point) {
    ui::TouchEvent press(ui::EventType::kTouchPressed, tap_point,
                         simulated_clock_.NowTicks(),
                         ui::PointerDetails(ui::EventPointerType::kTouch,
                                            ui::kPointerIdUnknown));
    generator_->Dispatch(&press);
    simulated_clock_.Advance(base::Milliseconds(kTapLengthMs));
    ui::TouchEvent release(ui::EventType::kTouchReleased, tap_point,
                           simulated_clock_.NowTicks(),
                           ui::PointerDetails(ui::EventPointerType::kTouch,
                                              ui::kPointerIdUnknown));
    generator_->Dispatch(&release);
  }

  void Tap() { Tap(kTestTapLocation); }

  MultipleTapDetector& detector() { return *triple_tap_detector_; }
  MockMultipleTapDetectorDelegate& delegate() { return *triple_tap_delegate_; }

 private:
  ui::GestureDetector::Config gesture_detector_config_;

  std::unique_ptr<MultipleTapDetector> triple_tap_detector_;
  std::unique_ptr<MockMultipleTapDetectorDelegate> triple_tap_delegate_;
  base::SimpleTestTickClock simulated_clock_;
  std::unique_ptr<ui::test::EventGenerator> generator_;
};

// Verify that a simple correct triple tap triggers the delegate.
TEST_F(MultipleTapDetectorTest, TripleTap) {
  EXPECT_CALL(delegate(), OnTripleTap(Eq(kTestTapLocation))).Times(1);
  EXPECT_CALL(delegate(), OnDoubleTap(Eq(kTestTapLocation))).Times(0);

  detector().set_enabled(true);

  Tap();
  DoubleTapPause();
  Tap();
  DoubleTapPause();
  Tap();

  base::RunLoop run_loop;
  run_loop.RunUntilIdle();
}

// Verify double tap when there's two taps then a pause past our double tap
// interval.
TEST_F(MultipleTapDetectorTest, DoubleTap) {
  EXPECT_CALL(delegate(), OnTripleTap(Eq(kTestTapLocation))).Times(0);
  EXPECT_CALL(delegate(), OnDoubleTap(Eq(kTestTapLocation))).Times(1);

  detector().set_enabled(true);

  Tap();
  DoubleTapPause();
  Tap();
  TooLongPause();

  base::RunLoop run_loop;
  run_loop.RunUntilIdle();
}

// Verify that no multi taps are detected when the detector is not enabled.
TEST_F(MultipleTapDetectorTest, Inactive) {
  EXPECT_CALL(delegate(), OnTripleTap(Eq(kTestTapLocation))).Times(0);
  EXPECT_CALL(delegate(), OnDoubleTap(Eq(kTestTapLocation))).Times(0);
  Tap();
  DoubleTapPause();
  Tap();
  DoubleTapPause();
  Tap();

  base::RunLoop run_loop;
  run_loop.RunUntilIdle();
}

// Verify it's not a multi tap of any kind if the pause from the first tap to
// the second tap is too long.
TEST_F(MultipleTapDetectorTest, FirstTapTooLong) {
  EXPECT_CALL(delegate(), OnTripleTap(Eq(kTestTapLocation))).Times(0);
  EXPECT_CALL(delegate(), OnDoubleTap(Eq(kTestTapLocation))).Times(0);

  detector().set_enabled(true);

  Tap();
  TooLongPause();
  Tap();
  DoubleTapPause();
  Tap();

  base::RunLoop run_loop;
  run_loop.RunUntilIdle();
}

// Verify it's not a triple tap but instead a doubletap if the pause from the
// second tap to the last tap is too long.
TEST_F(MultipleTapDetectorTest, LastTapTooLongIsDoubleTap) {
  EXPECT_CALL(delegate(), OnTripleTap(Eq(kTestTapLocation))).Times(0);
  EXPECT_CALL(delegate(), OnDoubleTap(Eq(kTestTapLocation))).Times(1);

  detector().set_enabled(true);

  Tap();
  DoubleTapPause();
  Tap();
  TooLongPause();
  Tap();

  base::RunLoop run_loop;
  run_loop.RunUntilIdle();
}

}  // namespace chromecast