11describe ( "Test URLSearchParams " , function ( ) {
22 const fooBar = "foo=1&bar=2" ;
33 it ( "Test URLSearchParams keys" , function ( ) {
4+ // keys() returns a spec iterator, not an array — consume it via spread.
45 const params = new URLSearchParams ( fooBar ) ;
5- const keys = params . keys ( ) ;
6+ const keys = [ ... params . keys ( ) ] ;
67 expect ( keys [ 0 ] ) . toBe ( "foo" ) ;
78 expect ( keys [ 1 ] ) . toBe ( "bar" ) ;
89 } ) ;
910
1011 it ( "Test URLSearchParams values" , function ( ) {
1112 const params = new URLSearchParams ( fooBar ) ;
12- const values = params . values ( ) ;
13+ const values = [ ... params . values ( ) ] ;
1314 expect ( values [ 0 ] ) . toBe ( "1" ) ;
1415 expect ( values [ 1 ] ) . toBe ( "2" ) ;
1516 } ) ;
1617
1718
1819 it ( "Test URLSearchParams entries" , function ( ) {
1920 const params = new URLSearchParams ( fooBar ) ;
20- const entries = params . entries ( ) ;
21+ const entries = [ ... params . entries ( ) ] ;
2122 expect ( entries [ 0 ] [ 0 ] ) . toBe ( "foo" ) ;
2223 expect ( entries [ 0 ] [ 1 ] ) . toBe ( "1" ) ;
2324
@@ -26,6 +27,74 @@ describe("Test URLSearchParams ", function () {
2627
2728 } ) ;
2829
30+ it ( "Test URLSearchParams keys/values/entries return spec iterators" , function ( ) {
31+ const params = new URLSearchParams ( fooBar ) ;
32+ // A spec iterator has a next() and is itself iterable.
33+ expect ( typeof params . entries ( ) . next ) . toBe ( "function" ) ;
34+ expect ( typeof params . keys ( ) . next ) . toBe ( "function" ) ;
35+ expect ( typeof params . values ( ) . next ) . toBe ( "function" ) ;
36+ const it = params . entries ( ) ;
37+ const first = it . next ( ) ;
38+ expect ( first . done ) . toBe ( false ) ;
39+ expect ( first . value [ 0 ] ) . toBe ( "foo" ) ;
40+ expect ( first . value [ 1 ] ) . toBe ( "1" ) ;
41+ } ) ;
42+
43+ it ( "Test URLSearchParams entries preserves duplicate keys" , function ( ) {
44+ // Regression: the old get_keys()+get() path returned the first value for
45+ // every occurrence of a repeated key.
46+ const params = new URLSearchParams ( "a=1&a=2&b=3" ) ;
47+ const entries = [ ...params . entries ( ) ] ;
48+ expect ( entries . length ) . toBe ( 3 ) ;
49+ expect ( entries [ 0 ] [ 1 ] ) . toBe ( "1" ) ;
50+ expect ( entries [ 1 ] [ 1 ] ) . toBe ( "2" ) ;
51+ expect ( entries [ 2 ] [ 1 ] ) . toBe ( "3" ) ;
52+ expect ( [ ...params . values ( ) ] . join ( "," ) ) . toBe ( "1,2,3" ) ;
53+ } ) ;
54+
55+ it ( "Test URLSearchParams default iterator aliases entries" , function ( ) {
56+ // The default @@iterator IS the entries method (browser identity). This binding
57+ // installs members per-instance (not on the prototype), so assert on an instance
58+ // AND assert it is actually a function — not the vacuous undefined === undefined.
59+ const params = new URLSearchParams ( fooBar ) ;
60+ expect ( typeof params [ Symbol . iterator ] ) . toBe ( "function" ) ;
61+ expect ( params [ Symbol . iterator ] ) . toBe ( params . entries ) ;
62+ } ) ;
63+
64+ it ( "Test URLSearchParams iterator carries the spec brand" , function ( ) {
65+ const params = new URLSearchParams ( fooBar ) ;
66+ expect ( Object . prototype . toString . call ( params . entries ( ) ) ) . toBe ( "[object URLSearchParams Iterator]" ) ;
67+ } ) ;
68+
69+ it ( "Test URLSearchParams iterator is live" , function ( ) {
70+ // Spec iterators reflect mutations made after they are created.
71+ const params = new URLSearchParams ( "a=1&b=2" ) ;
72+ const it = params . entries ( ) ;
73+ expect ( it . next ( ) . value [ 0 ] ) . toBe ( "a" ) ; // consume "a"
74+ params . append ( "c" , "3" ) ; // mutate mid-iteration
75+ const rest = [ ] ;
76+ let r ;
77+ while ( ! ( r = it . next ( ) ) . done ) {
78+ rest . push ( r . value [ 0 ] ) ;
79+ }
80+ expect ( rest . join ( "," ) ) . toBe ( "b,c" ) ; // sees the appended "c"
81+ } ) ;
82+
83+ it ( "Test URLSearchParams closes the source iterator on a bad pair" , function ( ) {
84+ // On an abrupt completion (a too-long pair) the source iterator must be
85+ // closed, so a generator's finally runs and resource-backed iterables free.
86+ let closed = false ;
87+ function * gen ( ) {
88+ try {
89+ yield [ "a" , "1" , "2" ] ; // 3-element pair → TypeError
90+ } finally {
91+ closed = true ;
92+ }
93+ }
94+ expect ( function ( ) { new URLSearchParams ( gen ( ) ) ; } ) . toThrow ( ) ;
95+ expect ( closed ) . toBe ( true ) ;
96+ } ) ;
97+
2998
3099 it ( "Test URLSearchParams size" , function ( ) {
31100 const params = new URLSearchParams ( fooBar ) ;
@@ -43,7 +112,27 @@ describe("Test URLSearchParams ", function () {
43112 const params = new URLSearchParams ( fooBar ) ;
44113 params . append ( "first" , "Osei" ) ;
45114 params . delete ( "first" ) ;
46- expect ( params . get ( "first" ) ) . toBe ( undefined ) ;
115+ // Spec: get() returns null for a missing name (url.bs:4016).
116+ expect ( params . get ( "first" ) ) . toBe ( null ) ;
117+ } ) ;
118+
119+ it ( "Test URLSearchParams get returns null for a missing name" , function ( ) {
120+ // Spec get(name): "...otherwise null" (url.bs:4016).
121+ const params = new URLSearchParams ( "a=1" ) ;
122+ expect ( params . get ( "missing" ) ) . toBe ( null ) ;
123+ } ) ;
124+
125+ it ( "Test URLSearchParams delete with value removes only matching pairs" , function ( ) {
126+ // Spec delete(name, value): when value is given, remove only tuples
127+ // matching BOTH name and value (url.bs:4000). The value is a USVString,
128+ // so a non-string (the number 1) coerces to "1".
129+ const params = new URLSearchParams ( "a=1&a=2&a=1&b=1" ) ;
130+ params . delete ( "a" , 1 ) ;
131+ expect ( params . getAll ( "a" ) . join ( "," ) ) . toBe ( "2" ) ;
132+ expect ( params . getAll ( "b" ) . join ( "," ) ) . toBe ( "1" ) ;
133+ // Single-arg delete still removes every pair with that name.
134+ params . delete ( "a" ) ;
135+ expect ( params . has ( "a" ) ) . toBe ( false ) ;
47136 } ) ;
48137
49138
@@ -52,6 +141,47 @@ describe("Test URLSearchParams ", function () {
52141 expect ( params . has ( "foo" ) ) . toBe ( true ) ;
53142 } ) ;
54143
144+ it ( "Test URLSearchParams has with value matches name and value" , function ( ) {
145+ // Spec has(name, value): true only for a tuple matching BOTH (url.bs:4028).
146+ // The value is a USVString, so non-strings (number, boolean) coerce.
147+ const params = new URLSearchParams ( "a=1&a=2&flag=true" ) ;
148+ expect ( params . has ( "a" , "1" ) ) . toBe ( true ) ;
149+ expect ( params . has ( "a" , 2 ) ) . toBe ( true ) ; // number coerces to "2"
150+ expect ( params . has ( "a" , "3" ) ) . toBe ( false ) ;
151+ expect ( params . has ( "flag" , true ) ) . toBe ( true ) ; // boolean coerces to "true"
152+ expect ( params . has ( "missing" , "1" ) ) . toBe ( false ) ;
153+ // Single-arg has still matches by name only.
154+ expect ( params . has ( "a" ) ) . toBe ( true ) ;
155+ } ) ;
156+
157+ it ( "Test URLSearchParams has/delete throw when the value cannot be coerced" , function ( ) {
158+ // The value argument is a USVString; a Symbol (or a throwing toString)
159+ // cannot convert, so the call must throw rather than silently matching ""
160+ // (WebIDL USVString conversion, url.bs:4000 / 4028).
161+ const params = new URLSearchParams ( "a=1" ) ;
162+ expect ( function ( ) { params . has ( "a" , Symbol ( "x" ) ) ; } ) . toThrow ( ) ;
163+ expect ( function ( ) { params . delete ( "a" , Symbol ( "x" ) ) ; } ) . toThrow ( ) ;
164+ } ) ;
165+
166+ it ( "Test URLSearchParams has/delete treat an explicit undefined value as omitted" , function ( ) {
167+ // Per WPT (urlsearchparams-has / -delete "respects undefined as second
168+ // arg"), an explicit `undefined` second argument is treated as omitted
169+ // (name-only), NOT as the string "undefined".
170+ const params = new URLSearchParams ( "a=b&a=d&c&e&" ) ;
171+ expect ( params . has ( "a" , "b" ) ) . toBe ( true ) ;
172+ expect ( params . has ( "a" , "c" ) ) . toBe ( false ) ;
173+ expect ( params . has ( "a" , undefined ) ) . toBe ( true ) ; // undefined -> name-only
174+
175+ const del = new URLSearchParams ( ) ;
176+ del . append ( "a" , "b" ) ;
177+ del . append ( "a" , "c" ) ;
178+ del . append ( "b" , "c" ) ;
179+ del . append ( "b" , "d" ) ;
180+ del . delete ( "b" , "c" ) ;
181+ del . delete ( "a" , undefined ) ; // undefined -> delete by name
182+ expect ( del . toString ( ) ) . toBe ( "b=d" ) ;
183+ } ) ;
184+
55185 it ( "Test URLSearchParams changes propagates to URL parent" , function ( ) {
56186 const toBe = 'https://github.com/triniwiz?first=Osei' ;
57187 const url = new URL ( 'https://github.com/triniwiz' ) ;
@@ -114,4 +244,171 @@ describe("Test URLSearchParams ", function () {
114244 expect ( results [ 2 ] . value ) . toBe ( "3" ) ;
115245 } ) ;
116246
247+ it ( "Test URLSearchParams from record object" , function ( ) {
248+ const params = new URLSearchParams ( { foo : "1" , bar : "2" } ) ;
249+ expect ( params . get ( "foo" ) ) . toBe ( "1" ) ;
250+ expect ( params . get ( "bar" ) ) . toBe ( "2" ) ;
251+ expect ( params . size ) . toBe ( 2 ) ;
252+ // A plain object must expand to its entries, not collapse into a
253+ // single "[object Object]" key.
254+ expect ( params . has ( "[object Object]" ) ) . toBe ( false ) ;
255+ } ) ;
256+
257+ it ( "Test URLSearchParams from record serializes every pair in toString" , function ( ) {
258+ const params = new URLSearchParams ( { one : "1" , two : "2" } ) ;
259+ expect ( params . toString ( ) ) . toBe ( "one=1&two=2" ) ;
260+ } ) ;
261+
262+ it ( "Test URLSearchParams from record coerces values to strings" , function ( ) {
263+ const params = new URLSearchParams ( { a : 1 , b : true } ) ;
264+ expect ( params . get ( "a" ) ) . toBe ( "1" ) ;
265+ expect ( params . get ( "b" ) ) . toBe ( "true" ) ;
266+ } ) ;
267+
268+ it ( "Test URLSearchParams from record encodes special characters" , function ( ) {
269+ const params = new URLSearchParams ( { q : "a b&c" } ) ;
270+ expect ( params . get ( "q" ) ) . toBe ( "a b&c" ) ;
271+ expect ( params . toString ( ) ) . toBe ( "q=a+b%26c" ) ;
272+ } ) ;
273+
274+ it ( "Test URLSearchParams from array of pairs" , function ( ) {
275+ const params = new URLSearchParams ( [ [ "foo" , "1" ] , [ "bar" , "2" ] , [ "foo" , "3" ] ] ) ;
276+ expect ( params . get ( "foo" ) ) . toBe ( "1" ) ;
277+ expect ( params . getAll ( "foo" ) . length ) . toBe ( 2 ) ;
278+ expect ( params . get ( "bar" ) ) . toBe ( "2" ) ;
279+ expect ( params . size ) . toBe ( 3 ) ;
280+ } ) ;
281+
282+ it ( "Test URLSearchParams empty record and no-arg produce empty query" , function ( ) {
283+ expect ( new URLSearchParams ( ) . toString ( ) ) . toBe ( "" ) ;
284+ expect ( new URLSearchParams ( { } ) . toString ( ) ) . toBe ( "" ) ;
285+ } ) ;
286+
287+ it ( "Test URLSearchParams from record throws when a value cannot be coerced to a string" , function ( ) {
288+ // Per spec the record/sequence init coerces every value to a USVString;
289+ // a value that cannot convert (a Symbol here) must throw rather than
290+ // silently dropping or emptying the entry.
291+ expect ( function ( ) { new URLSearchParams ( { bad : Symbol ( "x" ) } ) ; } ) . toThrow ( ) ;
292+ } ) ;
293+
294+ // --- Iterable (sequence) init: any iterable of pairs, not only arrays. ---
295+
296+ it ( "Test URLSearchParams from a Map" , function ( ) {
297+ const params = new URLSearchParams ( new Map ( [ [ "a" , "1" ] , [ "b" , "2" ] ] ) ) ;
298+ expect ( params . toString ( ) ) . toBe ( "a=1&b=2" ) ;
299+ } ) ;
300+
301+ it ( "Test URLSearchParams from a Set of pairs" , function ( ) {
302+ const params = new URLSearchParams ( new Set ( [ [ "x" , "1" ] , [ "y" , "2" ] ] ) ) ;
303+ expect ( params . toString ( ) ) . toBe ( "x=1&y=2" ) ;
304+ } ) ;
305+
306+ it ( "Test URLSearchParams copy-constructs from another URLSearchParams" , function ( ) {
307+ // A URLSearchParams is iterable, so per spec it resolves to the sequence
308+ // (copy) form — including duplicate keys, which proves the @@iterator walks
309+ // entries rather than collapsing them.
310+ const source = new URLSearchParams ( "a=1&a=2&b=3" ) ;
311+ const copy = new URLSearchParams ( source ) ;
312+ expect ( copy . toString ( ) ) . toBe ( "a=1&a=2&b=3" ) ;
313+ expect ( copy . getAll ( "a" ) . length ) . toBe ( 2 ) ;
314+ } ) ;
315+
316+ it ( "Test URLSearchParams from a generator of pairs" , function ( ) {
317+ function * pairs ( ) {
318+ yield [ "a" , "1" ] ;
319+ yield [ "b" , "2" ] ;
320+ }
321+ const params = new URLSearchParams ( pairs ( ) ) ;
322+ expect ( params . toString ( ) ) . toBe ( "a=1&b=2" ) ;
323+ } ) ;
324+
325+ it ( "Test URLSearchParams from sequence with non-array inner pairs" , function ( ) {
326+ // Each pair need only be a 2-element iterable, not specifically an array.
327+ // A Set is iterable but not an Array, so it exercises the inner iterator path.
328+ const params = new URLSearchParams ( [ new Set ( [ "k" , "v" ] ) ] ) ;
329+ expect ( params . get ( "k" ) ) . toBe ( "v" ) ;
330+ } ) ;
331+
332+ it ( "Test URLSearchParams sequence init throws on a too-long pair" , function ( ) {
333+ expect ( function ( ) { new URLSearchParams ( [ [ "a" , "1" , "2" ] ] ) ; } ) . toThrow ( ) ;
334+ } ) ;
335+
336+ it ( "Test URLSearchParams sequence init throws on a too-short pair" , function ( ) {
337+ expect ( function ( ) { new URLSearchParams ( [ [ "a" ] ] ) ; } ) . toThrow ( ) ;
338+ } ) ;
339+
340+ it ( "Test URLSearchParams sequence init throws on a non-iterable element" , function ( ) {
341+ expect ( function ( ) { new URLSearchParams ( [ null ] ) ; } ) . toThrow ( ) ;
342+ expect ( function ( ) { new URLSearchParams ( [ 1 ] ) ; } ) . toThrow ( ) ;
343+ } ) ;
344+
345+ it ( "Test URLSearchParams sequence init throws on a primitive string element" , function ( ) {
346+ // WebIDL converts each element to sequence<USVString>, whose first step throws
347+ // when the element is not an Object. A 2-code-point string must NOT be accepted
348+ // as the pair ("a","b").
349+ expect ( function ( ) { new URLSearchParams ( [ "ab" ] ) ; } ) . toThrow ( ) ;
350+ } ) ;
351+
352+ it ( "Test URLSearchParams sequence init accepts a String-object element" , function ( ) {
353+ // A String *object* IS an Object and is iterable, so it is a valid 2-char pair.
354+ const params = new URLSearchParams ( [ new String ( "ab" ) ] ) ;
355+ expect ( params . get ( "a" ) ) . toBe ( "b" ) ;
356+ } ) ;
357+
358+ it ( "Test URLSearchParams throws when @@iterator is present but not callable" , function ( ) {
359+ // Per WebIDL GetMethod, a non-callable @@iterator is a TypeError, not a
360+ // silent fall-back to the record form.
361+ expect ( function ( ) { new URLSearchParams ( { [ Symbol . iterator ] : 5 } ) ; } ) . toThrow ( ) ;
362+ } ) ;
363+
364+ // --- The type itself is iterable<USVString, USVString>. ---
365+
366+ it ( "Test URLSearchParams is spread-iterable via Symbol.iterator" , function ( ) {
367+ const params = new URLSearchParams ( "a=1&b=2" ) ;
368+ const entries = [ ...params ] ;
369+ expect ( entries . length ) . toBe ( 2 ) ;
370+ expect ( entries [ 0 ] [ 0 ] ) . toBe ( "a" ) ;
371+ expect ( entries [ 0 ] [ 1 ] ) . toBe ( "1" ) ;
372+ expect ( entries [ 1 ] [ 0 ] ) . toBe ( "b" ) ;
373+ expect ( entries [ 1 ] [ 1 ] ) . toBe ( "2" ) ;
374+ } ) ;
375+
376+ it ( "Test URLSearchParams works in a for..of loop" , function ( ) {
377+ const params = new URLSearchParams ( "a=1&a=2" ) ;
378+ const seen = [ ] ;
379+ for ( const [ key , value ] of params ) {
380+ seen . push ( key + "=" + value ) ;
381+ }
382+ expect ( seen . length ) . toBe ( 2 ) ;
383+ expect ( seen [ 0 ] ) . toBe ( "a=1" ) ;
384+ expect ( seen [ 1 ] ) . toBe ( "a=2" ) ;
385+ } ) ;
386+
387+ // --- Primitive init: coerced to USVString, then parsed. ---
388+
389+ it ( "Test URLSearchParams from a number" , function ( ) {
390+ expect ( new URLSearchParams ( 123 ) . toString ( ) ) . toBe ( "123=" ) ;
391+ } ) ;
392+
393+ it ( "Test URLSearchParams from a boolean" , function ( ) {
394+ expect ( new URLSearchParams ( true ) . toString ( ) ) . toBe ( "true=" ) ;
395+ } ) ;
396+
397+ it ( "Test URLSearchParams from a bigint" , function ( ) {
398+ expect ( new URLSearchParams ( 10n ) . toString ( ) ) . toBe ( "10=" ) ;
399+ } ) ;
400+
401+ it ( "Test URLSearchParams strips a single leading question mark" , function ( ) {
402+ expect ( new URLSearchParams ( "?a=1" ) . get ( "a" ) ) . toBe ( "1" ) ;
403+ } ) ;
404+
405+ it ( "Test URLSearchParams throws when init is a Symbol" , function ( ) {
406+ expect ( function ( ) { new URLSearchParams ( Symbol ( "x" ) ) ; } ) . toThrow ( ) ;
407+ } ) ;
408+
409+ it ( "Test URLSearchParams from null or undefined is empty" , function ( ) {
410+ expect ( new URLSearchParams ( null ) . toString ( ) ) . toBe ( "" ) ;
411+ expect ( new URLSearchParams ( undefined ) . toString ( ) ) . toBe ( "" ) ;
412+ } ) ;
413+
117414} ) ;
0 commit comments