| """Utilities for testing with Tkinter""" | |
| import functools | |
| def run_in_tk_mainloop(delay=1): | |
| """Decorator for running a test method with a real Tk mainloop. | |
| This starts a Tk mainloop before running the test, and stops it | |
| at the end. This is faster and more robust than the common | |
| alternative method of calling .update() and/or .update_idletasks(). | |
| Test methods using this must be written as generator functions, | |
| using "yield" to allow the mainloop to process events and "after" | |
| callbacks, and then continue the test from that point. | |
| The delay argument is passed into root.after(...) calls as the number | |
| of ms to wait before passing execution back to the generator function. | |
| This also assumes that the test class has a .root attribute, | |
| which is a tkinter.Tk object. | |
| For example (from test_sidebar.py): | |
| @run_test_with_tk_mainloop() | |
| def test_single_empty_input(self): | |
| self.do_input('\n') | |
| yield | |
| self.assert_sidebar_lines_end_with(['>>>', '>>>']) | |
| """ | |
| def decorator(test_method): | |
| @functools.wraps(test_method) | |
| def new_test_method(self): | |
| test_generator = test_method(self) | |
| root = self.root | |
| # Exceptions raised by self.assert...() need to be raised | |
| # outside of the after() callback in order for the test | |
| # harness to capture them. | |
| exception = None | |
| def after_callback(): | |
| nonlocal exception | |
| try: | |
| next(test_generator) | |
| except StopIteration: | |
| root.quit() | |
| except Exception as exc: | |
| exception = exc | |
| root.quit() | |
| else: | |
| # Schedule the Tk mainloop to call this function again, | |
| # using a robust method of ensuring that it gets a | |
| # chance to process queued events before doing so. | |
| # See: https://stackoverflow.com/q/18499082#comment65004099_38817470 | |
| root.after(delay, root.after_idle, after_callback) | |
| root.after(0, root.after_idle, after_callback) | |
| root.mainloop() | |
| if exception: | |
| raise exception | |
| return new_test_method | |
| return decorator |