diff options
author | Idan Horowitz <idan.horowitz@gmail.com> | 2021-06-12 23:57:01 +0300 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-06-13 00:33:18 +0100 |
commit | 6c0d5163a1bdbfab120525f075830a023500d8dc (patch) | |
tree | dc6c6f4dc17d98bade7fb6786019a8c607a188b6 | |
parent | a96ac8bd56f5a06552ed2a6fe7b21388d5454250 (diff) | |
download | serenity-6c0d5163a1bdbfab120525f075830a023500d8dc.zip |
LibJS: Add most of the Map.prototype methods
Specifically all aside from "keys", "values" and "entries" which
require an implementation of the MapIterator object.
8 files changed, 212 insertions, 0 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/MapPrototype.cpp b/Userland/Libraries/LibJS/Runtime/MapPrototype.cpp index dc1b8da235..b5fb37757f 100644 --- a/Userland/Libraries/LibJS/Runtime/MapPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/MapPrototype.cpp @@ -18,6 +18,14 @@ void MapPrototype::initialize(GlobalObject& global_object) { auto& vm = this->vm(); Object::initialize(global_object); + u8 attr = Attribute::Writable | Attribute::Configurable; + + define_native_function(vm.names.clear, clear, 0, attr); + define_native_function(vm.names.delete_, delete_, 1, attr); + define_native_function(vm.names.forEach, for_each, 1, attr); + define_native_function(vm.names.get, get, 1, attr); + define_native_function(vm.names.has, has, 1, attr); + define_native_function(vm.names.set, set, 2, attr); define_native_accessor(vm.names.size, size_getter, {}, Attribute::Configurable); @@ -40,6 +48,79 @@ Map* MapPrototype::typed_this(VM& vm, GlobalObject& global_object) return static_cast<Map*>(this_object); } +// 24.1.3.1 Map.prototype.clear ( ), https://tc39.es/ecma262/#sec-map.prototype.clear +JS_DEFINE_NATIVE_FUNCTION(MapPrototype::clear) +{ + auto* map = typed_this(vm, global_object); + if (!map) + return {}; + map->entries().clear(); + return js_undefined(); +} + +// 24.1.3.3 Map.prototype.delete ( key ), https://tc39.es/ecma262/#sec-map.prototype.delete +JS_DEFINE_NATIVE_FUNCTION(MapPrototype::delete_) +{ + auto* map = typed_this(vm, global_object); + if (!map) + return {}; + return Value(map->entries().remove(vm.argument(0))); +} + +// 24.1.3.5 Map.prototype.forEach ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-map.prototype.foreach +JS_DEFINE_NATIVE_FUNCTION(MapPrototype::for_each) +{ + auto* map = typed_this(vm, global_object); + if (!map) + return {}; + if (!vm.argument(0).is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, vm.argument(0).to_string_without_side_effects()); + return {}; + } + auto this_value = vm.this_value(global_object); + for (auto& entry : map->entries()) { + (void)vm.call(vm.argument(0).as_function(), vm.argument(1), entry.value, entry.key, this_value); + if (vm.exception()) + return {}; + } + return js_undefined(); +} + +// 24.1.3.6 Map.prototype.get ( key ), https://tc39.es/ecma262/#sec-map.prototype.get +JS_DEFINE_NATIVE_FUNCTION(MapPrototype::get) +{ + auto* map = typed_this(vm, global_object); + if (!map) + return {}; + auto result = map->entries().get(vm.argument(0)); + if (!result.has_value()) + return js_undefined(); + return result.value(); +} + +// 24.1.3.7 Map.prototype.has ( key ), https://tc39.es/ecma262/#sec-map.prototype.has +JS_DEFINE_NATIVE_FUNCTION(MapPrototype::has) +{ + auto* map = typed_this(vm, global_object); + if (!map) + return {}; + auto& entries = map->entries(); + return Value(entries.find(vm.argument(0)) != entries.end()); +} + +// 24.1.3.9 Map.prototype.set ( key, value ), https://tc39.es/ecma262/#sec-map.prototype.set +JS_DEFINE_NATIVE_FUNCTION(MapPrototype::set) +{ + auto* map = typed_this(vm, global_object); + if (!map) + return {}; + auto key = vm.argument(0); + if (key.is_negative_zero()) + key = Value(0); + map->entries().set(key, vm.argument(1)); + return map; +} + // 24.1.3.10 get Map.prototype.size, https://tc39.es/ecma262/#sec-get-map.prototype.size JS_DEFINE_NATIVE_GETTER(MapPrototype::size_getter) { diff --git a/Userland/Libraries/LibJS/Runtime/MapPrototype.h b/Userland/Libraries/LibJS/Runtime/MapPrototype.h index f11a2bc6c5..ae7b95ba96 100644 --- a/Userland/Libraries/LibJS/Runtime/MapPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/MapPrototype.h @@ -21,6 +21,13 @@ public: private: static Map* typed_this(VM&, GlobalObject&); + JS_DECLARE_NATIVE_FUNCTION(clear); + JS_DECLARE_NATIVE_FUNCTION(delete_); + JS_DECLARE_NATIVE_FUNCTION(for_each); + JS_DECLARE_NATIVE_FUNCTION(get); + JS_DECLARE_NATIVE_FUNCTION(has); + JS_DECLARE_NATIVE_FUNCTION(set); + JS_DECLARE_NATIVE_GETTER(size_getter); }; diff --git a/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.clear.js b/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.clear.js new file mode 100644 index 0000000000..0120d2f4ca --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.clear.js @@ -0,0 +1,12 @@ +test("basic functionality", () => { + expect(Map.prototype.clear).toHaveLength(0); + + const map = new Map([ + ["a", 0], + ["b", 1], + ["c", 2], + ]); + expect(map).toHaveSize(3); + map.clear(); + expect(map).toHaveSize(0); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.delete.js b/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.delete.js new file mode 100644 index 0000000000..c7d6bd9192 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.delete.js @@ -0,0 +1,14 @@ +test("basic functionality", () => { + expect(Map.prototype.delete).toHaveLength(1); + + const map = new Map([ + ["a", 0], + ["b", 1], + ["c", 2], + ]); + expect(map).toHaveSize(3); + expect(map.delete("b")).toBeTrue(); + expect(map).toHaveSize(2); + expect(map.delete("b")).toBeFalse(); + expect(map).toHaveSize(2); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.forEach.js b/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.forEach.js new file mode 100644 index 0000000000..abfb4ce3e2 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.forEach.js @@ -0,0 +1,56 @@ +test("length is 1", () => { + expect(Map.prototype.forEach).toHaveLength(1); +}); + +describe("errors", () => { + test("requires at least one argument", () => { + expect(() => { + new Map().forEach(); + }).toThrowWithMessage(TypeError, "undefined is not a function"); + }); + + test("callback must be a function", () => { + expect(() => { + new Map().forEach(undefined); + }).toThrowWithMessage(TypeError, "undefined is not a function"); + }); +}); + +describe("normal behavior", () => { + test("never calls callback with empty set", () => { + var callbackCalled = 0; + expect( + new Map().forEach(() => { + callbackCalled++; + }) + ).toBeUndefined(); + expect(callbackCalled).toBe(0); + }); + + test("calls callback once for every item", () => { + var callbackCalled = 0; + expect( + new Map([ + ["a", 0], + ["b", 1], + ["c", 2], + ]).forEach(() => { + callbackCalled++; + }) + ).toBeUndefined(); + expect(callbackCalled).toBe(3); + }); + + test("callback receives value, key and map", () => { + var a = new Map([ + ["a", 0], + ["b", 1], + ["c", 2], + ]); + a.forEach((value, key, map) => { + expect(a.has(key)).toBeTrue(); + expect(a.get(key)).toBe(value); + expect(map).toBe(a); + }); + }); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.get.js b/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.get.js new file mode 100644 index 0000000000..1b99f81cb6 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.get.js @@ -0,0 +1,11 @@ +test("basic functionality", () => { + expect(Map.prototype.get).toHaveLength(1); + + const map = new Map([ + ["a", 0], + ["b", 1], + ["c", 2], + ]); + expect(map.get("a")).toBe(0); + expect(map.get("d")).toBe(undefined); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.has.js b/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.has.js new file mode 100644 index 0000000000..744be27975 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.has.js @@ -0,0 +1,17 @@ +test("length is 1", () => { + expect(Map.prototype.has).toHaveLength(1); +}); + +test("basic functionality", () => { + var map = new Map([ + ["a", 0], + [1, "b"], + ["c", 2], + ]); + + expect(new Map().has()).toBeFalse(); + expect(new Map([{}]).has()).toBeTrue(); + expect(map.has("a")).toBeTrue(); + expect(map.has(1)).toBeTrue(); + expect(map.has("serenity")).toBeFalse(); +}); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.set.js b/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.set.js new file mode 100644 index 0000000000..c4c9a4ab07 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Map/Map.prototype.set.js @@ -0,0 +1,14 @@ +test("basic functionality", () => { + expect(Map.prototype.set).toHaveLength(2); + + const map = new Map([ + ["a", 0], + ["b", 1], + ["c", 2], + ]); + expect(map).toHaveSize(3); + expect(map.set("d", 3)).toBe(map); + expect(map).toHaveSize(4); + expect(map.set("a", -1)).toBe(map); + expect(map).toHaveSize(4); +}); |