// 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.

#import "ios/chrome/browser/infobars/model/infobar_badge_tab_helper.h"

#import "ios/chrome/browser/badges/ui_bundled/badge_item.h"
#import "ios/chrome/browser/infobars/model/infobar_badge_tab_helper.h"
#import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
#import "ios/chrome/browser/infobars/model/test/fake_infobar_ios.h"
#import "ios/chrome/browser/infobars/ui_bundled/test_infobar_badge_tab_helper_delegate.h"
#import "ios/web/public/test/fakes/fake_navigation_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "testing/platform_test.h"

namespace {
// The InfobarTypes to use for the test.
const InfobarType kInfobarTypeWithBadge = InfobarType::kInfobarTypePasswordSave;
const InfobarType kInfobarTypeNoBadge = InfobarType::kInfobarTypeConfirm;
}  // namespace

// Test fixture for testing InfobarBadgeTabHelper.
class InfobarBadgeTabHelperTest : public PlatformTest {
 protected:
  InfobarBadgeTabHelperTest()
      : delegate_([[TestInfobarTabHelperDelegate alloc] init]) {
    // Setup navigation manager. Needed for InfobarManager.
    web_state_.SetNavigationManager(
        std::make_unique<web::FakeNavigationManager>());

    // Create the InfobarManager for web_state_.
    InfoBarManagerImpl::CreateForWebState(&web_state_);

    // Create the InfobarBadgeTabHelper for web_state_ and set its delegate.
    InfobarBadgeTabHelper::CreateForWebState(&web_state_);
    delegate_.badgeTabHelper = tab_helper();
    tab_helper()->SetDelegate(delegate_);
  }

  // Adds a FakeInfobarIOS with specified badge support to the WebState's
  // InfoBarManagerImpl.  Set replace_existing to true, if a matching infobar
  // (same message_text) should be replaced with new one instead of being
  // ignored. Returns the added infobar.
  FakeInfobarIOS* AddInfobar(bool has_badge, bool replace_existing = false) {
    std::unique_ptr<FakeInfobarIOS> added_infobar =
        std::make_unique<FakeInfobarIOS>(has_badge ? kInfobarTypeWithBadge
                                                   : kInfobarTypeNoBadge);
    FakeInfobarIOS* infobar = added_infobar.get();
    InfoBarManagerImpl::FromWebState(&web_state_)
        ->AddInfoBar(std::move(added_infobar), replace_existing);
    return infobar;
  }

  // Returns InfobarBadgeTabHelper attached to web_state_.
  InfobarBadgeTabHelper* tab_helper() {
    return InfobarBadgeTabHelper::FromWebState(&web_state_);
  }

  web::FakeWebState web_state_;
  TestInfobarTabHelperDelegate* delegate_ = nil;
};

// Test the badge state after changes to the state of an Infobar. Infobar badges
// should always be tappable.
TEST_F(InfobarBadgeTabHelperTest, TestInfobarBadgeState) {
  // Add a badge-supporting infobar to the InfobarManager to create the badge
  // item.
  FakeInfobarIOS* infobar = AddInfobar(/*has_badge=*/true);
  InfobarType added_type = infobar->infobar_type();
  // Simulate presenting the banner UI and verify that the badge state is sent
  // to the delegate.
  tab_helper()->UpdateBadgeForInfobarBannerPresented(added_type);
  EXPECT_TRUE(tab_helper()->GetInfobarBadgeStates()[added_type] &
              BadgeStatePresented);
  id<BadgeItem> item = [delegate_ itemForInfobarType:added_type];
  EXPECT_TRUE(item);
  EXPECT_TRUE(item.tappable);
  EXPECT_TRUE(item.badgeState & BadgeStatePresented);
  // Simulate accepting the infobar and verify that the badge state is udpated.
  infobar->set_accepted(true);
  EXPECT_TRUE(tab_helper()->GetInfobarBadgeStates()[added_type] &
              BadgeStateAccepted);
  EXPECT_TRUE(tab_helper()->GetInfobarBadgeStates()[added_type] &
              BadgeStateRead);
  item = [delegate_ itemForInfobarType:added_type];
  EXPECT_TRUE(item.tappable);
  EXPECT_TRUE(item.badgeState & BadgeStateAccepted);
  EXPECT_TRUE(item.badgeState & BadgeStateRead);
  // Simulate reverting the infobar and verify that the badge state is udpated.
  infobar->set_accepted(false);
  EXPECT_FALSE(tab_helper()->GetInfobarBadgeStates()[added_type] &
               BadgeStateAccepted);
  item = [delegate_ itemForInfobarType:added_type];
  EXPECT_FALSE(item.badgeState & BadgeStateAccepted);
}

// Test the badge state after changes to the state of an Infobar. Infobar badges
// should always be tappable.  Uses deprecated API.
TEST_F(InfobarBadgeTabHelperTest, TestInfobarBadgeStateDeprecated) {
  // Add a badge-supporting infobar to the InfobarManager to create the badge
  // item.
  InfobarType added_type = AddInfobar(/*has_badge=*/true)->infobar_type();
  // Simulate presenting the banner UI and verify that the badge state is sent
  // to the delegate.
  tab_helper()->UpdateBadgeForInfobarBannerPresented(added_type);
  EXPECT_TRUE(tab_helper()->GetInfobarBadgeStates()[added_type] &
              BadgeStatePresented);
  id<BadgeItem> item = [delegate_ itemForInfobarType:added_type];
  EXPECT_TRUE(item);
  EXPECT_TRUE(item.tappable);
  EXPECT_TRUE(item.badgeState & BadgeStatePresented);
  // Simulate accepting the infobar and verify that the badge state is udpated.
  tab_helper()->UpdateBadgeForInfobarAccepted(added_type);
  EXPECT_TRUE(tab_helper()->GetInfobarBadgeStates()[added_type] &
              BadgeStateAccepted);
  EXPECT_TRUE(tab_helper()->GetInfobarBadgeStates()[added_type] &
              BadgeStateRead);
  item = [delegate_ itemForInfobarType:added_type];
  EXPECT_TRUE(item.tappable);
  EXPECT_TRUE(item.badgeState & BadgeStateAccepted);
  EXPECT_TRUE(item.badgeState & BadgeStateRead);
  // Simulate reverting the infobar and verify that the badge state is udpated.
  tab_helper()->UpdateBadgeForInfobarReverted(added_type);
  EXPECT_FALSE(tab_helper()->GetInfobarBadgeStates()[added_type] &
               BadgeStateAccepted);
  item = [delegate_ itemForInfobarType:added_type];
  EXPECT_FALSE(item.badgeState & BadgeStateAccepted);
}

// Tests that adding an infobar that doesn't support badges does not notify the
// delegate of BadgeItem creation.
TEST_F(InfobarBadgeTabHelperTest, TestInfobarBadgeStateNoBadge) {
  InfobarType added_type = AddInfobar(/*has_badge=*/false)->infobar_type();
  std::map<InfobarType, BadgeState> badge_states =
      tab_helper()->GetInfobarBadgeStates();
  EXPECT_FALSE(badge_states.contains(added_type));
  EXPECT_FALSE([delegate_ itemForInfobarType:added_type]);
}

// Tests that the InfobarBadge has not been removed after dismissing the
// InfobarBanner.
TEST_F(InfobarBadgeTabHelperTest, TestInfobarBadgeOnBannerDismissal) {
  // Add a badge-supporting infobar to the InfobarManager to create the badge
  // item, then simulate presentation and dismissal.
  InfobarType added_type = AddInfobar(/*has_badge=*/true)->infobar_type();
  tab_helper()->UpdateBadgeForInfobarBannerPresented(added_type);
  tab_helper()->UpdateBadgeForInfobarBannerDismissed(added_type);
  // Verify that the BadgeItem was not removed and that its state is dismissed.
  EXPECT_FALSE(tab_helper()->GetInfobarBadgeStates()[added_type] &
               BadgeStatePresented);
  id<BadgeItem> item = [delegate_ itemForInfobarType:added_type];
  EXPECT_TRUE(item);
  EXPECT_FALSE(item.badgeState & BadgeStatePresented);
}

// Test that the Accepted badge state remains after dismissing the
// InfobarBanner.
TEST_F(InfobarBadgeTabHelperTest, TestInfobarBadgeOnBannerAccepted) {
  // Add a badge-supporting infobar to the InfobarManager to create the badge
  // item, then simulate presentation, acceptance, and dismissal.
  FakeInfobarIOS* infobar = AddInfobar(/*has_badge=*/true);
  InfobarType added_type = infobar->infobar_type();
  tab_helper()->UpdateBadgeForInfobarBannerPresented(added_type);
  infobar->set_accepted(true);
  tab_helper()->UpdateBadgeForInfobarBannerDismissed(added_type);
  // Verify that the BadgeItem was not removed and that its state is dismissed.
  EXPECT_TRUE(tab_helper()->GetInfobarBadgeStates()[added_type] &
              BadgeStateAccepted);
  id<BadgeItem> item = [delegate_ itemForInfobarType:added_type];
  EXPECT_TRUE(item);
  EXPECT_TRUE(item.badgeState & BadgeStateAccepted);
}

// Test that the Accepted badge state remains after dismissing the
// InfobarBanner.
TEST_F(InfobarBadgeTabHelperTest, TestInfobarBadgeOnBannerAcceptedDeprecated) {
  // Add a badge-supporting infobar to the InfobarManager to create the badge
  // item, then simulate presentation, acceptance, and dismissal.
  InfobarType added_type = AddInfobar(/*has_badge=*/true)->infobar_type();
  tab_helper()->UpdateBadgeForInfobarBannerPresented(added_type);
  tab_helper()->UpdateBadgeForInfobarAccepted(added_type);
  tab_helper()->UpdateBadgeForInfobarBannerDismissed(added_type);
  // Verify that the BadgeItem was not removed and that its state is dismissed.
  EXPECT_TRUE(tab_helper()->GetInfobarBadgeStates()[added_type] &
              BadgeStateAccepted);
  id<BadgeItem> item = [delegate_ itemForInfobarType:added_type];
  EXPECT_TRUE(item);
  EXPECT_TRUE(item.badgeState & BadgeStateAccepted);
}

// Test that destroying the InfobarView stops displaying the badge.
TEST_F(InfobarBadgeTabHelperTest, TestInfobarBadgeOnInfobarDestruction) {
  // Add a badge-supporting infobar to the InfobarManager to create the badge
  // item, then simulate presentation and dismissal.
  FakeInfobarIOS* added_infobar = AddInfobar(/*has_badge=*/true);
  InfobarType added_type = added_infobar->infobar_type();
  tab_helper()->UpdateBadgeForInfobarBannerPresented(added_type);
  tab_helper()->UpdateBadgeForInfobarBannerDismissed(added_type);
  ASSERT_TRUE([delegate_ itemForInfobarType:added_type]);
  // Remove the infobar from the manager and verify that the BadgeItem is also
  // removed.
  InfoBarManagerImpl::FromWebState(&web_state_)->RemoveInfoBar(added_infobar);
  std::map<InfobarType, BadgeState> badge_states =
      tab_helper()->GetInfobarBadgeStates();
  EXPECT_FALSE(badge_states.contains(added_type));
  EXPECT_FALSE([delegate_ itemForInfobarType:added_type]);
}

// Test that replacing infobar, doesn't crash.
TEST_F(InfobarBadgeTabHelperTest, TestInfobarReplacing) {
  // Test tab helper by driving it through InfoBarManager.
  AddInfobar(/*has_badge=*/true);
  // Check first one added correctly.
  EXPECT_EQ(InfoBarManagerImpl::FromWebState(&web_state_)->infobars().size(),
            1u);
  // Replace with second one.
  FakeInfobarIOS* infobar2 =
      AddInfobar(/*has_badge=*/true, /*replace_existing=*/true);
  // Should be only one.
  EXPECT_EQ(InfoBarManagerImpl::FromWebState(&web_state_)->infobars().size(),
            1u);
  // If first one wasn't replaced this will fail.
  InfoBarManagerImpl::FromWebState(&web_state_)->RemoveInfoBar(infobar2);
  // Left with none.
  EXPECT_EQ(InfoBarManagerImpl::FromWebState(&web_state_)->infobars().size(),
            0u);
  // No crash.
}
