##// END OF EJS Templates
Write tests for custom serialization
Jason Grout -
Show More
@@ -1,832 +1,845
1 1 //
2 2 // Utility functions for the HTML notebook's CasperJS tests.
3 3 //
4 4 casper.get_notebook_server = function () {
5 5 // Get the URL of a notebook server on which to run tests.
6 6 var port = casper.cli.get("port");
7 7 port = (typeof port === 'undefined') ? '8888' : port;
8 8 return casper.cli.get("url") || ('http://127.0.0.1:' + port);
9 9 };
10 10
11 11 casper.open_new_notebook = function () {
12 12 // Create and open a new notebook.
13 13 var baseUrl = this.get_notebook_server();
14 14 this.start(baseUrl);
15 15 this.waitFor(this.page_loaded);
16 16 this.waitForSelector('#kernel-python2 a, #kernel-python3 a');
17 17 this.thenClick('#kernel-python2 a, #kernel-python3 a');
18 18
19 19 this.waitForPopup('');
20 20
21 21 this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
22 22 this.then(function () {
23 23 this.open(this.popups[0].url);
24 24 });
25 25 this.waitFor(this.page_loaded);
26 26
27 27 // Hook the log and error methods of the console, forcing them to
28 28 // serialize their arguments before printing. This allows the
29 29 // Objects to cross into the phantom/slimer regime for display.
30 30 this.thenEvaluate(function(){
31 31 var serialize_arguments = function(f, context) {
32 32 return function() {
33 33 var pretty_arguments = [];
34 34 for (var i = 0; i < arguments.length; i++) {
35 35 var value = arguments[i];
36 36 if (value instanceof Object) {
37 37 var name = value.name || 'Object';
38 38 // Print a JSON string representation of the object.
39 39 // If we don't do this, [Object object] gets printed
40 40 // by casper, which is useless. The long regular
41 41 // expression reduces the verbosity of the JSON.
42 42 pretty_arguments.push(name + ' {' + JSON.stringify(value, null, ' ')
43 43 .replace(/(\s+)?({)?(\s+)?(}(\s+)?,?)?(\s+)?(\s+)?\n/g, '\n')
44 44 .replace(/\n(\s+)?\n/g, '\n'));
45 45 } else {
46 46 pretty_arguments.push(value);
47 47 }
48 48 }
49 49 f.apply(context, pretty_arguments);
50 50 };
51 51 };
52 52 console.log = serialize_arguments(console.log, console);
53 53 console.error = serialize_arguments(console.error, console);
54 54 });
55 55
56 56 // Make sure the kernel has started
57 57 this.waitFor(this.kernel_running);
58 58 // track the IPython busy/idle state
59 59 this.thenEvaluate(function () {
60 60 require(['base/js/namespace', 'base/js/events'], function (IPython, events) {
61 61
62 62 events.on('kernel_idle.Kernel',function () {
63 63 IPython._status = 'idle';
64 64 });
65 65 events.on('kernel_busy.Kernel',function () {
66 66 IPython._status = 'busy';
67 67 });
68 68 });
69 69 });
70 70
71 71 // Because of the asynchronous nature of SlimerJS (Gecko), we need to make
72 72 // sure the notebook has actually been loaded into the IPython namespace
73 73 // before running any tests.
74 74 this.waitFor(function() {
75 75 return this.evaluate(function () {
76 76 return IPython.notebook;
77 77 });
78 78 });
79 79 };
80 80
81 81 casper.page_loaded = function() {
82 82 // Return whether or not the kernel is running.
83 83 return this.evaluate(function() {
84 84 return typeof IPython !== "undefined" &&
85 85 IPython.page !== undefined;
86 86 });
87 87 };
88 88
89 89 casper.kernel_running = function() {
90 90 // Return whether or not the kernel is running.
91 91 return this.evaluate(function() {
92 92 return IPython &&
93 93 IPython.notebook &&
94 94 IPython.notebook.kernel &&
95 95 IPython.notebook.kernel.is_connected();
96 96 });
97 97 };
98 98
99 99 casper.kernel_disconnected = function() {
100 100 return this.evaluate(function() {
101 101 return IPython.notebook.kernel.is_fully_disconnected();
102 102 });
103 103 };
104 104
105 105 casper.wait_for_kernel_ready = function () {
106 106 this.waitFor(this.kernel_running);
107 107 this.thenEvaluate(function () {
108 108 IPython._kernel_ready = false;
109 109 IPython.notebook.kernel.kernel_info(
110 110 function () {
111 111 IPython._kernel_ready = true;
112 112 });
113 113 });
114 114 this.waitFor(function () {
115 115 return this.evaluate(function () {
116 116 return IPython._kernel_ready;
117 117 });
118 118 });
119 119 };
120 120
121 121 casper.shutdown_current_kernel = function () {
122 122 // Shut down the current notebook's kernel.
123 123 this.thenEvaluate(function() {
124 124 IPython.notebook.session.delete();
125 125 });
126 126 // We close the page right after this so we need to give it time to complete.
127 127 this.wait(1000);
128 128 };
129 129
130 130 casper.delete_current_notebook = function () {
131 131 // Delete created notebook.
132 132
133 133 // For some unknown reason, this doesn't work?!?
134 134 this.thenEvaluate(function() {
135 135 IPython.notebook.delete();
136 136 });
137 137 };
138 138
139 139 casper.wait_for_busy = function () {
140 140 // Waits for the notebook to enter a busy state.
141 141 this.waitFor(function () {
142 142 return this.evaluate(function () {
143 143 return IPython._status == 'busy';
144 144 });
145 145 });
146 146 };
147 147
148 148 casper.wait_for_idle = function () {
149 149 // Waits for the notebook to idle.
150 150 this.waitFor(function () {
151 151 return this.evaluate(function () {
152 152 return IPython._status == 'idle';
153 153 });
154 154 });
155 155 };
156 156
157 157 casper.wait_for_output = function (cell_num, out_num) {
158 158 // wait for the nth output in a given cell
159 159 this.wait_for_idle();
160 160 out_num = out_num || 0;
161 161 this.then(function() {
162 162 this.waitFor(function (c, o) {
163 163 return this.evaluate(function get_output(c, o) {
164 164 var cell = IPython.notebook.get_cell(c);
165 165 return cell.output_area.outputs.length > o;
166 166 },
167 167 // pass parameter from the test suite js to the browser code js
168 168 {c : cell_num, o : out_num});
169 169 });
170 170 },
171 171 function then() { },
172 172 function timeout() {
173 173 this.echo("wait_for_output timed out!");
174 174 });
175 175 };
176 176
177 177 casper.wait_for_widget = function (widget_info) {
178 178 // wait for a widget msg que to reach 0
179 179 //
180 180 // Parameters
181 181 // ----------
182 182 // widget_info : object
183 183 // Object which contains info related to the widget. The model_id property
184 184 // is used to identify the widget.
185 185
186 186 // Clear the results of a previous query, if they exist. Make sure a
187 187 // dictionary exists to store the async results in.
188 188 this.thenEvaluate(function(model_id) {
189 189 if (window.pending_msgs === undefined) {
190 190 window.pending_msgs = {};
191 191 } else {
192 192 window.pending_msgs[model_id] = -1;
193 193 }
194 194 }, {model_id: widget_info.model_id});
195 195
196 196 // Wait for the pending messages to be 0.
197 197 this.waitFor(function () {
198 198 var pending = this.evaluate(function (model_id) {
199 199
200 200 // Get the model. Once the model is had, store it's pending_msgs
201 201 // count in the window's dictionary.
202 202 IPython.notebook.kernel.widget_manager.get_model(model_id)
203 203 .then(function(model) {
204 204 window.pending_msgs[model_id] = model.pending_msgs;
205 205 });
206 206
207 207 // Return the pending_msgs result.
208 208 return window.pending_msgs[model_id];
209 209 }, {model_id: widget_info.model_id});
210 210
211 211 if (pending === 0) {
212 212 return true;
213 213 } else {
214 214 return false;
215 215 }
216 216 });
217 217 };
218 218
219 219 casper.get_output_cell = function (cell_num, out_num) {
220 220 // return an output of a given cell
221 221 out_num = out_num || 0;
222 222 var result = casper.evaluate(function (c, o) {
223 223 var cell = IPython.notebook.get_cell(c);
224 224 return cell.output_area.outputs[o];
225 225 },
226 226 {c : cell_num, o : out_num});
227 227 if (!result) {
228 228 var num_outputs = casper.evaluate(function (c) {
229 229 var cell = IPython.notebook.get_cell(c);
230 230 return cell.output_area.outputs.length;
231 231 },
232 232 {c : cell_num});
233 233 this.test.assertTrue(false,
234 234 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
235 235 );
236 236 } else {
237 237 return result;
238 238 }
239 239 };
240 240
241 241 casper.get_cells_length = function () {
242 242 // return the number of cells in the notebook
243 243 var result = casper.evaluate(function () {
244 244 return IPython.notebook.get_cells().length;
245 245 });
246 246 return result;
247 247 };
248 248
249 249 casper.set_cell_text = function(index, text){
250 250 // Set the text content of a cell.
251 251 this.evaluate(function (index, text) {
252 252 var cell = IPython.notebook.get_cell(index);
253 253 cell.set_text(text);
254 254 }, index, text);
255 255 };
256 256
257 257 casper.get_cell_text = function(index){
258 258 // Get the text content of a cell.
259 259 return this.evaluate(function (index) {
260 260 var cell = IPython.notebook.get_cell(index);
261 261 return cell.get_text();
262 262 }, index);
263 263 };
264 264
265 265 casper.insert_cell_at_bottom = function(cell_type){
266 266 // Inserts a cell at the bottom of the notebook
267 267 // Returns the new cell's index.
268 268 return this.evaluate(function (cell_type) {
269 269 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
270 270 return IPython.notebook.find_cell_index(cell);
271 271 }, cell_type);
272 272 };
273 273
274 274 casper.append_cell = function(text, cell_type) {
275 275 // Insert a cell at the bottom of the notebook and set the cells text.
276 276 // Returns the new cell's index.
277 277 var index = this.insert_cell_at_bottom(cell_type);
278 278 if (text !== undefined) {
279 279 this.set_cell_text(index, text);
280 280 }
281 281 return index;
282 282 };
283 283
284 284 casper.execute_cell = function(index, expect_failure){
285 285 // Asynchronously executes a cell by index.
286 286 // Returns the cell's index.
287 287
288 288 if (expect_failure === undefined) expect_failure = false;
289 289 var that = this;
290 290 this.then(function(){
291 291 that.evaluate(function (index) {
292 292 var cell = IPython.notebook.get_cell(index);
293 293 cell.execute();
294 294 }, index);
295 295 });
296 296 this.wait_for_idle();
297 297
298 298 this.then(function () {
299 299 var error = that.evaluate(function (index) {
300 300 var cell = IPython.notebook.get_cell(index);
301 301 var outputs = cell.output_area.outputs;
302 302 for (var i = 0; i < outputs.length; i++) {
303 303 if (outputs[i].output_type == 'error') {
304 304 return outputs[i];
305 305 }
306 306 }
307 307 return false;
308 308 }, index);
309 309 if (error === null) {
310 310 this.test.fail("Failed to check for error output");
311 311 }
312 312 if (expect_failure && error === false) {
313 313 this.test.fail("Expected error while running cell");
314 314 } else if (!expect_failure && error !== false) {
315 315 this.test.fail("Error running cell:\n" + error.traceback.join('\n'));
316 316 }
317 317 });
318 318 return index;
319 319 };
320 320
321 321 casper.execute_cell_then = function(index, then_callback, expect_failure) {
322 322 // Synchronously executes a cell by index.
323 323 // Optionally accepts a then_callback parameter. then_callback will get called
324 324 // when the cell has finished executing.
325 325 // Returns the cell's index.
326 326 var return_val = this.execute_cell(index, expect_failure);
327 327
328 328 this.wait_for_idle();
329 329
330 330 var that = this;
331 331 this.then(function(){
332 332 if (then_callback!==undefined) {
333 333 then_callback.apply(that, [index]);
334 334 }
335 335 });
336 336
337 337 return return_val;
338 338 };
339 339
340 casper.append_cell_execute_then = function(text, then_callback, expect_failure) {
341 // Append a code cell and execute it, optionally calling a then_callback
342 var c = this.append_cell(text);
343 return this.execute_cell_then(c, then_callback, expect_failure);
344 };
345
346 casper.assert_output_equals = function(text, output_text, message) {
347 // Append a code cell with the text, then assert the output is equal to output_text
348 this.append_cell_execute_then(text, function(index) {
349 this.test.assertEquals(this.get_output_cell(index).text.trim(), output_text, message);
350 });
351 };
352
340 353 casper.wait_for_element = function(index, selector){
341 354 // Utility function that allows us to easily wait for an element
342 355 // within a cell. Uses JQuery selector to look for the element.
343 356 var that = this;
344 357 this.waitFor(function() {
345 358 return that.cell_element_exists(index, selector);
346 359 });
347 360 };
348 361
349 362 casper.cell_element_exists = function(index, selector){
350 363 // Utility function that allows us to easily check if an element exists
351 364 // within a cell. Uses JQuery selector to look for the element.
352 365 return casper.evaluate(function (index, selector) {
353 366 var $cell = IPython.notebook.get_cell(index).element;
354 367 return $cell.find(selector).length > 0;
355 368 }, index, selector);
356 369 };
357 370
358 371 casper.cell_element_function = function(index, selector, function_name, function_args){
359 372 // Utility function that allows us to execute a jQuery function on an
360 373 // element within a cell.
361 374 return casper.evaluate(function (index, selector, function_name, function_args) {
362 375 var $cell = IPython.notebook.get_cell(index).element;
363 376 var $el = $cell.find(selector);
364 377 return $el[function_name].apply($el, function_args);
365 378 }, index, selector, function_name, function_args);
366 379 };
367 380
368 381 casper.validate_notebook_state = function(message, mode, cell_index) {
369 382 // Validate the entire dual mode state of the notebook. Make sure no more than
370 383 // one cell is selected, focused, in edit mode, etc...
371 384
372 385 // General tests.
373 386 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
374 387 message + '; keyboard and notebook modes match');
375 388 // Is the selected cell the only cell that is selected?
376 389 if (cell_index!==undefined) {
377 390 this.test.assert(this.is_only_cell_selected(cell_index),
378 391 message + '; cell ' + cell_index + ' is the only cell selected');
379 392 }
380 393
381 394 // Mode specific tests.
382 395 if (mode==='command') {
383 396 // Are the notebook and keyboard manager in command mode?
384 397 this.test.assertEquals(this.get_keyboard_mode(), 'command',
385 398 message + '; in command mode');
386 399 // Make sure there isn't a single cell in edit mode.
387 400 this.test.assert(this.is_only_cell_edit(null),
388 401 message + '; all cells in command mode');
389 402 this.test.assert(this.is_cell_editor_focused(null),
390 403 message + '; no cell editors are focused while in command mode');
391 404
392 405 } else if (mode==='edit') {
393 406 // Are the notebook and keyboard manager in edit mode?
394 407 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
395 408 message + '; in edit mode');
396 409 if (cell_index!==undefined) {
397 410 // Is the specified cell the only cell in edit mode?
398 411 this.test.assert(this.is_only_cell_edit(cell_index),
399 412 message + '; cell ' + cell_index + ' is the only cell in edit mode '+ this.cells_modes());
400 413 // Is the specified cell the only cell with a focused code mirror?
401 414 this.test.assert(this.is_cell_editor_focused(cell_index),
402 415 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
403 416 }
404 417
405 418 } else {
406 419 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
407 420 }
408 421 };
409 422
410 423 casper.select_cell = function(index) {
411 424 // Select a cell in the notebook.
412 425 this.evaluate(function (i) {
413 426 IPython.notebook.select(i);
414 427 }, {i: index});
415 428 };
416 429
417 430 casper.click_cell_editor = function(index) {
418 431 // Emulate a click on a cell's editor.
419 432
420 433 // Code Mirror does not play nicely with emulated brower events.
421 434 // Instead of trying to emulate a click, here we run code similar to
422 435 // the code used in Code Mirror that handles the mousedown event on a
423 436 // region of codemirror that the user can focus.
424 437 this.evaluate(function (i) {
425 438 var cm = IPython.notebook.get_cell(i).code_mirror;
426 439 if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input)){
427 440 cm.display.input.focus();
428 441 }
429 442 }, {i: index});
430 443 };
431 444
432 445 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
433 446 // Set the Code Mirror instance cursor's location.
434 447 this.evaluate(function (i, l, c) {
435 448 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
436 449 }, {i: index, l: line_index, c: char_index});
437 450 };
438 451
439 452 casper.focus_notebook = function() {
440 453 // Focus the notebook div.
441 454 this.evaluate(function (){
442 455 $('#notebook').focus();
443 456 }, {});
444 457 };
445 458
446 459 casper.trigger_keydown = function() {
447 460 // Emulate a keydown in the notebook.
448 461 for (var i = 0; i < arguments.length; i++) {
449 462 this.evaluate(function (k) {
450 463 var element = $(document);
451 464 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
452 465 element.trigger(event);
453 466 }, {k: arguments[i]});
454 467 }
455 468 };
456 469
457 470 casper.get_keyboard_mode = function() {
458 471 // Get the mode of the keyboard manager.
459 472 return this.evaluate(function() {
460 473 return IPython.keyboard_manager.mode;
461 474 }, {});
462 475 };
463 476
464 477 casper.get_notebook_mode = function() {
465 478 // Get the mode of the notebook.
466 479 return this.evaluate(function() {
467 480 return IPython.notebook.mode;
468 481 }, {});
469 482 };
470 483
471 484 casper.get_cell = function(index) {
472 485 // Get a single cell.
473 486 //
474 487 // Note: Handles to DOM elements stored in the cell will be useless once in
475 488 // CasperJS context.
476 489 return this.evaluate(function(i) {
477 490 var cell = IPython.notebook.get_cell(i);
478 491 if (cell) {
479 492 return cell;
480 493 }
481 494 return null;
482 495 }, {i : index});
483 496 };
484 497
485 498 casper.is_cell_editor_focused = function(index) {
486 499 // Make sure a cell's editor is the only editor focused on the page.
487 500 return this.evaluate(function(i) {
488 501 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
489 502 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
490 503 if (i === null) {
491 504 return focused_textarea.length === 0;
492 505 } else {
493 506 var cell = IPython.notebook.get_cell(i);
494 507 if (cell) {
495 508 return cell.code_mirror.getInputField() == focused_textarea[0];
496 509 }
497 510 }
498 511 return false;
499 512 }, {i : index});
500 513 };
501 514
502 515 casper.is_only_cell_selected = function(index) {
503 516 // Check if a cell is the only cell selected.
504 517 // Pass null as the index to check if no cells are selected.
505 518 return this.is_only_cell_on(index, 'selected', 'unselected');
506 519 };
507 520
508 521 casper.is_only_cell_edit = function(index) {
509 522 // Check if a cell is the only cell in edit mode.
510 523 // Pass null as the index to check if all of the cells are in command mode.
511 524 var cells_length = this.get_cells_length();
512 525 for (var j = 0; j < cells_length; j++) {
513 526 if (j === index) {
514 527 if (!this.cell_mode_is(j, 'edit')) {
515 528 return false;
516 529 }
517 530 } else {
518 531 if (this.cell_mode_is(j, 'edit')) {
519 532 return false;
520 533 }
521 534 }
522 535 }
523 536 return true;
524 537 };
525 538
526 539 casper.is_only_cell_on = function(i, on_class, off_class) {
527 540 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
528 541 // All of the other cells are checked for the `off_class` DOM class.
529 542 // Pass null as the index to check if all of the cells have the `off_class`.
530 543 var cells_length = this.get_cells_length();
531 544 for (var j = 0; j < cells_length; j++) {
532 545 if (j === i) {
533 546 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
534 547 return false;
535 548 }
536 549 } else {
537 550 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
538 551 return false;
539 552 }
540 553 }
541 554 }
542 555 return true;
543 556 };
544 557
545 558 casper.cells_modes = function(){
546 559 return this.evaluate(function(){
547 560 return IPython.notebook.get_cells().map(function(x,c){return x.mode})
548 561 }, {});
549 562 };
550 563
551 564 casper.cell_mode_is = function(index, mode) {
552 565 // Check if a cell is in a specific mode
553 566 return this.evaluate(function(i, m) {
554 567 var cell = IPython.notebook.get_cell(i);
555 568 if (cell) {
556 569 return cell.mode === m;
557 570 }
558 571 return false;
559 572 }, {i : index, m: mode});
560 573 };
561 574
562 575
563 576 casper.cell_has_class = function(index, classes) {
564 577 // Check if a cell has a class.
565 578 return this.evaluate(function(i, c) {
566 579 var cell = IPython.notebook.get_cell(i);
567 580 if (cell) {
568 581 return cell.element.hasClass(c);
569 582 }
570 583 return false;
571 584 }, {i : index, c: classes});
572 585 };
573 586
574 587 casper.is_cell_rendered = function (index) {
575 588 return this.evaluate(function(i) {
576 589 return !!IPython.notebook.get_cell(i).rendered;
577 590 }, {i:index});
578 591 };
579 592
580 593 casper.assert_colors_equal = function (hex_color, local_color, msg) {
581 594 // Tests to see if two colors are equal.
582 595 //
583 596 // Parameters
584 597 // hex_color: string
585 598 // Hexadecimal color code, with or without preceeding hash character.
586 599 // local_color: string
587 600 // Local color representation. Can either be hexadecimal (default for
588 601 // phantom) or rgb (default for slimer).
589 602
590 603 // Remove parentheses, hashes, semi-colons, and space characters.
591 604 hex_color = hex_color.replace(/[\(\); #]/, '');
592 605 local_color = local_color.replace(/[\(\); #]/, '');
593 606
594 607 // If the local color is rgb, clean it up and replace
595 608 if (local_color.substr(0,3).toLowerCase() == 'rgb') {
596 609 var components = local_color.substr(3).split(',');
597 610 local_color = '';
598 611 for (var i = 0; i < components.length; i++) {
599 612 var part = parseInt(components[i]).toString(16);
600 613 while (part.length < 2) part = '0' + part;
601 614 local_color += part;
602 615 }
603 616 }
604 617
605 618 this.test.assertEquals(hex_color.toUpperCase(), local_color.toUpperCase(), msg);
606 619 };
607 620
608 621 casper.notebook_test = function(test) {
609 622 // Wrap a notebook test to reduce boilerplate.
610 623 this.open_new_notebook();
611 624
612 625 // Echo whether or not we are running this test using SlimerJS
613 626 if (this.evaluate(function(){
614 627 return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
615 628 })) {
616 629 console.log('This test is running in SlimerJS.');
617 630 this.slimerjs = true;
618 631 }
619 632
620 633 // Make sure to remove the onbeforeunload callback. This callback is
621 634 // responsible for the "Are you sure you want to quit?" type messages.
622 635 // PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
623 636 this.then(function(){
624 637 this.evaluate(function(){
625 638 window.onbeforeunload = function(){};
626 639 });
627 640 });
628 641
629 642 this.then(test);
630 643
631 644 // Kill the kernel and delete the notebook.
632 645 this.shutdown_current_kernel();
633 646 // This is still broken but shouldn't be a problem for now.
634 647 // this.delete_current_notebook();
635 648
636 649 // This is required to clean up the page we just finished with. If we don't call this
637 650 // casperjs will leak file descriptors of all the open WebSockets in that page. We
638 651 // have to set this.page=null so that next time casper.start runs, it will create a
639 652 // new page from scratch.
640 653 this.then(function () {
641 654 this.page.close();
642 655 this.page = null;
643 656 });
644 657
645 658 // Run the browser automation.
646 659 this.run(function() {
647 660 this.test.done();
648 661 });
649 662 };
650 663
651 664 casper.wait_for_dashboard = function () {
652 665 // Wait for the dashboard list to load.
653 666 casper.waitForSelector('.list_item');
654 667 };
655 668
656 669 casper.open_dashboard = function () {
657 670 // Start casper by opening the dashboard page.
658 671 var baseUrl = this.get_notebook_server();
659 672 this.start(baseUrl);
660 673 this.waitFor(this.page_loaded);
661 674 this.wait_for_dashboard();
662 675 };
663 676
664 677 casper.dashboard_test = function (test) {
665 678 // Open the dashboard page and run a test.
666 679 this.open_dashboard();
667 680 this.then(test);
668 681
669 682 this.then(function () {
670 683 this.page.close();
671 684 this.page = null;
672 685 });
673 686
674 687 // Run the browser automation.
675 688 this.run(function() {
676 689 this.test.done();
677 690 });
678 691 };
679 692
680 693 // note that this will only work for UNIQUE events -- if you want to
681 694 // listen for the same event twice, this will not work!
682 695 casper.event_test = function (name, events, action, timeout) {
683 696
684 697 // set up handlers to listen for each of the events
685 698 this.thenEvaluate(function (events) {
686 699 var make_handler = function (event) {
687 700 return function () {
688 701 IPython._events_triggered.push(event);
689 702 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
690 703 delete IPython._event_handlers[event];
691 704 };
692 705 };
693 706 IPython._event_handlers = {};
694 707 IPython._events_triggered = [];
695 708 for (var i=0; i < events.length; i++) {
696 709 IPython._event_handlers[events[i]] = make_handler(events[i]);
697 710 IPython.notebook.events.on(events[i], IPython._event_handlers[events[i]]);
698 711 }
699 712 }, [events]);
700 713
701 714 // execute the requested action
702 715 this.then(action);
703 716
704 717 // wait for all the events to be triggered
705 718 this.waitFor(function () {
706 719 return this.evaluate(function (events) {
707 720 return IPython._events_triggered.length >= events.length;
708 721 }, [events]);
709 722 }, undefined, undefined, timeout);
710 723
711 724 // test that the events were triggered in the proper order
712 725 this.then(function () {
713 726 var triggered = this.evaluate(function () {
714 727 return IPython._events_triggered;
715 728 });
716 729 var handlers = this.evaluate(function () {
717 730 return Object.keys(IPython._event_handlers);
718 731 });
719 732 this.test.assertEquals(triggered.length, events.length, name + ': ' + events.length + ' events were triggered');
720 733 this.test.assertEquals(handlers.length, 0, name + ': all handlers triggered');
721 734 for (var i=0; i < events.length; i++) {
722 735 this.test.assertEquals(triggered[i], events[i], name + ': ' + events[i] + ' was triggered');
723 736 }
724 737 });
725 738
726 739 // turn off any remaining event listeners
727 740 this.thenEvaluate(function () {
728 741 for (var event in IPython._event_handlers) {
729 742 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
730 743 delete IPython._event_handlers[event];
731 744 }
732 745 });
733 746 };
734 747
735 748 casper.options.waitTimeout=10000;
736 749 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
737 750 this.echo("Timeout for " + casper.get_notebook_server());
738 751 this.echo("Is the notebook server running?");
739 752 });
740 753
741 754 casper.print_log = function () {
742 755 // Pass `console.log` calls from page JS to casper.
743 756 this.on('remote.message', function(msg) {
744 757 this.echo('Remote message caught: ' + msg);
745 758 });
746 759 };
747 760
748 761 casper.on("page.error", function onError(msg, trace) {
749 762 // show errors in the browser
750 763 this.echo("Page Error");
751 764 this.echo(" Message: " + msg.split('\n').join('\n '));
752 765 this.echo(" Call stack:");
753 766 var local_path = this.get_notebook_server();
754 767 for (var i = 0; i < trace.length; i++) {
755 768 var frame = trace[i];
756 769 var file = frame.file;
757 770 // shorten common phantomjs evaluate url
758 771 // this will have a different value on slimerjs
759 772 if (file === "phantomjs://webpage.evaluate()") {
760 773 file = "evaluate";
761 774 }
762 775 // remove the version tag from the path
763 776 file = file.replace(/(\?v=[0-9abcdef]+)/, '');
764 777 // remove the local address from the beginning of the path
765 778 if (file.indexOf(local_path) === 0) {
766 779 file = file.substr(local_path.length);
767 780 }
768 781 var frame_text = (frame.function.length > 0) ? " in " + frame.function : "";
769 782 this.echo(" line " + frame.line + " of " + file + frame_text);
770 783 }
771 784 });
772 785
773 786
774 787 casper.capture_log = function () {
775 788 // show captured errors
776 789 var captured_log = [];
777 790 var seen_errors = 0;
778 791 this.on('remote.message', function(msg) {
779 792 captured_log.push(msg);
780 793 });
781 794
782 795 var that = this;
783 796 this.test.on("test.done", function (result) {
784 797 // test.done runs per-file,
785 798 // but suiteResults is per-suite (directory)
786 799 var current_errors;
787 800 if (this.suiteResults) {
788 801 // casper 1.1 has suiteResults
789 802 current_errors = this.suiteResults.countErrors() + this.suiteResults.countFailed();
790 803 } else {
791 804 // casper 1.0 has testResults instead
792 805 current_errors = this.testResults.failed;
793 806 }
794 807
795 808 if (current_errors > seen_errors && captured_log.length > 0) {
796 809 casper.echo("\nCaptured console.log:");
797 810 for (var i = 0; i < captured_log.length; i++) {
798 811 var output = String(captured_log[i]).split('\n');
799 812 for (var j = 0; j < output.length; j++) {
800 813 casper.echo(" " + output[j]);
801 814 }
802 815 }
803 816 }
804 817
805 818 seen_errors = current_errors;
806 819 captured_log = [];
807 820 });
808 821 };
809 822
810 823 casper.interact = function() {
811 824 // Start an interactive Javascript console.
812 825 var system = require('system');
813 826 system.stdout.writeLine('JS interactive console.');
814 827 system.stdout.writeLine('Type `exit` to quit.');
815 828
816 829 function read_line() {
817 830 system.stdout.writeLine('JS: ');
818 831 var line = system.stdin.readLine();
819 832 return line;
820 833 }
821 834
822 835 var input = read_line();
823 836 while (input.trim() != 'exit') {
824 837 var output = this.evaluate(function(code) {
825 838 return String(eval(code));
826 839 }, {code: input});
827 840 system.stdout.writeLine('\nOut: ' + output);
828 841 input = read_line();
829 842 }
830 843 };
831 844
832 845 casper.capture_log();
@@ -1,296 +1,297
1 1 var xor = function (a, b) {return !a ^ !b;};
2 2 var isArray = function (a) {
3 3 try {
4 4 return Object.toString.call(a) === "[object Array]" || Object.toString.call(a) === "[object RuntimeArray]";
5 5 } catch (e) {
6 6 return Array.isArray(a);
7 7 }
8 8 };
9 9 var recursive_compare = function(a, b) {
10 10 // Recursively compare two objects.
11 11 var same = true;
12 12 same = same && !xor(a instanceof Object || typeof a == 'object', b instanceof Object || typeof b == 'object');
13 13 same = same && !xor(isArray(a), isArray(b));
14 14
15 15 if (same) {
16 16 if (a instanceof Object) {
17 17 var key;
18 18 for (key in a) {
19 19 if (a.hasOwnProperty(key) && !recursive_compare(a[key], b[key])) {
20 20 same = false;
21 21 break;
22 22 }
23 23 }
24 24 for (key in b) {
25 25 if (b.hasOwnProperty(key) && !recursive_compare(a[key], b[key])) {
26 26 same = false;
27 27 break;
28 28 }
29 29 }
30 30 } else {
31 31 return a === b;
32 32 }
33 33 }
34 34
35 35 return same;
36 36 };
37 37
38 38 // Test the widget framework.
39 39 casper.notebook_test(function () {
40 40 var index;
41 41
42 42 index = this.append_cell(
43 43 ['from IPython.html import widgets',
44 44 'from IPython.display import display, clear_output',
45 45 'print("Success")'].join('\n'));
46 46 this.execute_cell_then(index);
47 47
48 48 this.then(function () {
49 49 // Test multi-set, single touch code. First create a custom widget.
50 50 this.thenEvaluate(function() {
51 51 var MultiSetView = IPython.DOMWidgetView.extend({
52 52 render: function(){
53 53 this.model.set('a', 1);
54 54 this.model.set('b', 2);
55 55 this.model.set('c', 3);
56 56 this.touch();
57 57 },
58 58 });
59 59 IPython.WidgetManager.register_widget_view('MultiSetView', MultiSetView);
60 60 }, {});
61 61 });
62 62
63 63 // Try creating the multiset widget, verify that sets the values correctly.
64 64 var multiset = {};
65 65 multiset.index = this.append_cell([
66 66 'from IPython.utils.traitlets import Unicode, CInt',
67 67 'class MultiSetWidget(widgets.Widget):',
68 68 ' _view_name = Unicode("MultiSetView", sync=True)',
69 69 ' a = CInt(0, sync=True)',
70 70 ' b = CInt(0, sync=True)',
71 71 ' c = CInt(0, sync=True)',
72 72 ' d = CInt(-1, sync=True)', // See if it sends a full state.
73 73 ' def set_state(self, sync_data):',
74 ' widgets.Widget.set_state(self, sync_data)'+
74 ' widgets.Widget.set_state(self, sync_data)',
75 75 ' self.d = len(sync_data)',
76 76 'multiset = MultiSetWidget()',
77 77 'display(multiset)',
78 78 'print(multiset.model_id)'].join('\n'));
79 79 this.execute_cell_then(multiset.index, function(index) {
80 80 multiset.model_id = this.get_output_cell(index).text.trim();
81 81 });
82 82
83 83 this.wait_for_widget(multiset);
84 84
85 85 index = this.append_cell(
86 86 'print("%d%d%d" % (multiset.a, multiset.b, multiset.c))');
87 87 this.execute_cell_then(index, function(index) {
88 88 this.test.assertEquals(this.get_output_cell(index).text.trim(), '123',
89 89 'Multiple model.set calls and one view.touch update state in back-end.');
90 90 });
91 91
92 92 index = this.append_cell(
93 93 'print("%d" % (multiset.d))');
94 94 this.execute_cell_then(index, function(index) {
95 95 this.test.assertEquals(this.get_output_cell(index).text.trim(), '3',
96 96 'Multiple model.set calls sent a partial state.');
97 97 });
98 98
99 99 var textbox = {};
100 100 throttle_index = this.append_cell([
101 101 'import time',
102 102 'textbox = widgets.Text()',
103 103 'display(textbox)',
104 104 'textbox._dom_classes = ["my-throttle-textbox"]',
105 105 'def handle_change(name, old, new):',
106 106 ' display(len(new))',
107 107 ' time.sleep(0.5)',
108 108 'textbox.on_trait_change(handle_change, "value")',
109 109 'print(textbox.model_id)'].join('\n'));
110 110 this.execute_cell_then(throttle_index, function(index){
111 111 textbox.model_id = this.get_output_cell(index).text.trim();
112 112
113 113 this.test.assert(this.cell_element_exists(index,
114 114 '.widget-area .widget-subarea'),
115 115 'Widget subarea exists.');
116 116
117 117 this.test.assert(this.cell_element_exists(index,
118 118 '.my-throttle-textbox'), 'Textbox exists.');
119 119
120 120 // Send 20 characters
121 121 this.sendKeys('.my-throttle-textbox input', '12345678901234567890');
122 122 });
123 123
124 124 this.wait_for_widget(textbox);
125 125
126 126 this.then(function () {
127 127 var outputs = this.evaluate(function(i) {
128 128 return IPython.notebook.get_cell(i).output_area.outputs;
129 129 }, {i : throttle_index});
130 130
131 131 // Only 4 outputs should have printed, but because of timing, sometimes
132 132 // 5 outputs will print. All we need to do is verify num outputs <= 5
133 133 // because that is much less than 20.
134 134 this.test.assert(outputs.length <= 5, 'Messages throttled.');
135 135
136 136 // We also need to verify that the last state sent was correct.
137 137 var last_state = outputs[outputs.length-1].data['text/plain'];
138 138 this.test.assertEquals(last_state, "20", "Last state sent when throttling.");
139 139 });
140 140
141 141
142 /* New Test
143
144
145 %%javascript
142 this.thenEvaluate(function() {
146 143 define('TestWidget', ['widgets/js/widget', 'base/js/utils'], function(widget, utils) {
147 144 var TestWidget = widget.DOMWidgetView.extend({
148 145 render: function () {
149 146 this.listenTo(this.model, 'msg:custom', this.handle_msg);
150 window.w = this;
151 console.log('data:', this.model.get('data'));
152 147 },
153 148 handle_msg: function(content, buffers) {
154 149 this.msg = [content, buffers];
155 150 }
156 151 });
157 152
158 153 var floatArray = {
159 154 deserialize: function (value, model) {
160 155 // DataView -> float64 typed array
161 156 return new Float64Array(value.buffer);
162 157 },
163 158 // serialization automatically handled by message buffers
164 159 };
165 160
166
167 161 var floatList = {
168 162 deserialize: function (value, model) {
169 163 // list of floats -> list of strings
170 164 return value.map(function(x) {return x.toString()});
171 165 },
172 166 serialize: function(value, model) {
173 167 // list of strings -> list of floats
174 168 return value.map(function(x) {return parseFloat(x);})
175 169 }
176 170 };
177 171 return {TestWidget: TestWidget, floatArray: floatArray, floatList: floatList};
178 172 });
173 });
179 174
175 var testwidget = {};
176 this.append_cell_execute_then([
177 'from IPython.html import widgets',
178 'from IPython.utils.traitlets import Unicode, Instance, List',
179 'from IPython.display import display',
180 'from array import array',
181 'def _array_to_memoryview(x):',
182 ' if x is None: return None, {}',
183 ' try:',
184 ' y = memoryview(x)',
185 ' except TypeError:',
186 " # in python 2, arrays don't support the new buffer protocol",
187 ' y = memoryview(buffer(x))',
188 " return y, {'serialization': ('floatArray', 'TestWidget')}",
189 'def _memoryview_to_array(x):',
190 " return array('d', x.tobytes())",
191 'arrays_binary = {',
192 " 'from_json': _memoryview_to_array,",
193 " 'to_json': _array_to_memoryview",
194 '}',
195 '',
196 'def _array_to_list(x):',
197 ' if x is None: return None, {}',
198 " return list(x), {'serialization': ('floatList', 'TestWidget')}",
199 'def _list_to_array(x):',
200 " return array('d',x)",
201 'arrays_list = {',
202 " 'from_json': _list_to_array,",
203 " 'to_json': _array_to_list",
204 '}',
205 '',
206 'class TestWidget(widgets.DOMWidget):',
207 " _view_module = Unicode('TestWidget', sync=True)",
208 " _view_name = Unicode('TestWidget', sync=True)",
209 ' array_binary = Instance(array, sync=True, **arrays_binary)',
210 ' array_list = Instance(array, sync=True, **arrays_list)',
211 ' msg = {}',
212 ' def __init__(self, **kwargs):',
213 ' super(widgets.DOMWidget, self).__init__(**kwargs)',
214 ' self.on_msg(self._msg)',
215 ' def _msg(self, _, content, buffers):',
216 ' self.msg = [content, buffers]',
217 'x=TestWidget()',
218 'display(x)',
219 'print x.model_id'].join('\n'), function(index){
220 testwidget.index = index;
221 testwidget.model_id = this.get_output_cell(index).text.trim();
222 });
223 this.wait_for_widget(testwidget);
180 224
181 --------------
182
183
184 from IPython.html import widgets
185 from IPython.utils.traitlets import Unicode, Instance, List
186 from IPython.display import display
187 from array import array
188 def _array_to_memoryview(x):
189 if x is None: return None, {}
190 try:
191 y = memoryview(x)
192 except TypeError:
193 # in python 2, arrays don't support the new buffer protocol
194 y = memoryview(buffer(x))
195 return y, {'serialization': ('floatArray', 'TestWidget')}
196
197 def _memoryview_to_array(x):
198 return array('d', x.tobytes())
199
200 arrays_binary = {
201 'from_json': _memoryview_to_array,
202 'to_json': _array_to_memoryview
203 }
204
205 def _array_to_list(x):
206 if x is None: return None, {}
207 return list(x), {'serialization': ('floatList', 'TestWidget')}
208
209 def _list_to_array(x):
210 return array('d',x)
211
212 arrays_list = {
213 'from_json': _list_to_array,
214 'to_json': _array_to_list
215 }
216
217
218 class TestWidget(widgets.DOMWidget):
219 _view_module = Unicode('TestWidget', sync=True)
220 _view_name = Unicode('TestWidget', sync=True)
221 array_binary = Instance(array, sync=True, **arrays_binary)
222 array_list = Instance(array, sync=True, **arrays_list)
223 def __init__(self, **kwargs):
224 super(widgets.DOMWidget, self).__init__(**kwargs)
225 self.on_msg(self._msg)
226 def _msg(self, _, content, buffers):
227 self.msg = [content, buffers]
228
229
230 ----------------
231
232 x=TestWidget()
233 display(x)
234 x.array_binary=array('d', [1.5,2.5,5])
235 print x.model_id
236
237 -----------------
238
239 %%javascript
240 console.log(w.model.get('array_binary'))
241 var z = w.model.get('array_binary')
242 z[0]*=3
243 z[1]*=3
244 z[2]*=3
245 // we set to null so that we recognize the change
246 // when we set data back to z
247 w.model.set('array_binary', null)
248 w.model.set('array_binary', z)
249 console.log(w.model.get('array_binary'))
250 w.touch()
251
252 ----------------
253 x.array_binary.tolist() == [4.5, 7.5, 15.0]
254 ----------------
255 225
256 x.array_list = array('d', [1.5, 2.0, 3.1])
257 ----------------
226 this.append_cell_execute_then('x.array_list = array("d", [1.5, 2.0, 3.1])');
227 this.wait_for_widget(testwidget);
228 this.then(function() {
229 var result = this.evaluate(function(index) {
230 var v = IPython.notebook.get_cell(index).widget_views[0];
231 var result = v.model.get('array_list');
232 var z = result.slice();
233 z[0]+="1234";
234 z[1]+="5678";
235 v.model.set('array_list', z);
236 v.touch();
237 return result;
238 }, testwidget.index);
239 this.test.assertEquals(result, ["1.5", "2", "3.1"], "JSON custom serializer kernel -> js");
240 });
258 241
259 %%javascript
260 console.log(w.model.get('array_list'))
261 var z = w.model.get('array_list')
262 z[0]+="1234"
263 z[1]+="5678"
242 this.assert_output_equals('print x.array_list.tolist() == [1.51234, 25678.0, 3.1]',
243 'True', 'JSON custom serializer js -> kernel');
244
245 if (this.slimerjs) {
246 this.append_cell_execute_then("x.array_binary=array('d', [1.5,2.5,5])", function() {
247 this.evaluate(function(index) {
248 var v = IPython.notebook.get_cell(index).widget_views[0];
249 var z = v.model.get('array_binary');
250 z[0]*=3;
251 z[1]*=3;
252 z[2]*=3;
264 253 // we set to null so that we recognize the change
265 254 // when we set data back to z
266 w.model.set('array_list', null)
267 w.model.set('array_list', z)
268 w.touch()
269
270 -----------------
271
272 x.array_list.tolist() == [1.51234, 25678.0, 3.1]
273
274 -------------------
275 x.send('some content', [memoryview(b'binarycontent'), memoryview('morecontent')])
276
277 -------------------
278
279 %%javascript
280 console.log(w.msg[0] === 'some content')
281 var d=new TextDecoder('utf-8')
282 console.log(d.decode(w.msg[1][0])==='binarycontent')
283 console.log(d.decode(w.msg[1][1])==='morecontent')
284 w.send('content back', [new Uint8Array([1,2,3,4]), new Float64Array([2.1828, 3.14159])])
285
286 --------------------
287
288 print x.msg[0] == 'content back'
289 print x.msg[1][0].tolist() == [1,2,3,4]
290 print array('d', x.msg[1][1].tobytes()).tolist() == [2.1828, 3.14159]
255 v.model.set('array_binary', null);
256 v.model.set('array_binary', z);
257 v.touch();
258 }, textwidget.index);
259 });
260 this.wait_for_widget(testwidget);
261 this.assert_output_equals('x.array_binary.tolist() == [4.5, 7.5, 15.0]',
262 'True\n', 'Binary custom serializer js -> kernel')
291 263
264 this.append_cell_execute_then('x.send("some content", [memoryview(b"binarycontent"), memoryview("morecontent")])');
265 this.wait_for_widget(testwidget);
292 266
293 */
267 this.then(function() {
268 var result = this.evaluate(function(index) {
269 var v = IPython.notebook.get_cell(index).widget_views[0];
270 var d = new TextDecoder('utf-8');
271 return {text: v.msg[0],
272 binary0: d.decode(v.msg[1][0]),
273 binary1: d.decode(v.msg[1][1])};
274 }, testwidget.index);
275 this.test.assertEquals(result, {text: 'some content',
276 binary0: 'binarycontent',
277 binary1: 'morecontent'},
278 "Binary widget messages kernel -> js");
279 });
294 280
281 this.then(function() {
282 this.evaluate(function(index) {
283 var v = IPython.notebook.get_cell(index).widget_views[0];
284 v.send('content back', [new Uint8Array([1,2,3,4]), new Float64Array([2.1828, 3.14159])])
285 }, testwidget.index);
286 });
287 this.wait_for_widget(testwidget);
288 this.assert_output_equals([
289 'all([x.msg[0] == "content back",',
290 ' x.msg[1][0].tolist() == [1,2,3,4],',
291 ' array("d", x.msg[1][1].tobytes()).tolist() == [2.1828, 3.14159]])'].join('\n'),
292 'True', 'Binary buffers message js -> kernel');
293 } else {
294 console.log("skipping binary websocket tests on phantomjs");
295 }
295 296
296 297 });
General Comments 0
You need to be logged in to leave comments. Login now