Merge lp:~huwshimi/juju-gui/bundle-charm-list-updates into lp:juju-gui/experimental

Proposed by Huw Wilkins
Status: Merged
Merged at revision: 1151
Proposed branch: lp:~huwshimi/juju-gui/bundle-charm-list-updates
Merge into: lp:juju-gui/experimental
Diff against target: 726 lines (+265/-247)
4 files modified
app/subapps/browser/views/bundle.js (+76/-54)
app/templates/bundle.handlebars (+22/-10)
lib/views/browser/bundle-panel.less (+3/-0)
test/test_bundle_details_view.js (+164/-183)
To merge this branch: bzr merge lp:~huwshimi/juju-gui/bundle-charm-list-updates
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+192264@code.launchpad.net

Description of the change

Bundle promise removal and service updates

Removed promises (merged from Rick)

Visual updates:
1) summary tab: delete charm stuff
2) change charms tab name to services
3) add service name to top of config info
4) sort services by charm name and then by service name
5) Add this text to top of Services tab: "These are the services that the bundle will deploy. Non-default configuration values are listed."
6) if charm is subordinate, do not show number of units; if charm is not subordinate and does not list number of units, show "1"

https://codereview.appspot.com/14920057/

To post a comment you must log in.
Revision history for this message
Huw Wilkins (huwshimi) wrote :

Reviewers: mp+192264_code.launchpad.net,

Message:
Please take a look.

Description:
Bundle promise removal and service updates

Removed promises (merged from Rick)

Visual updates:
1) summary tab: delete charm stuff
2) change charms tab name to services
3) add service name to top of config info
4) sort services by charm name and then by service name
5) Add this text to top of Services tab: "These are the services that
the bundle will deploy. Non-default configuration values are listed."
6) if charm is subordinate, do not show number of units; if charm is not
subordinate and does not list number of units, show "1"

https://code.launchpad.net/~huwshimi/juju-gui/bundle-charm-list-updates/+merge/192264

(do not edit description out of merge proposal)

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

Affected files (+236, -213 lines):
   [revision details]
   app/subapps/browser/views/bundle.js
   app/templates/bundle.handlebars
   lib/views/browser/bundle-panel.less
   test/test_bundle_details_view.js

1150. By Huw Wilkins

Merged trunk.

Revision history for this message
Huw Wilkins (huwshimi) wrote :
Revision history for this message
Gary Poster (gary) wrote :

I'd like to talk through my comments before giving a blessing, but it
looks very close. Thank you!

QA good. I think I want to try and get rid of the Summary tab, but that
can be a separate branch, maybe based on some work Benji and Brad are
doing.

https://codereview.appspot.com/14920057/diff/30001/app/subapps/browser/views/bundle.js
File app/subapps/browser/views/bundle.js (right):

https://codereview.appspot.com/14920057/diff/30001/app/subapps/browser/views/bundle.js#newcode120
app/subapps/browser/views/bundle.js:120:
_model is a pretty confusing name, IMO. Why not simply service._charm?
Also, this is using an underline effectively as a namespace tool, which
I don't love.

https://codereview.appspot.com/14920057/diff/30001/app/subapps/browser/views/bundle.js#newcode138
app/subapps/browser/views/bundle.js:138:
The 'entity' is a bundle here, right? It is called 'entity' because we
are sharing code with a charm view as well, right, so in other files
'entity' is a charm? If so, please add a comment explaining this (at
least the fact that this.get('entity') is a bundle).

https://codereview.appspot.com/14920057/diff/30001/app/subapps/browser/views/bundle.js#newcode142
app/subapps/browser/views/bundle.js:142:
I'd personally feel that this would be an easier-to-read data structure
if you changed this approach. For instance, one example would be to
build a new services mapping that was like this:
newServicesMappingExample[key] = {charm: new Y.juju.models.Charm(charm),
data: service}.

https://codereview.appspot.com/14920057/diff/30001/app/subapps/browser/views/bundle.js#newcode194
app/subapps/browser/views/bundle.js:194:
Per Jeff's comments
(http://jujugui.wordpress.com/2013/10/11/javascript-promise-error-handling-tricks/),
this should be in a separate .then(null, function(error) {...}) block so
that you can also catch and report errors from the success function.

https://codereview.appspot.com/14920057/

Revision history for this message
Richard Harding (rharding) wrote :

O>

https://codereview.appspot.com/14920057/diff/30001/app/subapps/browser/views/bundle.js#newcode120
> app/subapps/browser/views/bundle.js:120:
> _model is a pretty confusing name, IMO. Why not simply
service._charm? Also,
> this is using an underline effectively as a namespace tool, which I
don't love.

We have to be careful not to collide with keys that could occur in the
service block in the deployer file section. Using the _ is a
"collision-avoider". What would you prefer as an alternative?

https://codereview.appspot.com/14920057/diff/30001/app/subapps/browser/views/bundle.js#newcode138
> app/subapps/browser/views/bundle.js:138:
> The 'entity' is a bundle here, right? It is called 'entity' because
we are
> sharing code with a charm view as well, right, so in other files
'entity' is a
> charm? If so, please add a comment explaining this (at least the fact
that
> this.get('entity') is a bundle).

Yep, the details shares a lot of common code in entity-base.js which was
refactored out to share between charm and bundle details views.

https://codereview.appspot.com/14920057/

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

On 2013/10/23 13:57:52, rharding wrote:
> O>
> >

https://codereview.appspot.com/14920057/diff/30001/app/subapps/browser/views/bundle.js#newcode120
> > app/subapps/browser/views/bundle.js:120:
> > _model is a pretty confusing name, IMO. Why not simply
service._charm? Also,
> > this is using an underline effectively as a namespace tool, which I
don't
> love.

> We have to be careful not to collide with keys that could occur in the
service
> block in the deployer file section. Using the _ is a
"collision-avoider".

Right, that's what I meant by namespace

> What
> would you prefer as an alternative?

See my comment starting with "I'd personally feel that this would be an
easier-to-read data structure..."

> >

https://codereview.appspot.com/14920057/diff/30001/app/subapps/browser/views/bundle.js#newcode138
> > app/subapps/browser/views/bundle.js:138:
> > The 'entity' is a bundle here, right? It is called 'entity' because
we are
> > sharing code with a charm view as well, right, so in other files
'entity' is a
> > charm? If so, please add a comment explaining this (at least the
fact that
> > this.get('entity') is a bundle).

> Yep, the details shares a lot of common code in entity-base.js which
was
> refactored out to share between charm and bundle details views.

Cool.

Thank you.

https://codereview.appspot.com/14920057/

1151. By Huw Wilkins

Review changes.

1152. By Huw Wilkins

Merged trunk.

Revision history for this message
Huw Wilkins (huwshimi) wrote :
Revision history for this message
Jeff Pihach (hatch) wrote :

LGTM after some small changes below - Thanks for putting in the time on
this one!

https://codereview.appspot.com/14920057/diff/170001/app/subapps/browser/views/bundle.js
File app/subapps/browser/views/bundle.js (right):

https://codereview.appspot.com/14920057/diff/170001/app/subapps/browser/views/bundle.js#newcode139
app/subapps/browser/views/bundle.js:139: var attrs =
this.get('entity').getAttrs();
You have all of this information when you call this function so as a
micro performance improvement you could pass only the required data in
so that this is a utility method instead of just a private method which
isn't really a private method.

https://codereview.appspot.com/14920057/diff/170001/app/subapps/browser/views/bundle.js#newcode165
app/subapps/browser/views/bundle.js:165: var templateData =
Y.merge(bundleData);
If bundle is a Y.Model then this should be returning a new object so
this merge is not required. Then you can use bundleData below instead of
templateData

https://codereview.appspot.com/14920057/diff/170001/app/subapps/browser/views/bundle.js#newcode198
app/subapps/browser/views/bundle.js:198: // Fired event to test the
topology is rendered
Fire event to listen to during the tests so that we know when it's
rendered

https://codereview.appspot.com/14920057/diff/170001/app/subapps/browser/views/bundle.js#newcode221
app/subapps/browser/views/bundle.js:221: this.set('rendered', true);
Now that render is synchronous this is no longer required and the checks
can be removed from the tests.

https://codereview.appspot.com/14920057/

1153. By Huw Wilkins

Review fixes.

Revision history for this message
Huw Wilkins (huwshimi) wrote :
Revision history for this message
Huw Wilkins (huwshimi) wrote :

*** Submitted:

Bundle promise removal and service updates

Removed promises (merged from Rick)

Visual updates:
1) summary tab: delete charm stuff
2) change charms tab name to services
3) add service name to top of config info
4) sort services by charm name and then by service name
5) Add this text to top of Services tab: "These are the services that
the bundle will deploy. Non-default configuration values are listed."
6) if charm is subordinate, do not show number of units; if charm is not
subordinate and does not list number of units, show "1"

R=gary.poster, rharding, jeff.pihach
CC=
https://codereview.appspot.com/14920057

https://codereview.appspot.com/14920057/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/subapps/browser/views/bundle.js'
2--- app/subapps/browser/views/bundle.js 2013-10-17 16:55:19 +0000
3+++ app/subapps/browser/views/bundle.js 2013-10-24 00:19:58 +0000
4@@ -77,34 +77,6 @@
5 },
6
7 /**
8- Fetches and prepares the data for the bundle details page rendering.
9-
10- @method _fetchData
11- */
12- _fetchData: function() {
13- var self = this;
14-
15- return new Y.Promise(function(resolve, reject) {
16- var entity = self.get('entity');
17- // An entity here is a fully populated charm/bundle model so
18- // it's entirely possible that we have an id to load but
19- // no model has been populated yet.
20- if (entity) {
21- resolve(entity);
22- } else {
23- self.get('store').bundle(self.get('entityId'), {
24- 'success': function(data) {
25- var bundle = new models.Bundle(data);
26- self.set('entity', bundle);
27- resolve(bundle);
28- },
29- 'failure': reject
30- }, self);
31- }
32- });
33- },
34-
35- /**
36 Sends the bundle data to the local fakebackend to
37 import and then returns a promise when complete.
38
39@@ -140,36 +112,66 @@
40 Render the list of charms in the bundle.
41
42 @method _renderCharmListing
43+ @param {Object} services the services in the bundle.
44
45 */
46- _renderCharmListing: function() {
47- var attrs = this.get('entity').getAttrs();
48- Y.Object.each(attrs.charm_metadata, function(charm, key) {
49- var charmModel = new Y.juju.models.Charm(charm);
50- charm = charmModel.getAttrs();
51+ _renderCharmListing: function(services) {
52+ services.forEach(function(service) {
53+ var charm = service.charmModel.getAttrs();
54 charm.size = 'tiny';
55 charm.isDraggable = false;
56 var token = new widgets.browser.Token(charm);
57- var node = Y.one('[data-config="' + key + '"]');
58+ var node = Y.one('[data-config="' + service.origService.name + '"]');
59 token.render(node);
60 this._cleanup.tokens.push(token);
61 }, this);
62 },
63
64 /**
65+ Build and order a list of charms.
66+
67+ @method _buildCharmList
68+ @param {Object} the bundle entity attrs.
69+ @return {Array} the ordered list of charms in the bundle.
70+
71+ */
72+ _buildCharmList: function(bundleData) {
73+ var services = [];
74+ Y.Object.each(bundleData.services, function(service, key) {
75+ var charm = bundleData.charm_metadata[key];
76+ services.push({
77+ origService: {
78+ name: key,
79+ data: service
80+ },
81+ charmModel: new Y.juju.models.Charm(charm)
82+ });
83+ }, this);
84+ services.sort(function(a, b) {
85+ return a.charmModel.get('name') > b.charmModel.get('name') ? 1 : -1;
86+ });
87+ return services;
88+ },
89+
90+ /**
91 Renders the bundle view template into the DOM.
92
93 @method _renderBundleView
94 */
95 _renderBundleView: function() {
96- var entity = this.get('entity');
97- var attrs = entity.getAttrs();
98- attrs.charmIcons = utils.charmIconParser(attrs.charm_metadata);
99+ var bundle = this.get('entity');
100+ var bundleData = bundle.getAttrs();
101+ // Copy the bundle for use in the template so we can modify the content
102+ // without munipulating the entity.
103+ var templateData = Y.merge(bundleData);
104+ templateData.charmIcons = utils.charmIconParser(
105+ templateData.charm_metadata);
106 // Remove the svg files from the file list
107- attrs.files = attrs.files.filter(function(fileName) {
108+ templateData.files = templateData.files.filter(function(fileName) {
109 return !/\.svg$/.test(fileName);
110 });
111- var content = this.template(attrs);
112+ templateData.services = this._buildCharmList(bundleData);
113+ var content = this.template(templateData);
114 var node = this.get('container').setHTML(content);
115 var renderTo = this.get('renderTo');
116 var options = {size: [480, 360]};
117@@ -179,15 +181,27 @@
118 // remove the flag in the test(test_bundle_details_view.js)
119 // when this flag is no longer needed.
120 if (window.flags && window.flags.strictBundle) {
121- showTopo = this._positionAnnotationsIncluded(attrs.data.services);
122+ showTopo = this._positionAnnotationsIncluded(
123+ bundleData.data.services);
124 }
125 if (showTopo) {
126- this.environment = new views.BundleTopology(Y.mix({
127- db: this.fakebackend.db,
128- container: node.one('#bws-bundle'), // Id because of Y.TabView
129- store: this.get('store')
130- }, options));
131- this.environment.render();
132+ // Setup the fake backend to create topology to display the canvas-like
133+ // rendering of the bundle.
134+ this._setupLocalFakebackend();
135+ var self = this;
136+ this._parseData(bundle).then(function() {
137+ self.environment = new views.BundleTopology(Y.mix({
138+ db: self.fakebackend.db,
139+ container: node.one('#bws-bundle'), // Id because of Y.TabView
140+ store: self.get('store')
141+ }, options));
142+ self.environment.render();
143+ // Fire event to listen to during the tests so that we know when
144+ // it's rendered.
145+ self.fire('topologyRendered');
146+ }).then(null, function(error) {
147+ console.error(error.message, error);
148+ });
149 } else {
150 // Remove the bundle tab so it doesn't get PE'd when
151 // we instantiate the tabview.
152@@ -205,9 +219,7 @@
153 }
154 this._dispatchTabEvents(this.tabview);
155 this._showActiveTab();
156- this._renderCharmListing();
157-
158- this.set('rendered', true);
159+ this._renderCharmListing(templateData.services);
160 },
161
162 /**
163@@ -266,11 +278,20 @@
164 */
165 render: function() {
166 this.showIndicator(this.get('renderTo'));
167- this._setupLocalFakebackend();
168- this._fetchData().
169- then(this._parseData.bind(this)).
170- then(this._renderBundleView.bind(this)).
171- then(null, this.apiFailure.bind(this));
172+ var entity = this.get('entity');
173+ if (entity) {
174+ this._renderBundleView();
175+ } else {
176+ this.get('store').bundle(
177+ this.get('entityId'), {
178+ 'success': function(data) {
179+ this.set('entity', new models.Bundle(data));
180+ this._renderBundleView();
181+ },
182+ 'failure': this.apiFailure
183+ },
184+ this);
185+ }
186 }
187
188 }, {
189@@ -291,6 +312,7 @@
190 }, '', {
191 requires: [
192 'browser-overlay-indicator',
193+ 'juju-bundle-models',
194 'juju-charm-models',
195 'juju-view-utils',
196 'view',
197
198=== modified file 'app/templates/bundle.handlebars'
199--- app/templates/bundle.handlebars 2013-10-17 06:12:35 +0000
200+++ app/templates/bundle.handlebars 2013-10-24 00:19:58 +0000
201@@ -39,7 +39,7 @@
202 <li><a href="#bws-bundle" class="bundle">Bundle</a></li>
203 <li><a href="#bws-summary" class="summary">Summary</a></li>
204 <li><a href="#bws-readme" class="readme">Readme</a></li>
205- <li><a href="#bws-charms" class="charms">Charms</a></li>
206+ <li><a href="#bws-services" class="services">Services</a></li>
207 <li><a href="#bws-code" class="code">Code</a></li>
208 </ul>
209 <div>
210@@ -54,8 +54,6 @@
211 <p>No description</p>
212 {{/if}}
213 </div>
214- <h2>Charms within this bundle</h2>
215- [Charm list to go here]
216 <div class="changelog">
217 <h3 class="section-title">
218 {{#if recent_commits}}
219@@ -119,7 +117,11 @@
220 </div>
221 </div>
222 <div id="bws-readme"></div>
223- <div id="bws-charms">
224+ <div id="bws-services">
225+ <p>
226+ These are the services that the bundle will deploy.
227+ Non-default configuration values are listed.
228+ </p>
229 <div class="yui3-g">
230 <div class="interfaces-heading yui3-u-1-2">
231 <h3>Charm</h3>
232@@ -129,21 +131,31 @@
233 </div>
234 </div>
235 <ul class="interface-list">
236- {{#arrayObject services}}
237+ {{#each services}}
238 <li class="interface-row yui3-g">
239- <div class="yui3-u-1-2" data-config="{{ key }}"></div>
240+ <div class="yui3-u-1-2" data-config="{{ origService.name }}"></div>
241 <div class="yui3-u-1-2 charm-config">
242 <ul>
243- <li>Number of units: {{ value.num_units }}</li>
244- {{#if value.config}}
245- {{#arrayObject value.options}}
246+ <li>Service name: {{ origService.name }}</li>
247+ {{#unless charmModel.is_subordinate}}
248+ <li>
249+ Number of units:
250+ {{#if num_units }}
251+ {{ num_units }}
252+ {{else}}
253+ 1
254+ {{/if}}
255+ </li>
256+ {{/unless}}
257+ {{#if origService.options}}
258+ {{#arrayObject origService.options}}
259 <li>{{ key }}: {{ value }}</li>
260 {{/arrayObject}}
261 {{/if}}
262 </ul>
263 </div>
264 </li>
265- {{/arrayObject}}
266+ {{/each}}
267 </ul>
268 </div>
269 <div id="bws-code">
270
271=== modified file 'lib/views/browser/bundle-panel.less'
272--- lib/views/browser/bundle-panel.less 2013-10-10 00:05:02 +0000
273+++ lib/views/browser/bundle-panel.less 2013-10-24 00:19:58 +0000
274@@ -50,4 +50,7 @@
275 .topology-canvas {
276 .crosshatch-background;
277 }
278+ #bws-services {
279+ padding-top: 50px;
280+ }
281 }
282
283=== modified file 'test/test_bundle_details_view.js'
284--- test/test_bundle_details_view.js 2013-10-17 16:55:19 +0000
285+++ test/test_bundle_details_view.js 2013-10-24 00:19:58 +0000
286@@ -19,11 +19,12 @@
287 'use strict';
288
289 describe('Browser bundle detail view', function() {
290- var Y, utils, data, container, origData, view, fakestore, browser;
291+ var Y, cleanUp, utils, data, container, view, fakestore, browser, models;
292
293 before(function(done) {
294 Y = YUI(GlobalConfig).use(
295 'view',
296+ 'juju-bundle-models',
297 'juju-env-fakebackend',
298 'juju-view-bundle',
299 'subapp-browser', // required for handlebars helpers
300@@ -36,14 +37,13 @@
301 'event-simulate',
302 'node-event-simulate',
303 function(Y) {
304+ models = Y.namespace('juju.models');
305 utils = Y.namespace('juju-tests.utils');
306- var sampleData = Y.io('data/browserbundle.json', {sync: true});
307-
308- origData = Y.JSON.parse(sampleData.responseText);
309+ data = utils.loadFixture('data/browserbundle.json', true);
310
311 // Required to register the handlebars helpers
312 browser = new Y.juju.subapps.Browser({
313- store: modifyFakeStore()
314+ store: utils.makeFakeStore()
315 });
316
317 done();
318@@ -55,19 +55,20 @@
319 view._setupLocalFakebackend = function() {
320 this.fakebackend = utils.makeFakeBackend();
321 };
322+ cleanUp = utils.stubCharmIconPath();
323 });
324
325 afterEach(function() {
326 container.remove().destroy(true);
327 view.destroy();
328+ cleanUp();
329 });
330
331 function generateBundleView(options) {
332- data = Y.clone(origData);
333 container = utils.makeContainer();
334 container.append('<div class="bws-view-data"></div>');
335 var defaults = {
336- store: modifyFakeStore(),
337+ store: utils.makeFakeStore(3),
338 db: {},
339 entityId: data.id,
340 renderTo: container
341@@ -77,85 +78,60 @@
342 return view;
343 }
344
345- function modifyFakeStore(options) {
346- var defaults = {
347- bundle: function(id, callbacks) {
348- callbacks.success(data);
349- },
350- iconpath: function(id) {
351- return 'foo';
352- }
353- };
354-
355- var fakebackend = Y.mix(defaults, options, true);
356- if (view) {
357- view.set('store', fakebackend);
358- }
359- return fakebackend;
360- }
361-
362 it('can be instantiated', function() {
363 assert.equal(view instanceof Y.juju.browser.views.BrowserBundleView, true);
364 });
365
366- it('displays the bundle data in a tabview', function(done) {
367- view.after('renderedChange', function(e) {
368- assert.isNotNull(container.one('.yui3-tabview'));
369- done();
370- });
371+ it('displays the bundle data in a tabview', function() {
372+ view.set('entity', new models.Bundle(data));
373 view.render();
374+ assert.isNotNull(container.one('.yui3-tabview'));
375 });
376
377 it('fetches the readme when requested', function(done) {
378- modifyFakeStore({
379- file: function(id, filename, entityType, callbacks) {
380- assert.equal(entityType, 'bundle');
381- assert.equal(id, data.id);
382- assert.equal(filename, 'README');
383- assert.isFunction(callbacks.success);
384- assert.isFunction(callbacks.failure);
385- callbacks.success.call(view, '<div id="testit"></div>');
386- assert.isNotNull(container.one('#testit'));
387- done();
388- }
389- });
390- view.after('renderedChange', function(e) {
391- container.one('a.readme').simulate('click');
392- });
393+ var fakeStore = utils.makeFakeStore();
394+ fakeStore.file = function(id, filename, entityType, callbacks) {
395+ assert.equal(entityType, 'bundle');
396+ assert.equal(id, data.id);
397+ assert.equal(filename, 'README');
398+ assert.isFunction(callbacks.success);
399+ assert.isFunction(callbacks.failure);
400+ callbacks.success.call(view, '<div id="testit"></div>');
401+ assert.isNotNull(container.one('#testit'));
402+ done();
403+ };
404+ view.set('store', fakeStore);
405+ view.set('entity', new models.Bundle(data));
406 view.render();
407+ container.one('a.readme').simulate('click');
408 });
409
410 it('fetches a source file when requested', function(done) {
411- modifyFakeStore({
412- file: function(id, filename, entityType, callbacks) {
413- assert.equal(entityType, 'bundle');
414- assert.equal(id, data.id);
415- assert.equal(filename, 'bundles.yaml');
416- assert.isFunction(callbacks.success);
417- assert.isFunction(callbacks.failure);
418- callbacks.success.call(view, '<div id="testit"></div>');
419- assert.isNotNull(container.one('#testit'));
420- done();
421- }
422- });
423- view.after('renderedChange', function(e) {
424- container.one('a.code').simulate('click');
425- var codeNode = container.one('#bws-code');
426- codeNode.all('select option').item(2).set('selected', 'selected');
427- codeNode.one('select').simulate('change');
428- });
429-
430- view.render();
431- });
432-
433- it('renders the proper charm icons into the header', function(done) {
434- view.after('renderedChange', function(e) {
435- assert.equal(
436- container.one('.header .details .charms').all('img').size(),
437- 4);
438+ var fakeStore = utils.makeFakeStore();
439+ fakeStore.file = function(id, filename, entityType, callbacks) {
440+ assert.equal(entityType, 'bundle');
441+ assert.equal(id, data.id);
442+ assert.equal(filename, 'bundles.yaml');
443+ assert.isFunction(callbacks.success);
444+ assert.isFunction(callbacks.failure);
445+ callbacks.success.call(view, '<div id="testit"></div>');
446+ assert.isNotNull(container.one('#testit'));
447 done();
448- });
449- view.render();
450+ };
451+ view.set('store', fakeStore);
452+ view.set('entity', new models.Bundle(data));
453+ view.render();
454+ container.one('a.code').simulate('click');
455+ var codeNode = container.one('#bws-code');
456+ codeNode.all('select option').item(2).set('selected', 'selected');
457+ codeNode.one('select').simulate('change');
458+ });
459+
460+ it('renders the proper charm icons into the header', function() {
461+ view.set('entity', new models.Bundle(data));
462+ view.render();
463+ assert.equal(
464+ container.one('.header .details .charms').all('img').size(), 4);
465 });
466
467 it('deploys a bundle when \'add\' button is clicked', function(done) {
468@@ -165,14 +141,13 @@
469 assert.isObject(data);
470 done();
471 });
472- view.after('renderedChange', function(e) {
473- container.one('.bundle .add').simulate('click');
474- });
475+ view.set('entity', new models.Bundle(data));
476 view.render();
477+ container.one('.bundle .add').simulate('click');
478 });
479
480 it('fails gracefully if services don\'t provide xy annotations',
481- function(done) {
482+ function() {
483 window.flags = { strictBundle: true };
484 view._parseData = function() {
485 return new Y.Promise(function(resolve) { resolve(); });
486@@ -195,15 +170,13 @@
487 }
488 };
489 }});
490- view.after('renderedChange', function(e) {
491- assert.isNull(container.one('#bws-bundle'));
492- assert.isNull(container.one('a[href=#bws-bundle]'));
493- // Check that the charms tab is the landing tab
494- assert.equal(view.tabview.get('selection').get('index'), 2);
495- window.flags = {};
496- done();
497- });
498+ view.set('entity', new models.Bundle(data));
499 view.render();
500+ assert.isNull(container.one('#bws-bundle'));
501+ assert.isNull(container.one('a[href=#bws-bundle]'));
502+ // Check that the charms tab is the landing tab
503+ assert.equal(view.tabview.get('selection').get('index'), 2);
504+ window.flags = {};
505 });
506
507 it('renders the bundle topology into the view', function(done) {
508@@ -211,112 +184,120 @@
509 view._parseData = function() {
510 return new Y.Promise(function(resolve) { resolve(); });
511 };
512- view.set('entity', {
513- getAttrs: function() {
514- return {
515- charm_metadata: {},
516- files: [],
517- data: {
518- services: {
519- foo: {
520- annotations: {
521- 'gui-x': '1',
522- 'gui-y': '2'
523- }
524- },
525- bar: {
526- annotations: {
527- 'gui-x': '3',
528- 'gui-y': '4'
529- }
530- }
531+ var entity = {
532+ charm_metadata: {
533+ foo: {
534+ id: 'precise/foo-9',
535+ storeId: 'testid',
536+ name: 'foo'
537+ },
538+ bar: {
539+ id: 'precise/bar-10',
540+ storeId: 'testid',
541+ name: 'bar'
542+ }
543+ },
544+ files: [],
545+ data: {
546+ services: {
547+ foo: {
548+ annotations: {
549+ 'gui-x': '1',
550+ 'gui-y': '2'
551+ }
552+ },
553+ bar: {
554+ annotations: {
555+ 'gui-x': '3',
556+ 'gui-y': '4'
557 }
558 }
559- };
560- }});
561- view.after('renderedChange', function(e) {
562+ }
563+ }
564+ };
565+ view.on('topologyRendered', function(e) {
566 assert.isNotNull(container.one('.topology-canvas'));
567 // Check that the bundle topology tab is the landing tab.
568 assert.equal(view.tabview.get('selection').get('index'), 0);
569 window.flags = {};
570 done();
571 });
572- view.render();
573- });
574-
575- it('renders the charm list tab properly', function(done) {
576- view._parseData = function() {
577- return new Y.Promise(function(resolve) { resolve(); });
578- };
579- view.set('entity', {
580- getAttrs: function() {
581- return {
582- charm_metadata: {
583- foo: {
584- id: 'precise/foo-9',
585- storeId: 'testid',
586- name: 'foo'
587- },
588- bar: {
589- id: 'precise/bar-10',
590- storeId: 'testid',
591- name: 'bar'
592- }
593- },
594- files: [],
595- data: {
596- services: {
597- foo: {
598- annotations: {
599- 'gui-x': '1',
600- 'gui-y': '2'
601- }
602- },
603- bar: {
604- annotations: {
605- 'gui-x': '3',
606- 'gui-y': '4'
607- }
608- }
609- }
610- },
611- services: {
612- foo: {
613- annotations: {
614- 'gui-x': '1',
615- 'gui-y': '2'
616- }
617- },
618- bar: {
619- annotations: {
620- 'gui-x': '3',
621- 'gui-y': '4'
622- }
623- }
624- }
625- };
626- }});
627- view.after('renderedChange', function(e) {
628- var tab = container.one('#bws-charms');
629- assert.equal(tab.all('.token').size(), 2);
630- done();
631- });
632- view.render();
633- });
634-
635- it('selects the proper tab when given one', function(done) {
636- view = generateBundleView({
637- activeTab: '#bws-charms'
638- });
639- view._parseData = function() {
640- return new Y.Promise(function(resolve) { resolve(); });
641- };
642- view.render();
643- view.after('renderedChange', function(e) {
644- var selected = view.get('container').one('.yui3-tab-selected a');
645- assert.equal(selected.getAttribute('href'), '#bws-charms');
646- done();
647- });
648+ view.set('entity', new models.Bundle(entity));
649+ view.render();
650+ });
651+
652+ it('renders the charm list tab properly', function() {
653+ // This is not under test. It's async and only causes trouble in other
654+ // tests.
655+ view._parseData = function() {
656+ return new Y.Promise(function(resolve) { resolve(); });
657+ };
658+
659+ var entity = {
660+ charm_metadata: {
661+ foo: {
662+ id: 'precise/foo-9',
663+ storeId: 'testid',
664+ name: 'foo'
665+ },
666+ bar: {
667+ id: 'precise/bar-10',
668+ storeId: 'testid',
669+ name: 'bar'
670+ }
671+ },
672+ files: [],
673+ data: {
674+ services: {
675+ foo: {
676+ annotations: {
677+ 'gui-x': '1',
678+ 'gui-y': '2'
679+ }
680+ },
681+ bar: {
682+ annotations: {
683+ 'gui-x': '3',
684+ 'gui-y': '4'
685+ }
686+ }
687+ }
688+ },
689+ services: {
690+ foo: {
691+ annotations: {
692+ 'gui-x': '1',
693+ 'gui-y': '2'
694+ }
695+ },
696+ bar: {
697+ annotations: {
698+ 'gui-x': '3',
699+ 'gui-y': '4'
700+ }
701+ }
702+ }
703+ };
704+ view.set('entity', new models.Bundle(entity));
705+ view.render();
706+ var tab = container.one('#bws-services');
707+ assert.equal(tab.all('.token').size(), 2);
708+ var charmConfigNodes = tab.all('.charm-config');
709+ assert.equal(
710+ charmConfigNodes.item(0).one('li').get('text'), 'Service name: bar');
711+ assert.equal(
712+ charmConfigNodes.item(1).one('li').get('text'), 'Service name: foo');
713+ });
714+
715+ it('selects the proper tab when given one', function() {
716+ view.set('activeTab', '#bws-services');
717+ view._parseData = function() {
718+ return new Y.Promise(function(resolve) { resolve(); });
719+ };
720+ view.set('entity', new models.Bundle(data));
721+ view.render();
722+ var selected = view.get('container').one('.yui3-tab-selected a');
723+ assert.equal(selected.getAttribute('href'), '#bws-services');
724 });
725
726 });

Subscribers

People subscribed via source and target branches