diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 73320e527b4..340734bfe5a 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -544,10 +544,12 @@ pub(crate) mod svggraphicselement; pub(crate) mod svgsvgelement; pub(crate) mod testbinding; pub(crate) mod testbindingiterable; -pub(crate) mod testbindingmaplike; +pub(crate) mod testbindingmaplikewithinterface; +pub(crate) mod testbindingmaplikewithprimitive; pub(crate) mod testbindingpairiterable; pub(crate) mod testbindingproxy; -pub(crate) mod testbindingsetlike; +pub(crate) mod testbindingsetlikewithinterface; +pub(crate) mod testbindingsetlikewithprimitive; pub(crate) mod testns; pub(crate) mod testworklet; pub(crate) mod testworkletglobalscope; diff --git a/components/script/dom/testbindingmaplikewithinterface.rs b/components/script/dom/testbindingmaplikewithinterface.rs new file mode 100644 index 00000000000..1297f89e7fd --- /dev/null +++ b/components/script/dom/testbindingmaplikewithinterface.rs @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// check-tidy: no specs after this line + +use dom_struct::dom_struct; +use indexmap::IndexMap; +use js::rust::HandleObject; + +use super::bindings::error::Error; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::TestBindingMaplikeWithInterfaceBinding::TestBindingMaplikeWithInterfaceMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::like::Maplike; +use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::testbinding::TestBinding; +use crate::maplike; +use crate::script_runtime::CanGc; + +/// maplike +#[dom_struct] +pub(crate) struct TestBindingMaplikeWithInterface { + reflector: Reflector, + #[custom_trace] + internal: DomRefCell>>, +} + +impl TestBindingMaplikeWithInterface { + fn new( + global: &GlobalScope, + proto: Option, + can_gc: CanGc, + ) -> DomRoot { + reflect_dom_object_with_proto( + Box::new(TestBindingMaplikeWithInterface { + reflector: Reflector::new(), + internal: DomRefCell::new(IndexMap::new()), + }), + global, + proto, + can_gc, + ) + } +} + +impl TestBindingMaplikeWithInterfaceMethods + for TestBindingMaplikeWithInterface +{ + fn Constructor( + global: &GlobalScope, + proto: Option, + can_gc: CanGc, + ) -> Fallible> { + Ok(TestBindingMaplikeWithInterface::new(global, proto, can_gc)) + } + + fn SetInternal(&self, key: DOMString, value: &TestBinding) { + let value = DomRoot::from_ref(value); + self.internal.set(key, value) + } + + fn ClearInternal(&self) { + self.internal.clear() + } + + fn DeleteInternal(&self, key: DOMString) -> bool { + self.internal.delete(key) + } + + fn HasInternal(&self, key: DOMString) -> bool { + self.internal.has(key) + } + + fn GetInternal(&self, key: DOMString) -> Fallible> { + // TODO: error type? + self.internal + .borrow() + .get(&key) + .ok_or_else(|| Error::Type(format!("No entry for key {key}"))) + .cloned() + } + + fn Size(&self) -> u32 { + self.internal.size() + } +} + +// this error is wrong because if we inline Self::Key and Self::Value all errors are gone +// TODO: FIX THIS +#[cfg_attr(crown, allow(crown::unrooted_must_root))] +impl Maplike for TestBindingMaplikeWithInterface { + type Key = DOMString; + type Value = DomRoot; + + maplike!(self, internal); +} diff --git a/components/script/dom/testbindingmaplike.rs b/components/script/dom/testbindingmaplikewithprimitive.rs similarity index 78% rename from components/script/dom/testbindingmaplike.rs rename to components/script/dom/testbindingmaplikewithprimitive.rs index d055c68e12e..4768bb246c0 100644 --- a/components/script/dom/testbindingmaplike.rs +++ b/components/script/dom/testbindingmaplikewithprimitive.rs @@ -10,7 +10,7 @@ use js::rust::HandleObject; use super::bindings::error::Error; use crate::dom::bindings::cell::DomRefCell; -use crate::dom::bindings::codegen::Bindings::TestBindingMaplikeBinding::TestBindingMaplikeMethods; +use crate::dom::bindings::codegen::Bindings::TestBindingMaplikeWithPrimitiveBinding::TestBindingMaplikeWithPrimitiveMethods; use crate::dom::bindings::error::Fallible; use crate::dom::bindings::like::Maplike; use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector}; @@ -22,20 +22,20 @@ use crate::script_runtime::CanGc; /// maplike #[dom_struct] -pub(crate) struct TestBindingMaplike { +pub(crate) struct TestBindingMaplikeWithPrimitive { reflector: Reflector, #[custom_trace] internal: DomRefCell>, } -impl TestBindingMaplike { +impl TestBindingMaplikeWithPrimitive { fn new( global: &GlobalScope, proto: Option, can_gc: CanGc, - ) -> DomRoot { + ) -> DomRoot { reflect_dom_object_with_proto( - Box::new(TestBindingMaplike { + Box::new(TestBindingMaplikeWithPrimitive { reflector: Reflector::new(), internal: DomRefCell::new(IndexMap::new()), }), @@ -46,13 +46,15 @@ impl TestBindingMaplike { } } -impl TestBindingMaplikeMethods for TestBindingMaplike { +impl TestBindingMaplikeWithPrimitiveMethods + for TestBindingMaplikeWithPrimitive +{ fn Constructor( global: &GlobalScope, proto: Option, can_gc: CanGc, - ) -> Fallible> { - Ok(TestBindingMaplike::new(global, proto, can_gc)) + ) -> Fallible> { + Ok(TestBindingMaplikeWithPrimitive::new(global, proto, can_gc)) } fn SetInternal(&self, key: DOMString, value: i32) { @@ -88,7 +90,7 @@ impl TestBindingMaplikeMethods for TestBindingMaplike { // this error is wrong because if we inline Self::Key and Self::Value all errors are gone // TODO: FIX THIS #[cfg_attr(crown, allow(crown::unrooted_must_root))] -impl Maplike for TestBindingMaplike { +impl Maplike for TestBindingMaplikeWithPrimitive { type Key = DOMString; type Value = i32; diff --git a/components/script/dom/testbindingsetlikewithinterface.rs b/components/script/dom/testbindingsetlikewithinterface.rs new file mode 100644 index 00000000000..26439b20103 --- /dev/null +++ b/components/script/dom/testbindingsetlikewithinterface.rs @@ -0,0 +1,71 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// check-tidy: no specs after this line + +use dom_struct::dom_struct; +use indexmap::IndexSet; +use js::rust::HandleObject; + +use super::bindings::like::Setlike; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::TestBindingSetlikeWithInterfaceBinding::TestBindingSetlikeWithInterfaceMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use crate::dom::testbinding::TestBinding; +use crate::script_runtime::CanGc; +use crate::setlike; + +// setlike +#[dom_struct] +pub(crate) struct TestBindingSetlikeWithInterface { + reflector: Reflector, + #[custom_trace] + internal: DomRefCell>>, +} + +impl TestBindingSetlikeWithInterface { + fn new( + global: &GlobalScope, + proto: Option, + can_gc: CanGc, + ) -> DomRoot { + reflect_dom_object_with_proto( + Box::new(TestBindingSetlikeWithInterface { + reflector: Reflector::new(), + internal: DomRefCell::new(IndexSet::new()), + }), + global, + proto, + can_gc, + ) + } +} + +impl TestBindingSetlikeWithInterfaceMethods + for TestBindingSetlikeWithInterface +{ + fn Constructor( + global: &GlobalScope, + proto: Option, + can_gc: CanGc, + ) -> Fallible> { + Ok(TestBindingSetlikeWithInterface::new(global, proto, can_gc)) + } + + fn Size(&self) -> u32 { + self.internal.size() + } +} + +// this error is wrong because if we inline Self::Key and Self::Value all errors are gone +// TODO: FIX THIS +#[cfg_attr(crown, allow(crown::unrooted_must_root))] +impl Setlike for TestBindingSetlikeWithInterface { + type Key = DomRoot; + + setlike!(self, internal); +} diff --git a/components/script/dom/testbindingsetlike.rs b/components/script/dom/testbindingsetlikewithprimitive.rs similarity index 72% rename from components/script/dom/testbindingsetlike.rs rename to components/script/dom/testbindingsetlikewithprimitive.rs index d6fb9e05fd7..f1467b18b15 100644 --- a/components/script/dom/testbindingsetlike.rs +++ b/components/script/dom/testbindingsetlikewithprimitive.rs @@ -10,7 +10,7 @@ use js::rust::HandleObject; use super::bindings::like::Setlike; use crate::dom::bindings::cell::DomRefCell; -use crate::dom::bindings::codegen::Bindings::TestBindingSetlikeBinding::TestBindingSetlikeMethods; +use crate::dom::bindings::codegen::Bindings::TestBindingSetlikeWithPrimitiveBinding::TestBindingSetlikeWithPrimitiveMethods; use crate::dom::bindings::error::Fallible; use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector}; use crate::dom::bindings::root::DomRoot; @@ -21,20 +21,20 @@ use crate::setlike; // setlike #[dom_struct] -pub(crate) struct TestBindingSetlike { +pub(crate) struct TestBindingSetlikeWithPrimitive { reflector: Reflector, #[custom_trace] internal: DomRefCell>, } -impl TestBindingSetlike { +impl TestBindingSetlikeWithPrimitive { fn new( global: &GlobalScope, proto: Option, can_gc: CanGc, - ) -> DomRoot { + ) -> DomRoot { reflect_dom_object_with_proto( - Box::new(TestBindingSetlike { + Box::new(TestBindingSetlikeWithPrimitive { reflector: Reflector::new(), internal: DomRefCell::new(IndexSet::new()), }), @@ -45,13 +45,15 @@ impl TestBindingSetlike { } } -impl TestBindingSetlikeMethods for TestBindingSetlike { +impl TestBindingSetlikeWithPrimitiveMethods + for TestBindingSetlikeWithPrimitive +{ fn Constructor( global: &GlobalScope, proto: Option, can_gc: CanGc, - ) -> Fallible> { - Ok(TestBindingSetlike::new(global, proto, can_gc)) + ) -> Fallible> { + Ok(TestBindingSetlikeWithPrimitive::new(global, proto, can_gc)) } fn Size(&self) -> u32 { @@ -62,7 +64,7 @@ impl TestBindingSetlikeMethods for TestBindingSetlike { // this error is wrong because if we inline Self::Key and Self::Value all errors are gone // TODO: FIX THIS #[cfg_attr(crown, allow(crown::unrooted_must_root))] -impl Setlike for TestBindingSetlike { +impl Setlike for TestBindingSetlikeWithPrimitive { type Key = DOMString; setlike!(self, internal); diff --git a/components/script_bindings/codegen/CodegenRust.py b/components/script_bindings/codegen/CodegenRust.py index 8ad55f4657e..ffd2589376a 100644 --- a/components/script_bindings/codegen/CodegenRust.py +++ b/components/script_bindings/codegen/CodegenRust.py @@ -2688,6 +2688,10 @@ def DomTypes(descriptors, descriptorProvider, dictionaries, callbacks, typedefs, ] joinedTraits = ' + '.join(traits) elements = [CGGeneric(f"pub(crate) trait DomTypes: {joinedTraits} where Self: 'static {{\n")] + + def fixupInterfaceTypeReferences(typename): + return typename.replace("D::", "Self::") + for descriptor in descriptors: iface_name = descriptor.interface.identifier.name traits = [] @@ -2711,11 +2715,17 @@ def DomTypes(descriptors, descriptorProvider, dictionaries, callbacks, typedefs, iterableDecl = descriptor.interface.maplikeOrSetlikeOrIterable if iterableDecl: if iterableDecl.isMaplike(): - keytype = getRetvalDeclarationForType(iterableDecl.keyType, None).define() - valuetype = getRetvalDeclarationForType(iterableDecl.valueType, None).define() + keytype = fixupInterfaceTypeReferences( + getRetvalDeclarationForType(iterableDecl.keyType, descriptor).define() + ) + valuetype = fixupInterfaceTypeReferences( + getRetvalDeclarationForType(iterableDecl.valueType, descriptor).define() + ) traits += [f"crate::dom::bindings::like::Maplike"] if iterableDecl.isSetlike(): - keytype = getRetvalDeclarationForType(iterableDecl.keyType, None).define() + keytype = fixupInterfaceTypeReferences( + getRetvalDeclarationForType(iterableDecl.keyType, descriptor).define() + ) traits += [f"crate::dom::bindings::like::Setlike"] if iterableDecl.hasKeyType(): traits += [ @@ -2769,7 +2779,11 @@ def DomTypes(descriptors, descriptorProvider, dictionaries, callbacks, typedefs, CGGeneric(f" type {firstCap(iface_name)}: {' + '.join(traits)};\n") ] elements += [CGGeneric("}\n")] - return CGList([CGGeneric("use crate::dom::bindings::str::DOMString;\n")] + elements) + imports = [ + CGGeneric("use crate::dom::bindings::root::DomRoot;\n"), + CGGeneric("use crate::dom::bindings::str::DOMString;\n"), + ] + return CGList(imports + elements) def DomTypeHolder(descriptors, descriptorProvider, dictionaries, callbacks, typedefs, config): diff --git a/components/script_bindings/root.rs b/components/script_bindings/root.rs index c18c30cfae7..c573e163882 100644 --- a/components/script_bindings/root.rs +++ b/components/script_bindings/root.rs @@ -351,6 +351,14 @@ where } } +impl Eq for DomRoot {} + +impl Hash for DomRoot { + fn hash(&self, state: &mut H) { + self.value.hash(state); + } +} + impl Clone for DomRoot where T: DomObject, diff --git a/components/script_bindings/webidls/TestBindingMaplikeWithInterface.webidl b/components/script_bindings/webidls/TestBindingMaplikeWithInterface.webidl new file mode 100644 index 00000000000..1bdd45d7698 --- /dev/null +++ b/components/script_bindings/webidls/TestBindingMaplikeWithInterface.webidl @@ -0,0 +1,20 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// This interface is entirely internal to Servo, and should not be accessible to +// web pages. + +[Pref="dom_testbinding_enabled", Exposed=(Window,Worker)] +interface TestBindingMaplikeWithInterface { + [Throws] + constructor(); + + maplike; + undefined setInternal(DOMString aKey, TestBinding aValue); + undefined clearInternal(); + boolean deleteInternal(DOMString aKey); + boolean hasInternal(DOMString aKey); + [Throws] + TestBinding getInternal(DOMString aKey); +}; diff --git a/components/script_bindings/webidls/TestBindingMaplike.webidl b/components/script_bindings/webidls/TestBindingMaplikeWithPrimitive.webidl similarity index 93% rename from components/script_bindings/webidls/TestBindingMaplike.webidl rename to components/script_bindings/webidls/TestBindingMaplikeWithPrimitive.webidl index 8debb804a3e..f0e77fbcae4 100644 --- a/components/script_bindings/webidls/TestBindingMaplike.webidl +++ b/components/script_bindings/webidls/TestBindingMaplikeWithPrimitive.webidl @@ -6,7 +6,7 @@ // web pages. [Pref="dom_testbinding_enabled", Exposed=(Window,Worker)] -interface TestBindingMaplike { +interface TestBindingMaplikeWithPrimitive { [Throws] constructor(); diff --git a/components/script_bindings/webidls/TestBindingSetlikeWithInterface.webidl b/components/script_bindings/webidls/TestBindingSetlikeWithInterface.webidl new file mode 100644 index 00000000000..12254b62077 --- /dev/null +++ b/components/script_bindings/webidls/TestBindingSetlikeWithInterface.webidl @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// This interface is entirely internal to Servo, and should not be accessible to +// web pages. + +[Pref="dom_testbinding_enabled", Exposed=(Window,Worker)] +interface TestBindingSetlikeWithInterface { + [Throws] + constructor(); + + setlike; +}; diff --git a/components/script_bindings/webidls/TestBindingSetlike.webidl b/components/script_bindings/webidls/TestBindingSetlikeWithPrimitive.webidl similarity index 90% rename from components/script_bindings/webidls/TestBindingSetlike.webidl rename to components/script_bindings/webidls/TestBindingSetlikeWithPrimitive.webidl index 16b264bb9f6..7f675a62187 100644 --- a/components/script_bindings/webidls/TestBindingSetlike.webidl +++ b/components/script_bindings/webidls/TestBindingSetlikeWithPrimitive.webidl @@ -6,7 +6,7 @@ // web pages. [Pref="dom_testbinding_enabled", Exposed=(Window,Worker)] -interface TestBindingSetlike { +interface TestBindingSetlikeWithPrimitive { [Throws] constructor(); diff --git a/tests/wpt/mozilla/meta-legacy-layout/mozilla/like.any.js.ini b/tests/wpt/mozilla/meta-legacy-layout/mozilla/like.any.js.ini index a4040cad82c..a7f974265a7 100644 --- a/tests/wpt/mozilla/meta-legacy-layout/mozilla/like.any.js.ini +++ b/tests/wpt/mozilla/meta-legacy-layout/mozilla/like.any.js.ini @@ -1,23 +1,29 @@ [like.any.html] type: testharness prefs: [dom_testbinding_enabled:true] - [Test defaulting arguments on setlike to undefined] + [setlike - Default arguments for r/w methods is undefined] expected: FAIL - [Simple map creation and functionality test] + [maplike - 'get' with a bogus key should return undefined] expected: FAIL - [Test defaulting arguments on maplike to undefined] + [maplike - 'get' with a bogus key should return undefined] + expected: FAIL + + [maplike - Default arguments for r/w methods is undefined] expected: FAIL [like.any.worker.html] type: testharness prefs: [dom_testbinding_enabled:true] - [Test defaulting arguments on setlike to undefined] + [setlike - Default arguments for r/w methods is undefined] expected: FAIL - [Simple map creation and functionality test] + [maplike - 'get' with a bogus key should return undefined] expected: FAIL - [Test defaulting arguments on maplike to undefined] + [maplike - 'get' with a bogus key should return undefined] + expected: FAIL + + [maplike - Default arguments for r/w methods is undefined] expected: FAIL diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 7f63b5e2079..e23bcd9ea24 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -13538,7 +13538,7 @@ ] ], "like.any.js": [ - "11e4d86325ce6216754a0e0b760c59beb812db85", + "30d8007fb5cbef2847e36fe9b4cb9e6535aaf1e0", [ "mozilla/like.any.html", { diff --git a/tests/wpt/mozilla/meta/mozilla/like.any.js.ini b/tests/wpt/mozilla/meta/mozilla/like.any.js.ini index a4040cad82c..8cf36465a0e 100644 --- a/tests/wpt/mozilla/meta/mozilla/like.any.js.ini +++ b/tests/wpt/mozilla/meta/mozilla/like.any.js.ini @@ -1,23 +1,30 @@ [like.any.html] type: testharness prefs: [dom_testbinding_enabled:true] - [Test defaulting arguments on setlike to undefined] + [setlike - Default arguments for r/w methods is undefined] expected: FAIL - [Simple map creation and functionality test] + [maplike - 'get' with a bogus key should return undefined] expected: FAIL - [Test defaulting arguments on maplike to undefined] + [maplike - 'get' with a bogus key should return undefined] expected: FAIL + [maplike - Default arguments for r/w methods is undefined] + expected: FAIL + + [like.any.worker.html] type: testharness prefs: [dom_testbinding_enabled:true] - [Test defaulting arguments on setlike to undefined] + [setlike - Default arguments for r/w methods is undefined] expected: FAIL - [Simple map creation and functionality test] + [maplike - 'get' with a bogus key should return undefined] expected: FAIL - [Test defaulting arguments on maplike to undefined] + [maplike - 'get' with a bogus key should return undefined] + expected: FAIL + + [maplike - Default arguments for r/w methods is undefined] expected: FAIL diff --git a/tests/wpt/mozilla/tests/mozilla/like.any.js b/tests/wpt/mozilla/tests/mozilla/like.any.js index 11e4d86325c..30d8007fb5c 100644 --- a/tests/wpt/mozilla/tests/mozilla/like.any.js +++ b/tests/wpt/mozilla/tests/mozilla/like.any.js @@ -59,250 +59,308 @@ var testExistence = function testExistence(prefix, obj, properties) { } }; -test(function () { - var m = new TestBindingSetlike(); - assert_true(ok(m), "SimpleSet: got a TestBindingSetlike object"); - testExistence("SimpleSet: ", m, setlike_rw_properties); - assert_equals(m.size, 0, "SimpleSet: size should be zero"); - assert_true(!m.has("test"), "SimpleSet: maplike has should return false"); - m1 = m.add("test"); - assert_equals(m, m1, "SimpleSet: return from set should be map object"); - assert_equals(m.size, 1, "SimpleSet: size should be 1"); - assert_true(m.has("test"), "SimpleSet: maplike has should return true"); - m.add("test2"); - assert_equals(m.size, 2, "SimpleSet: size should be 2"); - testSet = ["test", "test2"]; - testIndex = 0; - m.forEach(function (v, k, o) { - "use strict"; - assert_equals(o, m, "SimpleSet: foreach obj is correct"); - assert_equals(k, testSet[testIndex], "SimpleSet: foreach set key: " + k + " = " + testSet[testIndex]); - testIndex += 1; - }); - assert_equals(testIndex, 2, "SimpleSet: foreach ran correct number of times"); - assert_true(m.has("test2"), "SimpleSet: maplike has should return true"); - assert_equals(m.delete("test2"), true, "SimpleSet: maplike deletion should return true"); - assert_equals(m.size, 1, "SimpleSet: size should be 1"); - iterable = false; - for (let e of m) { - iterable = true; - assert_equals(e, "test", "SimpleSet: iterable first array element should be key"); - } - assert_equals(m[Symbol.iterator].length, 0, "SimpleSet: @@iterator symbol is correct length"); - assert_equals(m[Symbol.iterator].name, "values", "SimpleSet: @@iterator symbol has correct name"); - assert_equals(m[Symbol.iterator], m.values, 'SimpleSet: @@iterator is an alias for "values"'); - assert_true(ok(iterable), "SimpleSet: @@iterator symbol resolved correctly"); - for (let k of m.keys()) { - assert_equals(k, "test", "SimpleSet: first keys element should be 'test'"); - } - for (let v of m.values()) { - assert_equals(v, "test", "SimpleSet: first values elements should be 'test'"); - } - for (let e of m.entries()) { - assert_equals(e[0], "test", "SimpleSet: Entries first array element should be 'test'"); - assert_equals(e[1], "test", "SimpleSet: Entries second array element should be 'test'"); - } - m.clear(); - assert_equals(m.size, 0, "SimpleSet: size should be 0 after clear"); -}, "Simple set creation and functionality"); +let setLikeTests = [ + { + setConstructor: TestBindingSetlikeWithPrimitive, + testValues: ["first", "second", "third", "fourth"], + memberType: "string" + }, + { + setConstructor: TestBindingSetlikeWithInterface, + testValues: [new TestBinding(), new TestBinding(), new TestBinding(), new TestBinding()], + memberType: "TestBinding" + }, +]; + +for (const { setConstructor, testValues, memberType } of setLikeTests) { + test(function () { + var s = new setConstructor(); + assert_true(ok(s), `got a ${setConstructor.name} object`); + testExistence(setConstructor.name, s, setlike_rw_properties); + assert_equals(s.size, 0, "size of new set should be zero"); + const testValue1 = testValues[0]; + assert_true(!s.has(testValue1), "has() should return false for bogus value"); + s1 = s.add(testValue1); + assert_equals(s, s1, "set.add() should be a chainable method"); + assert_equals(s.size, 1, "size should be 1"); + assert_true(s.has(testValue1), "has() should return true for the first test value"); + const testValue2 = testValues[1]; + s.add(testValue2); + assert_equals(s.size, 2, "size should be 2"); + testIndex = 0; + s.forEach(function (v, k, o) { + "use strict"; + assert_equals(o, s, "forEach obj is correct"); + assert_equals(k, testValues[testIndex], "forEach set key: " + k + " = " + testValues[testIndex]); + testIndex += 1; + }); + assert_equals(testIndex, 2, "forEach ran correct number of times"); + assert_true(s.has(testValue2), "maplike has should return true for second key"); + assert_equals(s.delete(testValue2), true, "maplike deletion should return true"); + assert_equals(s.size, 1, "size should be 1"); + iterable = false; + for (let e of s) { + iterable = true; + assert_equals(e, testValue1, "iterable first array element should be first test key"); + } + assert_equals(s[Symbol.iterator].length, 0, "@@iterator symbol is correct length"); + assert_equals(s[Symbol.iterator].name, "values", "@@iterator symbol has correct name"); + assert_equals(s[Symbol.iterator], s.values, '@@iterator is an alias for "values"'); + assert_true(ok(iterable), " @@iterator symbol resolved correctly"); + for (let k of s.keys()) { + assert_equals(k, testValue1, "first element of keys() should be the first test key"); + } + for (let v of s.values()) { + assert_equals(v, testValue1, "first element of values() should be the first test value"); + } + for (let e of s.entries()) { + assert_equals(e[0], testValue1, "first element of entries() should be the first test value"); + assert_equals(e[1], testValue1, "second element of entries() should be the second test value"); + } + s.clear(); + assert_equals(s.size, 0, "size should be 0 after clear"); + }, `setlike<${memberType}> - Basic set operations`); + + test(function () { + // Test this override for forEach + s = new setConstructor(); + s.add(testValues[0]); + s.forEach(function (v, k, o) { + "use strict"; + assert_equals(o, s, "forEach obj is correct"); + assert_equals(this, 5, "'this' value should be correct"); + }, 5); + }, `setke<${memberType}> - Test 'this' override for 'forEach'`); + + // some iterable test ported to *like interfaces + test(function () { + var s = new setConstructor(); + var empty = true; + s.forEach(function () { empty = false; }); + assert_true(empty); + }, `Empty setlike<${memberType}>`); + + test(function () { + var s = new setConstructor(); + function is_iterator(o) { + return o[Symbol.iterator]() === o; + } + assert_true(is_iterator(s.keys())); + assert_true(is_iterator(s.values())); + assert_true(is_iterator(s.entries())); + }, `setlike<${memberType}> are iterators`); + + test(function () { + var s = new setConstructor(); + s.add(testValues[0]); + s.add(testValues[1]); + s.add(testValues[2]); + assert_array_equals(collect(s.keys()), collect(s.values())); + assert_array_equals(collect(s.values()), testValues.slice(0, 3)); + var i = 0; + for (entry of s.entries()) { + assert_array_equals(entry, [testValues[i], testValues[i]]); + i += 1; + } + + s.add(testValues[3]); + assert_array_equals(collect(s.keys()), collect(s.values())); + assert_array_equals(collect(s.values()), testValues); + var i = 0; + for (entry of s.entries()) { + assert_array_equals(entry, [testValues[i], testValues[i]]); + i += 1; + } + }, `setlike<${memberType}> - Iterators iterate over values`); +} test(function () { - var m = new TestBindingSetlike(); + var m = new TestBindingSetlikeWithPrimitive(); m.add(); - assert_equals(m.size, 1, "SetArgsDefault: should have 1 entry"); + assert_equals(m.size, 1, "set should have 1 entry"); m.forEach(function (v, k) { "use strict"; - assert_equals(typeof k, "string", "SetArgsDefault: key is a string"); - assert_equals(k, "undefined", "SetArgsDefault: key is the string undefined"); + assert_equals(typeof k, "string", "key must be a string"); + assert_equals(k, "undefined", "key is the string undefined"); }); m.delete(); - assert_equals(m.size, 0, "SetArgsDefault: should have 0 entries"); -}, "Test defaulting arguments on setlike to undefined"); + assert_equals(m.size, 0, "after deleting key, set should have 0 entries"); +}, "setlike - Default arguments for r/w methods is undefined"); -test(function () { - // Simple map creation and functionality test - m = new TestBindingMaplike(); - assert_true(ok(m), "SimpleMap: got a TestBindingMaplike object"); - testExistence("SimpleMap: ", m, maplike_rw_properties); - assert_equals(m.size, 0, "SimpleMap: size should be zero"); - assert_true(!m.has("test"), "SimpleMap: maplike has should return false"); - assert_equals(m.get("test"), undefined, "SimpleMap: maplike get should return undefined on bogus lookup"); - var m1 = m.set("test", 1); - assert_equals(m, m1, "SimpleMap: return from set should be map object"); - assert_equals(m.size, 1, "SimpleMap: size should be 1"); - assert_true(m.has("test"), "SimpleMap: maplike has should return true"); - assert_equals(m.get("test"), 1, "SimpleMap: maplike get should return value entered"); - m.set("test2", 2); - assert_equals(m.size, 2, "SimpleMap: size should be 2"); - testSet = [["test", 1], ["test2", 2]]; - testIndex = 0; - m.forEach(function (v, k, o) { - "use strict"; - assert_equals(o, m, "SimpleMap: foreach obj is correct"); - assert_equals(k, testSet[testIndex][0], "SimpleMap: foreach map key: " + k + " = " + testSet[testIndex][0]); - assert_equals(v, testSet[testIndex][1], "SimpleMap: foreach map value: " + v + " = " + testSet[testIndex][1]); - testIndex += 1; - }); - assert_equals(testIndex, 2, "SimpleMap: foreach ran correct number of times"); - assert_true(m.has("test2"), "SimpleMap: maplike has should return true"); - assert_equals(m.get("test2"), 2, "SimpleMap: maplike get should return value entered"); - assert_equals(m.delete("test2"), true, "SimpleMap: maplike deletion should return boolean"); - assert_equals(m.size, 1, "SimpleMap: size should be 1"); - var iterable = false; - for (let e of m) { - iterable = true; - assert_equals(e[0], "test", "SimpleMap: iterable first array element should be key"); - assert_equals(e[1], 1, "SimpleMap: iterable second array element should be value"); - } - assert_equals(m[Symbol.iterator].length, 0, "SimpleMap: @@iterator symbol is correct length"); - assert_equals(m[Symbol.iterator].name, "entries", "SimpleMap: @@iterator symbol has correct name"); - assert_equals(m[Symbol.iterator], m.entries, 'SimpleMap: @@iterator is an alias for "entries"'); - assert_true(ok(iterable), "SimpleMap: @@iterator symbol resolved correctly"); - for (let k of m.keys()) { - assert_equals(k, "test", "SimpleMap: first keys element should be 'test'"); - } - for (let v of m.values()) { - assert_equals(v, 1, "SimpleMap: first values elements should be 1"); - } - for (let e of m.entries()) { - assert_equals(e[0], "test", "SimpleMap: entries first array element should be 'test'"); - assert_equals(e[1], 1, "SimpleMap: entries second array element should be 1"); - } - m.clear(); - assert_equals(m.size, 0, "SimpleMap: size should be 0 after clear"); -}, "Simple map creation and functionality test"); +let mapLikeTests = [ + { + mapConstructor: TestBindingMaplikeWithPrimitive, + testEntries: [["first", 1], ["second", 2], ["third", 3], ["fourth", 4]], + valueType: "number" + }, + { + mapConstructor: TestBindingMaplikeWithInterface, + testEntries: [ + ["first", new TestBinding()], + ["second", new TestBinding()], + ["third", new TestBinding()], + ["fourth", new TestBinding()], + ], + valueType: "TestBinding" + }, +]; -test(function () { - // Map convenience function test - m = new TestBindingMaplike(); - assert_true(ok(m), "MapConvenience: got a TestBindingMaplike object"); - assert_equals(m.size, 0, "MapConvenience: size should be zero"); - assert_true(!m.hasInternal("test"), "MapConvenience: maplike hasInternal should return false"); - // It's fine to let getInternal to return 0 if the key doesn't exist - // because this API can only be used internally in C++ and we'd throw - // an error if the key doesn't exist. - //SimpleTest.doesThrow(() => m.getInternal("test"), 0, "MapConvenience: maplike getInternal should throw if the key doesn't exist"); - m.setInternal("test", 1); - assert_equals(m.size, 1, "MapConvenience: size should be 1"); - assert_true(m.hasInternal("test"), "MapConvenience: maplike hasInternal should return true"); - assert_equals(m.get("test"), 1, "MapConvenience: maplike get should return value entered"); - assert_equals(m.getInternal("test"), 1, "MapConvenience: maplike getInternal should return value entered"); - m.setInternal("test2", 2); - assert_equals(m.size, 2, "size should be 2"); - assert_true(m.hasInternal("test2"), "MapConvenience: maplike hasInternal should return true"); - assert_equals(m.get("test2"), 2, "MapConvenience: maplike get should return value entered"); - assert_equals(m.getInternal("test2"), 2, "MapConvenience: maplike getInternal should return value entered"); - assert_equals(m.deleteInternal("test2"), true, "MapConvenience: maplike deleteInternal should return true"); - assert_equals(m.size, 1, "MapConvenience: size should be 1"); - m.clearInternal(); - assert_equals(m.size, 0, "MapConvenience: size should be 0 after clearInternal"); -}, "Map convenience function test"); +for (const { mapConstructor, testEntries, valueType } of mapLikeTests) { + test(function () { + m = new mapConstructor(); + assert_true(ok(m), `got a ${mapConstructor.name} object`); + assert_equals(m.get("test"), undefined, "get(bogusKey) is undefined"); + }, `maplike - 'get' with a bogus key should return undefined`); -// JS implemented map creation convenience function test -test(function () { - // Test this override for forEach - m = new TestBindingMaplike(); - m.set("test", 1); - m.forEach(function (v, k, o) { - "use strict"; - assert_equals(o, m, "ForEachThisOverride: foreach obj is correct"); - assert_equals(this, 5, "ForEachThisOverride: 'this' value should be correct"); - }, 5); -}, "Test this override for forEach"); + test(function () { + // Simple map creation and functionality test + m = new mapConstructor(); + assert_true(ok(m), `got a ${mapConstructor.name} object`); + testExistence(mapConstructor.name, m, maplike_rw_properties); + assert_equals(m.size, 0, "size of new map should be zero"); + let [testKey1, testValue1] = testEntries[0]; + assert_true(!m.has(testKey1), "maplike has should return false for bogus key"); + var m1 = m.set(testKey1, testValue1); + assert_equals(m, m1, "map.set should be a chainable method"); + assert_equals(m.size, 1, "size should be 1"); + assert_true(m.has(testKey1), "has() should return true for key already added"); + assert_equals(m.get(testKey1), testValue1, "get(testKey1) should return the same value provided to set()"); + let [testKey2, testValue2] = testEntries[1]; + m.set(testKey2, testValue2); + assert_equals(m.size, 2, "size should be 2"); + testSet = testEntries.slice(0, 2); + testIndex = 0; + m.forEach(function (v, k, o) { + "use strict"; + assert_equals(o, m, "forEach obj is correct"); + assert_equals(k, testSet[testIndex][0], "forEach map key: " + k + " = " + testSet[testIndex][0]); + assert_equals(v, testSet[testIndex][1], "forEach map value: " + v + " = " + testSet[testIndex][1]); + testIndex += 1; + }); + assert_equals(testIndex, 2, "forEach ran correct number of times"); + assert_true(m.has(testKey2), "has() should return true for second test key"); + assert_equals(m.get(testKey2), testValue2, "get(testKey2) should return the same value provided to set()"); + assert_equals(m.delete(testKey2), true, "maplike deletion should return boolean"); + assert_equals(m.size, 1, "size should be 1"); + var iterable = false; + for (let e of m) { + iterable = true; + assert_equals(e[0], testKey1, "iterable's first array element should be the first test key"); + assert_equals(e[1], testValue1, "iterable' second array element should be the first test value"); + } + assert_equals(m[Symbol.iterator].length, 0, "@@iterator symbol is correct length"); + assert_equals(m[Symbol.iterator].name, "entries", "@@iterator symbol has correct name"); + assert_equals(m[Symbol.iterator], m.entries, '@@iterator is an alias for "entries"'); + assert_true(ok(iterable), "@@iterator symbol resolved correctly"); + for (let k of m.keys()) { + assert_equals(k, testKey1, "first element of keys() should be the first test key"); + } + for (let v of m.values()) { + assert_equals(v, testValue1, "first element of values() should be 1"); + } + for (let e of m.entries()) { + assert_equals(e[0], testKey1, "first element of entries() should have the first test key"); + assert_equals(e[1], testValue1, "first element of entries() should have the second test value"); + } + m.clear(); + assert_equals(m.size, 0, "size should be 0 after clear"); + }, `maplike - Simple map creation and functionality test`); + + test(function () { + // Map convenience function test + m = new mapConstructor(); + assert_true(ok(m), `got a ${mapConstructor.name} object`); + assert_equals(m.size, 0, "size should be zero"); + assert_true(!m.hasInternal("test"), "hasInternal() should return false for bogus key"); + // It's fine to let getInternal to return 0 if the key doesn't exist + // because this API can only be used internally in C++ and we'd throw + // an error if the key doesn't exist. + //SimpleTest.doesThrow(() => m.getInternal("test"), 0, "MapConvenience: maplike getInternal should throw if the key doesn't exist"); + let [testKey1, testValue1] = testEntries[0]; + m.setInternal(testKey1, testValue1); + assert_equals(m.size, 1, "size should be 1 after adding first test key/value"); + assert_true(m.hasInternal(testKey1), "hasInternal() should return true"); + assert_equals(m.get(testKey1), testValue1, "get() should return the value set using setInternal()"); + assert_equals(m.getInternal(testKey1), testValue1, "getInternal() should return the value set using setInternal()"); + let [testKey2, testValue2] = testEntries[1]; + m.setInternal(testKey2, testValue2); + assert_equals(m.size, 2, "size should be 2"); + assert_true(m.hasInternal(testKey2), "hasInternal() should return true for newly added second test key"); + assert_equals(m.get(testKey2), testValue2, "get(testKey2) should return the value set using setInternal"); + assert_equals(m.getInternal(testKey2), testValue2, "getInternal(testKey2) should return the value set using setInternal()"); + assert_equals(m.deleteInternal(testKey2), true, "deleteInternal should return true when deleting existing key"); + assert_equals(m.size, 1, "size should be 1"); + m.clearInternal(); + assert_equals(m.size, 0, "size should be 0 after clearInternal"); + }, `Convenience methods for maplike`); + + // JS implemented map creation convenience function test + test(function () { + // Test this override for forEach + m = new mapConstructor(); + m.set(testEntries[0][0], testEntries[1][1]); + m.forEach(function (v, k, o) { + "use strict"; + assert_equals(o, m, "forEach obj is correct"); + assert_equals(this, 5, "'this' value should be correct"); + }, 5); + }, `maplike - Test 'this' override for 'forEach'`); + + // some iterable test ported to *like interfaces + test(function () { + var t = new mapConstructor(); + var empty = true; + t.forEach(function () { empty = false; }); + assert_true(empty); + }, `maplike - Empty maplike`); + + test(function () { + var t = new mapConstructor(); + function is_iterator(o) { + return o[Symbol.iterator]() === o; + } + assert_true(is_iterator(t.keys())); + assert_true(is_iterator(t.values())); + assert_true(is_iterator(t.entries())); + }, `maplike - Maplike are iterators`); + + test(function () { + var t = new mapConstructor(); + t.set(testEntries[0][0], testEntries[0][1]); + t.set(testEntries[1][0], testEntries[1][1]); + t.set(testEntries[2][0], testEntries[2][1]); + assert_array_equals(collect(t.keys()), [testEntries[0][0], testEntries[1][0], testEntries[2][0]]); + assert_array_equals(collect(t.values()), [testEntries[0][1], testEntries[1][1], testEntries[2][1]]); + var expected = testEntries.slice(0, 3); + var i = 0; + for (entry of t.entries()) { + assert_array_equals(entry, expected[i++]); + } + + t.set(testEntries[3][0], testEntries[3][1]); + assert_array_equals(collect(t.keys()), testEntries.map(entry => entry[0])); + assert_array_equals(collect(t.values()), testEntries.map(entry => entry[1])); + var expected = testEntries.slice(); + var i = 0; + for (entry of t.entries()) { + assert_array_equals(entry, expected[i++]); + } + }, `maplike - Maplike iteratable over key/value pairs`); +} test(function () { // Test defaulting arguments on maplike to undefined - m = new TestBindingMaplike(); + m = new TestBindingMaplikeWithPrimitive(); m.set(); - assert_equals(m.size, 1, "MapArgsDefault: should have 1 entry"); + assert_equals(m.size, 1, "should have 1 entry"); m.forEach(function (v, k) { "use strict"; - assert_equals(typeof k, "string", "MapArgsDefault: key is a string"); - assert_equals(k, "undefined", "MapArgsDefault: key is the string undefined"); - assert_equals(v, 0, "MapArgsDefault: value is 0"); + assert_equals(typeof k, "string", "defaulted key must be a string"); + assert_equals(k, "undefined", "defaulted key must be the string undefined"); + assert_equals(v, 0, "defaulted value must be 0"); }); - assert_equals(m.get(), 0, "MapArgsDefault: no argument to get() returns correct value"); + assert_equals(m.get(), 0, "no argument to get() returns correct value"); m.delete(); assert_equals(m.size, 0, "MapArgsDefault: should have 0 entries"); -}, "Test defaulting arguments on maplike to undefined"); +}, "maplike - Default arguments for r/w methods is undefined"); -// some iterable test ported to *like interfaces -test(function () { - var t = new TestBindingSetlike(); - var empty = true; - t.forEach(function () { empty = false; }); - assert_true(empty); -}, "Empty setlike"); - -test(function () { - var t = new TestBindingSetlike(); - function is_iterator(o) { - return o[Symbol.iterator]() === o; - } - assert_true(is_iterator(t.keys())); - assert_true(is_iterator(t.values())); - assert_true(is_iterator(t.entries())); -}, "Setlike are iterators"); - -test(function () { - var t = new TestBindingSetlike(); - t.add("first"); - t.add("second"); - t.add("third"); - assert_array_equals(collect(t.keys()), collect(t.values())); - assert_array_equals(collect(t.values()), ["first", "second", "third"]); - var expected = [["first", "first"], ["second", "second"], ["third", "third"]]; - var i = 0; - for (entry of t.entries()) { - assert_array_equals(entry, expected[i++]); - } - - t.add("fourth"); - assert_array_equals(collect(t.keys()), collect(t.values())); - assert_array_equals(collect(t.values()), ["first", "second", "third", "fourth"]); - var expected = [["first", "first"], ["second", "second"], ["third", "third"], ["fourth", "fourth"]]; - var i = 0; - for (entry of t.entries()) { - assert_array_equals(entry, expected[i++]); - } -}, "Iterators iterate over values"); - -test(function () { - var t = new TestBindingMaplike(); - var empty = true; - t.forEach(function () { empty = false; }); - assert_true(empty); -}, "Empty maplike"); - -test(function () { - var t = new TestBindingMaplike(); - function is_iterator(o) { - return o[Symbol.iterator]() === o; - } - assert_true(is_iterator(t.keys())); - assert_true(is_iterator(t.values())); - assert_true(is_iterator(t.entries())); -}, "Maplike are iterators"); - -test(function () { - var t = new TestBindingMaplike(); - t.set("first", 0); - t.set("second", 1); - t.set("third", 2); - assert_array_equals(collect(t.keys()), ["first", "second", "third"]); - assert_array_equals(collect(t.values()), [0, 1, 2]); - var expected = [["first", 0], ["second", 1], ["third", 2]]; - var i = 0; - for (entry of t.entries()) { - assert_array_equals(entry, expected[i++]); - } - - t.set("fourth", 3); - assert_array_equals(collect(t.keys()), ["first", "second", "third", "fourth"]); - assert_array_equals(collect(t.values()), [0, 1, 2, 3]); - var expected = [["first", 0], ["second", 1], ["third", 2], ["fourth", 3]]; - var i = 0; - for (entry of t.entries()) { - assert_array_equals(entry, expected[i++]); - } -}, "Maplike iterate over key/value pairs");