Merge pull request #6818 from jdfreder/race_conditions

Add promises to the widget framework.
This commit is contained in:
Thomas Kluyver 2014-11-17 17:14:08 -08:00
commit 7cf7d3f202
19 changed files with 614 additions and 390 deletions

View File

@ -605,7 +605,119 @@ define([
$.ajax(url, settings);
});
};
var WrappedError = function(message, error){
// Wrappable Error class
// The Error class doesn't actually act on `this`. Instead it always
// returns a new instance of Error. Here we capture that instance so we
// can apply it's properties to `this`.
var tmp = Error.apply(this, [message]);
// Copy the properties of the error over to this.
var properties = Object.getOwnPropertyNames(tmp);
for (var i = 0; i < properties.length; i++) {
this[properties[i]] = tmp[properties[i]];
}
// Keep a stack of the original error messages.
if (error instanceof WrappedError) {
this.error_stack = error.error_stack;
} else {
this.error_stack = [error];
}
this.error_stack.push(tmp);
return this;
};
WrappedError.prototype = Object.create(Error.prototype, {});
var load_class = function(class_name, module_name, registry) {
// Tries to load a class
//
// Tries to load a class from a module using require.js, if a module
// is specified, otherwise tries to load a class from the global
// registry, if the global registry is provided.
return new Promise(function(resolve, reject) {
// Try loading the view module using require.js
if (module_name) {
require([module_name], function(module) {
if (module[class_name] === undefined) {
reject(new Error('Class '+class_name+' not found in module '+module_name));
} else {
resolve(module[class_name]);
}
}, reject);
} else {
if (registry && registry[class_name]) {
resolve(registry[class_name]);
} else {
reject(new Error('Class '+class_name+' not found in registry '));
}
}
});
};
var resolve_promises_dict = function(d) {
// Resolve a promiseful dictionary.
// Returns a single Promise.
var keys = Object.keys(d);
var values = [];
keys.forEach(function(key) {
values.push(d[key]);
});
return Promise.all(values).then(function(v) {
d = {};
for(var i=0; i<keys.length; i++) {
d[keys[i]] = v[i];
}
return d;
});
};
var WrappedError = function(message, error){
// Wrappable Error class
// The Error class doesn't actually act on `this`. Instead it always
// returns a new instance of Error. Here we capture that instance so we
// can apply it's properties to `this`.
var tmp = Error.apply(this, [message]);
// Copy the properties of the error over to this.
var properties = Object.getOwnPropertyNames(tmp);
for (var i = 0; i < properties.length; i++) {
this[properties[i]] = tmp[properties[i]];
}
// Keep a stack of the original error messages.
if (error instanceof WrappedError) {
this.error_stack = error.error_stack;
} else {
this.error_stack = [error];
}
this.error_stack.push(tmp);
return this;
};
WrappedError.prototype = Object.create(Error.prototype, {});
var reject = function(message, log) {
// Creates a wrappable Promise rejection function.
//
// Creates a function that returns a Promise.reject with a new WrappedError
// that has the provided message and wraps the original error that
// caused the promise to reject.
return function(error) {
var wrapped_error = new WrappedError(message, error);
if (log) console.error(wrapped_error);
return Promise.reject(wrapped_error);
};
};
var utils = {
regex_split : regex_split,
uuid : uuid,
@ -635,6 +747,10 @@ define([
XHR_ERROR : XHR_ERROR,
wrap_ajax_error : wrap_ajax_error,
promising_ajax : promising_ajax,
WrappedError: WrappedError,
load_class: load_class,
resolve_promises_dict: resolve_promises_dict,
reject: reject,
};
// Backwards compatability.

View File

@ -51,7 +51,7 @@ define([
CommManager.prototype.register_comm = function (comm) {
// Register a comm in the mapping
this.comms[comm.comm_id] = comm;
this.comms[comm.comm_id] = Promise.resolve(comm);
comm.kernel = this.kernel;
return comm.comm_id;
};
@ -66,67 +66,62 @@ define([
CommManager.prototype.comm_open = function (msg) {
var content = msg.content;
var that = this;
var instantiate_comm = function(target) {
var comm = new Comm(content.target_name, content.comm_id);
that.register_comm(comm);
var comm_id = content.comm_id;
this.comms[comm_id] = utils.load_class(content.target_name, content.target_module,
this.targets).then(function(target) {
var comm = new Comm(content.target_name, comm_id);
comm.kernel = that.kernel;
try {
target(comm, msg);
var response = target(comm, msg);
if (response instanceof Promise) {
return response.then(function() { return Promise.resolve(comm); });
}
} catch (e) {
console.log("Exception opening new comm:", e, e.stack, msg);
comm.close();
that.unregister_comm(comm);
var wrapped_error = new utils.WrappedError("Exception opening new comm", e);
console.error(wrapped_error);
return Promise.reject(wrapped_error);
}
};
return Promise.resolve(comm);
}, utils.reject('Could not open comm', true));
return this.comms[comm_id];
};
CommManager.prototype.comm_close = function(msg) {
var content = msg.content;
if (this.comms[content.comm_id] === undefined) {
console.error('Comm promise not found for comm id ' + content.comm_id);
return;
}
if (content.target_module) {
// Load requirejs module for comm target
require([content.target_module], function(mod) {
var target = mod[content.target_name];
if (target !== undefined) {
instantiate_comm(target)
} else {
console.log("Comm target " + content.target_name +
" not found in module " + content.target_module);
}
}, function(err) { console.log(err); });
} else {
// No requirejs module specified: look for target in registry
var f = this.targets[content.target_name];
if (f === undefined) {
console.log("No such target registered: ", content.target_name);
console.log("Available targets are: ", this.targets);
return;
this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) {
this.unregister_comm(comm);
try {
comm.handle_close(msg);
} catch (e) {
console.log("Exception closing comm: ", e, e.stack, msg);
}
instantiate_comm(f)
}
});
};
CommManager.prototype.comm_close = function (msg) {
CommManager.prototype.comm_msg = function(msg) {
var content = msg.content;
var comm = this.comms[content.comm_id];
if (comm === undefined) {
if (this.comms[content.comm_id] === undefined) {
console.error('Comm promise not found for comm id ' + content.comm_id);
return;
}
this.unregister_comm(comm);
try {
comm.handle_close(msg);
} catch (e) {
console.log("Exception closing comm: ", e, e.stack, msg);
}
};
CommManager.prototype.comm_msg = function (msg) {
var content = msg.content;
var comm = this.comms[content.comm_id];
if (comm === undefined) {
return;
}
try {
comm.handle_msg(msg);
} catch (e) {
console.log("Exception handling comm msg: ", e, e.stack, msg);
}
this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) {
try {
comm.handle_msg(msg);
} catch (e) {
console.log("Exception handling comm msg: ", e, e.stack, msg);
}
return Promise.resolve(comm);
});
};
//-----------------------------------------------------------------------
@ -180,7 +175,7 @@ define([
// methods for handling incoming messages
Comm.prototype._maybe_callback = function (key, msg) {
Comm.prototype._callback = function (key, msg) {
var callback = this['_' + key + '_callback'];
if (callback) {
try {
@ -192,11 +187,11 @@ define([
};
Comm.prototype.handle_msg = function (msg) {
this._maybe_callback('msg', msg);
this._callback('msg', msg);
};
Comm.prototype.handle_close = function (msg) {
this._maybe_callback('close', msg);
this._callback('close', msg);
};
// For backwards compatability.

View File

@ -5,8 +5,9 @@ define([
"underscore",
"backbone",
"jquery",
"base/js/namespace"
], function (_, Backbone, $, IPython) {
"base/js/utils",
"base/js/namespace",
], function (_, Backbone, $, utils, IPython) {
"use strict";
//--------------------------------------------------------------------
// WidgetManager class
@ -47,20 +48,25 @@ define([
//--------------------------------------------------------------------
WidgetManager.prototype.display_view = function(msg, model) {
// Displays a view for a particular model.
var cell = this.get_msg_cell(msg.parent_header.msg_id);
if (cell === null) {
console.log("Could not determine where the display" +
" message was from. Widget will not be displayed");
} else {
var that = this;
this.create_view(model, {cell: cell, success: function(view) {
that._handle_display_view(view);
if (cell.widget_subarea) {
cell.widget_subarea.append(view.$el);
}
view.trigger('displayed');
}});
}
var that = this;
return new Promise(function(resolve, reject) {
var cell = that.get_msg_cell(msg.parent_header.msg_id);
if (cell === null) {
reject(new Error("Could not determine where the display" +
" message was from. Widget will not be displayed"));
} else if (cell.widget_subarea) {
var dummy = $('<div />');
cell.widget_subarea.append(dummy);
that.create_view(model, {cell: cell}).then(function(view) {
that._handle_display_view(view);
dummy.replaceWith(view.$el);
view.trigger('displayed');
resolve(view);
}, function(error) {
reject(new utils.WrappedError('Could not display view', error));
});
}
});
};
WidgetManager.prototype._handle_display_view = function (view) {
@ -70,52 +76,39 @@ define([
if (this.keyboard_manager) {
this.keyboard_manager.register_events(view.$el);
if (view.additional_elements) {
for (var i = 0; i < view.additional_elements.length; i++) {
if (view.additional_elements) {
for (var i = 0; i < view.additional_elements.length; i++) {
this.keyboard_manager.register_events(view.additional_elements[i]);
}
}
}
}
}
};
WidgetManager.prototype.create_view = function(model, options) {
// Creates a view for a particular model.
var view_name = model.get('_view_name');
var view_mod = model.get('_view_module');
var error = options.error || function(error) { console.log(error); };
// Creates a promise for a view of a given model
// Make sure the view creation is not out of order with
// any state updates.
model.state_change = model.state_change.then(function() {
return utils.load_class(model.get('_view_name'), model.get('_view_module'),
WidgetManager._view_types).then(function(ViewType) {
var instantiate_view = function(ViewType) {
if (ViewType) {
// If a view is passed into the method, use that view's cell as
// the cell for the view that is created.
options = options || {};
if (options.parent !== undefined) {
options.cell = options.parent.options.cell;
}
// Create and render the view...
var parameters = {model: model, options: options};
var view = new ViewType(parameters);
view.listenTo(model, 'destroy', view.remove);
view.render();
model.on('destroy', view.remove, view);
if (options.success) {
options.success(view);
}
} else {
error({unknown_view: true, view_name: view_name,
view_module: view_mod});
}
};
if (view_mod) {
require([view_mod], function(module) {
instantiate_view(module[view_name]);
}, error);
} else {
instantiate_view(WidgetManager._view_types[view_name]);
}
return view;
}, utils.reject("Couldn't create a view for model id '" + String(model.id) + "'"));
});
return model.state_change;
};
WidgetManager.prototype.get_msg_cell = function (msg_id) {
@ -179,24 +172,20 @@ define([
};
WidgetManager.prototype.get_model = function (model_id) {
// Look-up a model instance by its id.
var model = this._models[model_id];
if (model !== undefined && model.id == model_id) {
return model;
}
return null;
// Get a promise for a model by model id.
return this._models[model_id];
};
WidgetManager.prototype._handle_comm_open = function (comm, msg) {
// Handle when a comm is opened.
this.create_model({
return this.create_model({
model_name: msg.content.data.model_name,
model_module: msg.content.data.model_module,
comm: comm});
comm: comm}).catch(utils.reject("Couldn't create a model."));
};
WidgetManager.prototype.create_model = function (options) {
// Create and return a new widget model.
// Create and return a promise for a new widget model
//
// Minimally, one must provide the model_name and widget_class
// parameters to create a model from Javascript.
@ -206,8 +195,9 @@ define([
// JS:
// IPython.notebook.kernel.widget_manager.create_model({
// model_name: 'WidgetModel',
// widget_class: 'IPython.html.widgets.widget_int.IntSlider',
// init_state_callback: function(model) { console.log('Create success!', model); }});
// widget_class: 'IPython.html.widgets.widget_int.IntSlider'})
// .then(function(model) { console.log('Create success!', model); },
// $.proxy(console.error, console));
//
// Parameters
// ----------
@ -220,60 +210,33 @@ define([
// widget_class: (optional) string
// Target name of the widget in the back-end.
// comm: (optional) Comm
// success: (optional) callback
// Callback for when the model was created successfully.
// error: (optional) callback
// Callback for when the model wasn't created.
// init_state_callback: (optional) callback
// Called when the first state push from the back-end is
// recieved. Allows you to modify the model after it's
// complete state is filled and synced.
// Make default callbacks if not specified.
var error = options.error || function(error) { console.log(error); };
// Create a comm if it wasn't provided.
var comm = options.comm;
if (!comm) {
comm = this.comm_manager.new_comm('ipython.widget', {'widget_class': options.widget_class});
}
// Create a new model that is connected to the comm.
var that = this;
var instantiate_model = function(ModelType) {
var model_id = comm.comm_id;
var widget_model = new ModelType(that, model_id, comm, options.init_state_callback);
widget_model.on('comm:close', function () {
delete that._models[model_id];
var model_id = comm.comm_id;
var model_promise = utils.load_class(options.model_name, options.model_module, WidgetManager._model_types)
.then(function(ModelType) {
var widget_model = new ModelType(that, model_id, comm);
widget_model.once('comm:close', function () {
delete that._models[model_id];
});
return widget_model;
}, function(error) {
delete that._models[model_id];
var wrapped_error = new utils.WrappedError("Couldn't create model", error);
return Promise.reject(wrapped_error);
});
that._models[model_id] = widget_model;
if (options.success) {
options.success(widget_model);
}
};
// Get the model type using require or through the registry.
var widget_type_name = options.model_name;
var widget_module = options.model_module;
if (widget_module) {
// Load the module containing the widget model
require([widget_module], function(mod) {
if (mod[widget_type_name]) {
instantiate_model(mod[widget_type_name]);
} else {
error("Error creating widget model: " + widget_type_name
+ " not found in " + widget_module);
}
}, error);
} else {
// No module specified, load from the global models registry
instantiate_model(WidgetManager._model_types[widget_type_name]);
}
this._models[model_id] = model_promise;
return model_promise;
};
// Backwards compatability.
// Backwards compatibility.
IPython.WidgetManager = WidgetManager;
return {'WidgetManager': WidgetManager};

View File

@ -3,13 +3,14 @@
define(["widgets/js/manager",
"underscore",
"backbone",
"jquery",
"backbone",
"jquery",
"base/js/utils",
"base/js/namespace",
], function(widgetmanager, _, Backbone, $, IPython){
], function(widgetmanager, _, Backbone, $, utils, IPython){
var WidgetModel = Backbone.Model.extend({
constructor: function (widget_manager, model_id, comm, init_state_callback) {
constructor: function (widget_manager, model_id, comm) {
// Constructor
//
// Creates a WidgetModel instance.
@ -20,11 +21,8 @@ define(["widgets/js/manager",
// model_id : string
// An ID unique to this model.
// comm : Comm instance (optional)
// init_state_callback : callback (optional)
// Called once when the first state message is recieved from
// the back-end.
this.widget_manager = widget_manager;
this.init_state_callback = init_state_callback;
this.state_change = Promise.resolve();
this._buffered_state_diff = {};
this.pending_msgs = 0;
this.msg_buffer = null;
@ -71,13 +69,12 @@ define(["widgets/js/manager",
_handle_comm_msg: function (msg) {
// Handle incoming comm msg.
var method = msg.content.data.method;
var that = this;
switch (method) {
case 'update':
this.set_state(msg.content.data.state);
if (this.init_state_callback) {
this.init_state_callback.apply(this, [this]);
delete this.init_state_callback;
}
this.state_change = this.state_change.then(function() {
return that.set_state(msg.content.data.state);
}).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true));
break;
case 'custom':
this.trigger('msg:custom', msg.content.data.content);
@ -89,17 +86,17 @@ define(["widgets/js/manager",
},
set_state: function (state) {
var that = this;
// Handle when a widget is updated via the python side.
this.state_lock = state;
try {
var that = this;
WidgetModel.__super__.set.apply(this, [Object.keys(state).reduce(function(obj, key) {
obj[key] = that._unpack_models(state[key]);
return obj;
}, {})]);
} finally {
this.state_lock = null;
}
return this._unpack_models(state).then(function(state) {
that.state_lock = state;
try {
WidgetModel.__super__.set.call(that, state);
} finally {
that.state_lock = null;
}
return Promise.resolve();
}, utils.reject("Couldn't set model state", true));
},
_handle_status: function (msg, callbacks) {
@ -259,24 +256,18 @@ define(["widgets/js/manager",
_.each(value, function(sub_value, key) {
unpacked.push(that._unpack_models(sub_value));
});
return unpacked;
return Promise.all(unpacked);
} else if (value instanceof Object) {
unpacked = {};
_.each(value, function(sub_value, key) {
unpacked[key] = that._unpack_models(sub_value);
});
return unpacked;
return utils.resolve_promises_dict(unpacked);
} else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
var model = this.widget_manager.get_model(value.slice(10, value.length));
if (model) {
return model;
} else {
return value;
}
// get_model returns a promise already
return this.widget_manager.get_model(value.slice(10, value.length));
} else {
return value;
return Promise.resolve(value);
}
},
@ -304,7 +295,7 @@ define(["widgets/js/manager",
this.options = parameters.options;
this.child_model_views = {};
this.child_views = {};
this.id = this.id || IPython.utils.uuid();
this.id = this.id || utils.uuid();
this.model.views[this.id] = this;
this.on('displayed', function() {
this.is_displayed = true;
@ -318,29 +309,19 @@ define(["widgets/js/manager",
},
create_child_view: function(child_model, options) {
// Create and return a child view.
//
// -given a model and (optionally) a view name if the view name is
// not given, it defaults to the model's default view attribute.
// TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
// it would be great to have the widget manager add the cell metadata
// to the subview without having to add it here.
// Create and promise that resolves to a child view of a given model
var that = this;
var old_callback = options.callback || function(view) {};
options = $.extend({ parent: this, success: function(child_view) {
options = $.extend({ parent: this }, options || {});
return this.model.widget_manager.create_view(child_model, options).then(function(child_view) {
// Associate the view id with the model id.
if (that.child_model_views[child_model.id] === undefined) {
that.child_model_views[child_model.id] = [];
}
that.child_model_views[child_model.id].push(child_view.id);
// Remember the view by id.
that.child_views[child_view.id] = child_view;
old_callback(child_view);
}}, options || {});
this.model.widget_manager.create_view(child_model, options);
return child_view;
}, utils.reject("Couldn't create child view"));
},
pop_child_view: function(child_model) {

View File

@ -4,8 +4,9 @@
define([
"widgets/js/widget",
"jqueryui",
"base/js/utils",
"bootstrap",
], function(widget, $){
], function(widget, $, utils){
var BoxView = widget.DOMWidgetView.extend({
initialize: function(){
@ -75,14 +76,17 @@ define([
add_child_model: function(model) {
// Called when a model is added to the children list.
var that = this;
this.create_child_view(model, {callback: function(view) {
that.$box.append(view.$el);
var dummy = $('<div/>');
that.$box.append(dummy);
return this.create_child_view(model).then(function(view) {
dummy.replaceWith(view.el);
// Trigger the displayed event of the child view.
that.after_displayed(function() {
view.trigger('displayed');
});
}});
return view;
}, utils.reject("Couldn't add child view to box", true));
},
});

View File

@ -114,9 +114,10 @@ define([
accordion_group.container_index = container_index;
this.model_containers[model.id] = accordion_group;
this.create_child_view(model, {callback: function(view) {
accordion_inner.append(view.$el);
var dummy = $('<div/>');
accordion_inner.append(dummy);
return this.create_child_view(model).then(function(view) {
dummy.replaceWith(view.$el);
that.update();
that.update_titles();
@ -124,7 +125,8 @@ define([
that.after_displayed(function() {
view.trigger('displayed');
});
}});
return view;
}, utils.reject("Couldn't add child view to box", true));
},
});
@ -186,36 +188,40 @@ define([
.css('list-style-type', 'none')
.appendTo(this.$tabs);
this.create_child_view(model, {callback: function(view) {
var tab_text = $('<a />')
.attr('href', '#' + uuid)
.attr('data-toggle', 'tab')
.text('Page ' + index)
.appendTo(tab)
.click(function (e) {
// Calling model.set will trigger all of the other views of the
// model to update.
that.model.set("selected_index", index, {updated_view: that});
that.touch();
that.select_page(index);
});
tab.tab_text_index = that.containers.push(tab_text) - 1;
var dummy = $('<div />');
var contents_div = $('<div />', {id: uuid})
.addClass('tab-pane')
.addClass('fade')
.append(dummy)
.appendTo(that.$tab_contents);
return this.create_child_view(model).then(function(view) {
dummy.replaceWith(view.$el);
view.parent_tab = tab;
var tab_text = $('<a />')
.attr('href', '#' + uuid)
.attr('data-toggle', 'tab')
.text('Page ' + index)
.appendTo(tab)
.click(function (e) {
// Calling model.set will trigger all of the other views of the
// model to update.
that.model.set("selected_index", index, {updated_view: that});
that.touch();
that.select_page(index);
});
tab.tab_text_index = that.containers.push(tab_text) - 1;
var contents_div = $('<div />', {id: uuid})
.addClass('tab-pane')
.addClass('fade')
.append(view.$el)
.appendTo(that.$tab_contents);
view.parent_container = contents_div;
// Trigger the displayed event of the child view.
that.after_displayed(function() {
view.trigger('displayed');
});
}});
return view;
}, utils.reject("Couldn't add child view to box", true));
},
update: function(options) {

View File

@ -27,9 +27,9 @@
bootstrap: 'components/bootstrap/js/bootstrap.min',
bootstraptour: 'components/bootstrap-tour/build/js/bootstrap-tour.min',
jqueryui: 'components/jquery-ui/ui/minified/jquery-ui.min',
moment: "components/moment/moment",
moment: 'components/moment/moment',
codemirror: 'components/codemirror',
termjs: "components/term.js/src/term",
termjs: 'components/term.js/src/term',
contents: '{{ contents_js_source }}',
},
shim: {

View File

@ -22,6 +22,35 @@ casper.open_new_notebook = function () {
});
this.waitFor(this.page_loaded);
// Hook the log and error methods of the console, forcing them to
// serialize their arguments before printing. This allows the
// Objects to cross into the phantom/slimer regime for display.
this.thenEvaluate(function(){
var serialize_arguments = function(f, context) {
return function() {
var pretty_arguments = [];
for (var i = 0; i < arguments.length; i++) {
var value = arguments[i];
if (value instanceof Object) {
var name = value.name || 'Object';
// Print a JSON string representation of the object.
// If we don't do this, [Object object] gets printed
// by casper, which is useless. The long regular
// expression reduces the verbosity of the JSON.
pretty_arguments.push(name + ' {' + JSON.stringify(value, null, ' ')
.replace(/(\s+)?({)?(\s+)?(}(\s+)?,?)?(\s+)?(\s+)?\n/g, '\n')
.replace(/\n(\s+)?\n/g, '\n'));
} else {
pretty_arguments.push(value);
}
}
f.apply(context, pretty_arguments);
};
};
console.log = serialize_arguments(console.log, console);
console.error = serialize_arguments(console.error, console);
});
// Make sure the kernel has started
this.waitFor(this.kernel_running);
// track the IPython busy/idle state
@ -151,10 +180,31 @@ casper.wait_for_widget = function (widget_info) {
// widget_info : object
// Object which contains info related to the widget. The model_id property
// is used to identify the widget.
// Clear the results of a previous query, if they exist. Make sure a
// dictionary exists to store the async results in.
this.thenEvaluate(function(model_id) {
if (window.pending_msgs === undefined) {
window.pending_msgs = {};
} else {
window.pending_msgs[model_id] = -1;
}
}, {model_id: widget_info.model_id});
// Wait for the pending messages to be 0.
this.waitFor(function () {
var pending = this.evaluate(function (m) {
return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
}, {m: widget_info.model_id});
var pending = this.evaluate(function (model_id) {
// Get the model. Once the model is had, store it's pending_msgs
// count in the window's dictionary.
IPython.notebook.kernel.widget_manager.get_model(model_id)
.then(function(model) {
window.pending_msgs[model_id] = model.pending_msgs;
});
// Return the pending_msgs result.
return window.pending_msgs[model_id];
}, {model_id: widget_info.model_id});
if (pending === 0) {
return true;
@ -285,6 +335,15 @@ casper.execute_cell_then = function(index, then_callback, expect_failure) {
return return_val;
};
casper.wait_for_element = function(index, selector){
// Utility function that allows us to easily wait for an element
// within a cell. Uses JQuery selector to look for the element.
var that = this;
this.waitFor(function() {
return that.cell_element_exists(index, selector);
});
};
casper.cell_element_exists = function(index, selector){
// Utility function that allows us to easily check if an element exists
// within a cell. Uses JQuery selector to look for the element.
@ -655,7 +714,10 @@ casper.print_log = function () {
casper.on("page.error", function onError(msg, trace) {
// show errors in the browser
this.echo("Page Error!");
this.echo("Page Error");
this.echo(" Message: " + msg.split('\n').join('\n '));
this.echo(" Call stack:");
var local_path = this.get_notebook_server();
for (var i = 0; i < trace.length; i++) {
var frame = trace[i];
var file = frame.file;
@ -664,12 +726,15 @@ casper.on("page.error", function onError(msg, trace) {
if (file === "phantomjs://webpage.evaluate()") {
file = "evaluate";
}
this.echo("line " + frame.line + " of " + file);
if (frame.function.length > 0) {
this.echo("in " + frame.function);
// remove the version tag from the path
file = file.replace(/(\?v=[0-9abcdef]+)/, '');
// remove the local address from the beginning of the path
if (file.indexOf(local_path) === 0) {
file = file.substr(local_path.length);
}
var frame_text = (frame.function.length > 0) ? " in " + frame.function : "";
this.echo(" line " + frame.line + " of " + file + frame_text);
}
this.echo(msg);
});
@ -680,7 +745,8 @@ casper.capture_log = function () {
this.on('remote.message', function(msg) {
captured_log.push(msg);
});
var that = this;
this.test.on("test.done", function (result) {
// test.done runs per-file,
// but suiteResults is per-suite (directory)
@ -696,12 +762,38 @@ casper.capture_log = function () {
if (current_errors > seen_errors && captured_log.length > 0) {
casper.echo("\nCaptured console.log:");
for (var i = 0; i < captured_log.length; i++) {
casper.echo(" " + captured_log[i]);
var output = String(captured_log[i]).split('\n');
for (var j = 0; j < output.length; j++) {
casper.echo(" " + output[j]);
}
}
}
seen_errors = current_errors;
captured_log = [];
});
};
casper.interact = function() {
// Start an interactive Javascript console.
var system = require('system');
system.stdout.writeLine('JS interactive console.');
system.stdout.writeLine('Type `exit` to quit.');
function read_line() {
system.stdout.writeLine('JS: ');
var line = system.stdin.readLine();
return line;
}
var input = read_line();
while (input.trim() != 'exit') {
var output = this.evaluate(function(code) {
return String(eval(code));
}, {code: input});
system.stdout.writeLine('\nOut: ' + output);
input = read_line();
}
};
casper.capture_log();

View File

@ -18,12 +18,11 @@ casper.notebook_test(function () {
this.evaluate(function() {
IPython.notebook.kernel.widget_manager.create_model({
model_name: 'WidgetModel',
widget_class: 'IPython.html.widgets.widget_int.IntSlider',
init_state_callback: function(model) {
widget_class: 'IPython.html.widgets.widget_int.IntSlider'})
.then(function(model) {
console.log('Create success!', model);
window.slider_id = model.id;
}
});
}, function(error) { console.log(error); });
});
});

View File

@ -59,13 +59,27 @@ casper.notebook_test(function () {
JSON.stringify(input) + ' passed through Model._pack_model unchanged');
};
var test_unpack = function (input) {
var output = that.evaluate(function(input) {
that.thenEvaluate(function(input) {
window.results = undefined;
var model = new IPython.WidgetModel(IPython.notebook.kernel.widget_manager, undefined);
var results = model._unpack_models(input);
return results;
model._unpack_models(input).then(function(results) {
window.results = results;
});
}, {input: input});
that.test.assert(recursive_compare(input, output),
JSON.stringify(input) + ' passed through Model._unpack_model unchanged');
that.waitFor(function check() {
return that.evaluate(function() {
return window.results;
});
});
that.then(function() {
var results = that.evaluate(function() {
return window.results;
});
that.test.assert(recursive_compare(input, results),
JSON.stringify(input) + ' passed through Model._unpack_model unchanged');
});
};
var test_packing = function(input) {
test_pack(input);
@ -84,7 +98,7 @@ casper.notebook_test(function () {
test_packing([String('hi'), Date("Thu Nov 13 2014 13:46:21 GMT-0500")])
// Test multi-set, single touch code. First create a custom widget.
this.evaluate(function() {
this.thenEvaluate(function() {
var MultiSetView = IPython.DOMWidgetView.extend({
render: function(){
this.model.set('a', 1);

View File

@ -1,85 +1,90 @@
// Test widget bool class
casper.notebook_test(function () {
index = this.append_cell(
// Create a checkbox and togglebutton.
var bool_index = this.append_cell(
'from IPython.html import widgets\n' +
'from IPython.display import display, clear_output\n' +
'print("Success")');
this.execute_cell_then(index);
var bool_index = this.append_cell(
'bool_widgets = [widgets.Checkbox(description="Title", value=True),\n' +
' widgets.ToggleButton(description="Title", value=True)]\n' +
'display(bool_widgets[0])\n' +
'display(bool_widgets[1])\n' +
'print("Success")');
this.execute_cell_then(bool_index, function(index){
this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
'Create bool widget cell executed with correct output.');
});
this.test.assert(this.cell_element_exists(index,
// Wait for the widgets to actually display.
var widget_checkbox_selector = '.widget-area .widget-subarea .widget-hbox input';
var widget_togglebutton_selector = '.widget-area .widget-subarea button';
this.wait_for_element(bool_index, widget_checkbox_selector);
this.wait_for_element(bool_index, widget_togglebutton_selector);
// Continue the tests.
this.then(function() {
this.test.assert(this.cell_element_exists(bool_index,
'.widget-area .widget-subarea'),
'Widget subarea exists.');
this.test.assert(this.cell_element_exists(index,
'.widget-area .widget-subarea .widget-hbox input'),
this.test.assert(this.cell_element_exists(bool_index,
widget_checkbox_selector),
'Checkbox exists.');
this.test.assert(this.cell_element_function(index,
'.widget-area .widget-subarea .widget-hbox input', 'prop', ['checked']),
this.test.assert(this.cell_element_function(bool_index,
widget_checkbox_selector, 'prop', ['checked']),
'Checkbox is checked.');
this.test.assert(this.cell_element_exists(index,
this.test.assert(this.cell_element_exists(bool_index,
'.widget-area .widget-subarea .widget-hbox .widget-label'),
'Checkbox label exists.');
this.test.assert(this.cell_element_function(index,
this.test.assert(this.cell_element_function(bool_index,
'.widget-area .widget-subarea .widget-hbox .widget-label', 'html')=="Title",
'Checkbox labeled correctly.');
this.test.assert(this.cell_element_exists(index,
'.widget-area .widget-subarea button'),
this.test.assert(this.cell_element_exists(bool_index,
widget_togglebutton_selector),
'Toggle button exists.');
this.test.assert(this.cell_element_function(index,
'.widget-area .widget-subarea button', 'html')=="Title",
this.test.assert(this.cell_element_function(bool_index,
widget_togglebutton_selector, 'html')=="Title",
'Toggle button labeled correctly.');
this.test.assert(this.cell_element_function(index,
'.widget-area .widget-subarea button', 'hasClass', ['active']),
this.test.assert(this.cell_element_function(bool_index,
widget_togglebutton_selector, 'hasClass', ['active']),
'Toggle button is toggled.');
});
// Try changing the state of the widgets programatically.
index = this.append_cell(
'bool_widgets[0].value = False\n' +
'bool_widgets[1].value = False\n' +
'print("Success")');
this.execute_cell_then(index, function(index){
this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
'Change bool widget value cell executed with correct output.');
this.test.assert(! this.cell_element_function(bool_index,
'.widget-area .widget-subarea .widget-hbox input', 'prop', ['checked']),
widget_checkbox_selector, 'prop', ['checked']),
'Checkbox is not checked. (1)');
this.test.assert(! this.cell_element_function(bool_index,
'.widget-area .widget-subarea button', 'hasClass', ['active']),
widget_togglebutton_selector, 'hasClass', ['active']),
'Toggle button is not toggled. (1)');
// Try toggling the bool by clicking on the checkbox.
this.cell_element_function(bool_index, '.widget-area .widget-subarea .widget-hbox input', 'click');
this.cell_element_function(bool_index, widget_checkbox_selector, 'click');
this.test.assert(this.cell_element_function(bool_index,
'.widget-area .widget-subarea .widget-hbox input', 'prop', ['checked']),
widget_checkbox_selector, 'prop', ['checked']),
'Checkbox is checked. (2)');
// Try toggling the bool by clicking on the toggle button.
this.cell_element_function(bool_index, '.widget-area .widget-subarea button', 'click');
this.cell_element_function(bool_index, widget_togglebutton_selector, 'click');
this.test.assert(this.cell_element_function(bool_index,
'.widget-area .widget-subarea button', 'hasClass', ['active']),
widget_togglebutton_selector, 'hasClass', ['active']),
'Toggle button is toggled. (3)');
});

View File

@ -1,12 +1,10 @@
// Test container class
casper.notebook_test(function () {
index = this.append_cell(
// Create a box widget.
var container_index = this.append_cell(
'from IPython.html import widgets\n' +
'from IPython.display import display, clear_output\n' +
'print("Success")');
this.execute_cell_then(index);
var container_index = this.append_cell(
'container = widgets.Box()\n' +
'button = widgets.Button()\n'+
'container.children = [button]\n'+
@ -14,24 +12,32 @@ casper.notebook_test(function () {
'container._dom_classes = ["my-test-class"]\n'+
'print("Success")\n');
this.execute_cell_then(container_index, function(index){
this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
'Create container cell executed with correct output.');
});
this.test.assert(this.cell_element_exists(index,
// Wait for the widgets to actually display.
var widget_box_selector = '.widget-area .widget-subarea .widget-box';
var widget_box_button_selector = '.widget-area .widget-subarea .widget-box button';
this.wait_for_element(container_index, widget_box_selector);
this.wait_for_element(container_index, widget_box_button_selector);
// Continue with the tests.
this.then(function() {
this.test.assert(this.cell_element_exists(container_index,
'.widget-area .widget-subarea'),
'Widget subarea exists.');
this.test.assert(this.cell_element_exists(index,
'.widget-area .widget-subarea .widget-box'),
this.test.assert(this.cell_element_exists(container_index,
widget_box_selector),
'Widget container exists.');
this.test.assert(this.cell_element_exists(index,
this.test.assert(this.cell_element_exists(container_index,
'.widget-area .widget-subarea .my-test-class'),
'_dom_classes works.');
this.test.assert(this.cell_element_exists(index,
'.widget-area .widget-subarea .my-test-class button'),
this.test.assert(this.cell_element_exists(container_index,
widget_box_button_selector),
'Container parent/child relationship works.');
});
@ -61,20 +67,26 @@ casper.notebook_test(function () {
'_dom_classes can be used to remove a class.');
});
index = this.append_cell(
var boxalone_index = this.append_cell(
'display(button)\n'+
'print("Success")\n');
this.execute_cell_then(index, function(index){
this.execute_cell_then(boxalone_index, function(index){
this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
'Display container child executed with correct output.');
});
this.test.assert(! this.cell_element_exists(index,
'.widget-area .widget-subarea .widget-box'),
// Wait for the widget to actually display.
var widget_button_selector = '.widget-area .widget-subarea button';
this.wait_for_element(boxalone_index, widget_button_selector);
// Continue with the tests.
this.then(function() {
this.test.assert(! this.cell_element_exists(boxalone_index,
widget_box_selector),
'Parent container not displayed.');
this.test.assert(this.cell_element_exists(index,
'.widget-area .widget-subarea button'),
this.test.assert(this.cell_element_exists(boxalone_index,
widget_button_selector),
'Child displayed.');
});
});

View File

@ -1,12 +1,8 @@
// Test widget button class
casper.notebook_test(function () {
index = this.append_cell(
var button_index = this.append_cell(
'from IPython.html import widgets\n' +
'from IPython.display import display, clear_output\n' +
'print("Success")');
this.execute_cell_then(index);
var button_index = this.append_cell(
'button = widgets.Button(description="Title")\n' +
'display(button)\n' +
'print("Success")\n' +
@ -14,24 +10,30 @@ casper.notebook_test(function () {
' display("Clicked")\n' +
'button.on_click(handle_click)');
this.execute_cell_then(button_index, function(index){
this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
'Create button cell executed with correct output.');
});
this.test.assert(this.cell_element_exists(index,
// Wait for the widgets to actually display.
var widget_button_selector = '.widget-area .widget-subarea button';
this.wait_for_element(button_index, widget_button_selector);
// Continue with the tests.
this.then(function() {
this.test.assert(this.cell_element_exists(button_index,
'.widget-area .widget-subarea'),
'Widget subarea exists.');
this.test.assert(this.cell_element_exists(index,
'.widget-area .widget-subarea button'),
this.test.assert(this.cell_element_exists(button_index,
widget_button_selector),
'Widget button exists.');
this.test.assert(this.cell_element_function(index,
'.widget-area .widget-subarea button', 'html')=='Title',
this.test.assert(this.cell_element_function(button_index,
widget_button_selector, 'html')=='Title',
'Set button description.');
this.cell_element_function(index,
'.widget-area .widget-subarea button', 'click');
this.cell_element_function(button_index,
widget_button_selector, 'click');
});
this.wait_for_output(button_index, 1);

View File

@ -1,26 +1,28 @@
// Test widget float class
casper.notebook_test(function () {
index = this.append_cell(
'from IPython.html import widgets\n' +
'from IPython.display import display, clear_output\n' +
'print("Success")');
this.execute_cell_then(index);
var float_text = {};
float_text.query = '.widget-area .widget-subarea .my-second-float-text input';
float_text.index = this.append_cell(
'from IPython.html import widgets\n' +
'from IPython.display import display, clear_output\n' +
'float_widget = widgets.FloatText()\n' +
'display(float_widget)\n' +
'float_widget._dom_classes = ["my-second-float-text"]\n' +
'print(float_widget.model_id)\n');
this.execute_cell_then(float_text.index, function(index){
float_text.model_id = this.get_output_cell(index).text.trim();
this.test.assert(this.cell_element_exists(index,
});
// Wait for the widget to actually display.
this.wait_for_element(float_text.index, float_text.query);
// Continue with the tests
this.then(function(){
this.test.assert(this.cell_element_exists(float_text.index,
'.widget-area .widget-subarea'),
'Widget subarea exists.');
this.test.assert(this.cell_element_exists(index, float_text.query),
this.test.assert(this.cell_element_exists(float_text.index, float_text.query),
'Widget float textbox exists.');
this.cell_element_function(float_text.index, float_text.query, 'val', ['']);
@ -64,18 +66,23 @@ casper.notebook_test(function () {
'[display(floatrange[i]) for i in range(2)]\n' +
'print("Success")\n');
this.execute_cell_then(slider.index, function(index){
this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
'Create float range cell executed with correct output.');
});
this.test.assert(this.cell_element_exists(index,
// Wait for the widgets to actually display.
this.wait_for_element(slider.index, slider.query);
this.wait_for_element(slider.index, float_text_query);
this.then(function(){
this.test.assert(this.cell_element_exists(slider.index,
'.widget-area .widget-subarea'),
'Widget subarea exists.');
this.test.assert(this.cell_element_exists(index, slider.query),
this.test.assert(this.cell_element_exists(slider.index, slider.query),
'Widget slider exists.');
this.test.assert(this.cell_element_exists(index, float_text_query),
this.test.assert(this.cell_element_exists(slider.index, float_text_query),
'Widget float textbox exists.');
});

View File

@ -26,19 +26,23 @@ casper.notebook_test(function () {
'display(image)\n' +
'print("Success")\n');
this.execute_cell_then(image_index, function(index){
this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
'Create image executed with correct output.');
});
this.test.assert(this.cell_element_exists(index,
// Wait for the widget to actually display.
var img_selector = '.widget-area .widget-subarea img';
this.wait_for_element(image_index, img_selector);
this.then(function(){
this.test.assert(this.cell_element_exists(image_index,
'.widget-area .widget-subarea'),
'Widget subarea exists.');
var img_sel = '.widget-area .widget-subarea img';
this.test.assert(this.cell_element_exists(index, img_sel), 'Image exists.');
this.test.assert(this.cell_element_exists(image_index, img_selector), 'Image exists.');
// Verify that the image's base64 data has made it into the DOM.
var img_src = this.cell_element_function(image_index, img_sel, 'attr', ['src']);
var img_src = this.cell_element_function(image_index, img_selector, 'attr', ['src']);
this.test.assert(img_src.indexOf(test_jpg) > -1, 'Image src data exists.');
});
});

View File

@ -1,26 +1,28 @@
// Test widget int class
casper.notebook_test(function () {
index = this.append_cell(
'from IPython.html import widgets\n' +
'from IPython.display import display, clear_output\n' +
'print("Success")');
this.execute_cell_then(index);
var int_text = {};
int_text.query = '.widget-area .widget-subarea .my-second-int-text input';
int_text.index = this.append_cell(
'from IPython.html import widgets\n' +
'from IPython.display import display, clear_output\n' +
'int_widget = widgets.IntText()\n' +
'display(int_widget)\n' +
'int_widget._dom_classes = ["my-second-int-text"]\n' +
'print(int_widget.model_id)\n');
this.execute_cell_then(int_text.index, function(index){
int_text.model_id = this.get_output_cell(index).text.trim();
this.test.assert(this.cell_element_exists(index,
});
// Wait for the widget to actually display.
this.wait_for_element(int_text.index, int_text.query);
// Continue with the tests.
this.then(function() {
this.test.assert(this.cell_element_exists(int_text.index,
'.widget-area .widget-subarea'),
'Widget subarea exists.');
this.test.assert(this.cell_element_exists(index, int_text.query),
this.test.assert(this.cell_element_exists(int_text.index, int_text.query),
'Widget int textbox exists.');
this.cell_element_function(int_text.index, int_text.query, 'val', ['']);
@ -54,13 +56,6 @@ casper.notebook_test(function () {
this.test.assertEquals(this.get_output_cell(index).text, '12\n',
'Invald int textbox value caught and filtered.');
});
index = this.append_cell(
'from IPython.html import widgets\n' +
'from IPython.display import display, clear_output\n' +
'print("Success")');
this.execute_cell_then(index);
var slider_query = '.widget-area .widget-subarea .slider';
var int_text2 = {};
@ -73,15 +68,22 @@ casper.notebook_test(function () {
'print(intrange[0].model_id)\n');
this.execute_cell_then(int_text2.index, function(index){
int_text2.model_id = this.get_output_cell(index).text.trim();
});
this.test.assert(this.cell_element_exists(index,
// Wait for the widgets to actually display.
this.wait_for_element(int_text2.index, int_text2.query);
this.wait_for_element(int_text2.index, slider_query);
// Continue with the tests.
this.then(function(){
this.test.assert(this.cell_element_exists(int_text2.index,
'.widget-area .widget-subarea'),
'Widget subarea exists.');
this.test.assert(this.cell_element_exists(index, slider_query),
this.test.assert(this.cell_element_exists(int_text2.index, slider_query),
'Widget slider exists.');
this.test.assert(this.cell_element_exists(index, int_text2.query),
this.test.assert(this.cell_element_exists(int_text2.index, int_text2.query),
'Widget int textbox exists.');
});

View File

@ -58,21 +58,30 @@ casper.notebook_test(function () {
this.execute_cell_then(selection_index, function(index){
this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
'Create selection cell executed with correct output.');
});
this.test.assert(this.cell_element_exists(index,
// Wait for the widgets to actually display.
this.wait_for_element(selection_index, combo_selector);
this.wait_for_element(selection_index, multibtn_selector);
this.wait_for_element(selection_index, radio_selector);
this.wait_for_element(selection_index, list_selector);
// Continue with the tests.
this.then(function() {
this.test.assert(this.cell_element_exists(selection_index,
'.widget-area .widget-subarea'),
'Widget subarea exists.');
this.test.assert(this.cell_element_exists(index, combo_selector),
this.test.assert(this.cell_element_exists(selection_index, combo_selector),
'Widget combobox exists.');
this.test.assert(this.cell_element_exists(index, multibtn_selector),
this.test.assert(this.cell_element_exists(selection_index, multibtn_selector),
'Widget multibutton exists.');
this.test.assert(this.cell_element_exists(index, radio_selector),
this.test.assert(this.cell_element_exists(selection_index, radio_selector),
'Widget radio buttons exists.');
this.test.assert(this.cell_element_exists(index, list_selector),
this.test.assert(this.cell_element_exists(selection_index, list_selector),
'Widget list exists.');
// Verify that no items are selected.

View File

@ -18,20 +18,22 @@ casper.notebook_test(function () {
'multicontainer.selected_index = 0\n' +
'print("Success")\n');
this.execute_cell_then(multicontainer1_index, function(index){
this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
'Create multicontainer cell executed with correct output. (1)');
});
this.test.assert(this.cell_element_exists(index,
// Wait for the widget to actually display.
this.wait_for_element(multicontainer1_index, multicontainer1_query);
// Continue with the tests.
this.then(function() {
this.test.assert(this.cell_element_exists(multicontainer1_index,
'.widget-area .widget-subarea'),
'Widget subarea exists.');
this.test.assert(this.cell_element_exists(index, multicontainer1_query),
this.test.assert(this.cell_element_exists(multicontainer1_index, multicontainer1_query),
'Widget tab list exists.');
this.test.assert(this.cell_element_exists(index, multicontainer1_query),
'First widget tab list exists.');
// JQuery selector is 1 based
this.click(multicontainer1_query + ' li:nth-child(2) a');
});
@ -74,23 +76,28 @@ casper.notebook_test(function () {
'multicontainer.selected_index = 0\n' +
'print("Success")\n');
this.execute_cell_then(multicontainer2_index, function(index){
this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
'Create multicontainer cell executed with correct output. (2)');
});
this.test.assert(this.cell_element_exists(index,
// Wait for the widget to actually display.
this.wait_for_element(multicontainer2_index, multicontainer2_query);
// Continue with the tests.
this.then(function() {
this.test.assert(this.cell_element_exists(multicontainer2_index,
'.widget-area .widget-subarea'),
'Widget subarea exists.');
this.test.assert(this.cell_element_exists(index, multicontainer2_query),
this.test.assert(this.cell_element_exists(multicontainer2_index, multicontainer2_query),
'Widget accordion exists.');
this.test.assert(this.cell_element_exists(index, multicontainer2_query +
this.test.assert(this.cell_element_exists(multicontainer2_index, multicontainer2_query +
' .panel:nth-child(1) .panel-collapse'),
'First accordion page exists.');
// JQuery selector is 1 based
this.test.assert(this.cell_element_function(index, multicontainer2_query +
this.test.assert(this.cell_element_function(multicontainer2_index, multicontainer2_query +
' .panel.panel-default:nth-child(3) .panel-heading .accordion-toggle',
'html')=='good', 'Accordion page title set (before display).');

View File

@ -1,12 +1,8 @@
// Test widget string class
casper.notebook_test(function () {
index = this.append_cell(
var string_index = this.append_cell(
'from IPython.html import widgets\n' +
'from IPython.display import display, clear_output\n' +
'print("Success")');
this.execute_cell_then(index);
var string_index = this.append_cell(
'string_widget = [widgets.Text(value = "xyz", placeholder = "abc"),\n' +
' widgets.Textarea(value = "xyz", placeholder = "def"),\n' +
' widgets.HTML(value = "xyz"),\n' +
@ -14,40 +10,50 @@ casper.notebook_test(function () {
'[display(widget) for widget in string_widget]\n'+
'print("Success")');
this.execute_cell_then(string_index, function(index){
this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
'Create string widget cell executed with correct output.');
});
this.test.assert(this.cell_element_exists(index,
// Wait for the widget to actually display.
var textbox_selector = '.widget-area .widget-subarea .widget-hbox input[type=text]';
var textarea_selector = '.widget-area .widget-subarea .widget-hbox textarea';
var latex_selector = '.widget-area .widget-subarea div span.MathJax_Preview';
this.wait_for_element(string_index, textbox_selector);
this.wait_for_element(string_index, textarea_selector);
this.wait_for_element(string_index, latex_selector);
// Continue with the tests.
this.then(function(){
this.test.assert(this.cell_element_exists(string_index,
'.widget-area .widget-subarea'),
'Widget subarea exists.');
this.test.assert(this.cell_element_exists(index,
'.widget-area .widget-subarea .widget-hbox input[type=text]'),
this.test.assert(this.cell_element_exists(string_index,
textbox_selector),
'Textbox exists.');
this.test.assert(this.cell_element_exists(index,
'.widget-area .widget-subarea .widget-hbox textarea'),
this.test.assert(this.cell_element_exists(string_index,
textarea_selector),
'Textarea exists.');
this.test.assert(this.cell_element_function(index,
'.widget-area .widget-subarea .widget-hbox textarea', 'val')=='xyz',
this.test.assert(this.cell_element_function(string_index,
textarea_selector, 'val')=='xyz',
'Python set textarea value.');
this.test.assert(this.cell_element_function(index,
'.widget-area .widget-subarea .widget-hbox input[type=text]', 'val')=='xyz',
this.test.assert(this.cell_element_function(string_index,
textbox_selector, 'val')=='xyz',
'Python set textbox value.');
this.test.assert(this.cell_element_exists(string_index,
'.widget-area .widget-subarea div span.MathJax_Preview'),
latex_selector),
'MathJax parsed the LaTeX successfully.');
this.test.assert(this.cell_element_function(index,
'.widget-area .widget-subarea .widget-hbox textarea', 'attr', ['placeholder'])=='def',
this.test.assert(this.cell_element_function(string_index,
textarea_selector, 'attr', ['placeholder'])=='def',
'Python set textarea placeholder.');
this.test.assert(this.cell_element_function(index,
'.widget-area .widget-subarea .widget-hbox input[type=text]', 'attr', ['placeholder'])=='abc',
this.test.assert(this.cell_element_function(string_index,
textbox_selector, 'attr', ['placeholder'])=='abc',
'Python set textbox placehoder.');
});
});