diff --git a/.gitignore b/.gitignore index a87065c1d..28a83b48f 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ __pycache__ \#*# .#* .coverage +.pytest_cache src *.swp @@ -38,4 +39,5 @@ config.rst /.project /.pydevproject -package-lock.json \ No newline at end of file +package-lock.json +geckodriver.log diff --git a/.travis.yml b/.travis.yml index e03297fdd..02e8f103b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,10 +36,11 @@ before_install: - | if [[ $GROUP == docs ]]; then pip install -r docs/doc-requirements.txt + pip install --upgrade pytest fi - | if [[ $GROUP == selenium ]]; then - pip install selenium + pip install --upgrade selenium pytest # Install Webdriver backend for Firefox: wget https://github.com/mozilla/geckodriver/releases/download/v0.19.1/geckodriver-v0.19.1-linux64.tar.gz mkdir geckodriver diff --git a/docs/source/examples/Notebook/Running Code.ipynb b/docs/source/examples/Notebook/Running Code.ipynb index 02a6e8776..f0ffca939 100644 --- a/docs/source/examples/Notebook/Running Code.ipynb +++ b/docs/source/examples/Notebook/Running Code.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "First and foremost, the Jupyter Notebook is an interactive environment for writing and running code. The notebook is capable of running code in a wide range of languages. However, each notebook is associated with a single kernel. This notebook is associated with the IPython kernel, therefor runs Python code." + "First and foremost, the Jupyter Notebook is an interactive environment for writing and running code. The notebook is capable of running code in a wide range of languages. However, each notebook is associated with a single kernel. This notebook is associated with the IPython kernel, therefore runs Python code." ] }, { diff --git a/notebook/tests/notebook/multiselect.js b/notebook/tests/notebook/multiselect.js deleted file mode 100644 index 2a7e48eed..000000000 --- a/notebook/tests/notebook/multiselect.js +++ /dev/null @@ -1,100 +0,0 @@ - -// Test -casper.notebook_test(function () { - var that = this; - - var a = 'print("a")'; - var index = this.append_cell(a); - - var b = 'print("b")'; - index = this.append_cell(b); - - var c = 'print("c")'; - index = this.append_cell(c); - - this.then(function () { - var selectedIndex = this.evaluate(function () { - Jupyter.notebook.select(0); - return Jupyter.notebook.get_selected_index(); - }); - - this.test.assertEquals(this.evaluate(function() { - return Jupyter.notebook.get_selected_cells().length; - }), 1, 'only one cell is selected programmatically'); - - this.test.assertEquals(this.evaluate(function() { - return $('.cell.jupyter-soft-selected, .cell.selected').length; - }), 1, 'one cell is selected'); - - this.test.assertEquals(this.evaluate(function() { - Jupyter.notebook.extend_selection_by(1); - return Jupyter.notebook.get_selected_cells().length; - }), 2, 'extend selection by one'); - - - this.test.assertEquals(this.evaluate(function() { - Jupyter.notebook.extend_selection_by(-1); - return Jupyter.notebook.get_selected_cells().length; - }), 1, 'contract selection by one'); - - this.test.assertEquals(this.evaluate(function() { - Jupyter.notebook.select(1); - Jupyter.notebook.extend_selection_by(-1); - return Jupyter.notebook.get_selected_cells().length; - }), 2, 'extend selection by one up'); - - // Test multiple markdown conversions. - var cell_types = this.evaluate(function() { - Jupyter.notebook.select(0); - Jupyter.notebook.extend_selection_by(2); - var indices = Jupyter.notebook.get_selected_cells_indices(); - Jupyter.notebook.cells_to_markdown(); - return indices.map(function(i) { - return Jupyter.notebook.get_cell(i).cell_type; - }); - }); - this.test.assert(cell_types.every(function(cell_type) { - return cell_type === 'markdown'; - }), 'selected cells converted to markdown'); - - this.test.assertEquals(this.evaluate(function() { - return Jupyter.notebook.get_selected_cells_indices(); - }).length, 1, 'one cell selected after convert'); - - // Test multiple raw conversions. - cell_types = this.evaluate(function() { - Jupyter.notebook.select(0); - Jupyter.notebook.extend_selection_by(2); - var indices = Jupyter.notebook.get_selected_cells_indices(); - Jupyter.notebook.cells_to_raw(); - return indices.map(function(i) { - return Jupyter.notebook.get_cell(i).cell_type; - }); - }); - this.test.assert(cell_types.every(function(cell_type) { - return cell_type === 'raw'; - }), 'selected cells converted to raw'); - - this.test.assertEquals(this.evaluate(function() { - return Jupyter.notebook.get_selected_cells_indices(); - }).length, 1, 'one cell selected after convert'); - - // Test multiple code conversions. - cell_types = this.evaluate(function() { - Jupyter.notebook.select(0); - Jupyter.notebook.extend_selection_by(2); - var indices = Jupyter.notebook.get_selected_cells_indices(); - Jupyter.notebook.cells_to_code(); - return indices.map(function(i) { - return Jupyter.notebook.get_cell(i).cell_type; - }); - }); - this.test.assert(cell_types.every(function(cell_type) { - return cell_type === 'code'; - }), 'selected cells converted to code'); - - this.test.assertEquals(this.evaluate(function() { - return Jupyter.notebook.get_selected_cells_indices(); - }).length, 1, 'one cell selected after convert'); - }); -}); diff --git a/notebook/tests/selenium/test_multiselect.py b/notebook/tests/selenium/test_multiselect.py new file mode 100644 index 000000000..5ce6d4904 --- /dev/null +++ b/notebook/tests/selenium/test_multiselect.py @@ -0,0 +1,65 @@ +def test_multiselect(notebook): + def extend_selection_by(delta): + notebook.browser.execute_script( + "Jupyter.notebook.extend_selection_by(arguments[0]);", delta) + + def n_selected_cells(): + return notebook.browser.execute_script( + "return Jupyter.notebook.get_selected_cells().length;") + + a = 'print("a")' + b = 'print("b")' + c = 'print("c")' + notebook.edit_cell(index=0, content=a) + notebook.append(b, c) + + notebook.focus_cell(0) + assert n_selected_cells() == 1 + + # Check that only one cell is selected according to CSS classes as well + selected_css = notebook.browser.find_elements_by_css_selector( + '.cell.jupyter-soft-selected, .cell.selected') + assert len(selected_css) == 1 + + # Extend the selection down one + extend_selection_by(1) + assert n_selected_cells() == 2 + + # Contract the selection up one + extend_selection_by(-1) + assert n_selected_cells() == 1 + + # Extend the selection up one + notebook.focus_cell(1) + extend_selection_by(-1) + assert n_selected_cells() == 2 + + # Convert selected cells to Markdown + notebook.browser.execute_script("Jupyter.notebook.cells_to_markdown();") + cell_types = notebook.browser.execute_script( + "return Jupyter.notebook.get_cells().map(c => c.cell_type)") + assert cell_types == ['markdown', 'markdown', 'code'] + # One cell left selected after conversion + assert n_selected_cells() == 1 + + # Convert selected cells to raw + notebook.focus_cell(1) + extend_selection_by(1) + assert n_selected_cells() == 2 + notebook.browser.execute_script("Jupyter.notebook.cells_to_raw();") + cell_types = notebook.browser.execute_script( + "return Jupyter.notebook.get_cells().map(c => c.cell_type)") + assert cell_types == ['markdown', 'raw', 'raw'] + # One cell left selected after conversion + assert n_selected_cells() == 1 + + # Convert selected cells to code + notebook.focus_cell(0) + extend_selection_by(2) + assert n_selected_cells() == 3 + notebook.browser.execute_script("Jupyter.notebook.cells_to_code();") + cell_types = notebook.browser.execute_script( + "return Jupyter.notebook.get_cells().map(c => c.cell_type)") + assert cell_types == ['code'] * 3 + # One cell left selected after conversion + assert n_selected_cells() == 1 diff --git a/setup.py b/setup.py index 60e376214..3b579543f 100755 --- a/setup.py +++ b/setup.py @@ -98,7 +98,7 @@ for more information. zip_safe = False, install_requires = [ 'jinja2', - 'tornado>=4', + 'tornado>=4, <6', # pyzmq>=17 is not technically necessary, # but hopefully avoids incompatibilities with Tornado 5. April 2018 'pyzmq>=17',