diff options
author | Terry Jan Reedy <tjreedy@udel.edu> | 2016-05-17 23:58:02 (GMT) |
---|---|---|
committer | Terry Jan Reedy <tjreedy@udel.edu> | 2016-05-17 23:58:02 (GMT) |
commit | fdec2a3424ad48a2f1d94362c2e4c6e493895986 (patch) | |
tree | 3fbfa6549d454db4b3192fd8cd311d674ccbfdfc /Lib | |
parent | c359af153039fa6c2fadd5b88c4eb0db325692c8 (diff) | |
download | cpython-fdec2a3424ad48a2f1d94362c2e4c6e493895986.zip cpython-fdec2a3424ad48a2f1d94362c2e4c6e493895986.tar.gz cpython-fdec2a3424ad48a2f1d94362c2e4c6e493895986.tar.bz2 |
Issue #21676: test IDLE replace dialog. Original patch by Saimadhav Heblikar.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/idlelib/ReplaceDialog.py | 38 | ||||
-rw-r--r-- | Lib/idlelib/idle_test/test_replacedialog.py | 292 |
2 files changed, 321 insertions, 9 deletions
diff --git a/Lib/idlelib/ReplaceDialog.py b/Lib/idlelib/ReplaceDialog.py index 2665a1c..f2ea22e 100644 --- a/Lib/idlelib/ReplaceDialog.py +++ b/Lib/idlelib/ReplaceDialog.py @@ -1,3 +1,8 @@ +"""Replace dialog for IDLE. Inherits SearchDialogBase for GUI. +Uses idlelib.SearchEngine for search capability. +Defines various replace related functions like replace, replace all, +replace+find. +""" from tkinter import * from idlelib import SearchEngine @@ -6,6 +11,8 @@ import re def replace(text): + """Returns a singleton ReplaceDialog instance.The single dialog + saves user entries and preferences across instances.""" root = text._root() engine = SearchEngine.get(root) if not hasattr(engine, "_replacedialog"): @@ -24,6 +31,7 @@ class ReplaceDialog(SearchDialogBase): self.replvar = StringVar(root) def open(self, text): + """Display the replace dialog""" SearchDialogBase.open(self, text) try: first = text.index("sel.first") @@ -39,6 +47,7 @@ class ReplaceDialog(SearchDialogBase): self.ok = 1 def create_entries(self): + """Create label and text entry widgets""" SearchDialogBase.create_entries(self) self.replent = self.make_entry("Replace with:", self.replvar)[0] @@ -57,9 +66,10 @@ class ReplaceDialog(SearchDialogBase): self.do_replace() def default_command(self, event=None): + "Replace and find next." if self.do_find(self.ok): - if self.do_replace(): # Only find next match if replace succeeded. - # A bad re can cause it to fail. + if self.do_replace(): # Only find next match if replace succeeded. + # A bad re can cause it to fail. self.do_find(0) def _replace_expand(self, m, repl): @@ -77,6 +87,7 @@ class ReplaceDialog(SearchDialogBase): return new def replace_all(self, event=None): + """Replace all instances of patvar with replvar in text""" prog = self.engine.getprog() if not prog: return @@ -173,6 +184,8 @@ class ReplaceDialog(SearchDialogBase): return True def show_hit(self, first, last): + """Highlight text from 'first' to 'last'. + 'first', 'last' - Text indices""" text = self.text text.mark_set("insert", first) text.tag_remove("sel", "1.0", "end") @@ -189,11 +202,13 @@ class ReplaceDialog(SearchDialogBase): SearchDialogBase.close(self, event) self.text.tag_remove("hit", "1.0", "end") -def _replace_dialog(parent): - root = Tk() - root.title("Test ReplaceDialog") + +def _replace_dialog(parent): # htest # + """htest wrapper function""" + box = Toplevel(parent) + box.title("Test ReplaceDialog") width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) + box.geometry("+%d+%d"%(x, y + 150)) # mock undo delegator methods def undo_block_start(): @@ -202,20 +217,25 @@ def _replace_dialog(parent): def undo_block_stop(): pass - text = Text(root) + text = Text(box, inactiveselectbackground='gray') text.undo_block_start = undo_block_start text.undo_block_stop = undo_block_stop text.pack() - text.insert("insert","This is a sample string.\n"*10) + text.insert("insert","This is a sample sTring\nPlus MORE.") + text.focus_set() def show_replace(): text.tag_add(SEL, "1.0", END) replace(text) text.tag_remove(SEL, "1.0", END) - button = Button(root, text="Replace", command=show_replace) + button = Button(box, text="Replace", command=show_replace) button.pack() if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_replacedialog', + verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_replace_dialog) diff --git a/Lib/idlelib/idle_test/test_replacedialog.py b/Lib/idlelib/idle_test/test_replacedialog.py new file mode 100644 index 0000000..09669f8 --- /dev/null +++ b/Lib/idlelib/idle_test/test_replacedialog.py @@ -0,0 +1,292 @@ +"""Unittest for idlelib.ReplaceDialog""" +from test.support import requires +requires('gui') + +import unittest +from unittest.mock import Mock +from tkinter import Tk, Text +from idlelib.idle_test.mock_tk import Mbox +import idlelib.SearchEngine as se +import idlelib.ReplaceDialog as rd + +orig_mbox = se.tkMessageBox +showerror = Mbox.showerror + + +class ReplaceDialogTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.root = Tk() + cls.root.withdraw() + se.tkMessageBox = Mbox + cls.engine = se.SearchEngine(cls.root) + cls.dialog = rd.ReplaceDialog(cls.root, cls.engine) + cls.dialog.ok = Mock() + cls.text = Text(cls.root) + cls.text.undo_block_start = Mock() + cls.text.undo_block_stop = Mock() + cls.dialog.text = cls.text + + @classmethod + def tearDownClass(cls): + se.tkMessageBox = orig_mbox + cls.root.destroy() + del cls.text, cls.dialog, cls.engine, cls.root + + def setUp(self): + self.text.insert('insert', 'This is a sample sTring') + + def tearDown(self): + self.engine.patvar.set('') + self.dialog.replvar.set('') + self.engine.wordvar.set(False) + self.engine.casevar.set(False) + self.engine.revar.set(False) + self.engine.wrapvar.set(True) + self.engine.backvar.set(False) + showerror.title = '' + showerror.message = '' + self.text.delete('1.0', 'end') + + def test_replace_simple(self): + # Test replace function with all options at default setting. + # Wrap around - True + # Regular Expression - False + # Match case - False + # Match word - False + # Direction - Forwards + text = self.text + equal = self.assertEqual + pv = self.engine.patvar + rv = self.dialog.replvar + replace = self.dialog.replace_it + + # test accessor method + self.engine.setpat('asdf') + equal(self.engine.getpat(), pv.get()) + + # text found and replaced + pv.set('a') + rv.set('asdf') + self.dialog.open(self.text) + replace() + equal(text.get('1.8', '1.12'), 'asdf') + + # dont "match word" case + text.mark_set('insert', '1.0') + pv.set('is') + rv.set('hello') + replace() + equal(text.get('1.2', '1.7'), 'hello') + + # dont "match case" case + pv.set('string') + rv.set('world') + replace() + equal(text.get('1.23', '1.28'), 'world') + + # without "regular expression" case + text.mark_set('insert', 'end') + text.insert('insert', '\nline42:') + before_text = text.get('1.0', 'end') + pv.set('[a-z][\d]+') + replace() + after_text = text.get('1.0', 'end') + equal(before_text, after_text) + + # test with wrap around selected and complete a cycle + text.mark_set('insert', '1.9') + pv.set('i') + rv.set('j') + replace() + equal(text.get('1.8'), 'i') + equal(text.get('2.1'), 'j') + replace() + equal(text.get('2.1'), 'j') + equal(text.get('1.8'), 'j') + before_text = text.get('1.0', 'end') + replace() + after_text = text.get('1.0', 'end') + equal(before_text, after_text) + + # text not found + before_text = text.get('1.0', 'end') + pv.set('foobar') + replace() + after_text = text.get('1.0', 'end') + equal(before_text, after_text) + + # test access method + self.dialog.find_it(0) + + def test_replace_wrap_around(self): + text = self.text + equal = self.assertEqual + pv = self.engine.patvar + rv = self.dialog.replvar + replace = self.dialog.replace_it + self.engine.wrapvar.set(False) + + # replace candidate found both after and before 'insert' + text.mark_set('insert', '1.4') + pv.set('i') + rv.set('j') + replace() + equal(text.get('1.2'), 'i') + equal(text.get('1.5'), 'j') + replace() + equal(text.get('1.2'), 'i') + equal(text.get('1.20'), 'j') + replace() + equal(text.get('1.2'), 'i') + + # replace candidate found only before 'insert' + text.mark_set('insert', '1.8') + pv.set('is') + before_text = text.get('1.0', 'end') + replace() + after_text = text.get('1.0', 'end') + equal(before_text, after_text) + + def test_replace_whole_word(self): + text = self.text + equal = self.assertEqual + pv = self.engine.patvar + rv = self.dialog.replvar + replace = self.dialog.replace_it + self.engine.wordvar.set(True) + + pv.set('is') + rv.set('hello') + replace() + equal(text.get('1.0', '1.4'), 'This') + equal(text.get('1.5', '1.10'), 'hello') + + def test_replace_match_case(self): + equal = self.assertEqual + text = self.text + pv = self.engine.patvar + rv = self.dialog.replvar + replace = self.dialog.replace_it + self.engine.casevar.set(True) + + before_text = self.text.get('1.0', 'end') + pv.set('this') + rv.set('that') + replace() + after_text = self.text.get('1.0', 'end') + equal(before_text, after_text) + + pv.set('This') + replace() + equal(text.get('1.0', '1.4'), 'that') + + def test_replace_regex(self): + equal = self.assertEqual + text = self.text + pv = self.engine.patvar + rv = self.dialog.replvar + replace = self.dialog.replace_it + self.engine.revar.set(True) + + before_text = text.get('1.0', 'end') + pv.set('[a-z][\d]+') + rv.set('hello') + replace() + after_text = text.get('1.0', 'end') + equal(before_text, after_text) + + text.insert('insert', '\nline42') + replace() + equal(text.get('2.0', '2.8'), 'linhello') + + pv.set('') + replace() + self.assertIn('error', showerror.title) + self.assertIn('Empty', showerror.message) + + pv.set('[\d') + replace() + self.assertIn('error', showerror.title) + self.assertIn('Pattern', showerror.message) + + showerror.title = '' + showerror.message = '' + pv.set('[a]') + rv.set('test\\') + replace() + self.assertIn('error', showerror.title) + self.assertIn('Invalid Replace Expression', showerror.message) + + # test access method + self.engine.setcookedpat("\'") + equal(pv.get(), "\\'") + + def test_replace_backwards(self): + equal = self.assertEqual + text = self.text + pv = self.engine.patvar + rv = self.dialog.replvar + replace = self.dialog.replace_it + self.engine.backvar.set(True) + + text.insert('insert', '\nis as ') + + pv.set('is') + rv.set('was') + replace() + equal(text.get('1.2', '1.4'), 'is') + equal(text.get('2.0', '2.3'), 'was') + replace() + equal(text.get('1.5', '1.8'), 'was') + replace() + equal(text.get('1.2', '1.5'), 'was') + + def test_replace_all(self): + text = self.text + pv = self.engine.patvar + rv = self.dialog.replvar + replace_all = self.dialog.replace_all + + text.insert('insert', '\n') + text.insert('insert', text.get('1.0', 'end')*100) + pv.set('is') + rv.set('was') + replace_all() + self.assertNotIn('is', text.get('1.0', 'end')) + + self.engine.revar.set(True) + pv.set('') + replace_all() + self.assertIn('error', showerror.title) + self.assertIn('Empty', showerror.message) + + pv.set('[s][T]') + rv.set('\\') + replace_all() + + self.engine.revar.set(False) + pv.set('text which is not present') + rv.set('foobar') + replace_all() + + def test_default_command(self): + text = self.text + pv = self.engine.patvar + rv = self.dialog.replvar + replace_find = self.dialog.default_command + equal = self.assertEqual + + pv.set('This') + rv.set('was') + replace_find() + equal(text.get('sel.first', 'sel.last'), 'was') + + self.engine.revar.set(True) + pv.set('') + replace_find() + + +if __name__ == '__main__': + unittest.main(verbosity=2) |