Skip to content

Commit e61bc35

Browse files
Hugo-RMGoodKimchiEnochteo
committed
Finish validator functions for URL parser
Add validation functions to ensure URL parameters for p5.js versioning is correctly parsed and handled. Falls back to default functionality for invalid/missing parameters. Use npx prettier --write to format all modified files. Change test fallbacks to be consistent with default behavior. Add URL parser to client/modules/IDE/reducers/file.js Co-authored-by: Oscar Bedolla <GoodKimchi@users.noreply.github.com> Co-authored-by: Enoch Owoade <enochowoade@gmail.com>
1 parent a9076f4 commit e61bc35

File tree

3 files changed

+107
-98
lines changed

3 files changed

+107
-98
lines changed

client/modules/IDE/reducers/files.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import {
55
defaultCSS,
66
defaultHTML
77
} from '../../../../server/domain-objects/createDefaultFiles';
8+
import { parseUrlParams } from '../../../utils/parseURLParams';
89

910
export const initialState = () => {
1011
const a = objectID().toHexString();
1112
const b = objectID().toHexString();
1213
const c = objectID().toHexString();
1314
const r = objectID().toHexString();
15+
const params = parseUrlParams(window.location.href);
1416
return [
1517
{
1618
name: 'root',
@@ -32,7 +34,7 @@ export const initialState = () => {
3234
},
3335
{
3436
name: 'index.html',
35-
content: defaultHTML(),
37+
content: defaultHTML(params),
3638
id: b,
3739
_id: b,
3840
fileType: 'file',

client/utils/parseURLParams.js

Lines changed: 59 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,74 +7,81 @@ const DEFAULTS = {
77
data: false
88
};
99

10-
// One centralized parser
11-
export function parseUrlParams(url) {
12-
const params = new URLSearchParams(
13-
new URL(url, 'https://dummy.origin').search
14-
);
15-
16-
return {
17-
version: validateVersion(params.get('version')), // string
18-
sound: validateBool(params.get('sound'), DEFAULTS.sound), // bool
19-
preload: validateBool(params.get('preload'), DEFAULTS.preload), // bool
20-
shapes: validateBool(params.get('shapes'), DEFAULTS.shapes), // bool
21-
data: validateBool(params.get('data'), DEFAULTS.data) // bool
22-
// Easy to add more params here
23-
};
10+
/**
11+
* Sorts version strings in descending order and returns the highest version
12+
* @param {string[]} versions - Array of version strings (e.g., ['1.11.2', '1.11.1'])
13+
* @returns {string} The highest version from the array
14+
*/
15+
function getNewestVersion(versions) {
16+
return versions.sort((a, b) => {
17+
const pa = a.split('.').map((n) => parseInt(n, 10));
18+
const pb = b.split('.').map((n) => parseInt(n, 10));
19+
for (let i = 0; i < 3; i++) {
20+
const na = pa[i] || 0;
21+
const nb = pb[i] || 0;
22+
if (na !== nb) return nb - na;
23+
}
24+
return 0;
25+
})[0];
2426
}
2527

2628
function validateVersion(version) {
27-
if (!version) {
28-
return currentP5Version;
29+
if (!version) return currentP5Version;
30+
31+
const ver = String(version).trim();
32+
33+
if (p5Versions.includes(ver)) return ver;
34+
35+
// if only major.minor provided like "1.11"
36+
const majorMinorMatch = /^(\d+)\.(\d+)$/.exec(ver);
37+
if (majorMinorMatch) {
38+
const [, major, minor] = majorMinorMatch;
39+
const matches = p5Versions.filter((v) => {
40+
const parts = v.split('.');
41+
return parts[0] === major && parts[1] === minor;
42+
});
43+
if (matches.length) {
44+
return getNewestVersion(matches);
45+
}
2946
}
30-
const v = String(version).trim();
31-
if (v.toLowerCase() === 'latest') {
32-
const newest = getNewestVersion(p5Versions);
33-
return newest ?? currentP5Version; //The ?? operator means: “if newest is null or undefined, use currentP5Version
47+
48+
// if only major provided like "1"
49+
const majorOnlyMatch = /^(\d+)$/.exec(ver);
50+
if (majorOnlyMatch) {
51+
const [, major] = majorOnlyMatch;
52+
const matches = p5Versions.filter((v) => v.split('.')[0] === major);
53+
if (matches.length) {
54+
return getNewestVersion(matches);
55+
}
3456
}
35-
if (v.toLowerCase() === 'current') return currentP5Version;
36-
if (p5Versions.includes(v)) return v;
37-
const normalized = v.replace(/^v/i, '');
38-
if (p5Versions.includes(normalized)) return normalized;
39-
//This line strips that leading v using a regular expression ^v (meaning “v at the start”) and then rechecks.
4057

41-
// if valid return version
4258
return currentP5Version;
4359
}
4460

45-
//picks highest version number from array
46-
function getNewestVersion(list) {
47-
// Defensive copy + semver sort (major.minor.patch)
48-
const parts = (s) => s.split('.').map((n) => parseInt(n, 10) || 0);
49-
return [...list].sort((a, b) => {
50-
const [am, an, ap] = parts(a);
51-
const [bm, bn, bp] = parts(b);
52-
if (am !== bm) return bm - am;
53-
if (an !== bn) return bn - an;
54-
return bp - ap;
55-
})[0];
56-
}
57-
5861
function validateBool(value, defaultValue) {
59-
if (value) return defaultValue; // param absent
60-
//if (value === '') return true; // bare flag: ?flag
62+
if (!value) return defaultValue;
6163

6264
const v = String(value).trim().toLowerCase();
6365

64-
const TRUTHY = new Set(['on', 'true', '1', 'yes', 'y', 'enable', 'enabled']);
65-
const FALSY = new Set([
66-
'off',
67-
'false',
68-
'0',
69-
//'no',
70-
//'n',
71-
//'disable',
72-
//'disabled'
73-
]);
66+
const TRUTHY = new Set(['on', 'true', '1']);
67+
const FALSY = new Set(['off', 'false', '0']);
7468

7569
if (TRUTHY.has(v)) return true;
7670
if (FALSY.has(v)) return false;
7771

78-
return defaultValue; // unrecognized → fall back safely
72+
return defaultValue;
7973
}
8074

75+
export function parseUrlParams(url) {
76+
const params = new URLSearchParams(
77+
new URL(url, 'https://dummy.origin').search
78+
);
79+
80+
return {
81+
version: validateVersion(params.get('version')),
82+
sound: validateBool(params.get('sound'), DEFAULTS.sound),
83+
preload: validateBool(params.get('preload'), DEFAULTS.preload),
84+
shapes: validateBool(params.get('shapes'), DEFAULTS.shapes),
85+
data: validateBool(params.get('data'), DEFAULTS.data)
86+
};
87+
}
Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,51 @@
1-
import { parseUrlParams } from './parseURLParams.js';
1+
import { parseUrlParams } from './parseURLParams';
22
import { currentP5Version } from '../../common/p5Versions';
33

44
describe('parseUrlParams', () => {
5-
test('returns defaults when no params are provided', () => {
6-
const url = 'https://example.com';
7-
const result = parseUrlParams(url);
8-
9-
expect(result).toEqual({
10-
version: currentP5Version,
11-
sound: undefined,
12-
preload: undefined,
13-
shapes: undefined,
14-
data: undefined
15-
});
5+
test('returns defaults when no params are provided', () => {
6+
const url = 'https://example.com';
7+
const result = parseUrlParams(url);
8+
9+
expect(result).toEqual({
10+
version: currentP5Version,
11+
sound: true,
12+
preload: false,
13+
shapes: false,
14+
data: false
1615
});
17-
18-
test('parses a valid p5 version and falls back for invalid versions', () => {
19-
const good = parseUrlParams('https://example.com?version=1.4.0');
20-
expect(good.version).toBe('1.4.0');
21-
22-
const bad = parseUrlParams('https://example.com?version=9.9.9');
23-
expect(bad.version).toBe(currentP5Version);
16+
});
17+
18+
test('parses a valid p5 version and falls back for invalid versions', () => {
19+
const good = parseUrlParams('https://example.com?version=1.4.0');
20+
expect(good.version).toBe('1.4.0');
21+
22+
const bad = parseUrlParams('https://example.com?version=9.9.9');
23+
expect(bad.version).toBe(currentP5Version);
24+
});
25+
26+
test('parses boolean-like params for sound/preload/shapes/data (true variants)', () => {
27+
const trueVariants = ['on', 'true', '1', 'ON', 'True'];
28+
29+
trueVariants.forEach((v) => {
30+
const url = `https://example.com?sound=${v}&preload=${v}&shapes=${v}&data=${v}`;
31+
const result = parseUrlParams(url);
32+
expect(result.sound).toBe(true);
33+
expect(result.preload).toBe(true);
34+
expect(result.shapes).toBe(true);
35+
expect(result.data).toBe(true);
2436
});
25-
26-
test('parses boolean-like params for sound/preload/shapes/data (true variants)', () => {
27-
const trueVariants = ['on', 'true', '1', 'ON', 'True'];
28-
29-
trueVariants.forEach((v) => {
30-
const url = `https://example.com?sound=${v}&preload=${v}&shapes=${v}&data=${v}`;
31-
const result = parseUrlParams(url);
32-
expect(result.sound).toBe(true);
33-
expect(result.preload).toBe(true);
34-
expect(result.shapes).toBe(true);
35-
expect(result.data).toBe(true);
36-
});
37-
});
38-
39-
test('parses boolean-like params for sound/preload/shapes/data (false variants)', () => {
40-
const falseVariants = ['off', 'false', '0', 'OFF', 'False'];
41-
42-
falseVariants.forEach((v) => {
43-
const url = `https://example.com?sound=${v}&preload=${v}&shapes=${v}&data=${v}`;
44-
const result = parseUrlParams(url);
45-
expect(result.sound).toBe(false);
46-
expect(result.preload).toBe(false);
47-
expect(result.shapes).toBe(false);
48-
expect(result.data).toBe(false);
49-
});
37+
});
38+
39+
test('parses boolean-like params for sound/preload/shapes/data (false variants)', () => {
40+
const falseVariants = ['off', 'false', '0', 'OFF', 'False'];
41+
42+
falseVariants.forEach((v) => {
43+
const url = `https://example.com?sound=${v}&preload=${v}&shapes=${v}&data=${v}`;
44+
const result = parseUrlParams(url);
45+
expect(result.sound).toBe(false);
46+
expect(result.preload).toBe(false);
47+
expect(result.shapes).toBe(false);
48+
expect(result.data).toBe(false);
5049
});
51-
});
50+
});
51+
});

0 commit comments

Comments
 (0)