diff --git a/IPython/html/static/base/js/keyboard.js b/IPython/html/static/base/js/keyboard.js
index 8210ad7..56391e6 100644
--- a/IPython/html/static/base/js/keyboard.js
+++ b/IPython/html/static/base/js/keyboard.js
@@ -128,15 +128,6 @@ IPython.keyboard = (function (IPython) {
return shortcut;
};
- var trigger_keydown = function (shortcut, element) {
- // Trigger shortcut keydown on an element
- element = element || document;
- element = $(element);
- var event = shortcut_to_event(shortcut, 'keydown');
- element.trigger(event);
- };
-
-
// Shortcut manager class
var ShortcutManager = function (delay) {
@@ -262,8 +253,7 @@ IPython.keyboard = (function (IPython) {
normalize_key : normalize_key,
normalize_shortcut : normalize_shortcut,
shortcut_to_event : shortcut_to_event,
- event_to_shortcut : event_to_shortcut,
- trigger_keydown : trigger_keydown
+ event_to_shortcut : event_to_shortcut
};
}(IPython));
diff --git a/IPython/html/tests/notebook/arrow_keys.js b/IPython/html/tests/notebook/arrow_keys.js
deleted file mode 100644
index 6f089ba..0000000
--- a/IPython/html/tests/notebook/arrow_keys.js
+++ /dev/null
@@ -1,24 +0,0 @@
-//
-// Check for errors with up and down arrow presses in a non-empty notebook.
-//
-casper.notebook_test(function () {
- var result = this.evaluate(function() {
- IPython.notebook.command_mode();
- pos0 = IPython.notebook.get_selected_index();
- IPython.keyboard.trigger_keydown('b');
- pos1 = IPython.notebook.get_selected_index();
- IPython.keyboard.trigger_keydown('b');
- pos2 = IPython.notebook.get_selected_index();
- // Simulate the "up arrow" and "down arrow" keys.
- IPython.keyboard.trigger_keydown('up');
- pos3 = IPython.notebook.get_selected_index();
- IPython.keyboard.trigger_keydown('down');
- pos4 = IPython.notebook.get_selected_index();
- return pos0 == 0 &&
- pos1 == 1 &&
- pos2 == 2 &&
- pos3 == 1 &&
- pos4 == 2;
- });
- this.test.assertTrue(result, 'Up/down arrow okay in non-empty notebook.');
-});
diff --git a/IPython/html/tests/notebook/dualmode.js b/IPython/html/tests/notebook/dualmode.js
new file mode 100644
index 0000000..87b5567
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode.js
@@ -0,0 +1,78 @@
+// Test the notebook dual mode feature.
+
+// Test
+casper.notebook_test(function () {
+ var a = 'print("a")';
+ var index = this.append_cell(a);
+ this.execute_cell_then(index);
+
+ var b = 'print("b")';
+ index = this.append_cell(b);
+ this.execute_cell_then(index);
+
+ var c = 'print("c")';
+ index = this.append_cell(c);
+ this.execute_cell_then(index);
+
+ this.then(function () {
+ this.validate_notebook_state('initial state', 'edit', 0);
+ this.trigger_keydown('esc');
+ this.validate_notebook_state('esc', 'command', 0);
+ this.trigger_keydown('down');
+ this.validate_notebook_state('down', 'command', 1);
+ this.trigger_keydown('enter');
+ this.validate_notebook_state('enter', 'edit', 1);
+ this.trigger_keydown('j');
+ this.validate_notebook_state('j in edit mode', 'edit', 1);
+ this.trigger_keydown('esc');
+ this.validate_notebook_state('esc', 'command', 1);
+ this.trigger_keydown('j');
+ this.validate_notebook_state('j in command mode', 'command', 2);
+ this.click_cell_editor(0);
+ this.validate_notebook_state('click cell 0', 'edit', 0);
+ this.click_cell_editor(3);
+ this.validate_notebook_state('click cell 3', 'edit', 3);
+ this.trigger_keydown('esc');
+ this.validate_notebook_state('esc', 'command', 3);
+
+ // Open keyboard help
+ this.evaluate(function(){
+ $('#keyboard_shortcuts a').click();
+ }, {});
+
+ this.trigger_keydown('k');
+ this.validate_notebook_state('k in command mode while keyboard help is up', 'command', 3);
+
+ // Close keyboard help
+ this.evaluate(function(){
+ $('div.modal button.close').click();
+ }, {});
+
+ this.trigger_keydown('k');
+ this.validate_notebook_state('k in command mode', 'command', 2);
+ this.click_cell_editor(0);
+ this.validate_notebook_state('click cell 0', 'edit', 0);
+ this.focus_notebook();
+ this.validate_notebook_state('focus #notebook', 'command', 0);
+ this.click_cell_editor(0);
+ this.validate_notebook_state('click cell 0', 'edit', 0);
+ this.focus_notebook();
+ this.validate_notebook_state('focus #notebook', 'command', 0);
+ this.click_cell_editor(3);
+ this.validate_notebook_state('click cell 3', 'edit', 3);
+
+ // Cell deletion
+ this.trigger_keydown('esc', 'd', 'd');
+ this.test.assertEquals(this.get_cells_length(), 3, 'dd actually deletes a cell');
+ this.validate_notebook_state('dd', 'command', 2);
+
+ // Make sure that if the time between d presses is too long, nothing gets removed.
+ this.trigger_keydown('d');
+ });
+ this.wait(1000);
+ this.then(function () {
+ this.trigger_keydown('d');
+ this.test.assertEquals(this.get_cells_length(), 3, "d, 1 second wait, d doesn't delete a cell");
+ this.validate_notebook_state('d, 1 second wait, d', 'command', 2);
+ });
+});
diff --git a/IPython/html/tests/notebook/dualmode_arrows.js b/IPython/html/tests/notebook/dualmode_arrows.js
new file mode 100644
index 0000000..034929b
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_arrows.js
@@ -0,0 +1,51 @@
+
+// Test
+casper.notebook_test(function () {
+ var a = 'print("a")';
+ var index = this.append_cell(a);
+ this.execute_cell_then(index);
+
+ var b = 'print("b")';
+ index = this.append_cell(b);
+ this.execute_cell_then(index);
+
+ var c = 'print("c")';
+ index = this.append_cell(c);
+ this.execute_cell_then(index);
+
+ this.then(function () {
+
+ // Up and down in command mode
+ this.select_cell(3);
+ this.trigger_keydown('j');
+ this.validate_notebook_state('j at end of notebook', 'command', 3);
+ this.trigger_keydown('down');
+ this.validate_notebook_state('down at end of notebook', 'command', 3);
+ this.trigger_keydown('up');
+ this.validate_notebook_state('up', 'command', 2);
+ this.select_cell(0);
+ this.validate_notebook_state('select 0', 'command', 0);
+ this.trigger_keydown('k');
+ this.validate_notebook_state('k at top of notebook', 'command', 0);
+ this.trigger_keydown('up');
+ this.validate_notebook_state('up at top of notebook', 'command', 0);
+ this.trigger_keydown('down');
+ this.validate_notebook_state('down', 'command', 1);
+
+ // Up and down in edit mode
+ this.click_cell_editor(3);
+ this.validate_notebook_state('click cell 3', 'edit', 3);
+ this.trigger_keydown('down');
+ this.validate_notebook_state('down at end of notebook', 'edit', 3);
+ this.set_cell_editor_cursor(3, 0, 0);
+ this.trigger_keydown('up');
+ this.validate_notebook_state('up', 'edit', 2);
+ this.click_cell_editor(0);
+ this.validate_notebook_state('click 0', 'edit', 0);
+ this.trigger_keydown('up');
+ this.validate_notebook_state('up at top of notebook', 'edit', 0);
+ this.set_cell_editor_cursor(0, 0, 10);
+ this.trigger_keydown('down');
+ this.validate_notebook_state('down', 'edit', 1);
+ });
+});
diff --git a/IPython/html/tests/notebook/dualmode_cellinsert.js b/IPython/html/tests/notebook/dualmode_cellinsert.js
new file mode 100644
index 0000000..59b89a3
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_cellinsert.js
@@ -0,0 +1,27 @@
+
+// Test
+casper.notebook_test(function () {
+ var a = 'print("a")';
+ var index = this.append_cell(a);
+ this.execute_cell_then(index);
+
+ var b = 'print("b")';
+ index = this.append_cell(b);
+ this.execute_cell_then(index);
+
+ var c = 'print("c")';
+ index = this.append_cell(c);
+ this.execute_cell_then(index);
+
+ this.then(function () {
+ // Cell insertion
+ this.select_cell(2);
+ this.trigger_keydown('a'); // Creates one cell
+ this.test.assertEquals(this.get_cell_text(2), '', 'a; New cell 2 text is empty');
+ this.validate_notebook_state('a', 'command', 2);
+ this.trigger_keydown('b'); // Creates one cell
+ this.test.assertEquals(this.get_cell_text(2), '', 'b; Cell 2 text is still empty');
+ this.test.assertEquals(this.get_cell_text(3), '', 'b; New cell 3 text is empty');
+ this.validate_notebook_state('b', 'command', 3);
+ });
+});
\ No newline at end of file
diff --git a/IPython/html/tests/notebook/dualmode_cellmode.js b/IPython/html/tests/notebook/dualmode_cellmode.js
new file mode 100644
index 0000000..d4bf5f0
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_cellmode.js
@@ -0,0 +1,28 @@
+// Test keyboard shortcuts that change the cell's mode.
+
+// Test
+casper.notebook_test(function () {
+ this.then(function () {
+ // Cell mode change
+ this.select_cell(0);
+ this.trigger_keydown('esc','r');
+ this.test.assertEquals(this.get_cell(0).cell_type, 'raw', 'r; cell is raw');
+ this.trigger_keydown('1');
+ this.test.assertEquals(this.get_cell(0).cell_type, 'heading', '1; cell is heading');
+ this.test.assertEquals(this.get_cell(0).level, 1, '1; cell is level 1 heading');
+ this.trigger_keydown('2');
+ this.test.assertEquals(this.get_cell(0).level, 2, '2; cell is level 2 heading');
+ this.trigger_keydown('3');
+ this.test.assertEquals(this.get_cell(0).level, 3, '3; cell is level 3 heading');
+ this.trigger_keydown('4');
+ this.test.assertEquals(this.get_cell(0).level, 4, '4; cell is level 4 heading');
+ this.trigger_keydown('5');
+ this.test.assertEquals(this.get_cell(0).level, 5, '5; cell is level 5 heading');
+ this.trigger_keydown('6');
+ this.test.assertEquals(this.get_cell(0).level, 6, '6; cell is level 6 heading');
+ this.trigger_keydown('m');
+ this.test.assertEquals(this.get_cell(0).cell_type, 'markdown', 'm; cell is markdown');
+ this.trigger_keydown('y');
+ this.test.assertEquals(this.get_cell(0).cell_type, 'code', 'y; cell is code');
+ });
+});
\ No newline at end of file
diff --git a/IPython/html/tests/notebook/dualmode_clipboard.js b/IPython/html/tests/notebook/dualmode_clipboard.js
new file mode 100644
index 0000000..5068c49
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_clipboard.js
@@ -0,0 +1,55 @@
+
+
+// Test
+casper.notebook_test(function () {
+ var a = 'print("a")';
+ var index = this.append_cell(a);
+ this.execute_cell_then(index);
+
+ var b = 'print("b")';
+ index = this.append_cell(b);
+ this.execute_cell_then(index);
+
+ var c = 'print("c")';
+ index = this.append_cell(c);
+ this.execute_cell_then(index);
+
+ this.then(function () {
+ // Copy/paste/cut
+ var num_cells = this.get_cells_length();
+ this.test.assertEquals(this.get_cell_text(1), a, 'Verify that cell 1 is a');
+ this.select_cell(1);
+ this.trigger_keydown('x'); // Cut
+ this.validate_notebook_state('x', 'command', 1);
+ this.test.assertEquals(this.get_cells_length(), num_cells-1, 'Verify that a cell was removed.');
+ this.test.assertEquals(this.get_cell_text(1), b, 'Verify that cell 2 is now where cell 1 was.');
+ this.select_cell(2);
+ this.trigger_keydown('v'); // Paste
+ this.validate_notebook_state('v', 'command', 3); // Selection should move to pasted cell, below current cell.
+ this.test.assertEquals(this.get_cell_text(3), a, 'Verify that cell 3 has the cut contents.');
+ this.test.assertEquals(this.get_cells_length(), num_cells, 'Verify a the cell was added.');
+ this.trigger_keydown('v'); // Paste
+ this.validate_notebook_state('v', 'command', 4); // Selection should move to pasted cell, below current cell.
+ this.test.assertEquals(this.get_cell_text(4), a, 'Verify that cell 4 has the cut contents.');
+ this.test.assertEquals(this.get_cells_length(), num_cells+1, 'Verify a the cell was added.');
+ this.select_cell(1);
+ this.trigger_keydown('c'); // Copy
+ this.validate_notebook_state('c', 'command', 1);
+ this.test.assertEquals(this.get_cell_text(1), b, 'Verify that cell 1 is b');
+ this.select_cell(2);
+ this.trigger_keydown('c'); // Copy
+ this.validate_notebook_state('c', 'command', 2);
+ this.test.assertEquals(this.get_cell_text(2), c, 'Verify that cell 2 is c');
+ this.select_cell(4);
+ this.trigger_keydown('v'); // Paste
+ this.validate_notebook_state('v', 'command', 5);
+ this.test.assertEquals(this.get_cell_text(2), c, 'Verify that cell 2 still has the copied contents.');
+ this.test.assertEquals(this.get_cell_text(5), c, 'Verify that cell 5 has the copied contents.');
+ this.test.assertEquals(this.get_cells_length(), num_cells+2, 'Verify a the cell was added.');
+ this.select_cell(0);
+ this.trigger_keydown('shift-v'); // Paste
+ this.validate_notebook_state('shift-v', 'command', 0);
+ this.test.assertEquals(this.get_cell_text(0), c, 'Verify that cell 0 has the copied contents.');
+ this.test.assertEquals(this.get_cells_length(), num_cells+3, 'Verify a the cell was added.');
+ });
+});
\ No newline at end of file
diff --git a/IPython/html/tests/notebook/dualmode_execute.js b/IPython/html/tests/notebook/dualmode_execute.js
new file mode 100644
index 0000000..f4cd954
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_execute.js
@@ -0,0 +1,72 @@
+// Test keyboard invoked execution.
+
+// Test
+casper.notebook_test(function () {
+ var a = 'print("a")';
+ var index = this.append_cell(a);
+ this.execute_cell_then(index);
+
+ var b = 'print("b")';
+ index = this.append_cell(b);
+ this.execute_cell_then(index);
+
+ var c = 'print("c")';
+ index = this.append_cell(c);
+ this.execute_cell_then(index);
+
+ this.then(function () {
+
+ // shift-enter
+ // last cell in notebook
+ var base_index = 3;
+ this.select_cell(base_index);
+ this.trigger_keydown('shift-enter'); // Creates one cell
+ this.validate_notebook_state('shift-enter (no cell below)', 'edit', base_index + 1);
+ // not last cell in notebook & starts in edit mode
+ this.click_cell_editor(base_index);
+ this.validate_notebook_state('click cell ' + base_index, 'edit', base_index);
+ this.trigger_keydown('shift-enter');
+ this.validate_notebook_state('shift-enter (cell exists below)', 'command', base_index + 1);
+ // starts in command mode
+ this.trigger_keydown('k');
+ this.validate_notebook_state('k in comand mode', 'command', base_index);
+ this.trigger_keydown('shift-enter');
+ this.validate_notebook_state('shift-enter (start in command mode)', 'command', base_index + 1);
+
+ // ctrl-enter
+ // last cell in notebook
+ base_index++;
+ this.trigger_keydown('ctrl-enter');
+ this.validate_notebook_state('ctrl-enter (no cell below)', 'command', base_index);
+ // not last cell in notebook & starts in edit mode
+ this.click_cell_editor(base_index-1);
+ this.validate_notebook_state('click cell ' + (base_index-1), 'edit', base_index-1);
+ this.trigger_keydown('ctrl-enter');
+ this.validate_notebook_state('ctrl-enter (cell exists below)', 'command', base_index-1);
+ // starts in command mode
+ this.trigger_keydown('j');
+ this.validate_notebook_state('j in comand mode', 'command', base_index);
+ this.trigger_keydown('ctrl-enter');
+ this.validate_notebook_state('ctrl-enter (start in command mode)', 'command', base_index);
+
+ // alt-enter
+ // last cell in notebook
+ this.trigger_keydown('alt-enter'); // Creates one cell
+ this.validate_notebook_state('alt-enter (no cell below)', 'edit', base_index + 1);
+ // not last cell in notebook & starts in edit mode
+ this.click_cell_editor(base_index);
+ this.validate_notebook_state('click cell ' + base_index, 'edit', base_index);
+ this.trigger_keydown('alt-enter'); // Creates one cell
+ this.validate_notebook_state('alt-enter (cell exists below)', 'edit', base_index + 1);
+ // starts in command mode
+ this.trigger_keydown('esc', 'k');
+ this.validate_notebook_state('k in comand mode', 'command', base_index);
+ this.trigger_keydown('alt-enter'); // Creates one cell
+ this.validate_notebook_state('alt-enter (start in command mode)', 'edit', base_index + 1);
+
+ // Notebook will now have 8 cells, the index of the last cell will be 7.
+ this.test.assertEquals(this.get_cells_length(), 8, '*-enter commands added cells where needed.');
+ this.select_cell(7);
+ this.validate_notebook_state('click cell ' + 7 + ' and esc', 'command', 7);
+ });
+});
\ No newline at end of file
diff --git a/IPython/html/tests/notebook/dualmode_markdown.js b/IPython/html/tests/notebook/dualmode_markdown.js
new file mode 100644
index 0000000..d974057
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_markdown.js
@@ -0,0 +1,39 @@
+
+// Test
+casper.notebook_test(function () {
+ var a = 'print("a")';
+ var index = this.append_cell(a);
+ this.execute_cell_then(index);
+
+ this.then(function () {
+ // Markdown rendering / unredering
+ this.select_cell(1);
+ this.validate_notebook_state('select 1', 'command', 1);
+ this.trigger_keydown('m');
+ this.test.assertEquals(this.get_cell(1).cell_type, 'markdown', 'm; cell is markdown');
+ this.test.assertEquals(this.get_cell(1).rendered, false, 'm; cell is rendered');
+ this.trigger_keydown('enter');
+ this.test.assertEquals(this.get_cell(1).rendered, false, 'enter; cell is unrendered');
+ this.validate_notebook_state('enter', 'edit', 1);
+ this.trigger_keydown('ctrl-enter');
+ this.test.assertEquals(this.get_cell(1).rendered, true, 'ctrl-enter; cell is rendered');
+ this.validate_notebook_state('enter', 'command', 1);
+ this.trigger_keydown('enter');
+ this.test.assertEquals(this.get_cell(1).rendered, false, 'enter; cell is unrendered');
+ this.select_cell(0);
+ this.test.assertEquals(this.get_cell(1).rendered, false, 'select 0; cell 1 is still unrendered');
+ this.validate_notebook_state('select 0', 'command', 0);
+ this.select_cell(1);
+ this.validate_notebook_state('select 1', 'command', 1);
+ this.trigger_keydown('ctrl-enter');
+ this.test.assertEquals(this.get_cell(1).rendered, true, 'ctrl-enter; cell is rendered');
+ this.select_cell(0);
+ this.validate_notebook_state('select 0', 'command', 0);
+ this.trigger_keydown('shift-enter');
+ this.validate_notebook_state('shift-enter', 'command', 1);
+ this.test.assertEquals(this.get_cell(1).rendered, true, 'shift-enter; cell is rendered');
+ this.trigger_keydown('shift-enter'); // Creates one cell
+ this.validate_notebook_state('shift-enter', 'edit', 2);
+ this.test.assertEquals(this.get_cell(1).rendered, true, 'shift-enter; cell is rendered');
+ });
+});
\ No newline at end of file
diff --git a/IPython/html/tests/notebook/dualmode_merge.js b/IPython/html/tests/notebook/dualmode_merge.js
new file mode 100644
index 0000000..573b457
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_merge.js
@@ -0,0 +1,21 @@
+
+// Test
+casper.notebook_test(function () {
+ this.then(function () {
+ // Split and merge cells
+ this.select_cell(0);
+ this.trigger_keydown('a', 'enter'); // Create cell above and enter edit mode.
+ this.validate_notebook_state('a, enter', 'edit', 0);
+ this.set_cell_text(0, 'abcd');
+ this.set_cell_editor_cursor(0, 0, 2);
+ this.test.assertEquals(this.get_cell_text(0), 'abcd', 'Verify that cell 0 has the new contents.');
+ this.trigger_keydown('ctrl-shift-subtract'); // Split
+ this.test.assertEquals(this.get_cell_text(0), 'ab', 'split; Verify that cell 0 has the first half.');
+ this.test.assertEquals(this.get_cell_text(1), 'cd', 'split; Verify that cell 1 has the second half.');
+ this.validate_notebook_state('split', 'edit', 1);
+ this.select_cell(0); // Move up to cell 0
+ this.trigger_keydown('shift-m'); // Merge
+ this.validate_notebook_state('merge', 'command', 0);
+ this.test.assertEquals(this.get_cell_text(0), 'ab\ncd', 'merge; Verify that cell 0 has the merged contents.');
+ });
+});
\ No newline at end of file
diff --git a/IPython/html/tests/notebook/dualmode_movecell.js b/IPython/html/tests/notebook/dualmode_movecell.js
new file mode 100644
index 0000000..d2d0fad
--- /dev/null
+++ b/IPython/html/tests/notebook/dualmode_movecell.js
@@ -0,0 +1,25 @@
+
+// Test
+casper.notebook_test(function () {
+ var a = 'print("a")';
+ var index = this.append_cell(a);
+ this.execute_cell_then(index);
+
+ var b = 'print("b")';
+ index = this.append_cell(b);
+ this.execute_cell_then(index);
+
+ this.then(function () {
+ // Cell movement ( ctrl-(k or j) )
+ this.select_cell(2);
+ this.test.assertEquals(this.get_cell_text(2), b, 'select 2; Cell 2 text is correct');
+ this.trigger_keydown('ctrl-k'); // Move cell 2 up one
+ this.test.assertEquals(this.get_cell_text(1), b, 'ctrl-k; Cell 1 text is correct');
+ this.test.assertEquals(this.get_cell_text(2), a, 'ctrl-k; Cell 2 text is correct');
+ this.validate_notebook_state('ctrl-k', 'command', 1);
+ this.trigger_keydown('ctrl-j'); // Move cell 1 down one
+ this.test.assertEquals(this.get_cell_text(1), a, 'ctrl-j; Cell 1 text is correct');
+ this.test.assertEquals(this.get_cell_text(2), b, 'ctrl-j; Cell 2 text is correct');
+ this.validate_notebook_state('ctrl-j', 'command', 2);
+ });
+});
\ No newline at end of file
diff --git a/IPython/html/tests/notebook/empty_arrow_keys.js b/IPython/html/tests/notebook/empty_arrow_keys.js
index 6abed3a..a949ce5 100644
--- a/IPython/html/tests/notebook/empty_arrow_keys.js
+++ b/IPython/html/tests/notebook/empty_arrow_keys.js
@@ -10,12 +10,12 @@ casper.notebook_test(function () {
for (i = 0; i < ncells; i++) {
IPython.notebook.delete_cell();
}
-
- // Simulate the "up arrow" and "down arrow" keys.
- //
- IPython.keyboard.trigger_keydown('up');
- IPython.keyboard.trigger_keydown('down');
+
return true;
});
+
+ // Simulate the "up arrow" and "down arrow" keys.
+ this.trigger_keydown('up');
+ this.trigger_keydown('down');
this.test.assertTrue(result, 'Up/down arrow okay in empty notebook.');
});
diff --git a/IPython/html/tests/notebook/execute_code.js b/IPython/html/tests/notebook/execute_code.js
index 1af684f..076d3b7 100644
--- a/IPython/html/tests/notebook/execute_code.js
+++ b/IPython/html/tests/notebook/execute_code.js
@@ -22,7 +22,11 @@ casper.notebook_test(function () {
var cell = IPython.notebook.get_cell(0);
cell.set_text('a=11; print(a)');
cell.clear_output();
- IPython.keyboard.trigger_keydown('shift-enter');
+ });
+
+ this.then(function(){
+
+ this.trigger_keydown('shift-enter');
});
this.wait_for_output(0);
@@ -41,7 +45,10 @@ casper.notebook_test(function () {
var cell = IPython.notebook.get_cell(0);
cell.set_text('a=12; print(a)');
cell.clear_output();
- IPython.keyboard.trigger_keydown('ctrl-enter');
+ });
+
+ this.then(function(){
+ this.trigger_keydown('ctrl-enter');
});
this.wait_for_output(0);
diff --git a/IPython/html/tests/notebook/interrupt.js b/IPython/html/tests/notebook/interrupt.js
index 2bb87f8..7c2912c 100644
--- a/IPython/html/tests/notebook/interrupt.js
+++ b/IPython/html/tests/notebook/interrupt.js
@@ -31,8 +31,8 @@ casper.notebook_test(function () {
});
// interrupt using Ctrl-M I keyboard shortcut
- this.thenEvaluate( function() {
- IPython.keyboard.trigger_keydown('i');
+ this.then(function(){
+ this.trigger_keydown('i');
});
this.wait_for_output(0);
diff --git a/IPython/html/tests/notebook/merge_cells.js b/IPython/html/tests/notebook/merge_cells_api.js
similarity index 54%
rename from IPython/html/tests/notebook/merge_cells.js
rename to IPython/html/tests/notebook/merge_cells_api.js
index 2d85614..9dd2fbd 100644
--- a/IPython/html/tests/notebook/merge_cells.js
+++ b/IPython/html/tests/notebook/merge_cells_api.js
@@ -2,37 +2,42 @@
// Test merging two notebook cells.
//
casper.notebook_test(function() {
- var output = this.evaluate(function () {
- // Fill in test data.
- IPython.notebook.command_mode();
- var set_cell_text = function () {
+ var that = this;
+ var set_cells_text = function () {
+ that.evaluate(function() {
var cell_one = IPython.notebook.get_selected_cell();
cell_one.set_text('a = 5');
-
- IPython.keyboard.trigger_keydown('b');
+ });
+
+ that.trigger_keydown('b');
+
+ that.evaluate(function() {
var cell_two = IPython.notebook.get_selected_cell();
cell_two.set_text('print(a)');
- };
+ });
+ };
+
+ this.evaluate(function () {
+ IPython.notebook.command_mode();
+ });
- // merge_cell_above()
- set_cell_text();
+ // merge_cell_above()
+ set_cells_text();
+ var output_above = this.evaluate(function () {
IPython.notebook.merge_cell_above();
- var merged_above = IPython.notebook.get_selected_cell();
+ return IPython.notebook.get_selected_cell().get_text();
+ });
- // merge_cell_below()
- set_cell_text();
+ // merge_cell_below()
+ set_cells_text();
+ var output_below = this.evaluate(function() {
IPython.notebook.select(0);
IPython.notebook.merge_cell_below();
- var merged_below = IPython.notebook.get_selected_cell();
-
- return {
- above: merged_above.get_text(),
- below: merged_below.get_text()
- };
+ return IPython.notebook.get_selected_cell().get_text();
});
- this.test.assertEquals(output.above, 'a = 5\nprint(a)',
+ this.test.assertEquals(output_above, 'a = 5\nprint(a)',
'Successful merge_cell_above().');
- this.test.assertEquals(output.below, 'a = 5\nprint(a)',
+ this.test.assertEquals(output_below, 'a = 5\nprint(a)',
'Successful merge_cell_below().');
});
diff --git a/IPython/html/tests/util.js b/IPython/html/tests/util.js
index 8c49dd2..a572190 100644
--- a/IPython/html/tests/util.js
+++ b/IPython/html/tests/util.js
@@ -2,15 +2,15 @@
// Utility functions for the HTML notebook's CasperJS tests.
//
-// Get the URL of a notebook server on which to run tests.
casper.get_notebook_server = function () {
- port = casper.cli.get("port")
+ // Get the URL of a notebook server on which to run tests.
+ port = casper.cli.get("port");
port = (typeof port === 'undefined') ? '8888' : port;
- return 'http://127.0.0.1:' + port
+ return 'http://127.0.0.1:' + port;
};
-// Create and open a new notebook.
casper.open_new_notebook = function () {
+ // Create and open a new notebook.
var baseUrl = this.get_notebook_server();
this.start(baseUrl);
this.thenClick('button#new_notebook');
@@ -34,15 +34,15 @@ casper.open_new_notebook = function () {
});
};
-// Return whether or not the kernel is running.
casper.kernel_running = function kernel_running() {
+ // Return whether or not the kernel is running.
return this.evaluate(function kernel_running() {
return IPython.notebook.kernel.running;
});
};
-// Shut down the current notebook's kernel.
casper.shutdown_current_kernel = function () {
+ // Shut down the current notebook's kernel.
this.thenEvaluate(function() {
IPython.notebook.kernel.kill();
});
@@ -50,8 +50,9 @@ casper.shutdown_current_kernel = function () {
this.wait(1000);
};
-// Delete created notebook.
casper.delete_current_notebook = function () {
+ // Delete created notebook.
+
// For some unknown reason, this doesn't work?!?
this.thenEvaluate(function() {
IPython.notebook.delete();
@@ -59,6 +60,7 @@ casper.delete_current_notebook = function () {
};
casper.wait_for_busy = function () {
+ // Waits for the notebook to enter a busy state.
this.waitFor(function () {
return this.evaluate(function () {
return IPython._status == 'busy';
@@ -67,6 +69,7 @@ casper.wait_for_busy = function () {
};
casper.wait_for_idle = function () {
+ // Waits for the notebook to idle.
this.waitFor(function () {
return this.evaluate(function () {
return IPython._status == 'idle';
@@ -74,8 +77,8 @@ casper.wait_for_idle = function () {
});
};
-// wait for the nth output in a given cell
casper.wait_for_output = function (cell_num, out_num) {
+ // wait for the nth output in a given cell
this.wait_for_idle();
out_num = out_num || 0;
this.then(function() {
@@ -94,29 +97,29 @@ casper.wait_for_output = function (cell_num, out_num) {
});
};
-// wait for a widget msg que to reach 0
-//
-// Parameters
-// ----------
-// widget_info : object
-// Object which contains info related to the widget. The model_id property
-// is used to identify the widget.
casper.wait_for_widget = function (widget_info) {
+ // wait for a widget msg que to reach 0
+ //
+ // Parameters
+ // ----------
+ // widget_info : object
+ // Object which contains info related to the widget. The model_id property
+ // is used to identify the widget.
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});
- if (pending == 0) {
+ if (pending === 0) {
return true;
} else {
return false;
}
});
-}
+};
-// return an output of a given cell
casper.get_output_cell = function (cell_num, out_num) {
+ // return an output of a given cell
out_num = out_num || 0;
var result = casper.evaluate(function (c, o) {
var cell = IPython.notebook.get_cell(c);
@@ -137,25 +140,33 @@ casper.get_output_cell = function (cell_num, out_num) {
}
};
-// return the number of cells in the notebook
casper.get_cells_length = function () {
+ // return the number of cells in the notebook
var result = casper.evaluate(function () {
return IPython.notebook.get_cells().length;
- })
+ });
return result;
};
-// Set the text content of a cell.
casper.set_cell_text = function(index, text){
+ // Set the text content of a cell.
this.evaluate(function (index, text) {
var cell = IPython.notebook.get_cell(index);
cell.set_text(text);
}, index, text);
};
-// Inserts a cell at the bottom of the notebook
-// Returns the new cell's index.
+casper.get_cell_text = function(index){
+ // Get the text content of a cell.
+ return this.evaluate(function (index) {
+ var cell = IPython.notebook.get_cell(index);
+ return cell.get_text();
+ }, index);
+};
+
casper.insert_cell_at_bottom = function(cell_type){
+ // Inserts a cell at the bottom of the notebook
+ // Returns the new cell's index.
cell_type = cell_type || 'code';
return this.evaluate(function (cell_type) {
@@ -164,9 +175,9 @@ casper.insert_cell_at_bottom = function(cell_type){
}, cell_type);
};
-// Insert a cell at the bottom of the notebook and set the cells text.
-// Returns the new cell's index.
casper.append_cell = function(text, cell_type) {
+ // Insert a cell at the bottom of the notebook and set the cells text.
+ // Returns the new cell's index.
var index = this.insert_cell_at_bottom(cell_type);
if (text !== undefined) {
this.set_cell_text(index, text);
@@ -174,9 +185,9 @@ casper.append_cell = function(text, cell_type) {
return index;
};
-// Asynchronously executes a cell by index.
-// Returns the cell's index.
casper.execute_cell = function(index){
+ // Asynchronously executes a cell by index.
+ // Returns the cell's index.
var that = this;
this.then(function(){
that.evaluate(function (index) {
@@ -187,11 +198,11 @@ casper.execute_cell = function(index){
return index;
};
-// Synchronously executes a cell by index.
-// Optionally accepts a then_callback parameter. then_callback will get called
-// when the cell has finished executing.
-// Returns the cell's index.
casper.execute_cell_then = function(index, then_callback) {
+ // Synchronously executes a cell by index.
+ // Optionally accepts a then_callback parameter. then_callback will get called
+ // when the cell has finished executing.
+ // Returns the cell's index.
var return_val = this.execute_cell(index);
this.wait_for_idle();
@@ -206,18 +217,18 @@ casper.execute_cell_then = function(index, then_callback) {
return return_val;
};
-// Utility function that allows us to easily check if an element exists
-// within a cell. Uses JQuery selector to look for the element.
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.
return casper.evaluate(function (index, selector) {
var $cell = IPython.notebook.get_cell(index).element;
return $cell.find(selector).length > 0;
}, index, selector);
};
-// Utility function that allows us to execute a jQuery function on an
-// element within a cell.
casper.cell_element_function = function(index, selector, function_name, function_args){
+ // Utility function that allows us to execute a jQuery function on an
+ // element within a cell.
return casper.evaluate(function (index, selector, function_name, function_args) {
var $cell = IPython.notebook.get_cell(index).element;
var $el = $cell.find(selector);
@@ -225,8 +236,183 @@ casper.cell_element_function = function(index, selector, function_name, function
}, index, selector, function_name, function_args);
};
-// Wrap a notebook test to reduce boilerplate.
+casper.validate_notebook_state = function(message, mode, cell_index) {
+ // Validate the entire dual mode state of the notebook. Make sure no more than
+ // one cell is selected, focused, in edit mode, etc...
+
+ // General tests.
+ this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
+ message + '; keyboard and notebook modes match');
+ // Is the selected cell the only cell that is selected?
+ if (cell_index!==undefined) {
+ this.test.assert(this.is_only_cell_selected(cell_index),
+ message + '; cell ' + cell_index + ' is the only cell selected');
+ }
+
+ // Mode specific tests.
+ if (mode==='command') {
+ // Are the notebook and keyboard manager in command mode?
+ this.test.assertEquals(this.get_keyboard_mode(), 'command',
+ message + '; in command mode');
+ // Make sure there isn't a single cell in edit mode.
+ this.test.assert(this.is_only_cell_edit(null),
+ message + '; all cells in command mode');
+ this.test.assert(this.is_cell_editor_focused(null),
+ message + '; no cell editors are focused while in command mode');
+
+ } else if (mode==='edit') {
+ // Are the notebook and keyboard manager in edit mode?
+ this.test.assertEquals(this.get_keyboard_mode(), 'edit',
+ message + '; in edit mode');
+ if (cell_index!==undefined) {
+ // Is the specified cell the only cell in edit mode?
+ this.test.assert(this.is_only_cell_edit(cell_index),
+ message + '; cell ' + cell_index + ' is the only cell in edit mode');
+ // Is the specified cell the only cell with a focused code mirror?
+ this.test.assert(this.is_cell_editor_focused(cell_index),
+ message + '; cell ' + cell_index + '\'s editor is appropriately focused');
+ }
+
+ } else {
+ this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
+ }
+};
+
+casper.select_cell = function(index) {
+ // Select a cell in the notebook.
+ this.evaluate(function (i) {
+ IPython.notebook.select(i);
+ }, {i: index});
+};
+
+casper.click_cell_editor = function(index) {
+ // Emulate a click on a cell's editor.
+
+ // Code Mirror does not play nicely with emulated brower events.
+ // Instead of trying to emulate a click, here we run code similar to
+ // the code used in Code Mirror that handles the mousedown event on a
+ // region of codemirror that the user can focus.
+ this.evaluate(function (i) {
+ var cm = IPython.notebook.get_cell(i).code_mirror;
+ if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input))
+ cm.display.input.focus();
+ }, {i: index});
+};
+
+casper.set_cell_editor_cursor = function(index, line_index, char_index) {
+ // Set the Code Mirror instance cursor's location.
+ this.evaluate(function (i, l, c) {
+ IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
+ }, {i: index, l: line_index, c: char_index});
+};
+
+casper.focus_notebook = function() {
+ // Focus the notebook div.
+ this.evaluate(function (){
+ $('#notebook').focus();
+ }, {});
+};
+
+casper.trigger_keydown = function() {
+ // Emulate a keydown in the notebook.
+ for (var i = 0; i < arguments.length; i++) {
+ this.evaluate(function (k) {
+ var element = $(document);
+ var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
+ element.trigger(event);
+ }, {k: arguments[i]});
+ }
+};
+
+casper.get_keyboard_mode = function() {
+ // Get the mode of the keyboard manager.
+ return this.evaluate(function() {
+ return IPython.keyboard_manager.mode;
+ }, {});
+};
+
+casper.get_notebook_mode = function() {
+ // Get the mode of the notebook.
+ return this.evaluate(function() {
+ return IPython.notebook.mode;
+ }, {});
+};
+
+casper.get_cell = function(index) {
+ // Get a single cell.
+ //
+ // Note: Handles to DOM elements stored in the cell will be useless once in
+ // CasperJS context.
+ return this.evaluate(function(i) {
+ var cell = IPython.notebook.get_cell(i);
+ if (cell) {
+ return cell;
+ }
+ return null;
+ }, {i : index});
+};
+
+casper.is_cell_editor_focused = function(index) {
+ // Make sure a cell's editor is the only editor focused on the page.
+ return this.evaluate(function(i) {
+ var focused_textarea = $('#notebook .CodeMirror-focused textarea');
+ if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
+ if (i === null) {
+ return focused_textarea.length === 0;
+ } else {
+ var cell = IPython.notebook.get_cell(i);
+ if (cell) {
+ return cell.code_mirror.getInputField() == focused_textarea[0];
+ }
+ }
+ return false;
+ }, {i : index});
+};
+
+casper.is_only_cell_selected = function(index) {
+ // Check if a cell is the only cell selected.
+ // Pass null as the index to check if no cells are selected.
+ return this.is_only_cell_on(index, 'selected', 'unselected');
+};
+
+casper.is_only_cell_edit = function(index) {
+ // Check if a cell is the only cell in edit mode.
+ // Pass null as the index to check if all of the cells are in command mode.
+ return this.is_only_cell_on(index, 'edit_mode', 'command_mode');
+};
+
+casper.is_only_cell_on = function(i, on_class, off_class) {
+ // Check if a cell is the only cell with the `on_class` DOM class applied to it.
+ // All of the other cells are checked for the `off_class` DOM class.
+ // Pass null as the index to check if all of the cells have the `off_class`.
+ var cells_length = this.get_cells_length();
+ for (var j = 0; j < cells_length; j++) {
+ if (j === i) {
+ if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
+ return false;
+ }
+ } else {
+ if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
+ return false;
+ }
+ }
+ }
+ return true;
+};
+
+casper.cell_has_class = function(index, classes) {
+ // Check if a cell has a class.
+ return this.evaluate(function(i, c) {
+ var cell = IPython.notebook.get_cell(i);
+ if (cell) {
+ return cell.element.hasClass(c);
+ }
+ return false;
+ }, {i : index, c: classes});
+};
+
casper.notebook_test = function(test) {
+ // Wrap a notebook test to reduce boilerplate.
this.open_new_notebook();
this.then(test);
@@ -253,14 +439,14 @@ casper.notebook_test = function(test) {
casper.wait_for_dashboard = function () {
// Wait for the dashboard list to load.
casper.waitForSelector('.list_item');
-}
+};
casper.open_dashboard = function () {
// Start casper by opening the dashboard page.
var baseUrl = this.get_notebook_server();
this.start(baseUrl);
this.wait_for_dashboard();
-}
+};
casper.dashboard_test = function (test) {
// Open the dashboard page and run a test.
@@ -276,16 +462,16 @@ casper.dashboard_test = function (test) {
this.run(function() {
this.test.done();
});
-}
+};
-casper.options.waitTimeout=10000
+casper.options.waitTimeout=10000;
casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
this.echo("Timeout for " + casper.get_notebook_server());
this.echo("Is the notebook server running?");
});
-// Pass `console.log` calls from page JS to casper.
-casper.printLog = function () {
+casper.print_log = function () {
+ // Pass `console.log` calls from page JS to casper.
this.on('remote.message', function(msg) {
this.echo('Remote message caught: ' + msg);
});