diff --git a/comtypes/test/test_eventinterface.py b/comtypes/test/test_eventinterface.py index 9bfca80d..d30c0339 100644 --- a/comtypes/test/test_eventinterface.py +++ b/comtypes/test/test_eventinterface.py @@ -1,7 +1,11 @@ +import gc +import time import unittest as ut -from ctypes import byref +from ctypes import HRESULT, byref from ctypes.wintypes import MSG +from comtypes import COMMETHOD, GUID, IUnknown +from comtypes.automation import DISPID from comtypes.client import CreateObject, GetEvents from comtypes.messageloop import ( PM_REMOVE, @@ -10,42 +14,39 @@ TranslateMessage, ) -# FIXME: External test dependencies like this seem bad. Find a different -# built-in win32 API to use. # The primary goal is to verify how `GetEvents` behaves when the # `interface` argument is explicitly specified versus when it is omitted, # using an object that has multiple outgoing event interfaces. +class IPropertyNotifySink(IUnknown): + # https://learn.microsoft.com/en-us/windows/win32/api/ocidl/nn-ocidl-ipropertynotifysink + _iid_ = GUID("{9BFBBC02-EFF1-101A-84ED-00AA00341D07}") + _methods_ = [ + # Called when a property has changed. + COMMETHOD([], HRESULT, "OnChanged", (["in"], DISPID, "dispid")), + # Called when an object wants to know if it's okay to change a property. + COMMETHOD([], HRESULT, "OnRequestEdit", (["in"], DISPID, "dispid")), + ] + + class EventSink: def __init__(self): self._events = [] - # some DWebBrowserEvents - def OnVisible(self, this, *args): - # print "OnVisible", args - self._events.append("OnVisible") - - def BeforeNavigate(self, this, *args): - # print "BeforeNavigate", args - self._events.append("BeforeNavigate") + # Events from the default dispatch interface + def onreadystatechange(self, this, *args): + self._events.append("onreadystatechange") - def NavigateComplete(self, this, *args): - # print "NavigateComplete", args - self._events.append("NavigateComplete") + def ondataavailable(self, this, *args): + self._events.append("ondataavailable") - # some DWebBrowserEvents2 - def BeforeNavigate2(self, this, *args): - # print "BeforeNavigate2", args - self._events.append("BeforeNavigate2") + # Events from `IPropertyNotifySink` + def OnChanged(self, this, *args): + self._events.append("OnChanged") - def NavigateComplete2(self, this, *args): - # print "NavigateComplete2", args - self._events.append("NavigateComplete2") - - def DocumentComplete(self, this, *args): - # print "DocumentComplete", args - self._events.append("DocumentComplete") + def OnRequestEdit(self, this, *args): + self._events.append("OnRequestEdit") def PumpWaitingMessages(): @@ -55,70 +56,59 @@ def PumpWaitingMessages(): DispatchMessage(byref(msg)) -class Test(ut.TestCase): - def tearDown(self): - import gc +class Test_MSXML(ut.TestCase): + def setUp(self): + # We use `Msxml2.DOMDocument` because it is a built-in Windows + # component that supports both a default source interface and the + # `IPropertyNotifySink` connection point. + self.doc = CreateObject("Msxml2.DOMDocument") + self.doc.async_ = True + def tearDown(self): + del self.doc + # Force garbage collection and wait slightly to ensure COM resources + # are released properly between tests. gc.collect() - import time - time.sleep(2) - @ut.skip( - "External test dependencies like this seem bad. Find a different built-in " - "win32 API to use." - ) def test_default_eventinterface(self): + # Verify that `GetEvents` automatically connects to the default source + # interface (dispatch events like `onreadystatechange`) when no + # interface is explicitly requested. sink = EventSink() - ie = CreateObject("InternetExplorer.Application") - conn = GetEvents(ie, sink=sink) - ie.Visible = True - ie.Navigate2(URL="http://docs.python.org/", Flags=0) - import time + conn = GetEvents(self.doc, sink) + self.doc.loadXML("") - for i in range(50): + # Give the message loop time to process incoming events. + for _ in range(50): PumpWaitingMessages() time.sleep(0.1) - ie.Visible = False - ie.Quit() - - self.assertEqual( - sink._events, - [ - "OnVisible", - "BeforeNavigate2", - "NavigateComplete2", - "DocumentComplete", - "OnVisible", - ], - ) - - del ie + # Should receive events from the default dispatch interface, but not + # events from `IPropertyNotifySink`. + self.assertIn("onreadystatechange", sink._events) + self.assertNotIn("OnChanged", sink._events) + del conn - @ut.skip( - "External test dependencies like this seem bad. Find a different built-in " - "win32 API to use." - ) def test_nondefault_eventinterface(self): + # Verify that `GetEvents` can connect to a non-default interface + # (like `IPropertyNotifySink`) when it is explicitly provided. sink = EventSink() - ie = CreateObject("InternetExplorer.Application") - import comtypes.gen.SHDocVw as mod - conn = GetEvents(ie, sink, interface=mod.DWebBrowserEvents) + conn = GetEvents(self.doc, sink, interface=IPropertyNotifySink) - ie.Visible = True - ie.Navigate2(Flags=0, URL="http://docs.python.org/") - import time + self.doc.loadXML("") - for i in range(50): + # Give the message loop time to process incoming events. + for _ in range(50): PumpWaitingMessages() time.sleep(0.1) - ie.Visible = False - ie.Quit() + # Should receive events from `IPropertyNotifySink`, but not events from + # the default dispatch interface. + self.assertNotIn("onreadystatechange", sink._events) + self.assertIn("OnChanged", sink._events) - self.assertEqual(sink._events, ["BeforeNavigate", "NavigateComplete"]) - del ie + del conn if __name__ == "__main__":