chromium/chrome/browser/ui/web_applications/sub_apps_install_dialog_controller_unittest.cc

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

#include "chrome/browser/ui/web_applications/sub_apps_install_dialog_controller.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_future.h"
#include "base/types/cxx23_to_underlying.h"
#include "chrome/browser/ui/web_applications/web_app_dialogs.h"
#include "chrome/browser/web_applications/test/web_app_icon_test_utils.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_icon_generator.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "components/webapps/common/web_app_id.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/window/dialog_delegate.h"

namespace web_app {

using DialogViewIDForTesting =
    SubAppsInstallDialogController::SubAppsInstallDialogViewID;

constexpr const char kParentAppName[] = "Parent App";
constexpr const char kParentAppScope[] = "https://www.parent-app.com/";

constexpr const char kSubAppStartURL[] = "https://www.parent-app.com/sub-app";
constexpr const char kSubAppIconURL[] =
    "https://www.parent-app.com/sub-app/icon";

const std::u16string kSubAppName1 = u"Sub App 1";
const std::u16string kSubAppName2 = u"Sub App 2";
const std::u16string kSubAppName3 = u"Sub App 3";

class SubAppsInstallDialogControllerTest : public BrowserWithTestWindowTest {
 protected:
  std::unique_ptr<SubAppsInstallDialogController> CreateDefaultController(
      base::OnceCallback<void(bool)> callback) {
    const webapps::AppId parent_app_id =
        web_app::GenerateAppIdFromManifestId(GURL(kParentAppScope));

    auto controller = std::make_unique<SubAppsInstallDialogController>();
    controller->Init(std::move(callback), /*sub_apps=*/{}, kParentAppName,
                     parent_app_id, GetProfile(), GetContext());
    return controller;
  }
};

std::unique_ptr<WebAppInstallInfo> CreateInstallInfoWithIconForSubApp(
    const std::u16string& name) {
  const GeneratedIconsInfo icon_info(
      IconPurpose::ANY, {web_app::icon_size::k32}, {SK_ColorBLACK});
  std::unique_ptr<WebAppInstallInfo> sub_app_install_info =
      WebAppInstallInfo::CreateWithStartUrlForTesting(GURL(kSubAppStartURL));
  sub_app_install_info->title = name;
  web_app::AddIconsToWebAppInstallInfo(sub_app_install_info.get(),
                                       GURL(kSubAppIconURL), {icon_info});
  return sub_app_install_info;
}

TEST_F(SubAppsInstallDialogControllerTest, DialogViewSetUpCorrectly) {
  std::vector<std::unique_ptr<WebAppInstallInfo>> sub_apps;
  sub_apps.emplace_back(CreateInstallInfoWithIconForSubApp(kSubAppName1));
  sub_apps.emplace_back(CreateInstallInfoWithIconForSubApp(kSubAppName2));
  sub_apps.emplace_back(CreateInstallInfoWithIconForSubApp(kSubAppName3));

  views::Widget* widget = CreateSubAppsInstallDialogWidget(
      base::ASCIIToUTF16(std::string(kParentAppName)), sub_apps,
      base::DoNothing(), GetContext());
  views::DialogDelegate* dialog = widget->widget_delegate()->AsDialogDelegate();

  EXPECT_FALSE(dialog->ShouldShowCloseButton());
  EXPECT_TRUE(dialog->owned_by_widget());

  // Confirm that all sub app names and icons were added to the view.
  std::vector<raw_ptr<views::View, VectorExperimental>> sub_app_labels;
  widget->GetContentsView()->GetViewsInGroup(
      base::to_underlying(DialogViewIDForTesting::SUB_APP_LABEL),
      &sub_app_labels);
  EXPECT_EQ(sub_app_labels.size(), 3u);

  std::vector<raw_ptr<views::View, VectorExperimental>> sub_app_icons;
  widget->GetContentsView()->GetViewsInGroup(
      base::to_underlying(DialogViewIDForTesting::SUB_APP_ICON),
      &sub_app_icons);
  EXPECT_EQ(sub_app_icons.size(), 3u);

  widget->CloseNow();
}

TEST_F(SubAppsInstallDialogControllerTest, SubAppConvertedCorrectly) {
  std::unique_ptr<WebAppInstallInfo> sub_app_install_info =
      WebAppInstallInfo::CreateWithStartUrlForTesting(GURL(kSubAppStartURL));
  sub_app_install_info->title = kSubAppName1;
  const GeneratedIconsInfo icon_info(
      IconPurpose::ANY, {web_app::icon_size::k32}, {SK_ColorBLACK});
  web_app::AddIconsToWebAppInstallInfo(sub_app_install_info.get(),
                                       GURL(kSubAppIconURL), {icon_info});

  std::vector<std::unique_ptr<WebAppInstallInfo>> sub_apps;
  sub_apps.emplace_back(std::move(sub_app_install_info));

  webapps::AppId parent_app_id =
      web_app::GenerateAppIdFromManifestId(GURL(kParentAppScope));
  auto controller = std::make_unique<SubAppsInstallDialogController>();
  controller->Init(base::DoNothing(), sub_apps, kParentAppName, parent_app_id,
                   GetProfile(), GetContext());
  views::Widget* widget = controller->GetWidgetForTesting();

  std::vector<raw_ptr<views::View, VectorExperimental>> sub_app_labels;
  widget->GetContentsView()->GetViewsInGroup(
      base::to_underlying(DialogViewIDForTesting::SUB_APP_LABEL),
      &sub_app_labels);
  EXPECT_EQ(sub_app_labels.size(), 1u);
  EXPECT_EQ(static_cast<views::Label*>(sub_app_labels[0])->GetText(),
            kSubAppName1);

  std::vector<raw_ptr<views::View, VectorExperimental>> sub_app_icons;
  widget->GetContentsView()->GetViewsInGroup(
      base::to_underlying(DialogViewIDForTesting::SUB_APP_ICON),
      &sub_app_icons);
  EXPECT_EQ(sub_app_icons.size(), 1u);
  views::ImageView* icon_view =
      static_cast<views::ImageView*>(sub_app_icons[0]);
  EXPECT_FALSE(icon_view->GetImageModel().IsEmpty());
  EXPECT_EQ(icon_view->GetVisibleBounds().size(), gfx::Size(32, 32));
}

TEST_F(SubAppsInstallDialogControllerTest, DialogAccepted) {
  base::test::TestFuture<bool> future;
  auto controller = CreateDefaultController(future.GetCallback());
  auto* dialog =
      controller->GetWidgetForTesting()->widget_delegate()->AsDialogDelegate();

  dialog->AcceptDialog();

  EXPECT_TRUE(future.Get());
}

TEST_F(SubAppsInstallDialogControllerTest, DialogCancelled) {
  base::test::TestFuture<bool> future;
  auto controller = CreateDefaultController(future.GetCallback());
  auto* dialog =
      controller->GetWidgetForTesting()->widget_delegate()->AsDialogDelegate();

  dialog->CancelDialog();

  EXPECT_FALSE(future.Get());
}

TEST_F(SubAppsInstallDialogControllerTest, EscPressed) {
  base::test::TestFuture<bool> future;
  auto controller = CreateDefaultController(future.GetCallback());
  views::Widget* widget = controller->GetWidgetForTesting();

  // Simulate esc key press.
  ui::KeyEvent event(ui::EventType::kKeyPressed, ui::VKEY_ESCAPE, ui::EF_NONE);
  widget->OnKeyEvent(&event);

  EXPECT_FALSE(future.Get());
}

TEST_F(SubAppsInstallDialogControllerTest,
       WidgetLifetimeIsTiedToControllerLifetime) {
  views::Widget* widget;

  {
    auto controller = CreateDefaultController(base::DoNothing());
    widget = controller->GetWidgetForTesting();
    EXPECT_TRUE(widget->IsVisible());
  }

  views::test::WidgetDestroyedWaiter widget_observer(widget);
  widget_observer.Wait();
}

}  // namespace web_app