Merge lp:~hazmat/juju-gui/reliable-test into lp:juju-gui/experimental

Proposed by Kapil Thangavelu
Status: Needs review
Proposed branch: lp:~hazmat/juju-gui/reliable-test
Merge into: lp:juju-gui/experimental
Diff against target: 1717 lines (+781/-641) (has conflicts)
15 files modified
app/app.js (+16/-5)
app/modules-debug.js (+6/-1)
app/templates/overview.handlebars (+1/-1)
app/views/charm-panel.js (+3/-2)
app/views/environment.js (+4/-7)
app/views/topology/mega.js (+6/-2)
app/views/topology/panzoom.js (+3/-2)
app/views/utils.js (+5/-2)
package.json (+4/-0)
test/index.html (+75/-10)
test/test_app.js (+44/-14)
test/test_app_hotkeys.js (+47/-45)
test/test_d3_components.js (+0/-1)
test/test_notifications.js (+469/-454)
test/test_notifier_widget.js (+98/-95)
Text conflict in app/app.js
Text conflict in package.json
Text conflict in test/index.html
Text conflict in test/test_app.js
To merge this branch: bzr merge lp:~hazmat/juju-gui/reliable-test
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+141197@code.launchpad.net

Description of the change

Make tests more reliable.

- Disable async loading for yui to ensure app code is loaded before tests.
- Yank yui loader closures around tests, this is an anti-pattern.
- Any test file can be run in isolation now.
- Re-order test loading into alphabetical order to detect/correct collisions.
- Enable using firefox for tests via test loader fix.
- WIP enable using phantomjs for running tests on cli.
- Make topology-mega.js more foregiving on its (there's a event subscription leak here.)
- Fix event subscription leak in charm-panel code.

https://codereview.appspot.com/7003054/

To post a comment you must log in.
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

Reviewers: mp+141197_code.launchpad.net,

Message:
Please take a look.

Description:
Make tests more reliable.

- Disable async loading for yui to ensure app code is loaded before
tests.
- Yank yui loader closures around tests, this is an anti-pattern.
- Any test file can be run in isolation now.
- Re-order test loading into alphabetical order to detect/correct
collisions.
- Enable using firefox for tests via test loader fix.
- WIP enable using phantomjs for running tests on cli.
- Make topology-mega.js more foregiving on its (there's a event
subscription leak here.)
- Fix event subscription leak in charm-panel code.

https://code.launchpad.net/~hazmat/juju-gui/reliable-test/+merge/141197

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/7003054/

Affected files:
   A [revision details]
   M app/app.js
   M app/modules-debug.js
   M app/templates/overview.handlebars
   M app/views/charm-panel.js
   M app/views/environment.js
   M app/views/topology/mega.js
   M app/views/topology/panzoom.js
   M app/views/utils.js
   M package.json
   M test/index.html
   M test/test_app.js
   M test/test_app_hotkeys.js
   M test/test_d3_components.js
   M test/test_notifications.js
   M test/test_notifier_widget.js

Revision history for this message
Benjamin Saller (bcsaller) wrote :

This looks good, and looks like a real improvement, I made some changes
below that I think help as well.

Not sure why Y.use around the tests is an anti-pattern. With this style
of loading it all up front we could almost get away with use('*') which
would bind all the modules to the Y object but that gives up something
about scoped dependencies in the tests. Happy with the improvement.

https://codereview.appspot.com/7003054/diff/1/test/index.html
File test/index.html (left):

https://codereview.appspot.com/7003054/diff/1/test/index.html#oldcode44
test/index.html:44: <script>
I changed to a pre-load global config object and added use of the
delayUntil option. I think this makes this already nice improvement a
little nicer.

=== modified file 'test/index.html'
--- test/index.html 2012-12-24 03:17:11 +0000
+++ test/index.html 2012-12-24 18:21:50 +0000
@@ -53,29 +53,29 @@

    <script>
- YUI({'async': false}).use('node', 'event', function(Y) {
- Y.on('domready', function() {
-
- var config = GlobalConfig;
-
- config.async = false;
- config.consoleEnabled = true;
-
- for (group in config.groups) {
+ YUI_config = {
+ async: false,
+ consoleEnabled: true,
+ delayUntil: 'domready'
+ };
+
+ YUI().use(['node', 'event'], function(Y) {
+ var config = GlobalConfig;
+
+ for (group in config.groups) {
            var group = config.groups[group];
- for (m in group.modules) {
- var resource = group.modules[m];
- if (!m || !resource.fullpath) {
- continue
- }
- resource.fullpath = resource.fullpath.replace(
- '/juju-ui/', '../juju-ui/');
- }
- }
- // Run the tests.
- if (window.mochaPhantomJS) { mochaPhantomJS.run(); }
- else { mocha.run(); }
- });
+ for (m in group.modules) {
+ var resource = group.modules[m];
+ if (!m || !resource.fullpath) {
+ continue
+ }
+ resource.fullpath = resource.fullpath.replace(
+ '/juju-ui/', '../juju-ui/');
+ }
+ }
+ // Run the tests.
+ if (window.mochaPhantomJS) { mochaPhantomJS.run(); }
+ else { mocha.run(); }
    });
    </script>

https://codereview.appspot.com/7003054/

Revision history for this message
Brad Crittenden (bac) wrote :

These changes look good and should help standardize the way the tests
are run. Like Ben I'm curious as to the new pattern for Y.use in the
tests.

https://codereview.appspot.com/7003054/diff/1/app/app.js
File app/app.js (right):

https://codereview.appspot.com/7003054/diff/1/app/app.js#newcode754
app/app.js:754: // This alias doesn't seem to work, including refs by
hand.
It might be clear to say

The juju-controllers alias...

And if it doesn't work can we get rid of it?

https://codereview.appspot.com/7003054/diff/1/app/modules-debug.js
File app/modules-debug.js (right):

https://codereview.appspot.com/7003054/diff/1/app/modules-debug.js#newcode19
app/modules-debug.js:19:
Why are we combining for the development use? Won't that make debugging
more difficult?

https://codereview.appspot.com/7003054/diff/1/app/views/topology/panzoom.js
File app/views/topology/panzoom.js (right):

https://codereview.appspot.com/7003054/diff/1/app/views/topology/panzoom.js#newcode187
app/views/topology/panzoom.js:187: 'd3',
Just curious why you did the re-ordering. Is it required? Or are you
just grouping by hierarchy?

https://codereview.appspot.com/7003054/diff/1/test/index.html
File test/index.html (right):

https://codereview.appspot.com/7003054/diff/1/test/index.html#newcode31
test/index.html:31: <!-- Tests (Alphabetical)-->
Thanks!

https://codereview.appspot.com/7003054/diff/1/test/index.html#newcode61
test/index.html:61: config.async = false;
Is setting async in the YUI() call and here in the config necessary? A
comment explaining why the redundancy is required might be helpful.

https://codereview.appspot.com/7003054/

Revision history for this message
Kapil Thangavelu (hazmat) wrote :

On 2012/12/24 18:26:11, bcsaller wrote:
> This looks good, and looks like a real improvement, I made some
changes below
> that I think help as well.

> Not sure why Y.use around the tests is an anti-pattern. With this
style of
> loading it all up front we could almost get away with use('*') which
would bind
> all the modules to the Y object but that gives up something about
scoped
> dependencies in the tests. Happy with the improvement.

in practice Y.use around the tests made test loading unreliable. mocha
scan would often complete before the Y.use had finished resulting in no
tests founds for a module.

> https://codereview.appspot.com/7003054/diff/1/test/index.html
> File test/index.html (left):

https://codereview.appspot.com/7003054/diff/1/test/index.html#oldcode44
> test/index.html:44: <script>
> I changed to a pre-load global config object and added use of the
delayUntil
> option. I think this makes this already nice improvement a little
nicer.

> === modified file 'test/index.html'
> --- test/index.html 2012-12-24 03:17:11 +0000
> +++ test/index.html 2012-12-24 18:21:50 +0000
> @@ -53,29 +53,29 @@

> <script>
> - YUI({'async': false}).use('node', 'event', function(Y) {
> - Y.on('domready', function() {
> -
> - var config = GlobalConfig;
> -
> - config.async = false;
> - config.consoleEnabled = true;
> -
> - for (group in config.groups) {
> + YUI_config = {
> + async: false,
> + consoleEnabled: true,
> + delayUntil: 'domready'
> + };
> +
> + YUI().use(['node', 'event'], function(Y) {
> + var config = GlobalConfig;
> +
> + for (group in config.groups) {
> var group = config.groups[group];
> - for (m in group.modules) {
> - var resource = group.modules[m];
> - if (!m || !resource.fullpath) {
> - continue
> - }
> - resource.fullpath = resource.fullpath.replace(
> - '/juju-ui/', '../juju-ui/');
> - }
> - }
> - // Run the tests.
> - if (window.mochaPhantomJS) { mochaPhantomJS.run(); }
> - else { mocha.run(); }
> - });
> + for (m in group.modules) {
> + var resource = group.modules[m];
> + if (!m || !resource.fullpath) {
> + continue
> + }
> + resource.fullpath = resource.fullpath.replace(
> + '/juju-ui/', '../juju-ui/');
> + }
> + }
> + // Run the tests.
> + if (window.mochaPhantomJS) { mochaPhantomJS.run(); }
> + else { mocha.run(); }
> });
> </script>

looks nice, thanks. i'll give it a shot.

https://codereview.appspot.com/7003054/

lp:~hazmat/juju-gui/reliable-test updated
299. By Kapil Thangavelu

disable rollups in debug, yank yeti

Unmerged revisions

299. By Kapil Thangavelu

disable rollups in debug, yank yeti

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/app.js'
2--- app/app.js 2013-01-07 21:38:42 +0000
3+++ app/app.js 2013-01-09 15:35:25 +0000
4@@ -793,19 +793,30 @@
5
6 }, '0.5.2', {
7 requires: [
8+ 'juju-charm-models',
9+ 'juju-charm-panel',
10+ 'juju-charm-store',
11 'juju-models',
12+<<<<<<< TREE
13 'juju-notification-controller',
14 'juju-charm-models',
15+=======
16+ 'juju-notifications',
17+
18+ // This alias doesn't seem to work, including refs by hand.
19+ 'juju-controllers',
20+ 'juju-notification-controller',
21+ 'juju-env',
22+
23+>>>>>>> MERGE-SOURCE
24 'juju-views',
25- 'juju-controllers',
26- 'juju-view-charm-search',
27+ 'juju-templates',
28+ 'handlebars',
29 'io',
30 'json-parse',
31 'app-base',
32 'app-transitions',
33 'base',
34 'node',
35- 'model',
36- 'juju-charm-panel',
37- 'juju-charm-store']
38+ 'model']
39 });
40
41=== modified file 'app/modules-debug.js'
42--- app/modules-debug.js 2012-12-21 20:34:38 +0000
43+++ app/modules-debug.js 2013-01-09 15:35:25 +0000
44@@ -12,7 +12,9 @@
45 filter: 'debug',
46 // Set "true" for verbose logging of YUI
47 debug: false,
48+
49 base: '/juju-ui/assets/javascripts/yui/',
50+
51 // Use Rollups
52 combine: false,
53
54@@ -80,6 +82,7 @@
55 'juju-topology': {
56 fullpath: '/juju-ui/views/topology/topology.js'
57 },
58+
59 'juju-view-utils': {
60 fullpath: '/juju-ui/views/utils.js'
61 },
62@@ -163,7 +166,9 @@
63 },
64
65 'juju-controllers': {
66- use: ['juju-env', 'juju-charm-store',
67+ use: [
68+ 'juju-env',
69+ 'juju-charm-store',
70 'juju-notification-controller']
71 },
72
73
74=== modified file 'app/templates/overview.handlebars'
75--- app/templates/overview.handlebars 2012-12-17 15:39:40 +0000
76+++ app/templates/overview.handlebars 2013-01-09 15:35:25 +0000
77@@ -1,5 +1,5 @@
78 <div class="topology">
79- <div class="topology-canvas crosshatch-background">
80+ <div class="crosshatch-background topology-canvas">
81 <div class="environment-menu" id="service-menu">
82 <div class="triangle">&nbsp;</div>
83 <ul>
84
85=== modified file 'app/views/charm-panel.js'
86--- app/views/charm-panel.js 2012-12-14 20:25:16 +0000
87+++ app/views/charm-panel.js 2013-01-09 15:35:25 +0000
88@@ -1056,11 +1056,11 @@
89 setPanel({name: 'charms'});
90
91 // Update position if we resize the window.
92- Y.on('windowresize', function(e) {
93+ subscriptions.push(Y.on('windowresize', function(e) {
94 if (isPanelVisible) {
95 updatePanelPosition();
96 }
97- });
98+ }));
99
100 /**
101 * Hide the charm panel.
102@@ -1229,6 +1229,7 @@
103 requires: [
104 'view',
105 'juju-view-utils',
106+ 'juju-templates',
107 'node',
108 'handlebars',
109 'event-hover',
110
111=== modified file 'app/views/environment.js'
112--- app/views/environment.js 2012-12-20 17:28:17 +0000
113+++ app/views/environment.js 2013-01-09 15:35:25 +0000
114@@ -34,10 +34,10 @@
115
116 //If we need the initial HTML template
117 // take care of that.
118- if (!this.svg) {
119+ if (!this.rendered) {
120 EnvironmentView.superclass.render.apply(this, arguments);
121 container.setHTML(Templates.overview());
122- this.svg = container.one('.topology');
123+ this.rendered = true;
124 }
125
126 if (!topo) {
127@@ -79,13 +79,10 @@
128 requires: ['juju-templates',
129 'juju-view-utils',
130 'juju-models',
131- 'd3',
132- 'd3-components',
133+ 'juju-topology',
134+ 'svg-layouts',
135 'base-build',
136 'handlebars-base',
137 'node',
138- 'svg-layouts',
139- 'event-resize',
140- 'slider',
141 'view']
142 });
143
144=== modified file 'app/views/topology/mega.js'
145--- app/views/topology/mega.js 2013-01-08 16:08:41 +0000
146+++ app/views/topology/mega.js 2013-01-09 15:35:25 +0000
147@@ -721,6 +721,12 @@
148 svg = container.one('svg'),
149 canvas = container.one('.topology-canvas');
150
151+
152+ // Test band-aid due to lack of cleanup.
153+ if (!canvas) {
154+ return;
155+ }
156+
157 topo.fire('beforePageSizeRecalculation');
158 // Get the canvas out of the way so we can calculate the size
159 // correctly (the canvas contains the svg). We want it to be the
160@@ -894,8 +900,6 @@
161 'd3',
162 'd3-components',
163 'juju-templates',
164- 'node',
165- 'event',
166 'juju-models',
167 'juju-env'
168 ]
169
170=== modified file 'app/views/topology/panzoom.js'
171--- app/views/topology/panzoom.js 2012-12-21 20:46:01 +0000
172+++ app/views/topology/panzoom.js 2013-01-09 15:35:25 +0000
173@@ -180,10 +180,11 @@
174 views.PanZoomModule = PanZoomModule;
175 }, '0.1.0', {
176 requires: [
177+ 'node',
178+ 'event',
179+ 'slider',
180 'd3',
181 'd3-components',
182- 'node',
183- 'event',
184 'juju-models',
185 'juju-env'
186 ]
187
188=== modified file 'app/views/utils.js'
189--- app/views/utils.js 2012-12-20 21:59:21 +0000
190+++ app/views/utils.js 2013-01-09 15:35:25 +0000
191@@ -118,6 +118,8 @@
192 time: noop,
193 timeEnd: noop,
194 log: noop,
195+ info: noop,
196+ error: noop,
197 debug: noop
198 };
199
200@@ -970,12 +972,13 @@
201 });
202
203 }, '0.1.0', {
204- requires: ['base-build',
205+ requires: [
206 'handlebars',
207 'node',
208 'view',
209 'panel',
210 'json-stringify',
211 'gallery-markdown',
212- 'datatype-date-format']
213+ 'datatype-date-format',
214+ 'base-build',]
215 });
216
217=== modified file 'package.json'
218--- package.json 2013-01-02 12:57:09 +0000
219+++ package.json 2013-01-09 15:35:25 +0000
220@@ -15,7 +15,11 @@
221 "cryptojs": ">= 2.5.3"
222 },
223 "devDependencies": {
224+<<<<<<< TREE
225 "d3": "2.10.x",
226+=======
227+ "d3": "<3.0.0",
228+>>>>>>> MERGE-SOURCE
229 "yui": ">=3.7.0",
230 "mocha": "1.5.x",
231 "express": "3.x",
232
233=== modified file 'test/index.html'
234--- test/index.html 2013-01-07 21:38:42 +0000
235+++ test/index.html 2013-01-09 15:35:25 +0000
236@@ -3,15 +3,30 @@
237 <head>
238 <meta charset="utf-8">
239 <link rel="stylesheet" href="assets/mocha.css">
240+
241+
242+ <!-- Load test runner/environment -->
243+ <script src="assets/chai.js"></script>
244+ <script src="assets/mocha.js"></script>
245+ <script>
246+ var assert = chai.assert,
247+ expect = chai.expect;
248+
249+ var should = chai.should();
250+ console.log('mocha setup');
251+ mocha.setup({'ui': 'bdd', 'ignoreLeaks': false, 'timeout': 20000})
252+ console.log('mocha setup done');
253+ </script>
254+
255+ <!-- Load up YUI base, app modules, and test utils -->
256+ <!-- Since only the tests depend on these files and the prod tests disable
257+ the YUI loader, we have to include them manually here. -->
258 <script src="/juju-ui/assets/modules.js"></script>
259 <script src="/juju-ui/assets/all-yui.js"></script>
260- <!-- Since only the tests depend on these files and the prod tests disable
261- the YUI loader, we have to include them manually here. -->
262 <script src="/juju-ui/assets/event-simulate.js"></script>
263 <script src="/juju-ui/assets/node-event-simulate.js"></script>
264- <script src="assets/chai.js"></script>
265- <script src="assets/mocha.js"></script>
266 <script src="utils.js"></script>
267+<<<<<<< TREE
268 <script>
269 noLogin = true;
270 var assert = chai.assert,
271@@ -43,20 +58,38 @@
272 });
273 </script>
274
275+=======
276+
277+
278+ <!-- Tests (Alphabetical)-->
279+ <script src="test_app.js"></script>
280+ <script src="test_app_hotkeys.js"></script>
281+ <script src="test_application_notifications.js"></script>
282+ <script src="test_charm_collection_view.js"></script>
283+ <script src="test_charm_configuration.js"></script>
284+ <script src="test_charm_panel.js"></script>
285+ <script src="test_charm_store.js"></script>
286+ <script src="test_charm_view.js"></script>
287+ <script src="test_console.js"></script>
288+>>>>>>> MERGE-SOURCE
289 <script src="test_d3_components.js"></script>
290+<<<<<<< TREE
291 <script src="test_topology.js"></script>
292 <script src="test_panzoom.js"></script>
293+=======
294+ <script src="test_environment_view.js"></script>
295+>>>>>>> MERGE-SOURCE
296 <script src="test_env.js"></script>
297+ <script src="test_endpoints.js"></script>
298 <script src="test_model.js"></script>
299 <script src="test_notifications.js"></script>
300- <script src="test_app.js"></script>
301- <script src="test_unit_view.js"></script>
302- <script src="test_charm_collection_view.js"></script>
303- <script src="test_charm_view.js"></script>
304- <script src="test_environment_view.js"></script>
305+ <script src="test_notifier_widget.js"></script>
306+ <script src="test_topology.js"></script>
307 <script src="test_service_config_view.js"></script>
308 <script src="test_service_view.js"></script>
309+ <script src="test_unit_view.js"></script>
310 <script src="test_utils.js"></script>
311+<<<<<<< TREE
312 <script src="test_login.js"></script>
313 <script src="test_charm_panel.js"></script>
314 <script src="test_charm_configuration.js"></script>
315@@ -66,7 +99,39 @@
316 <script src="test_charm_store.js"></script>
317 <script src="test_app_hotkeys.js"></script>
318 <script src="test_notifier_widget.js"></script>
319-
320+=======
321+
322+>>>>>>> MERGE-SOURCE
323+
324+<<<<<<< TREE
325+=======
326+ <script>
327+ YUI({'async': false}).use('node', 'event', function(Y) {
328+ Y.on('domready', function() {
329+
330+ var config = GlobalConfig;
331+
332+ config.async = false;
333+ config.consoleEnabled = true;
334+
335+ for (group in config.groups) {
336+ var group = config.groups[group];
337+ for (m in group.modules) {
338+ var resource = group.modules[m];
339+ if (!m || !resource.fullpath) {
340+ continue
341+ }
342+ resource.fullpath = resource.fullpath.replace(
343+ '/juju-ui/', '../juju-ui/');
344+ }
345+ }
346+ // Run the tests.
347+ if (window.mochaPhantomJS) { mochaPhantomJS.run(); }
348+ else { mocha.run(); }
349+ });
350+ });
351+ </script>
352+>>>>>>> MERGE-SOURCE
353
354 </head>
355
356
357=== modified file 'test/test_app.js'
358--- test/test_app.js 2013-01-02 20:39:49 +0000
359+++ test/test_app.js 2013-01-09 15:35:25 +0000
360@@ -31,9 +31,18 @@
361 return app;
362 }
363
364-YUI(GlobalConfig).use(['juju-gui', 'juju-tests-utils'], function(Y) {
365+(function() {
366+
367 describe('Application basics', function() {
368- var app, container;
369+ var Y, app, container;
370+
371+ before(function(done) {
372+ Y = YUI(GlobalConfig).use(
373+ ['juju-gui', 'juju-tests-utils'],
374+ function(Y) {
375+ done();
376+ });
377+ });
378
379 beforeEach(function() {
380 container = Y.one('#main')
381@@ -116,15 +125,29 @@
382 });
383
384 });
385+<<<<<<< TREE
386 });
387
388 YUI(GlobalConfig).use(['juju-gui', 'juju-tests-utils'], function(Y) {
389
390+=======
391+})();
392+
393+
394+
395+(function() {
396+
397+>>>>>>> MERGE-SOURCE
398 describe('Application Connection State', function() {
399- var container;
400+ var container, Y;
401
402- before(function() {
403- container = Y.Node.create('<div id="test" class="container"></div>');
404+ before(function(done) {
405+ Y = YUI(GlobalConfig).use(['juju-gui', 'juju-tests-utils'],
406+ function(Y) {
407+ container = Y.Node.create(
408+ '<div id="test" class="container"></div>');
409+ done();
410+ });
411 });
412
413 it('should be able to handle env connection status changes', function() {
414@@ -135,7 +158,6 @@
415 reset_called = false,
416 noop = function() {return this;};
417
418-
419 // mock the db
420 app.db = {
421 // mock out notifications
422@@ -166,15 +188,23 @@
423 });
424
425 });
426-});
427-
428-YUI(GlobalConfig).use(['juju-models', 'juju-gui', 'datasource-local',
429- 'juju-tests-utils', 'json-stringify'], function(Y) {
430+})();
431+
432+
433+(function() {
434+
435 describe('Application prefetching', function() {
436- var models, conn, env, app, container, charm_store, data, juju;
437+ var Y, models, conn, env, app, container, charm_store, data, juju;
438
439- before(function() {
440- models = Y.namespace('juju.models');
441+ before(function(done) {
442+ console.log('Loading App prefetch test code');
443+ Y = YUI(GlobalConfig).use(
444+ ['juju-gui', 'datasource-local',
445+ 'juju-views', 'juju-templates',
446+ 'juju-tests-utils', 'json-stringify'], function(Y) {
447+ models = Y.namespace('juju.models');
448+ done();
449+ });
450 });
451
452 beforeEach(function() {
453@@ -243,4 +273,4 @@
454 get_endpoints_count.should.equal(2);
455 });
456 });
457-});
458+})();
459
460=== modified file 'test/test_app_hotkeys.js'
461--- test/test_app_hotkeys.js 2013-01-07 20:10:00 +0000
462+++ test/test_app_hotkeys.js 2013-01-09 15:35:25 +0000
463@@ -1,11 +1,11 @@
464 'use strict';
465
466-YUI(GlobalConfig).use(['juju-gui', 'juju-tests-utils', 'node-event-simulate'],
467- function(Y) {
468- describe('application hotkeys', function() {
469- var app, container, windowNode;
470+describe('application hotkeys', function() {
471+ var app, container, windowNode, Y;
472
473- before(function(done) {
474+ before(function(done) {
475+ Y = YUI(GlobalConfig).use(
476+ ['juju-gui', 'juju-tests-utils', 'node-event-simulate'], function(Y) {
477 var env = {
478 after: function() {},
479 get: function() {},
480@@ -22,43 +22,45 @@
481 done();
482 });
483
484- beforeEach(function() {
485- container = Y.Node.create('<div/>');
486- Y.one('#main').append(container);
487- app.render();
488- });
489-
490- afterEach(function() {
491- container.remove(true);
492- });
493-
494- it('should listen for alt-S events', function() {
495- var searchInput = Y.Node.create('<input/>');
496- searchInput.set('id', 'charm-search-field');
497- container.append(searchInput);
498- windowNode.simulate('keydown', {
499- keyCode: 83, // "S" key.
500- altKey: true
501- });
502- // Did charm-search-field get the focus?
503- assert.equal(searchInput, Y.one(document.activeElement));
504- });
505-
506- it('should listen for alt-E events', function() {
507- var altEtriggered = false;
508- app.on('navigateTo', function(ev) {
509- if (ev && ev.url === '/') {
510- altEtriggered = true;
511- }
512- // Avoid URL change performed by additional listeners.
513- ev.stopImmediatePropagation();
514- });
515- windowNode.simulate('keydown', {
516- keyCode: 69, // "E" key.
517- altKey: true
518- });
519- assert.isTrue(altEtriggered);
520- });
521-
522- });
523- });
524+ });
525+
526+ beforeEach(function() {
527+ container = Y.Node.create('<div/>');
528+ Y.one('#main').append(container);
529+ app.render();
530+ });
531+
532+ afterEach(function() {
533+ container.remove(true);
534+ });
535+
536+ it('should listen for alt-S events', function() {
537+ var searchInput = Y.Node.create('<input/>');
538+ searchInput.set('id', 'charm-search-field');
539+ container.append(searchInput);
540+ windowNode.simulate('keydown', {
541+ keyCode: 83, // "S" key.
542+ altKey: true
543+ });
544+ // Did charm-search-field get the focus?
545+ assert.equal(searchInput, Y.one(document.activeElement));
546+ });
547+
548+ it('should listen for alt-E events', function() {
549+ var altEtriggered = false;
550+ app.on('navigateTo', function(ev) {
551+ if (ev && ev.url === '/') {
552+ altEtriggered = true;
553+ }
554+ // Avoid URL change performed by additional listeners.
555+ ev.stopImmediatePropagation();
556+ });
557+ windowNode.simulate('keydown', {
558+ keyCode: 69, // "E" key.
559+ altKey: true
560+ });
561+ assert.isTrue(altEtriggered);
562+ });
563+
564+});
565+
566
567=== modified file 'test/test_d3_components.js'
568--- test/test_d3_components.js 2012-12-19 13:45:10 +0000
569+++ test/test_d3_components.js 2013-01-09 15:35:25 +0000
570@@ -32,7 +32,6 @@
571 state.cancelled = true;
572 }
573 });
574-
575 done();
576 });
577 });
578
579=== modified file 'test/test_notifications.js'
580--- test/test_notifications.js 2012-11-23 16:21:32 +0000
581+++ test/test_notifications.js 2013-01-09 15:35:25 +0000
582@@ -1,462 +1,477 @@
583 'use strict';
584
585-YUI(GlobalConfig).use(['juju-gui', 'node-event-simulate', 'juju-tests-utils'],
586+describe('notifications', function() {
587+ var Y, juju, models, views;
588+
589+ var default_env = {
590+ 'result': [
591+ ['service', 'add', {
592+ 'charm': 'cs:precise/wordpress-6',
593+ 'id': 'wordpress',
594+ 'exposed': false
595+ }],
596+ ['service', 'add', {
597+ 'charm': 'cs:precise/mediawiki-3',
598+ 'id': 'mediawiki',
599+ 'exposed': false
600+ }],
601+ ['service', 'add', {
602+ 'charm': 'cs:precise/mysql-6',
603+ 'id': 'mysql'
604+ }],
605+ ['relation', 'add', {
606+ 'interface': 'reversenginx',
607+ 'scope': 'global',
608+ 'endpoints':
609+ [['wordpress', {'role': 'peer', 'name': 'loadbalancer'}]],
610+ 'id': 'relation-0000000000'
611+ }],
612+ ['relation', 'add', {
613+ 'interface': 'mysql',
614+ 'scope': 'global',
615+ 'endpoints':
616+ [['mysql', {'role': 'server', 'name': 'db'}],
617+ ['wordpress', {'role': 'client', 'name': 'db'}]],
618+ 'id': 'relation-0000000001'
619+ }],
620+ ['machine', 'add', {
621+ 'agent-state': 'running',
622+ 'instance-state': 'running',
623+ 'id': 0,
624+ 'instance-id': 'local',
625+ 'dns-name': 'localhost'
626+ }],
627+ ['unit', 'add', {
628+ 'machine': 0,
629+ 'agent-state': 'started',
630+ 'public-address': '192.168.122.113',
631+ 'id': 'wordpress/0'
632+ }],
633+ ['unit', 'add', {
634+ 'machine': 0,
635+ 'agent-state': 'error',
636+ 'public-address': '192.168.122.222',
637+ 'id': 'mysql/0'
638+ }]
639+ ],
640+ 'op': 'delta'
641+ };
642+
643+
644+ before(function(done) {
645+ Y = YUI(GlobalConfig).use([
646+ 'juju-models',
647+ 'juju-views',
648+ 'juju-gui',
649+ 'juju-env',
650+ 'node-event-simulate',
651+ 'juju-tests-utils'],
652+
653 function(Y) {
654- describe('notifications', function() {
655- var juju, models, views;
656-
657- var default_env = {
658- 'result': [
659- ['service', 'add', {
660- 'charm': 'cs:precise/wordpress-6',
661- 'id': 'wordpress',
662- 'exposed': false
663- }],
664- ['service', 'add', {
665- 'charm': 'cs:precise/mediawiki-3',
666- 'id': 'mediawiki',
667- 'exposed': false
668- }],
669- ['service', 'add', {
670- 'charm': 'cs:precise/mysql-6',
671- 'id': 'mysql'
672- }],
673- ['relation', 'add', {
674- 'interface': 'reversenginx',
675- 'scope': 'global',
676- 'endpoints':
677- [['wordpress', {'role': 'peer', 'name': 'loadbalancer'}]],
678- 'id': 'relation-0000000000'
679- }],
680- ['relation', 'add', {
681- 'interface': 'mysql',
682- 'scope': 'global',
683- 'endpoints':
684- [['mysql', {'role': 'server', 'name': 'db'}],
685- ['wordpress', {'role': 'client', 'name': 'db'}]],
686- 'id': 'relation-0000000001'
687- }],
688- ['machine', 'add', {
689- 'agent-state': 'running',
690- 'instance-state': 'running',
691- 'id': 0,
692- 'instance-id': 'local',
693- 'dns-name': 'localhost'
694- }],
695- ['unit', 'add', {
696- 'machine': 0,
697- 'agent-state': 'started',
698- 'public-address': '192.168.122.113',
699- 'id': 'wordpress/0'
700- }],
701- ['unit', 'add', {
702- 'machine': 0,
703- 'agent-state': 'error',
704- 'public-address': '192.168.122.222',
705- 'id': 'mysql/0'
706- }]
707- ],
708- 'op': 'delta'
709- };
710-
711-
712- before(function() {
713- juju = Y.namespace('juju');
714- models = Y.namespace('juju.models');
715- views = Y.namespace('juju.views');
716- });
717-
718- it('must be able to make notification and lists of notifications',
719- function() {
720- var note1 = new models.Notification({
721- title: 'test1',
722- message: 'Hello'
723- }),
724- note2 = new models.Notification({
725- title: 'test2',
726- message: 'I said goodnight!'
727- }),
728- notifications = new models.NotificationList();
729-
730- notifications.add([note1, note2]);
731- notifications.size().should.equal(2);
732-
733- // timestamp should be generated once
734- var ts = note1.get('timestamp');
735- note1.get('timestamp').should.equal(ts);
736- // force an update so we can test ordering
737- // fast execution can result in same timestamp
738- note2.set('timestamp', ts + 1);
739- note2.get('timestamp').should.be.above(ts);
740-
741- // defaults as expected
742- note1.get('level').should.equal('info');
743- note2.get('level').should.equal('info');
744- // the sort order on the list should be by
745- // timestamp
746- notifications.get('title').should.eql(['test2', 'test1']);
747- });
748-
749- it('must be able to render its view with sample data',
750- function() {
751- var note1 = new models.Notification({
752- title: 'test1', message: 'Hello'}),
753- note2 = new models.Notification({
754- title: 'test2', message: 'I said goodnight!'}),
755- notifications = new models.NotificationList(),
756- container = Y.Node.create('<div id="test">'),
757- env = new juju.Environment(),
758- view = new views.NotificationsView({
759- container: container,
760- notifications: notifications,
761- env: env});
762- view.render();
763- // Verify the expected elements appear in the view
764- container.one('#notify-list').should.not.equal(undefined);
765- container.destroy();
766- });
767-
768- it('must be able to limit the size of notification events',
769- function() {
770- var note1 = new models.Notification({
771- title: 'test1',
772- message: 'Hello'
773- }),
774- note2 = new models.Notification({
775- title: 'test2',
776- message: 'I said goodnight!'
777- }),
778- note3 = new models.Notification({
779- title: 'test3',
780- message: 'Never remember'
781- }),
782- notifications = new models.NotificationList({
783- max_size: 2
784- });
785-
786- notifications.add([note1, note2]);
787- notifications.size().should.equal(2);
788-
789- // Adding a new notification should pop the oldest from the list
790- // (we exceed max_size)
791- notifications.add(note3);
792- notifications.size().should.equal(2);
793- notifications.get('title').should.eql(['test3', 'test2']);
794- });
795-
796- it('must be able to get notifications for a given model',
797- function() {
798- var m = new models.Service({id: 'mediawiki'}),
799- note1 = new models.Notification({
800- title: 'test1',
801- message: 'Hello',
802- modelId: m
803- }),
804- note2 = new models.Notification({
805- title: 'test2',
806- message: 'I said goodnight!'
807- }),
808- notifications = new models.NotificationList();
809-
810- notifications.add([note1, note2]);
811- notifications.size().should.equal(2);
812- notifications.getNotificationsForModel(m).should.eql(
813- [note1]);
814-
815- });
816-
817- it('must be able to include and show object links', function() {
818- var container = Y.Node.create('<div id="test">'),
819- conn = new(Y.namespace('juju-tests.utils')).SocketStub(),
820- env = new juju.Environment({conn: conn}),
821- app = new Y.juju.App({env: env, container: container}),
822- db = app.db,
823- mw = db.services.create({id: 'mediawiki',
824- name: 'mediawiki'}),
825- notifications = db.notifications,
826- view = new views.NotificationsOverview({
827- container: container,
828- notifications: notifications,
829- app: app,
830- env: env}).render();
831- // we use overview here for testing as it defaults
832- // to showing all notices
833-
834- // we can use app's routing table to derive a link
835- notifications.create({title: 'Service Down',
836- message: 'Your service has an error',
837- link: app.getModelURL(mw)
838- });
839- view.render();
840- var link = container.one('.notice').one('a');
841- link.getAttribute('href').should.equal(
842- '/service/mediawiki/');
843- link.getHTML().should.contain('View Details');
844-
845-
846- // create a new notice passing the link_title
847- notifications.create({title: 'Service Down',
848- message: 'Your service has an error',
849- link: app.getModelURL(mw),
850- link_title: 'Resolve this'
851- });
852- view.render();
853- link = container.one('.notice').one('a');
854- link.getAttribute('href').should.equal(
855- '/service/mediawiki/');
856- link.getHTML().should.contain('Resolve this');
857- });
858-
859- it('must be able to evict irrelevant notices', function() {
860- var container = Y.Node.create(
861- '<div id="test" class="container"></div>'),
862- conn = new(Y.namespace('juju-tests.utils')).SocketStub(),
863- env = new juju.Environment({conn: conn}),
864- app = new Y.juju.App({
865- env: env,
866- container: container,
867- viewContainer: container
868- });
869- var environment_delta = default_env;
870-
871- var notifications = app.db.notifications,
872- view = new views.NotificationsView({
873- container: container,
874- notifications: notifications,
875- env: app.env}).render();
876-
877-
878- app.env.dispatch_result(environment_delta);
879-
880-
881- notifications.size().should.equal(7);
882- // we have one unit in error
883- view.getShowable().length.should.equal(1);
884-
885- // now fire another delta event marking that node as
886- // started
887- app.env.dispatch_result({result: [['unit', 'change', {
888- 'machine': 0,
889- 'agent-state': 'started',
890- 'public-address': '192.168.122.222',
891- 'id': 'mysql/0'
892- }]], op: 'delta'});
893- notifications.size().should.equal(8);
894- // This should have evicted the prior notice from seen
895- view.getShowable().length.should.equal(0);
896- });
897-
898- it('must properly construct title and message based on level from ' +
899- 'event data',
900- function() {
901- var container = Y.Node.create(
902- '<div id="test" class="container"></div>'),
903- app = new Y.juju.App({
904- container: container,
905- viewContainer: container
906- });
907- var environment_delta = {
908- 'result': [
909- ['service', 'add', {
910- 'charm': 'cs:precise/wordpress-6',
911- 'id': 'wordpress'
912- }],
913- ['service', 'add', {
914- 'charm': 'cs:precise/mediawiki-3',
915- 'id': 'mediawiki'
916- }],
917- ['service', 'add', {
918- 'charm': 'cs:precise/mysql-6',
919- 'id': 'mysql'
920- }],
921- ['unit', 'add', {
922- 'agent-state': 'install-error',
923- 'id': 'wordpress/0'
924- }],
925- ['unit', 'add', {
926- 'agent-state': 'error',
927- 'public-address': '192.168.122.222',
928- 'id': 'mysql/0'
929- }],
930- ['unit', 'add', {
931- 'public-address': '192.168.122.222',
932- 'id': 'mysql/2'
933- }]
934- ],
935- 'op': 'delta'
936- };
937-
938- var notifications = app.db.notifications,
939- view = new views.NotificationsView({
940- container: container,
941- notifications: notifications,
942- app: app,
943- env: app.env}).render();
944-
945- app.env.dispatch_result(environment_delta);
946-
947- notifications.size().should.equal(6);
948- // we have one unit in error
949- var showable = view.getShowable();
950- showable.length.should.equal(2);
951- // The first showable notification should indicate an error.
952- showable[0].level.should.equal('error');
953- showable[0].title.should.equal('Error with mysql/0');
954- showable[0].message.should.equal('Agent-state = error.');
955- // The second showable notification should also indicate an error.
956- showable[1].level.should.equal('error');
957- showable[1].title.should.equal('Error with wordpress/0');
958- showable[1].message.should.equal('Agent-state = install-error.');
959- // The first non-error notice should have an 'info' level and less
960- // severe messaging.
961- var notice = notifications.item(0);
962- notice.get('level').should.equal('info');
963- notice.get('title').should.equal('Problem with mysql/2');
964- notice.get('message').should.equal('');
965- });
966-
967-
968- it('should open on click and close on clickoutside', function(done) {
969- var container = Y.Node.create('<div id="test-container" ' +
970- 'style="display: none" class="container"/>'),
971- notifications = new models.NotificationList(),
972- env = new juju.Environment(),
973- view = new views.NotificationsView({
974- container: container,
975- notifications: notifications,
976- env: env}).render(),
977- indicator;
978-
979- Y.one('body').append(container);
980- notifications.add({title: 'testing', 'level': 'error'});
981- indicator = container.one('#notify-indicator');
982-
983- indicator.simulate('click');
984- indicator.ancestor().hasClass('open').should.equal(true);
985-
986- Y.one('body').simulate('click');
987- indicator.ancestor().hasClass('open').should.equal(false);
988-
989- container.remove();
990- done();
991- });
992- });
993-
994- describe('changing notifications to words', function() {
995- var juju;
996-
997- before(function() {
998- juju = Y.namespace('juju');
999- });
1000-
1001- it('should correctly translate notification operations into English',
1002- function() {
1003- assert.equal(juju._changeNotificationOpToWords('add'), 'created');
1004- assert.equal(juju._changeNotificationOpToWords('remove'),
1005- 'removed');
1006- assert.equal(juju._changeNotificationOpToWords('not-an-op'),
1007- 'changed');
1008- });
1009- });
1010-
1011- describe('relation notifications', function() {
1012- var juju;
1013-
1014- before(function() {
1015- juju = Y.namespace('juju');
1016- });
1017-
1018- it('should produce reasonable titles', function() {
1019- assert.equal(
1020- juju._relationNotifications.title(undefined, 'add'),
1021- 'Relation created');
1022- assert.equal(
1023- juju._relationNotifications.title(undefined, 'remove'),
1024- 'Relation removed');
1025- });
1026-
1027- it('should generate messages about two-party relations', function() {
1028- var changeData =
1029- { endpoints:
1030- [['endpoint0', {name: 'relation0'}],
1031- ['endpoint1', {name: 'relation1'}]]};
1032- assert.equal(
1033- juju._relationNotifications.message(undefined, 'add',
1034- changeData), 'Relation between endpoint0 (relation type ' +
1035- '"relation0") and endpoint1 (relation type "relation1") ' +
1036- 'was created');
1037- });
1038-
1039- it('should generate messages about one-party relations', function() {
1040- var changeData =
1041- { endpoints:
1042- [['endpoint1', {name: 'relation1'}]]};
1043- assert.equal(
1044- juju._relationNotifications.message(undefined, 'add',
1045- changeData), 'Relation with endpoint1 (relation type ' +
1046- '"relation1") was created');
1047- });
1048- });
1049-
1050- describe('notification visual feedback', function() {
1051- var env, models, notifications, notificationsView, notifierBox, views;
1052-
1053- before(function() {
1054+ juju = Y.namespace('juju');
1055+ models = Y.namespace('juju.models');
1056+ views = Y.namespace('juju.views');
1057+ done();
1058+ });
1059+ });
1060+
1061+ it('must be able to make notification and lists of notifications',
1062+ function() {
1063+ var note1 = new models.Notification({
1064+ title: 'test1',
1065+ message: 'Hello'
1066+ }),
1067+ note2 = new models.Notification({
1068+ title: 'test2',
1069+ message: 'I said goodnight!'
1070+ }),
1071+ notifications = new models.NotificationList();
1072+
1073+ notifications.add([note1, note2]);
1074+ notifications.size().should.equal(2);
1075+
1076+ // timestamp should be generated once
1077+ var ts = note1.get('timestamp');
1078+ note1.get('timestamp').should.equal(ts);
1079+ // force an update so we can test ordering
1080+ // fast execution can result in same timestamp
1081+ note2.set('timestamp', ts + 1);
1082+ note2.get('timestamp').should.be.above(ts);
1083+
1084+ // defaults as expected
1085+ note1.get('level').should.equal('info');
1086+ note2.get('level').should.equal('info');
1087+ // the sort order on the list should be by
1088+ // timestamp
1089+ notifications.get('title').should.eql(['test2', 'test1']);
1090+ });
1091+
1092+ it('must be able to render its view with sample data',
1093+ function() {
1094+ var note1 = new models.Notification({
1095+ title: 'test1', message: 'Hello'}),
1096+ note2 = new models.Notification({
1097+ title: 'test2', message: 'I said goodnight!'}),
1098+ notifications = new models.NotificationList(),
1099+ container = Y.Node.create('<div id="test">'),
1100+ env = new juju.Environment(),
1101+ view = new views.NotificationsView({
1102+ container: container,
1103+ notifications: notifications,
1104+ env: env});
1105+ view.render();
1106+ // Verify the expected elements appear in the view
1107+ container.one('#notify-list').should.not.equal(undefined);
1108+ container.destroy();
1109+ });
1110+
1111+ it('must be able to limit the size of notification events',
1112+ function() {
1113+ var note1 = new models.Notification({
1114+ title: 'test1',
1115+ message: 'Hello'
1116+ }),
1117+ note2 = new models.Notification({
1118+ title: 'test2',
1119+ message: 'I said goodnight!'
1120+ }),
1121+ note3 = new models.Notification({
1122+ title: 'test3',
1123+ message: 'Never remember'
1124+ }),
1125+ notifications = new models.NotificationList({
1126+ max_size: 2
1127+ });
1128+
1129+ notifications.add([note1, note2]);
1130+ notifications.size().should.equal(2);
1131+
1132+ // Adding a new notification should pop the oldest from the list (we
1133+ // exceed max_size)
1134+ notifications.add(note3);
1135+ notifications.size().should.equal(2);
1136+ notifications.get('title').should.eql(['test3', 'test2']);
1137+ });
1138+
1139+ it('must be able to get notifications for a given model',
1140+ function() {
1141+ var m = new models.Service({id: 'mediawiki'}),
1142+ note1 = new models.Notification({
1143+ title: 'test1',
1144+ message: 'Hello',
1145+ modelId: m
1146+ }),
1147+ note2 = new models.Notification({
1148+ title: 'test2',
1149+ message: 'I said goodnight!'
1150+ }),
1151+ notifications = new models.NotificationList();
1152+
1153+ notifications.add([note1, note2]);
1154+ notifications.size().should.equal(2);
1155+ notifications.getNotificationsForModel(m).should.eql(
1156+ [note1]);
1157+
1158+ });
1159+
1160+ it('must be able to include and show object links', function() {
1161+ var container = Y.Node.create('<div id="test">'),
1162+ conn = new(Y.namespace('juju-tests.utils')).SocketStub(),
1163+ env = new juju.Environment({conn: conn}),
1164+ app = new Y.juju.App({env: env, container: container}),
1165+ db = app.db,
1166+ mw = db.services.create({id: 'mediawiki',
1167+ name: 'mediawiki'}),
1168+ notifications = db.notifications,
1169+ view = new views.NotificationsOverview({
1170+ container: container,
1171+ notifications: notifications,
1172+ app: app,
1173+ env: env}).render();
1174+ // we use overview here for testing as it defaults
1175+ // to showing all notices
1176+
1177+ // we can use app's routing table to derive a link
1178+ notifications.create({title: 'Service Down',
1179+ message: 'Your service has an error',
1180+ link: app.getModelURL(mw)
1181+ });
1182+ view.render();
1183+ var link = container.one('.notice').one('a');
1184+ link.getAttribute('href').should.equal(
1185+ '/service/mediawiki/');
1186+ link.getHTML().should.contain('View Details');
1187+
1188+
1189+ // create a new notice passing the link_title
1190+ notifications.create({title: 'Service Down',
1191+ message: 'Your service has an error',
1192+ link: app.getModelURL(mw),
1193+ link_title: 'Resolve this'
1194+ });
1195+ view.render();
1196+ link = container.one('.notice').one('a');
1197+ link.getAttribute('href').should.equal(
1198+ '/service/mediawiki/');
1199+ link.getHTML().should.contain('Resolve this');
1200+ });
1201+
1202+ it('must be able to evict irrelevant notices', function() {
1203+ var container = Y.Node.create(
1204+ '<div id="test" class="container"></div>'),
1205+ conn = new(Y.namespace('juju-tests.utils')).SocketStub(),
1206+ env = new juju.Environment({conn: conn}),
1207+ app = new Y.juju.App({
1208+ env: env,
1209+ container: container,
1210+ viewContainer: container
1211+ });
1212+ var environment_delta = default_env;
1213+
1214+ var notifications = app.db.notifications,
1215+ view = new views.NotificationsView({
1216+ container: container,
1217+ notifications: notifications,
1218+ env: app.env}).render();
1219+
1220+
1221+ app.env.dispatch_result(environment_delta);
1222+
1223+
1224+ notifications.size().should.equal(7);
1225+ // we have one unit in error
1226+ view.getShowable().length.should.equal(1);
1227+
1228+ // now fire another delta event marking that node as
1229+ // started
1230+ app.env.dispatch_result({result: [['unit', 'change', {
1231+ 'machine': 0,
1232+ 'agent-state': 'started',
1233+ 'public-address': '192.168.122.222',
1234+ 'id': 'mysql/0'
1235+ }]], op: 'delta'});
1236+ notifications.size().should.equal(8);
1237+ // This should have evicted the prior notice from seen
1238+ view.getShowable().length.should.equal(0);
1239+ });
1240+
1241+ it('must properly construct title and message based on level from ' +
1242+ 'event data',
1243+ function() {
1244+ var container = Y.Node.create(
1245+ '<div id="test" class="container"></div>'),
1246+ app = new Y.juju.App({
1247+ container: container,
1248+ viewContainer: container
1249+ });
1250+ var environment_delta = {
1251+ 'result': [
1252+ ['service', 'add', {
1253+ 'charm': 'cs:precise/wordpress-6',
1254+ 'id': 'wordpress'
1255+ }],
1256+ ['service', 'add', {
1257+ 'charm': 'cs:precise/mediawiki-3',
1258+ 'id': 'mediawiki'
1259+ }],
1260+ ['service', 'add', {
1261+ 'charm': 'cs:precise/mysql-6',
1262+ 'id': 'mysql'
1263+ }],
1264+ ['unit', 'add', {
1265+ 'agent-state': 'install-error',
1266+ 'id': 'wordpress/0'
1267+ }],
1268+ ['unit', 'add', {
1269+ 'agent-state': 'error',
1270+ 'public-address': '192.168.122.222',
1271+ 'id': 'mysql/0'
1272+ }],
1273+ ['unit', 'add', {
1274+ 'public-address': '192.168.122.222',
1275+ 'id': 'mysql/2'
1276+ }]
1277+ ],
1278+ 'op': 'delta'
1279+ };
1280+
1281+ var notifications = app.db.notifications,
1282+ view = new views.NotificationsView({
1283+ container: container,
1284+ notifications: notifications,
1285+ app: app,
1286+ env: app.env}).render();
1287+
1288+ app.env.dispatch_result(environment_delta);
1289+
1290+ notifications.size().should.equal(6);
1291+ // we have one unit in error
1292+ var showable = view.getShowable();
1293+ showable.length.should.equal(2);
1294+ // The first showable notification should indicate an error.
1295+ showable[0].level.should.equal('error');
1296+ showable[0].title.should.equal('Error with mysql/0');
1297+ showable[0].message.should.equal('Agent-state = error.');
1298+ // The second showable notification should also indicate an error.
1299+ showable[1].level.should.equal('error');
1300+ showable[1].title.should.equal('Error with wordpress/0');
1301+ showable[1].message.should.equal('Agent-state = install-error.');
1302+ // The first non-error notice should have an 'info' level and less
1303+ // severe messaging.
1304+ var notice = notifications.item(0);
1305+ notice.get('level').should.equal('info');
1306+ notice.get('title').should.equal('Problem with mysql/2');
1307+ notice.get('message').should.equal('');
1308+ });
1309+
1310+
1311+ it('should open on click and close on clickoutside', function(done) {
1312+ var container = Y.Node.create(
1313+ '<div id="test-container" style="display: none" class="container"/>'),
1314+ notifications = new models.NotificationList(),
1315+ env = new juju.Environment(),
1316+ view = new views.NotificationsView({
1317+ container: container,
1318+ notifications: notifications,
1319+ env: env}).render(),
1320+ indicator;
1321+
1322+ Y.one('body').append(container);
1323+ notifications.add({title: 'testing', 'level': 'error'});
1324+ indicator = container.one('#notify-indicator');
1325+
1326+ indicator.simulate('click');
1327+ indicator.ancestor().hasClass('open').should.equal(true);
1328+
1329+ Y.one('body').simulate('click');
1330+ indicator.ancestor().hasClass('open').should.equal(false);
1331+
1332+ container.remove();
1333+ done();
1334+ });
1335+
1336+});
1337+
1338+
1339+describe('changing notifications to words', function() {
1340+ var Y, juju;
1341+
1342+ before(function(done) {
1343+ Y = YUI(GlobalConfig).use(
1344+ ['juju-notification-controller'],
1345+ function(Y) {
1346+ juju = Y.namespace('juju');
1347+ done();
1348+ });
1349+ });
1350+
1351+ it('should correctly translate notification operations into English',
1352+ function() {
1353+ assert.equal(juju._changeNotificationOpToWords('add'), 'created');
1354+ assert.equal(juju._changeNotificationOpToWords('remove'), 'removed');
1355+ assert.equal(juju._changeNotificationOpToWords('not-an-op'), 'changed');
1356+ });
1357+});
1358+
1359+describe('relation notifications', function() {
1360+ var Y, juju;
1361+
1362+ before(function(done) {
1363+ Y = YUI(GlobalConfig).use(
1364+ ['juju-notification-controller'],
1365+ function(Y) {
1366+ juju = Y.namespace('juju');
1367+ done();
1368+ });
1369+ });
1370+
1371+ it('should produce reasonable titles', function() {
1372+ assert.equal(
1373+ juju._relationNotifications.title(undefined, 'add'),
1374+ 'Relation created');
1375+ assert.equal(
1376+ juju._relationNotifications.title(undefined, 'remove'),
1377+ 'Relation removed');
1378+ });
1379+
1380+ it('should generate messages about two-party relations', function() {
1381+ var changeData =
1382+ { endpoints:
1383+ [['endpoint0', {name: 'relation0'}],
1384+ ['endpoint1', {name: 'relation1'}]]};
1385+ assert.equal(
1386+ juju._relationNotifications.message(undefined, 'add', changeData),
1387+ 'Relation between endpoint0 (relation type "relation0") and ' +
1388+ 'endpoint1 (relation type "relation1") was created');
1389+ });
1390+
1391+ it('should generate messages about one-party relations', function() {
1392+ var changeData =
1393+ { endpoints:
1394+ [['endpoint1', {name: 'relation1'}]]};
1395+ assert.equal(
1396+ juju._relationNotifications.message(undefined, 'add', changeData),
1397+ 'Relation with endpoint1 (relation type "relation1") was created');
1398+ });
1399+});
1400+
1401+describe('notification visual feedback', function() {
1402+ var env, models, notifications, notificationsView, notifierBox, views, Y;
1403+
1404+ before(function(done) {
1405+ Y = YUI(GlobalConfig).use('juju-env', 'juju-models', 'juju-views',
1406+ function(Y) {
1407 var juju = Y.namespace('juju');
1408 env = new juju.Environment();
1409 models = Y.namespace('juju.models');
1410 views = Y.namespace('juju.views');
1411- });
1412-
1413- // Instantiate the notifications model list and view.
1414- // Also create the notifier box and attach it as first element of the
1415- // body.
1416- beforeEach(function() {
1417- notifications = new models.NotificationList();
1418- notificationsView = new views.NotificationsView({
1419- env: env,
1420- notifications: notifications
1421- });
1422- notifierBox = Y.Node.create('<div id="notifier-box"></div>');
1423- notifierBox.setStyle('display', 'none');
1424- Y.one('body').prepend(notifierBox);
1425- });
1426-
1427- // Destroy the notifier box created in beforeEach.
1428- afterEach(function() {
1429- notifierBox.remove();
1430- notifierBox.destroy(true);
1431- });
1432-
1433- // Assert the notifier box contains the expectedNumber of notifiers.
1434- var assertNumNotifiers = function(expectedNumber) {
1435- assert.equal(expectedNumber, notifierBox.get('children').size());
1436- };
1437-
1438- it('should appear when a new error is notified', function() {
1439- notifications.add({title: 'mytitle', level: 'error'});
1440- assertNumNotifiers(1);
1441- });
1442-
1443- it('should only appear when the DOM contains the notifier box',
1444- function() {
1445- notifierBox.remove();
1446- notifications.add({title: 'mytitle', level: 'error'});
1447- assertNumNotifiers(0);
1448- });
1449-
1450- it('should not appear when the notification is not an error',
1451- function() {
1452- notifications.add({title: 'mytitle', level: 'info'});
1453- assertNumNotifiers(0);
1454- });
1455-
1456- it('should not appear when the notification comes form delta',
1457- function() {
1458- notifications.add({title: 'mytitle', level: 'error', isDelta:
1459- true});
1460- assertNumNotifiers(0);
1461- });
1462-
1463- });
1464+ done();
1465+ });
1466+ });
1467+
1468+ // Instantiate the notifications model list and view.
1469+ // Also create the notifier box and attach it as first element of the body.
1470+ beforeEach(function() {
1471+ notifications = new models.NotificationList();
1472+ notificationsView = new views.NotificationsView({
1473+ env: env,
1474+ notifications: notifications
1475 });
1476+ notifierBox = Y.Node.create('<div id="notifier-box"></div>');
1477+ notifierBox.setStyle('display', 'none');
1478+ Y.one('body').prepend(notifierBox);
1479+ });
1480+
1481+ // Destroy the notifier box created in beforeEach.
1482+ afterEach(function() {
1483+ notifierBox.remove();
1484+ notifierBox.destroy(true);
1485+ });
1486+
1487+ // Assert the notifier box contains the expectedNumber of notifiers.
1488+ var assertNumNotifiers = function(expectedNumber) {
1489+ assert.equal(expectedNumber, notifierBox.get('children').size());
1490+ };
1491+
1492+ it('should appear when a new error is notified', function() {
1493+ notifications.add({title: 'mytitle', level: 'error'});
1494+ assertNumNotifiers(1);
1495+ });
1496+
1497+ it('should only appear when the DOM contains the notifier box', function() {
1498+ notifierBox.remove();
1499+ notifications.add({title: 'mytitle', level: 'error'});
1500+ assertNumNotifiers(0);
1501+ });
1502+
1503+ it('should not appear when the notification is not an error', function() {
1504+ notifications.add({title: 'mytitle', level: 'info'});
1505+ assertNumNotifiers(0);
1506+ });
1507+
1508+ it('should not appear when the notification comes form delta', function() {
1509+ notifications.add({title: 'mytitle', level: 'error', isDelta: true});
1510+ assertNumNotifiers(0);
1511+ });
1512+
1513+});
1514
1515=== modified file 'test/test_notifier_widget.js'
1516--- test/test_notifier_widget.js 2012-11-23 16:21:32 +0000
1517+++ test/test_notifier_widget.js 2013-01-09 15:35:25 +0000
1518@@ -1,101 +1,104 @@
1519 'use strict';
1520
1521-YUI(GlobalConfig).use(['notifier', 'node-event-simulate'], function(Y) {
1522- describe('notifier widget', function() {
1523- var Notifier, notifierBox;
1524-
1525- before(function() {
1526- Notifier = Y.namespace('juju.widgets').Notifier;
1527- });
1528-
1529- // Create the notifier box and attach it as first element of the body.
1530- beforeEach(function() {
1531- notifierBox = Y.Node.create('<div id="notifier-box"></div>');
1532- notifierBox.setStyle('display', 'none');
1533- Y.one('body').prepend(notifierBox);
1534- });
1535-
1536- // Destroy the notifier box created in beforeEach.
1537- afterEach(function() {
1538- notifierBox.remove();
1539- notifierBox.destroy(true);
1540- });
1541-
1542- // Factory rendering and returning a notifier instance.
1543- var makeNotifier = function(title, message, timeout) {
1544- var notifier = new Notifier({
1545- title: title || 'mytitle',
1546- message: message || 'mymessage',
1547- timeout: timeout || 10000
1548- });
1549- notifier.render(notifierBox);
1550- return notifier;
1551- };
1552-
1553- // Assert the notifier box contains the expectedNumber of notifiers.
1554- var assertNumNotifiers = function(expectedNumber) {
1555- assert.equal(expectedNumber, notifierBox.get('children').size());
1556- };
1557-
1558- it('should be able to display a notification', function() {
1559+
1560+describe('notifier widget', function() {
1561+ var Notifier, notifierBox, Y;
1562+
1563+ before(function(done) {
1564+ Y = YUI(GlobalConfig).use(['notifier', 'node-event-simulate'], function(Y) {
1565+ Notifier = Y.namespace('juju.widgets').Notifier;
1566+ done();
1567+ });
1568+ });
1569+
1570+ // Create the notifier box and attach it as first element of the body.
1571+ beforeEach(function() {
1572+ notifierBox = Y.Node.create('<div id="notifier-box"></div>');
1573+ notifierBox.setStyle('display', 'none');
1574+ Y.one('body').prepend(notifierBox);
1575+ });
1576+
1577+ // Destroy the notifier box created in beforeEach.
1578+ afterEach(function() {
1579+ notifierBox.remove();
1580+ notifierBox.destroy(true);
1581+ });
1582+
1583+ // Factory rendering and returning a notifier instance.
1584+ var makeNotifier = function(title, message, timeout) {
1585+ var notifier = new Notifier({
1586+ title: title || 'mytitle',
1587+ message: message || 'mymessage',
1588+ timeout: timeout || 10000
1589+ });
1590+ notifier.render(notifierBox);
1591+ return notifier;
1592+ };
1593+
1594+ // Assert the notifier box contains the expectedNumber of notifiers.
1595+ var assertNumNotifiers = function(expectedNumber) {
1596+ assert.equal(expectedNumber, notifierBox.get('children').size());
1597+ };
1598+
1599+ it('should be able to display a notification', function() {
1600+ makeNotifier();
1601+ assertNumNotifiers(1);
1602+ });
1603+
1604+ it('should display the given title and message', function() {
1605+ makeNotifier('mytitle', 'mymessage');
1606+ var notifierNode = notifierBox.one('*');
1607+ assert.equal('mytitle', notifierNode.one('h3').getContent());
1608+ assert.equal('mymessage', notifierNode.one('div').getContent());
1609+ });
1610+
1611+ it('should be able to display multiple notifications', function() {
1612+ var number = 10;
1613+ for (var i = 0; i < number; i += 1) {
1614 makeNotifier();
1615+ }
1616+ assertNumNotifiers(number);
1617+ });
1618+
1619+ it('should display new notifications on top', function() {
1620+ makeNotifier('mytitle1', 'mymessage1');
1621+ makeNotifier('mytitle2', 'mymessage2');
1622+ var notifierNode = notifierBox.one('*');
1623+ assert.equal('mytitle2', notifierNode.one('h3').getContent());
1624+ assert.equal('mymessage2', notifierNode.one('div').getContent());
1625+ });
1626+
1627+ it('should destroy notifications after N milliseconds', function(done) {
1628+ makeNotifier('mytitle', 'mymessage', 1);
1629+ // A timeout of 250 milliseconds is used so that we ensure the destroying
1630+ // animation can be completed.
1631+ setTimeout(function() {
1632+ assertNumNotifiers(0);
1633+ done();
1634+ }, 250);
1635+ });
1636+
1637+ it('should destroy notifications on click', function(done) {
1638+ makeNotifier();
1639+ notifierBox.one('*').simulate('click');
1640+ // A timeout of 250 milliseconds is used so that we ensure the destroying
1641+ // animation can be completed.
1642+ setTimeout(function() {
1643+ assertNumNotifiers(0);
1644+ done();
1645+ }, 250);
1646+ });
1647+
1648+ it('should prevent notification removal on mouse enter', function(done) {
1649+ makeNotifier('mytitle', 'mymessage', 1);
1650+ notifierBox.one('*').simulate('mouseover');
1651+ // A timeout of 250 milliseconds is used so that we ensure the node is not
1652+ // preserved by the destroying animation.
1653+ setTimeout(function() {
1654 assertNumNotifiers(1);
1655- });
1656-
1657- it('should display the given title and message', function() {
1658- makeNotifier('mytitle', 'mymessage');
1659- var notifierNode = notifierBox.one('*');
1660- assert.equal('mytitle', notifierNode.one('h3').getContent());
1661- assert.equal('mymessage', notifierNode.one('div').getContent());
1662- });
1663-
1664- it('should be able to display multiple notifications', function() {
1665- var number = 10;
1666- for (var i = 0; i < number; i += 1) {
1667- makeNotifier();
1668- }
1669- assertNumNotifiers(number);
1670- });
1671-
1672- it('should display new notifications on top', function() {
1673- makeNotifier('mytitle1', 'mymessage1');
1674- makeNotifier('mytitle2', 'mymessage2');
1675- var notifierNode = notifierBox.one('*');
1676- assert.equal('mytitle2', notifierNode.one('h3').getContent());
1677- assert.equal('mymessage2', notifierNode.one('div').getContent());
1678- });
1679-
1680- it('should destroy notifications after N milliseconds', function(done) {
1681- makeNotifier('mytitle', 'mymessage', 1);
1682- // A timeout of 250 milliseconds is used so that we ensure the destroying
1683- // animation can be completed.
1684- setTimeout(function() {
1685- assertNumNotifiers(0);
1686- done();
1687- }, 250);
1688- });
1689-
1690- it('should destroy notifications on click', function(done) {
1691- makeNotifier();
1692- notifierBox.one('*').simulate('click');
1693- // A timeout of 250 milliseconds is used so that we ensure the destroying
1694- // animation can be completed.
1695- setTimeout(function() {
1696- assertNumNotifiers(0);
1697- done();
1698- }, 250);
1699- });
1700-
1701- it('should prevent notification removal on mouse enter', function(done) {
1702- makeNotifier('mytitle', 'mymessage', 1);
1703- notifierBox.one('*').simulate('mouseover');
1704- // A timeout of 250 milliseconds is used so that we ensure the node is not
1705- // preserved by the destroying animation.
1706- setTimeout(function() {
1707- assertNumNotifiers(1);
1708- done();
1709- }, 250);
1710- });
1711-
1712+ done();
1713+ }, 250);
1714 });
1715+
1716 });
1717+

Subscribers

People subscribed via source and target branches