--- /dev/null
+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()