From 09911bf300f1a419907a9412154760efd0b7abc3 Mon Sep 17 00:00:00 2001 From: "initial.commit" Date: Sat, 26 Jul 2008 23:55:29 +0000 Subject: Add chrome to the repository. git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15 0039d316-1c4b-4281-b951-d872f2087c98 --- chrome/views/view_unittest.cc | 534 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 chrome/views/view_unittest.cc (limited to 'chrome/views/view_unittest.cc') diff --git a/chrome/views/view_unittest.cc b/chrome/views/view_unittest.cc new file mode 100644 index 0000000..0cb5af1 --- /dev/null +++ b/chrome/views/view_unittest.cc @@ -0,0 +1,534 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include + +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/views/background.h" +#include "chrome/views/event.h" +#include "chrome/views/view.h" +#include "chrome/views/window.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class ViewTest : public testing::Test { + public: + ViewTest() { + OleInitialize(NULL); + } + + ~ViewTest() { + OleUninitialize(); + } +}; + +// Paints the RootView. +void PaintRootView(ChromeViews::RootView* root, bool empty_paint) { + if (!empty_paint) { + root->PaintNow(); + } else { + // User isn't logged in, so that PaintNow will generate an empty rectangle. + // Invoke paint directly. + CRect paint_rect = root->GetScheduledPaintRect(); + ChromeCanvas canvas(paint_rect.Width(), paint_rect.Height(), true); + canvas.TranslateInt(-paint_rect.left, -paint_rect.top); + canvas.ClipRectInt(0, 0, paint_rect.Width(), paint_rect.Height()); + root->ProcessPaint(&canvas); + } +} + +/* +typedef CWinTraits CVTWTraits; + +// A trivial window implementation that tracks whether or not it has been +// painted. This is used by the painting test to determine if paint will result +// in an empty region. +class EmptyWindow : public CWindowImpl { + public: + DECLARE_FRAME_WND_CLASS(L"Chrome_ChromeViewsEmptyWindow", 0) + + BEGIN_MSG_MAP_EX(EmptyWindow) + MSG_WM_PAINT(OnPaint) + END_MSG_MAP() + + EmptyWindow::EmptyWindow(const CRect& bounds) : empty_paint_(false) { + Create(NULL, static_cast(bounds)); + ShowWindow(SW_SHOW); + } + + EmptyWindow::~EmptyWindow() { + ShowWindow(SW_HIDE); + DestroyWindow(); + } + + void EmptyWindow::OnPaint(HDC dc) { + PAINTSTRUCT ps; + HDC paint_dc = BeginPaint(&ps); + if (!empty_paint_ && (ps.rcPaint.top - ps.rcPaint.bottom) == 0 && + (ps.rcPaint.right - ps.rcPaint.left) == 0) { + empty_paint_ = true; + } + EndPaint(&ps); + } + + bool empty_paint() { + return empty_paint_; + } + + private: + bool empty_paint_; + + DISALLOW_EVIL_CONSTRUCTORS(EmptyWindow); +}; +*/ +} + +using namespace ChromeViews; + +//////////////////////////////////////////////////////////////////////////////// +// +// A view subclass for testing purpose +// +//////////////////////////////////////////////////////////////////////////////// +class TestView : public View { + public: + TestView() { + } + + virtual ~TestView() {} + + // Reset all test state + void Reset() { + did_change_bounds_ = false; + child_added_ = false; + child_removed_ = false; + last_mouse_event_type_ = 0; + location_.x = 0; + location_.y = 0; + last_clip_.setEmpty(); + } + + virtual void DidChangeBounds(const CRect& previous, const CRect& current); + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + virtual bool OnMousePressed(const MouseEvent& event); + virtual bool OnMouseDragged(const MouseEvent& event); + virtual void OnMouseReleased(const MouseEvent& event, bool canceled); + virtual void Paint(ChromeCanvas* canvas); + + // DidChangeBounds test + bool did_change_bounds_; + CRect previous_bounds_; + CRect new_bounds_; + + // AddRemoveNotifications test + bool child_added_; + bool child_removed_; + View* parent_; + View* child_; + + // MouseEvent + int last_mouse_event_type_; + CPoint location_; + + // Painting + SkRect last_clip_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// DidChangeBounds +//////////////////////////////////////////////////////////////////////////////// + +void TestView::DidChangeBounds(const CRect& previous, const CRect& current) { + did_change_bounds_ = true; + previous_bounds_ = previous; + new_bounds_ = current; +} + +TEST_F(ViewTest, DidChangeBounds) { + TestView* v = new TestView(); + + CRect prev_rect(0, 0, 200, 200); + CRect new_rect(100, 100, 250, 250); + + v->SetBounds(prev_rect); + v->Reset(); + + v->SetBounds(new_rect); + EXPECT_EQ(v->did_change_bounds_, true); + EXPECT_EQ(v->previous_bounds_, prev_rect); + EXPECT_EQ(v->new_bounds_, new_rect); + + CRect r; + v->GetBounds(&r); + EXPECT_EQ(r, new_rect); + delete v; +} + + +//////////////////////////////////////////////////////////////////////////////// +// AddRemoveNotifications +//////////////////////////////////////////////////////////////////////////////// + +void TestView::ViewHierarchyChanged(bool is_add, View *parent, View *child) { + if (is_add) { + child_added_ = true; + } else { + child_removed_ = true; + } + parent_ = parent; + child_ = child; +} + +TEST_F(ViewTest, AddRemoveNotifications) { + TestView* v1 = new TestView(); + v1->SetBounds(0, 0, 300, 300); + + TestView* v2 = new TestView(); + v2->SetBounds(0, 0, 300, 300); + + TestView* v3 = new TestView(); + v3->SetBounds(0, 0, 300, 300); + + // Add a child. Make sure both v2 and v3 receive the right + // notification + v2->Reset(); + v3->Reset(); + v2->AddChildView(v3); + EXPECT_EQ(v2->child_added_, true); + EXPECT_EQ(v2->parent_, v2); + EXPECT_EQ(v2->child_, v3); + + EXPECT_EQ(v3->child_added_, true); + EXPECT_EQ(v3->parent_, v2); + EXPECT_EQ(v3->child_, v3); + + // Add v2 and transitively v3 to v1. Make sure that all views + // received the right notification + + v1->Reset(); + v2->Reset(); + v3->Reset(); + v1->AddChildView(v2); + + EXPECT_EQ(v1->child_added_, true); + EXPECT_EQ(v1->child_, v2); + EXPECT_EQ(v1->parent_, v1); + + EXPECT_EQ(v2->child_added_, true); + EXPECT_EQ(v2->child_, v2); + EXPECT_EQ(v2->parent_, v1); + + EXPECT_EQ(v3->child_added_, true); + EXPECT_EQ(v3->child_, v2); + EXPECT_EQ(v3->parent_, v1); + + // Remove v2. Make sure all views received the right notification + v1->Reset(); + v2->Reset(); + v3->Reset(); + v1->RemoveChildView(v2); + + EXPECT_EQ(v1->child_removed_, true); + EXPECT_EQ(v1->parent_, v1); + EXPECT_EQ(v1->child_, v2); + + EXPECT_EQ(v2->child_removed_, true); + EXPECT_EQ(v2->parent_, v1); + EXPECT_EQ(v2->child_, v2); + + EXPECT_EQ(v3->child_removed_, true); + EXPECT_EQ(v3->parent_, v1); + EXPECT_EQ(v3->child_, v3); + + // Clean-up + delete v1; + delete v2; // This also deletes v3 (child of v2). +} + +//////////////////////////////////////////////////////////////////////////////// +// MouseEvent +//////////////////////////////////////////////////////////////////////////////// + +bool TestView::OnMousePressed(const MouseEvent& event) { + last_mouse_event_type_ = event.GetType(); + location_.x = event.GetX(); + location_.y = event.GetY(); + return true; +} + +bool TestView::OnMouseDragged(const MouseEvent& event) { + last_mouse_event_type_ = event.GetType(); + location_.x = event.GetX(); + location_.y = event.GetY(); + return true; +} + +void TestView::OnMouseReleased(const MouseEvent& event, bool canceled) { + last_mouse_event_type_ = event.GetType(); + location_.x = event.GetX(); + location_.y = event.GetY(); +} + +TEST_F(ViewTest, MouseEvent) { + TestView* v1 = new TestView(); + v1->SetBounds(0, 0, 300, 300); + + TestView* v2 = new TestView(); + v2->SetBounds (100, 100, 100, 100); + + ChromeViews::Window window; + window.set_delete_on_destroy(false); + window.set_window_style(WS_OVERLAPPEDWINDOW); + window.Init(NULL, gfx::Rect(50, 50, 650, 650), NULL, NULL); + RootView* root = window.GetRootView(); + + root->AddChildView(v1); + v1->AddChildView(v2); + + v1->Reset(); + v2->Reset(); + + MouseEvent pressed(Event::ET_MOUSE_PRESSED, + 110, + 120, + Event::EF_LEFT_BUTTON_DOWN); + root->OnMousePressed(pressed); + EXPECT_EQ(v2->last_mouse_event_type_, Event::ET_MOUSE_PRESSED); + EXPECT_EQ(v2->location_.x, 10); + EXPECT_EQ(v2->location_.y, 20); + // Make sure v1 did not receive the event + EXPECT_EQ(v1->last_mouse_event_type_, 0); + + // Drag event out of bounds. Should still go to v2 + v1->Reset(); + v2->Reset(); + MouseEvent dragged(Event::ET_MOUSE_DRAGGED, + 50, + 40, + Event::EF_LEFT_BUTTON_DOWN); + root->OnMouseDragged(dragged); + EXPECT_EQ(v2->last_mouse_event_type_, Event::ET_MOUSE_DRAGGED); + EXPECT_EQ(v2->location_.x, -50); + EXPECT_EQ(v2->location_.y, -60); + // Make sure v1 did not receive the event + EXPECT_EQ(v1->last_mouse_event_type_, 0); + + // Releasted event out of bounds. Should still go to v2 + v1->Reset(); + v2->Reset(); + MouseEvent released(Event::ET_MOUSE_RELEASED, 0, 0, 0); + root->OnMouseDragged(released); + EXPECT_EQ(v2->last_mouse_event_type_, Event::ET_MOUSE_RELEASED); + EXPECT_EQ(v2->location_.x, -100); + EXPECT_EQ(v2->location_.y, -100); + // Make sure v1 did not receive the event + EXPECT_EQ(v1->last_mouse_event_type_, 0); + + window.CloseNow(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Painting +//////////////////////////////////////////////////////////////////////////////// + +void TestView::Paint(ChromeCanvas* canvas) { + canvas->getClipBounds(&last_clip_); +} + +void CheckRect(const SkRect& check_rect, const SkRect& target_rect) { + EXPECT_EQ(target_rect.fLeft, check_rect.fLeft); + EXPECT_EQ(target_rect.fRight, check_rect.fRight); + EXPECT_EQ(target_rect.fTop, check_rect.fTop); + EXPECT_EQ(target_rect.fBottom, check_rect.fBottom); +} + +/* This test is disabled because it is flakey on some systems. +TEST_F(ViewTest, Painting) { + // Determine if InvalidateRect generates an empty paint rectangle. + EmptyWindow paint_window(CRect(50, 50, 650, 650)); + paint_window.RedrawWindow(CRect(0, 0, 600, 600), NULL, + RDW_UPDATENOW | RDW_INVALIDATE | RDW_ALLCHILDREN); + bool empty_paint = paint_window.empty_paint(); + + ChromeViews::Window window; + window.set_delete_on_destroy(false); + window.set_window_style(WS_OVERLAPPEDWINDOW); + window.Init(NULL, gfx::Rect(50, 50, 650, 650), NULL, NULL); + RootView* root = window.GetRootView(); + + TestView* v1 = new TestView(); + v1->SetBounds(0, 0, 650, 650); + root->AddChildView(v1); + + TestView* v2 = new TestView(); + v2->SetBounds(10, 10, 80, 80); + v1->AddChildView(v2); + + TestView* v3 = new TestView(); + v3->SetBounds(10, 10, 60, 60); + v2->AddChildView(v3); + + TestView* v4 = new TestView(); + v4->SetBounds(10, 200, 100, 100); + v1->AddChildView(v4); + + // Make sure to paint current rects + PaintRootView(root, empty_paint); + + + v1->Reset(); + v2->Reset(); + v3->Reset(); + v4->Reset(); + v3->SchedulePaint(10, 10, 10, 10); + PaintRootView(root, empty_paint); + + SkRect tmp_rect; + + tmp_rect.set(SkIntToScalar(10), + SkIntToScalar(10), + SkIntToScalar(20), + SkIntToScalar(20)); + CheckRect(v3->last_clip_, tmp_rect); + + tmp_rect.set(SkIntToScalar(20), + SkIntToScalar(20), + SkIntToScalar(30), + SkIntToScalar(30)); + CheckRect(v2->last_clip_, tmp_rect); + + tmp_rect.set(SkIntToScalar(30), + SkIntToScalar(30), + SkIntToScalar(40), + SkIntToScalar(40)); + CheckRect(v1->last_clip_, tmp_rect); + + // Make sure v4 was not painted + tmp_rect.setEmpty(); + CheckRect(v4->last_clip_, tmp_rect); + + window.DestroyWindow(); +} +*/ +typedef std::vector ViewList; + +class RemoveViewObserver : public NotificationObserver { +public: + RemoveViewObserver() { } + + void Observe(NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + ASSERT_TRUE(type == NOTIFY_VIEW_REMOVED); + removed_views_.push_back(Source(source).ptr()); + } + + bool WasRemoved(ChromeViews::View* view) { + return std::find(removed_views_.begin(), removed_views_.end(), view) != + removed_views_.end(); + } + + ViewList removed_views_; + +}; + +TEST_F(ViewTest, RemoveNotification) { + scoped_ptr observer(new RemoveViewObserver); + + NotificationService::current()->AddObserver( + observer.get(), NOTIFY_VIEW_REMOVED, NotificationService::AllSources()); + + ChromeViews::Window* window = new ChromeViews::Window; + ChromeViews::RootView* root_view = window->GetRootView(); + + View* v1 = new View; + root_view->AddChildView(v1); + View* v11 = new View; + v1->AddChildView(v11); + View* v111 = new View; + v11->AddChildView(v111); + View* v112 = new View; + v11->AddChildView(v112); + View* v113 = new View; + v11->AddChildView(v113); + View* v1131 = new View; + v113->AddChildView(v1131); + View* v12 = new View; + v1->AddChildView(v12); + + View* v2 = new View; + root_view->AddChildView(v2); + View* v21 = new View; + v2->AddChildView(v21); + View* v211 = new View; + v21->AddChildView(v211); + + // Try removing a leaf view. + v21->RemoveChildView(v211); + EXPECT_EQ(1, observer->removed_views_.size()); + EXPECT_TRUE(observer->WasRemoved(v211)); + delete v211; // We won't use this one anymore. + + // Now try removing a view with a hierarchy of depth 1. + observer->removed_views_.clear(); + v11->RemoveChildView(v113); + EXPECT_EQ(observer->removed_views_.size(), 2); + EXPECT_TRUE(observer->WasRemoved(v113) && observer->WasRemoved(v1131)); + delete v113; // We won't use this one anymore. + + // Now remove even more. + observer->removed_views_.clear(); + root_view->RemoveChildView(v1); + EXPECT_EQ(observer->removed_views_.size(), 5); + EXPECT_TRUE(observer->WasRemoved(v1) && + observer->WasRemoved(v11) && observer->WasRemoved(v12) && + observer->WasRemoved(v111) && observer->WasRemoved(v112)); + + // Put v1 back for more tests. + root_view->AddChildView(v1); + observer->removed_views_.clear(); + + // Now delete the root view (deleting the window will trigger a delete of the + // RootView) and make sure we are notified that the views were removed. + delete window; + EXPECT_EQ(observer->removed_views_.size(), 7); + EXPECT_TRUE(observer->WasRemoved(v1) && observer->WasRemoved(v2) && + observer->WasRemoved(v11) && observer->WasRemoved(v12) && + observer->WasRemoved(v21) && + observer->WasRemoved(v111) && observer->WasRemoved(v112)); + + NotificationService::current()->RemoveObserver(observer.get(), + NOTIFY_VIEW_REMOVED, NotificationService::AllSources()); +} -- cgit v1.1