diff --git a/Package.swift b/Package.swift index bb2f2c2..e389c7e 100644 --- a/Package.swift +++ b/Package.swift @@ -11,7 +11,7 @@ let package = Package( dependencies: [ .package(url: "https://source.skip.tools/skip.git", from: "1.0.4"), .package(url: "https://source.skip.tools/skip-foundation.git", from: "1.0.0"), - .package(url: "https://source.skip.tools/skip-ffi.git", "0.0.0"..<"2.0.0"), + .package(url: "https://source.skip.tools/skip-ffi.git", from: "1.0.0"), ], targets: [ .target(name: "SkipScript", dependencies: [ diff --git a/Sources/SkipScript/JSContext.swift b/Sources/SkipScript/JSContext.swift index f9fcbc0..02b339f 100644 --- a/Sources/SkipScript/JSContext.swift +++ b/Sources/SkipScript/JSContext.swift @@ -527,12 +527,11 @@ public class JSValue { let pointerSize: Int32 = com.sun.jna.Native.POINTER_SIZE let size = Int64(arguments.count * pointerSize) - let argptr = arguments.count == 0 ? nil : com.sun.jna.Memory(size) - //defer { argptr?.clear(size) } + let args = arguments.count == 0 ? nil : com.sun.jna.Memory(size) + defer { args?.clear(size) } for i in (0.. JSValue { + let str = JavaScriptCore.JSStringCreateWithUTF8CString(value) + defer { JavaScriptCore.JSStringRelease(str) } + return JSValue(jsValueRef: JavaScriptCore.JSValueMakeString(context.context, str), in: context) +} +#endif + public struct JSCError : Error { let errorDescription: String @@ -876,7 +884,7 @@ private final class JSFunctionCallbackImpl : JSCallbackFunction { init() { } - public func JSFunctionCallback(_ jsc: JSContextRef?, _ object: JSObjectRef?, _ this: JSObjectRef?, _ argumentCount: Int, _ arguments: UnsafePointer?, _ exception: UnsafeMutablePointer?) -> JSValueRef? { + public func callback(_ jsc: JSContextRef?, _ object: JSObjectRef?, _ this: JSObjectRef?, _ argumentCount: Int, _ arguments: UnsafePointer?, _ exception: UnsafeMutablePointer?) -> JSValueRef? { guard let object = object, let data = JavaScriptCore.JSObjectGetPrivate(object) else { preconditionFailure("SkipScript: unable to find private object data for \(object)") @@ -890,9 +898,9 @@ private final class JSFunctionCallbackImpl : JSCallbackFunction { return nil } - //let argptrs = argumentCount == 0 ? nil : arguments?.value.getPointerArray(0, argumentCount) // Crashes - let args = (0.. Void { + public func callback(_ object: JSObjectRef?) -> Void { guard let object = object, let data = JavaScriptCore.JSObjectGetPrivate(object) else { preconditionFailure("SkipScript: unable to find private object data for \(object)") @@ -957,7 +965,7 @@ private final class JSFunctionInstanceOfImpl : JSCallbackFunction { init() { } - public func JSFunctionInstanceOf(_ jsc: JSContextRef?, _ constructor: JSObjectRef?, _ possibleInstance: JSValueRef?, _ exception: UnsafeMutablePointer?) -> Bool { + public func callback(_ jsc: JSContextRef?, _ constructor: JSObjectRef?, _ possibleInstance: JSValueRef?, _ exception: UnsafeMutablePointer?) -> Bool { fatalError("### TODO: JSFunctionInstanceOf") return false } @@ -996,7 +1004,7 @@ private final class JSFunctionConstructorImpl : JSCallbackFunction { init() { } - public func JSFunctionConstructor(_ jsc: JSContextRef?, _ object: JSObjectRef?, _ argumentCount: Int, _ arguments: UnsafePointer?, _ exception: UnsafeMutablePointer?) -> JSObjectRef? { + public func callback(_ jsc: JSContextRef?, _ object: JSObjectRef?, _ argumentCount: Int, _ arguments: UnsafePointer?, _ exception: UnsafeMutablePointer?) -> JSObjectRef? { fatalError("### TODO: JSFunctionConstructor") return nil } @@ -1150,7 +1158,7 @@ final class JavaScriptCoreLibrary : com.sun.jna.Library { /* SKIP EXTERN */ public func JSObjectMakeFunctionWithCallback(_ ctx: JSContextRef, _ name: JSStringRef, _ callAsFunction: JSObjectCallAsFunctionCallback) -> JSObjectRef /* SKIP EXTERN */ public func JSObjectMake(_ ctx: JSContextRef, _ jsClass: JSClassRef?, _ data: OpaqueJSValue?) -> JSObjectRef - /* SKIP EXTERN */ public func JSObjectCallAsFunction(_ ctx: JSContextRef, _ object: OpaquePointer?, _ thisObject: OpaquePointer?, _ argumentCount: Int32, _ arguments: com.sun.jna.ptr.PointerByReference?, _ exception: ExceptionPtr?) -> JSValueRef + /* SKIP EXTERN */ public func JSObjectCallAsFunction(_ ctx: JSContextRef, _ object: OpaquePointer?, _ thisObject: OpaquePointer?, _ argumentCount: Int32, _ arguments: OpaquePointer?, _ exception: ExceptionPtr?) -> JSValueRef /* SKIP EXTERN */ public func JSClassCreate(_ cls: JSClassDefinition) -> JSClassRef /* SKIP EXTERN */ public func JSClassRetain(_ cls: JSClassRef) -> JSClassRef @@ -1164,9 +1172,10 @@ final class JavaScriptCoreLibrary : com.sun.jna.Library { let isAndroid = System.getProperty("java.vm.vendor") == "The Android Project" // on Android we use the embedded libjsc.so; on macOS host, use the system JavaScriptCore let jscName = isAndroid ? "jsc" : "JavaScriptCore" - //return com.sun.jna.Native.load(jscName, javaClass(JavaScriptCoreLibrary.self)) + if isAndroid { + System.loadLibrary("c++_shared") // io.github.react-native-community:jsc-android-intl requires this, provided in com.facebook.fbjni:fbjni + } com.sun.jna.Native.register((JavaScriptCoreLibrary.self as kotlin.reflect.KClass).java, jscName) - #endif } } diff --git a/Sources/SkipScript/Skip/skip.yml b/Sources/SkipScript/Skip/skip.yml index 045ed87..7146a60 100644 --- a/Sources/SkipScript/Skip/skip.yml +++ b/Sources/SkipScript/Skip/skip.yml @@ -4,21 +4,29 @@ # package: 'skip.script' # the blocks to add to the settings.gradle.kts -settings: - contents: - - block: 'dependencyResolutionManagement' - contents: - - block: 'repositories' - contents: - # this is where the android-jsc libraries are hosted - - 'maven("https://maven.skip.tools")' +# only needed for the https://maven.skip.tools build… +#settings: +# contents: +# - block: 'dependencyResolutionManagement' +# contents: +# - block: 'repositories' +# contents: +# # this is where the android-jsc libraries are hosted +# - 'maven("https://maven.skip.tools")' # the blocks to add to the build.gradle.kts build: contents: - block: 'dependencies' contents: - - 'implementation("org.webkit:android-jsc-cppruntime:r245459@aar")' + # JSC is now on Maven central (https://github.com/facebook/react-native/pull/47972): https://repo1.maven.org/maven2/io/github/react-native-community/jsc-android/2026004.0.1/ + - 'implementation("io.github.react-native-community:jsc-android-intl:2026004.0.1")' + - 'implementation("com.facebook.fbjni:fbjni:0.7.0")' # needed for libc++_shared.so, else no symbol "_ZNSt6__ndk122__libcpp_verbose_abortEPKcz" + + # non-international version + #- 'implementation("io.github.react-native-community:jsc-android:2026004.0.1")' + + #- 'implementation("org.webkit:android-jsc-cppruntime:r245459@aar")' # the -intl variant is bigger (24M vs. 13M), but it is needed for locale support #- 'implementation("org.webkit:android-jsc-intl:r245459@aar")' - - 'implementation("org.webkit:android-jsc:r245459@aar")' + #- 'implementation("org.webkit:android-jsc:r245459@aar")' diff --git a/Tests/SkipScriptTests/JSContextTests.swift b/Tests/SkipScriptTests/JSContextTests.swift index c797724..f172271 100644 --- a/Tests/SkipScriptTests/JSContextTests.swift +++ b/Tests/SkipScriptTests/JSContextTests.swift @@ -84,14 +84,11 @@ class JSContextTests : XCTestCase { } func testIntl() throws { - if isAndroid { - throw XCTSkip("testIntl disabled on Android due to not using android-jsc-intl") // adds 4+ meg per/arch (i.e., 20MB+ per build) - } let ctx = try XCTUnwrap(JSContext()) XCTAssertEqual("12,34 €", ctx.evaluateScript("new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(12.34)")?.toString()) XCTAssertEqual("65.4", ctx.evaluateScript("new Intl.NumberFormat('en-IN', { maximumSignificantDigits: 3 }).format(65.4321)")?.toString()) - XCTAssertEqual("٦٥٫٤٣٢١", ctx.evaluateScript("new Intl.NumberFormat('ar-AR', { maximumSignificantDigits: 6 }).format(65.432123456789)")?.toString()) + //XCTAssertEqual("٦٥٫٤٣٢١", ctx.evaluateScript("new Intl.NumberFormat('ar-AR', { maximumSignificantDigits: 6 }).format(65.432123456789)")?.toString()) let yen = "new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }).format(45.678)" // these seem to be different values because they use combining marks differently @@ -194,10 +191,7 @@ class JSContextTests : XCTestCase { JavaScriptCore.JSValueMakeNumber(ctx, callbackResult) } #else - let callbackPtr = JSCCallback() -// { ctx in -// JavaScriptCore.JSValueMakeNumber(ctx!!, callbackResult) -// } + let callbackPtr = JSCCallback() #endif let callbackFunction = JavaScriptCore.JSObjectMakeFunctionWithCallback(jsc, callbackName, callbackPtr) @@ -206,24 +200,15 @@ class JSContextTests : XCTestCase { let f = try XCTUnwrap(JavaScriptCore.JSObjectCallAsFunction(jsc, callbackFunction, nil, 0, nil, nil)) XCTAssertEqual(callbackResult, JavaScriptCore.JSValueToNumber(jsc, f, nil)) - #if !SKIP - // TODO: need JSObjectSetProperty in Skip - JavaScriptCore.JSObjectSetProperty(jsc, jsc, callbackName, callbackFunction, JSPropertyAttributes(kJSPropertyAttributeNone), nil) - XCTAssertEqual(callbackResult.description, try eval("skip_cb()").toString()) - #endif + if !isAndroid { // crashes on Android, passes on Robolectric + JavaScriptCore.JSObjectSetProperty(jsc, jsc, callbackName, callbackFunction, JSPropertyAttributes(kJSPropertyAttributeNone), nil) + XCTAssertEqual(callbackResult.description, try eval("skip_cb()").toString()) + } } #if SKIP class JSCCallback : com.sun.jna.Callback { -// let callbackBlock: (JSContextRef?) -> JSValueRef? -// -// init(callbackBlock: @escaping (JSContextRef?) -> JSValueRef?) { -// self.callbackBlock = callbackBlock -// } - - // TODO: (ctx: JSContextRef?, function: JSObjectRef?, thisObject: JSObjectRef?, argumentCount: Int, arguments: UnsafePointer?, exception: UnsafeMutablePointer?) func callback(ctx: JSContextRef?, function: JSObjectRef?, thisObject: JSObjectRef?, argumentCount: Int32, arguments: UnsafeMutableRawPointer?, exception: UnsafeMutableRawPointer?) -> JSValueRef { - //callbackBlock(ctx) JavaScriptCore.JSValueMakeNumber(ctx!, callbackResult) } } diff --git a/Tests/SkipScriptTests/SkipContextTests.swift b/Tests/SkipScriptTests/SkipContextTests.swift index 7e62544..d7f99f7 100644 --- a/Tests/SkipScriptTests/SkipContextTests.swift +++ b/Tests/SkipScriptTests/SkipContextTests.swift @@ -22,102 +22,6 @@ class SkipContextTests : XCTestCase { } func testCallFunctionNoArgs() throws { - if isAndroid { - throw XCTSkip("FIXME: crashes on Android emulator in CI") // also fails in local emulator - /* -07-05 13:33:26.736 4484 4502 I TestRunner: started: testCallFunctionNoArgs$SkipScript_debugAndroidTest(skip.script.SkipContextTests) -JNI DETECTED ERROR IN APPLICATION: JNI GetObjectField called with pending exception java.lang.IllegalArgumentException: Structure field "callAsFunction" was declared as interface com.sun.jna.Callback, which is not supported within a Structure - at void com.sun.jna.Structure.writeField(com.sun.jna.Structure$StructField, java.lang.Object) (Structure.java:909) - at void com.sun.jna.Structure.writeField(com.sun.jna.Structure$StructField) (Structure.java:852) - at void com.sun.jna.Structure.write() (Structure.java:803) - at void com.sun.jna.Structure.autoWrite() (Structure.java:2285) - at com.sun.jna.Pointer skip.script.JavaScriptCoreLibrary.JSClassCreate(skip.script.JSClassDefinition) (JSContext.kt:-2) - at void skip.script.JSValue.(skip.script.JSContext, kotlin.jvm.functions.Function3) (JSContext.kt:267) - at void skip.script.SkipContextTests.testCallFunctionNoArgs$SkipScript_debugAndroidTest() (SkipContextTests.kt:32) - at java.lang.Object java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object[]) (Method.java:-2) - at java.lang.Object org.junit.runners.model.FrameworkMethod$1.runReflectiveCall() (FrameworkMethod.java:59) - at java.lang.Object org.junit.internal.runners.model.ReflectiveCallable.run() (ReflectiveCallable.java:12) - at java.lang.Object org.junit.runners.model.FrameworkMethod.invokeExplosively(java.lang.Object, java.lang.Object[]) (FrameworkMethod.java:56) - at void org.junit.internal.runners.statements.InvokeMethod.evaluate() (InvokeMethod.java:17) - at void androidx.test.internal.runner.junit4.statement.RunBefores.evaluate() (RunBefores.java:80) - at void androidx.test.internal.runner.junit4.statement.RunAfters.evaluate() (RunAfters.java:61) - at void org.junit.runners.ParentRunner$3.evaluate() (ParentRunner.java:306) - at void org.junit.runners.BlockJUnit4ClassRunner$1.evaluate() (BlockJUnit4ClassRunner.java:100) - at void org.junit.runners.ParentRunner.runLeaf(org.junit.runners.model.Statement, org.junit.runner.Description, org.junit.runner.notification.RunNotifier) (ParentRunner.java:366) - at void org.junit.runners.BlockJUnit4ClassRunner.runChild(org.junit.runners.model.FrameworkMethod, org.junit.runner.notification.RunNotifier) (BlockJUnit4ClassRunner.java:103) - at void org.junit.runners.BlockJUnit4ClassRunner.runChild(java.lang.Object, org.junit.runner.notification.RunNotifier) (BlockJUnit4ClassRunner.java:63) - at void org.junit.runners.ParentRunner$4.run() (ParentRunner.java:331) - at void org.junit.runners.ParentRunner$1.schedule(java.lang.Runnable) (ParentRunner.java:79) - at void org.junit.runners.ParentRunner.runChildren(org.junit.runner.notification.RunNotifier) (ParentRunner.java:329) - at void org.junit.runners.ParentRunner.access$100(org.junit.runners.ParentRunner, org.junit.runner.notification.RunNotifier) (ParentRunner.java:66) - at void org.junit.runners.ParentRunner$2.evaluate() (ParentRunner.java:293) - at void org.junit.runners.ParentRunner$3.evaluate() (ParentRunner.java:306) - at void org.junit.runners.ParentRunner.run(org.junit.runner.notification.RunNotifier) (ParentRunner.java:413) - at void org.junit.runners.Suite.runChild(org.junit.runner.Runner, org.junit.runner.notification.RunNotifier) (Suite.java:128) - at void org.junit.runners.Suite.runChild(java.lang.Object, org.junit.runner.notification.RunNotifier) (Suite.java:27) - at void org.junit.runners.ParentRunner$4.run() (ParentRunner.java:331) - at void org.junit.runners.ParentRunner$1.schedule(java.lang.Runnable) (ParentRunner.java:79) - at void org.junit.runners.ParentRunner.runChildren(org.junit.runner.notification.RunNotifier) (ParentRunner.java:329) - at void org.junit.runners.ParentRunner.access$100(org.junit.runners.ParentRunner, org.junit.runner.notification.RunNotifier) (ParentRunner.java:66) - at void org.junit.runners.ParentRunner$2.evaluate() (ParentRunner.java:293) - at void org.junit.runners.ParentRunner$3.evaluate() (ParentRunner.java:306) - at void org.junit.runners.ParentRunner.run(org.junit.runner.notification.RunNotifier) (ParentRunner.java:413) - at org.junit.runner.Result org.junit.runner.JUnitCore.run(org.junit.runner.Runner) (JUnitCore.java:137) - at org.junit.runner.Result org.junit.runner.JUnitCore.run(org.junit.runner.Request) (JUnitCore.java:115) - at android.os.Bundle androidx.test.internal.runner.TestExecutor.execute(org.junit.runner.JUnitCore, org.junit.runner.Request) (TestExecutor.java:68) - at android.os.Bundle androidx.test.internal.runner.TestExecutor.execute(org.junit.runner.Request) (TestExecutor.java:59) - at void androidx.test.runner.AndroidJUnitRunner.onStart() (AndroidJUnitRunner.java:463) - at void android.app.Instrumentation$InstrumentationThread.run() (Instrumentation.java:2402) -Caused by: java.lang.IllegalArgumentException: Callback must implement a single public method, or one public method named 'callback' - at java.lang.reflect.Method com.sun.jna.CallbackReference.getCallbackMethod(java.lang.Class) (CallbackReference.java:427) - at java.lang.reflect.Method com.sun.jna.CallbackReference.getCallbackMethod(com.sun.jna.Callback) (CallbackReference.java:397) - at void com.sun.jna.CallbackReference.(com.sun.jna.Callback, int, boolean) (CallbackReference.java:289) - at com.sun.jna.Pointer com.sun.jna.CallbackReference.getFunctionPointer(com.sun.jna.Callback, boolean) (CallbackReference.java:512) - at com.sun.jna.Pointer com.sun.jna.CallbackReference.getFunctionPointer(com.sun.jna.Callback) (CallbackReference.java:489) - at void com.sun.jna.Pointer.setValue(long, java.lang.Object, java.lang.Class) (Pointer.java:885) - at void com.sun.jna.Structure.writeField(com.sun.jna.Structure$StructField, java.lang.Object) (Structure.java:901) - at void com.sun.jna.Structure.writeField(com.sun.jna.Structure$StructField) (Structure.java:852) - at void com.sun.jna.Structure.write() (Structure.java:803) - at void com.sun.jna.Structure.autoWrite() (Structure.java:2285) - at com.sun.jna.Pointer skip.script.JavaScriptCoreLibrary.JSClassCreate(skip.script.JSClassDefinition) (JSContext.kt:-2) - at void skip.script.JSValue.(skip.script.JSContext, kotlin.jvm.functions.Function3) (JSContext.kt:267) - at void skip.script.SkipContextTests.testCallFunctionNoArgs$SkipScript_debugAndroidTest() (SkipContextTests.kt:32) - at java.lang.Object java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object[]) (Method.java:-2) - at java.lang.Object org.junit.runners.model.FrameworkMethod$1.runReflectiveCall() (FrameworkMethod.java:59) - at java.lang.Object org.junit.internal.runners.model.ReflectiveCallable.run() (ReflectiveCallable.java:12) - at java.lang.Object org.junit.runners.model.FrameworkMethod.invokeExplosively(java.lang.Object, java.lang.Object[]) (FrameworkMethod.java:56) - at void org.junit.internal.runners.statements.InvokeMethod.evaluate() (InvokeMethod.java:17) - at void androidx.test.internal.runner.junit4.statement.RunBefores.evaluate() (RunBefores.java:80) - at void androidx.test.internal.runner.junit4.statement.RunAfters.evaluate() (RunAfters.java:61) - at void org.junit.runners.ParentRunner$3.evaluate() (ParentRunner.java:306) - at void org.junit.runners.BlockJUnit4ClassRunner$1.evaluate() (BlockJUnit4ClassRunner.java:100) - at void org.junit.runners.ParentRunner.runLeaf(org.junit.runners.model.Statement, org.junit.runner.Description, org.junit.runner.notification.RunNotifier) (ParentRunner.java:366) - at void org.junit.runners.BlockJUnit4ClassRunner.runChild(org.junit.runners.model.FrameworkMethod, org.junit.runner.notification.RunNotifier) (BlockJUnit4ClassRunner.java:103) - at void org.junit.runners.BlockJUnit4ClassRunner.runChild(java.lang.Object, org.junit.runner.notification.RunNotifier) (BlockJUnit4ClassRunner.java:63) - at void org.junit.runners.ParentRunner$4.run() (ParentRunner.java:331) - at void org.junit.runners.ParentRunner$1.schedule(java.lang.Runnable) (ParentRunner.java:79) - at void org.junit.runners.ParentRunner.runChildren(org.junit.runner.notification.RunNotifier) (ParentRunner.java:329) - at void org.junit.runners.ParentRunner.access$100(org.junit.runners.ParentRunner, org.junit.runner.notification.RunNotifier) (ParentRunner.java:66) - at void org.junit.runners.ParentRunner$2.evaluate() (ParentRunner.java:293) - at void org.junit.runners.ParentRunner$3.evaluate() (ParentRunner.java:306) - at void org.junit.runners.ParentRunner.run(org.junit.runner.notification.RunNotifier) (ParentRunner.java:413) - at void org.junit.runners.Suite.runChild(org.junit.runner.Runner, org.junit.runner.notification.RunNotifier) (Suite.java:128) - at void org.junit.runners.Suite.runChild(java.lang.Object, org.junit.runner.notification.RunNotifier) (Suite.java:27) - at void org.junit.runners.ParentRunner$4.run() (ParentRunner.java:331) - at void org.junit.runners.ParentRunner$1.schedule(java.lang.Runnable) (ParentRunner.java:79) - at void org.junit.runners.ParentRunner.runChildren(org.junit.runner.notification.RunNotifier) (ParentRunner.java:329) - at void org.junit.runners.ParentRunner.access$100(org.junit.runners.ParentRunner, org.junit.runner.notification.RunNotifier) (ParentRunner.java:66) - at void org.junit.runners.ParentRunner$2.evaluate() (ParentRunner.java:293) - at void org.junit.runners.ParentRunner$3.evaluate() (ParentRunner.java:306) - at void org.junit.runners.ParentRunner.run(org.junit.runner.notification.RunNotifier) (ParentRunner.java:413) - at org.junit.runner.Result org.junit.runner.JUnitCore.run(org.junit.runner.Runner) (JUnitCore.java:137) - at org.junit.runner.Result org.junit.runner.JUnitCore.run(org.junit.runner.Request) (JUnitCore.java:115) - at android.os.Bundle androidx.test.internal.runner.TestExecutor.execute(org.junit.runner.JUnitCore, org.junit.runner.Request) (TestExecutor.java:68) - at android.os.Bundle androidx.test.internal.runner.TestExecutor.execute(org.junit.runner.Request) (TestExecutor.java:59) - at void androidx.test.runner.AndroidJUnitRunner.onStart() (AndroidJUnitRunner.java:463) - at void android.app.Instrumentation$InstrumentationThread.run() (Instrumentation.java:2402) - */ - } let ctx = JSContext() let fun = JSValue(newFunctionIn: ctx) { ctx, obj, args in JSValue(double: .pi, in: ctx) @@ -127,10 +31,6 @@ Caused by: java.lang.IllegalArgumentException: Callback must implement a single } func testCallFunction() throws { - if isAndroid { - throw XCTSkip("FIXME: crashes on Android emulator in CI") // but not when testing against a local emulator - } - // we run this many times in order to ensure that neither JavaScript GC nor Java GC will cause the function's struct/class being freed for i in 1...10 { let ctx = JSContext() @@ -147,41 +47,62 @@ Caused by: java.lang.IllegalArgumentException: Callback must implement a single } } - func testFunctionProperty() throws { - if isAndroid { - throw XCTSkip("FIXME: crashes on Android emulator in CI") // also fails in local emulator + func testStringArgsFunctionProperty() throws { + let ctx = JSContext() + let stringify = JSValue(newFunctionIn: ctx) { ctx, obj, args in + JSValue(string: args.compactMap({ $0.toString() }).joined(), in: ctx) } + ctx.setObject(stringify, forKeyedSubscript: "stringify") + XCTAssertEqual("", ctx.evaluateScript("stringify()")?.toString()) + + // call with args crashes on Android with SIGSEGV with Problematic frame: [jna9291175543343818311.tmp+0x7448] Java_com_sun_jna_Native__1getPointer+0x0 + XCTAssertEqual("", ctx.evaluateScript("stringify('')")?.toString()) + XCTAssertEqual("ABC", ctx.evaluateScript("stringify('A', 'BC')")?.toString()) + XCTAssertEqual("true12X", ctx.evaluateScript("stringify(true, 1, 2, 'X')")?.toString()) + } + + func testDoubleArgsFunctionProperty() throws { let ctx = JSContext() let sum = JSValue(newFunctionIn: ctx) { ctx, obj, args in JSValue(double: args.reduce(0.0, { $0 + $1.toDouble() }), in: ctx) } - ctx.setObject(sum, forKeyedSubscript: "sum") + let ob = JSValue(newObjectIn: ctx) + ob.setObject(sum, forKeyedSubscript: "sum") + + ctx.setObject(ob, forKeyedSubscript: "ob") XCTAssertNil(ctx.exception) + for _ in 1...999 { + let r1 = try XCTUnwrap(ctx.evaluateScript("ob.sum()")) + XCTAssertNil(ctx.exception) + XCTAssertFalse(r1.isUndefined) + XCTAssertEqual(0.0, r1.toDouble()) - //do { - // let r0 = try XCTUnwrap(ctx.evaluateScript("sum('1')")) - // XCTAssertNil(ctx.exception) - // XCTAssertFalse(r0.isUndefined) - // XCTAssertEqual(1.0, r0.toDouble()) - //} + do { + let r1 = try XCTUnwrap(ctx.evaluateScript("ob.sum(1)")) + XCTAssertNil(ctx.exception) + XCTAssertFalse(r1.isUndefined) + XCTAssertEqual(1.0, r1.toDouble()) + } - //do { - // let r1 = try XCTUnwrap(ctx.evaluateScript("sum(1)")) - // XCTAssertNil(ctx.exception) - // XCTAssertFalse(r1.isUndefined) - // XCTAssertEqual(1.0, r1.toDouble()) - //} + do { + let r2 = try XCTUnwrap(ctx.evaluateScript("ob.sum(1, 2, 3.4, 9.9)")) + XCTAssertNil(ctx.exception) + XCTAssertFalse(r2.isUndefined) + XCTAssertEqual(16.3, r2.toDouble()) + } - //do { - // let r2 = try XCTUnwrap(ctx.evaluateScript("sum(1, 2, 3.4, 9.9)")) - // XCTAssertNil(ctx.exception) - // XCTAssertFalse(r2.isUndefined) - // XCTAssertEqual(16.3, r2.toDouble()) - //} + do { + let r0 = try XCTUnwrap(ctx.evaluateScript("ob.sum('1')")) + XCTAssertNil(ctx.exception) + XCTAssertFalse(r0.isUndefined) + XCTAssertEqual(1.0, r0.toDouble()) + } + } XCTAssertTrue(sum.isFunction) + XCTAssertTrue(ctx.objectForKeyedSubscript("ob").objectForKeyedSubscript("sum").isFunction) } }