feat(planning): grille hebdomadaire complète avec API et filtres

- Connexion API via proxy Angular (résolution CORS, base path /api)
- Import CSS ng-zorro global pour les modales et composants
- Filtres Camion/Show câblés sur l'affichage de la grille
- Camions affichés via TrucksService (linkés au show du même créneau)
- Panneau de détails : spectacles + camions du jour sélectionné
- Modale de création de spectacle stylisée avec fond et centrage
- Positionnement précis des events à la minute dans leur créneau
- Auto-scroll vers l'heure courante au chargement
- Ligne "maintenant" sur la colonne du jour actuel
- Régénération des services OpenAPI (nouveaux noms de types)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 20:36:03 +02:00
parent 150b97cd2e
commit 654b297e2e
3131 changed files with 149304 additions and 104334 deletions
+13 -440
View File
@@ -210,21 +210,6 @@ test('parse()', function (t) {
t.test('uses original key when depth = 0', function (st) {
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { depth: 0 }), { 'a[0]': 'b', 'a[1]': 'c' });
st.deepEqual(qs.parse('a[0][0]=b&a[0][1]=c&a[1]=d&e=2', { depth: 0 }), { 'a[0][0]': 'b', 'a[0][1]': 'c', 'a[1]': 'd', e: '2' });
st.deepEqual(qs.parse('a.b=c', { depth: 0, allowDots: true }), { 'a[b]': 'c' }, 'normalizes dots before applying depth-0 behavior');
st.deepEqual(qs.parse('toString=foo', { depth: 0 }), {}, 'respects prototype guard at depth 0');
st.deepEqual(qs.parse('toString=foo', { depth: 0, allowPrototypes: true }), { toString: 'foo' }, 'allows prototypes at depth 0 when enabled');
st.end();
});
t.test('ignores prototype keys when depth = 0 and allowPrototypes is false', function (st) {
st.deepEqual(qs.parse('toString=foo', { depth: 0 }), {});
st.deepEqual(qs.parse('hasOwnProperty=bar', { depth: 0 }), {});
st.deepEqual(qs.parse('toString=foo&a=b', { depth: 0 }), { a: 'b' });
st.end();
});
t.test('allows prototype keys when depth = 0 and allowPrototypes is true', function (st) {
st.deepEqual(qs.parse('toString=foo', { depth: 0, allowPrototypes: true }), { toString: 'foo' });
st.end();
});
@@ -250,11 +235,11 @@ test('parse()', function (t) {
st.deepEqual(qs.parse('a=b&a[0]=c'), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a[1]=b&a=c', { arrayLimit: 20 }), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a[]=b&a=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } });
st.deepEqual(qs.parse('a[]=b&a=c', { arrayLimit: 0 }), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a[]=b&a=c'), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a=b&a[1]=c', { arrayLimit: 20 }), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a=b&a[]=c', { arrayLimit: 0 }), { a: { 0: 'b', 1: 'c' } });
st.deepEqual(qs.parse('a=b&a[]=c', { arrayLimit: 0 }), { a: ['b', 'c'] });
st.deepEqual(qs.parse('a=b&a[]=c'), { a: ['b', 'c'] });
st.end();
@@ -266,52 +251,6 @@ test('parse()', function (t) {
st.end();
});
t.test('parses keys with literal [] inside a bracket group (#493)', function (st) {
// A bracket pair inside a bracket group should be treated literally as part of the key
st.deepEqual(
qs.parse('search[withbracket[]]=foobar'),
{ search: { 'withbracket[]': 'foobar' } },
'treats inner [] literally when inside a bracket group'
);
// Single-level variant
st.deepEqual(
qs.parse('a[b[]]=c'),
{ a: { 'b[]': 'c' } },
'keeps "b[]" as a literal key'
);
// Nested with an array push on the outer level
st.deepEqual(
qs.parse('list[][x[]]=y'),
{ list: [{ 'x[]': 'y' }] },
'preserves inner [] while still treating outer [] as array push'
);
// Multiple nested bracket pairs: inner [] remains literal as part of the key
st.deepEqual(
qs.parse('a[b[c[]]]=d'),
{ a: { 'b[c[]]': 'd' } },
'treats "b[c[]]" as a literal key inside the bracket group'
);
// Depth limits with literal brackets: preserve inner [] while limiting bracket-group parsing
st.deepEqual(
qs.parse('a[b[c[]]][d]=e', { depth: 1 }),
{ a: { 'b[c[]]': { '[d]': 'e' } } },
'respects depth: 1 and preserves literal inner [] in the parsed key'
);
// Unterminated inner bracket group is wrapped as a literal remainder segment
st.deepEqual(
qs.parse('a[[]b=c'),
{ a: { '[[]b': 'c' } },
'handles unterminated inner bracket groups without throwing'
);
st.end();
});
t.test('allows to specify array indices', function (st) {
st.deepEqual(qs.parse('a[1]=c&a[0]=b&a[2]=d'), { a: ['b', 'c', 'd'] });
st.deepEqual(qs.parse('a[1]=c&a[0]=b'), { a: ['b', 'c'] });
@@ -322,11 +261,11 @@ test('parse()', function (t) {
});
t.test('limits specific array indices to arrayLimit', function (st) {
st.deepEqual(qs.parse('a[19]=a', { arrayLimit: 20 }), { a: ['a'] });
st.deepEqual(qs.parse('a[20]=a', { arrayLimit: 20 }), { a: { 20: 'a' } });
st.deepEqual(qs.parse('a[20]=a', { arrayLimit: 20 }), { a: ['a'] });
st.deepEqual(qs.parse('a[21]=a', { arrayLimit: 20 }), { a: { 21: 'a' } });
st.deepEqual(qs.parse('a[19]=a'), { a: ['a'] });
st.deepEqual(qs.parse('a[20]=a'), { a: { 20: 'a' } });
st.deepEqual(qs.parse('a[20]=a'), { a: ['a'] });
st.deepEqual(qs.parse('a[21]=a'), { a: { 21: 'a' } });
st.end();
});
@@ -425,7 +364,7 @@ test('parse()', function (t) {
);
st.deepEqual(
qs.parse('a[]=b&a[]&a[]=c&a[]=', { strictNullHandling: true, arrayLimit: 0 }),
{ a: { 0: 'b', 1: null, 2: 'c', 3: '' } },
{ a: ['b', null, 'c', ''] },
'with arrayLimit 0 + array brackets: null then empty string works'
);
@@ -436,7 +375,7 @@ test('parse()', function (t) {
);
st.deepEqual(
qs.parse('a[]=b&a[]=&a[]=c&a[]', { strictNullHandling: true, arrayLimit: 0 }),
{ a: { 0: 'b', 1: '', 2: 'c', 3: null } },
{ a: ['b', '', 'c', null] },
'with arrayLimit 0 + array brackets: empty string then null works'
);
@@ -544,7 +483,7 @@ test('parse()', function (t) {
t.test('allows overriding array limit', function (st) {
st.deepEqual(qs.parse('a[0]=b', { arrayLimit: -1 }), { a: { 0: 'b' } });
st.deepEqual(qs.parse('a[0]=b', { arrayLimit: 0 }), { a: { 0: 'b' } });
st.deepEqual(qs.parse('a[0]=b', { arrayLimit: 0 }), { a: ['b'] });
st.deepEqual(qs.parse('a[-1]=b', { arrayLimit: -1 }), { a: { '-1': 'b' } });
st.deepEqual(qs.parse('a[-1]=b', { arrayLimit: 0 }), { a: { '-1': 'b' } });
@@ -845,25 +784,25 @@ test('parse()', function (t) {
t.test('add keys to objects', function (st) {
st.deepEqual(
qs.parse('a[b]=c&a=d', { strictMerge: false }),
qs.parse('a[b]=c&a=d'),
{ a: { b: 'c', d: true } },
'can add keys to objects'
);
st.deepEqual(
qs.parse('a[b]=c&a=toString', { strictMerge: false }),
qs.parse('a[b]=c&a=toString'),
{ a: { b: 'c' } },
'can not overwrite prototype'
);
st.deepEqual(
qs.parse('a[b]=c&a=toString', { strictMerge: false, allowPrototypes: true }),
qs.parse('a[b]=c&a=toString', { allowPrototypes: true }),
{ a: { b: 'c', toString: true } },
'can overwrite prototype with allowPrototypes true'
);
st.deepEqual(
qs.parse('a[b]=c&a=toString', { strictMerge: false, plainObjects: true }),
qs.parse('a[b]=c&a=toString', { plainObjects: true }),
{ __proto__: null, a: { __proto__: null, b: 'c', toString: true } },
'can overwrite prototype with plainObjects true'
);
@@ -871,34 +810,6 @@ test('parse()', function (t) {
st.end();
});
t.test('strictMerge wraps object and primitive into an array', function (st) {
st.deepEqual(
qs.parse('a[b]=c&a=d'),
{ a: [{ b: 'c' }, 'd'] },
'object then primitive produces array'
);
st.deepEqual(
qs.parse('a=d&a[b]=c'),
{ a: ['d', { b: 'c' }] },
'primitive then object produces array'
);
st.deepEqual(
qs.parse('a[b]=c&a=toString'),
{ a: [{ b: 'c' }, 'toString'] },
'prototype-colliding value is preserved in array'
);
st.deepEqual(
qs.parse('a[b]=c&a=toString', { plainObjects: true }),
{ __proto__: null, a: [{ __proto__: null, b: 'c' }, 'toString'] },
'plainObjects preserved in array wrapping'
);
st.end();
});
t.test('dunder proto is ignored', function (st) {
var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
var result = qs.parse(payload, { allowPrototypes: true });
@@ -1085,20 +996,6 @@ test('parse()', function (t) {
st.end();
});
t.test('handles a custom decoder returning `null`, with a string key of `null`', function (st) {
st.deepEqual(
qs.parse('null=1&ToNull=2', {
decoder: function (str, defaultDecoder, charset) {
return str === 'ToNull' ? null : defaultDecoder(str, defaultDecoder, charset);
}
}),
{ 'null': '1' },
'"null" key is not overridden by `null` decoder result'
);
st.end();
});
t.test('does not interpret numeric entities in iso-8859-1 when `interpretNumericEntities` is absent', function (st) {
st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'iso-8859-1' }), { foo: '&#9786;' });
st.end();
@@ -1135,15 +1032,6 @@ test('parse()', function (t) {
};
st.deepEqual(qs.parse('KeY=vAlUe', { decoder: decoder }), { key: 'VALUE' });
var noopDecoder = function () { return 'x'; };
noopDecoder();
st['throws'](
function () { decoder('x', noopDecoder, 'utf-8', 'unknown'); },
'this should never happen! type: unknown',
'decoder throws for unexpected type'
);
st.end();
});
@@ -1173,14 +1061,6 @@ test('parse()', function (t) {
new RangeError('Parameter limit exceeded. Only 3 parameters allowed.'),
'throws error when parameter limit is exceeded'
);
sst['throws'](
function () {
qs.parse('a=1&b=2', { parameterLimit: 1, throwOnLimitExceeded: true });
},
new RangeError('Parameter limit exceeded. Only 1 parameter allowed.'),
'throws error with singular "parameter" when parameterLimit is 1'
);
sst.end();
});
@@ -1202,12 +1082,6 @@ test('parse()', function (t) {
sst.end();
});
st.test('allows unlimited parameters when parameterLimit is Infinity and throwOnLimitExceeded is true', function (sst) {
var result = qs.parse('a=1&b=2&c=3&d=4&e=5&f=6', { parameterLimit: Infinity, throwOnLimitExceeded: true });
sst.deepEqual(result, { a: '1', b: '2', c: '3', d: '4', e: '5', f: '6' }, 'parses all parameters without truncation or throwing');
sst.end();
});
st.end();
});
@@ -1230,7 +1104,6 @@ test('parse()', function (t) {
});
st.test('throws error when array limit exceeded', function (sst) {
// 4 elements exceeds limit of 3
sst['throws'](
function () {
qs.parse('a[]=1&a[]=2&a[]=3&a[]=4', { arrayLimit: 3, throwOnLimitExceeded: true });
@@ -1241,14 +1114,6 @@ test('parse()', function (t) {
sst.end();
});
st.test('does not throw when at limit', function (sst) {
// 3 elements = limit of 3, should not throw
var result = qs.parse('a[]=1&a[]=2&a[]=3', { arrayLimit: 3, throwOnLimitExceeded: true });
sst.ok(Array.isArray(result.a), 'result is an array');
sst.deepEqual(result.a, ['1', '2', '3'], 'all values present');
sst.end();
});
st.test('converts array to object if length is greater than limit', function (sst) {
var result = qs.parse('a[1]=1&a[2]=2&a[3]=3&a[4]=4&a[5]=5&a[6]=6', { arrayLimit: 5 });
@@ -1256,59 +1121,6 @@ test('parse()', function (t) {
sst.end();
});
st.test('throws error when indexed notation exceeds arrayLimit with throwOnLimitExceeded', function (sst) {
sst['throws'](
function () {
qs.parse('a[1001]=b', { arrayLimit: 1000, throwOnLimitExceeded: true });
},
new RangeError('Array limit exceeded. Only 1000 elements allowed in an array.'),
'throws error for a single index exceeding arrayLimit'
);
sst['throws'](
function () {
qs.parse('a[0]=1&a[1]=2&a[2]=3&a[10]=4', { arrayLimit: 6, throwOnLimitExceeded: true, allowSparse: true });
},
new RangeError('Array limit exceeded. Only 6 elements allowed in an array.'),
'throws error when a sparse index exceeds arrayLimit'
);
sst['throws'](
function () {
qs.parse('a[2]=b', { arrayLimit: 1, throwOnLimitExceeded: true });
},
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
'throws error with singular "element" when arrayLimit is 1'
);
sst.end();
});
st.test('does not throw for indexed notation within arrayLimit with throwOnLimitExceeded', function (sst) {
var result = qs.parse('a[4]=b', { arrayLimit: 5, throwOnLimitExceeded: true, allowSparse: true });
sst.ok(Array.isArray(result.a), 'result is an array');
sst.equal(result.a.length, 5, 'array has correct length');
sst.equal(result.a[4], 'b', 'value at index 4 is correct');
sst.end();
});
st.test('silently converts to object for indexed notation exceeding arrayLimit without throwOnLimitExceeded', function (sst) {
var result = qs.parse('a[1001]=b', { arrayLimit: 1000 });
sst.deepEqual(result, { a: { 1001: 'b' } }, 'converts to object without throwing');
sst.end();
});
st.test('throws when duplicate bracket keys exceed arrayLimit with throwOnLimitExceeded', function (sst) {
sst['throws'](
function () {
qs.parse('a[]=1&a[]=2&a[]=3&a[]=4&a[]=5&a[]=6', { arrayLimit: 5, throwOnLimitExceeded: true });
},
new RangeError('Array limit exceeded. Only 5 elements allowed in an array.'),
'throws error when duplicate bracket notation exceeds array limit'
);
sst.end();
});
st.end();
});
@@ -1360,34 +1172,6 @@ test('`duplicates` option', function (t) {
'duplicates: last'
);
t.test('bracket notation always combines regardless of duplicates', function (st) {
st.deepEqual(
qs.parse('a=1&a=2&b[]=1&b[]=2', { duplicates: 'last' }),
{ a: '2', b: ['1', '2'] },
'duplicates last: unbracketed takes last, bracketed combines'
);
st.deepEqual(
qs.parse('b[]=1&b[]=2', { duplicates: 'last' }),
{ b: ['1', '2'] },
'duplicates last: bracketed always combines'
);
st.deepEqual(
qs.parse('b[]=1&b[]=2', { duplicates: 'first' }),
{ b: ['1', '2'] },
'duplicates first: bracketed always combines'
);
st.deepEqual(
qs.parse('a=1&a=2&b[]=1&b[]=2', { duplicates: 'first' }),
{ a: '1', b: ['1', '2'] },
'duplicates first: unbracketed takes first, bracketed combines'
);
st.end();
});
t.end();
});
@@ -1490,214 +1274,3 @@ test('qs strictDepth option - non-throw cases', function (t) {
st.end();
});
});
test('DOS', function (t) {
var arr = [];
for (var i = 0; i < 105; i++) {
arr[arr.length] = 'x';
}
var attack = 'a[]=' + arr.join('&a[]=');
var result = qs.parse(attack, { arrayLimit: 100 });
t.notOk(Array.isArray(result.a), 'arrayLimit is respected: result is an object, not an array');
t.equal(Object.keys(result.a).length, 105, 'all values are preserved');
t.end();
});
test('arrayLimit boundary conditions', function (t) {
// arrayLimit is the max number of elements allowed in an array
t.test('exactly at the limit stays as array', function (st) {
// 3 elements = limit of 3
var result = qs.parse('a[]=1&a[]=2&a[]=3', { arrayLimit: 3 });
st.ok(Array.isArray(result.a), 'result is an array when count equals limit');
st.deepEqual(result.a, ['1', '2', '3'], 'all values present');
st.end();
});
t.test('one over the limit converts to object', function (st) {
// 4 elements exceeds limit of 3
var result = qs.parse('a[]=1&a[]=2&a[]=3&a[]=4', { arrayLimit: 3 });
st.notOk(Array.isArray(result.a), 'result is not an array when over limit');
st.deepEqual(result.a, { 0: '1', 1: '2', 2: '3', 3: '4' }, 'all values preserved as object');
st.end();
});
t.test('arrayLimit 1 with one value', function (st) {
// 1 element = limit of 1
var result = qs.parse('a[]=1', { arrayLimit: 1 });
st.ok(Array.isArray(result.a), 'result is an array when count equals limit');
st.deepEqual(result.a, ['1'], 'value preserved as array');
st.end();
});
t.test('arrayLimit 1 with two values converts to object', function (st) {
// 2 elements exceeds limit of 1
var result = qs.parse('a[]=1&a[]=2', { arrayLimit: 1 });
st.notOk(Array.isArray(result.a), 'result is not an array');
st.deepEqual(result.a, { 0: '1', 1: '2' }, 'all values preserved as object');
st.end();
});
t.end();
});
test('comma + arrayLimit', function (t) {
t.test('comma-separated values within arrayLimit stay as array', function (st) {
var result = qs.parse('a=1,2,3', { comma: true, arrayLimit: 5 });
st.ok(Array.isArray(result.a), 'result is an array');
st.deepEqual(result.a, ['1', '2', '3'], 'all values present');
st.end();
});
t.test('comma-separated values exceeding arrayLimit convert to object', function (st) {
var result = qs.parse('a=1,2,3,4', { comma: true, arrayLimit: 3 });
st.notOk(Array.isArray(result.a), 'result is not an array when over limit');
st.deepEqual(result.a, { 0: '1', 1: '2', 2: '3', 3: '4' }, 'all values preserved as object');
st.end();
});
t.test('comma-separated values exceeding arrayLimit with throwOnLimitExceeded throws', function (st) {
st['throws'](
function () {
qs.parse('a=1,2,3,4', { comma: true, arrayLimit: 3, throwOnLimitExceeded: true });
},
new RangeError('Array limit exceeded. Only 3 elements allowed in an array.'),
'throws error when comma-split exceeds array limit'
);
st['throws'](
function () {
qs.parse('a=1,2,3', { comma: true, arrayLimit: 1, throwOnLimitExceeded: true });
},
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
'throws error with singular "element" when arrayLimit is 1'
);
st.end();
});
t.test('comma-separated values at exactly arrayLimit stay as array', function (st) {
var result = qs.parse('a=1,2,3', { comma: true, arrayLimit: 3 });
st.ok(Array.isArray(result.a), 'result is an array when exactly at limit');
st.deepEqual(result.a, ['1', '2', '3'], 'all values present');
st.end();
});
t.end();
});
test('mixed array and object notation', function (t) {
t.test('array brackets with object key - under limit', function (st) {
st.deepEqual(
qs.parse('a[]=b&a[c]=d'),
{ a: { 0: 'b', c: 'd' } },
'mixing [] and [key] converts to object'
);
st.end();
});
t.test('array index with object key - under limit', function (st) {
st.deepEqual(
qs.parse('a[0]=b&a[c]=d'),
{ a: { 0: 'b', c: 'd' } },
'mixing [0] and [key] produces object'
);
st.end();
});
t.test('plain value with array brackets - under limit', function (st) {
st.deepEqual(
qs.parse('a=b&a[]=c', { arrayLimit: 20 }),
{ a: ['b', 'c'] },
'plain value combined with [] stays as array under limit'
);
st.end();
});
t.test('array brackets with plain value - under limit', function (st) {
st.deepEqual(
qs.parse('a[]=b&a=c', { arrayLimit: 20 }),
{ a: ['b', 'c'] },
'[] combined with plain value stays as array under limit'
);
st.end();
});
t.test('plain value with array index - under limit', function (st) {
st.deepEqual(
qs.parse('a=b&a[0]=c', { arrayLimit: 20 }),
{ a: ['b', 'c'] },
'plain value combined with [0] stays as array under limit'
);
st.end();
});
t.test('multiple plain values with duplicates combine', function (st) {
st.deepEqual(
qs.parse('a=b&a=c&a=d', { arrayLimit: 20 }),
{ a: ['b', 'c', 'd'] },
'duplicate plain keys combine into array'
);
st.end();
});
t.test('multiple plain values exceeding limit', function (st) {
// 3 elements (indices 0-2), max index 2 > limit 1
st.deepEqual(
qs.parse('a=b&a=c&a=d', { arrayLimit: 1 }),
{ a: { 0: 'b', 1: 'c', 2: 'd' } },
'duplicate plain keys convert to object when exceeding limit'
);
st.end();
});
t.test('mixed notation produces consistent results when arrayLimit is exceeded', function (st) {
var expected = { a: { 0: 'b', 1: 'c', 2: 'd' } };
st.deepEqual(
qs.parse('a[]=b&a[1]=c&a=d', { arrayLimit: -1 }),
expected,
'arrayLimit -1'
);
st.deepEqual(
qs.parse('a[]=b&a[1]=c&a=d', { arrayLimit: 0 }),
expected,
'arrayLimit 0'
);
st.deepEqual(
qs.parse('a[]=b&a[1]=c&a=d', { arrayLimit: 1 }),
expected,
'arrayLimit 1'
);
st.end();
});
t.test('uses existing array length for currentArrayLength when parsing object input with bracket keys', function (st) {
var input = {};
var arr = ['x', 'y'];
arr.a = ['z', 'w'];
input['a[]'] = arr;
st.deepEqual(qs.parse(input), { a: ['x', 'y'] }, 'parses object input with bracket keys using existing array values');
st.end();
});
t.test('throws with singular message when object input bracket key exceeds arrayLimit of 1', function (st) {
var input = {};
var arr = ['x'];
arr.a = ['z', 'w'];
input['a[]'] = arr;
st['throws'](
function () {
qs.parse(input, { throwOnLimitExceeded: true, arrayLimit: 1 });
},
new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
'throws singular error for object input exceeding arrayLimit 1'
);
st.end();
});
t.end();
});