@schukai/monster 3.30.0 → 3.31.0

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schukai/monster",
3
- "version": "3.30.0",
3
+ "version": "3.31.0",
4
4
  "description": "Monster is a simple library for creating fast, robust and lightweight websites.",
5
5
  "keywords": [
6
6
  "framework",
@@ -0,0 +1,57 @@
1
+ export {generateRangeComparisonExpression}
2
+
3
+ /**
4
+ * Generates a comparison expression for a comma-separated string of ranges and single values.
5
+ * @param {string} expression - The string expression to generate the comparison for.
6
+ * @param {string} valueName - The name of the value to compare against.
7
+ * @param {Object} [options] - The optional parameters.
8
+ * @param {boolean} [options.urlEncode=false] - Whether to encode comparison operators for use in a URL.
9
+ * @param {string} [options.andOp='&&'] - The logical AND operator to use.
10
+ * @param {string} [options.orOp='||'] - The logical OR operator to use.
11
+ * @param {string} [options.eqOp='=='] - The comparison operator for equality to use.
12
+ * @param {string} [options.geOp='>='] - The comparison operator for greater than or equal to to use.
13
+ * @param {string} [options.leOp='<='] - The comparison operator for less than or equal to to use.
14
+ * @returns {string} The generated comparison expression.
15
+ * @throws {Error} If the input is invalid.
16
+ */
17
+ function generateRangeComparisonExpression(expression, valueName, options = {}) {
18
+ const {
19
+ urlEncode = false,
20
+ andOp = '&&',
21
+ orOp = '||',
22
+ eqOp = '==',
23
+ geOp = '>=',
24
+ leOp = '<=',
25
+ } = options;
26
+ const ranges = expression.split(',');
27
+ let comparison = '';
28
+ for (let i = 0; i < ranges.length; i++) {
29
+ const range = ranges[i].trim();
30
+ if (range === '') {
31
+ throw new Error(`Invalid range '${range}'`);
32
+ } else if (range.includes('-')) {
33
+ const [start, end] = range.split('-').map(s => (s === '' ? null : parseFloat(s)));
34
+ if ((start !== null && isNaN(start)) || (end !== null && isNaN(end))) {
35
+ throw new Error(`Invalid value in range '${range}'`);
36
+ }
37
+ if (start !== null && end !== null && start > end) {
38
+ throw new Error(`Invalid range '${range}'`);
39
+ }
40
+ const compStart = start !== null ? `${valueName}${urlEncode ? encodeURIComponent(geOp) : geOp}${start}` : '';
41
+ const compEnd = end !== null ? `${valueName}${urlEncode ? encodeURIComponent(leOp) : leOp}${end}` : '';
42
+ const compRange = `${compStart}${compStart && compEnd ? ` ${andOp} ` : ''}${compEnd}`;
43
+ comparison += ranges.length > 1 ? `(${compRange})` : compRange;
44
+ } else {
45
+ const value = parseFloat(range);
46
+ if (isNaN(value)) {
47
+ throw new Error(`Invalid value '${range}'`);
48
+ }
49
+ const compValue = `${valueName}${urlEncode ? encodeURIComponent(eqOp) : eqOp}${value}`;
50
+ comparison += ranges.length > 1 ? `(${compValue})` : compValue;
51
+ }
52
+ if (i < ranges.length - 1) {
53
+ comparison += ` ${orOp} `;
54
+ }
55
+ }
56
+ return comparison;
57
+ }
@@ -142,7 +142,7 @@ function getMonsterVersion() {
142
142
  }
143
143
 
144
144
  /** don't touch, replaced by make with package.json version */
145
- monsterVersion = new Version("3.30.0");
145
+ monsterVersion = new Version("3.31.0");
146
146
 
147
147
  return monsterVersion;
148
148
  }
@@ -7,7 +7,7 @@ describe('Monster', function () {
7
7
  let monsterVersion
8
8
 
9
9
  /** don´t touch, replaced by make with package.json version */
10
- monsterVersion = new Version("3.30.0")
10
+ monsterVersion = new Version("3.31.0")
11
11
 
12
12
  let m = getMonsterVersion();
13
13
 
@@ -204,4 +204,84 @@ describe('Formatter', function () {
204
204
  });
205
205
 
206
206
 
207
+
208
+
209
+ describe('Formatter', () => {
210
+ it('should format a basic string with object values', () => {
211
+ const formatter = new Formatter({name: 'John', age: 30});
212
+ const result = formatter.format('My name is ${name} and I am ${age | tostring} years old.');
213
+
214
+ expect(result).to.equal('My name is John and I am 30 years old.');
215
+ });
216
+
217
+ it('should format a string with nested markers', () => {
218
+ const text = '${mykey${subkey}}';
219
+ const obj = {mykey2: '1', subkey: '2'};
220
+ const formatter = new Formatter(obj);
221
+
222
+ expect(formatter.format(text)).to.equal('1');
223
+ });
224
+
225
+ it('should format a string with custom markers', () => {
226
+ const formatter = new Formatter({name: 'John', age: 30});
227
+ formatter.setMarker('[', ']');
228
+ const result = formatter.format('My name is [name] and I am [age | tostring] years old.');
229
+
230
+ expect(result).to.equal('My name is John and I am 30 years old.');
231
+ });
232
+
233
+ it('should format a string using callback', () => {
234
+ const formatter = new Formatter({x: '1'}, {
235
+ callbacks: {
236
+ quote: (value) => {
237
+ return '"' + value + '"';
238
+ },
239
+ },
240
+ });
241
+
242
+ expect(formatter.format('${x | call:quote}')).to.equal('"1"');
243
+ });
244
+
245
+ it('should format a string with parameters', () => {
246
+ const obj = {
247
+ a: {
248
+ b: {
249
+ c: 'Hello',
250
+ },
251
+ d: 'world',
252
+ },
253
+ };
254
+ const formatter = new Formatter(obj);
255
+ const result = formatter.format('${a.b.c} ${a.d | ucfirst}!');
256
+
257
+ expect(result).to.equal('Hello World!');
258
+ });
259
+
260
+ it('should throw a too deep nesting error', () => {
261
+ const formatter = new Formatter({name: 'John'});
262
+ const nestedText = '${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name}}}}}}}}}}}}}}}}}}';
263
+ expect(() => formatter.format(nestedText)).to.throw('syntax error in formatter template');
264
+ });
265
+
266
+ it('should throw a too deep nesting error', () => {
267
+ const inputObj = {
268
+ mykey: '${mykey}',
269
+ };
270
+
271
+ const formatter = new Formatter(inputObj);
272
+
273
+ const text = '${mykey}';
274
+ let formattedText = text;
275
+
276
+ // Create a string with 21 levels of nesting
277
+ for (let i = 0; i < 21; i++) {
278
+ formattedText = '${' + formattedText + '}';
279
+ }
280
+
281
+ expect(() => formatter.format(formattedText)).to.throw('too deep nesting');
282
+ });
283
+
284
+ });
285
+
286
+
207
287
  });
@@ -0,0 +1,109 @@
1
+ import {expect} from "chai"
2
+ import {generateRangeComparisonExpression} from "../../../../application/source/text/util.mjs";
3
+
4
+ describe('generateRangeComparisonExpression', () => {
5
+ it('should generate correct comparison expression for single values', () => {
6
+ const expression = '1,3,5';
7
+ const valueName = 'x';
8
+ const result = generateRangeComparisonExpression(expression, valueName);
9
+ expect(result).to.equal('(x==1) || (x==3) || (x==5)');
10
+ });
11
+
12
+ it('should generate correct comparison expression for ranges', () => {
13
+ const expression = '1-3,6-8';
14
+ const valueName = 'x';
15
+ const result = generateRangeComparisonExpression(expression, valueName);
16
+ expect(result).to.equal('(x>=1 && x<=3) || (x>=6 && x<=8)');
17
+ });
18
+
19
+ it('should generate correct comparison expression for mixed ranges and single values', () => {
20
+ const expression = '1-3,5,7-9';
21
+ const valueName = 'x';
22
+ const result = generateRangeComparisonExpression(expression, valueName);
23
+ expect(result).to.equal('(x>=1 && x<=3) || (x==5) || (x>=7 && x<=9)');
24
+ });
25
+
26
+ it('should throw an error for invalid range', () => {
27
+ const expression = '1-3,5-4';
28
+ const valueName = 'x';
29
+ expect(() => generateRangeComparisonExpression(expression, valueName)).to.throw(`Invalid range '5-4'`);
30
+ });
31
+
32
+
33
+ it('should throw an error for invalid value', () => {
34
+ const expression = '1-3,a';
35
+ const valueName = 'x';
36
+ expect(() => generateRangeComparisonExpression(expression, valueName)).to.throw('Invalid value');
37
+ });
38
+
39
+ it('should generate correct comparison expression with custom operators', () => {
40
+ const expression = '1-3,5';
41
+ const valueName = 'x';
42
+ const options = {
43
+ andOp: 'AND',
44
+ orOp: 'OR',
45
+ eqOp: '===',
46
+ geOp: '>=',
47
+ leOp: '<=',
48
+ };
49
+ const result = generateRangeComparisonExpression(expression, valueName, options);
50
+ expect(result).to.equal('(x>=1 AND x<=3) OR (x===5)');
51
+ });
52
+
53
+ it('should generate correct comparison expression with urlEncode option', () => {
54
+ const testCases = [
55
+ {
56
+ expression: '1,3,5',
57
+ valueName: 'x',
58
+ expected: '(x%3D%3D1) || (x%3D%3D3) || (x%3D%3D5)',
59
+ },
60
+ {
61
+ expression: '-10',
62
+ valueName: 'x',
63
+ expected: 'x%3C%3D10',
64
+ },
65
+ {
66
+ expression: '10-',
67
+ valueName: 'x',
68
+ expected: 'x%3E%3D10',
69
+ },
70
+ {
71
+ expression: '1-3,6-8',
72
+ valueName: 'y',
73
+ expected: '(y%3E%3D1 && y%3C%3D3) || (y%3E%3D6 && y%3C%3D8)',
74
+ },
75
+ {
76
+ expression: '1-3,5,7-9',
77
+ valueName: 'z',
78
+ expected: '(z%3E%3D1 && z%3C%3D3) || (z%3D%3D5) || (z%3E%3D7 && z%3C%3D9)',
79
+ },
80
+ ];
81
+
82
+ testCases.forEach(({expression, valueName, expected}) => {
83
+ const result = generateRangeComparisonExpression(expression, valueName, {urlEncode: true});
84
+ expect(result).to.equal(expected);
85
+ });
86
+ });
87
+
88
+ it('should generate correct comparison expression for open-ended ranges with urlEncode option', () => {
89
+ const testCases = [
90
+ {
91
+ expression: '10-',
92
+ valueName: 'x',
93
+ expected: 'x%3E%3D10',
94
+ },
95
+ {
96
+ expression: '-10',
97
+ valueName: 'y',
98
+ expected: 'y%3C%3D10',
99
+ },
100
+ ];
101
+
102
+ testCases.forEach(({expression, valueName, expected}) => {
103
+ const result = generateRangeComparisonExpression(expression, valueName, {urlEncode: true});
104
+ expect(result).to.equal(expected);
105
+ });
106
+ });
107
+
108
+
109
+ });