// Copyright 2018 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/views/status_bubble_views.h"

#include <utility>

#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/test_simple_task_runner.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/views/status_bubble_views_browsertest_mac.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"
#include "ui/gfx/animation/animation.h"
#include "ui/views/widget/widget.h"

class StatusBubbleViewsTest : public InProcessBrowserTest {
 public:
  StatusBubbleViews* GetBubble() {
    std::vector<StatusBubble*> status_bubbles =
        BrowserWindow::FromBrowser(browser())->GetStatusBubbles();
    if (status_bubbles.size() > 0) {
      return static_cast<StatusBubbleViews*>(status_bubbles.front());
    }
    return nullptr;
  }
  views::Widget* GetWidget() { return GetBubble()->popup(); }
  bool IsDestroyPopupTimerRunning() {
    return GetBubble()->IsDestroyPopupTimerRunningForTest();
  }
  gfx::Animation* GetShowHideAnimationForTesting() {
    return GetBubble()->GetShowHideAnimationForTest();
  }
  void SetTaskRunners(
      scoped_refptr<base::SequencedTaskRunner> task_runner,
      scoped_refptr<base::SequencedTaskRunner> best_effort_task_runner) {
    ASSERT_FALSE(orig_task_runner_);
    ASSERT_FALSE(orig_best_effort_task_runner_);
    orig_task_runner_ = std::exchange(GetBubble()->task_runner_, task_runner);
    orig_best_effort_task_runner_ = std::exchange(
        GetBubble()->best_effort_task_runner_, best_effort_task_runner);
  }
  void ResetTaskRunners() {
    ASSERT_TRUE(orig_task_runner_);
    ASSERT_TRUE(orig_best_effort_task_runner_);
    GetBubble()->task_runner_ = std::exchange(orig_task_runner_, nullptr);
    GetBubble()->best_effort_task_runner_ =
        std::exchange(orig_best_effort_task_runner_, nullptr);
  }

 private:
  scoped_refptr<base::SequencedTaskRunner> orig_task_runner_;
  scoped_refptr<base::SequencedTaskRunner> orig_best_effort_task_runner_;
};

IN_PROC_BROWSER_TEST_F(StatusBubbleViewsTest, WidgetLifetime) {
  auto task_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
  auto best_effort_task_runner =
      base::MakeRefCounted<base::TestSimpleTaskRunner>();
  SetTaskRunners(task_runner, best_effort_task_runner);

  // The widget does not exist until it needs to be shown.
  StatusBubble* bubble = GetBubble();
  ASSERT_TRUE(bubble);
  EXPECT_FALSE(GetWidget());

  // Setting status text shows the widget.
  bubble->SetStatus(u"test");
  views::Widget* widget = GetWidget();
  ASSERT_TRUE(widget);
  EXPECT_TRUE(widget->IsVisible());

  // Changing status text keeps the widget visible.
  bubble->SetStatus(u"foo");
  EXPECT_TRUE(widget->IsVisible());

  // Setting the URL keeps the widget visible.
  bubble->SetURL(GURL("http://www.foo.com"));
  EXPECT_TRUE(widget->IsVisible());

#if !BUILDFLAG(IS_MAC)
  // Clearing the URL and status closes the widget on platforms other than Mac.
  EXPECT_FALSE(IsDestroyPopupTimerRunning());
  bubble->SetStatus(std::u16string());
  bubble->SetURL(GURL());
  // The widget is not hidden immediately, instead a task is scheduled. Run that
  // now.
  task_runner->RunPendingTasks();
  // After the task, a timer is created that animates hidden. Advance that.
  ASSERT_TRUE(GetShowHideAnimationForTesting());
  // Advance well past the time for the animation to ensure it completes.
  static_cast<gfx::AnimationContainerElement*>(GetShowHideAnimationForTesting())
      ->Step(base::TimeTicks::Now() + base::Minutes(1));
  // Widget should still exist.
  ASSERT_TRUE(GetWidget());
  EXPECT_FALSE(widget->IsVisible());
  EXPECT_TRUE(IsDestroyPopupTimerRunning());

  // Run until idle, which should trigger deleting the widget.
  while (task_runner->HasPendingTask() ||
         best_effort_task_runner->HasPendingTask()) {
    task_runner->RunPendingTasks();
    best_effort_task_runner->RunPendingTasks();
  }
  EXPECT_FALSE(IsDestroyPopupTimerRunning());
  EXPECT_FALSE(GetWidget());
#endif
  ResetTaskRunners();
}

// Mac does not delete the widget after a delay, so this test only runs on
// non-mac platforms.
#if !BUILDFLAG(IS_MAC)
IN_PROC_BROWSER_TEST_F(StatusBubbleViewsTest, ShowHideDestroyShow) {
  auto task_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
  auto best_effort_task_runner =
      base::MakeRefCounted<base::TestSimpleTaskRunner>();
  SetTaskRunners(task_runner, best_effort_task_runner);

  // The widget does not exist until it needs to be shown.
  StatusBubble* bubble = GetBubble();
  ASSERT_TRUE(bubble);

  // Setting status text shows the widget.
  bubble->SetStatus(u"test");
  views::Widget* widget = GetWidget();
  ASSERT_TRUE(widget);
  EXPECT_TRUE(widget->IsVisible());

  bubble->SetStatus(std::u16string());
  // The widget is not hidden immediately, instead a task is scheduled. Run that
  // now.
  task_runner->RunPendingTasks();
  // After the task, a timer is created that animates hidden. Advance that.
  ASSERT_TRUE(GetShowHideAnimationForTesting());
  // Advance well past the time for the animation to ensure it completes.
  static_cast<gfx::AnimationContainerElement*>(GetShowHideAnimationForTesting())
      ->Step(base::TimeTicks::Now() + base::Minutes(1));
  // Widget should still exist.
  ASSERT_TRUE(GetWidget());
  EXPECT_FALSE(widget->IsVisible());
  EXPECT_TRUE(IsDestroyPopupTimerRunning());

  // Run until idle, which should trigger deleting the widget.
  while (task_runner->HasPendingTask() ||
         best_effort_task_runner->HasPendingTask()) {
    task_runner->RunPendingTasks();
    best_effort_task_runner->RunPendingTasks();
  }
  EXPECT_FALSE(IsDestroyPopupTimerRunning());
  EXPECT_FALSE(GetWidget());

  // Setting status text shows the widget.
  bubble->SetStatus(u"test");
  widget = GetWidget();
  ASSERT_TRUE(widget);
  EXPECT_TRUE(widget->IsVisible());

  ResetTaskRunners();
}
#endif
