diff --git a/client/utils/parseURLParams.test.js b/client/utils/parseURLParams.test.js deleted file mode 100644 index 56e0b52611..0000000000 --- a/client/utils/parseURLParams.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import { parseUrlParams } from './parseURLParams'; -import { currentP5Version } from '../../common/p5Versions'; - -describe('parseUrlParams', () => { - test('returns defaults when no params are provided', () => { - const url = 'https://example.com'; - const result = parseUrlParams(url); - - expect(result).toEqual({ - version: currentP5Version, - sound: true, - preload: false, - shapes: false, - data: false - }); - }); - - test('parses a valid p5 version and falls back for invalid versions', () => { - const good = parseUrlParams('https://example.com?version=1.4.0'); - expect(good.version).toBe('1.4.0'); - - const bad = parseUrlParams('https://example.com?version=9.9.9'); - expect(bad.version).toBe(currentP5Version); - }); - - test('parses boolean-like params for sound/preload/shapes/data (true variants)', () => { - const trueVariants = ['on', 'true', '1', 'ON', 'True']; - - trueVariants.forEach((v) => { - const url = `https://example.com?sound=${v}&preload=${v}&shapes=${v}&data=${v}`; - const result = parseUrlParams(url); - expect(result.sound).toBe(true); - expect(result.preload).toBe(true); - expect(result.shapes).toBe(true); - expect(result.data).toBe(true); - }); - }); - - test('parses boolean-like params for sound/preload/shapes/data (false variants)', () => { - const falseVariants = ['off', 'false', '0', 'OFF', 'False']; - - falseVariants.forEach((v) => { - const url = `https://example.com?sound=${v}&preload=${v}&shapes=${v}&data=${v}`; - const result = parseUrlParams(url); - expect(result.sound).toBe(false); - expect(result.preload).toBe(false); - expect(result.shapes).toBe(false); - expect(result.data).toBe(false); - }); - }); -}); diff --git a/client/utils/parseURLParams.test.ts b/client/utils/parseURLParams.test.ts new file mode 100644 index 0000000000..6a6cf8e151 --- /dev/null +++ b/client/utils/parseURLParams.test.ts @@ -0,0 +1,118 @@ +import { parseUrlParams, p5VersionStrings } from './parseURLParams'; +import { currentP5Version } from '../../common/p5Versions'; + +describe('parseUrlParams', () => { + describe('default behavior', () => { + test('returns defaults when no params are provided', () => { + const url = 'https://example.com'; + const result = parseUrlParams(url); + + expect(result).toEqual({ + version: currentP5Version, + sound: true, + preload: false, + shapes: false, + data: false + }); + }); + + test('falls back to defaults for unsupported inputs', () => { + const url = + 'https://example.com?version=A&sound=A&preload=A&shapes=A&data=A'; + const result = parseUrlParams(url); + + expect(result).toEqual({ + version: currentP5Version, + sound: true, + preload: false, + shapes: false, + data: false + }); + }); + }); + + describe('version parsing', () => { + // Uses regex since p5Versions may be updated over time. + // Checks to ensure version is valid too. + + test('parses a valid p5 version and falls back for invalid versions', () => { + const good = parseUrlParams('https://example.com?version=1.4.0'); + expect(good.version).toBe('1.4.0'); + + const bad = parseUrlParams('https://example.com?version=9.9.9'); + expect(bad.version).toBe(currentP5Version); + }); + + test('parses major.minor version 0.5.x to newest patch', () => { + const url = 'https://example.com?version=0.5'; + const result = parseUrlParams(url); + + expect(result.version).toMatch(/^0\.5\.\d+$/); + expect(p5VersionStrings).toContain(result.version); + }); + + test('parses major version 0 to newest 0.x version', () => { + const url = 'https://example.com?version=0'; + const result = parseUrlParams(url); + + expect(result.version).toMatch(/^0\.\d+\.\d+$/); + expect(p5VersionStrings).toContain(result.version); + }); + + test('parses major version 1 to newest 1.x version', () => { + const url = 'https://example.com?version=1'; + const result = parseUrlParams(url); + expect(result.version).toMatch(/^1\.\d+\.\d+$/); + expect(p5VersionStrings).toContain(result.version); + }); + + test('parses major.minor version 1.1.x to newest patch', () => { + const url = 'https://example.com?version=1.1'; + const result = parseUrlParams(url); + expect(result.version).toMatch(/^1\.1\.\d+$/); + expect(p5VersionStrings).toContain(result.version); + }); + + test('parses major version 2 to newest 2.x version', () => { + const url = 'https://example.com?version=2'; + const result = parseUrlParams(url); + expect(result.version).toMatch(/^2\.\d+\.\d+$/); + expect(p5VersionStrings).toContain(result.version); + }); + + test('parses major.minor version 2.0.x to newest patch', () => { + const url = 'https://example.com?version=2.0'; + const result = parseUrlParams(url); + expect(result.version).toMatch(/^2\.0\.\d+$/); + expect(p5VersionStrings).toContain(result.version); + }); + }); + + describe('boolean param parsing', () => { + test('parses boolean-like params for sound/preload/shapes/data (true variants)', () => { + const trueVariants = ['on', 'true', '1', 'ON', 'True']; + + trueVariants.forEach((v) => { + const url = `https://example.com?sound=${v}&preload=${v}&shapes=${v}&data=${v}`; + const result = parseUrlParams(url); + expect(result.sound).toBe(true); + expect(result.preload).toBe(true); + expect(result.shapes).toBe(true); + expect(result.data).toBe(true); + }); + }); + + test('parses boolean-like params for sound/preload/shapes/data (false variants)', () => { + const falseVariants = ['off', 'false', '0', 'OFF', 'False']; + + falseVariants.forEach((v) => { + const url = `https://example.com?sound=${v}&preload=${v}&shapes=${v}&data=${v}`; + const result = parseUrlParams(url); + expect(result.sound).toBe(false); + expect(result.preload).toBe(false); + expect(result.shapes).toBe(false); + expect(result.data).toBe(false); + }); + }); + }); +}); diff --git a/client/utils/parseURLParams.js b/client/utils/parseURLParams.ts similarity index 70% rename from client/utils/parseURLParams.js rename to client/utils/parseURLParams.ts index 3770c918e3..5e47683818 100644 --- a/client/utils/parseURLParams.js +++ b/client/utils/parseURLParams.ts @@ -1,5 +1,13 @@ import { p5Versions, currentP5Version } from '../../common/p5Versions'; +export interface ParsedUrlParams { + version: string; + sound: boolean; + preload: boolean; + shapes: boolean; + data: boolean; +} + const DEFAULTS = { sound: true, preload: false, @@ -7,12 +15,15 @@ const DEFAULTS = { data: false }; -/** - * Sorts version strings in descending order and returns the highest version - * @param {string[]} versions - Array of version strings (e.g., ['1.11.2', '1.11.1']) - * @returns {string} The highest version from the array - */ -function getNewestVersion(versions) { +function getVersionString( + item: string | { version: string; label: string } +): string { + return typeof item === 'string' ? item : item.version; +} + +export const p5VersionStrings = p5Versions.map(getVersionString); + +function getNewestVersion(versions: string[]): string { return versions.sort((a, b) => { const pa = a.split('.').map((n) => parseInt(n, 10)); const pb = b.split('.').map((n) => parseInt(n, 10)); @@ -25,18 +36,18 @@ function getNewestVersion(versions) { })[0]; } -function validateVersion(version) { +function validateVersion(version: string | null): string { if (!version) return currentP5Version; const ver = String(version).trim(); - if (p5Versions.includes(ver)) return ver; + if (p5VersionStrings.includes(ver)) return ver; // if only major.minor provided like "1.11" const majorMinorMatch = /^(\d+)\.(\d+)$/.exec(ver); if (majorMinorMatch) { const [, major, minor] = majorMinorMatch; - const matches = p5Versions.filter((v) => { + const matches = p5VersionStrings.filter((v) => { const parts = v.split('.'); return parts[0] === major && parts[1] === minor; }); @@ -49,7 +60,7 @@ function validateVersion(version) { const majorOnlyMatch = /^(\d+)$/.exec(ver); if (majorOnlyMatch) { const [, major] = majorOnlyMatch; - const matches = p5Versions.filter((v) => v.split('.')[0] === major); + const matches = p5VersionStrings.filter((v) => v.split('.')[0] === major); if (matches.length) { return getNewestVersion(matches); } @@ -58,7 +69,7 @@ function validateVersion(version) { return currentP5Version; } -function validateBool(value, defaultValue) { +function validateBool(value: string | null, defaultValue: boolean): boolean { if (!value) return defaultValue; const v = String(value).trim().toLowerCase(); @@ -72,7 +83,7 @@ function validateBool(value, defaultValue) { return defaultValue; } -export function parseUrlParams(url) { +export function parseUrlParams(url: string): ParsedUrlParams { const params = new URLSearchParams( new URL(url, 'https://dummy.origin').search );