910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/android/modal_dialog_wrapper.h"

#include "base/android/jni_android.h"
#include "base/android/scoped_java_ref.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/android/fake_modal_dialog_manager_bridge.h"
#include "ui/android/window_android.h"
#include "ui/base/interaction/element_identifier.h"
#include "ui/base/models/dialog_model.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_key.h"
#include "ui/color/color_provider_manager.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/gfx/vector_icon_types.h"
#include "ui/strings/grit/ui_strings.h"

namespace ui {

namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kCheckboxId);
// Icons used in tests below.
constexpr int kIconDim = 24;
const gfx::PathElement kTestIconPath[] = {
    // A 16x16 canvas.
    gfx::CANVAS_DIMENSIONS,
    16,
    // A square path.
    gfx::MOVE_TO,
    0,
    0,
    gfx::LINE_TO,
    16,
    0,
    gfx::LINE_TO,
    16,
    16,
    gfx::LINE_TO,
    0,
    16,
    gfx::CLOSE,
};
const gfx::VectorIconRep kTestIconReps[] = {
    {.path = kTestIconPath},
};
const gfx::VectorIcon kTestIcon(kTestIconReps,
                                std::size(kTestIconReps),
                                "test_icon");

ui::ImageModel CreateBitmapImage(SkColor color) {
  SkBitmap bitmap;
  bitmap.allocN32Pixels(kIconDim, kIconDim);
  bitmap.eraseColor(color);
  return ui::ImageModel::FromImage(gfx::Image::CreateFrom1xBitmap(bitmap));
}

}  // namespace

class ModalDialogWrapperTest : public testing::Test {
 protected:
  // Test helper class to build a DialogModel with a fluent interface.
  class DialogModelBuilder {
   public:
    explicit DialogModelBuilder(bool* dialog_destroyed_flag)
        : dialog_destroyed_flag_(dialog_destroyed_flag) {}

    DialogModelBuilder& WithOkButton(
        base::OnceClosure callback,
        ui::ButtonStyle style = ui::ButtonStyle::kDefault) {
      ok_callback_ = std::move(callback);
      ok_button_style_ = style;
      return *this;
    }

    DialogModelBuilder& WithCancelButton(
        base::OnceClosure callback,
        ui::ButtonStyle style = ui::ButtonStyle::kDefault) {
      has_cancel_button_ = true;
      cancel_callback_ = std::move(callback);
      cancel_button_style_ = style;
      return *this;
    }

    DialogModelBuilder& WithCloseAction(base::OnceClosure callback) {
      close_callback_ = std::move(callback);
      return *this;
    }

    DialogModelBuilder& OverrideDefaultButton(
        mojom::DialogButton override_button) {
      override_button_ = override_button;
      return *this;
    }

    DialogModelBuilder& WithParagraphs(
        const std::vector<std::u16string>& paragraphs) {
      paragraphs_ = paragraphs;
      return *this;
    }

    DialogModelBuilder& WithCheckbox(bool is_checked) {
      has_checkbox_ = true;
      checkbox_is_checked_ = is_checked;
      return *this;
    }

    DialogModelBuilder& WithIcon(ui::ImageModel icon) {
      icon_ = std::move(icon);
      return *this;
    }

    DialogModelBuilder& AddMenuItem(ui::ImageModel icon,
                                    const std::u16string& label) {
      menu_items_.emplace_back(std::move(icon), label, base::DoNothing());
      return *this;
    }

    DialogModelBuilder& AddMenuItemWithCallback(
        ui::ImageModel icon,
        const std::u16string& label,
        base::RepeatingClosure callback) {
      menu_items_.emplace_back(std::move(icon), label, std::move(callback));
      return *this;
    }

    std::unique_ptr<ui::DialogModel> Build() {
      ui::DialogModel::Builder dialog_builder;
      dialog_builder.SetTitle(u"title");

      if (icon_) {
        dialog_builder.SetIcon(*icon_);
      }

      for (const auto& paragraph_text : paragraphs_) {
        dialog_builder.AddParagraph(ui::DialogModelLabel(paragraph_text));
      }

      for (auto& item : menu_items_) {
        dialog_builder.AddMenuItem(
            std::move(std::get<0>(item)), std::get<1>(item),
            base::BindRepeating([](base::RepeatingClosure callback,
                                   int event_flags) { callback.Run(); },
                                std::move(std::get<2>(item))));
      }

      if (has_checkbox_) {
        dialog_builder.AddCheckbox(
            kCheckboxId, ui::DialogModelLabel(u"checkbox label"),
            ui::DialogModelCheckbox::Params().SetIsChecked(
                checkbox_is_checked_));
      }

      dialog_builder.AddOkButton(
          std::move(ok_callback_),
          ui::DialogModel::Button::Params().SetLabel(u"ok").SetStyle(
              ok_button_style_));

      if (has_cancel_button_) {
        dialog_builder.AddCancelButton(
            std::move(cancel_callback_),
            ui::DialogModel::Button::Params().SetLabel(u"cancel").SetStyle(
                cancel_button_style_));
      }

      // Capture the pointer to the destruction flag by value. This prevents
      // the lambda from holding a dangling reference to the temporary builder
      // instance.
      bool* flag_ptr = dialog_destroyed_flag_;
      dialog_builder.SetCloseActionCallback(std::move(close_callback_))
          .SetDialogDestroyingCallback(
              base::BindLambdaForTesting([flag_ptr]() { *flag_ptr = true; }));

      if (override_button_) {
        dialog_builder.OverrideDefaultButton(*override_button_);
      }
      return dialog_builder.Build();
    }

   private:
    raw_ptr<bool> dialog_destroyed_flag_;

    // Default values match the original CreateDialogModel function.
    base::OnceClosure ok_callback_ = base::DoNothing();
    ui::ButtonStyle ok_button_style_ = ui::ButtonStyle::kDefault;

    bool has_cancel_button_ = false;
    base::OnceClosure cancel_callback_ = base::DoNothing();
    ui::ButtonStyle cancel_button_style_ = ui::ButtonStyle::kDefault;

    base::OnceClosure close_callback_ = base::DoNothing();
    std::optional<mojom::DialogButton> override_button_;
    std::vector<std::u16string> paragraphs_ = {u"paragraph"};
    std::vector<
        std::tuple<ui::ImageModel, std::u16string, base::RepeatingClosure>>
        menu_items_;

    bool has_checkbox_ = false;
    bool checkbox_is_checked_ = false;
    std::optional<ui::ImageModel> icon_;
  };

  void SetUp() override {
    window_ = ui::WindowAndroid::CreateForTesting();
    fake_dialog_manager_ = FakeModalDialogManagerBridge::CreateForTab(
        window_->get(), /*use_empty_java_presenter=*/false);
  }

  bool dialog_destroyed_ = false;
  std::unique_ptr<ui::WindowAndroid::ScopedWindowAndroidForTesting> window_;
  std::unique_ptr<FakeModalDialogManagerBridge> fake_dialog_manager_;
};

TEST_F(ModalDialogWrapperTest, CallOkButton) {
  bool ok_called = false;

  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_)
          .WithOkButton(base::BindLambdaForTesting([&]() { ok_called = true; }))
          .Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());
  fake_dialog_manager_->ClickPositiveButton();

  EXPECT_TRUE(ok_called);
  EXPECT_TRUE(dialog_destroyed_);
}

TEST_F(ModalDialogWrapperTest, CallCancelButton) {
  bool ok_called = false, cancel_called = false;

  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_)
          .WithOkButton(base::BindLambdaForTesting([&]() { ok_called = true; }))
          .WithCancelButton(
              base::BindLambdaForTesting([&]() { cancel_called = true; }))
          .Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());
  fake_dialog_manager_->ClickNegativeButton();

  EXPECT_FALSE(ok_called);
  EXPECT_TRUE(cancel_called);
  EXPECT_TRUE(dialog_destroyed_);
}

TEST_F(ModalDialogWrapperTest, CloseDialogFromNative) {
  bool ok_called = false, cancel_called = false, closed = false;

  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_)
          .WithOkButton(base::BindLambdaForTesting([&]() { ok_called = true; }))
          .WithCancelButton(
              base::BindLambdaForTesting([&]() { cancel_called = true; }))
          .WithCloseAction(base::BindLambdaForTesting([&]() { closed = true; }))
          .Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());
  ModalDialogWrapper::GetDialogForTesting()->Close();

  EXPECT_FALSE(ok_called);
  EXPECT_FALSE(cancel_called);
  EXPECT_TRUE(closed);
  EXPECT_TRUE(dialog_destroyed_);
}

TEST_F(ModalDialogWrapperTest, ModalButtonsNoProminent) {
  auto dialog_model = DialogModelBuilder(&dialog_destroyed_).Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  EXPECT_EQ(static_cast<ui::ModalDialogWrapper::ModalDialogButtonStyles>(
                fake_dialog_manager_->GetButtonStyles()),
            ui::ModalDialogWrapper::ModalDialogButtonStyles::
                kPrimaryOutlineNegativeOutline);
  EXPECT_FALSE(dialog_destroyed_);
}

TEST_F(ModalDialogWrapperTest, ModalButtonsPrimaryProminentNoNegative) {
  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_)
          .WithOkButton(base::DoNothing(), ui::ButtonStyle::kProminent)
          .Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  EXPECT_EQ(static_cast<ui::ModalDialogWrapper::ModalDialogButtonStyles>(
                fake_dialog_manager_->GetButtonStyles()),
            ui::ModalDialogWrapper::ModalDialogButtonStyles::
                kPrimaryFilledNoNegative);
  EXPECT_FALSE(dialog_destroyed_);
}

TEST_F(ModalDialogWrapperTest, ModalButtonsPrimaryProminent) {
  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_)
          .WithOkButton(base::DoNothing(), ui::ButtonStyle::kProminent)
          .WithCancelButton(base::DoNothing())
          .Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  EXPECT_EQ(static_cast<ui::ModalDialogWrapper::ModalDialogButtonStyles>(
                fake_dialog_manager_->GetButtonStyles()),
            ui::ModalDialogWrapper::ModalDialogButtonStyles::
                kPrimaryFilledNegativeOutline);
  EXPECT_FALSE(dialog_destroyed_);
}

TEST_F(ModalDialogWrapperTest, ModalButtonsNegativeProminent) {
  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_)
          .WithCancelButton(base::DoNothing(), ui::ButtonStyle::kProminent)
          .Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  EXPECT_EQ(static_cast<ui::ModalDialogWrapper::ModalDialogButtonStyles>(
                fake_dialog_manager_->GetButtonStyles()),
            ui::ModalDialogWrapper::ModalDialogButtonStyles::
                kPrimaryOutlineNegativeFilled);
  EXPECT_FALSE(dialog_destroyed_);
}

TEST_F(ModalDialogWrapperTest, ModalButtonsOverriddenNone) {
  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_)
          .WithOkButton(base::DoNothing(), ui::ButtonStyle::kProminent)
          .WithCancelButton(base::DoNothing(), ui::ButtonStyle::kProminent)
          .OverrideDefaultButton(ui::mojom::DialogButton::kNone)
          .Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  EXPECT_EQ(static_cast<ui::ModalDialogWrapper::ModalDialogButtonStyles>(
                fake_dialog_manager_->GetButtonStyles()),
            ui::ModalDialogWrapper::ModalDialogButtonStyles::
                kPrimaryOutlineNegativeOutline);
  EXPECT_FALSE(dialog_destroyed_);
}

TEST_F(ModalDialogWrapperTest, ModalButtonsOverriddenPositive) {
  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_)
          .WithCancelButton(base::DoNothing(), ui::ButtonStyle::kProminent)
          .OverrideDefaultButton(ui::mojom::DialogButton::kOk)
          .Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  EXPECT_EQ(static_cast<ui::ModalDialogWrapper::ModalDialogButtonStyles>(
                fake_dialog_manager_->GetButtonStyles()),
            ui::ModalDialogWrapper::ModalDialogButtonStyles::
                kPrimaryFilledNegativeOutline);
  EXPECT_FALSE(dialog_destroyed_);
}

TEST_F(ModalDialogWrapperTest, ModalButtonsOverriddenNegative) {
  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_)
          .WithOkButton(base::DoNothing(), ui::ButtonStyle::kProminent)
          .WithCancelButton(base::DoNothing())
          .OverrideDefaultButton(ui::mojom::DialogButton::kCancel)
          .Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  EXPECT_EQ(static_cast<ui::ModalDialogWrapper::ModalDialogButtonStyles>(
                fake_dialog_manager_->GetButtonStyles()),
            ui::ModalDialogWrapper::ModalDialogButtonStyles::
                kPrimaryOutlineNegativeFilled);
  EXPECT_FALSE(dialog_destroyed_);
}

TEST_F(ModalDialogWrapperTest, ParagraphsAreSetAndReplaced) {
  std::vector<std::u16string> paragraphs = {u"This is the first paragraph.",
                                            u"This is the second paragraph."};

  auto dialog_model_1 =
      DialogModelBuilder(&dialog_destroyed_).WithParagraphs(paragraphs).Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model_1), window_->get());

  std::vector<std::u16string> displayed_paragraphs_1 =
      fake_dialog_manager_->GetMessageParagraphs();
  ASSERT_EQ(displayed_paragraphs_1.size(), 2u);
  EXPECT_EQ(displayed_paragraphs_1.front(), paragraphs.front());
  EXPECT_EQ(displayed_paragraphs_1.back(), paragraphs.back());

  // Remove the last element and confirm the behavior.
  paragraphs.pop_back();

  auto dialog_model_2 =
      DialogModelBuilder(&dialog_destroyed_).WithParagraphs(paragraphs).Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model_2), window_->get());

  std::vector<std::u16string> displayed_paragraphs_2 =
      fake_dialog_manager_->GetMessageParagraphs();
  ASSERT_EQ(displayed_paragraphs_2.size(), 1u);
  EXPECT_EQ(displayed_paragraphs_2.front(), paragraphs.front());
}

TEST_F(ModalDialogWrapperTest, ParagraphsContainReplacements) {
  // Use a vector of booleans to track the invocation of distinct callbacks.
  std::vector<bool> callbacks_called = {false, false, false};

  // In unit tests, the full resource pack isn't loaded.
  ResourceBundle::GetSharedInstance().OverrideLocaleStringResource(IDS_APP_OK,
                                                                   u"OK");
  ResourceBundle::GetSharedInstance().OverrideLocaleStringResource(
      IDS_APP_CANCEL, u"Cancel");
  ResourceBundle::GetSharedInstance().OverrideLocaleStringResource(
      IDS_APP_CLOSE, u"Close");

  ui::DialogModel::Builder dialog_builder;
  dialog_builder.SetTitle(u"Mixed Content Paragraph Test")
      .SetDialogDestroyingCallback(
          base::BindLambdaForTesting([&]() { dialog_destroyed_ = true; }));

  // Paragraph 0: A standard paragraph with only plain text.
  dialog_builder.AddParagraph(
      DialogModelLabel(u"This is a paragraph with no links."));

  // Paragraph 1: Contains multiple links and plain text spans to test
  // correct callback indexing across a single paragraph.
  auto replacements = std::vector<DialogModelLabel::TextReplacement>();

  // Helper lambda to create the correct callback type, avoiding ambiguity.
  auto make_callback = [&](int index) -> DialogModelLabel::Callback {
    return base::BindRepeating(
        [](std::vector<bool>* callbacks, int i, const ui::Event& event) {
          (*callbacks)[i] = true;
        },
        &callbacks_called, index);
  };

  // Link with global index 0.
  replacements.push_back(
      DialogModelLabel::CreateLink(IDS_APP_OK, make_callback(0)));
  replacements.push_back(
      DialogModelLabel::CreatePlainText(u" is the first link. "));
  // Link with global index 1.
  replacements.push_back(
      DialogModelLabel::CreateLink(IDS_APP_CANCEL, make_callback(1)));
  replacements.push_back(
      DialogModelLabel::CreatePlainText(u" is the second. Finally, "));
  // Link with global index 2.
  replacements.push_back(
      DialogModelLabel::CreateLink(IDS_APP_CLOSE, make_callback(2)));
  dialog_builder.AddParagraph(
      DialogModelLabel::CreateWithReplacements(0, std::move(replacements)));

  // Paragraph 2: An empty paragraph to test this edge case.
  dialog_builder.AddParagraph(DialogModelLabel(u""));

  // Paragraph 3: A trailing plain text paragraph to ensure correct handling
  // after paragraphs with links and empty ones.
  dialog_builder.AddParagraph(
      DialogModelLabel(u"This is a final plain paragraph."));

  auto dialog_model = dialog_builder.Build();

  // Trigger Dialog.
  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  // Verify the paragraphs text.
  std::vector<std::u16string> displayed_paragraphs =
      fake_dialog_manager_->GetMessageParagraphs();
  ASSERT_EQ(displayed_paragraphs.size(), 4u);
  EXPECT_EQ(displayed_paragraphs[0], u"This is a paragraph with no links.");
  EXPECT_EQ(displayed_paragraphs[1],
            u"OK is the first link. Cancel is the second. Finally, Close");
  EXPECT_EQ(displayed_paragraphs[2], u"");
  EXPECT_EQ(displayed_paragraphs[3], u"This is a final plain paragraph.");

  // Simulate link clicks.
  EXPECT_FALSE(callbacks_called[0]);
  EXPECT_FALSE(callbacks_called[1]);
  EXPECT_FALSE(callbacks_called[2]);

  // Click the middle link.
  fake_dialog_manager_->ClickLinkInMessageParagraphs(1);
  EXPECT_FALSE(callbacks_called[0]);
  EXPECT_TRUE(callbacks_called[1]);
  EXPECT_FALSE(callbacks_called[2]);

  // Click the last link.
  fake_dialog_manager_->ClickLinkInMessageParagraphs(2);
  EXPECT_FALSE(callbacks_called[0]);
  EXPECT_TRUE(callbacks_called[1]);
  EXPECT_TRUE(callbacks_called[2]);

  // Click the first link.
  fake_dialog_manager_->ClickLinkInMessageParagraphs(0);
  EXPECT_TRUE(callbacks_called[0]);
  EXPECT_TRUE(callbacks_called[1]);
  EXPECT_TRUE(callbacks_called[2]);
}

TEST_F(ModalDialogWrapperTest, Checkbox_InitialStateUnchecked) {
  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_).WithCheckbox(false).Build();
  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  EXPECT_FALSE(fake_dialog_manager_->IsCheckboxChecked());
}

TEST_F(ModalDialogWrapperTest, Checkbox_InitialStateChecked) {
  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_).WithCheckbox(true).Build();
  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  EXPECT_TRUE(fake_dialog_manager_->IsCheckboxChecked());
}

TEST_F(ModalDialogWrapperTest, Checkbox_StateSynchronizedAfterToggle) {
  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_).WithCheckbox(false).Build();
  // Get a raw pointer to the model before it's moved.
  auto* dialog_model_ptr = dialog_model.get();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  // Verify initial state.
  EXPECT_FALSE(fake_dialog_manager_->IsCheckboxChecked());
  EXPECT_FALSE(
      dialog_model_ptr->GetCheckboxByUniqueId(kCheckboxId)->is_checked());

  // Simulate a UI click on the checkbox.
  fake_dialog_manager_->ToggleCheckbox();

  // Verify both the Java model (via fake manager) and C++ model are updated.
  EXPECT_TRUE(fake_dialog_manager_->IsCheckboxChecked());
  EXPECT_TRUE(
      dialog_model_ptr->GetCheckboxByUniqueId(kCheckboxId)->is_checked());

  // Toggle it back.
  fake_dialog_manager_->ToggleCheckbox();

  EXPECT_FALSE(fake_dialog_manager_->IsCheckboxChecked());
  EXPECT_FALSE(
      dialog_model_ptr->GetCheckboxByUniqueId(kCheckboxId)->is_checked());
}

TEST_F(ModalDialogWrapperTest, TitleIcon_ShowsVectorIcon) {
  auto icon =
      ui::ImageModel::FromVectorIcon(kTestIcon, SK_ColorBLACK, kIconDim);

  // Convert icon to bitmap for comparison.
  ui::ColorProvider color_provider;
  color_provider.GenerateColorMapForTesting();
  const SkBitmap expected_bitmap = *icon.Rasterize(&color_provider).bitmap();

  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_).WithIcon(std::move(icon)).Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  SkBitmap actual_bitmap = fake_dialog_manager_->GetTitleIcon();

  EXPECT_TRUE(gfx::test::AreBitmapsEqual(expected_bitmap, actual_bitmap));
  EXPECT_FALSE(dialog_destroyed_);
}

TEST_F(ModalDialogWrapperTest, TitleIcon_ShowsGfxImage) {
  auto icon = CreateBitmapImage(SK_ColorGREEN);
  ui::ColorProvider color_provider;
  color_provider.GenerateColorMapForTesting();
  const SkBitmap expected_bitmap = *icon.Rasterize(&color_provider).bitmap();

  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_).WithIcon(std::move(icon)).Build();

  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  SkBitmap actual_bitmap = fake_dialog_manager_->GetTitleIcon();

  EXPECT_TRUE(gfx::test::AreBitmapsEqual(expected_bitmap, actual_bitmap));
  EXPECT_FALSE(dialog_destroyed_);
}

TEST_F(ModalDialogWrapperTest, ShowsMenuItems) {
  // --- Item 1: SkBitmap-based ---
  auto icon1 = CreateBitmapImage(SK_ColorRED);
  ui::ColorProvider color_provider;
  color_provider.GenerateColorMapForTesting();
  const SkBitmap expected_bitmap1 = *icon1.Rasterize(&color_provider).bitmap();
  const std::u16string label1 = u"Red Bitmap Item";

  // --- Item 2: VectorIcon-based ---
  auto icon2 =
      ui::ImageModel::FromVectorIcon(kTestIcon, SK_ColorBLUE, kIconDim);
  const SkBitmap expected_bitmap2 = *icon2.Rasterize(&color_provider).bitmap();
  const std::u16string label2 = u"Blue Vector Item";

  // --- Build and Show ---
  auto dialog_model = DialogModelBuilder(&dialog_destroyed_)
                          .AddMenuItem(std::move(icon1), label1)
                          .AddMenuItem(std::move(icon2), label2)
                          .Build();
  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  // --- Assert ---
  std::vector<std::u16string> actual_labels =
      fake_dialog_manager_->GetMenuItemTexts();
  std::vector<SkBitmap> actual_icons = fake_dialog_manager_->GetMenuItemIcons();
  // Try a null callback
  fake_dialog_manager_->ClickMenuItem(0);

  ASSERT_EQ(actual_labels.size(), 2u);
  ASSERT_EQ(actual_icons.size(), 2u);

  EXPECT_EQ(actual_labels[0], label1);
  EXPECT_EQ(actual_labels[1], label2);
  EXPECT_TRUE(gfx::test::AreBitmapsEqual(actual_icons[0], expected_bitmap1));
  EXPECT_TRUE(gfx::test::AreBitmapsEqual(actual_icons[1], expected_bitmap2));

  EXPECT_FALSE(dialog_destroyed_);
}

TEST_F(ModalDialogWrapperTest, MenuItem_Callbacks) {
  bool callback1_called = false;
  bool callback2_called = false;
  auto icon = CreateBitmapImage(SK_ColorRED);

  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_)
          .AddMenuItemWithCallback(
              icon, u"Item 1",
              base::BindLambdaForTesting([&]() { callback1_called = true; }))
          .AddMenuItemWithCallback(
              icon, u"Item 2",
              base::BindLambdaForTesting([&]() { callback2_called = true; }))
          .Build();
  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  fake_dialog_manager_->ClickMenuItem(1);

  EXPECT_FALSE(callback1_called);
  EXPECT_TRUE(callback2_called);

  fake_dialog_manager_->ClickMenuItem(0);
  EXPECT_TRUE(callback1_called);
}

TEST_F(ModalDialogWrapperTest, MenuItem_CallbackDismissesDialog) {
  auto dialog_model =
      DialogModelBuilder(&dialog_destroyed_)
          .AddMenuItemWithCallback(
              CreateBitmapImage(SK_ColorRED), u"Item",
              base::BindLambdaForTesting([&]() {
                ModalDialogWrapper::GetDialogForTesting()->Close();
              }))
          .Build();
  ModalDialogWrapper::ShowTabModal(std::move(dialog_model), window_->get());

  fake_dialog_manager_->ClickMenuItem(0);

  EXPECT_TRUE(dialog_destroyed_);
}

}  // namespace ui