From b1c51368830bfd8f73ef4dfcf539aae9b5c3118a Mon Sep 17 00:00:00 2001 From: Adrian Niculescu <15037449+adrian-niculescu@users.noreply.github.com> Date: Tue, 9 Dec 2025 18:44:02 +0200 Subject: [PATCH] Fix URLSearchParams.forEach() crash and spec compliance - Use v8::Undefined(isolate) instead of empty v8::Local() - Changed argument order from (key, value) to (value, key, searchParams) - Use get_entries() iterator instead of get_keys() + get() for duplicate keys - Support the optional thisArg second parameter --- NativeScript/runtime/URLSearchParamsImpl.cpp | 682 ++++++++++--------- TestRunner/app/tests/URLSearchParams.js | 55 +- 2 files changed, 396 insertions(+), 341 deletions(-) diff --git a/NativeScript/runtime/URLSearchParamsImpl.cpp b/NativeScript/runtime/URLSearchParamsImpl.cpp index d0ab2a63..fd39addd 100644 --- a/NativeScript/runtime/URLSearchParamsImpl.cpp +++ b/NativeScript/runtime/URLSearchParamsImpl.cpp @@ -3,358 +3,360 @@ // #include "URLSearchParamsImpl.h" + #include "Helpers.h" #include "ModuleBinding.hpp" using namespace ada; namespace tns { - URLSearchParamsImpl::URLSearchParamsImpl(ada::url_search_params params) : params_(params) {} +URLSearchParamsImpl::URLSearchParamsImpl(ada::url_search_params params) + : params_(params) {} - void URLSearchParamsImpl::Init(v8::Isolate* isolate, v8::Local globalTemplate) { - auto URLSearchParamsTemplate = URLSearchParamsImpl::GetCtor(isolate); +void URLSearchParamsImpl::Init(v8::Isolate* isolate, + v8::Local globalTemplate) { + auto URLSearchParamsTemplate = URLSearchParamsImpl::GetCtor(isolate); - v8::Local urlSearchParamsPropertyName = ToV8String(isolate, "URLSearchParams"); - globalTemplate->Set(urlSearchParamsPropertyName, URLSearchParamsTemplate); + v8::Local urlSearchParamsPropertyName = + ToV8String(isolate, "URLSearchParams"); + globalTemplate->Set(urlSearchParamsPropertyName, URLSearchParamsTemplate); +} + +URLSearchParamsImpl* URLSearchParamsImpl::GetPointer( + v8::Local object) { + auto ptr = object->GetAlignedPointerFromInternalField(0); + if (ptr == nullptr) { + return nullptr; + } + return static_cast(ptr); +} + +v8::Local URLSearchParamsImpl::GetCtor( + v8::Isolate* isolate) { + v8::Local ctorTmpl = + v8::FunctionTemplate::New(isolate, Ctor); + ctorTmpl->SetClassName(ToV8String(isolate, "URLSearchParams")); + + auto tmpl = ctorTmpl->InstanceTemplate(); + tmpl->SetInternalFieldCount(1); + tmpl->Set(ToV8String(isolate, "append"), + v8::FunctionTemplate::New(isolate, Append)); + tmpl->Set(ToV8String(isolate, "delete"), + v8::FunctionTemplate::New(isolate, Delete)); + + tmpl->Set(ToV8String(isolate, "entries"), + v8::FunctionTemplate::New(isolate, Entries)); + + tmpl->Set(ToV8String(isolate, "forEach"), + v8::FunctionTemplate::New(isolate, ForEach)); + + tmpl->Set(ToV8String(isolate, "get"), + v8::FunctionTemplate::New(isolate, Get)); + + tmpl->Set(ToV8String(isolate, "getAll"), + v8::FunctionTemplate::New(isolate, GetAll)); + + tmpl->Set(ToV8String(isolate, "has"), + v8::FunctionTemplate::New(isolate, Has)); + + tmpl->Set(ToV8String(isolate, "keys"), + v8::FunctionTemplate::New(isolate, Keys)); + + tmpl->Set(ToV8String(isolate, "set"), + v8::FunctionTemplate::New(isolate, Set)); + + tmpl->SetAccessor(ToV8String(isolate, "size"), GetSize); + + tmpl->Set(ToV8String(isolate, "sort"), + v8::FunctionTemplate::New(isolate, Sort)); + + tmpl->Set(ToV8String(isolate, "toString"), + v8::FunctionTemplate::New(isolate, &ToString)); + + tmpl->Set(ToV8String(isolate, "values"), + v8::FunctionTemplate::New(isolate, &Values)); + + return ctorTmpl; +} + +void URLSearchParamsImpl::Ctor( + const v8::FunctionCallbackInfo& args) { + auto value = args[0]; + auto isolate = args.GetIsolate(); + auto context = isolate->GetCurrentContext(); + + auto ret = args.This(); + + ada::url_search_params params; + if (value->IsString()) { + params = + ada::url_search_params(tns::ToString(isolate, value.As())); + } else if (value->IsObject()) { + params = ada::url_search_params( + tns::ToString(isolate, value->ToString(context).ToLocalChecked())); + } + + auto searchParams = new URLSearchParamsImpl(params); + + ret->SetAlignedPointerInInternalField(0, searchParams); + + searchParams->BindFinalizer(isolate, ret); + + args.GetReturnValue().Set(ret); +} + +void URLSearchParamsImpl::Append( + const v8::FunctionCallbackInfo& args) { + URLSearchParamsImpl* ptr = GetPointer(args.This()); + if (ptr == nullptr) { + return; + } + auto isolate = args.GetIsolate(); + auto key = tns::ToString(isolate, args[0].As()); + auto value = tns::ToString(isolate, args[1].As()); + ptr->GetURLSearchParams()->append(key.c_str(), value.c_str()); +} + +void URLSearchParamsImpl::Delete( + const v8::FunctionCallbackInfo& args) { + URLSearchParamsImpl* ptr = GetPointer(args.This()); + if (ptr == nullptr) { + return; + } + auto isolate = args.GetIsolate(); + auto key = tns::ToString(isolate, args[0].As()); + ptr->GetURLSearchParams()->remove(key.c_str()); +} + +void URLSearchParamsImpl::Entries( + const v8::FunctionCallbackInfo& args) { + URLSearchParamsImpl* ptr = GetPointer(args.This()); + auto isolate = args.GetIsolate(); + auto context = isolate->GetCurrentContext(); + if (ptr == nullptr) { + args.GetReturnValue().Set(v8::Array::New(isolate)); + return; + } + + auto keys = ptr->GetURLSearchParams()->get_keys(); + auto len = ptr->GetURLSearchParams()->size(); + auto ret = v8::Array::New(isolate, (int)len); + int i = 0; + while (keys.has_next()) { + auto key = keys.next(); + if (key) { + auto keyValue = key.value(); + auto value = ptr->GetURLSearchParams()->get(keyValue).value(); + v8::Local values[] = { + ToV8String(isolate, keyValue.data()), + ToV8String(isolate, value.data()), + }; + auto success = ret->Set(context, i++, v8::Array::New(isolate, values, 2)) + .FromMaybe(false); + tns::Assert(success, isolate); } - - URLSearchParamsImpl *URLSearchParamsImpl::GetPointer(v8::Local object) { - auto ptr = object->GetAlignedPointerFromInternalField(0); - if (ptr == nullptr) { - return nullptr; - } - return static_cast(ptr); + } + args.GetReturnValue().Set(ret); +} + +void URLSearchParamsImpl::ForEach( + const v8::FunctionCallbackInfo& args) { + URLSearchParamsImpl* ptr = GetPointer(args.This()); + auto isolate = args.GetIsolate(); + auto context = isolate->GetCurrentContext(); + if (ptr == nullptr) { + return; + } + auto callback = args[0].As(); + auto searchParams = args.This(); + // Use thisArg if provided, otherwise undefined + auto thisArg = + args.Length() > 1 ? args[1] : v8::Undefined(isolate).As(); + // Use get_entries() to correctly handle duplicate keys + auto entries = ptr->GetURLSearchParams()->get_entries(); + while (entries.has_next()) { + auto entry = entries.next(); + if (entry) { + auto& [key, value] = entry.value(); + // Per spec, forEach callback receives (value, key, searchParams) + v8::Local callbackArgs[] = { + ToV8String(isolate, value.data()), + ToV8String(isolate, key.data()), + searchParams, + }; + v8::Local result; + if (!callback->Call(context, thisArg, 3, callbackArgs).ToLocal(&result)) { + // If the callback throws an exception, stop iteration + return; + } } - - v8::Local URLSearchParamsImpl::GetCtor(v8::Isolate *isolate) { - v8::Local ctorTmpl = v8::FunctionTemplate::New(isolate, Ctor); - ctorTmpl->SetClassName(ToV8String(isolate, "URLSearchParams")); - - auto tmpl = ctorTmpl->InstanceTemplate(); - tmpl->SetInternalFieldCount(1); - tmpl->Set( - ToV8String(isolate, "append"), - v8::FunctionTemplate::New(isolate, Append)); - tmpl->Set( - ToV8String(isolate, "delete"), - v8::FunctionTemplate::New(isolate, Delete)); - - tmpl->Set( - ToV8String(isolate, "entries"), - v8::FunctionTemplate::New(isolate, Entries)); - - tmpl->Set( - ToV8String(isolate, "forEach"), - v8::FunctionTemplate::New(isolate, ForEach)); - - tmpl->Set( - ToV8String(isolate, "get"), - v8::FunctionTemplate::New(isolate, Get)); - - tmpl->Set( - ToV8String(isolate, "getAll"), - v8::FunctionTemplate::New(isolate, GetAll)); - - tmpl->Set( - ToV8String(isolate, "has"), - v8::FunctionTemplate::New(isolate, Has)); - - tmpl->Set( - ToV8String(isolate, "keys"), - v8::FunctionTemplate::New(isolate, Keys)); - - tmpl->Set( - ToV8String(isolate, "set"), - v8::FunctionTemplate::New(isolate, Set)); - - tmpl->SetAccessor( - ToV8String(isolate, "size"), - GetSize - ); - - - tmpl->Set( - ToV8String(isolate, "sort"), - v8::FunctionTemplate::New(isolate, Sort)); - - tmpl->Set(ToV8String(isolate, "toString"), - v8::FunctionTemplate::New(isolate, &ToString)); - - - tmpl->Set(ToV8String(isolate, "values"), - v8::FunctionTemplate::New(isolate, &Values)); - - - return ctorTmpl; - } - - void URLSearchParamsImpl::Ctor(const v8::FunctionCallbackInfo &args) { - auto value = args[0]; - auto isolate = args.GetIsolate(); - auto context = isolate->GetCurrentContext(); - - auto ret = args.This(); - - ada::url_search_params params; - if (value->IsString()) { - params = ada::url_search_params(tns::ToString(isolate, value.As())); - } else if (value->IsObject()) { - params = ada::url_search_params( - tns::ToString(isolate, value->ToString(context).ToLocalChecked())); - } - - - auto searchParams = new URLSearchParamsImpl(params); - - ret->SetAlignedPointerInInternalField(0, searchParams); - - searchParams->BindFinalizer(isolate, ret); - - args.GetReturnValue().Set(ret); - - } - - void URLSearchParamsImpl::Append(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - if (ptr == nullptr) { - return; - } - auto isolate = args.GetIsolate(); - auto key = tns::ToString(isolate, args[0].As()); - auto value = tns::ToString(isolate, args[1].As()); - ptr->GetURLSearchParams()->append(key.c_str(), value.c_str()); - } - - void URLSearchParamsImpl::Delete(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - if (ptr == nullptr) { - return; - } - auto isolate = args.GetIsolate(); - auto key = tns::ToString(isolate, args[0].As()); - ptr->GetURLSearchParams()->remove(key.c_str()); + } +} + +void URLSearchParamsImpl::Get(const v8::FunctionCallbackInfo& args) { + URLSearchParamsImpl* ptr = GetPointer(args.This()); + auto isolate = args.GetIsolate(); + if (ptr == nullptr) { + args.GetReturnValue().SetUndefined(); + return; + } + auto key = args[0].As(); + auto value = ptr->GetURLSearchParams()->get(tns::ToString(isolate, key)); + if (value.has_value()) { + auto ret = ToV8String(isolate, std::string(value.value())); + args.GetReturnValue().Set(ret); + } else { + args.GetReturnValue().SetUndefined(); + } +} + +void URLSearchParamsImpl::GetAll( + const v8::FunctionCallbackInfo& args) { + URLSearchParamsImpl* ptr = GetPointer(args.This()); + auto isolate = args.GetIsolate(); + auto context = isolate->GetCurrentContext(); + if (ptr == nullptr) { + args.GetReturnValue().Set(v8::Array::New(isolate)); + return; + } + auto key = args[0].As(); + auto values = ptr->GetURLSearchParams()->get_all(tns::ToString(isolate, key)); + auto ret = v8::Array::New(isolate, (int)values.size()); + int i = 0; + for (auto item : values) { + tns::Assert( + ret->Set(context, i++, ToV8String(isolate, item)).FromMaybe(false), + isolate); + } + args.GetReturnValue().Set(ret); +} + +void URLSearchParamsImpl::Has(const v8::FunctionCallbackInfo& args) { + URLSearchParamsImpl* ptr = GetPointer(args.This()); + if (ptr == nullptr) { + args.GetReturnValue().Set(false); + return; + } + auto isolate = args.GetIsolate(); + auto key = args[0].As(); + auto value = ptr->GetURLSearchParams()->has(tns::ToString(isolate, key)); + + args.GetReturnValue().Set(value); +} + +void URLSearchParamsImpl::Keys( + const v8::FunctionCallbackInfo& args) { + URLSearchParamsImpl* ptr = GetPointer(args.This()); + auto isolate = args.GetIsolate(); + auto context = isolate->GetCurrentContext(); + if (ptr == nullptr) { + args.GetReturnValue().Set(v8::Array::New(isolate)); + return; + } + + auto keys = ptr->GetURLSearchParams()->get_keys(); + + auto len = ptr->GetURLSearchParams()->size(); + auto ret = v8::Array::New(isolate, (int)len); + int i = 0; + while (keys.has_next()) { + auto key = keys.next(); + if (key) { + auto keyValue = key.value(); + tns::Assert(ret->Set(context, i++, ToV8String(isolate, keyValue.data())) + .FromMaybe(false), + isolate); } - - void URLSearchParamsImpl::Entries(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - auto isolate = args.GetIsolate(); - auto context = isolate->GetCurrentContext(); - if (ptr == nullptr) { - args.GetReturnValue().Set(v8::Array::New(isolate)); - return; - } - - - auto keys = ptr->GetURLSearchParams()->get_keys(); - auto len = ptr->GetURLSearchParams()->size(); - auto ret = v8::Array::New(isolate, (int)len); - int i = 0; - while (keys.has_next()) { - auto key = keys.next(); - if (key) { - auto keyValue = key.value(); - auto value = ptr->GetURLSearchParams()->get(keyValue).value(); - v8::Local values[] = { - ToV8String(isolate, keyValue.data()), - ToV8String(isolate, value.data()), - }; - auto success = ret->Set(context, i++, v8::Array::New(isolate, values, 2)).FromMaybe(false); - tns::Assert(success, isolate); - } - - } - args.GetReturnValue().Set(ret); - } - - void URLSearchParamsImpl::ForEach(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - auto isolate = args.GetIsolate(); - auto context = isolate->GetCurrentContext(); - if (ptr == nullptr) { - return; - } - auto callback = args[0].As(); - auto keys = ptr->GetURLSearchParams()->get_keys(); - while (keys.has_next()) { - auto key = keys.next(); - if (key) { - auto keyValue = key.value(); - auto value = ptr->GetURLSearchParams()->get(keyValue).value(); - v8::Local values[] = { - ToV8String(isolate, keyValue.data()), - ToV8String(isolate, value.data()), - }; - v8::Local result; - if (!callback->Call(context, v8::Local(), 2, values).ToLocal(&result)){ - tns::Assert(false, isolate); - } - } - - } - } - - void URLSearchParamsImpl::Get(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - auto isolate = args.GetIsolate(); - if (ptr == nullptr) { - args.GetReturnValue().SetUndefined(); - return; - } - auto key = args[0].As(); - auto value = ptr->GetURLSearchParams()->get(tns::ToString(isolate, key)); - if (value.has_value()) { - auto ret = ToV8String(isolate, std::string(value.value())); - args.GetReturnValue().Set(ret); - } else { - args.GetReturnValue().SetUndefined(); - } - } - - void URLSearchParamsImpl::GetAll(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - auto isolate = args.GetIsolate(); - auto context = isolate->GetCurrentContext(); - if (ptr == nullptr) { - args.GetReturnValue().Set(v8::Array::New(isolate)); - return; - } - auto key = args[0].As(); - auto values = ptr->GetURLSearchParams()->get_all(tns::ToString(isolate, key)); - auto ret = v8::Array::New(isolate, (int)values.size()); - int i = 0; - for (auto item: values) { - tns::Assert(ret->Set(context, i++, ToV8String(isolate, item)).FromMaybe(false), isolate); - } - args.GetReturnValue().Set(ret); - } - - void URLSearchParamsImpl::Has(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - if (ptr == nullptr) { - args.GetReturnValue().Set(false); - return; - } - auto isolate = args.GetIsolate(); - auto key = args[0].As(); - auto value = ptr->GetURLSearchParams()->has(tns::ToString(isolate, key)); - - args.GetReturnValue().Set(value); - } - - void URLSearchParamsImpl::Keys(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - auto isolate = args.GetIsolate(); - auto context = isolate->GetCurrentContext(); - if (ptr == nullptr) { - args.GetReturnValue().Set(v8::Array::New(isolate)); - return; - } - - auto keys = ptr->GetURLSearchParams()->get_keys(); - - auto len = ptr->GetURLSearchParams()->size(); - auto ret = v8::Array::New(isolate, (int)len); - int i = 0; - while (keys.has_next()) { - auto key = keys.next(); - if (key) { - auto keyValue = key.value(); - tns::Assert(ret->Set(context, i++, ToV8String(isolate, keyValue.data())).FromMaybe(false), isolate); - } - - } - args.GetReturnValue().Set(ret); - - } - - void URLSearchParamsImpl::Set(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - if (ptr == nullptr) { - return; - } - auto key = args[0].As(); - auto value = args[1].As(); - - auto isolate = args.GetIsolate(); - ptr->GetURLSearchParams()->set( - tns::ToString(isolate, key), - tns::ToString(isolate, value) - ); - } - - void URLSearchParamsImpl::GetSize(v8::Local property, - const v8::PropertyCallbackInfo &info) { - URLSearchParamsImpl *ptr = GetPointer(info.This()); - if (ptr == nullptr) { - info.GetReturnValue().Set(0); - return; - } - - auto value = ptr->GetURLSearchParams()->size(); - info.GetReturnValue().Set((int) value); - - } - - void URLSearchParamsImpl::Sort(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - if (ptr == nullptr) { - return; - } - ptr->GetURLSearchParams()->sort(); - } - - void URLSearchParamsImpl::ToString(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - if (ptr == nullptr) { - args.GetReturnValue().SetEmptyString(); - return; - } - auto isolate = args.GetIsolate(); - - - auto value = ptr->GetURLSearchParams()->to_string(); - - auto ret = ToV8String(isolate, value); - - args.GetReturnValue().Set(ret); - } - - void URLSearchParamsImpl::Values(const v8::FunctionCallbackInfo &args) { - URLSearchParamsImpl *ptr = GetPointer(args.This()); - auto isolate = args.GetIsolate(); - auto context = isolate->GetCurrentContext(); - if (ptr == nullptr) { - args.GetReturnValue().Set(v8::Array::New(isolate)); - return; - } - - auto keys = ptr->GetURLSearchParams()->get_keys(); - - auto len = ptr->GetURLSearchParams()->size(); - auto ret = v8::Array::New(isolate, (int)len); - int i = 0; - while (keys.has_next()) { - auto key = keys.next(); - if (key) { - auto value = ptr->GetURLSearchParams()->get(key.value()); - if (value.has_value()) { - tns::Assert(ret->Set(context, i++, - ToV8String(isolate, std::string(value.value()))).FromMaybe(false), isolate); - } - - } - - } - args.GetReturnValue().Set(ret); - - } - - - url_search_params *URLSearchParamsImpl::GetURLSearchParams() { - return &this->params_; + } + args.GetReturnValue().Set(ret); +} + +void URLSearchParamsImpl::Set(const v8::FunctionCallbackInfo& args) { + URLSearchParamsImpl* ptr = GetPointer(args.This()); + if (ptr == nullptr) { + return; + } + auto key = args[0].As(); + auto value = args[1].As(); + + auto isolate = args.GetIsolate(); + ptr->GetURLSearchParams()->set(tns::ToString(isolate, key), + tns::ToString(isolate, value)); +} + +void URLSearchParamsImpl::GetSize( + v8::Local property, + const v8::PropertyCallbackInfo& info) { + URLSearchParamsImpl* ptr = GetPointer(info.This()); + if (ptr == nullptr) { + info.GetReturnValue().Set(0); + return; + } + + auto value = ptr->GetURLSearchParams()->size(); + info.GetReturnValue().Set((int)value); +} + +void URLSearchParamsImpl::Sort( + const v8::FunctionCallbackInfo& args) { + URLSearchParamsImpl* ptr = GetPointer(args.This()); + if (ptr == nullptr) { + return; + } + ptr->GetURLSearchParams()->sort(); +} + +void URLSearchParamsImpl::ToString( + const v8::FunctionCallbackInfo& args) { + URLSearchParamsImpl* ptr = GetPointer(args.This()); + if (ptr == nullptr) { + args.GetReturnValue().SetEmptyString(); + return; + } + auto isolate = args.GetIsolate(); + + auto value = ptr->GetURLSearchParams()->to_string(); + + auto ret = ToV8String(isolate, value); + + args.GetReturnValue().Set(ret); +} + +void URLSearchParamsImpl::Values( + const v8::FunctionCallbackInfo& args) { + URLSearchParamsImpl* ptr = GetPointer(args.This()); + auto isolate = args.GetIsolate(); + auto context = isolate->GetCurrentContext(); + if (ptr == nullptr) { + args.GetReturnValue().Set(v8::Array::New(isolate)); + return; + } + + auto keys = ptr->GetURLSearchParams()->get_keys(); + + auto len = ptr->GetURLSearchParams()->size(); + auto ret = v8::Array::New(isolate, (int)len); + int i = 0; + while (keys.has_next()) { + auto key = keys.next(); + if (key) { + auto value = ptr->GetURLSearchParams()->get(key.value()); + if (value.has_value()) { + tns::Assert(ret->Set(context, i++, + ToV8String(isolate, std::string(value.value()))) + .FromMaybe(false), + isolate); + } } + } + args.GetReturnValue().Set(ret); +} -} // tns +url_search_params* URLSearchParamsImpl::GetURLSearchParams() { + return &this->params_; +} +} // namespace tns -NODE_BINDING_PER_ISOLATE_INIT_OBJ(urlsearchparams, tns::URLSearchParamsImpl::Init) \ No newline at end of file +NODE_BINDING_PER_ISOLATE_INIT_OBJ(urlsearchparams, + tns::URLSearchParamsImpl::Init) \ No newline at end of file diff --git a/TestRunner/app/tests/URLSearchParams.js b/TestRunner/app/tests/URLSearchParams.js index 6a722585..2da2ef20 100644 --- a/TestRunner/app/tests/URLSearchParams.js +++ b/TestRunner/app/tests/URLSearchParams.js @@ -60,5 +60,58 @@ describe("Test URLSearchParams ", function () { params.set('first', 'Osei'); expect(url.toString()).toBe(toBe); }); - + + it("Test URLSearchParams forEach", function(){ + const params = new URLSearchParams(fooBar); + const results = []; + params.forEach((value, key, searchParams) => { + results.push({ key, value }); + expect(searchParams).toBe(params); + }); + expect(results.length).toBe(2); + expect(results[0].key).toBe("foo"); + expect(results[0].value).toBe("1"); + expect(results[1].key).toBe("bar"); + expect(results[1].value).toBe("2"); + }); + + it("Test URLSearchParams forEach with URL", function(){ + const url = new URL('https://example.com?si=abc123&name=test'); + const results = []; + url.searchParams.forEach((value, key) => { + results.push({ key, value }); + }); + expect(results.length).toBe(2); + expect(results[0].key).toBe("si"); + expect(results[0].value).toBe("abc123"); + expect(results[1].key).toBe("name"); + expect(results[1].value).toBe("test"); + }); + + it("Test URLSearchParams forEach with thisArg", function(){ + const params = new URLSearchParams(fooBar); + const context = { results: [] }; + params.forEach(function(value, key) { + this.results.push({ key, value }); + }, context); + expect(context.results.length).toBe(2); + expect(context.results[0].key).toBe("foo"); + expect(context.results[0].value).toBe("1"); + }); + + it("Test URLSearchParams forEach with duplicate keys", function(){ + const params = new URLSearchParams("foo=1&foo=2&bar=3"); + const results = []; + params.forEach((value, key) => { + results.push({ key, value }); + }); + expect(results.length).toBe(3); + expect(results[0].key).toBe("foo"); + expect(results[0].value).toBe("1"); + expect(results[1].key).toBe("foo"); + expect(results[1].value).toBe("2"); + expect(results[2].key).toBe("bar"); + expect(results[2].value).toBe("3"); + }); + });