2021-04-28 19:27:55 -03:00
|
|
|
"""Utilities for testing with Tkinter"""
|
|
|
|
import functools
|
|
|
|
|
|
|
|
|
2021-06-02 20:53:41 -03:00
|
|
|
def run_in_tk_mainloop(delay=1):
|
2021-04-28 19:27:55 -03:00
|
|
|
"""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.
|
|
|
|
|
2021-06-02 20:53:41 -03:00
|
|
|
The delay argument is passed into root.after(...) calls as the number
|
|
|
|
of ms to wait before passing execution back to the generator function.
|
|
|
|
|
2021-04-28 19:27:55 -03:00
|
|
|
This also assumes that the test class has a .root attribute,
|
|
|
|
which is a tkinter.Tk object.
|
|
|
|
|
|
|
|
For example (from test_sidebar.py):
|
|
|
|
|
2021-06-02 20:53:41 -03:00
|
|
|
@run_test_with_tk_mainloop()
|
2021-04-28 19:27:55 -03:00
|
|
|
def test_single_empty_input(self):
|
|
|
|
self.do_input('\n')
|
|
|
|
yield
|
|
|
|
self.assert_sidebar_lines_end_with(['>>>', '>>>'])
|
|
|
|
"""
|
2021-06-02 20:53:41 -03:00
|
|
|
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
|