Commit 139eccc4 authored by Takumi Fujimoto's avatar Takumi Fujimoto Committed by Commit Bot

[Media Router] Add MediaRouterUiForTest

This CL adds test API that will be used by integration and e2e browser
tests to manipulate the Views Cast dialog.

Bug: 900243
Change-Id: I0dc0c4ce08a3f9b78764459b6298502d4a8e0901
Reviewed-on: https://chromium-review.googlesource.com/c/1284211
Commit-Queue: Takumi Fujimoto <takumif@chromium.org>
Reviewed-by: default avatarmark a. foltz <mfoltz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#607983}
parent 85504ef9
......@@ -35,6 +35,7 @@ class CastDialogSinkButton : public HoverButton {
const UIMediaSink& sink() const { return sink_; }
private:
friend class MediaRouterUiForTest;
FRIEND_TEST_ALL_PREFIXES(CastDialogSinkButtonTest, OverrideStatusText);
FRIEND_TEST_ALL_PREFIXES(CastDialogSinkButtonTest,
SetStatusLabelForActiveSink);
......
......@@ -75,10 +75,10 @@ void CastDialogView::ShowDialogTopCentered(CastDialogController* controller,
void CastDialogView::HideDialog() {
if (IsShowing())
instance_->GetWidget()->Close();
// We also set |instance_| to nullptr in WindowClosing() which is called
// asynchronously, because not all paths to close the dialog go through
// HideDialog(). We set it here because IsShowing() should be false after
// HideDialog() is called.
// We set |instance_| to null here because IsShowing() should be false after
// HideDialog() is called. Not all paths to close the dialog go through
// HideDialog(), so we also set it to null in WindowClosing(), which always
// gets called asynchronously.
instance_ = nullptr;
}
......@@ -87,6 +87,11 @@ bool CastDialogView::IsShowing() {
return instance_ != nullptr;
}
// static
CastDialogView* CastDialogView::GetInstance() {
return instance_;
}
// static
views::Widget* CastDialogView::GetCurrentDialogWidget() {
return instance_ ? instance_->GetWidget() : nullptr;
......@@ -152,6 +157,8 @@ void CastDialogView::OnModelUpdated(const CastDialogModel& model) {
MaybeSizeToContents();
// Update the main action button.
DialogModelChanged();
for (Observer& observer : observers_)
observer.OnDialogModelUpdated(this);
}
void CastDialogView::OnControllerInvalidated() {
......@@ -210,6 +217,14 @@ void CastDialogView::ExecuteCommand(int command_id, int event_flags) {
}
}
void CastDialogView::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void CastDialogView::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
// static
void CastDialogView::ShowDialog(views::View* anchor_view,
views::BubbleBorder::Arrow anchor_position,
......@@ -259,6 +274,8 @@ void CastDialogView::Init() {
}
void CastDialogView::WindowClosing() {
for (Observer& observer : observers_)
observer.OnDialogWillClose(this);
if (instance_ == this)
instance_ = nullptr;
metrics_.OnCloseDialog(base::Time::Now());
......
......@@ -9,6 +9,7 @@
#include <vector>
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "chrome/browser/ui/media_router/cast_dialog_controller.h"
#include "chrome/browser/ui/views/media_router/cast_dialog_metrics.h"
#include "ui/base/models/simple_menu_model.h"
......@@ -37,6 +38,14 @@ class CastDialogView : public views::BubbleDialogDelegateView,
public CastDialogController::Observer,
public ui::SimpleMenuModel::Delegate {
public:
class Observer : public base::CheckedObserver {
public:
virtual void OnDialogModelUpdated(CastDialogView* dialog_view) = 0;
virtual void OnDialogWillClose(CastDialogView* dialog_view) = 0;
};
enum SourceType { kTab, kDesktop, kLocalFile };
// Shows the singleton dialog anchored to the Cast toolbar icon. Requires that
// BrowserActionsContainer exists for |browser|.
static void ShowDialogWithToolbarAction(CastDialogController* controller,
......@@ -54,6 +63,8 @@ class CastDialogView : public views::BubbleDialogDelegateView,
static bool IsShowing();
static CastDialogView* GetInstance();
// Returns nullptr if the dialog is currently not shown.
static views::Widget* GetCurrentDialogWidget();
......@@ -86,6 +97,9 @@ class CastDialogView : public views::BubbleDialogDelegateView,
bool IsCommandIdEnabled(int command_id) const override;
void ExecuteCommand(int command_id, int event_flags) override;
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
// Called by tests.
const std::vector<CastDialogSinkButton*>& sink_buttons_for_test() const {
return sink_buttons_;
......@@ -108,8 +122,6 @@ class CastDialogView : public views::BubbleDialogDelegateView,
FRIEND_TEST_ALL_PREFIXES(CastDialogViewTest, ShowAndHideDialog);
FRIEND_TEST_ALL_PREFIXES(CastDialogViewTest, ShowSourcesMenu);
enum SourceType { kTab, kDesktop, kLocalFile };
// Instantiates and shows the singleton dialog. The dialog must not be
// currently shown.
static void ShowDialog(views::View* anchor_view,
......@@ -212,6 +224,8 @@ class CastDialogView : public views::BubbleDialogDelegateView,
// This value is set if the user has chosen a local file to cast.
base::Optional<base::string16> local_file_name_;
base::ObserverList<Observer> observers_;
base::WeakPtrFactory<CastDialogView> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(CastDialogView);
......
......@@ -21,6 +21,8 @@ source_set("browser_tests") {
"media_router_integration_browsertest.h",
"media_router_integration_ui_browsertest.cc",
"media_router_one_ua_integration_browsertest.cc",
"media_router_ui_for_test.cc",
"media_router_ui_for_test.h",
]
deps = [
":test_support",
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/test/media_router/media_router_ui_for_test.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/views/media_router/cast_dialog_sink_button.h"
#include "chrome/browser/ui/views/media_router/cast_dialog_view.h"
#include "chrome/browser/ui/views/media_router/media_router_dialog_controller_views.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/point.h"
namespace media_router {
namespace {
ui::MouseEvent CreateMousePressedEvent() {
return ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(0, 0),
gfx::Point(0, 0), ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, 0);
}
ui::MouseEvent CreateMouseReleasedEvent() {
return ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(0, 0),
gfx::Point(0, 0), ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, 0);
}
} // namespace
// static
MediaRouterUiForTest* MediaRouterUiForTest::GetOrCreateForWebContents(
content::WebContents* web_contents) {
// No-op if an instance already exists for the WebContents.
MediaRouterUiForTest::CreateForWebContents(web_contents);
return MediaRouterUiForTest::FromWebContents(web_contents);
}
MediaRouterUiForTest::~MediaRouterUiForTest() {
CHECK(!watch_callback_);
}
void MediaRouterUiForTest::ShowDialog() {
dialog_controller_->ShowMediaRouterDialog();
base::RunLoop().RunUntilIdle();
}
void MediaRouterUiForTest::HideDialog() {
dialog_controller_->HideMediaRouterDialog();
base::RunLoop().RunUntilIdle();
}
bool MediaRouterUiForTest::IsDialogShown() const {
return dialog_controller_->IsShowingMediaRouterDialog();
}
void MediaRouterUiForTest::ChooseSourceType(
CastDialogView::SourceType source_type) {
CastDialogView* dialog_view = CastDialogView::GetInstance();
CHECK(dialog_view);
dialog_view->ButtonPressed(dialog_view->sources_button_for_test(),
CreateMousePressedEvent());
int source_index;
switch (source_type) {
case CastDialogView::kTab:
source_index = 0;
break;
case CastDialogView::kDesktop:
source_index = 1;
break;
case CastDialogView::kLocalFile:
source_index = 2;
break;
}
dialog_view->sources_menu_model_for_test()->ActivatedAt(source_index);
}
void MediaRouterUiForTest::StartCasting(const MediaSink::Id& sink_id) {
CastDialogSinkButton* sink_button = GetSinkButton(sink_id);
sink_button->OnMousePressed(CreateMousePressedEvent());
sink_button->OnMouseReleased(CreateMouseReleasedEvent());
base::RunLoop().RunUntilIdle();
}
void MediaRouterUiForTest::StopCasting(const MediaSink::Id& sink_id) {
CastDialogSinkButton* sink_button = GetSinkButton(sink_id);
sink_button->icon_view()->OnMousePressed(CreateMousePressedEvent());
sink_button->icon_view()->OnMouseReleased(CreateMouseReleasedEvent());
base::RunLoop().RunUntilIdle();
}
void MediaRouterUiForTest::StopCasting() {
CastDialogView* dialog_view = CastDialogView::GetInstance();
CHECK(dialog_view);
for (CastDialogSinkButton* sink_button :
dialog_view->sink_buttons_for_test()) {
if (sink_button->sink().state == UIMediaSinkState::CONNECTED) {
sink_button->icon_view()->OnMousePressed(CreateMousePressedEvent());
sink_button->icon_view()->OnMouseReleased(CreateMouseReleasedEvent());
base::RunLoop().RunUntilIdle();
return;
}
}
NOTREACHED() << "Sink was not found";
}
void MediaRouterUiForTest::WaitForSink(const MediaSink::Id& sink_id) {
ObserveDialog(WatchType::kSink, sink_id);
}
void MediaRouterUiForTest::WaitForAnyIssue() {
ObserveDialog(WatchType::kAnyIssue);
}
void MediaRouterUiForTest::WaitForAnyRoute() {
ObserveDialog(WatchType::kAnyRoute);
}
void MediaRouterUiForTest::WaitForDialogClosed() {
ObserveDialog(WatchType::kDialogClosed);
}
std::string MediaRouterUiForTest::GetSinkName(
const MediaSink::Id& sink_id) const {
CastDialogSinkButton* sink_button = GetSinkButton(sink_id);
return base::UTF16ToUTF8(sink_button->sink().friendly_name);
}
MediaRoute::Id MediaRouterUiForTest::GetRouteIdForSink(
const MediaSink::Id& sink_id) const {
CastDialogSinkButton* sink_button = GetSinkButton(sink_id);
return sink_button->sink().route_id;
}
std::string MediaRouterUiForTest::GetStatusTextForSink(
const MediaSink::Id& sink_id) const {
CastDialogSinkButton* sink_button = GetSinkButton(sink_id);
return base::UTF16ToUTF8(sink_button->sink().status_text);
}
std::string MediaRouterUiForTest::GetIssueTextForSink(
const MediaSink::Id& sink_id) const {
CastDialogSinkButton* sink_button = GetSinkButton(sink_id);
if (!sink_button->sink().issue) {
NOTREACHED() << "Issue not found for sink " << sink_id;
return "";
}
return sink_button->sink().issue->info().title;
}
MediaRouterUiForTest::MediaRouterUiForTest(content::WebContents* web_contents)
: web_contents_(web_contents),
dialog_controller_(
MediaRouterDialogControllerViews::GetOrCreateForWebContents(
web_contents)) {}
void MediaRouterUiForTest::OnDialogModelUpdated(CastDialogView* dialog_view) {
if (!watch_callback_ || watch_type_ == WatchType::kDialogClosed)
return;
const std::vector<CastDialogSinkButton*>& sink_buttons =
dialog_view->sink_buttons_for_test();
if (std::find_if(sink_buttons.begin(), sink_buttons.end(),
[&, this](CastDialogSinkButton* sink_button) {
switch (watch_type_) {
case WatchType::kSink:
return sink_button->sink().id == *watch_sink_id_;
case WatchType::kAnyIssue:
return sink_button->sink().issue.has_value();
case WatchType::kAnyRoute:
return !sink_button->sink().route_id.empty();
case WatchType::kNone:
case WatchType::kDialogClosed:
NOTREACHED() << "Invalid WatchType";
return false;
}
}) != sink_buttons.end()) {
std::move(*watch_callback_).Run();
watch_callback_.reset();
watch_sink_id_.reset();
watch_type_ = WatchType::kNone;
dialog_view->RemoveObserver(this);
}
}
void MediaRouterUiForTest::OnDialogWillClose(CastDialogView* dialog_view) {
if (watch_type_ == WatchType::kDialogClosed) {
std::move(*watch_callback_).Run();
watch_callback_.reset();
watch_type_ = WatchType::kNone;
}
CHECK(!watch_callback_);
if (dialog_view)
dialog_view->RemoveObserver(this);
}
CastDialogSinkButton* MediaRouterUiForTest::GetSinkButton(
const MediaSink::Id& sink_id) const {
CastDialogView* dialog_view = CastDialogView::GetInstance();
CHECK(dialog_view);
const std::vector<CastDialogSinkButton*>& sink_buttons =
dialog_view->sink_buttons_for_test();
auto it = std::find_if(sink_buttons.begin(), sink_buttons.end(),
[sink_id](CastDialogSinkButton* sink_button) {
return sink_button->sink().id == sink_id;
});
if (it == sink_buttons.end()) {
NOTREACHED() << "Sink button not found for sink ID: " << sink_id;
return nullptr;
} else {
return *it;
}
}
void MediaRouterUiForTest::ObserveDialog(
WatchType watch_type,
base::Optional<MediaSink::Id> sink_id) {
CHECK(!watch_sink_id_);
CHECK(!watch_callback_);
CHECK_EQ(watch_type_, WatchType::kNone);
base::RunLoop run_loop;
watch_sink_id_ = std::move(sink_id);
watch_callback_ = run_loop.QuitClosure();
watch_type_ = watch_type;
CastDialogView* dialog_view = CastDialogView::GetInstance();
CHECK(dialog_view);
dialog_view->AddObserver(this);
// Check if the current dialog state already meets the condition that we are
// waiting for.
OnDialogModelUpdated(dialog_view);
run_loop.Run();
}
} // namespace media_router
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_TEST_MEDIA_ROUTER_MEDIA_ROUTER_UI_FOR_TEST_H_
#define CHROME_TEST_MEDIA_ROUTER_MEDIA_ROUTER_UI_FOR_TEST_H_
#include "base/callback_forward.h"
#include "base/macros.h"
#include "base/optional.h"
#include "chrome/browser/ui/views/media_router/cast_dialog_view.h"
#include "chrome/common/media_router/media_sink.h"
#include "chrome/common/media_router/media_source.h"
#include "content/public/browser/web_contents_user_data.h"
namespace content {
class WebContents;
} // namespace content
namespace media_router {
class MediaRouterDialogControllerViews;
class MediaRouterUiForTest
: public content::WebContentsUserData<MediaRouterUiForTest>,
public CastDialogView::Observer {
public:
static MediaRouterUiForTest* GetOrCreateForWebContents(
content::WebContents* web_contents);
~MediaRouterUiForTest() override;
void ShowDialog();
void HideDialog();
bool IsDialogShown() const;
// Chooses the source type in the dialog. Requires that the dialog is shown.
void ChooseSourceType(CastDialogView::SourceType source_type);
// These methods require that the dialog is shown and the specified sink is
// shown in the dialog.
void StartCasting(const MediaSink::Id& sink_id);
void StopCasting(const MediaSink::Id& sink_id);
// Stops casting to the first active sink found on the sink list. Requires
// that such a sink exists.
void StopCasting();
// Waits until . Requires that the dialog is shown.
void WaitForSink(const MediaSink::Id& sink_id);
void WaitForAnyIssue();
void WaitForAnyRoute();
void WaitForDialogClosed();
// These methods require that the dialog is shown, and the sink specified by
// |sink_id| is in the dialog.
std::string GetSinkName(const MediaSink::Id& sink_id) const;
MediaRoute::Id GetRouteIdForSink(const MediaSink::Id& sink_id) const;
std::string GetStatusTextForSink(const MediaSink::Id& sink_id) const;
std::string GetIssueTextForSink(const MediaSink::Id& sink_id) const;
content::WebContents* web_contents() const { return web_contents_; }
private:
friend class content::WebContentsUserData<MediaRouterUiForTest>;
enum class WatchType { kNone, kSink, kAnyIssue, kAnyRoute, kDialogClosed };
explicit MediaRouterUiForTest(content::WebContents* web_contents);
// CastDialogView::Observer:
void OnDialogModelUpdated(CastDialogView* dialog_view) override;
void OnDialogWillClose(CastDialogView* dialog_view) override;
CastDialogSinkButton* GetSinkButton(const MediaSink::Id& sink_id) const;
// Registers itself as an observer to the dialog, and waits until an event
// of |watch_type| is observed. |sink_id| should be set only if observing for
// a sink.
void ObserveDialog(WatchType watch_type,
base::Optional<MediaSink::Id> sink_id = base::nullopt);
content::WebContents* web_contents_;
MediaRouterDialogControllerViews* dialog_controller_;
base::Optional<MediaSink::Id> watch_sink_id_;
base::Optional<base::OnceClosure> watch_callback_;
WatchType watch_type_ = WatchType::kNone;
DISALLOW_COPY_AND_ASSIGN(MediaRouterUiForTest);
};
} // namespace media_router
#endif // CHROME_TEST_MEDIA_ROUTER_MEDIA_ROUTER_UI_FOR_TEST_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment