module __VMTEST.interop_objc use com.livecode.foreign use com.livecode.objc use com.livecode.__INTERNAL._testlib --------- __safe foreign handler NSObjectAlloc() returns ObjcRetainedId binds to "objc:NSObject.+alloc" __safe foreign handler NSNumberCreateWithInt(in pInt as CInt) returns ObjcAutoreleasedId binds to "objc:NSNumber.+numberWithInt:" __safe foreign handler NSNumberGetIntValue(in pObj as ObjcId) returns CInt binds to "objc:NSNumber.intValue" __safe foreign handler NSNumberCreateWithLongLong(in pInt as CLongLong) returns ObjcAutoreleasedId binds to "objc:NSNumber.+numberWithLongLong:" __safe foreign handler NSNumberGetLongLongValue(in pObj as ObjcId) returns CLongLong binds to "objc:NSNumber.longLongValue" __safe foreign handler NSNumberCreateWithFloat(in pInt as CFloat) returns ObjcAutoreleasedId binds to "objc:NSNumber.+numberWithFloat:" __safe foreign handler NSNumberGetFloatValue(in pObj as ObjcId) returns CFloat binds to "objc:NSNumber.floatValue" __safe foreign handler NSNumberCreateWithDouble(in pInt as CDouble) returns ObjcAutoreleasedId binds to "objc:NSNumber.+numberWithDouble:" __safe foreign handler NSNumberGetDoubleValue(in pObj as ObjcId) returns CDouble binds to "objc:NSNumber.doubleValue" --------- -- NEED TO CHECK class and instance methods, existant and non-existant -- also protocols and superclasses. __safe foreign handler CreateObjCObjectDoesntExist() returns ObjcId binds to "objc:NSObject.-foobar" private handler TestObjcInterop_NonExistantObjC() CreateObjCObjectDoesntExist() end handler private handler TestObjcInterop_ExistantObjC() variable tObj as ObjcObject put NSObjectAlloc() into tObj end handler public handler TestObjcInterop_Binding() if not the operating system is in ["mac", "ios"] then skip test "objc binding succeeds" because "not implemented on" && the operating system return end if MCUnitTestHandlerThrowsNamed(TestObjcInterop_NonExistantObjC, "livecode.lang.ForeignHandlerBindingError", "Failure to bind to non-existant objc function throws error") MCUnitTestHandlerDoesntThrow(TestObjcInterop_ExistantObjC, "Binding to existant objc function does not throw error") test "Non-existant objc function gives nothing as handler value" when CreateObjCObjectDoesntExist is nothing test "Existant objc function gives non-nothing as handler value" when NSObjectAlloc is not nothing end handler --------- __safe foreign handler NSObjectGetRetainCount(in pObj as ObjcId) returns CULong binds to "objc:NSObject.-retainCount" __safe foreign handler NSObjectGetSelf(in pObj as ObjcId) returns ObjcId binds to "objc:NSObject.-self" public handler TestObjInterop_ObjcObjectLifetime() if not the operating system is in ["mac", "ios"] then skip test "objc binding succeeds" because "not implemented on" && the operating system return end if /* Test that ObjcRetainedId gives its reference to ObjcObject */ variable tObjcObjectFromRetainedId as ObjcObject put NSObjectAlloc() into tObjcObjectFromRetainedId test "retained id into objc object doesn't retain" when NSObjectGetRetainCount(tObjcObjectFromRetainedId) is 1 /* Test that ObjcId copies its reference to ObjcObject */ variable tObjcObjectFromId as ObjcObject put NSObjectGetSelf(tObjcObjectFromRetainedId) into tObjcObjectFromId test "id into objc object retains" when NSObjectGetRetainCount(tObjcObjectFromId) is 2 /* Test that ObjcAutoreleasedId copies its reference to ObjcObject */ variable tObjcObjectFromAutoreleasedId as ObjcObject put NSNumberCreateWithDouble(3.14159) into tObjcObjectFromAutoreleasedId test "autoreleased id into objc object retains" when NSObjectGetRetainCount(tObjcObjectFromAutoreleasedId) is 2 end handler --------- public handler TestObjInterop_RoundtripNumber() if not the operating system is in ["mac", "ios"] then skip test "objc binding succeeds" because "not implemented on" && the operating system return end if variable tIntObj as ObjcObject put NSNumberCreateWithInt(314159) into tIntObj test "NSNumber roundtrip CInt" when NSNumberGetIntValue(tIntObj) is 314159 variable tLongLongObj as ObjcObject put NSNumberCreateWithLongLong(314159) into tLongLongObj test "NSNumber roundtrip CLongLong" when NSNumberGetLongLongValue(tLongLongObj) is 314159 variable tFloatObj as ObjcObject put NSNumberCreateWithFloat(314159) into tFloatObj test "NSNumber roundtrip CFloat" when NSNumberGetFloatValue(tFloatObj) is 314159 variable tDoubleObj as ObjcObject put NSNumberCreateWithDouble(314159) into tDoubleObj test "NSNumber roundtrip CDouble" when NSNumberGetDoubleValue(tDoubleObj) is 314159 end handler -------- __safe foreign handler MCHandlerTryToInvokeWithList(in Handler as any, inout Arguments as optional List, out Result as optional any) returns optional any binds to "" __safe foreign handler NSArrayFirstObject(in pObj as ObjcId) returns ObjcId binds to "objc:NSArray.firstObject" private handler __CallNSNumberMethodOnNSString(in pString as ObjcObject) returns optional any return NSArrayFirstObject(pString) end handler public handler TestObjcInterop_CatchObjcException() if not the operating system is in ["mac", "ios"] then skip test "objc exception handler succeeds" because "not implemented on" && the operating system return end if variable tArguments as List variable tResult as optional any put [ StringToNSString("foo") ] into tArguments test "objc exception is caught" when MCHandlerTryToInvokeWithList(__CallNSNumberMethodOnNSString, tArguments, tResult) is not nothing end handler --------- // NSRange is a pair of (natural sized) unsigned integers public foreign type NSRange binds to "MCAggregateTypeInfo:pp" // NSRange is 8 bytes on 64-bit arch, 4 bytes on 32-bit // so will use objc_msgSend_stret on 32-bit arch, but objc_msgSend on // 64 bit. foreign handler RangeOfString(in pHaystack as ObjcId, in pNeedle as ObjcId) returns NSRange binds to "objc:NSString.-rangeOfString:" public handler TestReturnNSRange() if not the operating system is in ["mac", "ios"] then skip test "return NSRange struct succeeds" because "not implemented on" && the operating system return end if variable tRange as NSRange unsafe put RangeOfString(StringToNSString("cartoon"), \ StringToNSString("art")) into tRange end unsafe variable tRangeList as List put tRange into tRangeList test "return NSRange struct succeeds" when \ tRangeList is [1,3] end handler // NSRect is a quadruple of (natural sized) floats public foreign type NSRect binds to "MCAggregateTypeInfo:qqqq" foreign handler NSValueWithRect(in pRect as NSRect) returns ObjcId \ binds to "objc:NSValue.+valueWithRect:" // NSRect is 32 bytes on 64-bit arch, 16 bytes on 32-bit // so will use objc_msgSend_stret on both archs foreign handler NSRectFromValue(in pValue as ObjcId) returns NSRect \ binds to "objc:NSValue.-rectValue" public handler TestReturnNSRect() if not the operating system is in ["mac", "ios"] then skip test "return NSRange struct succeeds" because "not implemented on" && the operating system return end if variable tRect as List variable tExponent as Integer repeat with tExponent from 1 up to 4 push the exponential of tExponent onto tRect end repeat variable tRoundTripped as NSRect unsafe variable tValue as ObjcId put NSValueWithRect(tRect) into tValue put NSRectFromValue(tValue) into tRoundTripped end unsafe variable tRectList as List put tRoundTripped into tRectList test "return NSRect struct succeeds" when \ tRectList is tRect end handler ---------- private foreign handler NSUserNotificationSetTitle(in Obj as ObjcId, in Title as ObjcId) returns nothing binds to "objc:NSUserNotification.-setTitle:" private foreign handler NSUserNotificationGetTitle(in Obj as ObjcId) returns ObjcId binds to "objc:NSUserNotification.title" public handler TestObjcDynamicPropertyBinding() if not the operating system is in ["mac", "ios"] then skip test "objc property binding succeeds" because "not implemented on" && the operating system return end if test "dynamic objc setter binds" when NSUserNotificationSetTitle is not nothing test "dynamic objc getter binds" when NSUserNotificationGetTitle is not nothing end handler -------- foreign handler DynamicGetInt(in pObject as ObjcId) returns CInt \ binds to "objc:.-intValue" public handler TestDynamicInstanceMethodBinding() if not the operating system is in ["mac", "ios"] then skip test "obj-c dynamic instance method binding" \ because "not implemented on" && the operating system return end if unsafe variable tNumber as ObjcObject put NSNumberCreateWithInt(10) into tNumber test "dynamic method binds" when DynamicGetInt is not nothing test "dynamic method call succeeds" when DynamicGetInt(tNumber) is 10 end unsafe end handler foreign handler DynamicGetIntNoClass(in pObject as ObjcId) returns CInt \ binds to "objc:-intValue" public handler TestDynamicInstanceMethodNoClassCrash() if not the operating system is in ["mac", "ios"] then skip test "obj-c dynamic instance method binding no class crash" \ because "not implemented on" && the operating system return end if unsafe test "dynamic method binds" when DynamicGetIntNoClass is not nothing end unsafe end handler foreign handler DynamicAlloc() returns ObjcRetainedId \ binds to "objc:.+alloc" public handler TestDynamicClassMethodBindingFails() if not the operating system is in ["mac", "ios"] then skip test "obj-c dynamic class method binding fails" \ because "not implemented on" && the operating system return end if test "dynamic class method binding fails" when DynamicAlloc is nothing end handler -------- handler _TestNativeReturnTypeConvert(in pParam as ObjcObject, in pParam2 as ObjcObject) returns Integer return 1 end handler foreign handler Objc_NSControlIsValidObject(in pTarget as ObjcId, in pControl as ObjcId, in pObject as ObjcId) returns CSChar binds to "objc:.-control:isValidObject:" foreign handler Objc_NSControlAlloc() returns ObjcRetainedId \ binds to "objc:AppKit.framework>NSControl.+alloc" public handler TestDelegateNativeReturnType() if not the operating system is in ["mac", "ios"] then skip test "objc delegate returns valid bridge type" \ because "not implemented on" && the operating system return end if plan 2 tests variable tDelegate as optional ObjcObject unsafe variable tControl as ObjcObject put Objc_NSControlAlloc() into tControl put CreateObjcDelegate("NSControlTextEditingDelegate", \ { "control:isValidObject:": \ _TestNativeReturnTypeConvert }) \ into tDelegate test "create delegate succeeds" when tDelegate is not nothing test "delegate converts to native return type" when \ Objc_NSControlIsValidObject(tDelegate, tControl, tControl) is 1 end unsafe end handler foreign handler Objc_NSMenuAlloc() returns ObjcRetainedId \ binds to "objc:AppKit.framework>NSMenu.+alloc" foreign handler Objc_NSMenuNumberOfItems(in pTarget as ObjcId, in pMenu as ObjcId) returns CLong binds to "objc:.-numberOfItemsInMenu:" handler _TestForeignReturnType(in pParam as ObjcObject) returns CLong return 1 end handler public handler TestDelegateForeignReturnType() if not the operating system is in ["mac", "ios"] then skip test "objc delegate returns correct foreign type" \ because "not implemented on" && the operating system return end if plan 2 tests variable tDelegate as optional ObjcObject unsafe variable tMenu as ObjcObject put Objc_NSMenuAlloc() into tMenu put CreateObjcDelegate("NSMenuDelegate", \ { "numberOfItemsInMenu:": \ _TestForeignReturnType }) \ into tDelegate test "create delegate succeeds" when tDelegate is not nothing test "delegate returns foreign type" when \ Objc_NSMenuNumberOfItems(tDelegate, tMenu) is 1 end unsafe end handler handler _TestDelegateBadSignatureCallback(in pParam as ObjcObject) returns String return "" end handler private handler _TestDelegateBadSignature() // Ensure NSMenuDelegate is available unsafe variable tMenu as ObjcObject put Objc_NSMenuAlloc() into tMenu end unsafe CreateObjcDelegate("NSMenuDelegate", \ { "numberOfItemsInMenu:": \ _TestDelegateBadSignatureCallback }) end handler private handler _TestDelegateContextBadSignature() // Ensure NSMenuDelegate is available unsafe variable tMenu as ObjcObject put Objc_NSMenuAlloc() into tMenu end unsafe // Use correct signature for no-context delegate CreateObjcDelegateWithContext("NSMenuDelegate", \ { "numberOfItemsInMenu:": \ _TestForeignReturnType }, \ "") end handler public handler TestDelegateBadSignature() if not the operating system is in ["mac", "ios"] then skip test "objc delegate error returning invalid bridge type" \ because "not implemented on" && the operating system return end if MCUnitTestHandlerThrowsNamed(_TestDelegateBadSignature, \ "livecode.objc.DelegateCallbackSignatureError", \ "Creating delegate throws error when callback has incorrect signature") end handler public handler TestDelegateContextBadSignature() if not the operating system is in ["mac", "ios"] then skip test "objc delegate error no context parameter" \ because "not implemented on" && the operating system return end if MCUnitTestHandlerThrowsNamed(_TestDelegateContextBadSignature, \ "livecode.objc.DelegateCallbackSignatureError", \ "Creating delegate throws error when callback omits context parameter") end handler foreign handler Objc_NSPortDelegateHandlePortMessage(in pTarget as ObjcId, in pPortMessage as ObjcId) returns nothing \ binds to "objc:.-handlePortMessage:" foreign handler Objc_NSPortMessageAlloc() returns ObjcRetainedId \ binds to "objc:Foundation.framework>NSPortMessage.+alloc" private variable mCalled as Boolean handler _SetTestDelegateMethodCalled(in pValue as Boolean) put pValue into mCalled end handler handler _GetTestDelegateMethodCalled() return mCalled end handler handler _TestDelegateMethodCalled(in pParam as ObjcObject) returns nothing _SetTestDelegateMethodCalled(true) end handler public handler TestInformalDelegateVoidReturn() if not the operating system is in ["mac", "ios"] then skip test "objc informal delegatevoid return type" \ because "not implemented on" && the operating system return end if _SetTestDelegateMethodCalled(false) plan 2 tests variable tDelegate as optional ObjcObject unsafe variable tPortMessage as ObjcObject put Objc_NSPortMessageAlloc() into tPortMessage put CreateObjcInformalDelegate([Objc_NSPortDelegateHandlePortMessage], \ { "handlePortMessage:": \ _TestDelegateMethodCalled }) \ into tDelegate test "create delegate succeeds" when tDelegate is not nothing Objc_NSPortDelegateHandlePortMessage(tDelegate, tPortMessage) test "delegate method void return" when \ _GetTestDelegateMethodCalled() end unsafe end handler handler _TestDelegateMethodCalledWithContext(in pContext as Boolean, in pParam as ObjcObject) returns nothing _SetTestDelegateMethodCalled(pContext) end handler public handler TestInformalDelegateWithContext() if not the operating system is in ["mac", "ios"] then skip test "objc informal delegate with context" \ because "not implemented on" && the operating system return end if _SetTestDelegateMethodCalled(false) plan 2 tests variable tDelegate as optional ObjcObject unsafe variable tPortMessage as ObjcObject put Objc_NSPortMessageAlloc() into tPortMessage put CreateObjcInformalDelegateWithContext([Objc_NSPortDelegateHandlePortMessage], \ { "handlePortMessage:": \ _TestDelegateMethodCalledWithContext }, \ true) \ into tDelegate test "create delegate succeeds" when tDelegate is not nothing Objc_NSPortDelegateHandlePortMessage(tDelegate, tPortMessage) test "delegate method called with context" when \ _GetTestDelegateMethodCalled() end unsafe end handler handler _TestInformalDelegateBadSignatureCallback(in pParam as ObjcObject) returns String return "" end handler private handler _TestInformalDelegateBadSignature() CreateObjcInformalDelegate([Objc_NSPortDelegateHandlePortMessage], \ { "handlePortMessage:": \ _TestInformalDelegateBadSignatureCallback }) end handler private handler _TestInformalDelegateContextBadSignature() // Correct signature for no-context delegate CreateObjcInformalDelegateWithContext([Objc_NSMenuNumberOfItems], \ { "numberOfItemsInMenu:": \ _TestDelegateMethodCalled }, \ "") end handler public handler TestInformalDelegateBadSignature() if not the operating system is in ["mac", "ios"] then skip test "objc informal delegate error returning invalid type" \ because "not implemented on" && the operating system return end if MCUnitTestHandlerThrowsNamed(_TestInformalDelegateBadSignature, \ "livecode.objc.DelegateCallbackSignatureError", \ "Creating informal delegate throws error when callback has incorrect signature") end handler public handler TestInformalDelegateContextBadSignature() if not the operating system is in ["mac", "ios"] then skip test "objc informal delegate error omitting context param" \ because "not implemented on" && the operating system return end if MCUnitTestHandlerThrowsNamed(_TestInformalDelegateContextBadSignature, \ "livecode.objc.DelegateCallbackSignatureError", \ "Creating informal delegate throws error when callback omits context parameter") end handler handler _TestFindBarViewCallback() returns ObjcId end handler handler _TestSetFindBarViewCallback(in pView as ObjcId) returns nothing end handler handler _TestIsFindBarVisibleCallback() returns CBool end handler handler _TestSetFindBarVisibleCallback(in pVisible as CBool) returns nothing end handler handler _TestFindBarViewDidChangeHeightCallback() returns nothing end handler private handler _TestDelegateMappedRequiredMethod() CreateObjcDelegate("NSTextFinderBarContainer", \ { "findBarView": \ _TestFindBarViewCallback, \ "setFindBarView:": \ _TestSetFindBarViewCallback, \ "isFindBarVisible": \ _TestIsFindBarVisibleCallback, \ "setFindBarVisible:": \ _TestSetFindBarVisibleCallback, \ "findBarViewDidChangeHeight": \ _TestFindBarViewDidChangeHeightCallback }) end handler private handler _TestDelegateUnmappedRequiredMethod() CreateObjcDelegate("NSTextFinderBarContainer", \ { "isFindBarVisible": \ _TestIsFindBarVisibleCallback }) end handler foreign handler NSTextFinderAlloc() returns ObjcRetainedId \ binds to "objc:AppKit.framework>NSTextFinder.+alloc" foreign handler NSTextFinderInit(in pObj as ObjcId) returns ObjcId \ binds to "objc:NSTextFinder.-init" public handler TestDelegateRequiredMethod() if not the operating system is in ["mac", "ios"] then skip test "objc delegate required method tests" \ because "not implemented on" && the operating system return end if unsafe // Ensure NSTextFinder is loaded variable tFinder as ObjcObject put NSTextFinderAlloc() into tFinder put NSTextFinderInit(tFinder) into tFinder end unsafe MCUnitTestHandlerDoesntThrow(_TestDelegateMappedRequiredMethod, \ "Creating delegate doesn't throw error when required protocol method is mapped") MCUnitTestHandlerThrowsNamed(_TestDelegateUnmappedRequiredMethod, \ "livecode.objc.DelegateMappingError", \ "Creating delegate throws error when required protocol method is not mapped") end handler __safe foreign handler CallClassMethodWithInstance(in pInstance as ObjcObject) returns ObjcId binds to "objc:NSObject.+class" private handler TestObjcInterop_CallClassMethodWithInstance() variable tObj as ObjcObject put NSObjectAlloc() into tObj put CallClassMethodWithInstance(tObj) into tObj end handler public handler TestObjcInterop_ArgumentCountMatchesMethod() if not the operating system is in ["mac", "ios"] then skip test "objc binding succeeds" because "not implemented on" && the operating system return end if MCUnitTestHandlerThrowsNamed(TestObjcInterop_CallClassMethodWithInstance, "livecode.lang.ForeignHandlerBindingError", "Failure to bind with incorrect arguments objc function throws error") end handler end module