diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp index af608a00e5..d547b64fc4 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp @@ -62,6 +62,7 @@ void ArrayPrototype::initialize(GlobalObject& global_object) define_native_function(vm.names.values, values, 0, attr); define_native_function(vm.names.flat, flat, 0, attr); define_native_function(vm.names.at, at, 1, attr); + define_native_function(vm.names.keys, keys, 0, attr); // Use define_property here instead of define_native_function so that // Object.is(Array.prototype[Symbol.iterator], Array.prototype.values) @@ -1033,6 +1034,16 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::values) return ArrayIterator::create(global_object, this_object, Object::PropertyKind::Value); } +// 23.1.3.16 Array.prototype.keys ( ), https://tc39.es/ecma262/#sec-array.prototype.keys +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::keys) +{ + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) + return {}; + + return ArrayIterator::create(global_object, this_object, Object::PropertyKind::Key); +} + static void recursive_array_flat(VM& vm, GlobalObject& global_object, Array& new_array, Object& array, double depth) { auto array_length = length_of_array_like(global_object, array); diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h index f46637c636..8bcceac0fc 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h @@ -48,6 +48,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(values); JS_DECLARE_NATIVE_FUNCTION(flat); JS_DECLARE_NATIVE_FUNCTION(at); + JS_DECLARE_NATIVE_FUNCTION(keys); }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.keys.js b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.keys.js new file mode 100644 index 0000000000..ae66ce10c4 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.keys.js @@ -0,0 +1,44 @@ +test("length", () => { + expect(Array.prototype.keys.length).toBe(0); +}); + +test("basic functionality", () => { + const a = ["a", "b", "c"]; + const it = a.keys(); + expect(it.next()).toEqual({ value: 0, done: false }); + expect(it.next()).toEqual({ value: 1, done: false }); + expect(it.next()).toEqual({ value: 2, done: false }); + expect(it.next()).toEqual({ value: undefined, done: true }); + expect(it.next()).toEqual({ value: undefined, done: true }); + expect(it.next()).toEqual({ value: undefined, done: true }); +}); + +test("works when applied to non-object", () => { + [true, false, 9, 2n, Symbol()].forEach(primitive => { + const it = [].keys.call(primitive); + expect(it.next()).toEqual({ value: undefined, done: true }); + expect(it.next()).toEqual({ value: undefined, done: true }); + expect(it.next()).toEqual({ value: undefined, done: true }); + }); +}); + +test("item added to array before exhaustion is accessible", () => { + const a = ["a", "b"]; + const it = a.keys(); + expect(it.next()).toEqual({ value: 0, done: false }); + expect(it.next()).toEqual({ value: 1, done: false }); + a.push("c"); + expect(it.next()).toEqual({ value: 2, done: false }); + expect(it.next()).toEqual({ value: undefined, done: true }); + expect(it.next()).toEqual({ value: undefined, done: true }); +}); + +test("item added to array after exhaustion is inaccessible", () => { + const a = ["a", "b"]; + const it = a.keys(); + expect(it.next()).toEqual({ value: 0, done: false }); + expect(it.next()).toEqual({ value: 1, done: false }); + expect(it.next()).toEqual({ value: undefined, done: true }); + a.push("c"); + expect(it.next()).toEqual({ value: undefined, done: true }); +});