##// END OF EJS Templates
Make all tests async display safe
Jonathan Frederic -
Show More
@@ -1,603 +1,603
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define(["widgets/js/manager",
5 5 "underscore",
6 6 "backbone",
7 7 "jquery",
8 8 "base/js/utils",
9 9 "base/js/namespace",
10 10 ], function(widgetmanager, _, Backbone, $, utils, IPython){
11 11
12 12 var WidgetModel = Backbone.Model.extend({
13 13 constructor: function (widget_manager, model_id, comm) {
14 14 // Constructor
15 15 //
16 16 // Creates a WidgetModel instance.
17 17 //
18 18 // Parameters
19 19 // ----------
20 20 // widget_manager : WidgetManager instance
21 21 // model_id : string
22 22 // An ID unique to this model.
23 23 // comm : Comm instance (optional)
24 24 this.widget_manager = widget_manager;
25 25 this.state_change = Promise.resolve();
26 26 this._buffered_state_diff = {};
27 27 this.pending_msgs = 0;
28 28 this.msg_buffer = null;
29 29 this.state_lock = null;
30 30 this.id = model_id;
31 31 this.views = {};
32 32
33 33 if (comm !== undefined) {
34 34 // Remember comm associated with the model.
35 35 this.comm = comm;
36 36 comm.model = this;
37 37
38 38 // Hook comm messages up to model.
39 39 comm.on_close($.proxy(this._handle_comm_closed, this));
40 40 comm.on_msg($.proxy(this._handle_comm_msg, this));
41 41 }
42 42 return Backbone.Model.apply(this);
43 43 },
44 44
45 45 send: function (content, callbacks) {
46 46 // Send a custom msg over the comm.
47 47 if (this.comm !== undefined) {
48 48 var data = {method: 'custom', content: content};
49 49 this.comm.send(data, callbacks);
50 50 this.pending_msgs++;
51 51 }
52 52 },
53 53
54 54 _handle_comm_closed: function (msg) {
55 55 // Handle when a widget is closed.
56 56 this.trigger('comm:close');
57 57 this.stopListening();
58 58 this.trigger('destroy', this);
59 59 delete this.comm.model; // Delete ref so GC will collect widget model.
60 60 delete this.comm;
61 61 delete this.model_id; // Delete id from model so widget manager cleans up.
62 62 for (var id in this.views) {
63 63 if (this.views.hasOwnProperty(id)) {
64 64 this.views[id].remove();
65 65 }
66 66 }
67 67 },
68 68
69 69 _handle_comm_msg: function (msg) {
70 70 // Handle incoming comm msg.
71 71 var method = msg.content.data.method;
72 72 var that = this;
73 73 switch (method) {
74 74 case 'update':
75 75 this.state_change = this.state_change.then(function() {
76 76 return that.set_state(msg.content.data.state);
77 77 }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true));
78 78 break;
79 79 case 'custom':
80 80 this.trigger('msg:custom', msg.content.data.content);
81 81 break;
82 82 case 'display':
83 return that.widget_manager.display_view(msg, that);
83 this.widget_manager.display_view(msg, that);
84 84 break;
85 85 }
86 86 },
87 87
88 88 set_state: function (state) {
89 89 var that = this;
90 90 // Handle when a widget is updated via the python side.
91 91 return this._unpack_models(state).then(function(state) {
92 92 that.state_lock = state;
93 93 try {
94 94 WidgetModel.__super__.set.call(that, state);
95 95 } finally {
96 96 that.state_lock = null;
97 97 }
98 98 return Promise.resolve();
99 99 }, utils.reject("Couldn't set model state", true));
100 100 },
101 101
102 102 _handle_status: function (msg, callbacks) {
103 103 // Handle status msgs.
104 104
105 105 // execution_state : ('busy', 'idle', 'starting')
106 106 if (this.comm !== undefined) {
107 107 if (msg.content.execution_state ==='idle') {
108 108 // Send buffer if this message caused another message to be
109 109 // throttled.
110 110 if (this.msg_buffer !== null &&
111 111 (this.get('msg_throttle') || 3) === this.pending_msgs) {
112 112 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
113 113 this.comm.send(data, callbacks);
114 114 this.msg_buffer = null;
115 115 } else {
116 116 --this.pending_msgs;
117 117 }
118 118 }
119 119 }
120 120 },
121 121
122 122 callbacks: function(view) {
123 123 // Create msg callbacks for a comm msg.
124 124 var callbacks = this.widget_manager.callbacks(view);
125 125
126 126 if (callbacks.iopub === undefined) {
127 127 callbacks.iopub = {};
128 128 }
129 129
130 130 var that = this;
131 131 callbacks.iopub.status = function (msg) {
132 132 that._handle_status(msg, callbacks);
133 133 };
134 134 return callbacks;
135 135 },
136 136
137 137 set: function(key, val, options) {
138 138 // Set a value.
139 139 var return_value = WidgetModel.__super__.set.apply(this, arguments);
140 140
141 141 // Backbone only remembers the diff of the most recent set()
142 142 // operation. Calling set multiple times in a row results in a
143 143 // loss of diff information. Here we keep our own running diff.
144 144 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
145 145 return return_value;
146 146 },
147 147
148 148 sync: function (method, model, options) {
149 149 // Handle sync to the back-end. Called when a model.save() is called.
150 150
151 151 // Make sure a comm exists.
152 152 var error = options.error || function() {
153 153 console.error('Backbone sync error:', arguments);
154 154 };
155 155 if (this.comm === undefined) {
156 156 error();
157 157 return false;
158 158 }
159 159
160 160 // Delete any key value pairs that the back-end already knows about.
161 161 var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
162 162 if (this.state_lock !== null) {
163 163 var keys = Object.keys(this.state_lock);
164 164 for (var i=0; i<keys.length; i++) {
165 165 var key = keys[i];
166 166 if (attrs[key] === this.state_lock[key]) {
167 167 delete attrs[key];
168 168 }
169 169 }
170 170 }
171 171
172 172 // Only sync if there are attributes to send to the back-end.
173 173 attrs = this._pack_models(attrs);
174 174 if (_.size(attrs) > 0) {
175 175
176 176 // If this message was sent via backbone itself, it will not
177 177 // have any callbacks. It's important that we create callbacks
178 178 // so we can listen for status messages, etc...
179 179 var callbacks = options.callbacks || this.callbacks();
180 180
181 181 // Check throttle.
182 182 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
183 183 // The throttle has been exceeded, buffer the current msg so
184 184 // it can be sent once the kernel has finished processing
185 185 // some of the existing messages.
186 186
187 187 // Combine updates if it is a 'patch' sync, otherwise replace updates
188 188 switch (method) {
189 189 case 'patch':
190 190 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
191 191 break;
192 192 case 'update':
193 193 case 'create':
194 194 this.msg_buffer = attrs;
195 195 break;
196 196 default:
197 197 error();
198 198 return false;
199 199 }
200 200 this.msg_buffer_callbacks = callbacks;
201 201
202 202 } else {
203 203 // We haven't exceeded the throttle, send the message like
204 204 // normal.
205 205 var data = {method: 'backbone', sync_data: attrs};
206 206 this.comm.send(data, callbacks);
207 207 this.pending_msgs++;
208 208 }
209 209 }
210 210 // Since the comm is a one-way communication, assume the message
211 211 // arrived. Don't call success since we don't have a model back from the server
212 212 // this means we miss out on the 'sync' event.
213 213 this._buffered_state_diff = {};
214 214 },
215 215
216 216 save_changes: function(callbacks) {
217 217 // Push this model's state to the back-end
218 218 //
219 219 // This invokes a Backbone.Sync.
220 220 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
221 221 },
222 222
223 223 _pack_models: function(value) {
224 224 // Replace models with model ids recursively.
225 225 var that = this;
226 226 var packed;
227 227 if (value instanceof Backbone.Model) {
228 228 return "IPY_MODEL_" + value.id;
229 229
230 230 } else if ($.isArray(value)) {
231 231 packed = [];
232 232 _.each(value, function(sub_value, key) {
233 233 packed.push(that._pack_models(sub_value));
234 234 });
235 235 return packed;
236 236 } else if (value instanceof Date || value instanceof String) {
237 237 return value;
238 238 } else if (value instanceof Object) {
239 239 packed = {};
240 240 _.each(value, function(sub_value, key) {
241 241 packed[key] = that._pack_models(sub_value);
242 242 });
243 243 return packed;
244 244
245 245 } else {
246 246 return value;
247 247 }
248 248 },
249 249
250 250 _unpack_models: function(value) {
251 251 // Replace model ids with models recursively.
252 252 var that = this;
253 253 var unpacked;
254 254 if ($.isArray(value)) {
255 255 unpacked = [];
256 256 _.each(value, function(sub_value, key) {
257 257 unpacked.push(that._unpack_models(sub_value));
258 258 });
259 259 return Promise.all(unpacked);
260 260 } else if (value instanceof Object) {
261 261 unpacked = {};
262 262 _.each(value, function(sub_value, key) {
263 263 unpacked[key] = that._unpack_models(sub_value);
264 264 });
265 265 return utils.resolve_dict(unpacked);
266 266 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
267 267 // get_model returns a promise already
268 268 return this.widget_manager.get_model(value.slice(10, value.length));
269 269 } else {
270 270 return Promise.resolve(value);
271 271 }
272 272 },
273 273
274 274 on_some_change: function(keys, callback, context) {
275 275 // on_some_change(["key1", "key2"], foo, context) differs from
276 276 // on("change:key1 change:key2", foo, context).
277 277 // If the widget attributes key1 and key2 are both modified,
278 278 // the second form will result in foo being called twice
279 279 // while the first will call foo only once.
280 280 this.on('change', function() {
281 281 if (keys.some(this.hasChanged, this)) {
282 282 callback.apply(context);
283 283 }
284 284 }, this);
285 285
286 286 },
287 287 });
288 288 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
289 289
290 290
291 291 var WidgetView = Backbone.View.extend({
292 292 initialize: function(parameters) {
293 293 // Public constructor.
294 294 this.model.on('change',this.update,this);
295 295 this.options = parameters.options;
296 296 this.child_model_views = {};
297 297 this.child_views = {};
298 298 this.id = this.id || utils.uuid();
299 299 this.model.views[this.id] = this;
300 300 this.on('displayed', function() {
301 301 this.is_displayed = true;
302 302 }, this);
303 303 },
304 304
305 305 update: function(){
306 306 // Triggered on model change.
307 307 //
308 308 // Update view to be consistent with this.model
309 309 },
310 310
311 311 create_child_view: function(child_model, options) {
312 312 // Create and promise that resolves to a child view of a given model
313 313 var that = this;
314 314 options = $.extend({ parent: this }, options || {});
315 315 return this.model.widget_manager.create_view(child_model, options).then(function(child_view) {
316 316 // Associate the view id with the model id.
317 317 if (that.child_model_views[child_model.id] === undefined) {
318 318 that.child_model_views[child_model.id] = [];
319 319 }
320 320 that.child_model_views[child_model.id].push(child_view.id);
321 321 // Remember the view by id.
322 322 that.child_views[child_view.id] = child_view;
323 323 return child_view;
324 324 }, utils.reject("Couldn't create child view"));
325 325 },
326 326
327 327 pop_child_view: function(child_model) {
328 328 // Delete a child view that was previously created using create_child_view.
329 329 var view_ids = this.child_model_views[child_model.id];
330 330 if (view_ids !== undefined) {
331 331
332 332 // Only delete the first view in the list.
333 333 var view_id = view_ids[0];
334 334 var view = this.child_views[view_id];
335 335 delete this.child_views[view_id];
336 336 view_ids.splice(0,1);
337 337 delete child_model.views[view_id];
338 338
339 339 // Remove the view list specific to this model if it is empty.
340 340 if (view_ids.length === 0) {
341 341 delete this.child_model_views[child_model.id];
342 342 }
343 343 return view;
344 344 }
345 345 return null;
346 346 },
347 347
348 348 do_diff: function(old_list, new_list, removed_callback, added_callback) {
349 349 // Difference a changed list and call remove and add callbacks for
350 350 // each removed and added item in the new list.
351 351 //
352 352 // Parameters
353 353 // ----------
354 354 // old_list : array
355 355 // new_list : array
356 356 // removed_callback : Callback(item)
357 357 // Callback that is called for each item removed.
358 358 // added_callback : Callback(item)
359 359 // Callback that is called for each item added.
360 360
361 361 // Walk the lists until an unequal entry is found.
362 362 var i;
363 363 for (i = 0; i < new_list.length; i++) {
364 364 if (i >= old_list.length || new_list[i] !== old_list[i]) {
365 365 break;
366 366 }
367 367 }
368 368
369 369 // Remove the non-matching items from the old list.
370 370 for (var j = i; j < old_list.length; j++) {
371 371 removed_callback(old_list[j]);
372 372 }
373 373
374 374 // Add the rest of the new list items.
375 375 for (; i < new_list.length; i++) {
376 376 added_callback(new_list[i]);
377 377 }
378 378 },
379 379
380 380 callbacks: function(){
381 381 // Create msg callbacks for a comm msg.
382 382 return this.model.callbacks(this);
383 383 },
384 384
385 385 render: function(){
386 386 // Render the view.
387 387 //
388 388 // By default, this is only called the first time the view is created
389 389 },
390 390
391 391 show: function(){
392 392 // Show the widget-area
393 393 if (this.options && this.options.cell &&
394 394 this.options.cell.widget_area !== undefined) {
395 395 this.options.cell.widget_area.show();
396 396 }
397 397 },
398 398
399 399 send: function (content) {
400 400 // Send a custom msg associated with this view.
401 401 this.model.send(content, this.callbacks());
402 402 },
403 403
404 404 touch: function () {
405 405 this.model.save_changes(this.callbacks());
406 406 },
407 407
408 408 after_displayed: function (callback, context) {
409 409 // Calls the callback right away is the view is already displayed
410 410 // otherwise, register the callback to the 'displayed' event.
411 411 if (this.is_displayed) {
412 412 callback.apply(context);
413 413 } else {
414 414 this.on('displayed', callback, context);
415 415 }
416 416 },
417 417 });
418 418
419 419
420 420 var DOMWidgetView = WidgetView.extend({
421 421 initialize: function (parameters) {
422 422 // Public constructor
423 423 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
424 424 this.on('displayed', this.show, this);
425 425 this.model.on('change:visible', this.update_visible, this);
426 426 this.model.on('change:_css', this.update_css, this);
427 427
428 428 this.model.on('change:_dom_classes', function(model, new_classes) {
429 429 var old_classes = model.previous('_dom_classes');
430 430 this.update_classes(old_classes, new_classes);
431 431 }, this);
432 432
433 433 this.model.on('change:color', function (model, value) {
434 434 this.update_attr('color', value); }, this);
435 435
436 436 this.model.on('change:background_color', function (model, value) {
437 437 this.update_attr('background', value); }, this);
438 438
439 439 this.model.on('change:width', function (model, value) {
440 440 this.update_attr('width', value); }, this);
441 441
442 442 this.model.on('change:height', function (model, value) {
443 443 this.update_attr('height', value); }, this);
444 444
445 445 this.model.on('change:border_color', function (model, value) {
446 446 this.update_attr('border-color', value); }, this);
447 447
448 448 this.model.on('change:border_width', function (model, value) {
449 449 this.update_attr('border-width', value); }, this);
450 450
451 451 this.model.on('change:border_style', function (model, value) {
452 452 this.update_attr('border-style', value); }, this);
453 453
454 454 this.model.on('change:font_style', function (model, value) {
455 455 this.update_attr('font-style', value); }, this);
456 456
457 457 this.model.on('change:font_weight', function (model, value) {
458 458 this.update_attr('font-weight', value); }, this);
459 459
460 460 this.model.on('change:font_size', function (model, value) {
461 461 this.update_attr('font-size', this._default_px(value)); }, this);
462 462
463 463 this.model.on('change:font_family', function (model, value) {
464 464 this.update_attr('font-family', value); }, this);
465 465
466 466 this.model.on('change:padding', function (model, value) {
467 467 this.update_attr('padding', value); }, this);
468 468
469 469 this.model.on('change:margin', function (model, value) {
470 470 this.update_attr('margin', this._default_px(value)); }, this);
471 471
472 472 this.model.on('change:border_radius', function (model, value) {
473 473 this.update_attr('border-radius', this._default_px(value)); }, this);
474 474
475 475 this.after_displayed(function() {
476 476 this.update_visible(this.model, this.model.get("visible"));
477 477 this.update_classes([], this.model.get('_dom_classes'));
478 478
479 479 this.update_attr('color', this.model.get('color'));
480 480 this.update_attr('background', this.model.get('background_color'));
481 481 this.update_attr('width', this.model.get('width'));
482 482 this.update_attr('height', this.model.get('height'));
483 483 this.update_attr('border-color', this.model.get('border_color'));
484 484 this.update_attr('border-width', this.model.get('border_width'));
485 485 this.update_attr('border-style', this.model.get('border_style'));
486 486 this.update_attr('font-style', this.model.get('font_style'));
487 487 this.update_attr('font-weight', this.model.get('font_weight'));
488 488 this.update_attr('font-size', this.model.get('font_size'));
489 489 this.update_attr('font-family', this.model.get('font_family'));
490 490 this.update_attr('padding', this.model.get('padding'));
491 491 this.update_attr('margin', this.model.get('margin'));
492 492 this.update_attr('border-radius', this.model.get('border_radius'));
493 493
494 494 this.update_css(this.model, this.model.get("_css"));
495 495 }, this);
496 496 },
497 497
498 498 _default_px: function(value) {
499 499 // Makes browser interpret a numerical string as a pixel value.
500 500 if (/^\d+\.?(\d+)?$/.test(value.trim())) {
501 501 return value.trim() + 'px';
502 502 }
503 503 return value;
504 504 },
505 505
506 506 update_attr: function(name, value) {
507 507 // Set a css attr of the widget view.
508 508 this.$el.css(name, value);
509 509 },
510 510
511 511 update_visible: function(model, value) {
512 512 // Update visibility
513 513 this.$el.toggle(value);
514 514 },
515 515
516 516 update_css: function (model, css) {
517 517 // Update the css styling of this view.
518 518 var e = this.$el;
519 519 if (css === undefined) {return;}
520 520 for (var i = 0; i < css.length; i++) {
521 521 // Apply the css traits to all elements that match the selector.
522 522 var selector = css[i][0];
523 523 var elements = this._get_selector_element(selector);
524 524 if (elements.length > 0) {
525 525 var trait_key = css[i][1];
526 526 var trait_value = css[i][2];
527 527 elements.css(trait_key ,trait_value);
528 528 }
529 529 }
530 530 },
531 531
532 532 update_classes: function (old_classes, new_classes, $el) {
533 533 // Update the DOM classes applied to an element, default to this.$el.
534 534 if ($el===undefined) {
535 535 $el = this.$el;
536 536 }
537 537 this.do_diff(old_classes, new_classes, function(removed) {
538 538 $el.removeClass(removed);
539 539 }, function(added) {
540 540 $el.addClass(added);
541 541 });
542 542 },
543 543
544 544 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
545 545 // Update the DOM classes applied to the widget based on a single
546 546 // trait's value.
547 547 //
548 548 // Given a trait value classes map, this function automatically
549 549 // handles applying the appropriate classes to the widget element
550 550 // and removing classes that are no longer valid.
551 551 //
552 552 // Parameters
553 553 // ----------
554 554 // class_map: dictionary
555 555 // Dictionary of trait values to class lists.
556 556 // Example:
557 557 // {
558 558 // success: ['alert', 'alert-success'],
559 559 // info: ['alert', 'alert-info'],
560 560 // warning: ['alert', 'alert-warning'],
561 561 // danger: ['alert', 'alert-danger']
562 562 // };
563 563 // trait_name: string
564 564 // Name of the trait to check the value of.
565 565 // previous_trait_value: optional string, default ''
566 566 // Last trait value
567 567 // $el: optional jQuery element handle, defaults to this.$el
568 568 // Element that the classes are applied to.
569 569 var key = previous_trait_value;
570 570 if (key === undefined) {
571 571 key = this.model.previous(trait_name);
572 572 }
573 573 var old_classes = class_map[key] ? class_map[key] : [];
574 574 key = this.model.get(trait_name);
575 575 var new_classes = class_map[key] ? class_map[key] : [];
576 576
577 577 this.update_classes(old_classes, new_classes, $el || this.$el);
578 578 },
579 579
580 580 _get_selector_element: function (selector) {
581 581 // Get the elements via the css selector.
582 582 var elements;
583 583 if (!selector) {
584 584 elements = this.$el;
585 585 } else {
586 586 elements = this.$el.find(selector).addBack(selector);
587 587 }
588 588 return elements;
589 589 },
590 590 });
591 591
592 592
593 593 var widget = {
594 594 'WidgetModel': WidgetModel,
595 595 'WidgetView': WidgetView,
596 596 'DOMWidgetView': DOMWidgetView,
597 597 };
598 598
599 599 // For backwards compatability.
600 600 $.extend(IPython, widget);
601 601
602 602 return widget;
603 603 });
@@ -1,778 +1,799
1 1 //
2 2 // Utility functions for the HTML notebook's CasperJS tests.
3 3 //
4 4 casper.get_notebook_server = function () {
5 5 // Get the URL of a notebook server on which to run tests.
6 6 var port = casper.cli.get("port");
7 7 port = (typeof port === 'undefined') ? '8888' : port;
8 8 return casper.cli.get("url") || ('http://127.0.0.1:' + port);
9 9 };
10 10
11 11 casper.open_new_notebook = function () {
12 12 // Create and open a new notebook.
13 13 var baseUrl = this.get_notebook_server();
14 14 this.start(baseUrl);
15 15 this.waitFor(this.page_loaded);
16 16 this.thenClick('button#new_notebook');
17 17 this.waitForPopup('');
18 18
19 19 this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
20 20 this.then(function () {
21 21 this.open(this.popups[0].url);
22 22 });
23 23 this.waitFor(this.page_loaded);
24 24
25 25 // Hook the log and error methods of the console, forcing them to
26 26 // serialize their arguments before printing. This allows the
27 27 // Objects to cross into the phantom/slimer regime for display.
28 28 this.thenEvaluate(function(){
29 29 var serialize_arguments = function(f, context) {
30 30 return function() {
31 31 var pretty_arguments = [];
32 32 for (var i = 0; i < arguments.length; i++) {
33 33 var value = arguments[i];
34 34 if (value instanceof Object) {
35 35 var name = value.name || 'Object';
36 36 // Print a JSON string representation of the object.
37 37 // If we don't do this, [Object object] gets printed
38 38 // by casper, which is useless. The long regular
39 39 // expression reduces the verbosity of the JSON.
40 40 pretty_arguments.push(name + ' {' + JSON.stringify(value, null, ' ')
41 41 .replace(/(\s+)?({)?(\s+)?(}(\s+)?,?)?(\s+)?(\s+)?\n/g, '\n')
42 42 .replace(/\n(\s+)?\n/g, '\n'));
43 43 } else {
44 44 pretty_arguments.push(value);
45 45 }
46 46 }
47 47 f.apply(context, pretty_arguments);
48 48 };
49 49 };
50 50 console.log = serialize_arguments(console.log, console);
51 51 console.error = serialize_arguments(console.error, console);
52 52 });
53 53
54 54 // Make sure the kernel has started
55 55 this.waitFor(this.kernel_running);
56 56 // track the IPython busy/idle state
57 57 this.thenEvaluate(function () {
58 58 require(['base/js/namespace', 'base/js/events'], function (IPython, events) {
59 59
60 60 events.on('kernel_idle.Kernel',function () {
61 61 IPython._status = 'idle';
62 62 });
63 63 events.on('kernel_busy.Kernel',function () {
64 64 IPython._status = 'busy';
65 65 });
66 66 });
67 67 });
68 68
69 69 // Because of the asynchronous nature of SlimerJS (Gecko), we need to make
70 70 // sure the notebook has actually been loaded into the IPython namespace
71 71 // before running any tests.
72 72 this.waitFor(function() {
73 73 return this.evaluate(function () {
74 74 return IPython.notebook;
75 75 });
76 76 });
77 77 };
78 78
79 79 casper.page_loaded = function() {
80 80 // Return whether or not the kernel is running.
81 81 return this.evaluate(function() {
82 82 return typeof IPython !== "undefined" &&
83 83 IPython.page !== undefined;
84 84 });
85 85 };
86 86
87 87 casper.kernel_running = function() {
88 88 // Return whether or not the kernel is running.
89 89 return this.evaluate(function() {
90 90 return IPython &&
91 91 IPython.notebook &&
92 92 IPython.notebook.kernel &&
93 93 IPython.notebook.kernel.is_connected();
94 94 });
95 95 };
96 96
97 97 casper.kernel_disconnected = function() {
98 98 return this.evaluate(function() {
99 99 return IPython.notebook.kernel.is_fully_disconnected();
100 100 });
101 101 };
102 102
103 103 casper.wait_for_kernel_ready = function () {
104 104 this.waitFor(this.kernel_running);
105 105 this.thenEvaluate(function () {
106 106 IPython._kernel_ready = false;
107 107 IPython.notebook.kernel.kernel_info(
108 108 function () {
109 109 IPython._kernel_ready = true;
110 110 });
111 111 });
112 112 this.waitFor(function () {
113 113 return this.evaluate(function () {
114 114 return IPython._kernel_ready;
115 115 });
116 116 });
117 117 };
118 118
119 119 casper.shutdown_current_kernel = function () {
120 120 // Shut down the current notebook's kernel.
121 121 this.thenEvaluate(function() {
122 122 IPython.notebook.session.delete();
123 123 });
124 124 // We close the page right after this so we need to give it time to complete.
125 125 this.wait(1000);
126 126 };
127 127
128 128 casper.delete_current_notebook = function () {
129 129 // Delete created notebook.
130 130
131 131 // For some unknown reason, this doesn't work?!?
132 132 this.thenEvaluate(function() {
133 133 IPython.notebook.delete();
134 134 });
135 135 };
136 136
137 137 casper.wait_for_busy = function () {
138 138 // Waits for the notebook to enter a busy state.
139 139 this.waitFor(function () {
140 140 return this.evaluate(function () {
141 141 return IPython._status == 'busy';
142 142 });
143 143 });
144 144 };
145 145
146 146 casper.wait_for_idle = function () {
147 147 // Waits for the notebook to idle.
148 148 this.waitFor(function () {
149 149 return this.evaluate(function () {
150 150 return IPython._status == 'idle';
151 151 });
152 152 });
153 153 };
154 154
155 155 casper.wait_for_output = function (cell_num, out_num) {
156 156 // wait for the nth output in a given cell
157 157 this.wait_for_idle();
158 158 out_num = out_num || 0;
159 159 this.then(function() {
160 160 this.waitFor(function (c, o) {
161 161 return this.evaluate(function get_output(c, o) {
162 162 var cell = IPython.notebook.get_cell(c);
163 163 return cell.output_area.outputs.length > o;
164 164 },
165 165 // pass parameter from the test suite js to the browser code js
166 166 {c : cell_num, o : out_num});
167 167 });
168 168 },
169 169 function then() { },
170 170 function timeout() {
171 171 this.echo("wait_for_output timed out!");
172 172 });
173 173 };
174 174
175 175 casper.wait_for_widget = function (widget_info) {
176 176 // wait for a widget msg que to reach 0
177 177 //
178 178 // Parameters
179 179 // ----------
180 180 // widget_info : object
181 181 // Object which contains info related to the widget. The model_id property
182 182 // is used to identify the widget.
183
184 // Clear the results of a previous query, if they exist. Make sure a
185 // dictionary exists to store the async results in.
186 this.thenEvaluate(function(model_id) {
187 if (window.pending_msgs === undefined) {
188 window.pending_msgs = {};
189 } else {
190 window.pending_msgs[model_id] = -1;
191 }
192 }, {model_id: widget_info.model_id});
193
194 // Wait for the pending messages to be 0.
183 195 this.waitFor(function () {
184 var pending = this.evaluate(function (m) {
185 return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
186 }, {m: widget_info.model_id});
196 var pending = this.evaluate(function (model_id) {
197
198 // Get the model. Once the model is had, store it's pending_msgs
199 // count in the window's dictionary.
200 IPython.notebook.kernel.widget_manager.get_model(model_id)
201 .then(function(model) {
202 window.pending_msgs[model_id] = model.pending_msgs;
203 });
204
205 // Return the pending_msgs result.
206 return window.pending_msgs[model_id];
207 }, {model_id: widget_info.model_id});
187 208
188 209 if (pending === 0) {
189 210 return true;
190 211 } else {
191 212 return false;
192 213 }
193 214 });
194 215 };
195 216
196 217 casper.get_output_cell = function (cell_num, out_num) {
197 218 // return an output of a given cell
198 219 out_num = out_num || 0;
199 220 var result = casper.evaluate(function (c, o) {
200 221 var cell = IPython.notebook.get_cell(c);
201 222 return cell.output_area.outputs[o];
202 223 },
203 224 {c : cell_num, o : out_num});
204 225 if (!result) {
205 226 var num_outputs = casper.evaluate(function (c) {
206 227 var cell = IPython.notebook.get_cell(c);
207 228 return cell.output_area.outputs.length;
208 229 },
209 230 {c : cell_num});
210 231 this.test.assertTrue(false,
211 232 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
212 233 );
213 234 } else {
214 235 return result;
215 236 }
216 237 };
217 238
218 239 casper.get_cells_length = function () {
219 240 // return the number of cells in the notebook
220 241 var result = casper.evaluate(function () {
221 242 return IPython.notebook.get_cells().length;
222 243 });
223 244 return result;
224 245 };
225 246
226 247 casper.set_cell_text = function(index, text){
227 248 // Set the text content of a cell.
228 249 this.evaluate(function (index, text) {
229 250 var cell = IPython.notebook.get_cell(index);
230 251 cell.set_text(text);
231 252 }, index, text);
232 253 };
233 254
234 255 casper.get_cell_text = function(index){
235 256 // Get the text content of a cell.
236 257 return this.evaluate(function (index) {
237 258 var cell = IPython.notebook.get_cell(index);
238 259 return cell.get_text();
239 260 }, index);
240 261 };
241 262
242 263 casper.insert_cell_at_bottom = function(cell_type){
243 264 // Inserts a cell at the bottom of the notebook
244 265 // Returns the new cell's index.
245 266 return this.evaluate(function (cell_type) {
246 267 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
247 268 return IPython.notebook.find_cell_index(cell);
248 269 }, cell_type);
249 270 };
250 271
251 272 casper.append_cell = function(text, cell_type) {
252 273 // Insert a cell at the bottom of the notebook and set the cells text.
253 274 // Returns the new cell's index.
254 275 var index = this.insert_cell_at_bottom(cell_type);
255 276 if (text !== undefined) {
256 277 this.set_cell_text(index, text);
257 278 }
258 279 return index;
259 280 };
260 281
261 282 casper.execute_cell = function(index, expect_failure){
262 283 // Asynchronously executes a cell by index.
263 284 // Returns the cell's index.
264 285
265 286 if (expect_failure === undefined) expect_failure = false;
266 287 var that = this;
267 288 this.then(function(){
268 289 that.evaluate(function (index) {
269 290 var cell = IPython.notebook.get_cell(index);
270 291 cell.execute();
271 292 }, index);
272 293 });
273 294 this.wait_for_idle();
274 295
275 296 this.then(function () {
276 297 var error = that.evaluate(function (index) {
277 298 var cell = IPython.notebook.get_cell(index);
278 299 var outputs = cell.output_area.outputs;
279 300 for (var i = 0; i < outputs.length; i++) {
280 301 if (outputs[i].output_type == 'error') {
281 302 return outputs[i];
282 303 }
283 304 }
284 305 return false;
285 306 }, index);
286 307 if (error === null) {
287 308 this.test.fail("Failed to check for error output");
288 309 }
289 310 if (expect_failure && error === false) {
290 311 this.test.fail("Expected error while running cell");
291 312 } else if (!expect_failure && error !== false) {
292 313 this.test.fail("Error running cell:\n" + error.traceback.join('\n'));
293 314 }
294 315 });
295 316 return index;
296 317 };
297 318
298 319 casper.execute_cell_then = function(index, then_callback, expect_failure) {
299 320 // Synchronously executes a cell by index.
300 321 // Optionally accepts a then_callback parameter. then_callback will get called
301 322 // when the cell has finished executing.
302 323 // Returns the cell's index.
303 324 var return_val = this.execute_cell(index, expect_failure);
304 325
305 326 this.wait_for_idle();
306 327
307 328 var that = this;
308 329 this.then(function(){
309 330 if (then_callback!==undefined) {
310 331 then_callback.apply(that, [index]);
311 332 }
312 333 });
313 334
314 335 return return_val;
315 336 };
316 337
317 casper.waitfor_cell_element = function(index, selector){
338 casper.wait_for_element = function(index, selector){
318 339 // Utility function that allows us to easily wait for an element
319 340 // within a cell. Uses JQuery selector to look for the element.
320 341 var that = this;
321 342 this.waitFor(function() {
322 343 return that.cell_element_exists(index, selector);
323 }, function() { console.log('FOUND!'); });
344 });
324 345 };
325 346
326 347 casper.cell_element_exists = function(index, selector){
327 348 // Utility function that allows us to easily check if an element exists
328 349 // within a cell. Uses JQuery selector to look for the element.
329 350 return casper.evaluate(function (index, selector) {
330 351 var $cell = IPython.notebook.get_cell(index).element;
331 352 return $cell.find(selector).length > 0;
332 353 }, index, selector);
333 354 };
334 355
335 356 casper.cell_element_function = function(index, selector, function_name, function_args){
336 357 // Utility function that allows us to execute a jQuery function on an
337 358 // element within a cell.
338 359 return casper.evaluate(function (index, selector, function_name, function_args) {
339 360 var $cell = IPython.notebook.get_cell(index).element;
340 361 var $el = $cell.find(selector);
341 362 return $el[function_name].apply($el, function_args);
342 363 }, index, selector, function_name, function_args);
343 364 };
344 365
345 366 casper.validate_notebook_state = function(message, mode, cell_index) {
346 367 // Validate the entire dual mode state of the notebook. Make sure no more than
347 368 // one cell is selected, focused, in edit mode, etc...
348 369
349 370 // General tests.
350 371 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
351 372 message + '; keyboard and notebook modes match');
352 373 // Is the selected cell the only cell that is selected?
353 374 if (cell_index!==undefined) {
354 375 this.test.assert(this.is_only_cell_selected(cell_index),
355 376 message + '; cell ' + cell_index + ' is the only cell selected');
356 377 }
357 378
358 379 // Mode specific tests.
359 380 if (mode==='command') {
360 381 // Are the notebook and keyboard manager in command mode?
361 382 this.test.assertEquals(this.get_keyboard_mode(), 'command',
362 383 message + '; in command mode');
363 384 // Make sure there isn't a single cell in edit mode.
364 385 this.test.assert(this.is_only_cell_edit(null),
365 386 message + '; all cells in command mode');
366 387 this.test.assert(this.is_cell_editor_focused(null),
367 388 message + '; no cell editors are focused while in command mode');
368 389
369 390 } else if (mode==='edit') {
370 391 // Are the notebook and keyboard manager in edit mode?
371 392 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
372 393 message + '; in edit mode');
373 394 if (cell_index!==undefined) {
374 395 // Is the specified cell the only cell in edit mode?
375 396 this.test.assert(this.is_only_cell_edit(cell_index),
376 397 message + '; cell ' + cell_index + ' is the only cell in edit mode');
377 398 // Is the specified cell the only cell with a focused code mirror?
378 399 this.test.assert(this.is_cell_editor_focused(cell_index),
379 400 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
380 401 }
381 402
382 403 } else {
383 404 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
384 405 }
385 406 };
386 407
387 408 casper.select_cell = function(index) {
388 409 // Select a cell in the notebook.
389 410 this.evaluate(function (i) {
390 411 IPython.notebook.select(i);
391 412 }, {i: index});
392 413 };
393 414
394 415 casper.click_cell_editor = function(index) {
395 416 // Emulate a click on a cell's editor.
396 417
397 418 // Code Mirror does not play nicely with emulated brower events.
398 419 // Instead of trying to emulate a click, here we run code similar to
399 420 // the code used in Code Mirror that handles the mousedown event on a
400 421 // region of codemirror that the user can focus.
401 422 this.evaluate(function (i) {
402 423 var cm = IPython.notebook.get_cell(i).code_mirror;
403 424 if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input))
404 425 cm.display.input.focus();
405 426 }, {i: index});
406 427 };
407 428
408 429 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
409 430 // Set the Code Mirror instance cursor's location.
410 431 this.evaluate(function (i, l, c) {
411 432 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
412 433 }, {i: index, l: line_index, c: char_index});
413 434 };
414 435
415 436 casper.focus_notebook = function() {
416 437 // Focus the notebook div.
417 438 this.evaluate(function (){
418 439 $('#notebook').focus();
419 440 }, {});
420 441 };
421 442
422 443 casper.trigger_keydown = function() {
423 444 // Emulate a keydown in the notebook.
424 445 for (var i = 0; i < arguments.length; i++) {
425 446 this.evaluate(function (k) {
426 447 var element = $(document);
427 448 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
428 449 element.trigger(event);
429 450 }, {k: arguments[i]});
430 451 }
431 452 };
432 453
433 454 casper.get_keyboard_mode = function() {
434 455 // Get the mode of the keyboard manager.
435 456 return this.evaluate(function() {
436 457 return IPython.keyboard_manager.mode;
437 458 }, {});
438 459 };
439 460
440 461 casper.get_notebook_mode = function() {
441 462 // Get the mode of the notebook.
442 463 return this.evaluate(function() {
443 464 return IPython.notebook.mode;
444 465 }, {});
445 466 };
446 467
447 468 casper.get_cell = function(index) {
448 469 // Get a single cell.
449 470 //
450 471 // Note: Handles to DOM elements stored in the cell will be useless once in
451 472 // CasperJS context.
452 473 return this.evaluate(function(i) {
453 474 var cell = IPython.notebook.get_cell(i);
454 475 if (cell) {
455 476 return cell;
456 477 }
457 478 return null;
458 479 }, {i : index});
459 480 };
460 481
461 482 casper.is_cell_editor_focused = function(index) {
462 483 // Make sure a cell's editor is the only editor focused on the page.
463 484 return this.evaluate(function(i) {
464 485 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
465 486 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
466 487 if (i === null) {
467 488 return focused_textarea.length === 0;
468 489 } else {
469 490 var cell = IPython.notebook.get_cell(i);
470 491 if (cell) {
471 492 return cell.code_mirror.getInputField() == focused_textarea[0];
472 493 }
473 494 }
474 495 return false;
475 496 }, {i : index});
476 497 };
477 498
478 499 casper.is_only_cell_selected = function(index) {
479 500 // Check if a cell is the only cell selected.
480 501 // Pass null as the index to check if no cells are selected.
481 502 return this.is_only_cell_on(index, 'selected', 'unselected');
482 503 };
483 504
484 505 casper.is_only_cell_edit = function(index) {
485 506 // Check if a cell is the only cell in edit mode.
486 507 // Pass null as the index to check if all of the cells are in command mode.
487 508 return this.is_only_cell_on(index, 'edit_mode', 'command_mode');
488 509 };
489 510
490 511 casper.is_only_cell_on = function(i, on_class, off_class) {
491 512 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
492 513 // All of the other cells are checked for the `off_class` DOM class.
493 514 // Pass null as the index to check if all of the cells have the `off_class`.
494 515 var cells_length = this.get_cells_length();
495 516 for (var j = 0; j < cells_length; j++) {
496 517 if (j === i) {
497 518 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
498 519 return false;
499 520 }
500 521 } else {
501 522 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
502 523 return false;
503 524 }
504 525 }
505 526 }
506 527 return true;
507 528 };
508 529
509 530 casper.cell_has_class = function(index, classes) {
510 531 // Check if a cell has a class.
511 532 return this.evaluate(function(i, c) {
512 533 var cell = IPython.notebook.get_cell(i);
513 534 if (cell) {
514 535 return cell.element.hasClass(c);
515 536 }
516 537 return false;
517 538 }, {i : index, c: classes});
518 539 };
519 540
520 541 casper.is_cell_rendered = function (index) {
521 542 return this.evaluate(function(i) {
522 543 return !!IPython.notebook.get_cell(i).rendered;
523 544 }, {i:index});
524 545 };
525 546
526 547 casper.assert_colors_equal = function (hex_color, local_color, msg) {
527 548 // Tests to see if two colors are equal.
528 549 //
529 550 // Parameters
530 551 // hex_color: string
531 552 // Hexadecimal color code, with or without preceeding hash character.
532 553 // local_color: string
533 554 // Local color representation. Can either be hexadecimal (default for
534 555 // phantom) or rgb (default for slimer).
535 556
536 557 // Remove parentheses, hashes, semi-colons, and space characters.
537 558 hex_color = hex_color.replace(/[\(\); #]/, '');
538 559 local_color = local_color.replace(/[\(\); #]/, '');
539 560
540 561 // If the local color is rgb, clean it up and replace
541 562 if (local_color.substr(0,3).toLowerCase() == 'rgb') {
542 563 components = local_color.substr(3).split(',');
543 564 local_color = '';
544 565 for (var i = 0; i < components.length; i++) {
545 566 var part = parseInt(components[i]).toString(16);
546 567 while (part.length < 2) part = '0' + part;
547 568 local_color += part;
548 569 }
549 570 }
550 571
551 572 this.test.assertEquals(hex_color.toUpperCase(), local_color.toUpperCase(), msg);
552 573 };
553 574
554 575 casper.notebook_test = function(test) {
555 576 // Wrap a notebook test to reduce boilerplate.
556 577 this.open_new_notebook();
557 578
558 579 // Echo whether or not we are running this test using SlimerJS
559 580 if (this.evaluate(function(){
560 581 return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
561 582 })) {
562 583 console.log('This test is running in SlimerJS.');
563 584 this.slimerjs = true;
564 585 }
565 586
566 587 // Make sure to remove the onbeforeunload callback. This callback is
567 588 // responsible for the "Are you sure you want to quit?" type messages.
568 589 // PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
569 590 this.then(function(){
570 591 this.evaluate(function(){
571 592 window.onbeforeunload = function(){};
572 593 });
573 594 });
574 595
575 596 this.then(test);
576 597
577 598 // Kill the kernel and delete the notebook.
578 599 this.shutdown_current_kernel();
579 600 // This is still broken but shouldn't be a problem for now.
580 601 // this.delete_current_notebook();
581 602
582 603 // This is required to clean up the page we just finished with. If we don't call this
583 604 // casperjs will leak file descriptors of all the open WebSockets in that page. We
584 605 // have to set this.page=null so that next time casper.start runs, it will create a
585 606 // new page from scratch.
586 607 this.then(function () {
587 608 this.page.close();
588 609 this.page = null;
589 610 });
590 611
591 612 // Run the browser automation.
592 613 this.run(function() {
593 614 this.test.done();
594 615 });
595 616 };
596 617
597 618 casper.wait_for_dashboard = function () {
598 619 // Wait for the dashboard list to load.
599 620 casper.waitForSelector('.list_item');
600 621 };
601 622
602 623 casper.open_dashboard = function () {
603 624 // Start casper by opening the dashboard page.
604 625 var baseUrl = this.get_notebook_server();
605 626 this.start(baseUrl);
606 627 this.waitFor(this.page_loaded);
607 628 this.wait_for_dashboard();
608 629 };
609 630
610 631 casper.dashboard_test = function (test) {
611 632 // Open the dashboard page and run a test.
612 633 this.open_dashboard();
613 634 this.then(test);
614 635
615 636 this.then(function () {
616 637 this.page.close();
617 638 this.page = null;
618 639 });
619 640
620 641 // Run the browser automation.
621 642 this.run(function() {
622 643 this.test.done();
623 644 });
624 645 };
625 646
626 647 // note that this will only work for UNIQUE events -- if you want to
627 648 // listen for the same event twice, this will not work!
628 649 casper.event_test = function (name, events, action, timeout) {
629 650
630 651 // set up handlers to listen for each of the events
631 652 this.thenEvaluate(function (events) {
632 653 var make_handler = function (event) {
633 654 return function () {
634 655 IPython._events_triggered.push(event);
635 656 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
636 657 delete IPython._event_handlers[event];
637 658 };
638 659 };
639 660 IPython._event_handlers = {};
640 661 IPython._events_triggered = [];
641 662 for (var i=0; i < events.length; i++) {
642 663 IPython._event_handlers[events[i]] = make_handler(events[i]);
643 664 IPython.notebook.events.on(events[i], IPython._event_handlers[events[i]]);
644 665 }
645 666 }, [events]);
646 667
647 668 // execute the requested action
648 669 this.then(action);
649 670
650 671 // wait for all the events to be triggered
651 672 this.waitFor(function () {
652 673 return this.evaluate(function (events) {
653 674 return IPython._events_triggered.length >= events.length;
654 675 }, [events]);
655 676 }, undefined, undefined, timeout);
656 677
657 678 // test that the events were triggered in the proper order
658 679 this.then(function () {
659 680 var triggered = this.evaluate(function () {
660 681 return IPython._events_triggered;
661 682 });
662 683 var handlers = this.evaluate(function () {
663 684 return Object.keys(IPython._event_handlers);
664 685 });
665 686 this.test.assertEquals(triggered.length, events.length, name + ': ' + events.length + ' events were triggered');
666 687 this.test.assertEquals(handlers.length, 0, name + ': all handlers triggered');
667 688 for (var i=0; i < events.length; i++) {
668 689 this.test.assertEquals(triggered[i], events[i], name + ': ' + events[i] + ' was triggered');
669 690 }
670 691 });
671 692
672 693 // turn off any remaining event listeners
673 694 this.thenEvaluate(function () {
674 695 for (var event in IPython._event_handlers) {
675 696 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
676 697 delete IPython._event_handlers[event];
677 698 }
678 699 });
679 700 };
680 701
681 702 casper.options.waitTimeout=10000;
682 703 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
683 704 this.echo("Timeout for " + casper.get_notebook_server());
684 705 this.echo("Is the notebook server running?");
685 706 });
686 707
687 708 casper.print_log = function () {
688 709 // Pass `console.log` calls from page JS to casper.
689 710 this.on('remote.message', function(msg) {
690 711 this.echo('Remote message caught: ' + msg);
691 712 });
692 713 };
693 714
694 715 casper.on("page.error", function onError(msg, trace) {
695 716 // show errors in the browser
696 717 this.echo("Page Error");
697 718 this.echo(" Message: " + msg.split('\n').join('\n '));
698 719 this.echo(" Call stack:");
699 720 var local_path = this.get_notebook_server();
700 721 for (var i = 0; i < trace.length; i++) {
701 722 var frame = trace[i];
702 723 var file = frame.file;
703 724 // shorten common phantomjs evaluate url
704 725 // this will have a different value on slimerjs
705 726 if (file === "phantomjs://webpage.evaluate()") {
706 727 file = "evaluate";
707 728 }
708 729 // remove the version tag from the path
709 730 file = file.replace(/(\?v=[0-9abcdef]+)/, '');
710 731 // remove the local address from the beginning of the path
711 732 if (file.indexOf(local_path) === 0) {
712 733 file = file.substr(local_path.length);
713 734 }
714 735 var frame_text = (frame.function.length > 0) ? " in " + frame.function : "";
715 736 this.echo(" line " + frame.line + " of " + file + frame_text);
716 737 }
717 738 });
718 739
719 740
720 741 casper.capture_log = function () {
721 742 // show captured errors
722 743 var captured_log = [];
723 744 var seen_errors = 0;
724 745 this.on('remote.message', function(msg) {
725 746 captured_log.push(msg);
726 747 });
727 748
728 749 var that = this;
729 750 this.test.on("test.done", function (result) {
730 751 // test.done runs per-file,
731 752 // but suiteResults is per-suite (directory)
732 753 var current_errors;
733 754 if (this.suiteResults) {
734 755 // casper 1.1 has suiteResults
735 756 current_errors = this.suiteResults.countErrors() + this.suiteResults.countFailed();
736 757 } else {
737 758 // casper 1.0 has testResults instead
738 759 current_errors = this.testResults.failed;
739 760 }
740 761
741 762 if (current_errors > seen_errors && captured_log.length > 0) {
742 763 casper.echo("\nCaptured console.log:");
743 764 for (var i = 0; i < captured_log.length; i++) {
744 765 var output = String(captured_log[i]).split('\n');
745 766 for (var j = 0; j < output.length; j++) {
746 767 casper.echo(" " + output[j]);
747 768 }
748 769 }
749 770 }
750 771
751 772 seen_errors = current_errors;
752 773 captured_log = [];
753 774 });
754 775 };
755 776
756 777 casper.interact = function() {
757 778 // Start an interactive Javascript console.
758 779 var system = require('system');
759 780 system.stdout.writeLine('JS interactive console.');
760 781 system.stdout.writeLine('Type `exit` to quit.');
761 782
762 783 function read_line() {
763 784 system.stdout.writeLine('JS: ');
764 785 var line = system.stdin.readLine();
765 786 return line;
766 787 }
767 788
768 789 var input = read_line();
769 790 while (input.trim() != 'exit') {
770 791 var output = this.evaluate(function(code) {
771 792 return String(eval(code));
772 793 }, {code: input});
773 794 system.stdout.writeLine('\nOut: ' + output);
774 795 input = read_line();
775 796 }
776 797 };
777 798
778 799 casper.capture_log();
@@ -1,191 +1,191
1 1 var xor = function (a, b) {return !a ^ !b;};
2 2 var isArray = function (a) {
3 3 try {
4 4 return Object.toString.call(a) === "[object Array]" || Object.toString.call(a) === "[object RuntimeArray]";
5 5 } catch (e) {
6 6 return Array.isArray(a);
7 7 }
8 8 };
9 9 var recursive_compare = function(a, b) {
10 10 // Recursively compare two objects.
11 11 var same = true;
12 12 same = same && !xor(a instanceof Object || typeof a == 'object', b instanceof Object || typeof b == 'object');
13 13 same = same && !xor(isArray(a), isArray(b));
14 14
15 15 if (same) {
16 16 if (a instanceof Object) {
17 17 var key;
18 18 for (key in a) {
19 19 if (a.hasOwnProperty(key) && !recursive_compare(a[key], b[key])) {
20 20 same = false;
21 21 break;
22 22 }
23 23 }
24 24 for (key in b) {
25 25 if (b.hasOwnProperty(key) && !recursive_compare(a[key], b[key])) {
26 26 same = false;
27 27 break;
28 28 }
29 29 }
30 30 } else {
31 31 return a === b;
32 32 }
33 33 }
34 34
35 35 return same;
36 36 };
37 37
38 38 // Test the widget framework.
39 39 casper.notebook_test(function () {
40 40 var index;
41 41
42 42 index = this.append_cell(
43 43 'from IPython.html import widgets\n' +
44 44 'from IPython.display import display, clear_output\n' +
45 45 'print("Success")');
46 46 this.execute_cell_then(index);
47 47
48 48 this.then(function () {
49 49
50 50 // Functions that can be used to test the packing and unpacking APIs
51 51 var that = this;
52 52 var test_pack = function (input) {
53 53 var output = that.evaluate(function(input) {
54 54 var model = new IPython.WidgetModel(IPython.notebook.kernel.widget_manager, undefined);
55 55 var results = model._pack_models(input);
56 56 return results;
57 57 }, {input: input});
58 58 that.test.assert(recursive_compare(input, output),
59 59 JSON.stringify(input) + ' passed through Model._pack_model unchanged');
60 60 };
61 61 var test_unpack = function (input) {
62 62 that.thenEvaluate(function(input) {
63 63 window.results = undefined;
64 64 var model = new IPython.WidgetModel(IPython.notebook.kernel.widget_manager, undefined);
65 65 model._unpack_models(input).then(function(results) {
66 66 window.results = results;
67 67 });
68 68 }, {input: input});
69 69
70 70 that.waitFor(function check() {
71 71 return that.evaluate(function() {
72 72 return window.results;
73 73 });
74 74 });
75 75
76 76 that.then(function() {
77 77 var results = that.evaluate(function() {
78 78 return window.results;
79 79 });
80 80 that.test.assert(recursive_compare(input, results),
81 81 JSON.stringify(input) + ' passed through Model._unpack_model unchanged');
82 82 });
83 83 };
84 84 var test_packing = function(input) {
85 85 test_pack(input);
86 86 test_unpack(input);
87 87 };
88 88
89 89 test_packing({0: 'hi', 1: 'bye'});
90 90 test_packing(['hi', 'bye']);
91 91 test_packing(['hi', 5]);
92 92 test_packing(['hi', '5']);
93 93 test_packing([1.0, 0]);
94 94 test_packing([1.0, false]);
95 95 test_packing([1, false]);
96 96 test_packing([1, false, {a: 'hi'}]);
97 97 test_packing([1, false, ['hi']]);
98 98 test_packing([String('hi'), Date("Thu Nov 13 2014 13:46:21 GMT-0500")])
99 99
100 100 // Test multi-set, single touch code. First create a custom widget.
101 101 this.thenEvaluate(function() {
102 102 var MultiSetView = IPython.DOMWidgetView.extend({
103 103 render: function(){
104 104 this.model.set('a', 1);
105 105 this.model.set('b', 2);
106 106 this.model.set('c', 3);
107 107 this.touch();
108 108 },
109 109 });
110 110 IPython.WidgetManager.register_widget_view('MultiSetView', MultiSetView);
111 111 }, {});
112 112 });
113 113
114 114 // Try creating the multiset widget, verify that sets the values correctly.
115 115 var multiset = {};
116 116 multiset.index = this.append_cell(
117 117 'from IPython.utils.traitlets import Unicode, CInt\n' +
118 118 'class MultiSetWidget(widgets.Widget):\n' +
119 119 ' _view_name = Unicode("MultiSetView", sync=True)\n' +
120 120 ' a = CInt(0, sync=True)\n' +
121 121 ' b = CInt(0, sync=True)\n' +
122 122 ' c = CInt(0, sync=True)\n' +
123 123 ' d = CInt(-1, sync=True)\n' + // See if it sends a full state.
124 124 ' def set_state(self, sync_data):\n' +
125 125 ' widgets.Widget.set_state(self, sync_data)\n'+
126 126 ' self.d = len(sync_data)\n' +
127 127 'multiset = MultiSetWidget()\n' +
128 128 'display(multiset)\n' +
129 129 'print(multiset.model_id)');
130 130 this.execute_cell_then(multiset.index, function(index) {
131 131 multiset.model_id = this.get_output_cell(index).text.trim();
132 132 });
133 133
134 this.wait_for_widget(multiset);
134 this.wait_for_widget(multiset);
135 135
136 136 index = this.append_cell(
137 137 'print("%d%d%d" % (multiset.a, multiset.b, multiset.c))');
138 138 this.execute_cell_then(index, function(index) {
139 139 this.test.assertEquals(this.get_output_cell(index).text.trim(), '123',
140 140 'Multiple model.set calls and one view.touch update state in back-end.');
141 141 });
142 142
143 143 index = this.append_cell(
144 144 'print("%d" % (multiset.d))');
145 145 this.execute_cell_then(index, function(index) {
146 146 this.test.assertEquals(this.get_output_cell(index).text.trim(), '3',
147 147 'Multiple model.set calls sent a partial state.');
148 148 });
149 149
150 150 var textbox = {};
151 151 throttle_index = this.append_cell(
152 152 'import time\n' +
153 153 'textbox = widgets.Text()\n' +
154 154 'display(textbox)\n' +
155 155 'textbox._dom_classes = ["my-throttle-textbox"]\n' +
156 156 'def handle_change(name, old, new):\n' +
157 157 ' display(len(new))\n' +
158 158 ' time.sleep(0.5)\n' +
159 159 'textbox.on_trait_change(handle_change, "value")\n' +
160 160 'print(textbox.model_id)');
161 161 this.execute_cell_then(throttle_index, function(index){
162 162 textbox.model_id = this.get_output_cell(index).text.trim();
163 163
164 164 this.test.assert(this.cell_element_exists(index,
165 165 '.widget-area .widget-subarea'),
166 166 'Widget subarea exists.');
167 167
168 168 this.test.assert(this.cell_element_exists(index,
169 169 '.my-throttle-textbox'), 'Textbox exists.');
170 170
171 171 // Send 20 characters
172 172 this.sendKeys('.my-throttle-textbox input', '....................');
173 173 });
174 174
175 175 this.wait_for_widget(textbox);
176 176
177 177 this.then(function () {
178 178 var outputs = this.evaluate(function(i) {
179 179 return IPython.notebook.get_cell(i).output_area.outputs;
180 180 }, {i : throttle_index});
181 181
182 182 // Only 4 outputs should have printed, but because of timing, sometimes
183 183 // 5 outputs will print. All we need to do is verify num outputs <= 5
184 184 // because that is much less than 20.
185 185 this.test.assert(outputs.length <= 5, 'Messages throttled.');
186 186
187 187 // We also need to verify that the last state sent was correct.
188 188 var last_state = outputs[outputs.length-1].data['text/plain'];
189 189 this.test.assertEquals(last_state, "20", "Last state sent when throttling.");
190 190 });
191 191 });
@@ -1,88 +1,91
1 1 // Test widget bool class
2 2 casper.notebook_test(function () {
3 // index = this.append_cell(
4 // 'print("Success")');
5 // this.execute_cell_then(index);
6 3
4 // Create a checkbox and togglebutton.
7 5 var bool_index = this.append_cell(
8 6 'from IPython.html import widgets\n' +
9 7 'from IPython.display import display, clear_output\n' +
10 8 'bool_widgets = [widgets.Checkbox(description="Title", value=True),\n' +
11 9 ' widgets.ToggleButton(description="Title", value=True)]\n' +
12 10 'display(bool_widgets[0])\n' +
13 11 'display(bool_widgets[1])\n' +
14 12 'print("Success")');
15 13 this.execute_cell_then(bool_index, function(index){
16 14 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
17 15 'Create bool widget cell executed with correct output.');
18 16 });
19 17
20 this.waitfor_cell_element(bool_index, '.widget-area .widget-subarea .widget-hbox input');
21 this.waitfor_cell_element(bool_index, '.widget-area .widget-subarea button');
18 // Wait for the widgets to actually display.
19 var widget_checkbox_selector = '.widget-area .widget-subarea .widget-hbox input';
20 var widget_togglebutton_selector = '.widget-area .widget-subarea button';
21 this.wait_for_element(bool_index, widget_checkbox_selector);
22 this.wait_for_element(bool_index, widget_togglebutton_selector);
22 23
24 // Continue the tests.
23 25 this.then(function() {
24 26 this.test.assert(this.cell_element_exists(bool_index,
25 27 '.widget-area .widget-subarea'),
26 28 'Widget subarea exists.');
27 29
28 30 this.test.assert(this.cell_element_exists(bool_index,
29 '.widget-area .widget-subarea .widget-hbox input'),
31 widget_checkbox_selector),
30 32 'Checkbox exists.');
31 33
32 34 this.test.assert(this.cell_element_function(bool_index,
33 '.widget-area .widget-subarea .widget-hbox input', 'prop', ['checked']),
35 widget_checkbox_selector, 'prop', ['checked']),
34 36 'Checkbox is checked.');
35 37
36 38 this.test.assert(this.cell_element_exists(bool_index,
37 39 '.widget-area .widget-subarea .widget-hbox .widget-label'),
38 40 'Checkbox label exists.');
39 41
40 42 this.test.assert(this.cell_element_function(bool_index,
41 43 '.widget-area .widget-subarea .widget-hbox .widget-label', 'html')=="Title",
42 44 'Checkbox labeled correctly.');
43 45
44 46 this.test.assert(this.cell_element_exists(bool_index,
45 '.widget-area .widget-subarea button'),
47 widget_togglebutton_selector),
46 48 'Toggle button exists.');
47 49
48 50 this.test.assert(this.cell_element_function(bool_index,
49 '.widget-area .widget-subarea button', 'html')=="Title",
51 widget_togglebutton_selector, 'html')=="Title",
50 52 'Toggle button labeled correctly.');
51 53
52 54 this.test.assert(this.cell_element_function(bool_index,
53 '.widget-area .widget-subarea button', 'hasClass', ['active']),
55 widget_togglebutton_selector, 'hasClass', ['active']),
54 56 'Toggle button is toggled.');
55 57 });
56 58
59 // Try changing the state of the widgets programatically.
57 60 index = this.append_cell(
58 61 'bool_widgets[0].value = False\n' +
59 62 'bool_widgets[1].value = False\n' +
60 63 'print("Success")');
61 64 this.execute_cell_then(index, function(index){
62 65 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
63 66 'Change bool widget value cell executed with correct output.');
64 67
65 68 this.test.assert(! this.cell_element_function(bool_index,
66 '.widget-area .widget-subarea .widget-hbox input', 'prop', ['checked']),
69 widget_checkbox_selector, 'prop', ['checked']),
67 70 'Checkbox is not checked. (1)');
68 71
69 72 this.test.assert(! this.cell_element_function(bool_index,
70 '.widget-area .widget-subarea button', 'hasClass', ['active']),
73 widget_togglebutton_selector, 'hasClass', ['active']),
71 74 'Toggle button is not toggled. (1)');
72 75
73 76 // Try toggling the bool by clicking on the checkbox.
74 this.cell_element_function(bool_index, '.widget-area .widget-subarea .widget-hbox input', 'click');
77 this.cell_element_function(bool_index, widget_checkbox_selector, 'click');
75 78
76 79 this.test.assert(this.cell_element_function(bool_index,
77 '.widget-area .widget-subarea .widget-hbox input', 'prop', ['checked']),
80 widget_checkbox_selector, 'prop', ['checked']),
78 81 'Checkbox is checked. (2)');
79 82
80 83 // Try toggling the bool by clicking on the toggle button.
81 this.cell_element_function(bool_index, '.widget-area .widget-subarea button', 'click');
84 this.cell_element_function(bool_index, widget_togglebutton_selector, 'click');
82 85
83 86 this.test.assert(this.cell_element_function(bool_index,
84 '.widget-area .widget-subarea button', 'hasClass', ['active']),
87 widget_togglebutton_selector, 'hasClass', ['active']),
85 88 'Toggle button is toggled. (3)');
86 89
87 90 });
88 91 }); No newline at end of file
@@ -1,80 +1,92
1 1 // Test container class
2 2 casper.notebook_test(function () {
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8 3
4 // Create a box widget.
9 5 var container_index = this.append_cell(
6 'from IPython.html import widgets\n' +
7 'from IPython.display import display, clear_output\n' +
10 8 'container = widgets.Box()\n' +
11 9 'button = widgets.Button()\n'+
12 10 'container.children = [button]\n'+
13 11 'display(container)\n'+
14 12 'container._dom_classes = ["my-test-class"]\n'+
15 13 'print("Success")\n');
16 14 this.execute_cell_then(container_index, function(index){
17
18 15 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
19 16 'Create container cell executed with correct output.');
17 });
20 18
21 this.test.assert(this.cell_element_exists(index,
19 // Wait for the widgets to actually display.
20 var widget_box_selector = '.widget-area .widget-subarea .widget-box';
21 var widget_box_button_selector = '.widget-area .widget-subarea .widget-box button';
22 this.wait_for_element(container_index, widget_box_selector);
23 this.wait_for_element(container_index, widget_box_button_selector);
24
25 // Continue with the tests.
26 this.then(function() {
27 this.test.assert(this.cell_element_exists(container_index,
22 28 '.widget-area .widget-subarea'),
23 29 'Widget subarea exists.');
24 30
25 this.test.assert(this.cell_element_exists(index,
26 '.widget-area .widget-subarea .widget-box'),
31 this.test.assert(this.cell_element_exists(container_index,
32 widget_box_selector),
27 33 'Widget container exists.');
28 34
29 this.test.assert(this.cell_element_exists(index,
35 this.test.assert(this.cell_element_exists(container_index,
30 36 '.widget-area .widget-subarea .my-test-class'),
31 37 '_dom_classes works.');
32 38
33 this.test.assert(this.cell_element_exists(index,
34 '.widget-area .widget-subarea .my-test-class button'),
39 this.test.assert(this.cell_element_exists(container_index,
40 widget_box_button_selector),
35 41 'Container parent/child relationship works.');
36 42 });
37 43
38 44 index = this.append_cell(
39 45 'container.box_style = "success"\n'+
40 46 'print("Success")\n');
41 47 this.execute_cell_then(index, function(index){
42 48
43 49 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
44 50 'Set box_style cell executed with correct output.');
45 51
46 52 this.test.assert(this.cell_element_exists(container_index,
47 53 '.widget-box.alert-success'),
48 54 'Set box_style works.');
49 55 });
50 56
51 57 index = this.append_cell(
52 58 'container._dom_classes = []\n'+
53 59 'print("Success")\n');
54 60 this.execute_cell_then(index, function(index){
55 61
56 62 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
57 63 'Remove container class cell executed with correct output.');
58 64
59 65 this.test.assert(! this.cell_element_exists(container_index,
60 66 '.widget-area .widget-subarea .my-test-class'),
61 67 '_dom_classes can be used to remove a class.');
62 68 });
63 69
64 index = this.append_cell(
70 var boxalone_index = this.append_cell(
65 71 'display(button)\n'+
66 72 'print("Success")\n');
67 this.execute_cell_then(index, function(index){
68
73 this.execute_cell_then(boxalone_index, function(index){
69 74 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
70 75 'Display container child executed with correct output.');
76 });
77
78 // Wait for the widget to actually display.
79 var widget_button_selector = '.widget-area .widget-subarea button';
80 this.wait_for_element(boxalone_index, widget_button_selector);
71 81
72 this.test.assert(! this.cell_element_exists(index,
73 '.widget-area .widget-subarea .widget-box'),
82 // Continue with the tests.
83 this.then(function() {
84 this.test.assert(! this.cell_element_exists(boxalone_index,
85 widget_box_selector),
74 86 'Parent container not displayed.');
75 87
76 this.test.assert(this.cell_element_exists(index,
77 '.widget-area .widget-subarea button'),
88 this.test.assert(this.cell_element_exists(boxalone_index,
89 widget_button_selector),
78 90 'Child displayed.');
79 91 });
80 92 }); No newline at end of file
@@ -1,43 +1,45
1 1 // Test widget button class
2 2 casper.notebook_test(function () {
3 index = this.append_cell(
3 var button_index = this.append_cell(
4 4 'from IPython.html import widgets\n' +
5 5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8
9 var button_index = this.append_cell(
10 6 'button = widgets.Button(description="Title")\n' +
11 7 'display(button)\n' +
12 8 'print("Success")\n' +
13 9 'def handle_click(sender):\n' +
14 10 ' display("Clicked")\n' +
15 11 'button.on_click(handle_click)');
16 12 this.execute_cell_then(button_index, function(index){
17
18 13 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
19 14 'Create button cell executed with correct output.');
15 });
16
17 // Wait for the widgets to actually display.
18 var widget_button_selector = '.widget-area .widget-subarea button';
19 this.wait_for_element(button_index, widget_button_selector);
20 20
21 this.test.assert(this.cell_element_exists(index,
21 // Continue with the tests.
22 this.then(function() {
23 this.test.assert(this.cell_element_exists(button_index,
22 24 '.widget-area .widget-subarea'),
23 25 'Widget subarea exists.');
24 26
25 this.test.assert(this.cell_element_exists(index,
26 '.widget-area .widget-subarea button'),
27 this.test.assert(this.cell_element_exists(button_index,
28 widget_button_selector),
27 29 'Widget button exists.');
28 30
29 this.test.assert(this.cell_element_function(index,
30 '.widget-area .widget-subarea button', 'html')=='Title',
31 this.test.assert(this.cell_element_function(button_index,
32 widget_button_selector, 'html')=='Title',
31 33 'Set button description.');
32 34
33 this.cell_element_function(index,
34 '.widget-area .widget-subarea button', 'click');
35 this.cell_element_function(button_index,
36 widget_button_selector, 'click');
35 37 });
36 38
37 39 this.wait_for_output(button_index, 1);
38 40
39 41 this.then(function () {
40 42 this.test.assertEquals(this.get_output_cell(button_index, 1).data['text/plain'], "'Clicked'",
41 43 'Button click event fires.');
42 44 });
43 45 }); No newline at end of file
@@ -1,100 +1,109
1 1 // Test widget float class
2 2 casper.notebook_test(function () {
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8
9 3 var float_text = {};
10 4 float_text.query = '.widget-area .widget-subarea .my-second-float-text input';
11 5 float_text.index = this.append_cell(
6 'from IPython.html import widgets\n' +
7 'from IPython.display import display, clear_output\n' +
12 8 'float_widget = widgets.FloatText()\n' +
13 9 'display(float_widget)\n' +
14 10 'float_widget._dom_classes = ["my-second-float-text"]\n' +
15 11 'print(float_widget.model_id)\n');
16 12 this.execute_cell_then(float_text.index, function(index){
17 13 float_text.model_id = this.get_output_cell(index).text.trim();
18
19 this.test.assert(this.cell_element_exists(index,
14 });
15
16 // Wait for the widget to actually display.
17 this.wait_for_element(float_text.index, float_text.query);
18
19 // Continue with the tests
20 this.then(function(){
21 this.test.assert(this.cell_element_exists(float_text.index,
20 22 '.widget-area .widget-subarea'),
21 23 'Widget subarea exists.');
22 24
23 this.test.assert(this.cell_element_exists(index, float_text.query),
25 this.test.assert(this.cell_element_exists(float_text.index, float_text.query),
24 26 'Widget float textbox exists.');
25 27
26 28 this.cell_element_function(float_text.index, float_text.query, 'val', ['']);
29 console.log('send keys');
27 30 this.sendKeys(float_text.query, '1.05');
31 console.log('send keys done');
28 32 });
29 33
30 34 this.wait_for_widget(float_text);
31 35
32 36 index = this.append_cell('print(float_widget.value)\n');
33 37 this.execute_cell_then(index, function(index){
34 38 this.test.assertEquals(this.get_output_cell(index).text, '1.05\n',
35 39 'Float textbox value set.');
36 40 this.cell_element_function(float_text.index, float_text.query, 'val', ['']);
37 41 this.sendKeys(float_text.query, '123456789.0');
38 42 });
39 43
40 44 this.wait_for_widget(float_text);
41 45
42 46 index = this.append_cell('print(float_widget.value)\n');
43 47 this.execute_cell_then(index, function(index){
44 48 this.test.assertEquals(this.get_output_cell(index).text, '123456789.0\n',
45 49 'Long float textbox value set (probably triggers throttling).');
46 50 this.cell_element_function(float_text.index, float_text.query, 'val', ['']);
47 51 this.sendKeys(float_text.query, '12hello');
48 52 });
49 53
50 54 this.wait_for_widget(float_text);
51 55
52 56 index = this.append_cell('print(float_widget.value)\n');
53 57 this.execute_cell_then(index, function(index){
54 58 this.test.assertEquals(this.get_output_cell(index).text, '12.0\n',
55 59 'Invald float textbox value caught and filtered.');
56 60 });
57 61
58 62 var float_text_query = '.widget-area .widget-subarea .widget-numeric-text';
59 63 var slider = {};
60 64 slider.query = '.widget-area .widget-subarea .slider';
61 65 slider.index = this.append_cell(
62 66 'floatrange = [widgets.BoundedFloatText(), \n' +
63 67 ' widgets.FloatSlider()]\n' +
64 68 '[display(floatrange[i]) for i in range(2)]\n' +
65 69 'print("Success")\n');
66 70 this.execute_cell_then(slider.index, function(index){
67
68 71 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
69 72 'Create float range cell executed with correct output.');
73 });
74
75 // Wait for the widgets to actually display.
76 this.wait_for_element(slider.index, slider.query);
77 this.wait_for_element(slider.index, float_text_query);
70 78
71 this.test.assert(this.cell_element_exists(index,
79 this.then(function(){
80 this.test.assert(this.cell_element_exists(slider.index,
72 81 '.widget-area .widget-subarea'),
73 82 'Widget subarea exists.');
74 83
75 this.test.assert(this.cell_element_exists(index, slider.query),
84 this.test.assert(this.cell_element_exists(slider.index, slider.query),
76 85 'Widget slider exists.');
77 86
78 this.test.assert(this.cell_element_exists(index, float_text_query),
87 this.test.assert(this.cell_element_exists(slider.index, float_text_query),
79 88 'Widget float textbox exists.');
80 89 });
81 90
82 91 index = this.append_cell(
83 92 'for widget in floatrange:\n' +
84 93 ' widget.max = 50.0\n' +
85 94 ' widget.min = -50.0\n' +
86 95 ' widget.value = 25.0\n' +
87 96 'print("Success")\n');
88 97 this.execute_cell_then(index, function(index){
89 98
90 99 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
91 100 'Float range properties cell executed with correct output.');
92 101
93 102 this.test.assert(this.cell_element_exists(slider.index, slider.query),
94 103 'Widget slider exists.');
95 104
96 105 this.test.assert(this.cell_element_function(slider.index, slider.query,
97 106 'slider', ['value']) == 25.0,
98 107 'Slider set to Python value.');
99 108 });
100 109 }); No newline at end of file
@@ -1,44 +1,48
1 1 // Test image class
2 2 casper.notebook_test(function () {
3 3 index = this.append_cell(
4 4 'from IPython.html import widgets\n' +
5 5 'from IPython.display import display, clear_output\n' +
6 6 'print("Success")');
7 7 this.execute_cell_then(index);
8 8
9 9 // Get the temporary directory that the test server is running in.
10 10 var cwd = '';
11 11 index = this.append_cell('!echo $(pwd)');
12 12 this.execute_cell_then(index, function(index){
13 13 cwd = this.get_output_cell(index).text.trim();
14 14 });
15 15
16 16 var test_jpg = '/9j/4AAQSkZJRgABAQEASABIAAD//gATQ3JlYXRlZCB3aXRoIEdJTVD/2wBDACAWGBwYFCAcGhwkIiAmMFA0MCwsMGJGSjpQdGZ6eHJmcG6AkLicgIiuim5woNqirr7EztDOfJri8uDI8LjKzsb/2wBDASIkJDAqMF40NF7GhHCExsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsb/wgARCAABAAEDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAAA//EABUBAQEAAAAAAAAAAAAAAAAAAAME/9oADAMBAAIQAxAAAAECv//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAQUCf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Bf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Bf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEABj8Cf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8hf//aAAwDAQACAAMAAAAQn//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Qf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Qf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8Qf//Z';
17 17
18 18 var image_index = this.append_cell(
19 19 'import base64\n' +
20 20 'data = base64.b64decode("' + test_jpg + '")\n' +
21 21 'image = widgets.Image()\n' +
22 22 'image.format = "jpeg"\n' +
23 23 'image.value = data\n' +
24 24 'image.width = "50px"\n' +
25 25 'image.height = "50px"\n' +
26 26 'display(image)\n' +
27 27 'print("Success")\n');
28 28 this.execute_cell_then(image_index, function(index){
29
30 29 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
31 30 'Create image executed with correct output.');
31 });
32
33 // Wait for the widget to actually display.
34 var img_selector = '.widget-area .widget-subarea img';
35 this.wait_for_element(image_index, img_selector);
32 36
33 this.test.assert(this.cell_element_exists(index,
37 this.then(function(){
38 this.test.assert(this.cell_element_exists(image_index,
34 39 '.widget-area .widget-subarea'),
35 40 'Widget subarea exists.');
36 41
37 var img_sel = '.widget-area .widget-subarea img';
38 this.test.assert(this.cell_element_exists(index, img_sel), 'Image exists.');
42 this.test.assert(this.cell_element_exists(image_index, img_selector), 'Image exists.');
39 43
40 44 // Verify that the image's base64 data has made it into the DOM.
41 var img_src = this.cell_element_function(image_index, img_sel, 'attr', ['src']);
45 var img_src = this.cell_element_function(image_index, img_selector, 'attr', ['src']);
42 46 this.test.assert(img_src.indexOf(test_jpg) > -1, 'Image src data exists.');
43 47 });
44 48 }); No newline at end of file
@@ -1,175 +1,177
1 1 // Test widget int class
2 2 casper.notebook_test(function () {
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8
9 3 var int_text = {};
10 4 int_text.query = '.widget-area .widget-subarea .my-second-int-text input';
11 5 int_text.index = this.append_cell(
6 'from IPython.html import widgets\n' +
7 'from IPython.display import display, clear_output\n' +
12 8 'int_widget = widgets.IntText()\n' +
13 9 'display(int_widget)\n' +
14 10 'int_widget._dom_classes = ["my-second-int-text"]\n' +
15 11 'print(int_widget.model_id)\n');
16 12 this.execute_cell_then(int_text.index, function(index){
17 13 int_text.model_id = this.get_output_cell(index).text.trim();
18
19 this.test.assert(this.cell_element_exists(index,
14 });
15
16 // Wait for the widget to actually display.
17 this.wait_for_element(int_text.index, int_text.query);
18
19 // Continue with the tests.
20 this.then(function() {
21 this.test.assert(this.cell_element_exists(int_text.index,
20 22 '.widget-area .widget-subarea'),
21 23 'Widget subarea exists.');
22 24
23 this.test.assert(this.cell_element_exists(index, int_text.query),
25 this.test.assert(this.cell_element_exists(int_text.index, int_text.query),
24 26 'Widget int textbox exists.');
25 27
26 28 this.cell_element_function(int_text.index, int_text.query, 'val', ['']);
27 29 this.sendKeys(int_text.query, '1.05');
28 30 });
29 31
30 32 this.wait_for_widget(int_text);
31 33
32 34 index = this.append_cell('print(int_widget.value)\n');
33 35 this.execute_cell_then(index, function(index){
34 36 this.test.assertEquals(this.get_output_cell(index).text, '1\n',
35 37 'Int textbox value set.');
36 38 this.cell_element_function(int_text.index, int_text.query, 'val', ['']);
37 39 this.sendKeys(int_text.query, '123456789');
38 40 });
39 41
40 42 this.wait_for_widget(int_text);
41 43
42 44 index = this.append_cell('print(int_widget.value)\n');
43 45 this.execute_cell_then(index, function(index){
44 46 this.test.assertEquals(this.get_output_cell(index).text, '123456789\n',
45 47 'Long int textbox value set (probably triggers throttling).');
46 48 this.cell_element_function(int_text.index, int_text.query, 'val', ['']);
47 49 this.sendKeys(int_text.query, '12hello');
48 50 });
49 51
50 52 this.wait_for_widget(int_text);
51 53
52 54 index = this.append_cell('print(int_widget.value)\n');
53 55 this.execute_cell_then(index, function(index){
54 56 this.test.assertEquals(this.get_output_cell(index).text, '12\n',
55 57 'Invald int textbox value caught and filtered.');
56 58 });
57
58 index = this.append_cell(
59 'from IPython.html import widgets\n' +
60 'from IPython.display import display, clear_output\n' +
61 'print("Success")');
62 this.execute_cell_then(index);
63
64 59
65 60 var slider_query = '.widget-area .widget-subarea .slider';
66 61 var int_text2 = {};
67 62 int_text2.query = '.widget-area .widget-subarea .my-second-num-test-text input';
68 63 int_text2.index = this.append_cell(
69 64 'intrange = [widgets.BoundedIntTextWidget(),\n' +
70 65 ' widgets.IntSliderWidget()]\n' +
71 66 '[display(intrange[i]) for i in range(2)]\n' +
72 67 'intrange[0]._dom_classes = ["my-second-num-test-text"]\n' +
73 68 'print(intrange[0].model_id)\n');
74 69 this.execute_cell_then(int_text2.index, function(index){
75 70 int_text2.model_id = this.get_output_cell(index).text.trim();
71 });
72
73 // Wait for the widgets to actually display.
74 this.wait_for_element(int_text2.index, int_text2.query);
75 this.wait_for_element(int_text2.index, slider_query);
76 76
77 this.test.assert(this.cell_element_exists(index,
77 // Continue with the tests.
78 this.then(function(){
79 this.test.assert(this.cell_element_exists(int_text2.index,
78 80 '.widget-area .widget-subarea'),
79 81 'Widget subarea exists.');
80 82
81 this.test.assert(this.cell_element_exists(index, slider_query),
83 this.test.assert(this.cell_element_exists(int_text2.index, slider_query),
82 84 'Widget slider exists.');
83 85
84 this.test.assert(this.cell_element_exists(index, int_text2.query),
86 this.test.assert(this.cell_element_exists(int_text2.index, int_text2.query),
85 87 'Widget int textbox exists.');
86 88 });
87 89
88 90 index = this.append_cell(
89 91 'for widget in intrange:\n' +
90 92 ' widget.max = 50\n' +
91 93 ' widget.min = -50\n' +
92 94 ' widget.value = 25\n' +
93 95 'print("Success")\n');
94 96 this.execute_cell_then(index, function(index){
95 97
96 98 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
97 99 'Int range properties cell executed with correct output.');
98 100
99 101 this.test.assert(this.cell_element_exists(int_text2.index, slider_query),
100 102 'Widget slider exists.');
101 103
102 104 this.test.assert(this.cell_element_function(int_text2.index, slider_query,
103 105 'slider', ['value']) == 25,
104 106 'Slider set to Python value.');
105 107
106 108 this.test.assert(this.cell_element_function(int_text2.index, int_text2.query,
107 109 'val') == 25, 'Int textbox set to Python value.');
108 110
109 111 // Clear the int textbox value and then set it to 1 by emulating
110 112 // keyboard presses.
111 113 this.evaluate(function(q){
112 114 var textbox = IPython.notebook.element.find(q);
113 115 textbox.val('1');
114 116 textbox.trigger('keyup');
115 117 }, {q: int_text2.query});
116 118 });
117 119
118 120 this.wait_for_widget(int_text2);
119 121
120 122 index = this.append_cell('print(intrange[0].value)\n');
121 123 this.execute_cell_then(index, function(index){
122 124 this.test.assertEquals(this.get_output_cell(index).text, '1\n',
123 125 'Int textbox set int range value');
124 126
125 127 // Clear the int textbox value and then set it to 120 by emulating
126 128 // keyboard presses.
127 129 this.evaluate(function(q){
128 130 var textbox = IPython.notebook.element.find(q);
129 131 textbox.val('120');
130 132 textbox.trigger('keyup');
131 133 }, {q: int_text2.query});
132 134 });
133 135
134 136 this.wait_for_widget(int_text2);
135 137
136 138 index = this.append_cell('print(intrange[0].value)\n');
137 139 this.execute_cell_then(index, function(index){
138 140 this.test.assertEquals(this.get_output_cell(index).text, '50\n',
139 141 'Int textbox value bound');
140 142
141 143 // Clear the int textbox value and then set it to 'hello world' by
142 144 // emulating keyboard presses. 'hello world' should get filtered...
143 145 this.evaluate(function(q){
144 146 var textbox = IPython.notebook.element.find(q);
145 147 textbox.val('hello world');
146 148 textbox.trigger('keyup');
147 149 }, {q: int_text2.query});
148 150 });
149 151
150 152 this.wait_for_widget(int_text2);
151 153
152 154 index = this.append_cell('print(intrange[0].value)\n');
153 155 this.execute_cell_then(index, function(index){
154 156 this.test.assertEquals(this.get_output_cell(index).text, '50\n',
155 157 'Invalid int textbox characters ignored');
156 158 });
157 159
158 160 index = this.append_cell(
159 161 'a = widgets.IntSlider()\n' +
160 162 'display(a)\n' +
161 163 'a.max = -1\n' +
162 164 'print("Success")\n');
163 165 this.execute_cell_then(index, function(index){
164 166 this.test.assertEquals(0, 0, 'Invalid int range max bound does not cause crash.');
165 167 }, true);
166 168
167 169 index = this.append_cell(
168 170 'a = widgets.IntSlider()\n' +
169 171 'display(a)\n' +
170 172 'a.min = 101\n' +
171 173 'print("Success")\n');
172 174 this.execute_cell_then(index, function(index){
173 175 this.test.assertEquals(0, 0, 'Invalid int range min bound does not cause crash.');
174 176 }, true);
175 177 }); No newline at end of file
@@ -1,138 +1,147
1 1 // Test selection class
2 2 casper.notebook_test(function () {
3 3 index = this.append_cell(
4 4 'from IPython.html import widgets\n' +
5 5 'from IPython.display import display, clear_output\n' +
6 6 'print("Success")');
7 7 this.execute_cell_then(index);
8 8
9 9 var combo_selector = '.widget-area .widget-subarea .widget-hbox .btn-group .widget-combo-btn';
10 10 var multibtn_selector = '.widget-area .widget-subarea .widget-hbox .btn-group[data-toggle="buttons-radio"]';
11 11 var radio_selector = '.widget-area .widget-subarea .widget-hbox .widget-radio-box';
12 12 var list_selector = '.widget-area .widget-subarea .widget-hbox .widget-listbox';
13 13
14 14 var selection_index;
15 15 var selection_values = 'abcd';
16 16 var check_state = function(context, index, state){
17 17 if (0 <= index && index < selection_values.length) {
18 18 var multibtn_state = context.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(' + (index + 1) + ')', 'hasClass', ['active']);
19 19 var radio_state = context.cell_element_function(selection_index, radio_selector + ' .radio:nth-child(' + (index + 1) + ') input', 'prop', ['checked']);
20 20 var list_val = context.cell_element_function(selection_index, list_selector, 'val');
21 21 var combo_val = context.cell_element_function(selection_index, combo_selector, 'html');
22 22
23 23 var val = selection_values.charAt(index);
24 24 var list_state = (val == list_val);
25 25 var combo_state = (val == combo_val);
26 26
27 27 return multibtn_state == state &&
28 28 radio_state == state &&
29 29 list_state == state &&
30 30 combo_state == state;
31 31 }
32 32 return true;
33 33 };
34 34
35 35 var verify_selection = function(context, index){
36 36 for (var i = 0; i < selection_values.length; i++) {
37 37 if (!check_state(context, i, i==index)) {
38 38 return false;
39 39 }
40 40 }
41 41 return true;
42 42 };
43 43
44 44 //values=["' + selection_values + '"[i] for i in range(4)]
45 45 selection_index = this.append_cell(
46 46 'values=["' + selection_values + '"[i] for i in range(4)]\n' +
47 47 'selection = [widgets.Dropdown(values=values),\n' +
48 48 ' widgets.ToggleButtons(values=values),\n' +
49 49 ' widgets.RadioButtons(values=values),\n' +
50 50 ' widgets.Select(values=values)]\n' +
51 51 '[display(selection[i]) for i in range(4)]\n' +
52 52 'for widget in selection:\n' +
53 53 ' def handle_change(name,old,new):\n' +
54 54 ' for other_widget in selection:\n' +
55 55 ' other_widget.value = new\n' +
56 56 ' widget.on_trait_change(handle_change, "value")\n' +
57 57 'print("Success")\n');
58 58 this.execute_cell_then(selection_index, function(index){
59 59 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
60 60 'Create selection cell executed with correct output.');
61 });
62
63 // Wait for the widgets to actually display.
64 this.wait_for_element(selection_index, combo_selector);
65 this.wait_for_element(selection_index, multibtn_selector);
66 this.wait_for_element(selection_index, radio_selector);
67 this.wait_for_element(selection_index, list_selector);
61 68
62 this.test.assert(this.cell_element_exists(index,
69 // Continue with the tests.
70 this.then(function() {
71 this.test.assert(this.cell_element_exists(selection_index,
63 72 '.widget-area .widget-subarea'),
64 73 'Widget subarea exists.');
65 74
66 this.test.assert(this.cell_element_exists(index, combo_selector),
75 this.test.assert(this.cell_element_exists(selection_index, combo_selector),
67 76 'Widget combobox exists.');
68 77
69 this.test.assert(this.cell_element_exists(index, multibtn_selector),
78 this.test.assert(this.cell_element_exists(selection_index, multibtn_selector),
70 79 'Widget multibutton exists.');
71 80
72 this.test.assert(this.cell_element_exists(index, radio_selector),
81 this.test.assert(this.cell_element_exists(selection_index, radio_selector),
73 82 'Widget radio buttons exists.');
74 83
75 this.test.assert(this.cell_element_exists(index, list_selector),
84 this.test.assert(this.cell_element_exists(selection_index, list_selector),
76 85 'Widget list exists.');
77 86
78 87 // Verify that no items are selected.
79 88 this.test.assert(verify_selection(this, 0), 'Default first item selected.');
80 89 });
81 90
82 91 index = this.append_cell(
83 92 'for widget in selection:\n' +
84 93 ' widget.value = "a"\n' +
85 94 'print("Success")\n');
86 95 this.execute_cell_then(index, function(index){
87 96 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
88 97 'Python select item executed with correct output.');
89 98
90 99 // Verify that the first item is selected.
91 100 this.test.assert(verify_selection(this, 0), 'Python selected');
92 101
93 102 // Verify that selecting a radio button updates all of the others.
94 103 this.cell_element_function(selection_index, radio_selector + ' .radio:nth-child(2) input', 'click');
95 104 });
96 105 this.wait_for_idle();
97 106 this.then(function () {
98 107 this.test.assert(verify_selection(this, 1), 'Radio button selection updated view states correctly.');
99 108
100 109 // Verify that selecting a list option updates all of the others.
101 110 this.cell_element_function(selection_index, list_selector + ' option:nth-child(3)', 'click');
102 111 });
103 112 this.wait_for_idle();
104 113 this.then(function () {
105 114 this.test.assert(verify_selection(this, 2), 'List selection updated view states correctly.');
106 115
107 116 // Verify that selecting a multibutton option updates all of the others.
108 117 // Bootstrap3 has changed the toggle button group behavior. Two clicks
109 118 // are required to actually select an item.
110 119 this.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(4)', 'click');
111 120 this.cell_element_function(selection_index, multibtn_selector + ' .btn:nth-child(4)', 'click');
112 121 });
113 122 this.wait_for_idle();
114 123 this.then(function () {
115 124 this.test.assert(verify_selection(this, 3), 'Multibutton selection updated view states correctly.');
116 125
117 126 // Verify that selecting a combobox option updates all of the others.
118 127 this.cell_element_function(selection_index, '.widget-area .widget-subarea .widget-hbox .btn-group ul.dropdown-menu li:nth-child(3) a', 'click');
119 128 });
120 129 this.wait_for_idle();
121 130 this.then(function () {
122 131 this.test.assert(verify_selection(this, 2), 'Combobox selection updated view states correctly.');
123 132 });
124 133
125 134 this.wait_for_idle();
126 135
127 136 index = this.append_cell(
128 137 'for widget in selection:\n' +
129 138 ' d = widget.values.copy()\n' +
130 139 ' d["z"] = "z"\n' +
131 140 ' widget.values = d\n' +
132 141 'selection[0].value = "z"');
133 142 this.execute_cell_then(index, function(index){
134 143
135 144 // Verify that selecting a combobox option updates all of the others.
136 145 this.test.assert(verify_selection(this, 4), 'Item added to selection widget.');
137 146 });
138 147 }); No newline at end of file
@@ -1,113 +1,120
1 1 // Test multicontainer class
2 2 casper.notebook_test(function () {
3 3 index = this.append_cell(
4 4 'from IPython.html import widgets\n' +
5 5 'from IPython.display import display, clear_output\n' +
6 6 'print("Success")');
7 7 this.execute_cell_then(index);
8 8
9 9 // Test tab view
10 10 var multicontainer1_query = '.widget-area .widget-subarea div div.nav-tabs';
11 11 var multicontainer1_index = this.append_cell(
12 12 'multicontainer = widgets.Tab()\n' +
13 13 'page1 = widgets.Text()\n' +
14 14 'page2 = widgets.Text()\n' +
15 15 'page3 = widgets.Text()\n' +
16 16 'multicontainer.children = [page1, page2, page3]\n' +
17 17 'display(multicontainer)\n' +
18 18 'multicontainer.selected_index = 0\n' +
19 19 'print("Success")\n');
20 20 this.execute_cell_then(multicontainer1_index, function(index){
21
22 21 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
23 22 'Create multicontainer cell executed with correct output. (1)');
23 });
24
25 // Wait for the widget to actually display.
26 this.wait_for_element(multicontainer1_index, multicontainer1_query);
24 27
25 this.test.assert(this.cell_element_exists(index,
28 // Continue with the tests.
29 this.then(function() {
30 this.test.assert(this.cell_element_exists(multicontainer1_index,
26 31 '.widget-area .widget-subarea'),
27 32 'Widget subarea exists.');
28 33
29 this.test.assert(this.cell_element_exists(index, multicontainer1_query),
34 this.test.assert(this.cell_element_exists(multicontainer1_index, multicontainer1_query),
30 35 'Widget tab list exists.');
31 36
32 this.test.assert(this.cell_element_exists(index, multicontainer1_query),
33 'First widget tab list exists.');
34
35 37 // JQuery selector is 1 based
36 38 this.click(multicontainer1_query + ' li:nth-child(2) a');
37 39 });
38 40
39 41 this.wait_for_idle();
40 42
41 43 index = this.append_cell(
42 44 'print(multicontainer.selected_index)\n' +
43 45 'multicontainer.selected_index = 2'); // 0 based
44 46 this.execute_cell_then(index, function(index){
45 47 this.test.assertEquals(this.get_output_cell(index).text, '1\n', // 0 based
46 48 'selected_index property updated with tab change.');
47 49
48 50 // JQuery selector is 1 based
49 51 this.test.assert(!this.cell_element_function(multicontainer1_index, multicontainer1_query + ' li:nth-child(1)', 'hasClass', ['active']),
50 52 "Tab 1 is not selected.");
51 53 this.test.assert(!this.cell_element_function(multicontainer1_index, multicontainer1_query + ' li:nth-child(2)', 'hasClass', ['active']),
52 54 "Tab 2 is not selected.");
53 55 this.test.assert(this.cell_element_function(multicontainer1_index, multicontainer1_query + ' li:nth-child(3)', 'hasClass', ['active']),
54 56 "Tab 3 is selected.");
55 57 });
56 58
57 59 index = this.append_cell('multicontainer.set_title(1, "hello")\nprint("Success")'); // 0 based
58 60 this.execute_cell_then(index, function(index){
59 61 this.test.assert(this.cell_element_function(multicontainer1_index, multicontainer1_query +
60 62 ' li:nth-child(2) a', 'html') == 'hello',
61 63 'Tab page title set (after display).');
62 64 });
63 65
64 66 // Test accordion view
65 67 var multicontainer2_query = '.widget-area .widget-subarea .panel-group';
66 68 var multicontainer2_index = this.append_cell(
67 69 'multicontainer = widgets.Accordion()\n' +
68 70 'page1 = widgets.Text()\n' +
69 71 'page2 = widgets.Text()\n' +
70 72 'page3 = widgets.Text()\n' +
71 73 'multicontainer.children = [page1, page2, page3]\n' +
72 74 'multicontainer.set_title(2, "good")\n' +
73 75 'display(multicontainer)\n' +
74 76 'multicontainer.selected_index = 0\n' +
75 77 'print("Success")\n');
76 78 this.execute_cell_then(multicontainer2_index, function(index){
77
78 79 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
79 80 'Create multicontainer cell executed with correct output. (2)');
81 });
82
83 // Wait for the widget to actually display.
84 this.wait_for_element(multicontainer2_index, multicontainer2_query);
80 85
81 this.test.assert(this.cell_element_exists(index,
86 // Continue with the tests.
87 this.then(function() {
88 this.test.assert(this.cell_element_exists(multicontainer2_index,
82 89 '.widget-area .widget-subarea'),
83 90 'Widget subarea exists.');
84 91
85 this.test.assert(this.cell_element_exists(index, multicontainer2_query),
92 this.test.assert(this.cell_element_exists(multicontainer2_index, multicontainer2_query),
86 93 'Widget accordion exists.');
87 94
88 this.test.assert(this.cell_element_exists(index, multicontainer2_query +
95 this.test.assert(this.cell_element_exists(multicontainer2_index, multicontainer2_query +
89 96 ' .panel:nth-child(1) .panel-collapse'),
90 97 'First accordion page exists.');
91 98
92 99 // JQuery selector is 1 based
93 this.test.assert(this.cell_element_function(index, multicontainer2_query +
100 this.test.assert(this.cell_element_function(multicontainer2_index, multicontainer2_query +
94 101 ' .panel.panel-default:nth-child(3) .panel-heading .accordion-toggle',
95 102 'html')=='good', 'Accordion page title set (before display).');
96 103
97 104 // JQuery selector is 1 based
98 105 this.click(multicontainer2_query + ' .panel:nth-child(2) .panel-heading .accordion-toggle');
99 106 });
100 107
101 108 this.wait_for_idle();
102 109
103 110 index = this.append_cell('print(multicontainer.selected_index)'); // 0 based
104 111 this.execute_cell_then(index, function(index){
105 112 this.test.assertEquals(this.get_output_cell(index).text, '1\n', // 0 based
106 113 'selected_index property updated with tab change.');
107 114
108 115 var is_collapsed = this.evaluate(function(s){
109 116 return $(s + ' div.panel:nth-child(2) a').hasClass('collapsed'); // 1 based
110 117 }, {s: multicontainer2_query});
111 118 this.test.assertEquals(is_collapsed, false, 'Was tab actually opened?');
112 119 });
113 120 }); No newline at end of file
@@ -1,53 +1,59
1 1 // Test widget string class
2 2 casper.notebook_test(function () {
3 index = this.append_cell(
3 var string_index = this.append_cell(
4 4 'from IPython.html import widgets\n' +
5 5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
7 this.execute_cell_then(index);
8
9 var string_index = this.append_cell(
10 6 'string_widget = [widgets.Text(value = "xyz", placeholder = "abc"),\n' +
11 7 ' widgets.Textarea(value = "xyz", placeholder = "def"),\n' +
12 8 ' widgets.HTML(value = "xyz"),\n' +
13 9 ' widgets.Latex(value = "$\\\\LaTeX{}$")]\n' +
14 10 '[display(widget) for widget in string_widget]\n'+
15 11 'print("Success")');
16 12 this.execute_cell_then(string_index, function(index){
17
18 13 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
19 14 'Create string widget cell executed with correct output.');
15 });
16
17 // Wait for the widget to actually display.
18 var textbox_selector = '.widget-area .widget-subarea .widget-hbox input[type=text]';
19 var textarea_selector = '.widget-area .widget-subarea .widget-hbox textarea';
20 var latex_selector = '.widget-area .widget-subarea div span.MathJax_Preview';
21 this.wait_for_element(string_index, textbox_selector);
22 this.wait_for_element(string_index, textarea_selector);
23 this.wait_for_element(string_index, latex_selector);
20 24
21 this.test.assert(this.cell_element_exists(index,
25 // Continue with the tests.
26 this.then(function(){
27 this.test.assert(this.cell_element_exists(string_index,
22 28 '.widget-area .widget-subarea'),
23 29 'Widget subarea exists.');
24 30
25 this.test.assert(this.cell_element_exists(index,
26 '.widget-area .widget-subarea .widget-hbox input[type=text]'),
31 this.test.assert(this.cell_element_exists(string_index,
32 textbox_selector),
27 33 'Textbox exists.');
28 34
29 this.test.assert(this.cell_element_exists(index,
30 '.widget-area .widget-subarea .widget-hbox textarea'),
35 this.test.assert(this.cell_element_exists(string_index,
36 textarea_selector),
31 37 'Textarea exists.');
32 38
33 this.test.assert(this.cell_element_function(index,
34 '.widget-area .widget-subarea .widget-hbox textarea', 'val')=='xyz',
39 this.test.assert(this.cell_element_function(string_index,
40 textarea_selector, 'val')=='xyz',
35 41 'Python set textarea value.');
36 42
37 this.test.assert(this.cell_element_function(index,
38 '.widget-area .widget-subarea .widget-hbox input[type=text]', 'val')=='xyz',
43 this.test.assert(this.cell_element_function(string_index,
44 textbox_selector, 'val')=='xyz',
39 45 'Python set textbox value.');
40 46
41 47 this.test.assert(this.cell_element_exists(string_index,
42 '.widget-area .widget-subarea div span.MathJax_Preview'),
48 latex_selector),
43 49 'MathJax parsed the LaTeX successfully.');
44 50
45 this.test.assert(this.cell_element_function(index,
46 '.widget-area .widget-subarea .widget-hbox textarea', 'attr', ['placeholder'])=='def',
51 this.test.assert(this.cell_element_function(string_index,
52 textarea_selector, 'attr', ['placeholder'])=='def',
47 53 'Python set textarea placeholder.');
48 54
49 this.test.assert(this.cell_element_function(index,
50 '.widget-area .widget-subarea .widget-hbox input[type=text]', 'attr', ['placeholder'])=='abc',
55 this.test.assert(this.cell_element_function(string_index,
56 textbox_selector, 'attr', ['placeholder'])=='abc',
51 57 'Python set textbox placehoder.');
52 58 });
53 59 });
General Comments 0
You need to be logged in to leave comments. Login now