~bac/juju-gui/1103207

« back to all changes in this revision

Viewing changes to test/test_notifications.js

  • Committer: Matthew Scott
  • Date: 2013-01-09 17:23:33 UTC
  • mfrom: (307 juju-gui)
  • mto: This revision was merged to the branch mainline in revision 312.
  • Revision ID: matthew.scott@canonical.com-20130109172333-ad6ndevmn5p44dz0
Merging with trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
'use strict';
2
2
 
3
 
YUI(GlobalConfig).use(['juju-gui', 'node-event-simulate', 'juju-tests-utils'],
 
3
describe('notifications', function() {
 
4
  var Y, juju, models, views;
 
5
 
 
6
  var default_env = {
 
7
    'result': [
 
8
      ['service', 'add', {
 
9
        'charm': 'cs:precise/wordpress-6',
 
10
        'id': 'wordpress',
 
11
        'exposed': false
 
12
      }],
 
13
      ['service', 'add', {
 
14
        'charm': 'cs:precise/mediawiki-3',
 
15
        'id': 'mediawiki',
 
16
        'exposed': false
 
17
      }],
 
18
      ['service', 'add', {
 
19
        'charm': 'cs:precise/mysql-6',
 
20
        'id': 'mysql'
 
21
      }],
 
22
      ['relation', 'add', {
 
23
        'interface': 'reversenginx',
 
24
        'scope': 'global',
 
25
        'endpoints':
 
26
         [['wordpress', {'role': 'peer', 'name': 'loadbalancer'}]],
 
27
        'id': 'relation-0000000000'
 
28
      }],
 
29
      ['relation', 'add', {
 
30
        'interface': 'mysql',
 
31
        'scope': 'global',
 
32
        'endpoints':
 
33
         [['mysql', {'role': 'server', 'name': 'db'}],
 
34
          ['wordpress', {'role': 'client', 'name': 'db'}]],
 
35
        'id': 'relation-0000000001'
 
36
      }],
 
37
      ['machine', 'add', {
 
38
        'agent-state': 'running',
 
39
        'instance-state': 'running',
 
40
        'id': 0,
 
41
        'instance-id': 'local',
 
42
        'dns-name': 'localhost'
 
43
      }],
 
44
      ['unit', 'add', {
 
45
        'machine': 0,
 
46
        'agent-state': 'started',
 
47
        'public-address': '192.168.122.113',
 
48
        'id': 'wordpress/0'
 
49
      }],
 
50
      ['unit', 'add', {
 
51
        'machine': 0,
 
52
        'agent-state': 'error',
 
53
        'public-address': '192.168.122.222',
 
54
        'id': 'mysql/0'
 
55
      }]
 
56
    ],
 
57
    'op': 'delta'
 
58
  };
 
59
 
 
60
 
 
61
  before(function(done) {
 
62
    Y = YUI(GlobalConfig).use([
 
63
      'juju-models',
 
64
      'juju-views',
 
65
      'juju-gui',
 
66
      'juju-env',
 
67
      'node-event-simulate',
 
68
      'juju-tests-utils'],
 
69
 
4
70
    function(Y) {
5
 
      describe('notifications', function() {
6
 
        var juju, models, views;
7
 
 
8
 
        var default_env = {
9
 
          'result': [
10
 
            ['service', 'add', {
11
 
              'charm': 'cs:precise/wordpress-6',
12
 
              'id': 'wordpress',
13
 
              'exposed': false
14
 
            }],
15
 
            ['service', 'add', {
16
 
              'charm': 'cs:precise/mediawiki-3',
17
 
              'id': 'mediawiki',
18
 
              'exposed': false
19
 
            }],
20
 
            ['service', 'add', {
21
 
              'charm': 'cs:precise/mysql-6',
22
 
              'id': 'mysql'
23
 
            }],
24
 
            ['relation', 'add', {
25
 
              'interface': 'reversenginx',
26
 
              'scope': 'global',
27
 
              'endpoints':
28
 
               [['wordpress', {'role': 'peer', 'name': 'loadbalancer'}]],
29
 
              'id': 'relation-0000000000'
30
 
            }],
31
 
            ['relation', 'add', {
32
 
              'interface': 'mysql',
33
 
              'scope': 'global',
34
 
              'endpoints':
35
 
               [['mysql', {'role': 'server', 'name': 'db'}],
36
 
                ['wordpress', {'role': 'client', 'name': 'db'}]],
37
 
              'id': 'relation-0000000001'
38
 
            }],
39
 
            ['machine', 'add', {
40
 
              'agent-state': 'running',
41
 
              'instance-state': 'running',
42
 
              'id': 0,
43
 
              'instance-id': 'local',
44
 
              'dns-name': 'localhost'
45
 
            }],
46
 
            ['unit', 'add', {
47
 
              'machine': 0,
48
 
              'agent-state': 'started',
49
 
              'public-address': '192.168.122.113',
50
 
              'id': 'wordpress/0'
51
 
            }],
52
 
            ['unit', 'add', {
53
 
              'machine': 0,
54
 
              'agent-state': 'error',
55
 
              'public-address': '192.168.122.222',
56
 
              'id': 'mysql/0'
57
 
            }]
58
 
          ],
59
 
          'op': 'delta'
60
 
        };
61
 
 
62
 
 
63
 
        before(function() {
64
 
          juju = Y.namespace('juju');
65
 
          models = Y.namespace('juju.models');
66
 
          views = Y.namespace('juju.views');
67
 
        });
68
 
 
69
 
        it('must be able to make notification and lists of notifications',
70
 
           function() {
71
 
             var note1 = new models.Notification({
72
 
               title: 'test1',
73
 
               message: 'Hello'
74
 
             }),
75
 
             note2 = new models.Notification({
76
 
               title: 'test2',
77
 
               message: 'I said goodnight!'
78
 
             }),
79
 
             notifications = new models.NotificationList();
80
 
 
81
 
             notifications.add([note1, note2]);
82
 
             notifications.size().should.equal(2);
83
 
 
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);
91
 
 
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
96
 
             // timestamp
97
 
             notifications.get('title').should.eql(['test2', 'test1']);
98
 
           });
99
 
 
100
 
        it('must be able to render its view with sample data',
101
 
           function() {
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,
112
 
                     env: env});
113
 
             view.render();
114
 
             // Verify the expected elements appear in the view
115
 
             container.one('#notify-list').should.not.equal(undefined);
116
 
             container.destroy();
117
 
           });
118
 
 
119
 
        it('must be able to limit the size of notification events',
120
 
           function() {
121
 
             var note1 = new models.Notification({
122
 
               title: 'test1',
123
 
               message: 'Hello'
124
 
             }),
125
 
             note2 = new models.Notification({
126
 
               title: 'test2',
127
 
               message: 'I said goodnight!'
128
 
             }),
129
 
             note3 = new models.Notification({
130
 
               title: 'test3',
131
 
               message: 'Never remember'
132
 
             }),
133
 
             notifications = new models.NotificationList({
134
 
               max_size: 2
135
 
             });
136
 
 
137
 
             notifications.add([note1, note2]);
138
 
             notifications.size().should.equal(2);
139
 
 
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']);
145
 
           });
146
 
 
147
 
        it('must be able to get notifications for a given model',
148
 
           function() {
149
 
             var m = new models.Service({id: 'mediawiki'}),
150
 
             note1 = new models.Notification({
151
 
               title: 'test1',
152
 
               message: 'Hello',
153
 
               modelId: m
154
 
             }),
155
 
             note2 = new models.Notification({
156
 
               title: 'test2',
157
 
               message: 'I said goodnight!'
158
 
             }),
159
 
             notifications = new models.NotificationList();
160
 
 
161
 
             notifications.add([note1, note2]);
162
 
             notifications.size().should.equal(2);
163
 
             notifications.getNotificationsForModel(m).should.eql(
164
 
             [note1]);
165
 
 
166
 
           });
167
 
 
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}),
173
 
              db = app.db,
174
 
              mw = db.services.create({id: 'mediawiki',
175
 
                                      name: 'mediawiki'}),
176
 
              notifications = db.notifications,
177
 
              view = new views.NotificationsOverview({
178
 
                        container: container,
179
 
                        notifications: notifications,
180
 
                        app: app,
181
 
                        env: env}).render();
182
 
          // we use overview here for testing as it defaults
183
 
          // to showing all notices
184
 
 
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)
189
 
          });
190
 
          view.render();
191
 
          var link = container.one('.notice').one('a');
192
 
          link.getAttribute('href').should.equal(
193
 
              '/service/mediawiki/');
194
 
          link.getHTML().should.contain('View Details');
195
 
 
196
 
 
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'
202
 
          });
203
 
          view.render();
204
 
          link = container.one('.notice').one('a');
205
 
          link.getAttribute('href').should.equal(
206
 
              '/service/mediawiki/');
207
 
          link.getHTML().should.contain('Resolve this');
208
 
        });
209
 
 
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({
216
 
                env: env,
217
 
                container: container,
218
 
                viewContainer: container
219
 
              });
220
 
          var environment_delta = default_env;
221
 
 
222
 
          var notifications = app.db.notifications,
223
 
              view = new views.NotificationsView({
224
 
                container: container,
225
 
                notifications: notifications,
226
 
                env: app.env}).render();
227
 
 
228
 
 
229
 
          app.env.dispatch_result(environment_delta);
230
 
 
231
 
 
232
 
          notifications.size().should.equal(7);
233
 
          // we have one unit in error
234
 
          view.getShowable().length.should.equal(1);
235
 
 
236
 
          // now fire another delta event marking that node as
237
 
          // started
238
 
          app.env.dispatch_result({result: [['unit', 'change', {
239
 
            'machine': 0,
240
 
            'agent-state': 'started',
241
 
            'public-address': '192.168.122.222',
242
 
            'id': 'mysql/0'
243
 
          }]], op: 'delta'});
244
 
          notifications.size().should.equal(8);
245
 
          // This should have evicted the prior notice from seen
246
 
          view.getShowable().length.should.equal(0);
247
 
        });
248
 
 
249
 
        it('must properly construct title and message based on level from ' +
250
 
           'event data',
251
 
           function() {
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
257
 
             });
258
 
             var environment_delta = {
259
 
               'result': [
260
 
                 ['service', 'add', {
261
 
                   'charm': 'cs:precise/wordpress-6',
262
 
                   'id': 'wordpress'
263
 
                 }],
264
 
                 ['service', 'add', {
265
 
                   'charm': 'cs:precise/mediawiki-3',
266
 
                   'id': 'mediawiki'
267
 
                 }],
268
 
                 ['service', 'add', {
269
 
                   'charm': 'cs:precise/mysql-6',
270
 
                   'id': 'mysql'
271
 
                 }],
272
 
                 ['unit', 'add', {
273
 
                   'agent-state': 'install-error',
274
 
                   'id': 'wordpress/0'
275
 
                 }],
276
 
                 ['unit', 'add', {
277
 
                   'agent-state': 'error',
278
 
                   'public-address': '192.168.122.222',
279
 
                   'id': 'mysql/0'
280
 
                 }],
281
 
                 ['unit', 'add', {
282
 
                   'public-address': '192.168.122.222',
283
 
                   'id': 'mysql/2'
284
 
                 }]
285
 
               ],
286
 
               'op': 'delta'
287
 
             };
288
 
 
289
 
             var notifications = app.db.notifications,
290
 
             view = new views.NotificationsView({
291
 
               container: container,
292
 
               notifications: notifications,
293
 
               app: app,
294
 
               env: app.env}).render();
295
 
 
296
 
             app.env.dispatch_result(environment_delta);
297
 
 
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
311
 
             // severe messaging.
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('');
316
 
           });
317
 
 
318
 
 
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,
327
 
                env: env}).render(),
328
 
              indicator;
329
 
 
330
 
          Y.one('body').append(container);
331
 
          notifications.add({title: 'testing', 'level': 'error'});
332
 
          indicator = container.one('#notify-indicator');
333
 
 
334
 
          indicator.simulate('click');
335
 
          indicator.ancestor().hasClass('open').should.equal(true);
336
 
 
337
 
          Y.one('body').simulate('click');
338
 
          indicator.ancestor().hasClass('open').should.equal(false);
339
 
 
340
 
          container.remove();
341
 
          done();
342
 
        });
343
 
      });
344
 
 
345
 
      describe('changing notifications to words', function() {
346
 
        var juju;
347
 
 
348
 
        before(function() {
349
 
          juju = Y.namespace('juju');
350
 
        });
351
 
 
352
 
        it('should correctly translate notification operations into English',
353
 
           function() {
354
 
             assert.equal(juju._changeNotificationOpToWords('add'), 'created');
355
 
             assert.equal(juju._changeNotificationOpToWords('remove'),
356
 
                 'removed');
357
 
             assert.equal(juju._changeNotificationOpToWords('not-an-op'),
358
 
                 'changed');
359
 
           });
360
 
      });
361
 
 
362
 
      describe('relation notifications', function() {
363
 
        var juju;
364
 
 
365
 
        before(function() {
366
 
          juju = Y.namespace('juju');
367
 
        });
368
 
 
369
 
        it('should produce reasonable titles', function() {
370
 
          assert.equal(
371
 
              juju._relationNotifications.title(undefined, 'add'),
372
 
              'Relation created');
373
 
          assert.equal(
374
 
              juju._relationNotifications.title(undefined, 'remove'),
375
 
              'Relation removed');
376
 
        });
377
 
 
378
 
        it('should generate messages about two-party relations', function() {
379
 
          var changeData =
380
 
              { endpoints:
381
 
                    [['endpoint0', {name: 'relation0'}],
382
 
                     ['endpoint1', {name: 'relation1'}]]};
383
 
          assert.equal(
384
 
              juju._relationNotifications.message(undefined, 'add',
385
 
                  changeData), 'Relation between endpoint0 (relation type ' +
386
 
                  '"relation0") and endpoint1 (relation type "relation1") ' +
387
 
                  'was created');
388
 
        });
389
 
 
390
 
        it('should generate messages about one-party relations', function() {
391
 
          var changeData =
392
 
              { endpoints:
393
 
                    [['endpoint1', {name: 'relation1'}]]};
394
 
          assert.equal(
395
 
              juju._relationNotifications.message(undefined, 'add',
396
 
                  changeData), 'Relation with endpoint1 (relation type ' +
397
 
                  '"relation1") was created');
398
 
        });
399
 
      });
400
 
 
401
 
      describe('notification visual feedback', function() {
402
 
        var env, models, notifications, notificationsView, notifierBox, views;
403
 
 
404
 
        before(function() {
 
71
      juju = Y.namespace('juju');
 
72
      models = Y.namespace('juju.models');
 
73
      views = Y.namespace('juju.views');
 
74
      done();
 
75
    });
 
76
  });
 
77
 
 
78
  it('must be able to make notification and lists of notifications',
 
79
     function() {
 
80
        var note1 = new models.Notification({
 
81
         title: 'test1',
 
82
         message: 'Hello'
 
83
        }),
 
84
            note2 = new models.Notification({
 
85
         title: 'test2',
 
86
         message: 'I said goodnight!'
 
87
            }),
 
88
            notifications = new models.NotificationList();
 
89
 
 
90
        notifications.add([note1, note2]);
 
91
        notifications.size().should.equal(2);
 
92
 
 
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);
 
100
 
 
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
 
105
        // timestamp
 
106
        notifications.get('title').should.eql(['test2', 'test1']);
 
107
     });
 
108
 
 
109
  it('must be able to render its view with sample data',
 
110
     function() {
 
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,
 
121
                   env: env});
 
122
       view.render();
 
123
       // Verify the expected elements appear in the view
 
124
       container.one('#notify-list').should.not.equal(undefined);
 
125
       container.destroy();
 
126
     });
 
127
 
 
128
  it('must be able to limit the size of notification events',
 
129
     function() {
 
130
       var note1 = new models.Notification({
 
131
         title: 'test1',
 
132
         message: 'Hello'
 
133
       }),
 
134
           note2 = new models.Notification({
 
135
         title: 'test2',
 
136
         message: 'I said goodnight!'
 
137
       }),
 
138
           note3 = new models.Notification({
 
139
         title: 'test3',
 
140
         message: 'Never remember'
 
141
       }),
 
142
           notifications = new models.NotificationList({
 
143
         max_size: 2
 
144
       });
 
145
 
 
146
       notifications.add([note1, note2]);
 
147
       notifications.size().should.equal(2);
 
148
 
 
149
       // Adding a new notification should pop the oldest from the list (we
 
150
       // exceed max_size)
 
151
       notifications.add(note3);
 
152
       notifications.size().should.equal(2);
 
153
       notifications.get('title').should.eql(['test3', 'test2']);
 
154
     });
 
155
 
 
156
  it('must be able to get notifications for a given model',
 
157
     function() {
 
158
       var m = new models.Service({id: 'mediawiki'}),
 
159
           note1 = new models.Notification({
 
160
         title: 'test1',
 
161
         message: 'Hello',
 
162
         modelId: m
 
163
       }),
 
164
           note2 = new models.Notification({
 
165
         title: 'test2',
 
166
         message: 'I said goodnight!'
 
167
       }),
 
168
           notifications = new models.NotificationList();
 
169
 
 
170
       notifications.add([note1, note2]);
 
171
       notifications.size().should.equal(2);
 
172
       notifications.getNotificationsForModel(m).should.eql(
 
173
       [note1]);
 
174
 
 
175
     });
 
176
 
 
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}),
 
182
        db = app.db,
 
183
        mw = db.services.create({id: 'mediawiki',
 
184
                                    name: 'mediawiki'}),
 
185
        notifications = db.notifications,
 
186
        view = new views.NotificationsOverview({
 
187
                      container: container,
 
188
                      notifications: notifications,
 
189
                      app: app,
 
190
                      env: env}).render();
 
191
    // we use overview here for testing as it defaults
 
192
    // to showing all notices
 
193
 
 
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)
 
198
    });
 
199
    view.render();
 
200
    var link = container.one('.notice').one('a');
 
201
    link.getAttribute('href').should.equal(
 
202
        '/service/mediawiki/');
 
203
    link.getHTML().should.contain('View Details');
 
204
 
 
205
 
 
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'
 
211
    });
 
212
    view.render();
 
213
    link = container.one('.notice').one('a');
 
214
    link.getAttribute('href').should.equal(
 
215
        '/service/mediawiki/');
 
216
    link.getHTML().should.contain('Resolve this');
 
217
  });
 
218
 
 
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({
 
225
          env: env,
 
226
          container: container,
 
227
          viewContainer: container
 
228
        });
 
229
    var environment_delta = default_env;
 
230
 
 
231
    var notifications = app.db.notifications,
 
232
        view = new views.NotificationsView({
 
233
          container: container,
 
234
          notifications: notifications,
 
235
          env: app.env}).render();
 
236
 
 
237
 
 
238
    app.env.dispatch_result(environment_delta);
 
239
 
 
240
 
 
241
    notifications.size().should.equal(7);
 
242
    // we have one unit in error
 
243
    view.getShowable().length.should.equal(1);
 
244
 
 
245
    // now fire another delta event marking that node as
 
246
    // started
 
247
    app.env.dispatch_result({result: [['unit', 'change', {
 
248
      'machine': 0,
 
249
      'agent-state': 'started',
 
250
      'public-address': '192.168.122.222',
 
251
      'id': 'mysql/0'
 
252
    }]], op: 'delta'});
 
253
    notifications.size().should.equal(8);
 
254
    // This should have evicted the prior notice from seen
 
255
    view.getShowable().length.should.equal(0);
 
256
  });
 
257
 
 
258
  it('must properly construct title and message based on level from ' +
 
259
     'event data',
 
260
     function() {
 
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
 
266
       });
 
267
       var environment_delta = {
 
268
         'result': [
 
269
           ['service', 'add', {
 
270
             'charm': 'cs:precise/wordpress-6',
 
271
             'id': 'wordpress'
 
272
           }],
 
273
           ['service', 'add', {
 
274
             'charm': 'cs:precise/mediawiki-3',
 
275
             'id': 'mediawiki'
 
276
           }],
 
277
           ['service', 'add', {
 
278
             'charm': 'cs:precise/mysql-6',
 
279
             'id': 'mysql'
 
280
           }],
 
281
           ['unit', 'add', {
 
282
             'agent-state': 'install-error',
 
283
             'id': 'wordpress/0'
 
284
           }],
 
285
           ['unit', 'add', {
 
286
             'agent-state': 'error',
 
287
             'public-address': '192.168.122.222',
 
288
             'id': 'mysql/0'
 
289
           }],
 
290
           ['unit', 'add', {
 
291
             'public-address': '192.168.122.222',
 
292
             'id': 'mysql/2'
 
293
           }]
 
294
         ],
 
295
         'op': 'delta'
 
296
       };
 
297
 
 
298
       var notifications = app.db.notifications,
 
299
       view = new views.NotificationsView({
 
300
         container: container,
 
301
         notifications: notifications,
 
302
         app: app,
 
303
         env: app.env}).render();
 
304
 
 
305
       app.env.dispatch_result(environment_delta);
 
306
 
 
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
 
320
       // severe messaging.
 
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('');
 
325
     });
 
326
 
 
327
 
 
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,
 
336
          env: env}).render(),
 
337
        indicator;
 
338
 
 
339
    Y.one('body').append(container);
 
340
    notifications.add({title: 'testing', 'level': 'error'});
 
341
    indicator = container.one('#notify-indicator');
 
342
 
 
343
    indicator.simulate('click');
 
344
    indicator.ancestor().hasClass('open').should.equal(true);
 
345
 
 
346
    Y.one('body').simulate('click');
 
347
    indicator.ancestor().hasClass('open').should.equal(false);
 
348
 
 
349
    container.remove();
 
350
    done();
 
351
  });
 
352
 
 
353
});
 
354
 
 
355
 
 
356
describe('changing notifications to words', function() {
 
357
  var Y, juju;
 
358
 
 
359
  before(function(done) {
 
360
    Y = YUI(GlobalConfig).use(
 
361
        ['juju-notification-controller'],
 
362
        function(Y) {
 
363
          juju = Y.namespace('juju');
 
364
          done();
 
365
        });
 
366
  });
 
367
 
 
368
  it('should correctly translate notification operations into English',
 
369
     function() {
 
370
       assert.equal(juju._changeNotificationOpToWords('add'), 'created');
 
371
       assert.equal(juju._changeNotificationOpToWords('remove'), 'removed');
 
372
       assert.equal(juju._changeNotificationOpToWords('not-an-op'), 'changed');
 
373
     });
 
374
});
 
375
 
 
376
describe('relation notifications', function() {
 
377
  var Y, juju;
 
378
 
 
379
  before(function(done) {
 
380
    Y = YUI(GlobalConfig).use(
 
381
        ['juju-notification-controller'],
 
382
        function(Y) {
 
383
          juju = Y.namespace('juju');
 
384
          done();
 
385
        });
 
386
  });
 
387
 
 
388
  it('should produce reasonable titles', function() {
 
389
    assert.equal(
 
390
        juju._relationNotifications.title(undefined, 'add'),
 
391
        'Relation created');
 
392
    assert.equal(
 
393
        juju._relationNotifications.title(undefined, 'remove'),
 
394
        'Relation removed');
 
395
  });
 
396
 
 
397
  it('should generate messages about two-party relations', function() {
 
398
    var changeData =
 
399
        { endpoints:
 
400
              [['endpoint0', {name: 'relation0'}],
 
401
                ['endpoint1', {name: 'relation1'}]]};
 
402
    assert.equal(
 
403
        juju._relationNotifications.message(undefined, 'add', changeData),
 
404
        'Relation between endpoint0 (relation type "relation0") and ' +
 
405
        'endpoint1 (relation type "relation1") was created');
 
406
  });
 
407
 
 
408
  it('should generate messages about one-party relations', function() {
 
409
    var changeData =
 
410
        { endpoints:
 
411
              [['endpoint1', {name: 'relation1'}]]};
 
412
    assert.equal(
 
413
        juju._relationNotifications.message(undefined, 'add', changeData),
 
414
        'Relation with endpoint1 (relation type "relation1") was created');
 
415
  });
 
416
});
 
417
 
 
418
describe('notification visual feedback', function() {
 
419
  var env, models, notifications, notificationsView, notifierBox, views, Y;
 
420
 
 
421
  before(function(done) {
 
422
    Y = YUI(GlobalConfig).use('juju-env', 'juju-models', 'juju-views',
 
423
        function(Y) {
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');
409
 
        });
410
 
 
411
 
        // Instantiate the notifications model list and view.
412
 
        // Also create the notifier box and attach it as first element of the
413
 
        // body.
414
 
        beforeEach(function() {
415
 
          notifications = new models.NotificationList();
416
 
          notificationsView = new views.NotificationsView({
417
 
            env: env,
418
 
            notifications: notifications
419
 
          });
420
 
          notifierBox = Y.Node.create('<div id="notifier-box"></div>');
421
 
          notifierBox.setStyle('display', 'none');
422
 
          Y.one('body').prepend(notifierBox);
423
 
        });
424
 
 
425
 
        // Destroy the notifier box created in beforeEach.
426
 
        afterEach(function() {
427
 
          notifierBox.remove();
428
 
          notifierBox.destroy(true);
429
 
        });
430
 
 
431
 
        // Assert the notifier box contains the expectedNumber of notifiers.
432
 
        var assertNumNotifiers = function(expectedNumber) {
433
 
          assert.equal(expectedNumber, notifierBox.get('children').size());
434
 
        };
435
 
 
436
 
        it('should appear when a new error is notified', function() {
437
 
          notifications.add({title: 'mytitle', level: 'error'});
438
 
          assertNumNotifiers(1);
439
 
        });
440
 
 
441
 
        it('should only appear when the DOM contains the notifier box',
442
 
            function() {
443
 
             notifierBox.remove();
444
 
             notifications.add({title: 'mytitle', level: 'error'});
445
 
             assertNumNotifiers(0);
446
 
           });
447
 
 
448
 
        it('should not appear when the notification is not an error',
449
 
            function() {
450
 
             notifications.add({title: 'mytitle', level: 'info'});
451
 
             assertNumNotifiers(0);
452
 
           });
453
 
 
454
 
        it('should not appear when the notification comes form delta',
455
 
            function() {
456
 
             notifications.add({title: 'mytitle', level: 'error', isDelta:
457
 
               true});
458
 
             assertNumNotifiers(0);
459
 
           });
460
 
 
461
 
      });
 
428
          done();
 
429
        });
 
430
  });
 
431
 
 
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({
 
437
      env: env,
 
438
      notifications: notifications
462
439
    });
 
440
    notifierBox = Y.Node.create('<div id="notifier-box"></div>');
 
441
    notifierBox.setStyle('display', 'none');
 
442
    Y.one('body').prepend(notifierBox);
 
443
  });
 
444
 
 
445
  // Destroy the notifier box created in beforeEach.
 
446
  afterEach(function() {
 
447
    notifierBox.remove();
 
448
    notifierBox.destroy(true);
 
449
  });
 
450
 
 
451
  // Assert the notifier box contains the expectedNumber of notifiers.
 
452
  var assertNumNotifiers = function(expectedNumber) {
 
453
    assert.equal(expectedNumber, notifierBox.get('children').size());
 
454
  };
 
455
 
 
456
  it('should appear when a new error is notified', function() {
 
457
    notifications.add({title: 'mytitle', level: 'error'});
 
458
    assertNumNotifiers(1);
 
459
  });
 
460
 
 
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);
 
465
  });
 
466
 
 
467
  it('should not appear when the notification is not an error', function() {
 
468
    notifications.add({title: 'mytitle', level: 'info'});
 
469
    assertNumNotifiers(0);
 
470
  });
 
471
 
 
472
  it('should not appear when the notification comes form delta', function() {
 
473
    notifications.add({title: 'mytitle', level: 'error', isDelta: true});
 
474
    assertNumNotifiers(0);
 
475
  });
 
476
 
 
477
});