Merge lp:~tveronezi/juju-gui/change-requires-param into lp:juju-gui/experimental

Proposed by Thiago Veronezi
Status: Merged
Merged at revision: 256
Proposed branch: lp:~tveronezi/juju-gui/change-requires-param
Merge into: lp:juju-gui/experimental
Diff against target: 1823 lines (+808/-847)
10 files modified
app/app.js (+1/-0)
app/models/charm.js (+2/-1)
app/models/models.js (+1/-0)
app/modules-debug.js (+8/-12)
app/store/charm.js (+1/-0)
test/test_app.js (+176/-191)
test/test_app_hotkeys.js (+52/-52)
test/test_model.js (+18/-25)
test/test_notifications.js (+454/-469)
test/test_notifier_widget.js (+95/-97)
To merge this branch: bzr merge lp:~tveronezi/juju-gui/change-requires-param
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+135198@code.launchpad.net

Description of the change

Remove "requires" from modules-debug.js

The minification process does not read the "requires" parameters defined in "modules-debug.js". This parameter should be defined in each custom yui object that uses some kind of internal or external requirement. The sole use of "all-app-debug.js" is to define the fullpath of the file that defines a given module.

This patch removes all the existing "requires" properties from "modules-debug.js" and add then where they are needed.

https://codereview.appspot.com/6856070/

To post a comment you must log in.
Revision history for this message
Thiago Veronezi (tveronezi) wrote :
Download full text (4.1 KiB)

Reviewers: mp+135198_code.launchpad.net,

Message:
Please take a look.

Description:
Remove "requires" from modules-debug.js

The minification process does not read the "requires" parameters defined
in "modules-debug.js". This parameter should be defined in each custom
yui object that uses some kind of internal or external requirement. The
sole use of "all-app-debug.js" is to define the fullpath of the file
that defines a given module.

This patch removes all the existing "requires" properties from
"modules-debug.js" and add then where they are needed.

https://code.launchpad.net/~tveronezi/juju-gui/change-requires-param/+merge/135198

(do not edit description out of merge proposal)

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

Affected files:
   A [revision details]
   M app/app.js
   M app/models/charm.js
   M app/models/models.js
   M app/modules-debug.js
   M app/store/charm.js

Index: [revision details]
=== added file '[revision details]'
--- [revision details] 2012-01-01 00:00:00 +0000
+++ [revision details] 2012-01-01 00:00:00 +0000
@@ -0,0 +1,2 @@
+Old revision: <email address hidden>
+New revision: <email address hidden>

Index: app/app.js
=== modified file 'app/app.js'
--- app/app.js 2012-11-20 14:51:46 +0000
+++ app/app.js 2012-11-20 16:55:43 +0000
@@ -746,6 +746,7 @@
      'juju-models',
      'juju-views',
      'juju-controllers',
+ 'juju-view-charm-search',
      'io',
      'json-parse',
      'app-base',

Index: app/modules-debug.js
=== modified file 'app/modules-debug.js'
--- app/modules-debug.js 2012-11-15 15:44:00 +0000
+++ app/modules-debug.js 2012-11-20 16:55:43 +0000
@@ -1,6 +1,8 @@
  // This file is used for development only. In order to use it you should
call
  // the "make debug" command. This command passes the "debug" argument to
the
-// "lib/server.js".
+// "lib/server.js". The sole use of this file is to define the "aliases"
("use"
+// property) and the "fullpath" of the file that implement a given module.
The
+// "requires" property should not be used here.
  var GlobalConfig = {
    filter: 'debug',
    // Set "true" for verbose logging of YUI
@@ -93,19 +95,15 @@
          },

          'juju-charm-models': {
- requires: ['juju-charm-id'],
            fullpath: '/juju-ui/models/charm.js'
          },

          'juju-models': {
- requires: [
- 'model', 'model-list', 'juju-endpoints', 'juju-charm-models'],
            fullpath: '/juju-ui/models/models.js'
          },

          // Connectivity
          'juju-env': {
- requires: ['reconnecting-websocket'],
            fullpath: '/juju-ui/store/env.js'
          },

@@ -114,7 +112,6 @@
          },

          'juju-charm-store': {
- requires: ['juju-charm-id'],
            fullpath: '/juju-ui/store/charm.js'
          },

@@ -125,13 +122,7 @@

          // App
          'juju-gui': {
- fullpath: '/juju-ui/app.js',
- requires: [
- 'juju-controllers',
- 'juju-views',
- 'juju-models',
- 'juju-view-charm-search'
- ]
+ fullpath: '/juj...

Read more...

256. By Thiago Veronezi

fix tests

257. By Thiago Veronezi

make lint happy

Revision history for this message
Thiago Veronezi (tveronezi) wrote :
Revision history for this message
Gary Poster (gary) wrote :
Download full text (3.1 KiB)

Hi Thiago. Thank you for the quick turnaround on this fix. I have only
done a code review of this so far, and not a functional test/review.
However, I have some concerns with the way you changed the tests, and
I'd like to see those resolved before I approve.

Gary

https://codereview.appspot.com/6856070/diff/3001/app/modules-debug.js
File app/modules-debug.js (left):

https://codereview.appspot.com/6856070/diff/3001/app/modules-debug.js#oldcode96
app/modules-debug.js:96: requires: ['juju-charm-id'],
I don't really understand why these were here instead of in the modules
to begin with. That makes me nervous, because often people have a good
reason for this sort of thing. Do you know the answer?

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

https://codereview.appspot.com/6856070/diff/3001/app/modules-debug.js#newcode4
app/modules-debug.js:4: // property) and the "fullpath" of the file that
implement a given module. The
Might as well take the opportunity to be clearer about what the "use"
property does. ("""This file declares which files implement modules,
using the "fullpath" property; and declares the membership of rollup
modules, using the "use" property to specify what the module name
aliases.""" or something like that.)

https://codereview.appspot.com/6856070/diff/3001/app/modules-debug.js#newcode5
app/modules-debug.js:5: // "requires" property should not be used here.
Please explain why ("...should not be used here because the javascript
minimizer will not parse it" or something like that).

https://codereview.appspot.com/6856070/diff/3001/app/modules-debug.js#newcode16
app/modules-debug.js:16: modules: {
I really am tempted to suggest that the production files should parse
this and rewrite it after removing the "fullpath" modules, instead of
making developers maintain two versions of this "modules" file. Even if
that's a good idea, that's a separate bug/branch. I'm just thinking.

https://codereview.appspot.com/6856070/diff/3001/test/index.html
File test/index.html (right):

https://codereview.appspot.com/6856070/diff/3001/test/index.html#newcode40
test/index.html:40: YUI().use('node', 'event', function(runnerY) {
In terms of scoping rules, I'm pretty sure that this could be "Y" and
the one used by tests, below, could be "Y" also. Did you not do it that
way because you felt it was more readable this way?

https://codereview.appspot.com/6856070/diff/3001/test/index.html#newcode61
test/index.html:61: window.Y = testsYUI;
We usually create our own Y in the tests. Why do we have to do this?
Wouldn't it be better to make our own Y in each test?

https://codereview.appspot.com/6856070/diff/3001/test/test_app.js
File test/test_app.js (left):

https://codereview.appspot.com/6856070/diff/3001/test/test_app.js#oldcode184
test/test_app.js:184: 'json-stringify',
This was specifying different requirements than you have put in the
global Y. We really should not share the Y object. We should keep our
dependencies tight to our tests in order to increase the maintainability
and understandability of our code. This comment addresses all changes
like this one.

https://codereview.apps...

Read more...

Revision history for this message
Madison Scott-Clary (makyo) wrote :

 From IRC, this branch looks good functionally. +1 from me, so long as
Gary's comments are addressed to both of your satisfaction.

https://codereview.appspot.com/6856070/

258. By Thiago Veronezi

code review

Revision history for this message
Thiago Veronezi (tveronezi) wrote :
Download full text (3.6 KiB)

Thanks for your review, guys!

https://codereview.appspot.com/6856070/diff/3001/app/modules-debug.js
File app/modules-debug.js (left):

https://codereview.appspot.com/6856070/diff/3001/app/modules-debug.js#oldcode96
app/modules-debug.js:96: requires: ['juju-charm-id'],
On 2012/11/20 21:01:19, gary.poster wrote:
> I don't really understand why these were here instead of in the
modules to begin
> with. That makes me nervous, because often people have a good reason
for this
> sort of thing. Do you know the answer?

It is not their fault. The problem is that YUI has two places to define
the very same thing... which is not bad when we are loading non yui js
files that depend on other js files.

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

https://codereview.appspot.com/6856070/diff/3001/app/modules-debug.js#newcode4
app/modules-debug.js:4: // property) and the "fullpath" of the file that
implement a given module. The
On 2012/11/20 21:01:19, gary.poster wrote:
> Might as well take the opportunity to be clearer about what the "use"
property
> does. ("""This file declares which files implement modules, using the
> "fullpath" property; and declares the membership of rollup modules,
using the
> "use" property to specify what the module name aliases.""" or
something like
> that.)

Done.

https://codereview.appspot.com/6856070/diff/3001/app/modules-debug.js#newcode5
app/modules-debug.js:5: // "requires" property should not be used here.
On 2012/11/20 21:01:19, gary.poster wrote:
> Please explain why ("...should not be used here because the
javascript
> minimizer will not parse it" or something like that).

Done.

https://codereview.appspot.com/6856070/diff/3001/app/modules-debug.js#newcode16
app/modules-debug.js:16: modules: {
On 2012/11/20 21:01:19, gary.poster wrote:
> I really am tempted to suggest that the production files should parse
this and
> rewrite it after removing the "fullpath" modules, instead of making
developers
> maintain two versions of this "modules" file. Even if that's a good
idea,
> that's a separate bug/branch. I'm just thinking.

I am tempted to simply remove the aliases. They are one of the reasons
we cannot completely turn the loader off, remember?

https://codereview.appspot.com/6856070/diff/3001/test/index.html
File test/index.html (right):

https://codereview.appspot.com/6856070/diff/3001/test/index.html#newcode40
test/index.html:40: YUI().use('node', 'event', function(runnerY) {
On 2012/11/20 21:01:19, gary.poster wrote:
> In terms of scoping rules, I'm pretty sure that this could be "Y" and
the one
> used by tests, below, could be "Y" also. Did you not do it that way
because you
> felt it was more readable this way?

Reverting...

https://codereview.appspot.com/6856070/diff/3001/test/index.html#newcode61
test/index.html:61: window.Y = testsYUI;
On 2012/11/20 21:01:19, gary.poster wrote:
> We usually create our own Y in the tests. Why do we have to do this?
Wouldn't
> it be better to make our own Y in each test?

Reverting...

https://codereview.appspot.com/6856070/diff/3001/test/test_app.js
File test/test_app.js (left):

https://codereview.appspot.com/68...

Read more...

259. By Thiago Veronezi

code review

260. By Thiago Veronezi

code review

Revision history for this message
Thiago Veronezi (tveronezi) wrote :
Revision history for this message
Gary Poster (gary) wrote :

Hi Thiago. I like what you did with the tests, even though I wish it
didn't have to be part of this branch. I'm OK with it landing though.

As I mentioned on IRC, I do see a test failure, but it seems shallow.
Please fix that, and maybe the small comment change I suggest.

Thank you,

Gary

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

https://codereview.appspot.com/6856070/diff/9001/app/modules-debug.js#newcode10
app/modules-debug.js:10: // minimizer will not parse it.
Maybe add "Please put in in the module itself, instead."

https://codereview.appspot.com/6856070/diff/9001/test/test_app.js
File test/test_app.js (right):

https://codereview.appspot.com/6856070/diff/9001/test/test_app.js#newcode34
test/test_app.js:34: YUI(GlobalConfig).use(['juju-gui',
'juju-tests-utils'], function(Y) {
This looks good to me. However, it is a big change to how we write our
tests, and not consistently applied to our tests. As I said on IRC, I
think there is a good argument to change our tests to be written this
way, but I don't understand why we need to change them here, in this
branch.

In the interest of progress, I'm ok with landing this if you then make a
retrospective discussion card for us to collectively determine the way
we want to move forward.

https://codereview.appspot.com/6856070/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'app/app.js'
--- app/app.js 2012-11-20 14:51:46 +0000
+++ app/app.js 2012-11-20 23:07:20 +0000
@@ -746,6 +746,7 @@
746 'juju-models',746 'juju-models',
747 'juju-views',747 'juju-views',
748 'juju-controllers',748 'juju-controllers',
749 'juju-view-charm-search',
749 'io',750 'io',
750 'json-parse',751 'json-parse',
751 'app-base',752 'app-base',
752753
=== modified file 'app/models/charm.js'
--- app/models/charm.js 2012-10-29 11:20:31 +0000
+++ app/models/charm.js 2012-11-20 23:07:20 +0000
@@ -226,6 +226,7 @@
226}, '0.1.0', {226}, '0.1.0', {
227 requires: [227 requires: [
228 'model',228 'model',
229 'model-list'229 'model-list',
230 'juju-charm-id'
230 ]231 ]
231});232});
232233
=== modified file 'app/models/models.js'
--- app/models/models.js 2012-11-16 08:25:02 +0000
+++ app/models/models.js 2012-11-20 23:07:20 +0000
@@ -487,6 +487,7 @@
487 'datasource-jsonschema',487 'datasource-jsonschema',
488 'io-base',488 'io-base',
489 'json-parse',489 'json-parse',
490 'juju-endpoints',
490 'juju-view-utils',491 'juju-view-utils',
491 'juju-charm-models'492 'juju-charm-models'
492 ]493 ]
493494
=== modified file 'app/modules-debug.js'
--- app/modules-debug.js 2012-11-15 15:44:00 +0000
+++ app/modules-debug.js 2012-11-20 23:07:20 +0000
@@ -1,6 +1,13 @@
1// This file is used for development only. In order to use it you should call1// This file is used for development only. In order to use it you should call
2// the "make debug" command. This command passes the "debug" argument to the2// the "make debug" command. This command passes the "debug" argument to the
3// "lib/server.js".3// "lib/server.js".
4//
5// This file declares which files implement modules, using the
6// "fullpath" property; and declares the membership of rollup modules, using
7// the "use" property to specify what the module name aliases.
8//
9// The "requires" property should not be used here because the javascript
10// minimizer will not parse it.
4var GlobalConfig = {11var GlobalConfig = {
5 filter: 'debug',12 filter: 'debug',
6 // Set "true" for verbose logging of YUI13 // Set "true" for verbose logging of YUI
@@ -93,19 +100,15 @@
93 },100 },
94101
95 'juju-charm-models': {102 'juju-charm-models': {
96 requires: ['juju-charm-id'],
97 fullpath: '/juju-ui/models/charm.js'103 fullpath: '/juju-ui/models/charm.js'
98 },104 },
99105
100 'juju-models': {106 'juju-models': {
101 requires: [
102 'model', 'model-list', 'juju-endpoints', 'juju-charm-models'],
103 fullpath: '/juju-ui/models/models.js'107 fullpath: '/juju-ui/models/models.js'
104 },108 },
105109
106 // Connectivity110 // Connectivity
107 'juju-env': {111 'juju-env': {
108 requires: ['reconnecting-websocket'],
109 fullpath: '/juju-ui/store/env.js'112 fullpath: '/juju-ui/store/env.js'
110 },113 },
111114
@@ -114,7 +117,6 @@
114 },117 },
115118
116 'juju-charm-store': {119 'juju-charm-store': {
117 requires: ['juju-charm-id'],
118 fullpath: '/juju-ui/store/charm.js'120 fullpath: '/juju-ui/store/charm.js'
119 },121 },
120122
@@ -125,13 +127,7 @@
125127
126 // App128 // App
127 'juju-gui': {129 'juju-gui': {
128 fullpath: '/juju-ui/app.js',130 fullpath: '/juju-ui/app.js'
129 requires: [
130 'juju-controllers',
131 'juju-views',
132 'juju-models',
133 'juju-view-charm-search'
134 ]
135 }131 }
136 }132 }
137 }133 }
138134
=== modified file 'app/store/charm.js'
--- app/store/charm.js 2012-11-05 21:24:42 +0000
+++ app/store/charm.js 2012-11-20 23:07:20 +0000
@@ -114,6 +114,7 @@
114114
115}, '0.1.0', {115}, '0.1.0', {
116 requires: [116 requires: [
117 'juju-charm-id',
117 'datasource-io',118 'datasource-io',
118 'json-parse'119 'json-parse'
119 ]120 ]
120121
=== modified file 'test/test_app.js'
--- test/test_app.js 2012-11-14 14:34:31 +0000
+++ test/test_app.js 2012-11-20 23:07:20 +0000
@@ -31,196 +31,181 @@
31 return app;31 return app;
32}32}
3333
34describe('Application basics', function() {34YUI(GlobalConfig).use(['juju-gui', 'juju-tests-utils'], function(Y) {
35 var Y, app, container;35 describe('Application basics', function() {
3636 var app, container;
37 before(function(done) {37
38 Y = YUI(GlobalConfig).use(['juju-gui', 'juju-tests-utils'], function(Y) {38 beforeEach(function() {
39 done();39 if (container) {
40 });40 container.remove(true);
41 });41 }
4242 container = Y.one('#main')
43 beforeEach(function(done) {43 .appendChild(Y.Node.create('<div/>'))
44 // XXX Apparently removing a DOM node is asynchronous (on Chrome at least)44 .set('id', 'test-container')
45 // and we occasionally lose the race if this code is in the afterEach45 .addClass('container')
46 // function, so instead we do it here, but only if one has previously been46 .append(Y.Node.create('<span/>')
47 // created.47 .set('id', 'environment-name'))
48 if (container) {48 .append(Y.Node.create('<span/>')
49 container.remove(true);49 .set('id', 'provider-type'));
50 }50 app = new Y.juju.App(
51 container = Y.one('#main')51 { container: container,
52 .appendChild(Y.Node.create('<div/>'))52 viewContainer: container});
53 .set('id', 'test-container')53 injectData(app);
54 .addClass('container')54 });
55 .append(Y.Node.create('<span/>')55
56 .set('id', 'environment-name'))56 it('should produce a valid index', function() {
57 .append(Y.Node.create('<span/>')57 var container = app.get('container');
58 .set('id', 'provider-type'));58 app.render();
59 app = new Y.juju.App(59 container.getAttribute('id').should.equal('test-container');
60 { container: container,60 container.getAttribute('class').should.include('container');
61 viewContainer: container});61 });
62 injectData(app);62
63 done();63 it('should be able to route objects to internal URLs', function() {
64 });64 // take handles to database objects and ensure we can route to the view
6565 // needed to show them
66 it('should produce a valid index', function() {66 var wordpress = app.db.services.getById('wordpress'),
67 var container = app.get('container');67 wp0 = app.db.units.get_units_for_service(wordpress)[0],
68 app.render();68 wp_charm = app.db.charms.add({id: wordpress.get('charm')});
69 container.getAttribute('id').should.equal('test-container');69
70 container.getAttribute('class').should.include('container');70 // 'service/wordpress/' is the primary and so other URL are not returned
71 });71 app.getModelURL(wordpress).should.equal('/service/wordpress/');
7272 // however passing 'intent' can force selection of another
73 it('should be able to route objects to internal URLs', function() {73 app.getModelURL(wordpress, 'config').should.equal(
74 // take handles to database objects and ensure we can route to the view74 '/service/wordpress/config');
75 // needed to show them75
76 var wordpress = app.db.services.getById('wordpress'),76 // service units use argument rewriting (thus not /u/wp/0)
77 wp0 = app.db.units.get_units_for_service(wordpress)[0],77 app.getModelURL(wp0).should.equal('/unit/wordpress-0/');
78 wp_charm = app.db.charms.add({id: wordpress.get('charm')});78
7979 // charms also require a mapping but only a name, not a function
80 // 'service/wordpress/' is the primary and so other URL are not returned80 app.getModelURL(wp_charm).should.equal(
81 app.getModelURL(wordpress).should.equal('/service/wordpress/');81 '/charms/charms/precise/wordpress-6/json');
82 // however passing 'intent' can force selection of another82 });
83 app.getModelURL(wordpress, 'config').should.equal(83
84 '/service/wordpress/config');84 it('should display the configured environment name', function() {
8585 var environment_name = 'This is the environment name. Deal with it.';
86 // service units use argument rewriting (thus not /u/wp/0)86 app = new Y.juju.App(
87 app.getModelURL(wp0).should.equal('/unit/wordpress-0/');87 { container: container,
8888 viewContainer: container,
89 // charms also require a mapping but only a name, not a function89 environment_name: environment_name});
90 app.getModelURL(wp_charm).should.equal(90 assert.equal(
91 '/charms/charms/precise/wordpress-6/json');91 container.one('#environment-name').get('text'),
92 });92 environment_name);
9393 });
94 it('should display the configured environment name', function() {94
95 var environment_name = 'This is the environment name. Deal with it.';95 it('should show a generic environment name if none configured', function() {
96 app = new Y.juju.App(96 app = new Y.juju.App(
97 { container: container,97 { container: container,
98 viewContainer: container,98 viewContainer: container});
99 environment_name: environment_name});99 assert.equal(
100 assert.equal(100 container.one('#environment-name').get('text'),
101 container.one('#environment-name').get('text'),101 'Environment');
102 environment_name);102 });
103 });103
104104 it('should show the provider type, when available', function() {
105 it('should show a generic environment name if none configured', function() {105 var providerType = 'excellent provider';
106 app = new Y.juju.App(106 // Since no provider type has been set yet, none is displayed.
107 { container: container,107 assert.equal(
108 viewContainer: container});108 container.one('#provider-type').get('text'),
109 assert.equal(109 '');
110 container.one('#environment-name').get('text'),110 app.env.set('providerType', providerType);
111 'Environment');111 // The provider type has been displayed.
112 });112 assert.equal(
113113 container.one('#provider-type').get('text'),
114 it('should show the provider type, when available', function() {114 'on ' + providerType);
115 var providerType = 'excellent provider';115 });
116 // Since no provider type has been set yet, none is displayed.116
117 assert.equal(117 });
118 container.one('#provider-type').get('text'),118});
119 '');119
120 app.env.set('providerType', providerType);120YUI(GlobalConfig).use(['juju-gui', 'juju-tests-utils'], function(Y) {
121 // The provider type has been displayed.121 describe('Application Connection State', function() {
122 assert.equal(122 var container;
123 container.one('#provider-type').get('text'),123
124 'on ' + providerType);124 before(function() {
125 });125 container = Y.Node.create('<div id="test" class="container"></div>');
126126 });
127});127
128128 it('should be able to handle env connection status changes', function() {
129129 var juju = Y.namespace('juju'),
130describe('Application Connection State', function() {130 conn = new(Y.namespace('juju-tests.utils')).SocketStub(),
131 var Y, container;131 env = new juju.Environment({conn: conn}),
132132 app = new Y.juju.App({env: env, container: container}),
133 before(function(done) {133 reset_called = false,
134 Y = YUI(GlobalConfig).use(['juju-gui', 'juju-tests-utils'], function(Y) {134 noop = function() {return this;};
135 container = Y.Node.create('<div id="test" class="container"></div>');135
136 done();136
137 });137 // mock the db
138138 app.db = {
139 });139 // mock out notifications
140140 // so app can start normally
141 it('should be able to handle env connection status changes', function() {141 notifications: {
142 var juju = Y.namespace('juju'),142 addTarget: noop,
143 conn = new(Y.namespace('juju-tests.utils')).SocketStub(),143 after: noop,
144 env = new juju.Environment({conn: conn}),144 filter: noop,
145 app = new Y.juju.App({env: env, container: container}),145 map: noop,
146 reset_called = false,146 on: noop,
147 noop = function() {return this;};147 size: function() {return 0;}
148148 },
149149 reset: function() {
150 // mock the db150 reset_called = true;
151 app.db = {151 }
152 // mock out notifications152 };
153 // so app can start normally153 env.connect();
154 notifications: {154 conn.open();
155 addTarget: noop,155 reset_called.should.equal(true);
156 after: noop,156
157 filter: noop,157 // trigger a second time and verify
158 map: noop,158 reset_called = false;
159 on: noop,159 conn.open();
160 size: function() {return 0;}160 reset_called.should.equal(true);
161 },161 });
162 reset: function() {162
163 reset_called = true;163 });
164 }164});
165 };165
166 env.connect();166YUI(GlobalConfig).use(['juju-models', 'juju-gui', 'datasource-local',
167 conn.open();167 'juju-tests-utils', 'json-stringify'], function(Y) {
168 reset_called.should.equal(true);168 describe('Application prefetching', function() {
169169 var models, conn, env, app, container, charm_store, data, juju;
170 // trigger a second time and verify170
171 reset_called = false;171 before(function() {
172 conn.open();172 models = Y.namespace('juju.models');
173 reset_called.should.equal(true);173 });
174 });174
175175 beforeEach(function() {
176});176 conn = new (Y.namespace('juju-tests.utils')).SocketStub(),
177177 env = new Y.juju.Environment({conn: conn});
178describe('Application prefetching', function() {178 env.connect();
179 var Y, models, conn, env, app, container, charm_store, data, juju;179 conn.open();
180180 container = Y.Node.create('<div id="test" class="container"></div>');
181 before(function(done) {181 data = [];
182 Y = YUI(GlobalConfig).use(182 app = new Y.juju.App(
183 'juju-models', 'juju-gui', 'datasource-local', 'juju-tests-utils',183 { container: container,
184 'json-stringify',184 viewContainer: container,
185 function(Y) {185 env: env,
186 models = Y.namespace('juju.models');186 charm_store: {} });
187 done();187
188 });188 app.updateEndpoints = function() {};
189 });189 env.get_endpoints = function() {};
190190 });
191 beforeEach(function() {191
192 conn = new (Y.namespace('juju-tests.utils')).SocketStub(),192 afterEach(function() {
193 env = new Y.juju.Environment({conn: conn});193 container.destroy();
194 env.connect();194 app.destroy();
195 conn.open();195 });
196 container = Y.Node.create('<div id="test" class="container"></div>');196
197 data = [];197 it('must prefetch charm and service for service pages', function() {
198 app = new Y.juju.App(198 injectData(app);
199 { container: container,199 var _ = expect(
200 viewContainer: container,200 app.db.charms.getById('cs:precise/wordpress-6')).to.not.exist;
201 env: env,201 app.show_service({params: {id: 'wordpress'}, query: {}});
202 charm_store: {} });202 // The app made a request of juju for the service info.
203203 conn.messages[conn.messages.length - 2].op.should.equal('get_service');
204 app.updateEndpoints = function() {};204 // The app also requested juju (not the charm store--see discussion in
205 env.get_endpoints = function() {};205 // app/models/charm.js) for the charm info.
206 });206 conn.last_message().op.should.equal('get_charm');
207207 // Tests of the actual load machinery are in the model and env tests, and
208 afterEach(function() {208 // so are not repeated here.
209 container.destroy();209 });
210 app.destroy();
211 });
212
213 it('must prefetch charm and service for service pages', function() {
214 injectData(app);
215 var _ = expect(
216 app.db.charms.getById('cs:precise/wordpress-6')).to.not.exist;
217 app.show_service({params: {id: 'wordpress'}, query: {}});
218 // The app made a request of juju for the service info.
219 conn.messages[conn.messages.length - 2].op.should.equal('get_service');
220 // The app also requested juju (not the charm store--see discussion in
221 // app/models/charm.js) for the charm info.
222 conn.last_message().op.should.equal('get_charm');
223 // Tests of the actual load machinery are in the model and env tests, and
224 // so are not repeated here.
225 });210 });
226});211});
227212
=== modified file 'test/test_app_hotkeys.js'
--- test/test_app_hotkeys.js 2012-11-20 15:39:46 +0000
+++ test/test_app_hotkeys.js 2012-11-20 23:07:20 +0000
@@ -1,12 +1,11 @@
1'use strict';1'use strict';
22
3describe('application hotkeys', function() {3YUI(GlobalConfig).use(['juju-gui', 'juju-tests-utils', 'node-event-simulate'],
4 var Y, app, container, env, conn, testUtils, windowNode;4 function(Y) {
5 describe('application hotkeys', function() {
6 var app, container, env, conn, testUtils, windowNode;
57
6 before(function() {8 before(function() {
7 Y = YUI(GlobalConfig).use(
8 ['juju-gui', 'juju-tests-utils',
9 'node-event-simulate'], function(Y) {
10 windowNode = Y.one(window);9 windowNode = Y.one(window);
11 app = new Y.juju.App({10 app = new Y.juju.App({
12 env: env,11 env: env,
@@ -15,49 +14,50 @@
15 });14 });
16 app.activateHotkeys();15 app.activateHotkeys();
17 });16 });
18 });17
1918 afterEach(function() {
20 afterEach(function() {19 container.remove(true);
21 container.remove(true);20 });
22 });21
2322 beforeEach(function() {
24 beforeEach(function() {23 container = Y.one('#main').appendChild(Y.Node.create(
25 container = Y.one('#main').appendChild(Y.Node.create('<div/>')).set('id',24 '<div/>')).set('id', 'test-container').append(
26 'test-container').append(25 Y.Node.create('<input />').set('id', 'charm-search-field'));
27 Y.Node.create('<input />').set('id', 'charm-search-field'));26 testUtils = Y.namespace('juju-tests.utils');
28 testUtils = Y.namespace('juju-tests.utils');27 env = {
29 env = {28 get: function() {
30 get: function() {29 },
31 },30 on: function() {
32 on: function() {31 },
33 },32 after: function() {
34 after: function() {33 }
35 }34 };
36 };35 });
37 });36
3837 it('should listen for alt-S events', function() {
39 it('should listen for alt-S events', function() {38 app.render();
40 app.render();39 windowNode.simulate('keydown', {
41 windowNode.simulate('keydown', {40 keyCode: 83, // "S" key
42 keyCode: 83, // "S" key41 altKey: true
43 altKey: true42 });
44 });43 // Did charm-search-field get the focus?
45 // Did charm-search-field get the focus?44 assert.equal(Y.one('#charm-search-field'),
46 assert.equal(Y.one('#charm-search-field'), Y.one(document.activeElement));45 Y.one(document.activeElement));
47 });46 });
4847
49 it('should listen for alt-E events', function() {48 it('should listen for alt-E events', function() {
50 var altEtriggered = false;49 var altEtriggered = false;
51 app.on('navigateTo', function(param) {50 app.on('navigateTo', function(param) {
52 if (param && param.url === '/') {51 if (param && param.url === '/') {
53 altEtriggered = true;52 altEtriggered = true;
54 }53 }
55 });54 });
56 app.render();55 app.render();
57 windowNode.simulate('keydown', {56 windowNode.simulate('keydown', {
58 keyCode: 69, // "E" key57 keyCode: 69, // "E" key
59 altKey: true58 altKey: true
60 });59 });
61 assert.isTrue(altEtriggered);60 assert.isTrue(altEtriggered);
62 });61 });
63});62 });
63 });
6464
=== modified file 'test/test_model.js'
--- test/test_model.js 2012-10-23 20:02:17 +0000
+++ test/test_model.js 2012-11-20 23:07:20 +0000
@@ -1,15 +1,11 @@
1'use strict';1'use strict';
22
3(function() {3YUI(GlobalConfig).use('juju-models', function(Y) {
4
5 describe('charm normalization', function() {4 describe('charm normalization', function() {
6 var Y, models;5 var models;
76
8 before(function(done) {7 before(function() {
9 Y = YUI(GlobalConfig).use('juju-models', function(Y) {8 models = Y.namespace('juju.models');
10 models = Y.namespace('juju.models');
11 done();
12 });
13 });9 });
1410
15 it('must create derived attributes from official charm id', function() {11 it('must create derived attributes from official charm id', function() {
@@ -31,15 +27,14 @@
31 });27 });
3228
33 });29 });
30});
3431
32YUI(GlobalConfig).use('juju-models', function(Y) {
35 describe('juju models', function() {33 describe('juju models', function() {
36 var Y, models;34 var models;
3735
38 before(function(done) {36 before(function() {
39 Y = YUI(GlobalConfig).use('juju-models', function(Y) {37 models = Y.namespace('juju.models');
40 models = Y.namespace('juju.models');
41 done();
42 });
43 });38 });
4439
45 it('must be able to create charm', function() {40 it('must be able to create charm', function() {
@@ -355,19 +350,17 @@
355 .should.eql(['relation-2', 'relation-3', 'relation-4']);350 .should.eql(['relation-2', 'relation-3', 'relation-4']);
356 });351 });
357 });352 });
353});
358354
355YUI(GlobalConfig).use(['juju-models', 'juju-gui', 'datasource-local',
356 'juju-tests-utils', 'json-stringify',
357 'juju-charm-store'], function(Y) {
359 describe('juju charm load', function() {358 describe('juju charm load', function() {
360 var Y, models, conn, env, app, container, charm_store, data, juju;359 var models, conn, env, app, container, charm_store, data, juju;
361360
362 before(function(done) {361 before(function() {
363 Y = YUI(GlobalConfig).use(362 models = Y.namespace('juju.models');
364 'juju-models', 'juju-gui', 'datasource-local', 'juju-tests-utils',363 juju = Y.namespace('juju');
365 'json-stringify', 'juju-charm-store',
366 function(Y) {
367 models = Y.namespace('juju.models');
368 juju = Y.namespace('juju');
369 done();
370 });
371 });364 });
372365
373 beforeEach(function() {366 beforeEach(function() {
@@ -513,4 +506,4 @@
513 });506 });
514507
515 });508 });
516})();509});
517510
=== modified file 'test/test_notifications.js'
--- test/test_notifications.js 2012-11-16 13:38:21 +0000
+++ test/test_notifications.js 2012-11-20 23:07:20 +0000
@@ -1,477 +1,462 @@
1'use strict';1'use strict';
22
3describe('notifications', function() {3YUI(GlobalConfig).use(['juju-gui', 'node-event-simulate', 'juju-tests-utils'],
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
70 function(Y) {4 function(Y) {
71 juju = Y.namespace('juju');5 describe('notifications', function() {
72 models = Y.namespace('juju.models');6 var juju, models, views;
73 views = Y.namespace('juju.views');7
74 done();8 var default_env = {
75 });9 'result': [
76 });10 ['service', 'add', {
7711 'charm': 'cs:precise/wordpress-6',
78 it('must be able to make notification and lists of notifications',12 'id': 'wordpress',
79 function() {13 'exposed': false
80 var note1 = new models.Notification({14 }],
81 title: 'test1',15 ['service', 'add', {
82 message: 'Hello'16 'charm': 'cs:precise/mediawiki-3',
83 }),17 'id': 'mediawiki',
84 note2 = new models.Notification({18 'exposed': false
85 title: 'test2',19 }],
86 message: 'I said goodnight!'20 ['service', 'add', {
87 }),21 'charm': 'cs:precise/mysql-6',
88 notifications = new models.NotificationList();22 'id': 'mysql'
8923 }],
90 notifications.add([note1, note2]);24 ['relation', 'add', {
91 notifications.size().should.equal(2);25 'interface': 'reversenginx',
9226 'scope': 'global',
93 // timestamp should be generated once27 'endpoints':
94 var ts = note1.get('timestamp');28 [['wordpress', {'role': 'peer', 'name': 'loadbalancer'}]],
95 note1.get('timestamp').should.equal(ts);29 'id': 'relation-0000000000'
96 // force an update so we can test ordering30 }],
97 // fast execution can result in same timestamp31 ['relation', 'add', {
98 note2.set('timestamp', ts + 1);32 'interface': 'mysql',
99 note2.get('timestamp').should.be.above(ts);33 'scope': 'global',
10034 'endpoints':
101 // defaults as expected35 [['mysql', {'role': 'server', 'name': 'db'}],
102 note1.get('level').should.equal('info');36 ['wordpress', {'role': 'client', 'name': 'db'}]],
103 note2.get('level').should.equal('info');37 'id': 'relation-0000000001'
104 // the sort order on the list should be by38 }],
105 // timestamp39 ['machine', 'add', {
106 notifications.get('title').should.eql(['test2', 'test1']);40 'agent-state': 'running',
107 });41 'instance-state': 'running',
10842 'id': 0,
109 it('must be able to render its view with sample data',43 'instance-id': 'local',
110 function() {44 'dns-name': 'localhost'
111 var note1 = new models.Notification({45 }],
112 title: 'test1', message: 'Hello'}),46 ['unit', 'add', {
113 note2 = new models.Notification({47 'machine': 0,
114 title: 'test2', message: 'I said goodnight!'}),48 'agent-state': 'started',
115 notifications = new models.NotificationList(),49 'public-address': '192.168.122.113',
116 container = Y.Node.create('<div id="test">'),50 'id': 'wordpress/0'
117 env = new juju.Environment(),51 }],
118 view = new views.NotificationsView({52 ['unit', 'add', {
119 container: container,53 'machine': 0,
120 notifications: notifications,54 'agent-state': 'error',
121 env: env});55 'public-address': '192.168.122.222',
122 view.render();56 'id': 'mysql/0'
123 // Verify the expected elements appear in the view57 }]
124 container.one('#notify-list').should.not.equal(undefined);58 ],
125 container.destroy();59 'op': 'delta'
126 });60 };
12761
128 it('must be able to limit the size of notification events',62
129 function() {63 before(function() {
130 var note1 = new models.Notification({64 juju = Y.namespace('juju');
131 title: 'test1',65 models = Y.namespace('juju.models');
132 message: 'Hello'66 views = Y.namespace('juju.views');
133 }),67 });
134 note2 = new models.Notification({68
135 title: 'test2',69 it('must be able to make notification and lists of notifications',
136 message: 'I said goodnight!'70 function() {
137 }),71 var note1 = new models.Notification({
138 note3 = new models.Notification({72 title: 'test1',
139 title: 'test3',73 message: 'Hello'
140 message: 'Never remember'74 }),
141 }),75 note2 = new models.Notification({
142 notifications = new models.NotificationList({76 title: 'test2',
143 max_size: 277 message: 'I said goodnight!'
144 });78 }),
14579 notifications = new models.NotificationList();
146 notifications.add([note1, note2]);80
147 notifications.size().should.equal(2);81 notifications.add([note1, note2]);
14882 notifications.size().should.equal(2);
149 // Adding a new notification should pop the oldest from the list (we83
150 // exceed max_size)84 // timestamp should be generated once
151 notifications.add(note3);85 var ts = note1.get('timestamp');
152 notifications.size().should.equal(2);86 note1.get('timestamp').should.equal(ts);
153 notifications.get('title').should.eql(['test3', 'test2']);87 // force an update so we can test ordering
154 });88 // fast execution can result in same timestamp
15589 note2.set('timestamp', ts + 1);
156 it('must be able to get notifications for a given model',90 note2.get('timestamp').should.be.above(ts);
157 function() {91
158 var m = new models.Service({id: 'mediawiki'}),92 // defaults as expected
159 note1 = new models.Notification({93 note1.get('level').should.equal('info');
160 title: 'test1',94 note2.get('level').should.equal('info');
161 message: 'Hello',95 // the sort order on the list should be by
162 modelId: m96 // timestamp
163 }),97 notifications.get('title').should.eql(['test2', 'test1']);
164 note2 = new models.Notification({98 });
165 title: 'test2',99
166 message: 'I said goodnight!'100 it('must be able to render its view with sample data',
167 }),101 function() {
168 notifications = new models.NotificationList();102 var note1 = new models.Notification({
169103 title: 'test1', message: 'Hello'}),
170 notifications.add([note1, note2]);104 note2 = new models.Notification({
171 notifications.size().should.equal(2);105 title: 'test2', message: 'I said goodnight!'}),
172 notifications.getNotificationsForModel(m).should.eql(106 notifications = new models.NotificationList(),
173 [note1]);107 container = Y.Node.create('<div id="test">'),
174108 env = new juju.Environment(),
175 });109 view = new views.NotificationsView({
176110 container: container,
177 it('must be able to include and show object links', function() {111 notifications: notifications,
178 var container = Y.Node.create('<div id="test">'),112 env: env});
179 conn = new(Y.namespace('juju-tests.utils')).SocketStub(),113 view.render();
180 env = new juju.Environment({conn: conn}),114 // Verify the expected elements appear in the view
181 app = new Y.juju.App({env: env, container: container}),115 container.one('#notify-list').should.not.equal(undefined);
182 db = app.db,116 container.destroy();
183 mw = db.services.create({id: 'mediawiki',117 });
184 name: 'mediawiki'}),118
185 notifications = db.notifications,119 it('must be able to limit the size of notification events',
186 view = new views.NotificationsOverview({120 function() {
187 container: container,121 var note1 = new models.Notification({
188 notifications: notifications,122 title: 'test1',
189 app: app,123 message: 'Hello'
190 env: env}).render();124 }),
191 // we use overview here for testing as it defaults125 note2 = new models.Notification({
192 // to showing all notices126 title: 'test2',
193127 message: 'I said goodnight!'
194 // we can use app's routing table to derive a link128 }),
195 notifications.create({title: 'Service Down',129 note3 = new models.Notification({
196 message: 'Your service has an error',130 title: 'test3',
197 link: app.getModelURL(mw)131 message: 'Never remember'
198 });132 }),
199 view.render();133 notifications = new models.NotificationList({
200 var link = container.one('.notice').one('a');134 max_size: 2
201 link.getAttribute('href').should.equal(135 });
202 '/service/mediawiki/');136
203 link.getHTML().should.contain('View Details');137 notifications.add([note1, note2]);
204138 notifications.size().should.equal(2);
205139
206 // create a new notice passing the link_title140 // Adding a new notification should pop the oldest from the list
207 notifications.create({title: 'Service Down',141 // (we exceed max_size)
208 message: 'Your service has an error',142 notifications.add(note3);
209 link: app.getModelURL(mw),143 notifications.size().should.equal(2);
210 link_title: 'Resolve this'144 notifications.get('title').should.eql(['test3', 'test2']);
211 });145 });
212 view.render();146
213 link = container.one('.notice').one('a');147 it('must be able to get notifications for a given model',
214 link.getAttribute('href').should.equal(148 function() {
215 '/service/mediawiki/');149 var m = new models.Service({id: 'mediawiki'}),
216 link.getHTML().should.contain('Resolve this');150 note1 = new models.Notification({
217 });151 title: 'test1',
218152 message: 'Hello',
219 it('must be able to evict irrelevant notices', function() {153 modelId: m
220 var container = Y.Node.create(154 }),
221 '<div id="test" class="container"></div>'),155 note2 = new models.Notification({
222 conn = new(Y.namespace('juju-tests.utils')).SocketStub(),156 title: 'test2',
223 env = new juju.Environment({conn: conn}),157 message: 'I said goodnight!'
224 app = new Y.juju.App({158 }),
225 env: env,159 notifications = new models.NotificationList();
226 container: container,160
227 viewContainer: container161 notifications.add([note1, note2]);
228 });162 notifications.size().should.equal(2);
229 var environment_delta = default_env;163 notifications.getNotificationsForModel(m).should.eql(
230164 [note1]);
231 var notifications = app.db.notifications,165
232 view = new views.NotificationsView({166 });
233 container: container,167
234 notifications: notifications,168 it('must be able to include and show object links', function() {
235 env: app.env}).render();169 var container = Y.Node.create('<div id="test">'),
236170 conn = new(Y.namespace('juju-tests.utils')).SocketStub(),
237171 env = new juju.Environment({conn: conn}),
238 app.env.dispatch_result(environment_delta);172 app = new Y.juju.App({env: env, container: container}),
239173 db = app.db,
240174 mw = db.services.create({id: 'mediawiki',
241 notifications.size().should.equal(7);175 name: 'mediawiki'}),
242 // we have one unit in error176 notifications = db.notifications,
243 view.getShowable().length.should.equal(1);177 view = new views.NotificationsOverview({
244178 container: container,
245 // now fire another delta event marking that node as179 notifications: notifications,
246 // started180 app: app,
247 app.env.dispatch_result({result: [['unit', 'change', {181 env: env}).render();
248 'machine': 0,182 // we use overview here for testing as it defaults
249 'agent-state': 'started',183 // to showing all notices
250 'public-address': '192.168.122.222',184
251 'id': 'mysql/0'185 // we can use app's routing table to derive a link
252 }]], op: 'delta'});186 notifications.create({title: 'Service Down',
253 notifications.size().should.equal(8);187 message: 'Your service has an error',
254 // This should have evicted the prior notice from seen188 link: app.getModelURL(mw)
255 view.getShowable().length.should.equal(0);189 });
256 });190 view.render();
257191 var link = container.one('.notice').one('a');
258 it('must properly construct title and message based on level from ' +192 link.getAttribute('href').should.equal(
259 'event data',193 '/service/mediawiki/');
260 function() {194 link.getHTML().should.contain('View Details');
261 var container = Y.Node.create(195
262 '<div id="test" class="container"></div>'),196
263 app = new Y.juju.App({197 // create a new notice passing the link_title
264 container: container,198 notifications.create({title: 'Service Down',
265 viewContainer: container199 message: 'Your service has an error',
266 });200 link: app.getModelURL(mw),
267 var environment_delta = {201 link_title: 'Resolve this'
268 'result': [202 });
269 ['service', 'add', {203 view.render();
270 'charm': 'cs:precise/wordpress-6',204 link = container.one('.notice').one('a');
271 'id': 'wordpress'205 link.getAttribute('href').should.equal(
272 }],206 '/service/mediawiki/');
273 ['service', 'add', {207 link.getHTML().should.contain('Resolve this');
274 'charm': 'cs:precise/mediawiki-3',208 });
275 'id': 'mediawiki'209
276 }],210 it('must be able to evict irrelevant notices', function() {
277 ['service', 'add', {211 var container = Y.Node.create(
278 'charm': 'cs:precise/mysql-6',212 '<div id="test" class="container"></div>'),
279 'id': 'mysql'213 conn = new(Y.namespace('juju-tests.utils')).SocketStub(),
280 }],214 env = new juju.Environment({conn: conn}),
281 ['unit', 'add', {215 app = new Y.juju.App({
282 'agent-state': 'install-error',216 env: env,
283 'id': 'wordpress/0'217 container: container,
284 }],218 viewContainer: container
285 ['unit', 'add', {219 });
286 'agent-state': 'error',220 var environment_delta = default_env;
287 'public-address': '192.168.122.222',221
288 'id': 'mysql/0'222 var notifications = app.db.notifications,
289 }],223 view = new views.NotificationsView({
290 ['unit', 'add', {224 container: container,
291 'public-address': '192.168.122.222',225 notifications: notifications,
292 'id': 'mysql/2'226 env: app.env}).render();
293 }]227
294 ],228
295 'op': 'delta'229 app.env.dispatch_result(environment_delta);
296 };230
297231
298 var notifications = app.db.notifications,232 notifications.size().should.equal(7);
299 view = new views.NotificationsView({233 // we have one unit in error
300 container: container,234 view.getShowable().length.should.equal(1);
301 notifications: notifications,235
302 app: app,236 // now fire another delta event marking that node as
303 env: app.env}).render();237 // started
304238 app.env.dispatch_result({result: [['unit', 'change', {
305 app.env.dispatch_result(environment_delta);239 'machine': 0,
306240 'agent-state': 'started',
307 notifications.size().should.equal(6);241 'public-address': '192.168.122.222',
308 // we have one unit in error242 'id': 'mysql/0'
309 var showable = view.getShowable();243 }]], op: 'delta'});
310 showable.length.should.equal(2);244 notifications.size().should.equal(8);
311 // The first showable notification should indicate an error.245 // This should have evicted the prior notice from seen
312 showable[0].level.should.equal('error');246 view.getShowable().length.should.equal(0);
313 showable[0].title.should.equal('Error with mysql/0');247 });
314 showable[0].message.should.equal('Agent-state = error.');248
315 // The second showable notification should also indicate an error.249 it('must properly construct title and message based on level from ' +
316 showable[1].level.should.equal('error');250 'event data',
317 showable[1].title.should.equal('Error with wordpress/0');251 function() {
318 showable[1].message.should.equal('Agent-state = install-error.');252 var container = Y.Node.create(
319 // The first non-error notice should have an 'info' level and less253 '<div id="test" class="container"></div>'),
320 // severe messaging.254 app = new Y.juju.App({
321 var notice = notifications.item(0);255 container: container,
322 notice.get('level').should.equal('info');256 viewContainer: container
323 notice.get('title').should.equal('Problem with mysql/2');257 });
324 notice.get('message').should.equal('');258 var environment_delta = {
325 });259 'result': [
326260 ['service', 'add', {
327261 'charm': 'cs:precise/wordpress-6',
328 it('should open on click and close on clickoutside', function(done) {262 'id': 'wordpress'
329 var container = Y.Node.create(263 }],
330 '<div id="test-container" style="display: none" class="container"/>'),264 ['service', 'add', {
331 notifications = new models.NotificationList(),265 'charm': 'cs:precise/mediawiki-3',
332 env = new juju.Environment(),266 'id': 'mediawiki'
333 view = new views.NotificationsView({267 }],
334 container: container,268 ['service', 'add', {
335 notifications: notifications,269 'charm': 'cs:precise/mysql-6',
336 env: env}).render(),270 'id': 'mysql'
337 indicator;271 }],
338272 ['unit', 'add', {
339 Y.one('body').append(container);273 'agent-state': 'install-error',
340 notifications.add({title: 'testing', 'level': 'error'});274 'id': 'wordpress/0'
341 indicator = container.one('#notify-indicator');275 }],
342276 ['unit', 'add', {
343 indicator.simulate('click');277 'agent-state': 'error',
344 indicator.ancestor().hasClass('open').should.equal(true);278 'public-address': '192.168.122.222',
345279 'id': 'mysql/0'
346 Y.one('body').simulate('click');280 }],
347 indicator.ancestor().hasClass('open').should.equal(false);281 ['unit', 'add', {
348282 'public-address': '192.168.122.222',
349 container.remove();283 'id': 'mysql/2'
350 done();284 }]
351 });285 ],
352286 'op': 'delta'
353});287 };
354288
355289 var notifications = app.db.notifications,
356describe('changing notifications to words', function() {290 view = new views.NotificationsView({
357 var Y, juju;291 container: container,
358292 notifications: notifications,
359 before(function(done) {293 app: app,
360 Y = YUI(GlobalConfig).use(294 env: app.env}).render();
361 ['juju-notification-controller'],295
362 function(Y) {296 app.env.dispatch_result(environment_delta);
363 juju = Y.namespace('juju');297
364 done();298 notifications.size().should.equal(6);
365 });299 // we have one unit in error
366 });300 var showable = view.getShowable();
367301 showable.length.should.equal(2);
368 it('should correctly translate notification operations into English',302 // The first showable notification should indicate an error.
369 function() {303 showable[0].level.should.equal('error');
370 assert.equal(juju._changeNotificationOpToWords('add'), 'created');304 showable[0].title.should.equal('Error with mysql/0');
371 assert.equal(juju._changeNotificationOpToWords('remove'), 'removed');305 showable[0].message.should.equal('Agent-state = error.');
372 assert.equal(juju._changeNotificationOpToWords('not-an-op'), 'changed');306 // The second showable notification should also indicate an error.
373 });307 showable[1].level.should.equal('error');
374});308 showable[1].title.should.equal('Error with wordpress/0');
375309 showable[1].message.should.equal('Agent-state = install-error.');
376describe('relation notifications', function() {310 // The first non-error notice should have an 'info' level and less
377 var Y, juju;311 // severe messaging.
378312 var notice = notifications.item(0);
379 before(function(done) {313 notice.get('level').should.equal('info');
380 Y = YUI(GlobalConfig).use(314 notice.get('title').should.equal('Problem with mysql/2');
381 ['juju-notification-controller'],315 notice.get('message').should.equal('');
382 function(Y) {316 });
383 juju = Y.namespace('juju');317
384 done();318
385 });319 it('should open on click and close on clickoutside', function(done) {
386 });320 var container = Y.Node.create('<div id="test-container" ' +
387321 'style="display: none" class="container"/>'),
388 it('should produce reasonable titles', function() {322 notifications = new models.NotificationList(),
389 assert.equal(323 env = new juju.Environment(),
390 juju._relationNotifications.title(undefined, 'add'),324 view = new views.NotificationsView({
391 'Relation created');325 container: container,
392 assert.equal(326 notifications: notifications,
393 juju._relationNotifications.title(undefined, 'remove'),327 env: env}).render(),
394 'Relation removed');328 indicator;
395 });329
396330 Y.one('body').append(container);
397 it('should generate messages about two-party relations', function() {331 notifications.add({title: 'testing', 'level': 'error'});
398 var changeData =332 indicator = container.one('#notify-indicator');
399 { endpoints:333
400 [['endpoint0', {name: 'relation0'}],334 indicator.simulate('click');
401 ['endpoint1', {name: 'relation1'}]]};335 indicator.ancestor().hasClass('open').should.equal(true);
402 assert.equal(336
403 juju._relationNotifications.message(undefined, 'add', changeData),337 Y.one('body').simulate('click');
404 'Relation between endpoint0 (relation type "relation0") and ' +338 indicator.ancestor().hasClass('open').should.equal(false);
405 'endpoint1 (relation type "relation1") was created');339
406 });340 container.remove();
407341 done();
408 it('should generate messages about one-party relations', function() {342 });
409 var changeData =343 });
410 { endpoints:344
411 [['endpoint1', {name: 'relation1'}]]};345 describe('changing notifications to words', function() {
412 assert.equal(346 var juju;
413 juju._relationNotifications.message(undefined, 'add', changeData),347
414 'Relation with endpoint1 (relation type "relation1") was created');348 before(function() {
415 });349 juju = Y.namespace('juju');
416});350 });
417351
418describe('notification visual feedback', function() {352 it('should correctly translate notification operations into English',
419 var env, models, notifications, notificationsView, notifierBox, views, Y;353 function() {
420354 assert.equal(juju._changeNotificationOpToWords('add'), 'created');
421 before(function(done) {355 assert.equal(juju._changeNotificationOpToWords('remove'),
422 Y = YUI(GlobalConfig).use('juju-env', 'juju-models', 'juju-views',356 'removed');
423 function(Y) {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() {
424 var juju = Y.namespace('juju');405 var juju = Y.namespace('juju');
425 env = new juju.Environment();406 env = new juju.Environment();
426 models = Y.namespace('juju.models');407 models = Y.namespace('juju.models');
427 views = Y.namespace('juju.views');408 views = Y.namespace('juju.views');
428 done();409 });
429 });410
430 });411 // Instantiate the notifications model list and view.
431412 // Also create the notifier box and attach it as first element of the
432 // Instantiate the notifications model list and view.413 // body.
433 // Also create the notifier box and attach it as first element of the body.414 beforeEach(function() {
434 beforeEach(function() {415 notifications = new models.NotificationList();
435 notifications = new models.NotificationList();416 notificationsView = new views.NotificationsView({
436 notificationsView = new views.NotificationsView({417 env: env,
437 env: env,418 notifications: notifications
438 notifications: notifications419 });
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 });
439 });462 });
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});
478463
=== modified file 'test/test_notifier_widget.js'
--- test/test_notifier_widget.js 2012-11-16 13:37:20 +0000
+++ test/test_notifier_widget.js 2012-11-20 23:07:20 +0000
@@ -1,103 +1,101 @@
1'use strict';1'use strict';
22
3describe('notifier widget', function() {3YUI(GlobalConfig).use(['notifier', 'node-event-simulate'], function(Y) {
4 var Notifier, notifierBox, Y;4 describe('notifier widget', function() {
55 var Notifier, notifierBox;
6 before(function(done) {6
7 Y = YUI(GlobalConfig).use('notifier', 'node-event-simulate',7 before(function() {
8 function(Y) {8 Notifier = Y.namespace('juju.widgets').Notifier;
9 Notifier = Y.namespace('juju.widgets').Notifier;9 });
10 done();10
11 });11 // Create the notifier box and attach it as first element of the body.
12 });12 beforeEach(function() {
1313 notifierBox = Y.Node.create('<div id="notifier-box"></div>');
14 // Create the notifier box and attach it as first element of the body.14 notifierBox.setStyle('display', 'none');
15 beforeEach(function() {15 Y.one('body').prepend(notifierBox);
16 notifierBox = Y.Node.create('<div id="notifier-box"></div>');16 });
17 notifierBox.setStyle('display', 'none');17
18 Y.one('body').prepend(notifierBox);18 // Destroy the notifier box created in beforeEach.
19 });19 afterEach(function() {
2020 notifierBox.remove();
21 // Destroy the notifier box created in beforeEach.21 notifierBox.destroy(true);
22 afterEach(function() {22 });
23 notifierBox.remove();23
24 notifierBox.destroy(true);24 // Factory rendering and returning a notifier instance.
25 });25 var makeNotifier = function(title, message, timeout) {
2626 var notifier = new Notifier({
27 // Factory rendering and returning a notifier instance.27 title: title || 'mytitle',
28 var makeNotifier = function(title, message, timeout) {28 message: message || 'mymessage',
29 var notifier = new Notifier({29 timeout: timeout || 10000
30 title: title || 'mytitle',30 });
31 message: message || 'mymessage',31 notifier.render(notifierBox);
32 timeout: timeout || 1000032 return notifier;
33 });33 };
34 notifier.render(notifierBox);34
35 return notifier;35 // Assert the notifier box contains the expectedNumber of notifiers.
36 };36 var assertNumNotifiers = function(expectedNumber) {
3737 assert.equal(expectedNumber, notifierBox.get('children').size());
38 // Assert the notifier box contains the expectedNumber of notifiers.38 };
39 var assertNumNotifiers = function(expectedNumber) {39
40 assert.equal(expectedNumber, notifierBox.get('children').size());40 it('should be able to display a notification', function() {
41 };
42
43 it('should be able to display a notification', function() {
44 makeNotifier();
45 assertNumNotifiers(1);
46 });
47
48 it('should display the given title and message', function() {
49 makeNotifier('mytitle', 'mymessage');
50 var notifierNode = notifierBox.one('*');
51 assert.equal('mytitle', notifierNode.one('h3').getContent());
52 assert.equal('mymessage', notifierNode.one('div').getContent());
53 });
54
55 it('should be able to display multiple notifications', function() {
56 var number = 10;
57 for (var i = 0; i < number; i += 1) {
58 makeNotifier();41 makeNotifier();
59 }
60 assertNumNotifiers(number);
61 });
62
63 it('should display new notifications on top', function() {
64 makeNotifier('mytitle1', 'mymessage1');
65 makeNotifier('mytitle2', 'mymessage2');
66 var notifierNode = notifierBox.one('*');
67 assert.equal('mytitle2', notifierNode.one('h3').getContent());
68 assert.equal('mymessage2', notifierNode.one('div').getContent());
69 });
70
71 it('should destroy notifications after N milliseconds', function(done) {
72 makeNotifier('mytitle', 'mymessage', 1);
73 // A timeout of 250 milliseconds is used so that we ensure the destroying
74 // animation can be completed.
75 setTimeout(function() {
76 assertNumNotifiers(0);
77 done();
78 }, 250);
79 });
80
81 it('should destroy notifications on click', function(done) {
82 makeNotifier();
83 notifierBox.one('*').simulate('click');
84 // A timeout of 250 milliseconds is used so that we ensure the destroying
85 // animation can be completed.
86 setTimeout(function() {
87 assertNumNotifiers(0);
88 done();
89 }, 250);
90 });
91
92 it('should prevent notification removal on mouse enter', function(done) {
93 makeNotifier('mytitle', 'mymessage', 1);
94 notifierBox.one('*').simulate('mouseover');
95 // A timeout of 250 milliseconds is used so that we ensure the node is not
96 // preserved by the destroying animation.
97 setTimeout(function() {
98 assertNumNotifiers(1);42 assertNumNotifiers(1);
99 done();43 });
100 }, 250);44
45 it('should display the given title and message', function() {
46 makeNotifier('mytitle', 'mymessage');
47 var notifierNode = notifierBox.one('*');
48 assert.equal('mytitle', notifierNode.one('h3').getContent());
49 assert.equal('mymessage', notifierNode.one('div').getContent());
50 });
51
52 it('should be able to display multiple notifications', function() {
53 var number = 10;
54 for (var i = 0; i < number; i += 1) {
55 makeNotifier();
56 }
57 assertNumNotifiers(number);
58 });
59
60 it('should display new notifications on top', function() {
61 makeNotifier('mytitle1', 'mymessage1');
62 makeNotifier('mytitle2', 'mymessage2');
63 var notifierNode = notifierBox.one('*');
64 assert.equal('mytitle2', notifierNode.one('h3').getContent());
65 assert.equal('mymessage2', notifierNode.one('div').getContent());
66 });
67
68 it('should destroy notifications after N milliseconds', function(done) {
69 makeNotifier('mytitle', 'mymessage', 1);
70 // A timeout of 250 milliseconds is used so that we ensure the destroying
71 // animation can be completed.
72 setTimeout(function() {
73 assertNumNotifiers(0);
74 done();
75 }, 250);
76 });
77
78 it('should destroy notifications on click', function(done) {
79 makeNotifier();
80 notifierBox.one('*').simulate('click');
81 // A timeout of 250 milliseconds is used so that we ensure the destroying
82 // animation can be completed.
83 setTimeout(function() {
84 assertNumNotifiers(0);
85 done();
86 }, 250);
87 });
88
89 it('should prevent notification removal on mouse enter', function(done) {
90 makeNotifier('mytitle', 'mymessage', 1);
91 notifierBox.one('*').simulate('mouseover');
92 // A timeout of 250 milliseconds is used so that we ensure the node is not
93 // preserved by the destroying animation.
94 setTimeout(function() {
95 assertNumNotifiers(1);
96 done();
97 }, 250);
98 });
99
101 });100 });
102
103});101});

Subscribers

People subscribed via source and target branches