chromium/components/webapps/browser/android/installable/installable_ambient_badge_message_controller_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 "components/webapps/browser/android/installable/installable_ambient_badge_message_controller.h"

#include "base/android/jni_android.h"
#include "base/memory/raw_ptr.h"
#include "components/messages/android/mock_message_dispatcher_bridge.h"
#include "components/webapps/browser/android/installable/installable_ambient_badge_client.h"
#include "components/webapps/browser/android/webapps_icon_utils.h"
#include "content/public/test/test_renderer_host.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "url/gurl.h"

namespace webapps {

namespace {
constexpr char16_t kAppName[] = u"App name";
}  // namespace

class MockInstallableAmbientBadgeClient : public InstallableAmbientBadgeClient {
 public:
  MockInstallableAmbientBadgeClient() = default;
  ~MockInstallableAmbientBadgeClient() override = default;

  MOCK_METHOD(void, AddToHomescreenFromBadge, (), (override));
  MOCK_METHOD(void, BadgeDismissed, (), (override));
  MOCK_METHOD(void, BadgeIgnored, (), (override));
};

class InstallableAmbientBadgeMessageControllerTest
    : public content::RenderViewHostTestHarness {
 public:
  InstallableAmbientBadgeMessageControllerTest();

  void SetUp() override;
  void TearDown() override;

  void EnqueueMessage();
  void EnqueueMessage(bool maskable);
  void EnqueueMessageWithExpectNotCalled();
  void DismissMessage(bool expected);

  void TriggerActionClick();
  void TriggerMessageDismissedWithGesture();
  void TriggerMessageDismissedWithTimer();

  InstallableAmbientBadgeMessageController* message_controller() {
    return &message_controller_;
  }

  void ExpectedIconChanged() {
    SkBitmap bitmap = message_wrapper_->GetIconBitmap();
    EXPECT_NE(bitmap.bounds(), test_icon.bounds());
  }

  void ExpectedIconUnchanged() {
    SkBitmap bitmap = message_wrapper_->GetIconBitmap();
    EXPECT_EQ(bitmap.bounds(), test_icon.bounds());
  }

  messages::MessageWrapper* message_wrapper() { return message_wrapper_; }
  MockInstallableAmbientBadgeClient& client_mock() { return client_mock_; }

 private:
  messages::MockMessageDispatcherBridge message_dispatcher_bridge_;
  MockInstallableAmbientBadgeClient client_mock_;
  InstallableAmbientBadgeMessageController message_controller_;
  raw_ptr<messages::MessageWrapper> message_wrapper_ = nullptr;
  SkBitmap test_icon;
};

InstallableAmbientBadgeMessageControllerTest::
    InstallableAmbientBadgeMessageControllerTest()
    : message_controller_(&client_mock_) {}

void InstallableAmbientBadgeMessageControllerTest::SetUp() {
  content::RenderViewHostTestHarness::SetUp();
  messages::MessageDispatcherBridge::SetInstanceForTesting(
      &message_dispatcher_bridge_);
}

void InstallableAmbientBadgeMessageControllerTest::TearDown() {
  messages::MessageDispatcherBridge::SetInstanceForTesting(nullptr);
  content::RenderViewHostTestHarness::TearDown();
}

void InstallableAmbientBadgeMessageControllerTest::EnqueueMessage() {
  EnqueueMessage(false);
}

void InstallableAmbientBadgeMessageControllerTest::EnqueueMessage(
    bool maskable) {
  EXPECT_CALL(message_dispatcher_bridge_, EnqueueMessage)
      .WillOnce(testing::DoAll(testing::SaveArg<0>(&message_wrapper_),
                               testing::Return(true)));
  test_icon.allocPixels(SkImageInfo::Make(100, 100, kRGBA_8888_SkColorType,
                                          kUnpremul_SkAlphaType));
  message_controller_.EnqueueMessage(web_contents(), kAppName, test_icon,
                                     maskable, GURL("https://example.com/"));
}

void InstallableAmbientBadgeMessageControllerTest::
    EnqueueMessageWithExpectNotCalled() {
  EXPECT_CALL(message_dispatcher_bridge_, EnqueueMessage).Times(0);
  test_icon.allocPixels(SkImageInfo::Make(100, 100, kRGBA_8888_SkColorType,
                                          kUnpremul_SkAlphaType));
  message_controller_.EnqueueMessage(web_contents(), kAppName, test_icon, false,
                                     GURL("https://example.com/"));
}

void InstallableAmbientBadgeMessageControllerTest::DismissMessage(
    bool bridge_dismiss_call_expected) {
  if (bridge_dismiss_call_expected) {
    EXPECT_CALL(message_dispatcher_bridge_, DismissMessage)
        .WillOnce([](messages::MessageWrapper* message,
                     messages::DismissReason dismiss_reason) {
          message->HandleDismissCallback(base::android::AttachCurrentThread(),
                                         static_cast<int>(dismiss_reason));
        });
  }
  message_controller_.DismissMessage();
}

void InstallableAmbientBadgeMessageControllerTest::TriggerActionClick() {
  message_wrapper()->HandleActionClick(base::android::AttachCurrentThread());
  // Simulate call from Java to dismiss message on primary button click.
  message_wrapper()->HandleDismissCallback(
      base::android::AttachCurrentThread(),
      static_cast<int>(messages::DismissReason::PRIMARY_ACTION));
}

void InstallableAmbientBadgeMessageControllerTest::
    TriggerMessageDismissedWithGesture() {
  message_wrapper()->HandleDismissCallback(
      base::android::AttachCurrentThread(),
      static_cast<int>(messages::DismissReason::GESTURE));
}

void InstallableAmbientBadgeMessageControllerTest::
    TriggerMessageDismissedWithTimer() {
  message_wrapper()->HandleDismissCallback(
      base::android::AttachCurrentThread(),
      static_cast<int>(messages::DismissReason::TIMER));
}

// Tests InstallableAmbientBadgeMessageController API: EnqueueMessage,
// IsMessageEnqueued, DismissMessage.
TEST_F(InstallableAmbientBadgeMessageControllerTest, APITest) {
  ASSERT_FALSE(message_controller()->IsMessageEnqueued());
  EnqueueMessage();
  ASSERT_TRUE(message_controller()->IsMessageEnqueued());
  DismissMessage(true);
  ASSERT_FALSE(message_controller()->IsMessageEnqueued());
  // Calling DismissMessage when there is no message enqueued should not fail or
  // result in a call to MessageDispatcherBridge.
  DismissMessage(false);
}

// Tests that message properties are set correctly.
TEST_F(InstallableAmbientBadgeMessageControllerTest, MessagePropertyValues) {
  EnqueueMessage();

  EXPECT_NE(std::u16string::npos, message_wrapper()->GetTitle().find(kAppName));
  EXPECT_FALSE(message_wrapper()->GetDescription().empty());
  EXPECT_FALSE(message_wrapper()->GetPrimaryButtonText().empty());
  EXPECT_TRUE(message_wrapper()->IsValidIcon());

  DismissMessage(true);
}

// Tests that when the user taps on Install, client's AddToHomescreenFromBadge
// method is called.
TEST_F(InstallableAmbientBadgeMessageControllerTest, AddToHomeSceen) {
  EnqueueMessage();
  EXPECT_CALL(client_mock(), AddToHomescreenFromBadge);
  EXPECT_CALL(client_mock(), BadgeDismissed).Times(0);
  EXPECT_CALL(client_mock(), BadgeIgnored).Times(0);
  ExpectedIconUnchanged();
  TriggerActionClick();
}

// Tests that message is using an adaptive icon when icon is maskable.
TEST_F(InstallableAmbientBadgeMessageControllerTest, MaskableIcon) {
  EnqueueMessage(true);
  EXPECT_CALL(client_mock(), AddToHomescreenFromBadge);
  EXPECT_CALL(client_mock(), BadgeDismissed).Times(0);
  EXPECT_CALL(client_mock(), BadgeIgnored).Times(0);
  if (WebappsIconUtils::DoesAndroidSupportMaskableIcons()) {
    ExpectedIconChanged();
  } else {
    ExpectedIconUnchanged();
  }
  TriggerActionClick();
}

// Tests that when the user dismisses the message with a gesture, client's
// BadgeDismissed method is called.
TEST_F(InstallableAmbientBadgeMessageControllerTest, Dismiss) {
  EnqueueMessage();
  EXPECT_CALL(client_mock(), AddToHomescreenFromBadge).Times(0);
  EXPECT_CALL(client_mock(), BadgeDismissed);
  TriggerMessageDismissedWithGesture();
}

// Tests that when the user dismisses the message with a gesture, client's
// BadgeDismissed method is called and the message is not enqueued
// because of throttling.
TEST_F(InstallableAmbientBadgeMessageControllerTest, Throttle) {
  EnqueueMessage();
  EXPECT_CALL(client_mock(), AddToHomescreenFromBadge).Times(0);
  DismissMessage(true);
  EnqueueMessageWithExpectNotCalled();
  ASSERT_FALSE(message_controller()->IsMessageEnqueued());
}

// Tests that when the message is dismissed with the timer, client's
// BadgeIgnored method is called.
TEST_F(InstallableAmbientBadgeMessageControllerTest, TimerDismissed) {
  EnqueueMessage();
  EXPECT_CALL(client_mock(), AddToHomescreenFromBadge).Times(0);
  EXPECT_CALL(client_mock(), BadgeDismissed);
  TriggerMessageDismissedWithGesture();
}

}  // namespace webapps