Skip to content

Commit a85fcd4

Browse files
serhiy-storchakaclaude
authored andcommitted
gh-151678: Add more tests for tkinter.dnd (GH-152362)
Cover the drag cursor, the Motion and ButtonRelease bindings, switching between targets, the target search up the master chain, dnd_accept() returning None, and restarting after a drag has finished. (cherry picked from commit 389e00f) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 069c9c0 commit a85fcd4

1 file changed

Lines changed: 73 additions & 0 deletions

File tree

Lib/test/test_tkinter/test_dnd.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ def setUp(self):
6060
self.source = Source(self.log)
6161
self.target = Target(self.canvas, self.log)
6262

63+
def tearDown(self):
64+
# Make sure no drag-and-drop is left active between tests: the
65+
# recursion guard is a name-mangled attribute on the root.
66+
try:
67+
del self.root._DndHandler__dnd
68+
except AttributeError:
69+
pass
70+
super().tearDown()
71+
6372
def test_drag_and_drop(self):
6473
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
6574
self.assertIsNotNone(handler)
@@ -93,6 +102,70 @@ def test_no_recursive_start(self):
93102
def test_high_button_number_ignored(self):
94103
self.assertIsNone(dnd.dnd_start(self.source, FakeEvent(self.canvas, num=6)))
95104

105+
def test_restart_after_finish(self):
106+
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
107+
handler.cancel()
108+
# Once a drag has finished a new one can start.
109+
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
110+
self.assertIsNotNone(handler)
111+
handler.cancel()
112+
113+
def test_drag_cursor(self):
114+
self.canvas['cursor'] = 'watch'
115+
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
116+
# The drag cursor is shown while dragging, the original restored after.
117+
self.assertEqual(handler.save_cursor, 'watch')
118+
self.assertEqual(str(self.canvas['cursor']), 'hand2')
119+
handler.cancel()
120+
self.assertEqual(str(self.canvas['cursor']), 'watch')
121+
122+
def test_bindings_added_and_removed(self):
123+
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
124+
self.assertIn('<Motion>', self.canvas.bind())
125+
self.assertIn('<B1-ButtonRelease-1>', self.canvas.bind())
126+
handler.cancel()
127+
self.assertNotIn('<Motion>', self.canvas.bind())
128+
self.assertNotIn('<B1-ButtonRelease-1>', self.canvas.bind())
129+
130+
def test_switch_target(self):
131+
log1, log2 = [], []
132+
w1, w2 = tkinter.Frame(self.root), tkinter.Frame(self.root)
133+
target1, target2 = Target(w1, log1), Target(w2, log2)
134+
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
135+
self.canvas.winfo_containing = lambda x, y: w1
136+
handler.on_motion(FakeEvent(self.canvas)) # Enter target1.
137+
self.canvas.winfo_containing = lambda x, y: w2
138+
handler.on_motion(FakeEvent(self.canvas)) # Leave target1, enter target2.
139+
self.assertIs(handler.target, target2)
140+
self.assertEqual(log1, ['accept', 'enter', 'leave'])
141+
self.assertEqual(log2, ['accept', 'enter'])
142+
handler.cancel()
143+
144+
def test_target_in_ancestor(self):
145+
# The widget under the pointer has no dnd_accept, but an ancestor
146+
# does: the search walks up the master chain to find it.
147+
parent = tkinter.Frame(self.root)
148+
target = Target(parent, self.log)
149+
child = tkinter.Frame(parent)
150+
self.canvas.winfo_containing = lambda x, y: child
151+
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
152+
handler.on_motion(FakeEvent(self.canvas))
153+
self.assertIs(handler.target, target)
154+
self.assertEqual(self.log, ['accept', 'enter'])
155+
handler.cancel()
156+
157+
def test_accept_returning_none_continues(self):
158+
# dnd_accept() returning None means "not me, keep looking up".
159+
parent = tkinter.Frame(self.root)
160+
target = Target(parent, self.log)
161+
child = tkinter.Frame(parent)
162+
child.dnd_accept = lambda source, event: None
163+
self.canvas.winfo_containing = lambda x, y: child
164+
handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
165+
handler.on_motion(FakeEvent(self.canvas))
166+
self.assertIs(handler.target, target)
167+
handler.cancel()
168+
96169

97170
if __name__ == "__main__":
98171
unittest.main()

0 commit comments

Comments
 (0)