1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "ui/message_center/cocoa/notification_controller.h"
7 #include "base/mac/foundation_util.h"
8 #include "base/mac/scoped_nsobject.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "third_party/skia/include/core/SkBitmap.h"
13 #import "ui/base/cocoa/hover_image_button.h"
14 #import "ui/gfx/test/ui_cocoa_test_helper.h"
15 #include "ui/message_center/fake_message_center.h"
16 #include "ui/message_center/message_center_style.h"
17 #include "ui/message_center/notification.h"
18 #include "ui/message_center/notification_types.h"
20 using base::ASCIIToUTF16;
21 using base::UTF8ToUTF16;
25 class MockMessageCenter : public message_center::FakeMessageCenter {
28 : last_removed_by_user_(false),
30 last_clicked_index_(-1) {}
32 void RemoveNotification(const std::string& id, bool by_user) override {
33 last_removed_id_ = id;
34 last_removed_by_user_ = by_user;
38 void ClickOnNotificationButton(const std::string& id,
39 int button_index) override {
40 last_clicked_id_ = id;
41 last_clicked_index_ = button_index;
44 const std::string& last_removed_id() const { return last_removed_id_; }
45 bool last_removed_by_user() const { return last_removed_by_user_; }
46 int remove_count() const { return remove_count_; }
47 const std::string& last_clicked_id() const { return last_clicked_id_; }
48 int last_clicked_index() const { return last_clicked_index_; }
51 std::string last_removed_id_;
52 bool last_removed_by_user_;
55 std::string last_clicked_id_;
56 int last_clicked_index_;
58 DISALLOW_COPY_AND_ASSIGN(MockMessageCenter);
63 @implementation MCNotificationController (TestingInterface)
64 - (NSButton*)closeButton {
65 return closeButton_.get();
68 - (NSImageView*)smallImageView {
69 return smallImage_.get();
72 - (NSButton*)secondButton {
73 // The buttons are in Cocoa-y-order, so the 2nd button is first.
74 NSView* view = [[bottomView_ subviews] objectAtIndex:0];
75 return base::mac::ObjCCastStrict<NSButton>(view);
78 - (NSArray*)bottomSubviews {
79 return [bottomView_ subviews];
82 - (NSImageView*)iconView {
86 - (NSTextView*)titleView {
90 - (NSTextView*)messageView {
91 return message_.get();
94 - (NSTextView*)contextMessageView {
95 return contextMessage_.get();
99 return listView_.get();
103 namespace message_center {
105 class NotificationControllerTest : public ui::CocoaTest {
107 NSImage* TestIcon() {
108 return [NSImage imageNamed:NSImageNameUser];
112 message_center::NotifierId DummyNotifierId() {
113 return message_center::NotifierId();
117 TEST_F(NotificationControllerTest, BasicLayout) {
118 scoped_ptr<message_center::Notification> notification(
119 new message_center::Notification(
120 message_center::NOTIFICATION_TYPE_SIMPLE, "",
121 ASCIIToUTF16("Added to circles"),
122 ASCIIToUTF16("Jonathan and 5 others"), gfx::Image(), base::string16(),
123 GURL(), DummyNotifierId(), message_center::RichNotificationData(),
125 gfx::Image testIcon([TestIcon() retain]);
126 notification->set_icon(testIcon);
127 notification->set_small_image(testIcon);
129 base::scoped_nsobject<MCNotificationController> controller(
130 [[MCNotificationController alloc] initWithNotification:notification.get()
131 messageCenter:NULL]);
134 EXPECT_EQ(TestIcon(), [[controller iconView] image]);
135 EXPECT_EQ(TestIcon(), [[controller smallImageView] image]);
136 EXPECT_EQ(base::SysNSStringToUTF16([[controller titleView] string]),
137 notification->title());
138 EXPECT_EQ(base::SysNSStringToUTF16([[controller messageView] string]),
139 notification->message());
140 EXPECT_EQ(controller.get(), [[controller closeButton] target]);
143 TEST_F(NotificationControllerTest, OverflowText) {
144 scoped_ptr<message_center::Notification> notification(
145 new message_center::Notification(
146 message_center::NOTIFICATION_TYPE_SIMPLE, "",
147 ASCIIToUTF16("This is a much longer title that should wrap "
149 ASCIIToUTF16("And even the message is long. This sure is a wordy "
150 "notification. Are you really going to read this "
152 gfx::Image(), base::string16(), GURL(), DummyNotifierId(),
153 message_center::RichNotificationData(), NULL));
154 base::scoped_nsobject<MCNotificationController> controller(
155 [[MCNotificationController alloc] initWithNotification:notification.get()
156 messageCenter:NULL]);
159 EXPECT_GT(NSHeight([[controller view] frame]),
160 message_center::kNotificationIconSize);
163 TEST_F(NotificationControllerTest, Close) {
164 scoped_ptr<message_center::Notification> notification(
165 new message_center::Notification(
166 message_center::NOTIFICATION_TYPE_SIMPLE, "an_id", base::string16(),
167 base::string16(), gfx::Image(), base::string16(), GURL(),
168 DummyNotifierId(), message_center::RichNotificationData(), NULL));
169 MockMessageCenter message_center;
171 base::scoped_nsobject<MCNotificationController> controller(
172 [[MCNotificationController alloc] initWithNotification:notification.get()
173 messageCenter:&message_center]);
176 [[controller closeButton] performClick:nil];
178 EXPECT_EQ(1, message_center.remove_count());
179 EXPECT_EQ("an_id", message_center.last_removed_id());
180 EXPECT_TRUE(message_center.last_removed_by_user());
183 TEST_F(NotificationControllerTest, Update) {
184 scoped_ptr<message_center::Notification> notification(
185 new message_center::Notification(
186 message_center::NOTIFICATION_TYPE_SIMPLE, "",
187 ASCIIToUTF16("A simple title"),
188 ASCIIToUTF16("This message isn't too long and should fit in the"
190 gfx::Image(), base::string16(), GURL(), DummyNotifierId(),
191 message_center::RichNotificationData(), NULL));
192 base::scoped_nsobject<MCNotificationController> controller(
193 [[MCNotificationController alloc] initWithNotification:notification.get()
194 messageCenter:NULL]);
196 // Set up the default layout.
198 EXPECT_EQ(NSHeight([[controller view] frame]),
199 message_center::kNotificationIconSize);
200 EXPECT_FALSE([[controller iconView] image]);
201 EXPECT_FALSE([[controller smallImageView] image]);
204 gfx::Image testIcon([TestIcon() retain]);
205 notification->set_icon(testIcon);
206 notification->set_small_image(testIcon);
207 [controller updateNotification:notification.get()];
208 EXPECT_EQ(TestIcon(), [[controller iconView] image]);
209 EXPECT_EQ(TestIcon(), [[controller smallImageView] image]);
210 EXPECT_EQ(NSHeight([[controller view] frame]),
211 message_center::kNotificationIconSize);
214 TEST_F(NotificationControllerTest, Buttons) {
215 message_center::RichNotificationData optional;
216 message_center::ButtonInfo button1(UTF8ToUTF16("button1"));
217 optional.buttons.push_back(button1);
218 message_center::ButtonInfo button2(UTF8ToUTF16("button2"));
219 optional.buttons.push_back(button2);
221 scoped_ptr<message_center::Notification> notification(
222 new message_center::Notification(
223 message_center::NOTIFICATION_TYPE_BASE_FORMAT, "an_id",
224 base::string16(), base::string16(), gfx::Image(), base::string16(),
225 GURL(), DummyNotifierId(), optional, NULL));
226 MockMessageCenter message_center;
228 base::scoped_nsobject<MCNotificationController> controller(
229 [[MCNotificationController alloc] initWithNotification:notification.get()
230 messageCenter:&message_center]);
233 [[controller secondButton] performClick:nil];
235 EXPECT_EQ("an_id", message_center.last_clicked_id());
236 EXPECT_EQ(1, message_center.last_clicked_index());
239 TEST_F(NotificationControllerTest, Image) {
240 scoped_ptr<message_center::Notification> notification(
241 new message_center::Notification(
242 message_center::NOTIFICATION_TYPE_BASE_FORMAT, "an_id",
243 base::string16(), base::string16(), gfx::Image(), base::string16(),
244 GURL(), DummyNotifierId(), message_center::RichNotificationData(),
246 NSImage* image = [NSImage imageNamed:NSImageNameFolder];
247 notification->set_image(gfx::Image([image retain]));
249 MockMessageCenter message_center;
251 base::scoped_nsobject<MCNotificationController> controller(
252 [[MCNotificationController alloc] initWithNotification:notification.get()
253 messageCenter:&message_center]);
256 ASSERT_EQ(1u, [[controller bottomSubviews] count]);
257 ASSERT_TRUE([[[[controller bottomSubviews] lastObject] contentView]
258 isKindOfClass:[NSImageView class]]);
260 [[[[controller bottomSubviews] lastObject] contentView] image]);
263 TEST_F(NotificationControllerTest, List) {
264 message_center::RichNotificationData optional;
265 message_center::NotificationItem item1(
266 UTF8ToUTF16("First title"), UTF8ToUTF16("first message"));
267 optional.items.push_back(item1);
268 message_center::NotificationItem item2(
269 UTF8ToUTF16("Second title"),
270 UTF8ToUTF16("second slightly longer message"));
271 optional.items.push_back(item2);
272 message_center::NotificationItem item3(
273 UTF8ToUTF16(""), // Test for empty string.
274 UTF8ToUTF16(" ")); // Test for string containing only spaces.
275 optional.items.push_back(item3);
276 optional.context_message = UTF8ToUTF16("Context Message");
278 scoped_ptr<message_center::Notification> notification(
279 new message_center::Notification(
280 message_center::NOTIFICATION_TYPE_BASE_FORMAT, "an_id",
281 UTF8ToUTF16("Notification Title"),
282 UTF8ToUTF16("Notification Message - should be hidden"), gfx::Image(),
283 base::string16(), GURL(), DummyNotifierId(), optional, NULL));
285 MockMessageCenter message_center;
286 base::scoped_nsobject<MCNotificationController> controller(
287 [[MCNotificationController alloc] initWithNotification:notification.get()
288 messageCenter:&message_center]);
291 EXPECT_FALSE([[controller titleView] isHidden]);
292 EXPECT_TRUE([[controller messageView] isHidden]);
293 EXPECT_FALSE([[controller contextMessageView] isHidden]);
295 EXPECT_EQ(3u, [[[controller listView] subviews] count]);
296 EXPECT_LT(NSMaxY([[controller listView] frame]),
297 NSMinY([[controller titleView] frame]));
300 TEST_F(NotificationControllerTest, NoMessage) {
301 message_center::RichNotificationData optional;
302 optional.context_message = UTF8ToUTF16("Context Message");
304 scoped_ptr<message_center::Notification> notification(
305 new message_center::Notification(
306 message_center::NOTIFICATION_TYPE_BASE_FORMAT, "an_id",
307 UTF8ToUTF16("Notification Title"), UTF8ToUTF16(""), gfx::Image(),
308 base::string16(), GURL(), DummyNotifierId(), optional, NULL));
310 MockMessageCenter message_center;
311 base::scoped_nsobject<MCNotificationController> controller(
312 [[MCNotificationController alloc] initWithNotification:notification.get()
313 messageCenter:&message_center]);
316 EXPECT_FALSE([[controller titleView] isHidden]);
317 EXPECT_TRUE([[controller messageView] isHidden]);
318 EXPECT_FALSE([[controller contextMessageView] isHidden]);
321 TEST_F(NotificationControllerTest, MessageSize) {
322 message_center::RichNotificationData data;
323 std::string id("id");
324 NotifierId notifier_id(NotifierId::APPLICATION, "notifier");
325 scoped_ptr<Notification> notification(new Notification(
326 NOTIFICATION_TYPE_BASE_FORMAT, id, base::UTF8ToUTF16(""),
327 ASCIIToUTF16("And\neven\nthe\nmessage is long.\nThis sure is wordy"),
328 gfx::Image(), base::string16() /* display_source */, GURL(), notifier_id,
329 data, NULL /* delegate */));
331 base::scoped_nsobject<MCNotificationController> controller(
332 [[MCNotificationController alloc] initWithNotification:notification.get()
333 messageCenter:NULL]);
335 // Set up the default layout.
338 auto compute_message_lines = ^{
339 NSString* string = [[[controller messageView] textStorage] string];
340 unsigned numberOfLines, index, stringLength = [string length];
341 for (index = 0, numberOfLines = 0; index < stringLength; numberOfLines++)
342 index = NSMaxRange([string lineRangeForRange:NSMakeRange(index, 0)]);
344 return numberOfLines;
347 // Message and no title: 5 lines.
348 EXPECT_EQ(5u, compute_message_lines());
350 // Message and one line title: 5 lines.
351 notification->set_title(ASCIIToUTF16("one line"));
352 [controller updateNotification:notification.get()];
353 EXPECT_EQ(5u, compute_message_lines());
355 // Message and two line title: 3 lines.
356 notification->set_title(ASCIIToUTF16("two\nlines"));
357 [controller updateNotification:notification.get()];
358 EXPECT_EQ(3u, compute_message_lines());
360 // Message, image and no title: 2 lines.
362 bitmap.allocN32Pixels(2, 2);
363 bitmap.eraseColor(SK_ColorGREEN);
364 notification->set_title(ASCIIToUTF16(""));
365 notification->set_image(gfx::Image::CreateFrom1xBitmap(bitmap));
366 [controller updateNotification:notification.get()];
367 EXPECT_EQ(2u, compute_message_lines());
369 // Message, image and one line title: 2 lines.
370 notification->set_title(ASCIIToUTF16("one line"));
371 [controller updateNotification:notification.get()];
372 EXPECT_EQ(2u, compute_message_lines());
374 // Message, image and two line title: 1 lines.
375 notification->set_title(ASCIIToUTF16("two\nlines"));
376 [controller updateNotification:notification.get()];
377 EXPECT_EQ(1u, compute_message_lines());
379 // Same as above, but context message takes away from message lines.
380 notification->set_context_message(UTF8ToUTF16("foo"));
381 notification->set_title(ASCIIToUTF16(""));
382 [controller updateNotification:notification.get()];
383 EXPECT_EQ(1u, compute_message_lines());
385 notification->set_title(ASCIIToUTF16("one line"));
386 [controller updateNotification:notification.get()];
387 EXPECT_EQ(1u, compute_message_lines());
389 notification->set_title(ASCIIToUTF16("two\nlines"));
390 [controller updateNotification:notification.get()];
391 EXPECT_EQ(0u, compute_message_lines());
394 } // namespace message_center