diff --git a/dcp/dry/class_manager.py b/dcp/dry/class_manager.py index d45eae1..3c731aa 100644 --- a/dcp/dry/class_manager.py +++ b/dcp/dry/class_manager.py @@ -101,7 +101,9 @@ def js_ref_generator(self, *args, **kwargs): # otherwise, instantiate a new underlying js ref using the ctor args else: async_wrapped_ctor = blockify(pm.new(js_class)) - self.js_ref = async_wrapped_ctor(*args, **kwargs) + # If constructor takes other BF2 objects, the underlying JS proxy must be passed instead + unwrapped_args = tuple(arg.js_ref if hasattr(arg, 'js_ref') else arg for arg in args) + self.js_ref = async_wrapped_ctor(*unwrapped_args, **kwargs) return self.js_ref return make_new_class(js_ref_generator, name, js_class=js_class) @@ -120,6 +122,10 @@ def wrap_obj(js_val): bfclass = reg.add(bfclass) return bfclass(js_val) + + # TODO: Arrays from bf2 objects are returned UNWRAPPED as pm.JSArrayProxy + # TODO: This causes errors when you call an async function within an array + # TODO: To solve this we likely have to write a bf2 array wrapper? return js_val # TODO: there must be a better way to check for this... diff --git a/dcp/initialization.py b/dcp/initialization.py index 099fee4..ebe286b 100644 --- a/dcp/initialization.py +++ b/dcp/initialization.py @@ -37,7 +37,9 @@ def fn_wrapper(*args, **kwargs): for arg in args: if js.utils.throws_in_pm(arg): raise Exception(f'{type(arg)} is not supported in PythonMonkey') - ret_val = aio.blockify(prop_ref)(*args, **kwargs) + # If function takes BF2 objects, the underlying JS proxy must be passed instead + unwrapped_args = tuple(arg.js_ref if hasattr(arg, 'js_ref') else arg for arg in args) + ret_val = aio.blockify(prop_ref)(*unwrapped_args, **kwargs) return _wrap_js('dynamically_accessed_property', ret_val) return fn_wrapper diff --git a/tests/test_js_wrappers/test_object_wrapper.py b/tests/test_js_wrappers/test_object_wrapper.py new file mode 100644 index 0000000..ee25c10 --- /dev/null +++ b/tests/test_js_wrappers/test_object_wrapper.py @@ -0,0 +1,97 @@ +import unittest +import pythonmonkey as pm +from dcp.initialization import _wrap_js + +JSCookieJar = pm.eval(""" +class CookieJar +{ + constructor(...cookies) + { + this.cookies = [] + for (var cookie of cookies) { + this.addCookie(cookie) + } + } + addCookie(cookie) + { + this.cookies.push(cookie) + } + topCookie() + { + return this.cookies.at(-1) + } +} +CookieJar; +""") + +JSCookie = pm.eval(""" +class Cookie +{ + constructor(flavor) + { + this.flavor = flavor + } +} +Cookie; +""") + +PyCookie = _wrap_js("Cookie", JSCookie) +PyCookieJar = _wrap_js("CookieJar", JSCookieJar) + + +class TestObjectUnwrapping(unittest.TestCase): + + def test_primitive_args_func(self): + # ensure wrapped functions return correct primitives + bf_fn = _wrap_js('test_fn', pm.eval('(x) => x')) + self.assertEqual(bf_fn("Hello"), "Hello") + self.assertEqual(type(bf_fn(7)), float) + + def test_bifrost_args_func(self): + # object must be unwrapped before being passed to pm + bf_fn = _wrap_js('test_fn', pm.eval('(cookie) => cookie instanceof Cookie')) + cookie = PyCookie('chocolate chip') + self.assertTrue(bf_fn(cookie)) + + # returned object must be wrapped + bf_fn = _wrap_js('test_fn', pm.eval('(cookie) => cookie')) + cookie = PyCookie('chocolate chip') + self.assertEqual(type(bf_fn(cookie)), PyCookie) + + def test_primitive_args_ctor(self): + + cookie = PyCookie('chocolate chip') + + # Verify object is wrapped properly + self.assertEqual(type(cookie), PyCookie) + self.assertTrue(hasattr(cookie, 'js_ref')) + self.assertEqual(cookie.flavor, 'chocolate chip') + + # Verify underlying object is the right class + self.assertTrue(pm.eval('(cookie) => cookie instanceof Cookie')(cookie.js_ref)) + + def test_bifrost_args_ctor(self): + + cookie_one = PyCookie('chocolate chip') + cookie_two = PyCookie('oatmeal') + + # Constructors that take bf2 objects must unwrap them + py_jar = PyCookieJar(cookie_one, cookie_two) + + # Attributes and obj need to be the correct type when obtained in underlying js + js_fn = pm.eval('(jar) => jar instanceof CookieJar && jar.topCookie() instanceof Cookie') + self.assertTrue(js_fn(py_jar.js_ref)) + + def test_bifrost_args_method(self): + + py_jar = PyCookieJar() + + # bf2 objects passed as function arguments must be unwrapped + py_jar.addCookie( PyCookie('chocolate chip') ) + js_fn = pm.eval('(jar) => jar.topCookie() instanceof Cookie') + + self.assertTrue(js_fn(py_jar.js_ref)) + + +if __name__ == '__main__': + unittest.main()