summaryrefslogtreecommitdiffstats
path: root/Lib/idlelib/idle_test
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/idlelib/idle_test')
-rw-r--r--Lib/idlelib/idle_test/htest.py14
-rw-r--r--Lib/idlelib/idle_test/test_codecontext.py77
-rw-r--r--Lib/idlelib/idle_test/test_sidebar.py351
3 files changed, 403 insertions, 39 deletions
diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py
index 20e5e90..f2f37e1 100644
--- a/Lib/idlelib/idle_test/htest.py
+++ b/Lib/idlelib/idle_test/htest.py
@@ -67,6 +67,7 @@ outwin.OutputWindow (indirectly being tested with grep test)
import idlelib.pyshell # Set Windows DPI awareness before Tk().
from importlib import import_module
+import textwrap
import tkinter as tk
from tkinter.ttk import Scrollbar
tk.NoDefaultRoot()
@@ -205,6 +206,19 @@ _io_binding_spec = {
"Check that changes were saved by opening the file elsewhere."
}
+_linenumbers_drag_scrolling_spec = {
+ 'file': 'sidebar',
+ 'kwds': {},
+ 'msg': textwrap.dedent("""\
+ Click on the line numbers and drag down below the edge of the
+ window, moving the mouse a bit and then leaving it there for a while.
+ The text and line numbers should gradually scroll down, with the
+ selection updated continuously.
+ Do the same as above, dragging to above the window. The text and line
+ numbers should gradually scroll up, with the selection updated
+ continuously."""),
+ }
+
_multi_call_spec = {
'file': 'multicall',
'kwds': {},
diff --git a/Lib/idlelib/idle_test/test_codecontext.py b/Lib/idlelib/idle_test/test_codecontext.py
index c6c8e8e..3ec49e9 100644
--- a/Lib/idlelib/idle_test/test_codecontext.py
+++ b/Lib/idlelib/idle_test/test_codecontext.py
@@ -4,7 +4,7 @@ from idlelib import codecontext
import unittest
import unittest.mock
from test.support import requires
-from tkinter import Tk, Frame, Text, TclError
+from tkinter import NSEW, Tk, Frame, Text, TclError
from unittest import mock
import re
@@ -62,7 +62,7 @@ class CodeContextTest(unittest.TestCase):
text.insert('1.0', code_sample)
# Need to pack for creation of code context text widget.
frame.pack(side='left', fill='both', expand=1)
- text.pack(side='top', fill='both', expand=1)
+ text.grid(row=1, column=1, sticky=NSEW)
cls.editor = DummyEditwin(root, frame, text)
codecontext.idleConf.userCfg = testcfg
@@ -77,6 +77,7 @@ class CodeContextTest(unittest.TestCase):
def setUp(self):
self.text.yview(0)
+ self.text['font'] = 'TkFixedFont'
self.cc = codecontext.CodeContext(self.editor)
self.highlight_cfg = {"background": '#abcdef',
@@ -86,10 +87,18 @@ class CodeContextTest(unittest.TestCase):
if element == 'context':
return self.highlight_cfg
return orig_idleConf_GetHighlight(theme, element)
- patcher = unittest.mock.patch.object(
+ GetHighlight_patcher = unittest.mock.patch.object(
codecontext.idleConf, 'GetHighlight', mock_idleconf_GetHighlight)
- patcher.start()
- self.addCleanup(patcher.stop)
+ GetHighlight_patcher.start()
+ self.addCleanup(GetHighlight_patcher.stop)
+
+ self.font_override = 'TkFixedFont'
+ def mock_idleconf_GetFont(root, configType, section):
+ return self.font_override
+ GetFont_patcher = unittest.mock.patch.object(
+ codecontext.idleConf, 'GetFont', mock_idleconf_GetFont)
+ GetFont_patcher.start()
+ self.addCleanup(GetFont_patcher.stop)
def tearDown(self):
if self.cc.context:
@@ -339,69 +348,59 @@ class CodeContextTest(unittest.TestCase):
def test_font(self):
eq = self.assertEqual
cc = self.cc
- save_font = cc.text['font']
+
+ orig_font = cc.text['font']
test_font = 'TkTextFont'
+ self.assertNotEqual(orig_font, test_font)
# Ensure code context is not active.
if cc.context is not None:
cc.toggle_code_context_event()
+ self.font_override = test_font
# Nothing breaks or changes with inactive code context.
- cc.update_font(test_font)
+ cc.update_font()
- # Activate code context, but no change to font.
- cc.toggle_code_context_event()
- eq(cc.context['font'], save_font)
- # Call font update with the existing font.
- cc.update_font(save_font)
- eq(cc.context['font'], save_font)
+ # Activate code context, previous font change is immediately effective.
cc.toggle_code_context_event()
-
- # Change text widget font and activate code context.
- cc.text['font'] = test_font
- cc.toggle_code_context_event(test_font)
eq(cc.context['font'], test_font)
- # Just call the font update.
- cc.update_font(save_font)
- eq(cc.context['font'], save_font)
- cc.text['font'] = save_font
+ # Call the font update, change is picked up.
+ self.font_override = orig_font
+ cc.update_font()
+ eq(cc.context['font'], orig_font)
def test_highlight_colors(self):
eq = self.assertEqual
cc = self.cc
- save_colors = dict(self.highlight_cfg)
+
+ orig_colors = dict(self.highlight_cfg)
test_colors = {'background': '#222222', 'foreground': '#ffff00'}
+ def assert_colors_are_equal(colors):
+ eq(cc.context['background'], colors['background'])
+ eq(cc.context['foreground'], colors['foreground'])
+
# Ensure code context is not active.
if cc.context:
cc.toggle_code_context_event()
+ self.highlight_cfg = test_colors
# Nothing breaks with inactive code context.
cc.update_highlight_colors()
- # Activate code context, but no change to colors.
+ # Activate code context, previous colors change is immediately effective.
cc.toggle_code_context_event()
- eq(cc.context['background'], save_colors['background'])
- eq(cc.context['foreground'], save_colors['foreground'])
+ assert_colors_are_equal(test_colors)
- # Call colors update, but no change to font.
+ # Call colors update with no change to the configured colors.
cc.update_highlight_colors()
- eq(cc.context['background'], save_colors['background'])
- eq(cc.context['foreground'], save_colors['foreground'])
- cc.toggle_code_context_event()
-
- # Change colors and activate code context.
- self.highlight_cfg = test_colors
- cc.toggle_code_context_event()
- eq(cc.context['background'], test_colors['background'])
- eq(cc.context['foreground'], test_colors['foreground'])
+ assert_colors_are_equal(test_colors)
- # Change colors and call highlight colors update.
- self.highlight_cfg = save_colors
+ # Call the colors update with code context active, change is picked up.
+ self.highlight_cfg = orig_colors
cc.update_highlight_colors()
- eq(cc.context['background'], save_colors['background'])
- eq(cc.context['foreground'], save_colors['foreground'])
+ assert_colors_are_equal(orig_colors)
class HelperFunctionText(unittest.TestCase):
diff --git a/Lib/idlelib/idle_test/test_sidebar.py b/Lib/idlelib/idle_test/test_sidebar.py
new file mode 100644
index 0000000..8c98a0c
--- /dev/null
+++ b/Lib/idlelib/idle_test/test_sidebar.py
@@ -0,0 +1,351 @@
+"""Test sidebar, coverage 93%"""
+from itertools import chain
+import unittest
+import unittest.mock
+from test.support import requires
+import tkinter as tk
+
+from idlelib.delegator import Delegator
+from idlelib.percolator import Percolator
+import idlelib.sidebar
+
+
+class Dummy_editwin:
+ def __init__(self, text):
+ self.text = text
+ self.text_frame = self.text.master
+ self.per = Percolator(text)
+ self.undo = Delegator()
+ self.per.insertfilter(self.undo)
+
+ def setvar(self, name, value):
+ pass
+
+ def getlineno(self, index):
+ return int(float(self.text.index(index)))
+
+
+class LineNumbersTest(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ requires('gui')
+ cls.root = tk.Tk()
+
+ cls.text_frame = tk.Frame(cls.root)
+ cls.text_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
+ cls.text_frame.rowconfigure(1, weight=1)
+ cls.text_frame.columnconfigure(1, weight=1)
+
+ cls.text = tk.Text(cls.text_frame, width=80, height=24, wrap=tk.NONE)
+ cls.text.grid(row=1, column=1, sticky=tk.NSEW)
+
+ cls.editwin = Dummy_editwin(cls.text)
+ cls.editwin.vbar = tk.Scrollbar(cls.text_frame)
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.editwin.per.close()
+ cls.root.update()
+ cls.root.destroy()
+ del cls.text, cls.text_frame, cls.editwin, cls.root
+
+ def setUp(self):
+ self.linenumber = idlelib.sidebar.LineNumbers(self.editwin)
+
+ self.highlight_cfg = {"background": '#abcdef',
+ "foreground": '#123456'}
+ orig_idleConf_GetHighlight = idlelib.sidebar.idleConf.GetHighlight
+ def mock_idleconf_GetHighlight(theme, element):
+ if element == 'linenumber':
+ return self.highlight_cfg
+ return orig_idleConf_GetHighlight(theme, element)
+ GetHighlight_patcher = unittest.mock.patch.object(
+ idlelib.sidebar.idleConf, 'GetHighlight', mock_idleconf_GetHighlight)
+ GetHighlight_patcher.start()
+ self.addCleanup(GetHighlight_patcher.stop)
+
+ self.font_override = 'TkFixedFont'
+ def mock_idleconf_GetFont(root, configType, section):
+ return self.font_override
+ GetFont_patcher = unittest.mock.patch.object(
+ idlelib.sidebar.idleConf, 'GetFont', mock_idleconf_GetFont)
+ GetFont_patcher.start()
+ self.addCleanup(GetFont_patcher.stop)
+
+ def tearDown(self):
+ self.text.delete('1.0', 'end')
+
+ def get_selection(self):
+ return tuple(map(str, self.text.tag_ranges('sel')))
+
+ def get_line_screen_position(self, line):
+ bbox = self.linenumber.sidebar_text.bbox(f'{line}.end -1c')
+ x = bbox[0] + 2
+ y = bbox[1] + 2
+ return x, y
+
+ def assert_state_disabled(self):
+ state = self.linenumber.sidebar_text.config()['state']
+ self.assertEqual(state[-1], tk.DISABLED)
+
+ def get_sidebar_text_contents(self):
+ return self.linenumber.sidebar_text.get('1.0', tk.END)
+
+ def assert_sidebar_n_lines(self, n_lines):
+ expected = '\n'.join(chain(map(str, range(1, n_lines + 1)), ['']))
+ self.assertEqual(self.get_sidebar_text_contents(), expected)
+
+ def assert_text_equals(self, expected):
+ return self.assertEqual(self.text.get('1.0', 'end'), expected)
+
+ def test_init_empty(self):
+ self.assert_sidebar_n_lines(1)
+
+ def test_init_not_empty(self):
+ self.text.insert('insert', 'foo bar\n'*3)
+ self.assert_text_equals('foo bar\n'*3 + '\n')
+ self.assert_sidebar_n_lines(4)
+
+ def test_toggle_linenumbering(self):
+ self.assertEqual(self.linenumber.is_shown, False)
+ self.linenumber.show_sidebar()
+ self.assertEqual(self.linenumber.is_shown, True)
+ self.linenumber.hide_sidebar()
+ self.assertEqual(self.linenumber.is_shown, False)
+ self.linenumber.hide_sidebar()
+ self.assertEqual(self.linenumber.is_shown, False)
+ self.linenumber.show_sidebar()
+ self.assertEqual(self.linenumber.is_shown, True)
+ self.linenumber.show_sidebar()
+ self.assertEqual(self.linenumber.is_shown, True)
+
+ def test_insert(self):
+ self.text.insert('insert', 'foobar')
+ self.assert_text_equals('foobar\n')
+ self.assert_sidebar_n_lines(1)
+ self.assert_state_disabled()
+
+ self.text.insert('insert', '\nfoo')
+ self.assert_text_equals('foobar\nfoo\n')
+ self.assert_sidebar_n_lines(2)
+ self.assert_state_disabled()
+
+ self.text.insert('insert', 'hello\n'*2)
+ self.assert_text_equals('foobar\nfoohello\nhello\n\n')
+ self.assert_sidebar_n_lines(4)
+ self.assert_state_disabled()
+
+ self.text.insert('insert', '\nworld')
+ self.assert_text_equals('foobar\nfoohello\nhello\n\nworld\n')
+ self.assert_sidebar_n_lines(5)
+ self.assert_state_disabled()
+
+ def test_delete(self):
+ self.text.insert('insert', 'foobar')
+ self.assert_text_equals('foobar\n')
+ self.text.delete('1.1', '1.3')
+ self.assert_text_equals('fbar\n')
+ self.assert_sidebar_n_lines(1)
+ self.assert_state_disabled()
+
+ self.text.insert('insert', 'foo\n'*2)
+ self.assert_text_equals('fbarfoo\nfoo\n\n')
+ self.assert_sidebar_n_lines(3)
+ self.assert_state_disabled()
+
+ # Note: deleting up to "2.end" doesn't delete the final newline.
+ self.text.delete('2.0', '2.end')
+ self.assert_text_equals('fbarfoo\n\n\n')
+ self.assert_sidebar_n_lines(3)
+ self.assert_state_disabled()
+
+ self.text.delete('1.3', 'end')
+ self.assert_text_equals('fba\n')
+ self.assert_sidebar_n_lines(1)
+ self.assert_state_disabled()
+
+ # Note: Text widgets always keep a single '\n' character at the end.
+ self.text.delete('1.0', 'end')
+ self.assert_text_equals('\n')
+ self.assert_sidebar_n_lines(1)
+ self.assert_state_disabled()
+
+ def test_sidebar_text_width(self):
+ """
+ Test that linenumber text widget is always at the minimum
+ width
+ """
+ def get_width():
+ return self.linenumber.sidebar_text.config()['width'][-1]
+
+ self.assert_sidebar_n_lines(1)
+ self.assertEqual(get_width(), 1)
+
+ self.text.insert('insert', 'foo')
+ self.assert_sidebar_n_lines(1)
+ self.assertEqual(get_width(), 1)
+
+ self.text.insert('insert', 'foo\n'*8)
+ self.assert_sidebar_n_lines(9)
+ self.assertEqual(get_width(), 1)
+
+ self.text.insert('insert', 'foo\n')
+ self.assert_sidebar_n_lines(10)
+ self.assertEqual(get_width(), 2)
+
+ self.text.insert('insert', 'foo\n')
+ self.assert_sidebar_n_lines(11)
+ self.assertEqual(get_width(), 2)
+
+ self.text.delete('insert -1l linestart', 'insert linestart')
+ self.assert_sidebar_n_lines(10)
+ self.assertEqual(get_width(), 2)
+
+ self.text.delete('insert -1l linestart', 'insert linestart')
+ self.assert_sidebar_n_lines(9)
+ self.assertEqual(get_width(), 1)
+
+ self.text.insert('insert', 'foo\n'*90)
+ self.assert_sidebar_n_lines(99)
+ self.assertEqual(get_width(), 2)
+
+ self.text.insert('insert', 'foo\n')
+ self.assert_sidebar_n_lines(100)
+ self.assertEqual(get_width(), 3)
+
+ self.text.insert('insert', 'foo\n')
+ self.assert_sidebar_n_lines(101)
+ self.assertEqual(get_width(), 3)
+
+ self.text.delete('insert -1l linestart', 'insert linestart')
+ self.assert_sidebar_n_lines(100)
+ self.assertEqual(get_width(), 3)
+
+ self.text.delete('insert -1l linestart', 'insert linestart')
+ self.assert_sidebar_n_lines(99)
+ self.assertEqual(get_width(), 2)
+
+ self.text.delete('50.0 -1c', 'end -1c')
+ self.assert_sidebar_n_lines(49)
+ self.assertEqual(get_width(), 2)
+
+ self.text.delete('5.0 -1c', 'end -1c')
+ self.assert_sidebar_n_lines(4)
+ self.assertEqual(get_width(), 1)
+
+ # Note: Text widgets always keep a single '\n' character at the end.
+ self.text.delete('1.0', 'end -1c')
+ self.assert_sidebar_n_lines(1)
+ self.assertEqual(get_width(), 1)
+
+ def test_click_selection(self):
+ self.linenumber.show_sidebar()
+ self.text.insert('1.0', 'one\ntwo\nthree\nfour\n')
+ self.root.update()
+
+ # Click on the second line.
+ x, y = self.get_line_screen_position(2)
+ self.linenumber.sidebar_text.event_generate('<Button-1>', x=x, y=y)
+ self.linenumber.sidebar_text.update()
+ self.root.update()
+
+ self.assertEqual(self.get_selection(), ('2.0', '3.0'))
+
+ def test_drag_selection(self):
+ self.linenumber.show_sidebar()
+ self.text.insert('1.0', 'one\ntwo\nthree\nfour\n')
+ self.root.update()
+
+ # Drag from the first line to the third line.
+ start_x, start_y = self.get_line_screen_position(1)
+ end_x, end_y = self.get_line_screen_position(3)
+ self.linenumber.sidebar_text.event_generate('<Button-1>',
+ x=start_x, y=start_y)
+ self.linenumber.sidebar_text.event_generate('<B1-Motion>',
+ x=start_x, y=start_y)
+ self.linenumber.sidebar_text.event_generate('<B1-Motion>',
+ x=end_x, y=end_y)
+ self.linenumber.sidebar_text.event_generate('<ButtonRelease-1>',
+ x=end_x, y=end_y)
+ self.root.update()
+
+ self.assertEqual(self.get_selection(), ('1.0', '4.0'))
+
+ def test_scroll(self):
+ self.linenumber.show_sidebar()
+ self.text.insert('1.0', 'line\n' * 100)
+ self.root.update()
+
+ # Scroll down 10 lines.
+ self.text.yview_scroll(10, 'unit')
+ self.root.update()
+ self.assertEqual(self.text.index('@0,0'), '11.0')
+ self.assertEqual(self.linenumber.sidebar_text.index('@0,0'), '11.0')
+
+ # Generate a mouse-wheel event and make sure it scrolled up or down.
+ # The meaning of the "delta" is OS-dependant, so this just checks for
+ # any change.
+ self.linenumber.sidebar_text.event_generate('<MouseWheel>',
+ x=0, y=0,
+ delta=10)
+ self.root.update()
+ self.assertNotEqual(self.text.index('@0,0'), '11.0')
+ self.assertNotEqual(self.linenumber.sidebar_text.index('@0,0'), '11.0')
+
+ def test_font(self):
+ ln = self.linenumber
+
+ orig_font = ln.sidebar_text['font']
+ test_font = 'TkTextFont'
+ self.assertNotEqual(orig_font, test_font)
+
+ # Ensure line numbers aren't shown.
+ ln.hide_sidebar()
+
+ self.font_override = test_font
+ # Nothing breaks when line numbers aren't shown.
+ ln.update_font()
+
+ # Activate line numbers, previous font change is immediately effective.
+ ln.show_sidebar()
+ self.assertEqual(ln.sidebar_text['font'], test_font)
+
+ # Call the font update with line numbers shown, change is picked up.
+ self.font_override = orig_font
+ ln.update_font()
+ self.assertEqual(ln.sidebar_text['font'], orig_font)
+
+ def test_highlight_colors(self):
+ ln = self.linenumber
+
+ orig_colors = dict(self.highlight_cfg)
+ test_colors = {'background': '#222222', 'foreground': '#ffff00'}
+
+ def assert_colors_are_equal(colors):
+ self.assertEqual(ln.sidebar_text['background'], colors['background'])
+ self.assertEqual(ln.sidebar_text['foreground'], colors['foreground'])
+
+ # Ensure line numbers aren't shown.
+ ln.hide_sidebar()
+
+ self.highlight_cfg = test_colors
+ # Nothing breaks with inactive code context.
+ ln.update_colors()
+
+ # Show line numbers, previous colors change is immediately effective.
+ ln.show_sidebar()
+ assert_colors_are_equal(test_colors)
+
+ # Call colors update with no change to the configured colors.
+ ln.update_colors()
+ assert_colors_are_equal(test_colors)
+
+ # Call the colors update with line numbers shown, change is picked up.
+ self.highlight_cfg = orig_colors
+ ln.update_colors()
+ assert_colors_are_equal(orig_colors)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)