From 0a00e7c97bd72aa3b81d4c5fd4bd1218d66e994d Mon Sep 17 00:00:00 2001 From: Philipp Renoth Date: Mon, 11 Jun 2018 03:00:11 +0200 Subject: [PATCH] src: implement missing descriptor defs for symbols Implements descriptor definitions with symbols for `StaticMethod`, `StaticAccessor`, `InstanceAccessor`, `StaticValue`, `InstanceValue`. Ref: https://github.com/nodejs/node-addon-api/issues/279 PR-URL: https://github.com/nodejs/node-addon-api/pull/280 Refs: https://github.com/nodejs/node-addon-api/issues/279 Reviewed-By: Michael Dawson --- doc/object_wrap.md | 133 ++++++++++++++++++++++++++ napi-inl.h | 96 +++++++++++++++++++ napi.h | 24 +++++ test/objectwrap.cc | 120 ++++++++++++++++------- test/objectwrap.js | 234 ++++++++++++++++++++++++++++++++++++--------- 5 files changed, 530 insertions(+), 77 deletions(-) diff --git a/doc/object_wrap.md b/doc/object_wrap.md index d422b4e8e..93773da69 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -234,6 +234,50 @@ One or more of `napi_property_attributes`. Returns `Napi::PropertyDescriptor` object that represents a static method of a JavaScript class. +### StaticMethod + +Creates property descriptor that represents a static method of a JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, + StaticVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: Napi:Symbol that represents the name of a static +method for the class. +- `[in] method`: The native function that represents a static method of a +JavaScript class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents the static method of a +JavaScript class. + +### StaticMethod + +Creates property descriptor that represents a static method of a JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticMethod(Symbol name, + StaticMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +method for the class. +- `[in] name`: Napi:Symbol that represents the name of a static. +- `[in] method`: The native function that represents a static method of a +JavaScript class. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into method when it is invoked. + +Returns `Napi::PropertyDescriptor` object that represents a static method of a +JavaScript class. + ### StaticAccessor Creates property descriptor that represents a static accessor property of a @@ -261,6 +305,32 @@ is invoked. Returns `Napi::PropertyDescriptor` object that represents a static accessor property of a JavaScript class. +### StaticAccessor + +Creates property descriptor that represents a static accessor property of a +JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticAccessor(Symbol name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: Napi:Symbol that represents the name of a static accessor. +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] attributes`: The attributes associated with a particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into getter or setter when +is invoked. + +Returns `Napi::PropertyDescriptor` object that represents a static accessor +property of a JavaScript class. + ### InstanceMethod Creates property descriptor that represents an instance method of a JavaScript class. @@ -375,6 +445,32 @@ One or more of `napi_property_attributes`. Returns `Napi::PropertyDescriptor` object that represents an instance accessor property of a JavaScript class. +### InstanceAccessor + +Creates property descriptor that represents an instance accessor property of a +JavaScript class. + +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceAccessor(Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); +``` + +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +instance accessor. +- `[in] getter`: The native function to call when a get access to the property of +a JavaScript class is performed. +- `[in] setter`: The native function to call when a set access to the property of +a JavaScript class is performed. +- `[in] attributes`: The attributes associated with the particular property. +One or more of `napi_property_attributes`. +- `[in] data`: User-provided data passed into getter or setter when this is invoked. + +Returns `Napi::PropertyDescriptor` object that represents an instance accessor +property of a JavaScript class. + ### StaticValue Creates property descriptor that represents an static value property of a @@ -394,6 +490,25 @@ to the napi_static attribute. One or more of `napi_property_attributes`. Returns `Napi::PropertyDescriptor` object that represents an static value property of a JavaScript class +### StaticValue + +Creates property descriptor that represents an static value property of a +JavaScript class. +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::StaticValue(Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); +``` + +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +name of the static property. +- `[in] value`: The value that's retrieved by a get access of the property. +- `[in] attributes`: The attributes to be associated with the property in addition +to the napi_static attribute. One or more of `napi_property_attributes`. + +Returns `Napi::PropertyDescriptor` object that represents an static value +property of a JavaScript class + ### InstanceValue Creates property descriptor that represents an instance value property of a @@ -411,3 +526,21 @@ One or more of `napi_property_attributes`. Returns `Napi::PropertyDescriptor` object that represents an instance value property of a JavaScript class. + +### InstanceValue + +Creates property descriptor that represents an instance value property of a +JavaScript class. +```cpp +static Napi::PropertyDescriptor Napi::ObjectWrap::InstanceValue(Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); +``` + +- `[in] name`: The `Napi::Symbol` object whose value is used to identify the +name of the property. +- `[in] value`: The value that's retrieved by a get access of the property. +- `[in] attributes`: The attributes to be associated with the property. +One or more of `napi_property_attributes`. + +Returns `Napi::PropertyDescriptor` object that represents an instance value diff --git a/napi-inl.h b/napi-inl.h index 813a27adc..59eacf9f8 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -2823,6 +2823,40 @@ inline ClassPropertyDescriptor ObjectWrap::StaticMethod( return desc; } +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, + StaticVoidMethodCallback method, + napi_property_attributes attributes, + void* data) { + // TODO: Delete when the class is destroyed + StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data }); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::StaticVoidMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + +template +inline ClassPropertyDescriptor ObjectWrap::StaticMethod( + Symbol name, + StaticMethodCallback method, + napi_property_attributes attributes, + void* data) { + // TODO: Delete when the class is destroyed + StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data }); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.method = T::StaticMethodCallbackWrapper; + desc.data = callbackData; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + template inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( const char* utf8name, @@ -2843,6 +2877,26 @@ inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( return desc; } +template +inline ClassPropertyDescriptor ObjectWrap::StaticAccessor( + Symbol name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes, + void* data) { + // TODO: Delete when the class is destroyed + StaticAccessorCallbackData* callbackData = + new StaticAccessorCallbackData({ getter, setter, data }); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + template inline ClassPropertyDescriptor ObjectWrap::InstanceMethod( const char* utf8name, @@ -2933,6 +2987,26 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( return desc; } +template +inline ClassPropertyDescriptor ObjectWrap::InstanceAccessor( + Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes, + void* data) { + // TODO: Delete when the class is destroyed + InstanceAccessorCallbackData* callbackData = + new InstanceAccessorCallbackData({ getter, setter, data }); + + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr; + desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr; + desc.data = callbackData; + desc.attributes = attributes; + return desc; +} + template inline ClassPropertyDescriptor ObjectWrap::StaticValue(const char* utf8name, Napi::Value value, napi_property_attributes attributes) { @@ -2943,6 +3017,16 @@ inline ClassPropertyDescriptor ObjectWrap::StaticValue(const char* utf8nam return desc; } +template +inline ClassPropertyDescriptor ObjectWrap::StaticValue(Symbol name, + Napi::Value value, napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.value = value; + desc.attributes = static_cast(attributes | napi_static); + return desc; +} + template inline ClassPropertyDescriptor ObjectWrap::InstanceValue( const char* utf8name, @@ -2955,6 +3039,18 @@ inline ClassPropertyDescriptor ObjectWrap::InstanceValue( return desc; } +template +inline ClassPropertyDescriptor ObjectWrap::InstanceValue( + Symbol name, + Napi::Value value, + napi_property_attributes attributes) { + napi_property_descriptor desc = napi_property_descriptor(); + desc.name = name; + desc.value = value; + desc.attributes = attributes; + return desc; +} + template inline napi_value ObjectWrap::ConstructorCallbackWrapper( napi_env env, diff --git a/napi.h b/napi.h index e142ee79b..a8eafd312 100644 --- a/napi.h +++ b/napi.h @@ -1453,11 +1453,24 @@ namespace Napi { StaticMethodCallback method, napi_property_attributes attributes = napi_default, void* data = nullptr); + static PropertyDescriptor StaticMethod(Symbol name, + StaticVoidMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); + static PropertyDescriptor StaticMethod(Symbol name, + StaticMethodCallback method, + napi_property_attributes attributes = napi_default, + void* data = nullptr); static PropertyDescriptor StaticAccessor(const char* utf8name, StaticGetterCallback getter, StaticSetterCallback setter, napi_property_attributes attributes = napi_default, void* data = nullptr); + static PropertyDescriptor StaticAccessor(Symbol name, + StaticGetterCallback getter, + StaticSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); static PropertyDescriptor InstanceMethod(const char* utf8name, InstanceVoidMethodCallback method, napi_property_attributes attributes = napi_default, @@ -1479,12 +1492,23 @@ namespace Napi { InstanceSetterCallback setter, napi_property_attributes attributes = napi_default, void* data = nullptr); + static PropertyDescriptor InstanceAccessor(Symbol name, + InstanceGetterCallback getter, + InstanceSetterCallback setter, + napi_property_attributes attributes = napi_default, + void* data = nullptr); static PropertyDescriptor StaticValue(const char* utf8name, Napi::Value value, napi_property_attributes attributes = napi_default); + static PropertyDescriptor StaticValue(Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); static PropertyDescriptor InstanceValue(const char* utf8name, Napi::Value value, napi_property_attributes attributes = napi_default); + static PropertyDescriptor InstanceValue(Symbol name, + Napi::Value value, + napi_property_attributes attributes = napi_default); private: static napi_value ConstructorCallbackWrapper(napi_env env, napi_callback_info info); diff --git a/test/objectwrap.cc b/test/objectwrap.cc index fae7b2652..ef1cfa73e 100644 --- a/test/objectwrap.cc +++ b/test/objectwrap.cc @@ -1,66 +1,118 @@ #include -class TestIter : public Napi::ObjectWrap { -public: - TestIter(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) {} +Napi::ObjectReference testStaticContextRef; - Napi::Value Next(const Napi::CallbackInfo& info) { - auto object = Napi::Object::New(info.Env()); - object.Set("done", Napi::Boolean::New(info.Env(), true)); - return object; - } +Napi::Value StaticGetter(const Napi::CallbackInfo& /*info*/) { + return testStaticContextRef.Value().Get("value"); +} - static Napi::FunctionReference Initialize(Napi::Env env) { - return Napi::Persistent(DefineClass(env, "TestIter", { - InstanceMethod("next", &TestIter::Next), - })); - } -}; +void StaticSetter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) { + testStaticContextRef.Value().Set("value", value); +} + +Napi::Value TestStaticMethod(const Napi::CallbackInfo& info) { + std::string str = info[0].ToString(); + return Napi::String::New(info.Env(), str + " static"); +} + +Napi::Value TestStaticMethodInternal(const Napi::CallbackInfo& info) { + std::string str = info[0].ToString(); + return Napi::String::New(info.Env(), str + " static internal"); +} class Test : public Napi::ObjectWrap { public: Test(const Napi::CallbackInfo& info) : - Napi::ObjectWrap(info), - Constructor(TestIter::Initialize(info.Env())) { + Napi::ObjectWrap(info) { } - void SetMethod(const Napi::CallbackInfo& info) { - value = info[0].As(); + void Setter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) { + value_ = value.ToString(); } - Napi::Value GetMethod(const Napi::CallbackInfo& info) { - return Napi::Number::New(info.Env(), value); + Napi::Value Getter(const Napi::CallbackInfo& info) { + return Napi::String::New(info.Env(), value_); } - Napi::Value Iter(const Napi::CallbackInfo& /*info*/) { - return Constructor.New({}); + Napi::Value TestMethod(const Napi::CallbackInfo& info) { + std::string str = info[0].ToString(); + return Napi::String::New(info.Env(), str + " instance"); } - void Setter(const Napi::CallbackInfo& /*info*/, const Napi::Value& new_value) { - value = new_value.As(); + Napi::Value TestMethodInternal(const Napi::CallbackInfo& info) { + std::string str = info[0].ToString(); + return Napi::String::New(info.Env(), str + " instance internal"); } - Napi::Value Getter(const Napi::CallbackInfo& info) { - return Napi::Number::New(info.Env(), value); + Napi::Value ToStringTag(const Napi::CallbackInfo& info) { + return Napi::String::From(info.Env(), "TestTag"); + } + + // creates dummy array, returns `([value])[Symbol.iterator]()` + Napi::Value Iterator(const Napi::CallbackInfo& info) { + Napi::Array array = Napi::Array::New(info.Env()); + array.Set(array.Length(), Napi::String::From(info.Env(), value_)); + return array.Get(Napi::Symbol::WellKnown(info.Env(), "iterator")).As().Call(array, {}); } static void Initialize(Napi::Env env, Napi::Object exports) { + + Napi::Symbol kTestStaticValueInternal = Napi::Symbol::New(env, "kTestStaticValueInternal"); + Napi::Symbol kTestStaticAccessorInternal = Napi::Symbol::New(env, "kTestStaticAccessorInternal"); + Napi::Symbol kTestStaticMethodInternal = Napi::Symbol::New(env, "kTestStaticMethodInternal"); + + Napi::Symbol kTestValueInternal = Napi::Symbol::New(env, "kTestValueInternal"); + Napi::Symbol kTestAccessorInternal = Napi::Symbol::New(env, "kTestAccessorInternal"); + Napi::Symbol kTestMethodInternal = Napi::Symbol::New(env, "kTestMethodInternal"); + exports.Set("Test", DefineClass(env, "Test", { - InstanceMethod("test_set_method", &Test::SetMethod), - InstanceMethod("test_get_method", &Test::GetMethod), - InstanceMethod(Napi::Symbol::WellKnown(env, "iterator"), &Test::Iter), - InstanceAccessor("test_getter_only", &Test::Getter, nullptr), - InstanceAccessor("test_setter_only", nullptr, &Test::Setter), - InstanceAccessor("test_getter_setter", &Test::Getter, &Test::Setter), + + // expose symbols for testing + StaticValue("kTestStaticValueInternal", kTestStaticValueInternal), + StaticValue("kTestStaticAccessorInternal", kTestStaticAccessorInternal), + StaticValue("kTestStaticMethodInternal", kTestStaticMethodInternal), + StaticValue("kTestValueInternal", kTestValueInternal), + StaticValue("kTestAccessorInternal", kTestAccessorInternal), + StaticValue("kTestMethodInternal", kTestMethodInternal), + + // test data + StaticValue("testStaticValue", Napi::String::New(env, "value"), napi_enumerable), + StaticValue(kTestStaticValueInternal, Napi::Number::New(env, 5), napi_default), + + StaticAccessor("testStaticGetter", &StaticGetter, nullptr, napi_enumerable), + StaticAccessor("testStaticSetter", nullptr, &StaticSetter, napi_default), + StaticAccessor("testStaticGetSet", &StaticGetter, &StaticSetter, napi_enumerable), + StaticAccessor(kTestStaticAccessorInternal, &StaticGetter, &StaticSetter, napi_enumerable), + + StaticMethod("testStaticMethod", &TestStaticMethod, napi_enumerable), + StaticMethod(kTestStaticMethodInternal, &TestStaticMethodInternal, napi_default), + + InstanceValue("testValue", Napi::Boolean::New(env, true), napi_enumerable), + InstanceValue(kTestValueInternal, Napi::Boolean::New(env, false), napi_enumerable), + + InstanceAccessor("testGetter", &Test::Getter, nullptr, napi_enumerable), + InstanceAccessor("testSetter", nullptr, &Test::Setter, napi_default), + InstanceAccessor("testGetSet", &Test::Getter, &Test::Setter, napi_enumerable), + InstanceAccessor(kTestAccessorInternal, &Test::Getter, &Test::Setter, napi_enumerable), + + InstanceMethod("testMethod", &Test::TestMethod, napi_enumerable), + InstanceMethod(kTestMethodInternal, &Test::TestMethodInternal, napi_default), + + // conventions + InstanceAccessor(Napi::Symbol::WellKnown(env, "toStringTag"), &Test::ToStringTag, nullptr, napi_enumerable), + InstanceMethod(Napi::Symbol::WellKnown(env, "iterator"), &Test::Iterator, napi_default), + })); } private: - uint32_t value; - Napi::FunctionReference Constructor; + std::string value_; }; Napi::Object InitObjectWrap(Napi::Env env) { + testStaticContextRef = Napi::Persistent(Napi::Object::New(env)); + testStaticContextRef.SuppressDestruct(); + Napi::Object exports = Napi::Object::New(env); Test::Initialize(env, exports); return exports; diff --git a/test/objectwrap.js b/test/objectwrap.js index 4098c7e32..72805000d 100644 --- a/test/objectwrap.js +++ b/test/objectwrap.js @@ -2,61 +2,209 @@ const buildType = process.config.target_defaults.default_configuration; const assert = require('assert'); -test(require(`./build/${buildType}/binding.node`)); -test(require(`./build/${buildType}/binding_noexcept.node`)); +const test = (binding) => { + const Test = binding.objectwrap.Test; -function test(binding) { - var Test = binding.objectwrap.Test; + const testValue = (obj, clazz) => { + assert.strictEqual(obj.testValue, true); + assert.strictEqual(obj[clazz.kTestValueInternal], false); + }; - function testSetGetMethod(obj) { - obj.test_set_method(90); - assert.strictEqual(obj.test_get_method(), 90); - } + const testAccessor = (obj, clazz) => { + // read-only, write-only + { + obj.testSetter = 'instance getter'; + assert.strictEqual(obj.testGetter, 'instance getter'); - function testIter(obj) { - for (const value of obj) { + obj.testSetter = 'instance getter 2'; + assert.strictEqual(obj.testGetter, 'instance getter 2'); + } + + // read write-only + { + let error; + try { const read = obj.testSetter; } catch (e) { error = e; } + // no error + assert.strictEqual(error, undefined); + + // read is undefined + assert.strictEqual(obj.testSetter, undefined); } - } - function testGetterOnly(obj) { - obj.test_set_method(91); - assert.strictEqual(obj.test_getter_only, 91); - - let error; - try { - // Can not assign to read only property. - obj.test_getter_only = 92; - } catch(e) { - error = e; - } finally { + // write read-only + { + let error; + try { obj.testGetter = 'write'; } catch (e) { error = e; } assert.strictEqual(error.name, 'TypeError'); } - } - function testSetterOnly(obj) { - obj.test_setter_only = 93; - assert.strictEqual(obj.test_setter_only, undefined); - assert.strictEqual(obj.test_getter_only, 93); - } + // rw + { + obj.testGetSet = 'instance getset'; + assert.strictEqual(obj.testGetSet, 'instance getset'); + + obj.testGetSet = 'instance getset 2'; + assert.strictEqual(obj.testGetSet, 'instance getset 2'); + } + + // rw symbol + { + obj[clazz.kTestAccessorInternal] = 'instance internal getset'; + assert.strictEqual(obj[clazz.kTestAccessorInternal], 'instance internal getset'); + + obj[clazz.kTestAccessorInternal] = 'instance internal getset 2'; + assert.strictEqual(obj[clazz.kTestAccessorInternal], 'instance internal getset 2'); + } + }; + + const testMethod = (obj, clazz) => { + assert.strictEqual(obj.testMethod('method'), 'method instance'); + assert.strictEqual(obj[clazz.kTestMethodInternal]('method'), 'method instance internal'); + }; + + const testEnumerables = (obj, clazz) => { + // Object.keys: only object + assert.deepEqual(Object.keys(obj), []); + + // for..in: object + prototype + { + const keys = []; + for (let key in obj) { + keys.push(key); + } + + assert.deepEqual(keys, [ + 'testGetSet', + 'testGetter', + 'testValue', + 'testMethod' + ]); + } + }; - function testGetterSetter(obj) { - obj.test_getter_setter = 94; - assert.strictEqual(obj.test_getter_setter, 94); + const testConventions = (obj, clazz) => { + // test @@toStringTag + { + assert.strictEqual(obj[Symbol.toStringTag], 'TestTag'); + assert.strictEqual('' + obj, '[object TestTag]'); + } + + // test @@iterator + { + obj.testSetter = 'iterator'; + const values = []; + + for (let item of obj) { + values.push(item); + } - obj.test_getter_setter = 95; - assert.strictEqual(obj.test_getter_setter, 95); + assert.deepEqual(values, ['iterator']); + } + }; + + const testStaticValue = (clazz) => { + assert.strictEqual(clazz.testStaticValue, 'value'); + assert.strictEqual(clazz[clazz.kTestStaticValueInternal], 5); } - function testObj(obj) { - testSetGetMethod(obj); - testIter(obj); - testGetterOnly(obj); - testSetterOnly(obj); - testGetterSetter(obj); + const testStaticAccessor = (clazz) => { + // read-only, write-only + { + const tempObj = {}; + clazz.testStaticSetter = tempObj; + assert.strictEqual(clazz.testStaticGetter, tempObj); + + const tempArray = []; + clazz.testStaticSetter = tempArray; + assert.strictEqual(clazz.testStaticGetter, tempArray); + } + + // read write-only + { + let error; + try { const read = clazz.testStaticSetter; } catch (e) { error = e; } + // no error + assert.strictEqual(error, undefined); + + // read is undefined + assert.strictEqual(clazz.testStaticSetter, undefined); + } + + // write-read-only + { + let error; + try { clazz.testStaticGetter = 'write'; } catch (e) { error = e; } + assert.strictEqual(error.name, 'TypeError'); + } + + // rw + { + clazz.testStaticGetSet = 9; + assert.strictEqual(clazz.testStaticGetSet, 9); + + clazz.testStaticGetSet = 4; + assert.strictEqual(clazz.testStaticGetSet, 4); + } + + // rw symbol + { + clazz[clazz.kTestStaticAccessorInternal] = 'static internal getset'; + assert.strictEqual(clazz[clazz.kTestStaticAccessorInternal], 'static internal getset'); + } + }; + + const testStaticMethod = (clazz) => { + assert.strictEqual(clazz.testStaticMethod('method'), 'method static'); + assert.strictEqual(clazz[clazz.kTestStaticMethodInternal]('method'), 'method static internal'); + }; + + const testStaticEnumerables = (clazz) => { + // Object.keys + assert.deepEqual(Object.keys(clazz), [ + 'testStaticValue', + 'testStaticGetter', + 'testStaticGetSet', + 'testStaticMethod' + ]); + + // for..in + { + const keys = []; + for (let key in clazz) { + keys.push(key); + } + + assert.deepEqual(keys, [ + 'testStaticValue', + 'testStaticGetter', + 'testStaticGetSet', + 'testStaticMethod' + ]); + } + }; + + const testObj = (obj, clazz) => { + testValue(obj, clazz); + testAccessor(obj, clazz); + testMethod(obj, clazz); + + testEnumerables(obj, clazz); + + testConventions(obj, clazz); } - testObj(new Test()); - testObj(new Test(1)); - testObj(new Test(1, 2, 3, 4, 5, 6)); - testObj(new Test(1, 2, 3, 4, 5, 6, 7)); + const testClass = (clazz) => { + testStaticValue(clazz); + testStaticAccessor(clazz); + testStaticMethod(clazz); + + testStaticEnumerables(clazz); + }; + + // `Test` is needed for accessing exposed symbols + testObj(new Test(), Test); + testClass(Test); } + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); \ No newline at end of file