chromium/ash/system/network/network_info_bubble_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/system/network/network_info_bubble.h"

#include <string>
#include <utility>

#include "ash/strings/grit/ash_strings.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chromeos/ash/services/network_config/public/cpp/cros_network_config_test_helper.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/views/controls/label.h"
#include "ui/views/view.h"

namespace ash {

namespace {

const char kIPv4ConfigPath[] = "/ipconfig/stub_ipv4_config";
const char kIPv6ConfigPath[] = "/ipconfig/stub_ipv6_config";

const char kWiFiServiceGuid[] = "wifi_guid";
const char kWiFiServiceName[] = "stub_wifi_service";
const char kWiFiServicePath[] = "/service/stub_wifi_service";

const char kStubEthernetDeviceName[] = "stub_ethernet_device";
const char kStubEthernetDevicePath[] = "/device/stub_ethernet_device";
const char kStubWiFiDeviceName[] = "stub_wifi_device";
const char kStubWiFiDevicePath[] = "/device/stub_wifi_device";
const char kStubCellularDeviceName[] = "stub_cellular_device";
const char kStubCellularDevicePath[] = "/device/stub_cellular_device";

const char* kEthernetMacAddress = "33:33:33:33:33:33";
const char* kWiFiMacAddress = "44:44:44:44:44:44";
const char* kCellularMacAddress = "55:55:55:55:55:55";
const char* kIpv4Address = "1.1.1.1";
const char* kIpv6Address = "2222:2222:2222:2222:2222:2222:2222:2222";

class FakeNetworkInfoBubbleDelegate : public NetworkInfoBubble::Delegate {
 public:
  FakeNetworkInfoBubbleDelegate() = default;
  ~FakeNetworkInfoBubbleDelegate() override = default;

  void SetShouldIncludeDeviceAddresses(bool should_include_device_addresses) {
    should_include_device_addresses_ = should_include_device_addresses;
  }

  bool should_include_device_addresses() const {
    return should_include_device_addresses_;
  }

  size_t on_info_bubble_destroyed_call_count() const {
    return on_info_bubble_destroyed_call_count_;
  }

 private:
  // NetworkInfoBubble::Delegate:
  bool ShouldIncludeDeviceAddresses() override {
    return should_include_device_addresses_;
  }

  void OnInfoBubbleDestroyed() override {
    ++on_info_bubble_destroyed_call_count_;
  }

  bool should_include_device_addresses_ = true;
  size_t on_info_bubble_destroyed_call_count_ = 0;
};

}  // namespace

class NetworkInfoBubbleTest : public AshTestBase {
 public:
  void SetUp() override {
    AshTestBase::SetUp();

    network_state_helper()->ClearDevices();

    network_state_helper()->manager_test()->AddTechnology(shill::kTypeCellular,
                                                          /*enabled=*/true);
    network_state_helper()->AddDevice(
        kStubCellularDevicePath, shill::kTypeCellular, kStubCellularDeviceName);
    network_state_helper()->AddDevice(
        kStubEthernetDevicePath, shill::kTypeEthernet, kStubEthernetDeviceName);
    network_state_helper()->AddDevice(kStubWiFiDevicePath, shill::kTypeWifi,
                                      kStubWiFiDeviceName);

    widget_ = CreateFramelessTestWidget();

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

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

    AshTestBase::TearDown();
  }

  void OpenBubble() {
    ASSERT_FALSE(network_info_bubble_);

    std::unique_ptr<NetworkInfoBubble> network_info_bubble =
        std::make_unique<NetworkInfoBubble>(weak_factory_.GetWeakPtr(),
                                            widget_->GetRootView());
    network_info_bubble_ = network_info_bubble.get();

    views::BubbleDialogDelegateView::CreateBubble(network_info_bubble.release())
        ->Show();
  }

  void CloseBubble() {
    ASSERT_TRUE(network_info_bubble_);
    network_info_bubble_->GetWidget()->Close();
    network_info_bubble_ = nullptr;
    base::RunLoop().RunUntilIdle();
  }

  void AddDefaultNetworkWithIPAddresses() {
    base::Value::Dict ipv4;
    ipv4.Set(shill::kAddressProperty, kIpv4Address);
    ipv4.Set(shill::kMethodProperty, shill::kTypeIPv4);

    base::Value::Dict ipv6;
    ipv6.Set(shill::kAddressProperty, kIpv6Address);
    ipv6.Set(shill::kMethodProperty, shill::kTypeIPv6);

    network_config_helper_.network_state_helper().ip_config_test()->AddIPConfig(
        kIPv4ConfigPath, std::move(ipv4));
    network_config_helper_.network_state_helper().ip_config_test()->AddIPConfig(
        kIPv6ConfigPath, std::move(ipv6));

    base::Value::List ip_configs;
    ip_configs.Append(kIPv4ConfigPath);
    ip_configs.Append(kIPv6ConfigPath);

    network_config_helper_.network_state_helper()
        .device_test()
        ->SetDeviceProperty(kStubWiFiDevicePath, shill::kIPConfigsProperty,
                            base::Value(std::move(ip_configs)),
                            /*notify_changed=*/true);

    network_config_helper_.network_state_helper().service_test()->AddService(
        kWiFiServicePath, kWiFiServiceGuid, kWiFiServiceName, shill::kTypeWifi,
        shill::kStateOnline, true);

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

  void SetDeviceMacAddress(const std::string& device_path,
                           const std::string& mac_address) {
    network_config_helper_.network_state_helper()
        .device_test()
        ->SetDeviceProperty(device_path, shill::kAddressProperty,
                            base::Value(mac_address), true);
    base::RunLoop().RunUntilIdle();
  }

  void SimulateMouseExit() {
    ASSERT_TRUE(network_info_bubble_);
    static_cast<views::View*>(network_info_bubble_)
        ->OnMouseExited(ui::MouseEvent(ui::EventType::kMouseExited,
                                       gfx::PointF(), gfx::PointF(),
                                       base::TimeTicks(), 0, 0));
    network_info_bubble_ = nullptr;
    base::RunLoop().RunUntilIdle();
  }

  void InvalidateDelegate() { weak_factory_.InvalidateWeakPtrs(); }

  views::Label* FindLabelView() {
    return static_cast<views::Label*>(network_info_bubble_->GetViewByID(
        NetworkInfoBubble::kNetworkInfoBubbleLabelViewId));
  }

  NetworkStateTestHelper* network_state_helper() {
    return &network_config_helper_.network_state_helper();
  }

  FakeNetworkInfoBubbleDelegate* fake_delegate() { return &fake_delegate_; }

 private:
  network_config::CrosNetworkConfigTestHelper network_config_helper_;
  FakeNetworkInfoBubbleDelegate fake_delegate_;
  raw_ptr<NetworkInfoBubble> network_info_bubble_ = nullptr;
  std::string wifi_path_;
  std::unique_ptr<views::Widget> widget_;

  base::WeakPtrFactory<FakeNetworkInfoBubbleDelegate> weak_factory_{
      &fake_delegate_};
};

TEST_F(NetworkInfoBubbleTest, NotifiesDelegateOnDestruction) {
  OpenBubble();
  EXPECT_EQ(0u, fake_delegate()->on_info_bubble_destroyed_call_count());
  CloseBubble();
  EXPECT_EQ(1u, fake_delegate()->on_info_bubble_destroyed_call_count());
}

TEST_F(NetworkInfoBubbleTest, BubbleClosesOnMouseExit) {
  OpenBubble();
  EXPECT_EQ(0u, fake_delegate()->on_info_bubble_destroyed_call_count());
  SimulateMouseExit();
  EXPECT_EQ(1u, fake_delegate()->on_info_bubble_destroyed_call_count());
}

TEST_F(NetworkInfoBubbleTest, DoesNotNotifyDelegateIfDelegateInvalid) {
  OpenBubble();
  EXPECT_EQ(0u, fake_delegate()->on_info_bubble_destroyed_call_count());
  InvalidateDelegate();
  CloseBubble();
  EXPECT_EQ(0u, fake_delegate()->on_info_bubble_destroyed_call_count());
}

TEST_F(NetworkInfoBubbleTest, HasCorrectText) {
  OpenBubble();
  EXPECT_EQ(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_NO_NETWORKS),
            FindLabelView()->GetText());
  CloseBubble();

  std::u16string expected_title =
      l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_ETHERNET_ADDRESS, u"");
  std::u16string expected_address = base::UTF8ToUTF16(kEthernetMacAddress);
  SetDeviceMacAddress(kStubEthernetDevicePath, kEthernetMacAddress);
  OpenBubble();
  // TODO(b/307363886): Use the view ids to tget the title and address.
  EXPECT_EQ(expected_title, static_cast<views::Label*>(
                                FindLabelView()->children()[0]->children()[0])
                                ->GetText());
  EXPECT_EQ(expected_address, static_cast<views::Label*>(
                                  FindLabelView()->children()[0]->children()[1])
                                  ->GetText());
  CloseBubble();

  expected_title =
      l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_WIFI_ADDRESS, u"");
  expected_address = base::UTF8ToUTF16(kWiFiMacAddress);
  SetDeviceMacAddress(kStubWiFiDevicePath, kWiFiMacAddress);
  OpenBubble();
  EXPECT_EQ(expected_title, static_cast<views::Label*>(
                                FindLabelView()->children()[1]->children()[0])
                                ->GetText());
  EXPECT_EQ(expected_address, static_cast<views::Label*>(
                                  FindLabelView()->children()[1]->children()[1])
                                  ->GetText());
  CloseBubble();

  expected_title =
      l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_CELLULAR_ADDRESS, u"");
  expected_address = base::UTF8ToUTF16(kCellularMacAddress);
  SetDeviceMacAddress(kStubCellularDevicePath, kCellularMacAddress);
  OpenBubble();
  EXPECT_EQ(expected_title, static_cast<views::Label*>(
                                FindLabelView()->children()[2]->children()[0])
                                ->GetText());
  EXPECT_EQ(expected_address, static_cast<views::Label*>(
                                  FindLabelView()->children()[2]->children()[1])
                                  ->GetText());
  CloseBubble();

  AddDefaultNetworkWithIPAddresses();
  expected_title =
      l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_IP_ADDRESS, u"");
  expected_address = base::UTF8ToUTF16(kIpv4Address);
  OpenBubble();
  EXPECT_EQ(expected_title, static_cast<views::Label*>(
                                FindLabelView()->children()[0]->children()[0])
                                ->GetText());
  EXPECT_EQ(expected_address, static_cast<views::Label*>(
                                  FindLabelView()->children()[0]->children()[1])
                                  ->GetText());
  CloseBubble();
  expected_title =
      l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_IPV6_ADDRESS, u"");
  expected_address = base::UTF8ToUTF16(kIpv6Address);
  OpenBubble();
  EXPECT_EQ(expected_title, static_cast<views::Label*>(
                                FindLabelView()->children()[1]->children()[0])
                                ->GetText());
  EXPECT_EQ(expected_address, static_cast<views::Label*>(
                                  FindLabelView()->children()[1]->children()[1])
                                  ->GetText());
  CloseBubble();

  fake_delegate()->SetShouldIncludeDeviceAddresses(true);
  OpenBubble();
  expected_title =
      l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_IPV6_ADDRESS, u"");
  expected_address = base::UTF8ToUTF16(kIpv6Address);
  EXPECT_EQ(expected_title, static_cast<views::Label*>(
                                FindLabelView()->children()[1]->children()[0])
                                ->GetText());
  EXPECT_EQ(expected_address, static_cast<views::Label*>(
                                  FindLabelView()->children()[1]->children()[1])
                                  ->GetText());
  expected_title =
      l10n_util::GetStringFUTF16(IDS_ASH_STATUS_TRAY_WIFI_ADDRESS, u"");
  expected_address = base::UTF8ToUTF16(kWiFiMacAddress);
  EXPECT_EQ(expected_title, static_cast<views::Label*>(
                                FindLabelView()->children()[3]->children()[0])
                                ->GetText());
  EXPECT_EQ(expected_address, static_cast<views::Label*>(
                                  FindLabelView()->children()[3]->children()[1])
                                  ->GetText());
  CloseBubble();
}

}  // namespace ash