// Copyright 2011 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/views/controls/progress_bar.h"

#include <string>

#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/color/color_id.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/color_utils.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/test/ax_event_counter.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/test/views_test_utils.h"

namespace views {

class ProgressBarTest : public ViewsTestBase {
 protected:
  // ViewsTestBase:
  void SetUp() override {
    ViewsTestBase::SetUp();

    widget_ = CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
    container_view_ = widget_->SetContentsView(std::make_unique<View>());
    auto* layout =
        container_view_->SetLayoutManager(std::make_unique<FlexLayout>());
    layout->SetOrientation(views::LayoutOrientation::kVertical);
    bar_ = container_view_->AddChildView(std::make_unique<ProgressBar>());
    views::test::RunScheduledLayout(container_view_);
    widget_->Show();
  }

  void TearDown() override {
    container_view_ = nullptr;
    bar_ = nullptr;
    widget_.reset();
    ViewsTestBase::TearDown();
  }

  ProgressBar* bar() { return bar_.get(); }

  std::unique_ptr<Widget> widget_;
  raw_ptr<views::View> container_view_;
  raw_ptr<ProgressBar> bar_;
};

TEST_F(ProgressBarTest, AccessibleNodeData) {
  bar()->SetValue(0.626);

  ui::AXNodeData node_data;
  bar()->GetViewAccessibility().GetAccessibleNodeData(&node_data);
  EXPECT_EQ(ax::mojom::Role::kProgressIndicator, node_data.role);
  EXPECT_EQ(std::u16string(),
            node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
  EXPECT_EQ(std::string("62%"),
            node_data.GetStringAttribute(ax::mojom::StringAttribute::kValue));
  EXPECT_FALSE(
      node_data.HasIntAttribute(ax::mojom::IntAttribute::kRestriction));
}

// Verifies the correct a11y events are raised for an accessible progress bar.
TEST_F(ProgressBarTest, AccessibilityEvents) {
  test::AXEventCounter ax_counter(views::AXUpdateNotifier::Get());
  EXPECT_EQ(0, ax_counter.GetCount(ax::mojom::Event::kValueChanged));

  bar()->SetValue(0.50);
  EXPECT_EQ(1, ax_counter.GetCount(ax::mojom::Event::kValueChanged));

  bar()->SetValue(0.63);
  EXPECT_EQ(2, ax_counter.GetCount(ax::mojom::Event::kValueChanged));

  bar()->SetValue(0.636);
  EXPECT_EQ(2, ax_counter.GetCount(ax::mojom::Event::kValueChanged));

  bar()->SetValue(0.642);
  EXPECT_EQ(3, ax_counter.GetCount(ax::mojom::Event::kValueChanged));

  widget_->Hide();
  widget_->Show();
  EXPECT_EQ(3, ax_counter.GetCount(ax::mojom::Event::kValueChanged));

  widget_->Hide();
  bar()->SetValue(0.8);
  EXPECT_EQ(3, ax_counter.GetCount(ax::mojom::Event::kValueChanged));

  widget_->Show();
  EXPECT_EQ(4, ax_counter.GetCount(ax::mojom::Event::kValueChanged));
}

// Test that default colors can be overridden. Used by Chromecast.
TEST_F(ProgressBarTest, OverrideDefaultColors) {
  EXPECT_NE(SK_ColorRED, bar()->GetForegroundColor());
  EXPECT_NE(SK_ColorGREEN, bar()->GetBackgroundColor());
  EXPECT_NE(bar()->GetForegroundColor(), bar()->GetBackgroundColor());

  bar()->SetForegroundColor(SK_ColorRED);
  bar()->SetBackgroundColor(SK_ColorGREEN);
  EXPECT_EQ(SK_ColorRED, bar()->GetForegroundColor());
  EXPECT_EQ(SK_ColorGREEN, bar()->GetBackgroundColor());

  // Override colors with color ID. It will also override the colors set with
  // SkColor.
  bar()->SetForegroundColorId(ui::kColorSysPrimary);
  bar()->SetBackgroundColorId(ui::kColorSysPrimaryContainer);
  const auto* color_provider = bar()->GetColorProvider();
  EXPECT_EQ(color_provider->GetColor(ui::kColorSysPrimary),
            bar()->GetForegroundColor());
  EXPECT_EQ(color_provider->GetColor(ui::kColorSysPrimaryContainer),
            bar()->GetBackgroundColor());
  EXPECT_EQ(ui::kColorSysPrimary, bar()->GetForegroundColorId().value());
  EXPECT_EQ(ui::kColorSysPrimaryContainer,
            bar()->GetBackgroundColorId().value());

  // Override the colors set with color ID by SkColor.
  bar()->SetForegroundColor(SK_ColorRED);
  bar()->SetBackgroundColor(SK_ColorGREEN);
  EXPECT_EQ(SK_ColorRED, bar()->GetForegroundColor());
  EXPECT_EQ(SK_ColorGREEN, bar()->GetBackgroundColor());
  EXPECT_EQ(std::nullopt, bar()->GetForegroundColorId());
  EXPECT_EQ(std::nullopt, bar()->GetBackgroundColorId());
}

// Test that if no `preferred_corner_radii` are provided the default radius is
// 3, and a value of `std::nullopt` will not round the corners.
TEST_F(ProgressBarTest, RoundCornerDefault) {
  // The default bar should have a rounded corner radius of 3.
  EXPECT_EQ(gfx::RoundedCornersF(3), bar()->GetPreferredCornerRadii());

  // Setting `std::nullopt` for the corner radius should make the bar have no
  // rounded corners.
  bar()->SetPreferredHeight(12);
  bar()->SetPreferredCornerRadii(std::nullopt);
  views::test::RunScheduledLayout(container_view_);
  EXPECT_EQ(gfx::RoundedCornersF(0), bar()->GetPreferredCornerRadii());
  EXPECT_TRUE(bar()->GetPreferredCornerRadii().IsEmpty());
}

// Test that a value set for `preferred_corner_radii` is saved and can be
// retrieved from `GetPreferredCornerRadii()`.
TEST_F(ProgressBarTest, RoundCornerRetrieval) {
  // Setting custom corners should result in them being saved.
  bar()->SetPreferredHeight(12);
  bar()->SetPreferredCornerRadii(gfx::RoundedCornersF(6));
  views::test::RunScheduledLayout(container_view_);
  EXPECT_EQ(gfx::RoundedCornersF(6), bar()->GetPreferredCornerRadii());
}

// Test that `GetPreferredCornerRadii()` will return no corner with a radius
// greater than the height of the bar.
TEST_F(ProgressBarTest, RoundCornerMax) {
  // The max corner radius for a bar with a height of 12 should be 12.
  bar()->SetPreferredHeight(12);
  bar()->SetPreferredCornerRadii(gfx::RoundedCornersF(13, 14, 15, 16));
  views::test::RunScheduledLayout(container_view_);
  EXPECT_EQ(gfx::RoundedCornersF(12, 12, 12, 12),
            bar()->GetPreferredCornerRadii());
}

// Test that if value is set negative, which means progress bar is
// indeterminate, the string attribute value should be empty.
TEST_F(ProgressBarTest, RemoveValue) {
  // setting negative progress bar value
  bar()->SetValue(-0.626);

  ui::AXNodeData node_data;
  bar()->GetViewAccessibility().GetAccessibleNodeData(&node_data);
  EXPECT_EQ(std::string(""),
            node_data.GetStringAttribute(ax::mojom::StringAttribute::kValue));
}

}  // namespace views
