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