From a2a5952b3a35c1254b115d7d55d9b15998287623 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 10:48:24 +0000 Subject: [PATCH] Add parseQueryString utility and tests in JavaScript Created utils/utils.js with a parseQueryString implementation that handles leading question marks, URI decoding, '+' to space conversion, and multiple values for the same key. Added comprehensive tests in utils/utils.test.js using the built-in node:test and node:assert modules. Co-authored-by: Ruh-Al-Tarikh <203426218+Ruh-Al-Tarikh@users.noreply.github.com> --- utils/utils.js | 46 ++++++++++++++++++++++++++++++++++++++++ utils/utils.test.js | 51 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 utils/utils.js create mode 100644 utils/utils.test.js diff --git a/utils/utils.js b/utils/utils.js new file mode 100644 index 00000000000..a7bcebc643f --- /dev/null +++ b/utils/utils.js @@ -0,0 +1,46 @@ +/** + * Parses a URL query string into an object. + * @param {string} query - The query string to parse. + * @returns {Object} - An object containing the parsed key-value pairs. + */ +function parseQueryString(query) { + if (!query) { + return {}; + } + + // Remove leading '?' if present + if (query.startsWith('?')) { + query = query.substring(1); + } + + const params = {}; + const pairs = query.split('&'); + + for (const pair of pairs) { + if (!pair) continue; + + const [rawKey, rawValue] = pair.split('='); + + // Replace '+' with space and decode URI components + const key = decodeURIComponent(rawKey.replace(/\+/g, ' ')); + const value = rawValue !== undefined + ? decodeURIComponent(rawValue.replace(/\+/g, ' ')) + : ''; + + if (!key) continue; + + if (Object.prototype.hasOwnProperty.call(params, key)) { + if (Array.isArray(params[key])) { + params[key].push(value); + } else { + params[key] = [params[key], value]; + } + } else { + params[key] = value; + } + } + + return params; +} + +module.exports = { parseQueryString }; diff --git a/utils/utils.test.js b/utils/utils.test.js new file mode 100644 index 00000000000..9cc0563070d --- /dev/null +++ b/utils/utils.test.js @@ -0,0 +1,51 @@ +const { test, describe } = require('node:test'); +const assert = require('node:assert'); +const { parseQueryString } = require('./utils.js'); + +describe('parseQueryString', () => { + test('parses basic key-value pairs', () => { + const result = parseQueryString('foo=bar&baz=qux'); + assert.deepStrictEqual(result, { foo: 'bar', baz: 'qux' }); + }); + + test('handles empty query string', () => { + assert.deepStrictEqual(parseQueryString(''), {}); + assert.deepStrictEqual(parseQueryString(null), {}); + assert.deepStrictEqual(parseQueryString(undefined), {}); + }); + + test('handles leading question mark', () => { + const result = parseQueryString('?foo=bar&baz=qux'); + assert.deepStrictEqual(result, { foo: 'bar', baz: 'qux' }); + }); + + test('decodes URI components', () => { + const result = parseQueryString('foo%20bar=baz%21'); + assert.deepStrictEqual(result, { 'foo bar': 'baz!' }); + }); + + test('replaces + with space', () => { + const result = parseQueryString('foo+bar=baz+qux'); + assert.deepStrictEqual(result, { 'foo bar': 'baz qux' }); + }); + + test('handles multiple values for the same key', () => { + const result = parseQueryString('foo=bar&foo=baz&foo=qux'); + assert.deepStrictEqual(result, { foo: ['bar', 'baz', 'qux'] }); + }); + + test('handles keys without values', () => { + const result = parseQueryString('foo=&bar'); + assert.deepStrictEqual(result, { foo: '', bar: '' }); + }); + + test('handles malformed pairs', () => { + const result = parseQueryString('foo=bar&&&baz=qux'); + assert.deepStrictEqual(result, { foo: 'bar', baz: 'qux' }); + }); + + test('handles only key', () => { + const result = parseQueryString('foo'); + assert.deepStrictEqual(result, { foo: '' }); + }); +});