diff --git a/examples/Apps_script_and_Workspace_codelab/parseQueryString.js b/examples/Apps_script_and_Workspace_codelab/parseQueryString.js
new file mode 100644
index 000000000..bee3fa637
--- /dev/null
+++ b/examples/Apps_script_and_Workspace_codelab/parseQueryString.js
@@ -0,0 +1,44 @@
+/**
+ * Parses a query string into an object.
+ *
+ * @param {string} queryString The query string to parse.
+ * @return {object} An object containing the parsed key-value pairs.
+ */
+function parseQueryString(queryString) {
+ const params = {};
+ if (!queryString) {
+ return params;
+ }
+
+ // Remove leading '?' if present
+ if (queryString.startsWith('?')) {
+ queryString = queryString.substring(1);
+ }
+
+ if (!queryString) { // Check again in case query string was only "?"
+ return params;
+ }
+
+ const pairs = queryString.split('&');
+
+ for (let i = 0; i < pairs.length; i++) {
+ const pair = pairs[i].split('=');
+ const key = decodeURIComponent(pair[0]);
+ let value = '';
+ if (pair.length > 1) {
+ value = decodeURIComponent(pair[1] || '');
+ }
+
+ if (key in params) {
+ if (Array.isArray(params[key])) {
+ params[key].push(value);
+ } else {
+ params[key] = [params[key], value];
+ }
+ } else {
+ params[key] = value;
+ }
+ }
+
+ return params;
+}
diff --git a/examples/Apps_script_and_Workspace_codelab/parseQueryString.test.js b/examples/Apps_script_and_Workspace_codelab/parseQueryString.test.js
new file mode 100644
index 000000000..10bd9e7f1
--- /dev/null
+++ b/examples/Apps_script_and_Workspace_codelab/parseQueryString.test.js
@@ -0,0 +1,177 @@
+// Content of parseQueryString.js (ideally this would be imported)
+// For this self-contained test, we'll assume parseQueryString is defined globally
+// or paste its content here if it's not too large. For now, this test file
+// will *not* include the function itself but will be written as if `parseQueryString` is accessible.
+
+// Simple deep equal function for comparing objects and arrays in tests
+function deepEqual(obj1, obj2) {
+ if (obj1 === obj2) return true;
+ if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
+ return false;
+ }
+ let keys1 = Object.keys(obj1);
+ let keys2 = Object.keys(obj2);
+ if (keys1.length !== keys2.length) return false;
+ for (let key of keys1) {
+ if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Test suite
+const tests = [
+ {
+ name: 'should parse a very simple query string (key=value)',
+ input: 'key=value',
+ expected: { key: 'value' },
+ },
+ {
+ name: 'should parse a basic query string with multiple parameters',
+ input: 'name=John&age=30',
+ expected: { name: 'John', age: '30' },
+ },
+ {
+ name: 'should handle query string with leading ?',
+ input: '?name=Jane&city=NewYork',
+ expected: { name: 'Jane', city: 'NewYork' },
+ },
+ {
+ name: 'should return an empty object for an empty string',
+ input: '',
+ expected: {},
+ },
+ {
+ name: 'should return an empty object for null input',
+ input: null,
+ expected: {},
+ },
+ {
+ name: 'should return an empty object for undefined input',
+ input: undefined,
+ expected: {},
+ },
+ {
+ name: 'should return an empty object for a string with only ?',
+ input: '?',
+ expected: {},
+ },
+ {
+ name: 'should handle URL-encoded characters',
+ input: 'name=John%20Doe&occupation=Software%20Engineer',
+ expected: { name: 'John Doe', occupation: 'Software Engineer' },
+ },
+ {
+ name: 'should handle multiple values for the same parameter',
+ input: 'key=value1&key=value2&other=abc',
+ expected: { key: ['value1', 'value2'], other: 'abc' },
+ },
+ {
+ name: 'should handle a key appearing multiple times, starting with a value then empty then another value',
+ input: 'key=value1&key=&key=value3',
+ expected: { key: ['value1', '', 'value3'] },
+ },
+ {
+ name: 'should handle parameters with no values (empty string)',
+ input: 'name=John&emptyParam=&another=value',
+ expected: { name: 'John', emptyParam: '', another: 'value' },
+ },
+ {
+ name: 'should handle parameters with no values at the end',
+ input: 'name=John&emptyParam=',
+ expected: { name: 'John', emptyParam: '' },
+ },
+ {
+ name: 'should handle a single parameter with no value',
+ input: 'empty=',
+ expected: { empty: '' },
+ },
+ {
+ name: 'should handle a parameter without an equals sign (current behavior: key becomes empty string)',
+ input: 'flag&name=John',
+ // Assuming current parseQueryString function's behavior:
+ // 'flag'.split('=') -> ['flag']
+ // key = decodeURIComponent('flag')
+ // value = decodeURIComponent(undefined) -> "undefined" (string)
+ // So, this test documents that. If 'flag' should be true or '', function needs change.
+ // Based on the provided function, pair[1] would be undefined, and decodeURIComponent(undefined) is "undefined".
+ // Let's re-check the function: if (pair.length > 1) { value = decodeURIComponent(pair[1] || ''); } else { value = ''; }
+ // So 'flag' would result in key='flag', value=''
+ expected: { flag: '', name: 'John'},
+ },
+ {
+ name: 'should handle complex query string with mixed cases',
+ input: '?name=John%20Doe&age=30&city=New%20York&hobbies=reading&hobbies=hiking&status=active&empty=&novalue',
+ // As per logic for 'novalue': key='novalue', value=''
+ expected: {
+ name: 'John Doe',
+ age: '30',
+ city: 'New York',
+ hobbies: ['reading', 'hiking'],
+ status: 'active',
+ empty: '',
+ novalue: ''
+ },
+ },
+ {
+ name: 'should handle query string with special characters in values',
+ input: 'email=test%40example.com&message=Hello%2C%20world%21',
+ expected: { email: 'test@example.com', message: 'Hello, world!' },
+ }
+];
+
+// Function to run tests (assuming parseQueryString is globally available)
+function runTests() {
+ console.log('Running parseQueryString tests...\n');
+ let passedCount = 0;
+ let failedCount = 0;
+
+ // This is a placeholder. In a real environment, ensure parseQueryString is loaded.
+ // For example, if parseQueryString.js just defines the function globally, this will work.
+ // If it uses modules, you'd need an environment that supports them.
+ if (typeof parseQueryString !== 'function') {
+ console.error('FATAL ERROR: parseQueryString function is not defined. Cannot run tests.');
+ console.log('Please ensure parseQueryString.js is loaded before running the tests, or include the function definition in this file.');
+ return;
+ }
+
+ tests.forEach((test, index) => {
+ const result = parseQueryString(test.input);
+ const passed = deepEqual(result, test.expected);
+
+ if (passed) {
+ console.log(`Test ${index + 1}: ${test.name} - PASSED`);
+ passedCount++;
+ } else {
+ console.error(`Test ${index + 1}: ${test.name} - FAILED`);
+ console.error(' Input: ', test.input);
+ console.error(' Expected: ', JSON.stringify(test.expected));
+ console.error(' Got: ', JSON.stringify(result));
+ failedCount++;
+ }
+ console.log('---');
+ });
+
+ console.log(`\nTests finished. Passed: ${passedCount}, Failed: ${failedCount}`);
+}
+
+// To run the tests, uncomment the line below or run it in a console
+// where parseQueryString function is available.
+// runTests();
+
+// For the purpose of this environment, we will not call runTests() here
+// but the structure is ready.
+console.log("Test file 'parseQueryString.test.js' updated with a simple test runner structure.");
+console.log("To execute tests: ensure 'parseQueryString' function is loaded/defined, then call 'runTests()'.");
+
+// Note: The original parseQueryString function needs to be accessible for these tests to run.
+// One way to ensure this for a standalone HTML file would be:
+//
+//
+//
+// Or in Node.js:
+// const fs = require('fs');
+// eval(fs.readFileSync('parseQueryString.js','utf8'));
+// eval(fs.readFileSync('parseQueryString.test.js','utf8'));
+// runTests();