diff options
Diffstat (limited to 'Lib/idlelib/idle_test')
| -rw-r--r-- | Lib/idlelib/idle_test/htest.py | 14 | ||||
| -rw-r--r-- | Lib/idlelib/idle_test/test_codecontext.py | 77 | ||||
| -rw-r--r-- | Lib/idlelib/idle_test/test_sidebar.py | 351 |
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) |
