3
YUI(GlobalConfig).use(['juju-gui', 'node-event-simulate', 'juju-tests-utils'],
3
describe('notifications', function() {
4
var Y, juju, models, views;
9
'charm': 'cs:precise/wordpress-6',
14
'charm': 'cs:precise/mediawiki-3',
19
'charm': 'cs:precise/mysql-6',
23
'interface': 'reversenginx',
26
[['wordpress', {'role': 'peer', 'name': 'loadbalancer'}]],
27
'id': 'relation-0000000000'
33
[['mysql', {'role': 'server', 'name': 'db'}],
34
['wordpress', {'role': 'client', 'name': 'db'}]],
35
'id': 'relation-0000000001'
38
'agent-state': 'running',
39
'instance-state': 'running',
41
'instance-id': 'local',
42
'dns-name': 'localhost'
46
'agent-state': 'started',
47
'public-address': '192.168.122.113',
52
'agent-state': 'error',
53
'public-address': '192.168.122.222',
61
before(function(done) {
62
Y = YUI(GlobalConfig).use([
67
'node-event-simulate',
5
describe('notifications', function() {
6
var juju, models, views;
11
'charm': 'cs:precise/wordpress-6',
16
'charm': 'cs:precise/mediawiki-3',
21
'charm': 'cs:precise/mysql-6',
25
'interface': 'reversenginx',
28
[['wordpress', {'role': 'peer', 'name': 'loadbalancer'}]],
29
'id': 'relation-0000000000'
35
[['mysql', {'role': 'server', 'name': 'db'}],
36
['wordpress', {'role': 'client', 'name': 'db'}]],
37
'id': 'relation-0000000001'
40
'agent-state': 'running',
41
'instance-state': 'running',
43
'instance-id': 'local',
44
'dns-name': 'localhost'
48
'agent-state': 'started',
49
'public-address': '192.168.122.113',
54
'agent-state': 'error',
55
'public-address': '192.168.122.222',
64
juju = Y.namespace('juju');
65
models = Y.namespace('juju.models');
66
views = Y.namespace('juju.views');
69
it('must be able to make notification and lists of notifications',
71
var note1 = new models.Notification({
75
note2 = new models.Notification({
77
message: 'I said goodnight!'
79
notifications = new models.NotificationList();
81
notifications.add([note1, note2]);
82
notifications.size().should.equal(2);
84
// timestamp should be generated once
85
var ts = note1.get('timestamp');
86
note1.get('timestamp').should.equal(ts);
87
// force an update so we can test ordering
88
// fast execution can result in same timestamp
89
note2.set('timestamp', ts + 1);
90
note2.get('timestamp').should.be.above(ts);
92
// defaults as expected
93
note1.get('level').should.equal('info');
94
note2.get('level').should.equal('info');
95
// the sort order on the list should be by
97
notifications.get('title').should.eql(['test2', 'test1']);
100
it('must be able to render its view with sample data',
102
var note1 = new models.Notification({
103
title: 'test1', message: 'Hello'}),
104
note2 = new models.Notification({
105
title: 'test2', message: 'I said goodnight!'}),
106
notifications = new models.NotificationList(),
107
container = Y.Node.create('<div id="test">'),
108
env = new juju.Environment(),
109
view = new views.NotificationsView({
110
container: container,
111
notifications: notifications,
114
// Verify the expected elements appear in the view
115
container.one('#notify-list').should.not.equal(undefined);
119
it('must be able to limit the size of notification events',
121
var note1 = new models.Notification({
125
note2 = new models.Notification({
127
message: 'I said goodnight!'
129
note3 = new models.Notification({
131
message: 'Never remember'
133
notifications = new models.NotificationList({
137
notifications.add([note1, note2]);
138
notifications.size().should.equal(2);
140
// Adding a new notification should pop the oldest from the list
141
// (we exceed max_size)
142
notifications.add(note3);
143
notifications.size().should.equal(2);
144
notifications.get('title').should.eql(['test3', 'test2']);
147
it('must be able to get notifications for a given model',
149
var m = new models.Service({id: 'mediawiki'}),
150
note1 = new models.Notification({
155
note2 = new models.Notification({
157
message: 'I said goodnight!'
159
notifications = new models.NotificationList();
161
notifications.add([note1, note2]);
162
notifications.size().should.equal(2);
163
notifications.getNotificationsForModel(m).should.eql(
168
it('must be able to include and show object links', function() {
169
var container = Y.Node.create('<div id="test">'),
170
conn = new(Y.namespace('juju-tests.utils')).SocketStub(),
171
env = new juju.Environment({conn: conn}),
172
app = new Y.juju.App({env: env, container: container}),
174
mw = db.services.create({id: 'mediawiki',
176
notifications = db.notifications,
177
view = new views.NotificationsOverview({
178
container: container,
179
notifications: notifications,
182
// we use overview here for testing as it defaults
183
// to showing all notices
185
// we can use app's routing table to derive a link
186
notifications.create({title: 'Service Down',
187
message: 'Your service has an error',
188
link: app.getModelURL(mw)
191
var link = container.one('.notice').one('a');
192
link.getAttribute('href').should.equal(
193
'/service/mediawiki/');
194
link.getHTML().should.contain('View Details');
197
// create a new notice passing the link_title
198
notifications.create({title: 'Service Down',
199
message: 'Your service has an error',
200
link: app.getModelURL(mw),
201
link_title: 'Resolve this'
204
link = container.one('.notice').one('a');
205
link.getAttribute('href').should.equal(
206
'/service/mediawiki/');
207
link.getHTML().should.contain('Resolve this');
210
it('must be able to evict irrelevant notices', function() {
211
var container = Y.Node.create(
212
'<div id="test" class="container"></div>'),
213
conn = new(Y.namespace('juju-tests.utils')).SocketStub(),
214
env = new juju.Environment({conn: conn}),
215
app = new Y.juju.App({
217
container: container,
218
viewContainer: container
220
var environment_delta = default_env;
222
var notifications = app.db.notifications,
223
view = new views.NotificationsView({
224
container: container,
225
notifications: notifications,
226
env: app.env}).render();
229
app.env.dispatch_result(environment_delta);
232
notifications.size().should.equal(7);
233
// we have one unit in error
234
view.getShowable().length.should.equal(1);
236
// now fire another delta event marking that node as
238
app.env.dispatch_result({result: [['unit', 'change', {
240
'agent-state': 'started',
241
'public-address': '192.168.122.222',
244
notifications.size().should.equal(8);
245
// This should have evicted the prior notice from seen
246
view.getShowable().length.should.equal(0);
249
it('must properly construct title and message based on level from ' +
252
var container = Y.Node.create(
253
'<div id="test" class="container"></div>'),
254
app = new Y.juju.App({
255
container: container,
256
viewContainer: container
258
var environment_delta = {
261
'charm': 'cs:precise/wordpress-6',
265
'charm': 'cs:precise/mediawiki-3',
269
'charm': 'cs:precise/mysql-6',
273
'agent-state': 'install-error',
277
'agent-state': 'error',
278
'public-address': '192.168.122.222',
282
'public-address': '192.168.122.222',
289
var notifications = app.db.notifications,
290
view = new views.NotificationsView({
291
container: container,
292
notifications: notifications,
294
env: app.env}).render();
296
app.env.dispatch_result(environment_delta);
298
notifications.size().should.equal(6);
299
// we have one unit in error
300
var showable = view.getShowable();
301
showable.length.should.equal(2);
302
// The first showable notification should indicate an error.
303
showable[0].level.should.equal('error');
304
showable[0].title.should.equal('Error with mysql/0');
305
showable[0].message.should.equal('Agent-state = error.');
306
// The second showable notification should also indicate an error.
307
showable[1].level.should.equal('error');
308
showable[1].title.should.equal('Error with wordpress/0');
309
showable[1].message.should.equal('Agent-state = install-error.');
310
// The first non-error notice should have an 'info' level and less
312
var notice = notifications.item(0);
313
notice.get('level').should.equal('info');
314
notice.get('title').should.equal('Problem with mysql/2');
315
notice.get('message').should.equal('');
319
it('should open on click and close on clickoutside', function(done) {
320
var container = Y.Node.create('<div id="test-container" ' +
321
'style="display: none" class="container"/>'),
322
notifications = new models.NotificationList(),
323
env = new juju.Environment(),
324
view = new views.NotificationsView({
325
container: container,
326
notifications: notifications,
330
Y.one('body').append(container);
331
notifications.add({title: 'testing', 'level': 'error'});
332
indicator = container.one('#notify-indicator');
334
indicator.simulate('click');
335
indicator.ancestor().hasClass('open').should.equal(true);
337
Y.one('body').simulate('click');
338
indicator.ancestor().hasClass('open').should.equal(false);
345
describe('changing notifications to words', function() {
349
juju = Y.namespace('juju');
352
it('should correctly translate notification operations into English',
354
assert.equal(juju._changeNotificationOpToWords('add'), 'created');
355
assert.equal(juju._changeNotificationOpToWords('remove'),
357
assert.equal(juju._changeNotificationOpToWords('not-an-op'),
362
describe('relation notifications', function() {
366
juju = Y.namespace('juju');
369
it('should produce reasonable titles', function() {
371
juju._relationNotifications.title(undefined, 'add'),
374
juju._relationNotifications.title(undefined, 'remove'),
378
it('should generate messages about two-party relations', function() {
381
[['endpoint0', {name: 'relation0'}],
382
['endpoint1', {name: 'relation1'}]]};
384
juju._relationNotifications.message(undefined, 'add',
385
changeData), 'Relation between endpoint0 (relation type ' +
386
'"relation0") and endpoint1 (relation type "relation1") ' +
390
it('should generate messages about one-party relations', function() {
393
[['endpoint1', {name: 'relation1'}]]};
395
juju._relationNotifications.message(undefined, 'add',
396
changeData), 'Relation with endpoint1 (relation type ' +
397
'"relation1") was created');
401
describe('notification visual feedback', function() {
402
var env, models, notifications, notificationsView, notifierBox, views;
71
juju = Y.namespace('juju');
72
models = Y.namespace('juju.models');
73
views = Y.namespace('juju.views');
78
it('must be able to make notification and lists of notifications',
80
var note1 = new models.Notification({
84
note2 = new models.Notification({
86
message: 'I said goodnight!'
88
notifications = new models.NotificationList();
90
notifications.add([note1, note2]);
91
notifications.size().should.equal(2);
93
// timestamp should be generated once
94
var ts = note1.get('timestamp');
95
note1.get('timestamp').should.equal(ts);
96
// force an update so we can test ordering
97
// fast execution can result in same timestamp
98
note2.set('timestamp', ts + 1);
99
note2.get('timestamp').should.be.above(ts);
101
// defaults as expected
102
note1.get('level').should.equal('info');
103
note2.get('level').should.equal('info');
104
// the sort order on the list should be by
106
notifications.get('title').should.eql(['test2', 'test1']);
109
it('must be able to render its view with sample data',
111
var note1 = new models.Notification({
112
title: 'test1', message: 'Hello'}),
113
note2 = new models.Notification({
114
title: 'test2', message: 'I said goodnight!'}),
115
notifications = new models.NotificationList(),
116
container = Y.Node.create('<div id="test">'),
117
env = new juju.Environment(),
118
view = new views.NotificationsView({
119
container: container,
120
notifications: notifications,
123
// Verify the expected elements appear in the view
124
container.one('#notify-list').should.not.equal(undefined);
128
it('must be able to limit the size of notification events',
130
var note1 = new models.Notification({
134
note2 = new models.Notification({
136
message: 'I said goodnight!'
138
note3 = new models.Notification({
140
message: 'Never remember'
142
notifications = new models.NotificationList({
146
notifications.add([note1, note2]);
147
notifications.size().should.equal(2);
149
// Adding a new notification should pop the oldest from the list (we
151
notifications.add(note3);
152
notifications.size().should.equal(2);
153
notifications.get('title').should.eql(['test3', 'test2']);
156
it('must be able to get notifications for a given model',
158
var m = new models.Service({id: 'mediawiki'}),
159
note1 = new models.Notification({
164
note2 = new models.Notification({
166
message: 'I said goodnight!'
168
notifications = new models.NotificationList();
170
notifications.add([note1, note2]);
171
notifications.size().should.equal(2);
172
notifications.getNotificationsForModel(m).should.eql(
177
it('must be able to include and show object links', function() {
178
var container = Y.Node.create('<div id="test">'),
179
conn = new(Y.namespace('juju-tests.utils')).SocketStub(),
180
env = new juju.Environment({conn: conn}),
181
app = new Y.juju.App({env: env, container: container}),
183
mw = db.services.create({id: 'mediawiki',
185
notifications = db.notifications,
186
view = new views.NotificationsOverview({
187
container: container,
188
notifications: notifications,
191
// we use overview here for testing as it defaults
192
// to showing all notices
194
// we can use app's routing table to derive a link
195
notifications.create({title: 'Service Down',
196
message: 'Your service has an error',
197
link: app.getModelURL(mw)
200
var link = container.one('.notice').one('a');
201
link.getAttribute('href').should.equal(
202
'/service/mediawiki/');
203
link.getHTML().should.contain('View Details');
206
// create a new notice passing the link_title
207
notifications.create({title: 'Service Down',
208
message: 'Your service has an error',
209
link: app.getModelURL(mw),
210
link_title: 'Resolve this'
213
link = container.one('.notice').one('a');
214
link.getAttribute('href').should.equal(
215
'/service/mediawiki/');
216
link.getHTML().should.contain('Resolve this');
219
it('must be able to evict irrelevant notices', function() {
220
var container = Y.Node.create(
221
'<div id="test" class="container"></div>'),
222
conn = new(Y.namespace('juju-tests.utils')).SocketStub(),
223
env = new juju.Environment({conn: conn}),
224
app = new Y.juju.App({
226
container: container,
227
viewContainer: container
229
var environment_delta = default_env;
231
var notifications = app.db.notifications,
232
view = new views.NotificationsView({
233
container: container,
234
notifications: notifications,
235
env: app.env}).render();
238
app.env.dispatch_result(environment_delta);
241
notifications.size().should.equal(7);
242
// we have one unit in error
243
view.getShowable().length.should.equal(1);
245
// now fire another delta event marking that node as
247
app.env.dispatch_result({result: [['unit', 'change', {
249
'agent-state': 'started',
250
'public-address': '192.168.122.222',
253
notifications.size().should.equal(8);
254
// This should have evicted the prior notice from seen
255
view.getShowable().length.should.equal(0);
258
it('must properly construct title and message based on level from ' +
261
var container = Y.Node.create(
262
'<div id="test" class="container"></div>'),
263
app = new Y.juju.App({
264
container: container,
265
viewContainer: container
267
var environment_delta = {
270
'charm': 'cs:precise/wordpress-6',
274
'charm': 'cs:precise/mediawiki-3',
278
'charm': 'cs:precise/mysql-6',
282
'agent-state': 'install-error',
286
'agent-state': 'error',
287
'public-address': '192.168.122.222',
291
'public-address': '192.168.122.222',
298
var notifications = app.db.notifications,
299
view = new views.NotificationsView({
300
container: container,
301
notifications: notifications,
303
env: app.env}).render();
305
app.env.dispatch_result(environment_delta);
307
notifications.size().should.equal(6);
308
// we have one unit in error
309
var showable = view.getShowable();
310
showable.length.should.equal(2);
311
// The first showable notification should indicate an error.
312
showable[0].level.should.equal('error');
313
showable[0].title.should.equal('Error with mysql/0');
314
showable[0].message.should.equal('Agent-state = error.');
315
// The second showable notification should also indicate an error.
316
showable[1].level.should.equal('error');
317
showable[1].title.should.equal('Error with wordpress/0');
318
showable[1].message.should.equal('Agent-state = install-error.');
319
// The first non-error notice should have an 'info' level and less
321
var notice = notifications.item(0);
322
notice.get('level').should.equal('info');
323
notice.get('title').should.equal('Problem with mysql/2');
324
notice.get('message').should.equal('');
328
it('should open on click and close on clickoutside', function(done) {
329
var container = Y.Node.create(
330
'<div id="test-container" style="display: none" class="container"/>'),
331
notifications = new models.NotificationList(),
332
env = new juju.Environment(),
333
view = new views.NotificationsView({
334
container: container,
335
notifications: notifications,
339
Y.one('body').append(container);
340
notifications.add({title: 'testing', 'level': 'error'});
341
indicator = container.one('#notify-indicator');
343
indicator.simulate('click');
344
indicator.ancestor().hasClass('open').should.equal(true);
346
Y.one('body').simulate('click');
347
indicator.ancestor().hasClass('open').should.equal(false);
356
describe('changing notifications to words', function() {
359
before(function(done) {
360
Y = YUI(GlobalConfig).use(
361
['juju-notification-controller'],
363
juju = Y.namespace('juju');
368
it('should correctly translate notification operations into English',
370
assert.equal(juju._changeNotificationOpToWords('add'), 'created');
371
assert.equal(juju._changeNotificationOpToWords('remove'), 'removed');
372
assert.equal(juju._changeNotificationOpToWords('not-an-op'), 'changed');
376
describe('relation notifications', function() {
379
before(function(done) {
380
Y = YUI(GlobalConfig).use(
381
['juju-notification-controller'],
383
juju = Y.namespace('juju');
388
it('should produce reasonable titles', function() {
390
juju._relationNotifications.title(undefined, 'add'),
393
juju._relationNotifications.title(undefined, 'remove'),
397
it('should generate messages about two-party relations', function() {
400
[['endpoint0', {name: 'relation0'}],
401
['endpoint1', {name: 'relation1'}]]};
403
juju._relationNotifications.message(undefined, 'add', changeData),
404
'Relation between endpoint0 (relation type "relation0") and ' +
405
'endpoint1 (relation type "relation1") was created');
408
it('should generate messages about one-party relations', function() {
411
[['endpoint1', {name: 'relation1'}]]};
413
juju._relationNotifications.message(undefined, 'add', changeData),
414
'Relation with endpoint1 (relation type "relation1") was created');
418
describe('notification visual feedback', function() {
419
var env, models, notifications, notificationsView, notifierBox, views, Y;
421
before(function(done) {
422
Y = YUI(GlobalConfig).use('juju-env', 'juju-models', 'juju-views',
405
424
var juju = Y.namespace('juju');
406
425
env = new juju.Environment();
407
426
models = Y.namespace('juju.models');
408
427
views = Y.namespace('juju.views');
411
// Instantiate the notifications model list and view.
412
// Also create the notifier box and attach it as first element of the
414
beforeEach(function() {
415
notifications = new models.NotificationList();
416
notificationsView = new views.NotificationsView({
418
notifications: notifications
420
notifierBox = Y.Node.create('<div id="notifier-box"></div>');
421
notifierBox.setStyle('display', 'none');
422
Y.one('body').prepend(notifierBox);
425
// Destroy the notifier box created in beforeEach.
426
afterEach(function() {
427
notifierBox.remove();
428
notifierBox.destroy(true);
431
// Assert the notifier box contains the expectedNumber of notifiers.
432
var assertNumNotifiers = function(expectedNumber) {
433
assert.equal(expectedNumber, notifierBox.get('children').size());
436
it('should appear when a new error is notified', function() {
437
notifications.add({title: 'mytitle', level: 'error'});
438
assertNumNotifiers(1);
441
it('should only appear when the DOM contains the notifier box',
443
notifierBox.remove();
444
notifications.add({title: 'mytitle', level: 'error'});
445
assertNumNotifiers(0);
448
it('should not appear when the notification is not an error',
450
notifications.add({title: 'mytitle', level: 'info'});
451
assertNumNotifiers(0);
454
it('should not appear when the notification comes form delta',
456
notifications.add({title: 'mytitle', level: 'error', isDelta:
458
assertNumNotifiers(0);
432
// Instantiate the notifications model list and view.
433
// Also create the notifier box and attach it as first element of the body.
434
beforeEach(function() {
435
notifications = new models.NotificationList();
436
notificationsView = new views.NotificationsView({
438
notifications: notifications
440
notifierBox = Y.Node.create('<div id="notifier-box"></div>');
441
notifierBox.setStyle('display', 'none');
442
Y.one('body').prepend(notifierBox);
445
// Destroy the notifier box created in beforeEach.
446
afterEach(function() {
447
notifierBox.remove();
448
notifierBox.destroy(true);
451
// Assert the notifier box contains the expectedNumber of notifiers.
452
var assertNumNotifiers = function(expectedNumber) {
453
assert.equal(expectedNumber, notifierBox.get('children').size());
456
it('should appear when a new error is notified', function() {
457
notifications.add({title: 'mytitle', level: 'error'});
458
assertNumNotifiers(1);
461
it('should only appear when the DOM contains the notifier box', function() {
462
notifierBox.remove();
463
notifications.add({title: 'mytitle', level: 'error'});
464
assertNumNotifiers(0);
467
it('should not appear when the notification is not an error', function() {
468
notifications.add({title: 'mytitle', level: 'info'});
469
assertNumNotifiers(0);
472
it('should not appear when the notification comes form delta', function() {
473
notifications.add({title: 'mytitle', level: 'error', isDelta: true});
474
assertNumNotifiers(0);