611 lines
22 KiB
Python
611 lines
22 KiB
Python
|
import sys
|
||
|
import unittest
|
||
|
|
||
|
from flaky import flaky
|
||
|
import pytest
|
||
|
from qtpy import QtCore, QtGui, QtWidgets
|
||
|
from qtpy.QtTest import QTest
|
||
|
|
||
|
from qtconsole.console_widget import ConsoleWidget
|
||
|
from qtconsole.qtconsoleapp import JupyterQtConsoleApp
|
||
|
from . import no_display
|
||
|
|
||
|
if sys.version[0] == '2': # Python 2
|
||
|
from IPython.core.inputsplitter import InputSplitter as TransformerManager
|
||
|
else:
|
||
|
from IPython.core.inputtransformer2 import TransformerManager
|
||
|
|
||
|
|
||
|
SHELL_TIMEOUT = 20000
|
||
|
|
||
|
|
||
|
@pytest.fixture
|
||
|
def qtconsole(qtbot):
|
||
|
"""Qtconsole fixture."""
|
||
|
# Create a console
|
||
|
console = JupyterQtConsoleApp()
|
||
|
console.initialize(argv=[])
|
||
|
|
||
|
qtbot.addWidget(console.window)
|
||
|
console.window.confirm_exit = False
|
||
|
console.window.show()
|
||
|
return console
|
||
|
|
||
|
|
||
|
@flaky(max_runs=3)
|
||
|
@pytest.mark.parametrize(
|
||
|
"debug", [True, False])
|
||
|
def test_scroll(qtconsole, qtbot, debug):
|
||
|
"""
|
||
|
Make sure the scrolling works.
|
||
|
"""
|
||
|
window = qtconsole.window
|
||
|
shell = window.active_frontend
|
||
|
control = shell._control
|
||
|
scroll_bar = control.verticalScrollBar()
|
||
|
|
||
|
# Wait until the console is fully up
|
||
|
qtbot.waitUntil(lambda: shell._prompt_html is not None,
|
||
|
timeout=SHELL_TIMEOUT)
|
||
|
|
||
|
assert scroll_bar.value() == 0
|
||
|
|
||
|
# Define a function with loads of output
|
||
|
# Check the outputs are working as well
|
||
|
code = ["import time",
|
||
|
"def print_numbers():",
|
||
|
" for i in range(1000):",
|
||
|
" print(i)",
|
||
|
" time.sleep(.01)"]
|
||
|
for line in code:
|
||
|
qtbot.keyClicks(control, line)
|
||
|
qtbot.keyClick(control, QtCore.Qt.Key_Enter)
|
||
|
|
||
|
with qtbot.waitSignal(shell.executed):
|
||
|
qtbot.keyClick(control, QtCore.Qt.Key_Enter,
|
||
|
modifier=QtCore.Qt.ShiftModifier)
|
||
|
|
||
|
def run_line(line, block=True):
|
||
|
qtbot.keyClicks(control, line)
|
||
|
if block:
|
||
|
with qtbot.waitSignal(shell.executed):
|
||
|
qtbot.keyClick(control, QtCore.Qt.Key_Enter,
|
||
|
modifier=QtCore.Qt.ShiftModifier)
|
||
|
else:
|
||
|
qtbot.keyClick(control, QtCore.Qt.Key_Enter,
|
||
|
modifier=QtCore.Qt.ShiftModifier)
|
||
|
|
||
|
if debug:
|
||
|
# Enter debug
|
||
|
run_line('%debug print()', block=False)
|
||
|
qtbot.keyClick(control, QtCore.Qt.Key_Enter)
|
||
|
# redefine run_line
|
||
|
def run_line(line, block=True):
|
||
|
qtbot.keyClicks(control, '!' + line)
|
||
|
qtbot.keyClick(control, QtCore.Qt.Key_Enter,
|
||
|
modifier=QtCore.Qt.ShiftModifier)
|
||
|
if block:
|
||
|
qtbot.waitUntil(
|
||
|
lambda: control.toPlainText().strip(
|
||
|
).split()[-1] == "ipdb>")
|
||
|
|
||
|
prev_position = scroll_bar.value()
|
||
|
|
||
|
# Create a bunch of inputs
|
||
|
for i in range(20):
|
||
|
run_line('a = 1')
|
||
|
|
||
|
assert scroll_bar.value() > prev_position
|
||
|
|
||
|
# Put the scroll bar higher and check it doesn't move
|
||
|
prev_position = scroll_bar.value() + scroll_bar.pageStep() // 2
|
||
|
scroll_bar.setValue(prev_position)
|
||
|
|
||
|
for i in range(2):
|
||
|
run_line('a')
|
||
|
|
||
|
assert scroll_bar.value() == prev_position
|
||
|
|
||
|
# add more input and check it moved
|
||
|
for i in range(10):
|
||
|
run_line('a')
|
||
|
|
||
|
assert scroll_bar.value() > prev_position
|
||
|
|
||
|
prev_position = scroll_bar.value()
|
||
|
|
||
|
# Run the printing function
|
||
|
run_line('print_numbers()', block=False)
|
||
|
|
||
|
qtbot.wait(1000)
|
||
|
|
||
|
# Check everything advances
|
||
|
assert scroll_bar.value() > prev_position
|
||
|
|
||
|
# move up
|
||
|
prev_position = scroll_bar.value() - scroll_bar.pageStep()
|
||
|
scroll_bar.setValue(prev_position)
|
||
|
|
||
|
qtbot.wait(1000)
|
||
|
|
||
|
# Check position stayed the same
|
||
|
assert scroll_bar.value() == prev_position
|
||
|
|
||
|
# reset position
|
||
|
prev_position = scroll_bar.maximum() - (scroll_bar.pageStep() * 8) // 10
|
||
|
scroll_bar.setValue(prev_position)
|
||
|
|
||
|
qtbot.wait(1000)
|
||
|
assert scroll_bar.value() > prev_position
|
||
|
|
||
|
|
||
|
@flaky(max_runs=3)
|
||
|
def test_input(qtconsole, qtbot):
|
||
|
"""
|
||
|
Test input function
|
||
|
"""
|
||
|
window = qtconsole.window
|
||
|
shell = window.active_frontend
|
||
|
control = shell._control
|
||
|
|
||
|
# Wait until the console is fully up
|
||
|
qtbot.waitUntil(lambda: shell._prompt_html is not None,
|
||
|
timeout=SHELL_TIMEOUT)
|
||
|
|
||
|
with qtbot.waitSignal(shell.executed):
|
||
|
shell.execute("import time")
|
||
|
|
||
|
if sys.version[0] == '2':
|
||
|
input_function = 'raw_input'
|
||
|
else:
|
||
|
input_function = 'input'
|
||
|
shell.execute("print(" + input_function + "('name: ')); time.sleep(3)")
|
||
|
|
||
|
qtbot.waitUntil(lambda: control.toPlainText().split()[-1] == 'name:')
|
||
|
|
||
|
qtbot.keyClicks(control, 'test')
|
||
|
qtbot.keyClick(control, QtCore.Qt.Key_Enter)
|
||
|
qtbot.waitUntil(lambda: not shell._reading)
|
||
|
qtbot.keyClick(control, 'z', modifier=QtCore.Qt.ControlModifier)
|
||
|
for i in range(10):
|
||
|
qtbot.keyClick(control, QtCore.Qt.Key_Backspace)
|
||
|
qtbot.waitUntil(lambda: shell._prompt_html is not None,
|
||
|
timeout=SHELL_TIMEOUT)
|
||
|
|
||
|
assert 'name: test\ntest' in control.toPlainText()
|
||
|
|
||
|
|
||
|
@flaky(max_runs=3)
|
||
|
def test_debug(qtconsole, qtbot):
|
||
|
"""
|
||
|
Make sure the cursor works while debugging
|
||
|
|
||
|
It might not because the console is "_executing"
|
||
|
"""
|
||
|
window = qtconsole.window
|
||
|
shell = window.active_frontend
|
||
|
control = shell._control
|
||
|
|
||
|
# Wait until the console is fully up
|
||
|
qtbot.waitUntil(lambda: shell._prompt_html is not None,
|
||
|
timeout=SHELL_TIMEOUT)
|
||
|
|
||
|
# Enter execution
|
||
|
code = "%debug range(1)"
|
||
|
qtbot.keyClicks(control, code)
|
||
|
qtbot.keyClick(control, QtCore.Qt.Key_Enter,
|
||
|
modifier=QtCore.Qt.ShiftModifier)
|
||
|
|
||
|
qtbot.waitUntil(
|
||
|
lambda: control.toPlainText().strip().split()[-1] == "ipdb>",
|
||
|
timeout=SHELL_TIMEOUT)
|
||
|
|
||
|
# We should be able to move the cursor while debugging
|
||
|
qtbot.keyClicks(control, "abd")
|
||
|
qtbot.wait(100)
|
||
|
qtbot.keyClick(control, QtCore.Qt.Key_Left)
|
||
|
qtbot.keyClick(control, 'c')
|
||
|
qtbot.wait(100)
|
||
|
assert control.toPlainText().strip().split()[-1] == "abcd"
|
||
|
|
||
|
|
||
|
@pytest.mark.skipif(no_display, reason="Doesn't work without a display")
|
||
|
class TestConsoleWidget(unittest.TestCase):
|
||
|
|
||
|
@classmethod
|
||
|
def setUpClass(cls):
|
||
|
""" Create the application for the test case.
|
||
|
"""
|
||
|
cls._app = QtWidgets.QApplication.instance()
|
||
|
if cls._app is None:
|
||
|
cls._app = QtWidgets.QApplication([])
|
||
|
cls._app.setQuitOnLastWindowClosed(False)
|
||
|
|
||
|
@classmethod
|
||
|
def tearDownClass(cls):
|
||
|
""" Exit the application.
|
||
|
"""
|
||
|
QtWidgets.QApplication.quit()
|
||
|
|
||
|
def assert_text_equal(self, cursor, text):
|
||
|
cursor.select(cursor.Document)
|
||
|
selection = cursor.selectedText()
|
||
|
self.assertEqual(selection, text)
|
||
|
|
||
|
def test_special_characters(self):
|
||
|
""" Are special characters displayed correctly?
|
||
|
"""
|
||
|
w = ConsoleWidget()
|
||
|
cursor = w._get_prompt_cursor()
|
||
|
|
||
|
test_inputs = ['xyz\b\b=\n',
|
||
|
'foo\b\nbar\n',
|
||
|
'foo\b\nbar\r\n',
|
||
|
'abc\rxyz\b\b=']
|
||
|
expected_outputs = [u'x=z\u2029',
|
||
|
u'foo\u2029bar\u2029',
|
||
|
u'foo\u2029bar\u2029',
|
||
|
'x=z']
|
||
|
for i, text in enumerate(test_inputs):
|
||
|
w._insert_plain_text(cursor, text)
|
||
|
self.assert_text_equal(cursor, expected_outputs[i])
|
||
|
# clear all the text
|
||
|
cursor.insertText('')
|
||
|
|
||
|
def test_link_handling(self):
|
||
|
noKeys = QtCore.Qt
|
||
|
noButton = QtCore.Qt.MouseButton(0)
|
||
|
noButtons = QtCore.Qt.MouseButtons(0)
|
||
|
noModifiers = QtCore.Qt.KeyboardModifiers(0)
|
||
|
MouseMove = QtCore.QEvent.MouseMove
|
||
|
QMouseEvent = QtGui.QMouseEvent
|
||
|
|
||
|
w = ConsoleWidget()
|
||
|
cursor = w._get_prompt_cursor()
|
||
|
w._insert_html(cursor, '<a href="http://python.org">written in</a>')
|
||
|
obj = w._control
|
||
|
tip = QtWidgets.QToolTip
|
||
|
self.assertEqual(tip.text(), u'')
|
||
|
|
||
|
# should be somewhere else
|
||
|
elsewhereEvent = QMouseEvent(MouseMove, QtCore.QPoint(50,50),
|
||
|
noButton, noButtons, noModifiers)
|
||
|
w.eventFilter(obj, elsewhereEvent)
|
||
|
self.assertEqual(tip.isVisible(), False)
|
||
|
self.assertEqual(tip.text(), u'')
|
||
|
# should be over text
|
||
|
overTextEvent = QMouseEvent(MouseMove, QtCore.QPoint(1,5),
|
||
|
noButton, noButtons, noModifiers)
|
||
|
w.eventFilter(obj, overTextEvent)
|
||
|
self.assertEqual(tip.isVisible(), True)
|
||
|
self.assertEqual(tip.text(), "http://python.org")
|
||
|
|
||
|
# should still be over text
|
||
|
stillOverTextEvent = QMouseEvent(MouseMove, QtCore.QPoint(1,5),
|
||
|
noButton, noButtons, noModifiers)
|
||
|
w.eventFilter(obj, stillOverTextEvent)
|
||
|
self.assertEqual(tip.isVisible(), True)
|
||
|
self.assertEqual(tip.text(), "http://python.org")
|
||
|
|
||
|
def test_width_height(self):
|
||
|
# width()/height() QWidget properties should not be overridden.
|
||
|
w = ConsoleWidget()
|
||
|
self.assertEqual(w.width(), QtWidgets.QWidget.width(w))
|
||
|
self.assertEqual(w.height(), QtWidgets.QWidget.height(w))
|
||
|
|
||
|
def test_prompt_cursors(self):
|
||
|
"""Test the cursors that keep track of where the prompt begins and
|
||
|
ends"""
|
||
|
w = ConsoleWidget()
|
||
|
w._prompt = 'prompt>'
|
||
|
doc = w._control.document()
|
||
|
|
||
|
# Fill up the QTextEdit area with the maximum number of blocks
|
||
|
doc.setMaximumBlockCount(10)
|
||
|
for _ in range(9):
|
||
|
w._append_plain_text('line\n')
|
||
|
|
||
|
# Draw the prompt, this should cause the first lines to be deleted
|
||
|
w._show_prompt()
|
||
|
self.assertEqual(doc.blockCount(), 10)
|
||
|
|
||
|
# _prompt_pos should be at the end of the document
|
||
|
self.assertEqual(w._prompt_pos, w._get_end_pos())
|
||
|
|
||
|
# _append_before_prompt_pos should be at the beginning of the prompt
|
||
|
self.assertEqual(w._append_before_prompt_pos,
|
||
|
w._prompt_pos - len(w._prompt))
|
||
|
|
||
|
# insert some more text without drawing a new prompt
|
||
|
w._append_plain_text('line\n')
|
||
|
self.assertEqual(w._prompt_pos,
|
||
|
w._get_end_pos() - len('line\n'))
|
||
|
self.assertEqual(w._append_before_prompt_pos,
|
||
|
w._prompt_pos - len(w._prompt))
|
||
|
|
||
|
# redraw the prompt
|
||
|
w._show_prompt()
|
||
|
self.assertEqual(w._prompt_pos, w._get_end_pos())
|
||
|
self.assertEqual(w._append_before_prompt_pos,
|
||
|
w._prompt_pos - len(w._prompt))
|
||
|
|
||
|
# insert some text before the prompt
|
||
|
w._append_plain_text('line', before_prompt=True)
|
||
|
self.assertEqual(w._prompt_pos, w._get_end_pos())
|
||
|
self.assertEqual(w._append_before_prompt_pos,
|
||
|
w._prompt_pos - len(w._prompt))
|
||
|
|
||
|
def test_select_all(self):
|
||
|
w = ConsoleWidget()
|
||
|
w._append_plain_text('Header\n')
|
||
|
w._prompt = 'prompt>'
|
||
|
w._show_prompt()
|
||
|
control = w._control
|
||
|
app = QtWidgets.QApplication.instance()
|
||
|
|
||
|
cursor = w._get_cursor()
|
||
|
w._insert_plain_text_into_buffer(cursor, "if:\n pass")
|
||
|
|
||
|
cursor.clearSelection()
|
||
|
control.setTextCursor(cursor)
|
||
|
|
||
|
# "select all" action selects cell first
|
||
|
w.select_all_smart()
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_C, QtCore.Qt.ControlModifier)
|
||
|
copied = app.clipboard().text()
|
||
|
self.assertEqual(copied, 'if:\n> pass')
|
||
|
|
||
|
# # "select all" action triggered a second time selects whole document
|
||
|
w.select_all_smart()
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_C, QtCore.Qt.ControlModifier)
|
||
|
copied = app.clipboard().text()
|
||
|
self.assertEqual(copied, 'Header\nprompt>if:\n> pass')
|
||
|
|
||
|
def test_keypresses(self):
|
||
|
"""Test the event handling code for keypresses."""
|
||
|
w = ConsoleWidget()
|
||
|
w._append_plain_text('Header\n')
|
||
|
w._prompt = 'prompt>'
|
||
|
w._show_prompt()
|
||
|
app = QtWidgets.QApplication.instance()
|
||
|
control = w._control
|
||
|
|
||
|
# Test setting the input buffer
|
||
|
w._set_input_buffer('test input')
|
||
|
self.assertEqual(w._get_input_buffer(), 'test input')
|
||
|
|
||
|
# Ctrl+K kills input until EOL
|
||
|
w._set_input_buffer('test input')
|
||
|
c = control.textCursor()
|
||
|
c.setPosition(c.position() - 3)
|
||
|
control.setTextCursor(c)
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_K, QtCore.Qt.ControlModifier)
|
||
|
self.assertEqual(w._get_input_buffer(), 'test in')
|
||
|
|
||
|
# Ctrl+V pastes
|
||
|
w._set_input_buffer('test input ')
|
||
|
app.clipboard().setText('pasted text')
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_V, QtCore.Qt.ControlModifier)
|
||
|
self.assertEqual(w._get_input_buffer(), 'test input pasted text')
|
||
|
self.assertEqual(control.document().blockCount(), 2)
|
||
|
|
||
|
# Paste should strip indentation
|
||
|
w._set_input_buffer('test input ')
|
||
|
app.clipboard().setText(' pasted text')
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_V, QtCore.Qt.ControlModifier)
|
||
|
self.assertEqual(w._get_input_buffer(), 'test input pasted text')
|
||
|
self.assertEqual(control.document().blockCount(), 2)
|
||
|
|
||
|
# Multiline paste, should also show continuation marks
|
||
|
w._set_input_buffer('test input ')
|
||
|
app.clipboard().setText('line1\nline2\nline3')
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_V, QtCore.Qt.ControlModifier)
|
||
|
self.assertEqual(w._get_input_buffer(),
|
||
|
'test input line1\nline2\nline3')
|
||
|
self.assertEqual(control.document().blockCount(), 4)
|
||
|
self.assertEqual(control.document().findBlockByNumber(1).text(),
|
||
|
'prompt>test input line1')
|
||
|
self.assertEqual(control.document().findBlockByNumber(2).text(),
|
||
|
'> line2')
|
||
|
self.assertEqual(control.document().findBlockByNumber(3).text(),
|
||
|
'> line3')
|
||
|
|
||
|
# Multiline paste should strip indentation intelligently
|
||
|
# in the case where pasted text has leading whitespace on first line
|
||
|
# and we're pasting into indented position
|
||
|
w._set_input_buffer(' ')
|
||
|
app.clipboard().setText(' If 1:\n pass')
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_V, QtCore.Qt.ControlModifier)
|
||
|
self.assertEqual(w._get_input_buffer(),
|
||
|
' If 1:\n pass')
|
||
|
|
||
|
# Ctrl+Backspace should intelligently remove the last word
|
||
|
w._set_input_buffer("foo = ['foo', 'foo', 'foo', \n"
|
||
|
" 'bar', 'bar', 'bar']")
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Backspace,
|
||
|
QtCore.Qt.ControlModifier)
|
||
|
self.assertEqual(w._get_input_buffer(),
|
||
|
("foo = ['foo', 'foo', 'foo', \n"
|
||
|
" 'bar', 'bar', '"))
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Backspace,
|
||
|
QtCore.Qt.ControlModifier)
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Backspace,
|
||
|
QtCore.Qt.ControlModifier)
|
||
|
self.assertEqual(w._get_input_buffer(),
|
||
|
("foo = ['foo', 'foo', 'foo', \n"
|
||
|
" '"))
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Backspace,
|
||
|
QtCore.Qt.ControlModifier)
|
||
|
self.assertEqual(w._get_input_buffer(),
|
||
|
("foo = ['foo', 'foo', 'foo', \n"
|
||
|
""))
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Backspace,
|
||
|
QtCore.Qt.ControlModifier)
|
||
|
self.assertEqual(w._get_input_buffer(),
|
||
|
"foo = ['foo', 'foo', 'foo',")
|
||
|
|
||
|
# Ctrl+Delete should intelligently remove the next word
|
||
|
w._set_input_buffer("foo = ['foo', 'foo', 'foo', \n"
|
||
|
" 'bar', 'bar', 'bar']")
|
||
|
c = control.textCursor()
|
||
|
c.setPosition(35)
|
||
|
control.setTextCursor(c)
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Delete,
|
||
|
QtCore.Qt.ControlModifier)
|
||
|
self.assertEqual(w._get_input_buffer(),
|
||
|
("foo = ['foo', 'foo', ', \n"
|
||
|
" 'bar', 'bar', 'bar']"))
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Delete,
|
||
|
QtCore.Qt.ControlModifier)
|
||
|
self.assertEqual(w._get_input_buffer(),
|
||
|
("foo = ['foo', 'foo', \n"
|
||
|
" 'bar', 'bar', 'bar']"))
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Delete,
|
||
|
QtCore.Qt.ControlModifier)
|
||
|
self.assertEqual(w._get_input_buffer(),
|
||
|
"foo = ['foo', 'foo', 'bar', 'bar', 'bar']")
|
||
|
w._set_input_buffer("foo = ['foo', 'foo', 'foo', \n"
|
||
|
" 'bar', 'bar', 'bar']")
|
||
|
c = control.textCursor()
|
||
|
c.setPosition(48)
|
||
|
control.setTextCursor(c)
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Delete,
|
||
|
QtCore.Qt.ControlModifier)
|
||
|
self.assertEqual(w._get_input_buffer(),
|
||
|
("foo = ['foo', 'foo', 'foo', \n"
|
||
|
"'bar', 'bar', 'bar']"))
|
||
|
|
||
|
# Left and right keys should respect the continuation prompt
|
||
|
w._set_input_buffer("line 1\n"
|
||
|
"line 2\n"
|
||
|
"line 3")
|
||
|
c = control.textCursor()
|
||
|
c.setPosition(20) # End of line 1
|
||
|
control.setTextCursor(c)
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Right)
|
||
|
# Cursor should have moved after the continuation prompt
|
||
|
self.assertEqual(control.textCursor().position(), 23)
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Left)
|
||
|
# Cursor should have moved to the end of the previous line
|
||
|
self.assertEqual(control.textCursor().position(), 20)
|
||
|
|
||
|
# TODO: many more keybindings
|
||
|
|
||
|
def test_indent(self):
|
||
|
"""Test the event handling code for indent/dedent keypresses ."""
|
||
|
w = ConsoleWidget()
|
||
|
w._append_plain_text('Header\n')
|
||
|
w._prompt = 'prompt>'
|
||
|
w._show_prompt()
|
||
|
control = w._control
|
||
|
|
||
|
# TAB with multiline selection should block-indent
|
||
|
w._set_input_buffer("")
|
||
|
c = control.textCursor()
|
||
|
pos=c.position()
|
||
|
w._set_input_buffer("If 1:\n pass")
|
||
|
c.setPosition(pos, QtGui.QTextCursor.KeepAnchor)
|
||
|
control.setTextCursor(c)
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Tab)
|
||
|
self.assertEqual(w._get_input_buffer()," If 1:\n pass")
|
||
|
|
||
|
# TAB with multiline selection, should block-indent to next multiple
|
||
|
# of 4 spaces, if first line has 0 < indent < 4
|
||
|
w._set_input_buffer("")
|
||
|
c = control.textCursor()
|
||
|
pos=c.position()
|
||
|
w._set_input_buffer(" If 2:\n pass")
|
||
|
c.setPosition(pos, QtGui.QTextCursor.KeepAnchor)
|
||
|
control.setTextCursor(c)
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Tab)
|
||
|
self.assertEqual(w._get_input_buffer()," If 2:\n pass")
|
||
|
|
||
|
# Shift-TAB with multiline selection should block-dedent
|
||
|
w._set_input_buffer("")
|
||
|
c = control.textCursor()
|
||
|
pos=c.position()
|
||
|
w._set_input_buffer(" If 3:\n pass")
|
||
|
c.setPosition(pos, QtGui.QTextCursor.KeepAnchor)
|
||
|
control.setTextCursor(c)
|
||
|
QTest.keyClick(control, QtCore.Qt.Key_Backtab)
|
||
|
self.assertEqual(w._get_input_buffer(),"If 3:\n pass")
|
||
|
|
||
|
def test_complete(self):
|
||
|
class TestKernelClient(object):
|
||
|
def is_complete(self, source):
|
||
|
calls.append(source)
|
||
|
return msg_id
|
||
|
w = ConsoleWidget()
|
||
|
cursor = w._get_prompt_cursor()
|
||
|
w._execute = lambda *args: calls.append(args)
|
||
|
w.kernel_client = TestKernelClient()
|
||
|
msg_id = object()
|
||
|
calls = []
|
||
|
|
||
|
# test incomplete statement (no _execute called, but indent added)
|
||
|
w.execute("thing", interactive=True)
|
||
|
self.assertEqual(calls, ["thing"])
|
||
|
calls = []
|
||
|
w._handle_is_complete_reply(
|
||
|
dict(parent_header=dict(msg_id=msg_id),
|
||
|
content=dict(status="incomplete", indent="!!!")))
|
||
|
self.assert_text_equal(cursor, u"thing\u2029> !!!")
|
||
|
self.assertEqual(calls, [])
|
||
|
|
||
|
# test complete statement (_execute called)
|
||
|
msg_id = object()
|
||
|
w.execute("else", interactive=True)
|
||
|
self.assertEqual(calls, ["else"])
|
||
|
calls = []
|
||
|
w._handle_is_complete_reply(
|
||
|
dict(parent_header=dict(msg_id=msg_id),
|
||
|
content=dict(status="complete", indent="###")))
|
||
|
self.assertEqual(calls, [("else", False)])
|
||
|
calls = []
|
||
|
self.assert_text_equal(cursor, u"thing\u2029> !!!else\u2029")
|
||
|
|
||
|
# test missing answer from is_complete
|
||
|
msg_id = object()
|
||
|
w.execute("done", interactive=True)
|
||
|
self.assertEqual(calls, ["done"])
|
||
|
calls = []
|
||
|
self.assert_text_equal(cursor, u"thing\u2029> !!!else\u2029")
|
||
|
w._trigger_is_complete_callback()
|
||
|
self.assert_text_equal(cursor, u"thing\u2029> !!!else\u2029\u2029> ")
|
||
|
|
||
|
# assert that late answer isn't destroying anything
|
||
|
w._handle_is_complete_reply(
|
||
|
dict(parent_header=dict(msg_id=msg_id),
|
||
|
content=dict(status="complete", indent="###")))
|
||
|
self.assertEqual(calls, [])
|
||
|
|
||
|
def test_complete_python(self):
|
||
|
"""Test that is_complete is working correctly for Python."""
|
||
|
# Kernel client to test the responses of is_complete
|
||
|
class TestIPyKernelClient(object):
|
||
|
def is_complete(self, source):
|
||
|
tm = TransformerManager()
|
||
|
check_complete = tm.check_complete(source)
|
||
|
responses.append(check_complete)
|
||
|
|
||
|
# Initialize widget
|
||
|
responses = []
|
||
|
w = ConsoleWidget()
|
||
|
w._append_plain_text('Header\n')
|
||
|
w._prompt = 'prompt>'
|
||
|
w._show_prompt()
|
||
|
w.kernel_client = TestIPyKernelClient()
|
||
|
|
||
|
# Execute incomplete statement inside a block
|
||
|
code = '\n'.join(["if True:", " a = 1"])
|
||
|
w._set_input_buffer(code)
|
||
|
w.execute(interactive=True)
|
||
|
assert responses == [('incomplete', 4)]
|
||
|
|
||
|
# Execute complete statement inside a block
|
||
|
responses = []
|
||
|
code = '\n'.join(["if True:", " a = 1\n\n"])
|
||
|
w._set_input_buffer(code)
|
||
|
w.execute(interactive=True)
|
||
|
assert responses == [('complete', None)]
|