https://github.com/python/cpython/commit/3618730de3b51423d54642603c9e7490d2a769d1
commit: 3618730de3b51423d54642603c9e7490d2a769d1
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-06-20T13:37:55Z
summary:

[3.15] gh-151678: Add tests for tkinter.dnd (GH-151780) (GH-151790)

Drive the drag-and-drop protocol (dnd_start and the DndHandler enter/
motion/commit, leave/cancel and end callbacks).
(cherry picked from commit 2a126a534b0253cf65fb6d06da0cce72eb2eaa23)

Co-authored-by: Serhiy Storchaka <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>

files:
A Lib/test/test_tkinter/test_dnd.py

diff --git a/Lib/test/test_tkinter/test_dnd.py 
b/Lib/test/test_tkinter/test_dnd.py
new file mode 100644
index 00000000000000..501b0d7f78586c
--- /dev/null
+++ b/Lib/test/test_tkinter/test_dnd.py
@@ -0,0 +1,98 @@
+import unittest
+import tkinter
+from tkinter import dnd
+from test.support import requires
+from test.test_tkinter.support import setUpModule  # noqa: F401
+from test.test_tkinter.support import AbstractTkTest
+
+requires('gui')
+
+
+class Target:
+    def __init__(self, widget, log):
+        self.widget = widget
+        self.log = log
+        widget.dnd_accept = self.dnd_accept
+
+    def dnd_accept(self, source, event):
+        self.log.append('accept')
+        return self
+
+    def dnd_enter(self, source, event):
+        self.log.append('enter')
+
+    def dnd_motion(self, source, event):
+        self.log.append('motion')
+
+    def dnd_leave(self, source, event):
+        self.log.append('leave')
+
+    def dnd_commit(self, source, event):
+        self.log.append('commit')
+
+
+class Source:
+    def __init__(self, log):
+        self.log = log
+
+    def dnd_end(self, target, event):
+        self.log.append('end')
+
+
+class FakeEvent:
+    def __init__(self, widget, num=1):
+        self.num = num
+        self.widget = widget
+        self.x = self.y = self.x_root = self.y_root = 0
+
+
+class DndTest(AbstractTkTest, unittest.TestCase):
+
+    def setUp(self):
+        super().setUp()
+        self.canvas = tkinter.Canvas(self.root)
+        self.canvas.pack()
+        # on_motion() locates the target with winfo_containing().  Bypass that
+        # real screen lookup, which depends on the window being visible and
+        # unobscured, so the test does not hinge on the window manager.
+        self.canvas.winfo_containing = lambda x, y: self.canvas
+        self.log = []
+        self.source = Source(self.log)
+        self.target = Target(self.canvas, self.log)
+
+    def test_drag_and_drop(self):
+        handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
+        self.assertIsNotNone(handler)
+        handler.on_motion(FakeEvent(self.canvas))  # Enter the target.
+        handler.on_motion(FakeEvent(self.canvas))  # Move within the target.
+        handler.on_release(FakeEvent(self.canvas))  # Drop on the target.
+        self.assertEqual(self.log,
+                         ['accept', 'enter', 'accept', 'motion', 'commit', 
'end'])
+
+    def test_cancel(self):
+        handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
+        handler.on_motion(FakeEvent(self.canvas))  # Enter the target.
+        handler.cancel()  # Leaves the target without committing.
+        self.assertEqual(self.log, ['accept', 'enter', 'leave', 'end'])
+
+    def test_no_target(self):
+        # Nothing under the pointer accepts the drag.
+        self.canvas.winfo_containing = lambda x, y: None
+        handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
+        handler.on_motion(FakeEvent(self.canvas))
+        handler.on_release(FakeEvent(self.canvas))
+        self.assertEqual(self.log, ['end'])
+
+    def test_no_recursive_start(self):
+        handler = dnd.dnd_start(self.source, FakeEvent(self.canvas))
+        self.assertIsNotNone(handler)
+        # A drag is already in progress, so a second start is ignored.
+        self.assertIsNone(dnd.dnd_start(self.source, FakeEvent(self.canvas)))
+        handler.cancel()
+
+    def test_high_button_number_ignored(self):
+        self.assertIsNone(dnd.dnd_start(self.source, FakeEvent(self.canvas, 
num=6)))
+
+
+if __name__ == "__main__":
+    unittest.main()

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to