diff options
author | johnnyg@chromium.org <johnnyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-11 17:40:31 +0000 |
---|---|---|
committer | johnnyg@chromium.org <johnnyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-11 17:40:31 +0000 |
commit | 8b7135d6aa44d68307bfb7ff5e6441fcf1c5231b (patch) | |
tree | 39f24246b28900218dded1cfe462ebe400ee3a6d /chrome/browser/notifications | |
parent | 4a3dab23cadf0cbe68bd82c06fd970ae8b1d7e10 (diff) | |
download | chromium_src-8b7135d6aa44d68307bfb7ff5e6441fcf1c5231b.zip chromium_src-8b7135d6aa44d68307bfb7ff5e6441fcf1c5231b.tar.gz chromium_src-8b7135d6aa44d68307bfb7ff5e6441fcf1c5231b.tar.bz2 |
Unit tests for desktop notifications.
* Allow non-windows BalloonCollections to create non-viewable balloons (so that non-viewing aspects are cross-platform testable)
* Makes NotificationObjectProxy overridable by a mock object which logs instead of calls to JS
* Move Layout code from private to protected in BalloonCollection to enable verification of position code.
BUG=none
TEST=these
Review URL: http://codereview.chromium.org/371041
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@31670 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/notifications')
6 files changed, 414 insertions, 29 deletions
diff --git a/chrome/browser/notifications/balloon_collection.h b/chrome/browser/notifications/balloon_collection.h index 8241f19..918f993 100644 --- a/chrome/browser/notifications/balloon_collection.h +++ b/chrome/browser/notifications/balloon_collection.h @@ -70,18 +70,6 @@ class BalloonCollectionImpl : public BalloonCollection, virtual void OnBalloonClosed(Balloon* source); protected: - // Creates a new balloon. Overridable by unit tests. The caller is - // responsible for freeing the pointer returned. - virtual Balloon* MakeBalloon(const Notification& notification, - Profile* profile); - - private: - // The number of balloons being displayed. - int count() const { return balloons_.size(); } - - // Adjusts the positions of the balloons (e.g., when one is closed). - void PositionBalloons(bool is_reposition); - // Calculates layout values for the balloons including // the scaling, the max/min sizes, and the upper left corner of each. class Layout { @@ -92,10 +80,10 @@ class BalloonCollectionImpl : public BalloonCollection, void OnDisplaySettingsChanged(); // TODO(johnnyg): Scale the size to account for the system font factor. - int min_balloon_width() const { return kBalloonMinWidth; } - int max_balloon_width() const { return kBalloonMaxWidth; } - int min_balloon_height() const { return kBalloonMinHeight; } - int max_balloon_height() const { return kBalloonMaxHeight; } + static int min_balloon_width() { return kBalloonMinWidth; } + static int max_balloon_width() { return kBalloonMaxWidth; } + static int min_balloon_height() { return kBalloonMinHeight; } + static int max_balloon_height() { return kBalloonMaxHeight; } // Returns both the total space available and the maximum // allowed per balloon. @@ -140,6 +128,18 @@ class BalloonCollectionImpl : public BalloonCollection, DISALLOW_COPY_AND_ASSIGN(Layout); }; + // Creates a new balloon. Overridable by unit tests. The caller is + // responsible for freeing the pointer returned. + virtual Balloon* MakeBalloon(const Notification& notification, + Profile* profile); + + private: + // The number of balloons being displayed. + int count() const { return balloons_.size(); } + + // Adjusts the positions of the balloons (e.g., when one is closed). + void PositionBalloons(bool is_reposition); + // Non-owned pointer to an object listening for space changes. BalloonSpaceChangeListener* space_change_listener_; diff --git a/chrome/browser/notifications/balloon_collection_linux.cc b/chrome/browser/notifications/balloon_collection_linux.cc index 49ed818..e1439f2 100644 --- a/chrome/browser/notifications/balloon_collection_linux.cc +++ b/chrome/browser/notifications/balloon_collection_linux.cc @@ -4,14 +4,12 @@ #include "chrome/browser/notifications/balloon_collection.h" -#include "base/gfx/size.h" #include "base/logging.h" Balloon* BalloonCollectionImpl::MakeBalloon(const Notification& notification, Profile* profile) { - // TODO(johnnyg): http://crbug.com/23954. Part of future Linux support. - NOTIMPLEMENTED(); - return NULL; + // TODO(johnnyg): http://crbug.com/23954. Hook up to views. + return new Balloon(notification, profile, this); } bool BalloonCollectionImpl::Layout::RefreshSystemMetrics() { diff --git a/chrome/browser/notifications/balloon_collection_mac.mm b/chrome/browser/notifications/balloon_collection_mac.mm index 6197b49..2bff15e 100644 --- a/chrome/browser/notifications/balloon_collection_mac.mm +++ b/chrome/browser/notifications/balloon_collection_mac.mm @@ -4,14 +4,12 @@ #include "chrome/browser/notifications/balloon_collection.h" -#include "base/gfx/size.h" #include "base/logging.h" Balloon* BalloonCollectionImpl::MakeBalloon(const Notification& notification, Profile* profile) { - // TODO(johnnyg): http://crbug.com/23066. Part of future Mac support. - NOTIMPLEMENTED(); - return NULL; + // TODO(johnnyg): http://crbug.com/23066. Hook up to views. + return new Balloon(notification, profile, this); } bool BalloonCollectionImpl::Layout::RefreshSystemMetrics() { diff --git a/chrome/browser/notifications/desktop_notifications_unittest.cc b/chrome/browser/notifications/desktop_notifications_unittest.cc new file mode 100644 index 0000000..819e70f --- /dev/null +++ b/chrome/browser/notifications/desktop_notifications_unittest.cc @@ -0,0 +1,250 @@ +// Copyright (c) 2009 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/browser/notifications/desktop_notifications_unittest.h" + +// static +const int MockBalloonCollection::kMockBalloonSpace = 5; + +// static +std::string DesktopNotificationsTest::log_output_; + +void LoggingNotificationProxy::Display() { + DesktopNotificationsTest::log("notification displayed\n"); +} + +void LoggingNotificationProxy::Error() { + DesktopNotificationsTest::log("notification error\n"); +} + +void LoggingNotificationProxy::Close(bool by_user) { + if (by_user) + DesktopNotificationsTest::log("notification closed by user\n"); + else + DesktopNotificationsTest::log("notification closed by script\n"); +} + +void MockBalloonCollection::Add(const Notification& notification, + Profile* profile) { + // Swap in the logging proxy for the purpose of logging calls that + // would be made into javascript, then pass this down to the + // balloon collection. + Notification test_notification(notification.origin_url(), + notification.content_url(), + log_proxy_.get()); + BalloonCollectionImpl::Add(test_notification, profile); +} + +bool MockBalloonCollection::Remove(const Notification& notification) { + Notification test_notification(notification.origin_url(), + notification.content_url(), + log_proxy_.get()); + return BalloonCollectionImpl::Remove(test_notification); +} + +Balloon* MockBalloonCollection::MakeBalloon(const Notification& notification, + Profile* profile) { + // Start with a normal balloon but mock out the view. + Balloon* balloon = BalloonCollectionImpl::MakeBalloon(notification, profile); + balloon->set_view(new MockBalloonView(balloon)); + balloons_.insert(balloon); + return balloon; +} + +void MockBalloonCollection::OnBalloonClosed(Balloon* source) { + balloons_.erase(source); + BalloonCollectionImpl::OnBalloonClosed(source); +} + +int MockBalloonCollection::UppermostVerticalPosition() { + int min = 0; + std::set<Balloon*>::iterator iter; + for (iter = balloons_.begin(); iter != balloons_.end(); ++iter) { + int pos = (*iter)->position().y(); + if (iter == balloons_.begin() || pos < min) + min = pos; + } + return min; +} + +DesktopNotificationsTest::DesktopNotificationsTest() : + ui_thread_(ChromeThread::UI, &message_loop_) { +} + +DesktopNotificationsTest::~DesktopNotificationsTest() { +} + +void DesktopNotificationsTest::SetUp() { + profile_.reset(new TestingProfile()); + balloon_collection_ = new MockBalloonCollection(); + ui_manager_.reset(new NotificationUIManager()); + ui_manager_->Initialize(balloon_collection_); + balloon_collection_->set_space_change_listener(ui_manager_.get()); + service_.reset(new DesktopNotificationService(profile(), ui_manager_.get())); + log_output_.clear(); +} + +void DesktopNotificationsTest::TearDown() { + profile_.reset(NULL); + ui_manager_.reset(NULL); + service_.reset(NULL); +} + +TEST_F(DesktopNotificationsTest, TestShow) { + EXPECT_TRUE(service_->ShowDesktopNotificationText( + GURL("http://www.google.com"), + GURL("/icon.png"), ASCIIToUTF16("Title"), ASCIIToUTF16("Text"), + 0, 0, DesktopNotificationService::PageNotification, 1)); + MessageLoopForUI::current()->RunAllPending(); + EXPECT_EQ(1, balloon_collection_->count()); + + EXPECT_TRUE(service_->ShowDesktopNotification( + GURL("http://www.google.com"), + GURL("http://www.google.com/notification.html"), + 0, 0, DesktopNotificationService::PageNotification, 2)); + MessageLoopForUI::current()->RunAllPending(); + EXPECT_EQ(2, balloon_collection_->count()); + + EXPECT_EQ("notification displayed\n" + "notification displayed\n", + log_output_); +} + +TEST_F(DesktopNotificationsTest, TestClose) { + // Request a notification; should open a balloon. + EXPECT_TRUE(service_->ShowDesktopNotificationText( + GURL("http://www.google.com"), + GURL("/icon.png"), ASCIIToUTF16("Title"), ASCIIToUTF16("Text"), + 0, 0, DesktopNotificationService::PageNotification, 1)); + MessageLoopForUI::current()->RunAllPending(); + EXPECT_EQ(1, balloon_collection_->count()); + + // Close all the open balloons. + std::set<Balloon*> balloons = balloon_collection_->balloons(); + std::set<Balloon*>::iterator iter; + for (iter = balloons.begin(); iter != balloons.end(); ++iter) { + (*iter)->OnClose(true); + } + + // Verify that the balloon collection is now empty. + EXPECT_EQ(0, balloon_collection_->count()); + + EXPECT_EQ("notification displayed\n" + "notification closed by user\n", + log_output_); +} + +TEST_F(DesktopNotificationsTest, TestCancel) { + int process_id = 0; + int route_id = 0; + int notification_id = 1; + // Request a notification; should open a balloon. + EXPECT_TRUE(service_->ShowDesktopNotificationText( + GURL("http://www.google.com"), + GURL("/icon.png"), ASCIIToUTF16("Title"), ASCIIToUTF16("Text"), + process_id, route_id, DesktopNotificationService::PageNotification, + notification_id)); + MessageLoopForUI::current()->RunAllPending(); + EXPECT_EQ(1, balloon_collection_->count()); + + // Cancel the same notification + service_->CancelDesktopNotification(process_id, + route_id, + notification_id); + MessageLoopForUI::current()->RunAllPending(); + // Verify that the balloon collection is now empty. + EXPECT_EQ(0, balloon_collection_->count()); + + EXPECT_EQ("notification displayed\n" + "notification closed by script\n", + log_output_); +} + +#if defined(OS_WIN) +TEST_F(DesktopNotificationsTest, TestPositioning) { + std::string expected_log; + // Create some toasts. After each but the first, make sure there + // is a minimum separation between the toasts. + int last_top = 0; + for (int id = 0; id <= 3; ++id) { + EXPECT_TRUE(service_->ShowDesktopNotificationText( + GURL("http://www.google.com"), + GURL("/icon.png"), ASCIIToUTF16("Title"), ASCIIToUTF16("Text"), + 0, 0, DesktopNotificationService::PageNotification, id)); + expected_log.append("notification displayed\n"); + int top = balloon_collection_->UppermostVerticalPosition(); + if (id > 0) + EXPECT_LE(top, last_top - balloon_collection_->MinHeight()); + last_top = top; + } + + EXPECT_EQ(expected_log, log_output_); +} +#endif + +TEST_F(DesktopNotificationsTest, TestQueueing) { + int process_id = 0; + int route_id = 0; + + // Request lots of identical notifications. + const int kLotsOfToasts = 20; + for (int id = 1; id <= kLotsOfToasts; ++id) { + EXPECT_TRUE(service_->ShowDesktopNotificationText( + GURL("http://www.google.com"), + GURL("/icon.png"), ASCIIToUTF16("Title"), ASCIIToUTF16("Text"), + process_id, route_id, + DesktopNotificationService::PageNotification, id)); + } + MessageLoopForUI::current()->RunAllPending(); + + // Build up an expected log of what should be happening. + std::string expected_log; + for (int i = 0; i < balloon_collection_->max_balloon_count(); ++i) { + expected_log.append("notification displayed\n"); + } + + // The max number that our balloon collection can hold should be + // shown. + EXPECT_EQ(balloon_collection_->max_balloon_count(), + balloon_collection_->count()); + EXPECT_EQ(expected_log, log_output_); + + // Cancel the notifications from the start; the balloon space should + // remain full. + int id; + for (id = 1; + id <= kLotsOfToasts - balloon_collection_->max_balloon_count(); + ++id) { + service_->CancelDesktopNotification(process_id, route_id, id); + MessageLoopForUI::current()->RunAllPending(); + expected_log.append("notification closed by script\n"); + expected_log.append("notification displayed\n"); + EXPECT_EQ(balloon_collection_->max_balloon_count(), + balloon_collection_->count()); + EXPECT_EQ(expected_log, log_output_); + } + + // Now cancel the rest. It should empty the balloon space. + for (; id <= kLotsOfToasts; ++id) { + service_->CancelDesktopNotification(process_id, route_id, id); + expected_log.append("notification closed by script\n"); + MessageLoopForUI::current()->RunAllPending(); + EXPECT_EQ(expected_log, log_output_); + } + + // Verify that the balloon collection is now empty. + EXPECT_EQ(0, balloon_collection_->count()); +} + +TEST_F(DesktopNotificationsTest, TestEarlyDestruction) { + // Create some toasts and then prematurely delete the notification service, + // just to make sure nothing crashes/leaks. + for (int id = 0; id <= 3; ++id) { + EXPECT_TRUE(service_->ShowDesktopNotificationText( + GURL("http://www.google.com"), + GURL("/icon.png"), ASCIIToUTF16("Title"), ASCIIToUTF16("Text"), + 0, 0, DesktopNotificationService::PageNotification, id)); + } + service_.reset(NULL); +} diff --git a/chrome/browser/notifications/desktop_notifications_unittest.h b/chrome/browser/notifications/desktop_notifications_unittest.h new file mode 100644 index 0000000..91c65c8 --- /dev/null +++ b/chrome/browser/notifications/desktop_notifications_unittest.h @@ -0,0 +1,138 @@ +// Copyright (c) 2009 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_BROWSER_NOTIFICATIONS_DESKTOP_NOTIFICATIONS_UNITTEST_H_ +#define CHROME_BROWSER_NOTIFICATIONS_DESKTOP_NOTIFICATIONS_UNITTEST_H_ + +#include <set> +#include <string> + +#include "base/message_loop.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/notifications/balloon.h" +#include "chrome/browser/notifications/balloon_collection.h" +#include "chrome/browser/notifications/desktop_notification_service.h" +#include "chrome/browser/notifications/notification.h" +#include "chrome/browser/notifications/notification_object_proxy.h" +#include "chrome/browser/notifications/notification_ui_manager.h" +#include "chrome/browser/notifications/notifications_prefs_cache.h" +#include "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/api/public/WebNotificationPresenter.h" + +// Mock implementation of Javascript object proxy which logs events that +// would have been fired on it. +class LoggingNotificationProxy : public NotificationObjectProxy { + public: + LoggingNotificationProxy() : + NotificationObjectProxy(0, 0, 0, false) {} + + // NotificationObjectProxy override + virtual void Display(); + virtual void Error(); + virtual void Close(bool by_user); +}; + +// Test version of a balloon view which doesn't do anything +// viewable, but does know how to close itself the same as a regular +// BalloonView. +class MockBalloonView : public BalloonView { + public: + explicit MockBalloonView(Balloon * balloon) : + balloon_(balloon) {} + void Show(Balloon* balloon) {} + void RepositionToBalloon() {} + void Close(bool by_user) { balloon_->OnClose(by_user); } + + private: + // Non-owned pointer. + Balloon* balloon_; +}; + +// Test version of the balloon collection which counts the number +// of notifications that are added to it. +class MockBalloonCollection : public BalloonCollectionImpl { + public: + MockBalloonCollection() : + log_proxy_(new LoggingNotificationProxy()) {} + + // Our mock collection has an area large enough for a fixed number + // of balloons. + static const int kMockBalloonSpace; + int max_balloon_count() const { return kMockBalloonSpace; } + + // BalloonCollectionImpl overrides + virtual void Add(const Notification& notification, + Profile* profile); + virtual bool Remove(const Notification& notification); + virtual bool HasSpace() const { return count() < kMockBalloonSpace; } + virtual Balloon* MakeBalloon(const Notification& notification, + Profile* profile); + virtual void OnBalloonClosed(Balloon* source); + + // Number of balloons being shown. + std::set<Balloon*>& balloons() { return balloons_; } + int count() const { return balloons_.size(); } + + // Returns the highest y-coordinate of all the balloons in the collection. + int UppermostVerticalPosition(); + + // Returns the minimum height of a balloon. + int MinHeight() { return Layout::min_balloon_height(); } + + private: + std::set<Balloon*> balloons_; + scoped_refptr<LoggingNotificationProxy> log_proxy_; +}; + +class DesktopNotificationsTest : public testing::Test { + public: + DesktopNotificationsTest(); + ~DesktopNotificationsTest(); + + static void log(const std::string& message) { + log_output_.append(message); + } + + Profile* profile() { return profile_.get(); } + + protected: + // testing::Test overrides + virtual void SetUp(); + virtual void TearDown(); + + void AllowOrigin(const GURL& origin) { + service_->GrantPermission(origin); + } + + void DenyOrigin(const GURL& origin) { + service_->DenyPermission(origin); + } + + int HasPermission(const GURL& origin) { + return service_->prefs_cache()->HasPermission(origin); + } + + // Create a message loop to allow notifications code to post tasks, + // and a thread so that notifications code runs on the expected thread. + MessageLoopForUI message_loop_; + ChromeThread ui_thread_; + + // Test profile. + scoped_ptr<TestingProfile> profile_; + + // Mock balloon collection -- owned by the NotificationUIManager + MockBalloonCollection* balloon_collection_; + + // Real UI manager. + scoped_ptr<NotificationUIManager> ui_manager_; + + // Real DesktopNotificationService + scoped_ptr<DesktopNotificationService> service_; + + // Contains the cumulative output of the unit test. + static std::string log_output_; +}; + +#endif diff --git a/chrome/browser/notifications/notification_object_proxy.h b/chrome/browser/notifications/notification_object_proxy.h index ff71bc3..1ff17aa 100644 --- a/chrome/browser/notifications/notification_object_proxy.h +++ b/chrome/browser/notifications/notification_object_proxy.h @@ -24,15 +24,15 @@ class NotificationObjectProxy : int notification_id, bool worker); // To be called when the desktop notification is actually shown. - void Display(); + virtual void Display(); // To be called when the desktop notification cannot be shown due to an // error. - void Error(); + virtual void Error(); // To be called when the desktop notification is closed. If closed by a // user explicitly (as opposed to timeout/script), |by_user| should be true. - void Close(bool by_user); + virtual void Close(bool by_user); // Compares two proxies by ids to decide if they are equal. bool IsSame(const NotificationObjectProxy& other) const { @@ -42,11 +42,12 @@ class NotificationObjectProxy : worker_ == other.worker_); } - private: + protected: friend class base::RefCountedThreadSafe<NotificationObjectProxy>; - ~NotificationObjectProxy() {} + virtual ~NotificationObjectProxy() {} + private: // Called on UI thread to schedule a message for sending. void DeliverMessage(IPC::Message* message); |