test_00_console_widget.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. import sys
  2. import unittest
  3. from flaky import flaky
  4. import pytest
  5. from qtpy import QtCore, QtGui, QtWidgets
  6. from qtpy.QtTest import QTest
  7. from qtconsole.console_widget import ConsoleWidget
  8. from qtconsole.qtconsoleapp import JupyterQtConsoleApp
  9. from . import no_display
  10. if sys.version[0] == '2': # Python 2
  11. from IPython.core.inputsplitter import InputSplitter as TransformerManager
  12. else:
  13. from IPython.core.inputtransformer2 import TransformerManager
  14. SHELL_TIMEOUT = 20000
  15. @pytest.fixture
  16. def qtconsole(qtbot):
  17. """Qtconsole fixture."""
  18. # Create a console
  19. console = JupyterQtConsoleApp()
  20. console.initialize(argv=[])
  21. qtbot.addWidget(console.window)
  22. console.window.confirm_exit = False
  23. console.window.show()
  24. return console
  25. @flaky(max_runs=3)
  26. @pytest.mark.parametrize(
  27. "debug", [True, False])
  28. def test_scroll(qtconsole, qtbot, debug):
  29. """
  30. Make sure the scrolling works.
  31. """
  32. window = qtconsole.window
  33. shell = window.active_frontend
  34. control = shell._control
  35. scroll_bar = control.verticalScrollBar()
  36. # Wait until the console is fully up
  37. qtbot.waitUntil(lambda: shell._prompt_html is not None,
  38. timeout=SHELL_TIMEOUT)
  39. assert scroll_bar.value() == 0
  40. # Define a function with loads of output
  41. # Check the outputs are working as well
  42. code = ["import time",
  43. "def print_numbers():",
  44. " for i in range(1000):",
  45. " print(i)",
  46. " time.sleep(.01)"]
  47. for line in code:
  48. qtbot.keyClicks(control, line)
  49. qtbot.keyClick(control, QtCore.Qt.Key_Enter)
  50. with qtbot.waitSignal(shell.executed):
  51. qtbot.keyClick(control, QtCore.Qt.Key_Enter,
  52. modifier=QtCore.Qt.ShiftModifier)
  53. def run_line(line, block=True):
  54. qtbot.keyClicks(control, line)
  55. if block:
  56. with qtbot.waitSignal(shell.executed):
  57. qtbot.keyClick(control, QtCore.Qt.Key_Enter,
  58. modifier=QtCore.Qt.ShiftModifier)
  59. else:
  60. qtbot.keyClick(control, QtCore.Qt.Key_Enter,
  61. modifier=QtCore.Qt.ShiftModifier)
  62. if debug:
  63. # Enter debug
  64. run_line('%debug print()', block=False)
  65. qtbot.keyClick(control, QtCore.Qt.Key_Enter)
  66. # redefine run_line
  67. def run_line(line, block=True):
  68. qtbot.keyClicks(control, '!' + line)
  69. qtbot.keyClick(control, QtCore.Qt.Key_Enter,
  70. modifier=QtCore.Qt.ShiftModifier)
  71. if block:
  72. qtbot.waitUntil(
  73. lambda: control.toPlainText().strip(
  74. ).split()[-1] == "ipdb>")
  75. prev_position = scroll_bar.value()
  76. # Create a bunch of inputs
  77. for i in range(20):
  78. run_line('a = 1')
  79. assert scroll_bar.value() > prev_position
  80. # Put the scroll bar higher and check it doesn't move
  81. prev_position = scroll_bar.value() + scroll_bar.pageStep() // 2
  82. scroll_bar.setValue(prev_position)
  83. for i in range(2):
  84. run_line('a')
  85. assert scroll_bar.value() == prev_position
  86. # add more input and check it moved
  87. for i in range(10):
  88. run_line('a')
  89. assert scroll_bar.value() > prev_position
  90. prev_position = scroll_bar.value()
  91. # Run the printing function
  92. run_line('print_numbers()', block=False)
  93. qtbot.wait(1000)
  94. # Check everything advances
  95. assert scroll_bar.value() > prev_position
  96. # move up
  97. prev_position = scroll_bar.value() - scroll_bar.pageStep()
  98. scroll_bar.setValue(prev_position)
  99. qtbot.wait(1000)
  100. # Check position stayed the same
  101. assert scroll_bar.value() == prev_position
  102. # reset position
  103. prev_position = scroll_bar.maximum() - (scroll_bar.pageStep() * 8) // 10
  104. scroll_bar.setValue(prev_position)
  105. qtbot.wait(1000)
  106. assert scroll_bar.value() > prev_position
  107. @flaky(max_runs=3)
  108. def test_input(qtconsole, qtbot):
  109. """
  110. Test input function
  111. """
  112. window = qtconsole.window
  113. shell = window.active_frontend
  114. control = shell._control
  115. # Wait until the console is fully up
  116. qtbot.waitUntil(lambda: shell._prompt_html is not None,
  117. timeout=SHELL_TIMEOUT)
  118. with qtbot.waitSignal(shell.executed):
  119. shell.execute("import time")
  120. if sys.version[0] == '2':
  121. input_function = 'raw_input'
  122. else:
  123. input_function = 'input'
  124. shell.execute("print(" + input_function + "('name: ')); time.sleep(3)")
  125. qtbot.waitUntil(lambda: control.toPlainText().split()[-1] == 'name:')
  126. qtbot.keyClicks(control, 'test')
  127. qtbot.keyClick(control, QtCore.Qt.Key_Enter)
  128. qtbot.waitUntil(lambda: not shell._reading)
  129. qtbot.keyClick(control, 'z', modifier=QtCore.Qt.ControlModifier)
  130. for i in range(10):
  131. qtbot.keyClick(control, QtCore.Qt.Key_Backspace)
  132. qtbot.waitUntil(lambda: shell._prompt_html is not None,
  133. timeout=SHELL_TIMEOUT)
  134. assert 'name: test\ntest' in control.toPlainText()
  135. @flaky(max_runs=3)
  136. def test_debug(qtconsole, qtbot):
  137. """
  138. Make sure the cursor works while debugging
  139. It might not because the console is "_executing"
  140. """
  141. window = qtconsole.window
  142. shell = window.active_frontend
  143. control = shell._control
  144. # Wait until the console is fully up
  145. qtbot.waitUntil(lambda: shell._prompt_html is not None,
  146. timeout=SHELL_TIMEOUT)
  147. # Enter execution
  148. code = "%debug range(1)"
  149. qtbot.keyClicks(control, code)
  150. qtbot.keyClick(control, QtCore.Qt.Key_Enter,
  151. modifier=QtCore.Qt.ShiftModifier)
  152. qtbot.waitUntil(
  153. lambda: control.toPlainText().strip().split()[-1] == "ipdb>",
  154. timeout=SHELL_TIMEOUT)
  155. # We should be able to move the cursor while debugging
  156. qtbot.keyClicks(control, "abd")
  157. qtbot.wait(100)
  158. qtbot.keyClick(control, QtCore.Qt.Key_Left)
  159. qtbot.keyClick(control, 'c')
  160. qtbot.wait(100)
  161. assert control.toPlainText().strip().split()[-1] == "abcd"
  162. @pytest.mark.skipif(no_display, reason="Doesn't work without a display")
  163. class TestConsoleWidget(unittest.TestCase):
  164. @classmethod
  165. def setUpClass(cls):
  166. """ Create the application for the test case.
  167. """
  168. cls._app = QtWidgets.QApplication.instance()
  169. if cls._app is None:
  170. cls._app = QtWidgets.QApplication([])
  171. cls._app.setQuitOnLastWindowClosed(False)
  172. @classmethod
  173. def tearDownClass(cls):
  174. """ Exit the application.
  175. """
  176. QtWidgets.QApplication.quit()
  177. def assert_text_equal(self, cursor, text):
  178. cursor.select(cursor.Document)
  179. selection = cursor.selectedText()
  180. self.assertEqual(selection, text)
  181. def test_special_characters(self):
  182. """ Are special characters displayed correctly?
  183. """
  184. w = ConsoleWidget()
  185. cursor = w._get_prompt_cursor()
  186. test_inputs = ['xyz\b\b=\n',
  187. 'foo\b\nbar\n',
  188. 'foo\b\nbar\r\n',
  189. 'abc\rxyz\b\b=']
  190. expected_outputs = [u'x=z\u2029',
  191. u'foo\u2029bar\u2029',
  192. u'foo\u2029bar\u2029',
  193. 'x=z']
  194. for i, text in enumerate(test_inputs):
  195. w._insert_plain_text(cursor, text)
  196. self.assert_text_equal(cursor, expected_outputs[i])
  197. # clear all the text
  198. cursor.insertText('')
  199. def test_link_handling(self):
  200. noKeys = QtCore.Qt
  201. noButton = QtCore.Qt.MouseButton(0)
  202. noButtons = QtCore.Qt.MouseButtons(0)
  203. noModifiers = QtCore.Qt.KeyboardModifiers(0)
  204. MouseMove = QtCore.QEvent.MouseMove
  205. QMouseEvent = QtGui.QMouseEvent
  206. w = ConsoleWidget()
  207. cursor = w._get_prompt_cursor()
  208. w._insert_html(cursor, '<a href="http://python.org">written in</a>')
  209. obj = w._control
  210. tip = QtWidgets.QToolTip
  211. self.assertEqual(tip.text(), u'')
  212. # should be somewhere else
  213. elsewhereEvent = QMouseEvent(MouseMove, QtCore.QPoint(50,50),
  214. noButton, noButtons, noModifiers)
  215. w.eventFilter(obj, elsewhereEvent)
  216. self.assertEqual(tip.isVisible(), False)
  217. self.assertEqual(tip.text(), u'')
  218. # should be over text
  219. overTextEvent = QMouseEvent(MouseMove, QtCore.QPoint(1,5),
  220. noButton, noButtons, noModifiers)
  221. w.eventFilter(obj, overTextEvent)
  222. self.assertEqual(tip.isVisible(), True)
  223. self.assertEqual(tip.text(), "http://python.org")
  224. # should still be over text
  225. stillOverTextEvent = QMouseEvent(MouseMove, QtCore.QPoint(1,5),
  226. noButton, noButtons, noModifiers)
  227. w.eventFilter(obj, stillOverTextEvent)
  228. self.assertEqual(tip.isVisible(), True)
  229. self.assertEqual(tip.text(), "http://python.org")
  230. def test_width_height(self):
  231. # width()/height() QWidget properties should not be overridden.
  232. w = ConsoleWidget()
  233. self.assertEqual(w.width(), QtWidgets.QWidget.width(w))
  234. self.assertEqual(w.height(), QtWidgets.QWidget.height(w))
  235. def test_prompt_cursors(self):
  236. """Test the cursors that keep track of where the prompt begins and
  237. ends"""
  238. w = ConsoleWidget()
  239. w._prompt = 'prompt>'
  240. doc = w._control.document()
  241. # Fill up the QTextEdit area with the maximum number of blocks
  242. doc.setMaximumBlockCount(10)
  243. for _ in range(9):
  244. w._append_plain_text('line\n')
  245. # Draw the prompt, this should cause the first lines to be deleted
  246. w._show_prompt()
  247. self.assertEqual(doc.blockCount(), 10)
  248. # _prompt_pos should be at the end of the document
  249. self.assertEqual(w._prompt_pos, w._get_end_pos())
  250. # _append_before_prompt_pos should be at the beginning of the prompt
  251. self.assertEqual(w._append_before_prompt_pos,
  252. w._prompt_pos - len(w._prompt))
  253. # insert some more text without drawing a new prompt
  254. w._append_plain_text('line\n')
  255. self.assertEqual(w._prompt_pos,
  256. w._get_end_pos() - len('line\n'))
  257. self.assertEqual(w._append_before_prompt_pos,
  258. w._prompt_pos - len(w._prompt))
  259. # redraw the prompt
  260. w._show_prompt()
  261. self.assertEqual(w._prompt_pos, w._get_end_pos())
  262. self.assertEqual(w._append_before_prompt_pos,
  263. w._prompt_pos - len(w._prompt))
  264. # insert some text before the prompt
  265. w._append_plain_text('line', before_prompt=True)
  266. self.assertEqual(w._prompt_pos, w._get_end_pos())
  267. self.assertEqual(w._append_before_prompt_pos,
  268. w._prompt_pos - len(w._prompt))
  269. def test_select_all(self):
  270. w = ConsoleWidget()
  271. w._append_plain_text('Header\n')
  272. w._prompt = 'prompt>'
  273. w._show_prompt()
  274. control = w._control
  275. app = QtWidgets.QApplication.instance()
  276. cursor = w._get_cursor()
  277. w._insert_plain_text_into_buffer(cursor, "if:\n pass")
  278. cursor.clearSelection()
  279. control.setTextCursor(cursor)
  280. # "select all" action selects cell first
  281. w.select_all_smart()
  282. QTest.keyClick(control, QtCore.Qt.Key_C, QtCore.Qt.ControlModifier)
  283. copied = app.clipboard().text()
  284. self.assertEqual(copied, 'if:\n> pass')
  285. # # "select all" action triggered a second time selects whole document
  286. w.select_all_smart()
  287. QTest.keyClick(control, QtCore.Qt.Key_C, QtCore.Qt.ControlModifier)
  288. copied = app.clipboard().text()
  289. self.assertEqual(copied, 'Header\nprompt>if:\n> pass')
  290. def test_keypresses(self):
  291. """Test the event handling code for keypresses."""
  292. w = ConsoleWidget()
  293. w._append_plain_text('Header\n')
  294. w._prompt = 'prompt>'
  295. w._show_prompt()
  296. app = QtWidgets.QApplication.instance()
  297. control = w._control
  298. # Test setting the input buffer
  299. w._set_input_buffer('test input')
  300. self.assertEqual(w._get_input_buffer(), 'test input')
  301. # Ctrl+K kills input until EOL
  302. w._set_input_buffer('test input')
  303. c = control.textCursor()
  304. c.setPosition(c.position() - 3)
  305. control.setTextCursor(c)
  306. QTest.keyClick(control, QtCore.Qt.Key_K, QtCore.Qt.ControlModifier)
  307. self.assertEqual(w._get_input_buffer(), 'test in')
  308. # Ctrl+V pastes
  309. w._set_input_buffer('test input ')
  310. app.clipboard().setText('pasted text')
  311. QTest.keyClick(control, QtCore.Qt.Key_V, QtCore.Qt.ControlModifier)
  312. self.assertEqual(w._get_input_buffer(), 'test input pasted text')
  313. self.assertEqual(control.document().blockCount(), 2)
  314. # Paste should strip indentation
  315. w._set_input_buffer('test input ')
  316. app.clipboard().setText(' pasted text')
  317. QTest.keyClick(control, QtCore.Qt.Key_V, QtCore.Qt.ControlModifier)
  318. self.assertEqual(w._get_input_buffer(), 'test input pasted text')
  319. self.assertEqual(control.document().blockCount(), 2)
  320. # Multiline paste, should also show continuation marks
  321. w._set_input_buffer('test input ')
  322. app.clipboard().setText('line1\nline2\nline3')
  323. QTest.keyClick(control, QtCore.Qt.Key_V, QtCore.Qt.ControlModifier)
  324. self.assertEqual(w._get_input_buffer(),
  325. 'test input line1\nline2\nline3')
  326. self.assertEqual(control.document().blockCount(), 4)
  327. self.assertEqual(control.document().findBlockByNumber(1).text(),
  328. 'prompt>test input line1')
  329. self.assertEqual(control.document().findBlockByNumber(2).text(),
  330. '> line2')
  331. self.assertEqual(control.document().findBlockByNumber(3).text(),
  332. '> line3')
  333. # Multiline paste should strip indentation intelligently
  334. # in the case where pasted text has leading whitespace on first line
  335. # and we're pasting into indented position
  336. w._set_input_buffer(' ')
  337. app.clipboard().setText(' If 1:\n pass')
  338. QTest.keyClick(control, QtCore.Qt.Key_V, QtCore.Qt.ControlModifier)
  339. self.assertEqual(w._get_input_buffer(),
  340. ' If 1:\n pass')
  341. # Ctrl+Backspace should intelligently remove the last word
  342. w._set_input_buffer("foo = ['foo', 'foo', 'foo', \n"
  343. " 'bar', 'bar', 'bar']")
  344. QTest.keyClick(control, QtCore.Qt.Key_Backspace,
  345. QtCore.Qt.ControlModifier)
  346. self.assertEqual(w._get_input_buffer(),
  347. ("foo = ['foo', 'foo', 'foo', \n"
  348. " 'bar', 'bar', '"))
  349. QTest.keyClick(control, QtCore.Qt.Key_Backspace,
  350. QtCore.Qt.ControlModifier)
  351. QTest.keyClick(control, QtCore.Qt.Key_Backspace,
  352. QtCore.Qt.ControlModifier)
  353. self.assertEqual(w._get_input_buffer(),
  354. ("foo = ['foo', 'foo', 'foo', \n"
  355. " '"))
  356. QTest.keyClick(control, QtCore.Qt.Key_Backspace,
  357. QtCore.Qt.ControlModifier)
  358. self.assertEqual(w._get_input_buffer(),
  359. ("foo = ['foo', 'foo', 'foo', \n"
  360. ""))
  361. QTest.keyClick(control, QtCore.Qt.Key_Backspace,
  362. QtCore.Qt.ControlModifier)
  363. self.assertEqual(w._get_input_buffer(),
  364. "foo = ['foo', 'foo', 'foo',")
  365. # Ctrl+Delete should intelligently remove the next word
  366. w._set_input_buffer("foo = ['foo', 'foo', 'foo', \n"
  367. " 'bar', 'bar', 'bar']")
  368. c = control.textCursor()
  369. c.setPosition(35)
  370. control.setTextCursor(c)
  371. QTest.keyClick(control, QtCore.Qt.Key_Delete,
  372. QtCore.Qt.ControlModifier)
  373. self.assertEqual(w._get_input_buffer(),
  374. ("foo = ['foo', 'foo', ', \n"
  375. " 'bar', 'bar', 'bar']"))
  376. QTest.keyClick(control, QtCore.Qt.Key_Delete,
  377. QtCore.Qt.ControlModifier)
  378. self.assertEqual(w._get_input_buffer(),
  379. ("foo = ['foo', 'foo', \n"
  380. " 'bar', 'bar', 'bar']"))
  381. QTest.keyClick(control, QtCore.Qt.Key_Delete,
  382. QtCore.Qt.ControlModifier)
  383. self.assertEqual(w._get_input_buffer(),
  384. "foo = ['foo', 'foo', 'bar', 'bar', 'bar']")
  385. w._set_input_buffer("foo = ['foo', 'foo', 'foo', \n"
  386. " 'bar', 'bar', 'bar']")
  387. c = control.textCursor()
  388. c.setPosition(48)
  389. control.setTextCursor(c)
  390. QTest.keyClick(control, QtCore.Qt.Key_Delete,
  391. QtCore.Qt.ControlModifier)
  392. self.assertEqual(w._get_input_buffer(),
  393. ("foo = ['foo', 'foo', 'foo', \n"
  394. "'bar', 'bar', 'bar']"))
  395. # Left and right keys should respect the continuation prompt
  396. w._set_input_buffer("line 1\n"
  397. "line 2\n"
  398. "line 3")
  399. c = control.textCursor()
  400. c.setPosition(20) # End of line 1
  401. control.setTextCursor(c)
  402. QTest.keyClick(control, QtCore.Qt.Key_Right)
  403. # Cursor should have moved after the continuation prompt
  404. self.assertEqual(control.textCursor().position(), 23)
  405. QTest.keyClick(control, QtCore.Qt.Key_Left)
  406. # Cursor should have moved to the end of the previous line
  407. self.assertEqual(control.textCursor().position(), 20)
  408. # TODO: many more keybindings
  409. def test_indent(self):
  410. """Test the event handling code for indent/dedent keypresses ."""
  411. w = ConsoleWidget()
  412. w._append_plain_text('Header\n')
  413. w._prompt = 'prompt>'
  414. w._show_prompt()
  415. control = w._control
  416. # TAB with multiline selection should block-indent
  417. w._set_input_buffer("")
  418. c = control.textCursor()
  419. pos=c.position()
  420. w._set_input_buffer("If 1:\n pass")
  421. c.setPosition(pos, QtGui.QTextCursor.KeepAnchor)
  422. control.setTextCursor(c)
  423. QTest.keyClick(control, QtCore.Qt.Key_Tab)
  424. self.assertEqual(w._get_input_buffer()," If 1:\n pass")
  425. # TAB with multiline selection, should block-indent to next multiple
  426. # of 4 spaces, if first line has 0 < indent < 4
  427. w._set_input_buffer("")
  428. c = control.textCursor()
  429. pos=c.position()
  430. w._set_input_buffer(" If 2:\n pass")
  431. c.setPosition(pos, QtGui.QTextCursor.KeepAnchor)
  432. control.setTextCursor(c)
  433. QTest.keyClick(control, QtCore.Qt.Key_Tab)
  434. self.assertEqual(w._get_input_buffer()," If 2:\n pass")
  435. # Shift-TAB with multiline selection should block-dedent
  436. w._set_input_buffer("")
  437. c = control.textCursor()
  438. pos=c.position()
  439. w._set_input_buffer(" If 3:\n pass")
  440. c.setPosition(pos, QtGui.QTextCursor.KeepAnchor)
  441. control.setTextCursor(c)
  442. QTest.keyClick(control, QtCore.Qt.Key_Backtab)
  443. self.assertEqual(w._get_input_buffer(),"If 3:\n pass")
  444. def test_complete(self):
  445. class TestKernelClient(object):
  446. def is_complete(self, source):
  447. calls.append(source)
  448. return msg_id
  449. w = ConsoleWidget()
  450. cursor = w._get_prompt_cursor()
  451. w._execute = lambda *args: calls.append(args)
  452. w.kernel_client = TestKernelClient()
  453. msg_id = object()
  454. calls = []
  455. # test incomplete statement (no _execute called, but indent added)
  456. w.execute("thing", interactive=True)
  457. self.assertEqual(calls, ["thing"])
  458. calls = []
  459. w._handle_is_complete_reply(
  460. dict(parent_header=dict(msg_id=msg_id),
  461. content=dict(status="incomplete", indent="!!!")))
  462. self.assert_text_equal(cursor, u"thing\u2029> !!!")
  463. self.assertEqual(calls, [])
  464. # test complete statement (_execute called)
  465. msg_id = object()
  466. w.execute("else", interactive=True)
  467. self.assertEqual(calls, ["else"])
  468. calls = []
  469. w._handle_is_complete_reply(
  470. dict(parent_header=dict(msg_id=msg_id),
  471. content=dict(status="complete", indent="###")))
  472. self.assertEqual(calls, [("else", False)])
  473. calls = []
  474. self.assert_text_equal(cursor, u"thing\u2029> !!!else\u2029")
  475. # test missing answer from is_complete
  476. msg_id = object()
  477. w.execute("done", interactive=True)
  478. self.assertEqual(calls, ["done"])
  479. calls = []
  480. self.assert_text_equal(cursor, u"thing\u2029> !!!else\u2029")
  481. w._trigger_is_complete_callback()
  482. self.assert_text_equal(cursor, u"thing\u2029> !!!else\u2029\u2029> ")
  483. # assert that late answer isn't destroying anything
  484. w._handle_is_complete_reply(
  485. dict(parent_header=dict(msg_id=msg_id),
  486. content=dict(status="complete", indent="###")))
  487. self.assertEqual(calls, [])
  488. def test_complete_python(self):
  489. """Test that is_complete is working correctly for Python."""
  490. # Kernel client to test the responses of is_complete
  491. class TestIPyKernelClient(object):
  492. def is_complete(self, source):
  493. tm = TransformerManager()
  494. check_complete = tm.check_complete(source)
  495. responses.append(check_complete)
  496. # Initialize widget
  497. responses = []
  498. w = ConsoleWidget()
  499. w._append_plain_text('Header\n')
  500. w._prompt = 'prompt>'
  501. w._show_prompt()
  502. w.kernel_client = TestIPyKernelClient()
  503. # Execute incomplete statement inside a block
  504. code = '\n'.join(["if True:", " a = 1"])
  505. w._set_input_buffer(code)
  506. w.execute(interactive=True)
  507. assert responses == [('incomplete', 4)]
  508. # Execute complete statement inside a block
  509. responses = []
  510. code = '\n'.join(["if True:", " a = 1\n\n"])
  511. w._set_input_buffer(code)
  512. w.execute(interactive=True)
  513. assert responses == [('complete', None)]