@osimatic/helpers-js 1.4.24 → 1.4.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/chartjs.js +1 -1
  3. package/date_time.js +25 -16
  4. package/draw.js +3 -2
  5. package/duration.js +12 -15
  6. package/event_bus.js +2 -2
  7. package/file.js +1 -1
  8. package/form_helper.js +1 -1
  9. package/http_client.js +2 -0
  10. package/jwt.js +18 -6
  11. package/media.js +1 -1
  12. package/number.js +2 -3
  13. package/package.json +3 -2
  14. package/social_network.js +5 -0
  15. package/string.js +11 -2
  16. package/tests/chartjs.test.js +273 -0
  17. package/tests/date_time/DatePeriod.test.js +179 -0
  18. package/tests/date_time/DateTime.test.js +492 -0
  19. package/tests/date_time/SqlDate.test.js +205 -0
  20. package/tests/date_time/SqlDateTime.test.js +326 -0
  21. package/tests/date_time/SqlTime.test.js +162 -0
  22. package/tests/date_time/TimestampUnix.test.js +262 -0
  23. package/tests/draw.test.js +271 -0
  24. package/tests/duration.test.js +365 -0
  25. package/tests/event_bus.test.js +268 -0
  26. package/tests/file.test.js +358 -0
  27. package/tests/form_date.test.js +417 -0
  28. package/tests/form_helper.test.js +415 -0
  29. package/tests/http_client.test.js +570 -0
  30. package/tests/jwt.test.js +804 -0
  31. package/tests/media.test.js +458 -0
  32. package/tests/network.test.js +489 -0
  33. package/tests/number.test.js +448 -0
  34. package/tests/open_street_map.test.js +388 -0
  35. package/tests/shopping_cart.test.js +355 -0
  36. package/tests/social_network.test.js +333 -0
  37. package/tests/string.test.js +473 -0
  38. package/tests/user.test.js +204 -0
  39. package/tests/util.test.js +99 -0
  40. package/tests/visitor.test.js +508 -0
  41. package/tmpclaude-00a6-cwd +1 -0
  42. package/tmpclaude-0526-cwd +1 -0
  43. package/tmpclaude-0973-cwd +1 -0
  44. package/tmpclaude-0b61-cwd +1 -0
  45. package/tmpclaude-146f-cwd +1 -0
  46. package/tmpclaude-223d-cwd +1 -0
  47. package/tmpclaude-2330-cwd +1 -0
  48. package/tmpclaude-282a-cwd +1 -0
  49. package/tmpclaude-2846-cwd +1 -0
  50. package/tmpclaude-28a6-cwd +1 -0
  51. package/tmpclaude-2b5a-cwd +1 -0
  52. package/tmpclaude-2def-cwd +1 -0
  53. package/tmpclaude-3906-cwd +1 -0
  54. package/tmpclaude-3b32-cwd +1 -0
  55. package/tmpclaude-3da9-cwd +1 -0
  56. package/tmpclaude-3dc3-cwd +1 -0
  57. package/tmpclaude-3e3b-cwd +1 -0
  58. package/tmpclaude-43b6-cwd +1 -0
  59. package/tmpclaude-4495-cwd +1 -0
  60. package/tmpclaude-462f-cwd +1 -0
  61. package/tmpclaude-4b29-cwd +1 -0
  62. package/tmpclaude-4db5-cwd +1 -0
  63. package/tmpclaude-4e01-cwd +1 -0
  64. package/tmpclaude-5101-cwd +1 -0
  65. package/tmpclaude-524f-cwd +1 -0
  66. package/tmpclaude-5636-cwd +1 -0
  67. package/tmpclaude-5cdd-cwd +1 -0
  68. package/tmpclaude-5f1f-cwd +1 -0
  69. package/tmpclaude-6078-cwd +1 -0
  70. package/tmpclaude-622e-cwd +1 -0
  71. package/tmpclaude-6802-cwd +1 -0
  72. package/tmpclaude-6e36-cwd +1 -0
  73. package/tmpclaude-7793-cwd +1 -0
  74. package/tmpclaude-7f96-cwd +1 -0
  75. package/tmpclaude-8566-cwd +1 -0
  76. package/tmpclaude-8874-cwd +1 -0
  77. package/tmpclaude-8915-cwd +1 -0
  78. package/tmpclaude-8c8b-cwd +1 -0
  79. package/tmpclaude-94df-cwd +1 -0
  80. package/tmpclaude-9859-cwd +1 -0
  81. package/tmpclaude-9ac5-cwd +1 -0
  82. package/tmpclaude-9f18-cwd +1 -0
  83. package/tmpclaude-a202-cwd +1 -0
  84. package/tmpclaude-a741-cwd +1 -0
  85. package/tmpclaude-ab5f-cwd +1 -0
  86. package/tmpclaude-b008-cwd +1 -0
  87. package/tmpclaude-b0a1-cwd +1 -0
  88. package/tmpclaude-b63d-cwd +1 -0
  89. package/tmpclaude-b681-cwd +1 -0
  90. package/tmpclaude-b72d-cwd +1 -0
  91. package/tmpclaude-b92f-cwd +1 -0
  92. package/tmpclaude-bc49-cwd +1 -0
  93. package/tmpclaude-bc50-cwd +1 -0
  94. package/tmpclaude-bccf-cwd +1 -0
  95. package/tmpclaude-be55-cwd +1 -0
  96. package/tmpclaude-c228-cwd +1 -0
  97. package/tmpclaude-c717-cwd +1 -0
  98. package/tmpclaude-c7ce-cwd +1 -0
  99. package/tmpclaude-cf3e-cwd +1 -0
  100. package/tmpclaude-d142-cwd +1 -0
  101. package/tmpclaude-d5bc-cwd +1 -0
  102. package/tmpclaude-d6ae-cwd +1 -0
  103. package/tmpclaude-d77a-cwd +1 -0
  104. package/tmpclaude-d8da-cwd +1 -0
  105. package/tmpclaude-dbdb-cwd +1 -0
  106. package/tmpclaude-de61-cwd +1 -0
  107. package/tmpclaude-de81-cwd +1 -0
  108. package/tmpclaude-df9d-cwd +1 -0
  109. package/tmpclaude-e786-cwd +1 -0
  110. package/tmpclaude-f01d-cwd +1 -0
  111. package/tmpclaude-f2a9-cwd +1 -0
  112. package/tmpclaude-fc36-cwd +1 -0
  113. package/tmpclaude-ffef-cwd +1 -0
  114. package/visitor.js +2 -2
@@ -6,7 +6,8 @@
6
6
  "Bash(npm run test:coverage:*)",
7
7
  "Bash(npm login)",
8
8
  "Bash(npm publish:*)",
9
- "Bash(npm logout:*)"
9
+ "Bash(npm logout:*)",
10
+ "Bash(node -e:*)",
10
11
  ]
11
12
  }
12
13
  }
package/chartjs.js CHANGED
@@ -298,7 +298,7 @@ class Chartjs {
298
298
  }
299
299
 
300
300
  static getAutoGranularity(data) {
301
- const dates = Object.keys(data);
301
+ const dates = Object.keys(data).sort();
302
302
  const days = (new Date(dates[dates.length - 1]) - new Date(dates[0])) / (1000 * 60 * 60 * 24);
303
303
  if (days > 90) return 'month';
304
304
  if (days > 30) return 'week';
package/date_time.js CHANGED
@@ -206,9 +206,7 @@ class DateTime {
206
206
  }
207
207
 
208
208
  static getLastDayOfYear(date) {
209
- date.setUTCDate(31);
210
- date.setUTCMonth(11);
211
- return new Date(date);
209
+ return new Date(Date.UTC(date.getUTCFullYear(), 11, 31));
212
210
  }
213
211
 
214
212
  static getFirstDayOfWeekAndYear(year, week) {
@@ -308,7 +306,7 @@ class DatePeriod {
308
306
  return partOfDay === DatePeriod.PART_OF_DAY_MORNING || partOfDay === DatePeriod.PART_OF_DAY_AFTERNOON;
309
307
  }
310
308
 
311
- static getNbDayBetweenTwo(jsDate1, jsDate2, asPeriod=false, timeZone="Europe/Paris") {
309
+ static getNbDayBetweenTwo(jsDate1, jsDate2, considerTime=false, timeZone="Europe/Paris", returnDecimal=false) {
312
310
  //jsDate1.set
313
311
  if (jsDate1 == null || jsDate2 == null) {
314
312
  return 0;
@@ -317,7 +315,7 @@ class DatePeriod {
317
315
  let timestamp1 = jsDate1.getTime() / 1000;
318
316
  let timestamp2 = jsDate2.getTime() / 1000;
319
317
 
320
- if (!asPeriod) {
318
+ if (!considerTime) {
321
319
  let jsMidnightDate1 = new Date(jsDate1.toLocaleDateString('en-US', {timeZone: timeZone})+' 00:00:00');
322
320
  let jsMidnightDate2 = new Date(jsDate2.toLocaleDateString('en-US', {timeZone: timeZone})+' 00:00:00');
323
321
  timestamp1 = jsMidnightDate1.getTime() / 1000;
@@ -327,7 +325,9 @@ class DatePeriod {
327
325
  //jsDate1.setUTCHours(0, 0, 0);
328
326
  //jsDate2.setUTCHours(0, 0, 0);
329
327
  }
330
- return parseInt(Math.round((timestamp2-timestamp1)/86400));
328
+
329
+ const daysDiff = (timestamp2-timestamp1)/86400;
330
+ return returnDecimal ? daysDiff : parseInt(Math.round(daysDiff));
331
331
  }
332
332
 
333
333
  static getPeriodLabels(data, period, locale = 'fr-FR', timeZone = 'Europe/Paris') {
@@ -392,23 +392,32 @@ class TimestampUnix {
392
392
  }
393
393
 
394
394
  static getYear(timestamp, timeZone="Europe/Paris") {
395
- return this.parse(timestamp).toLocaleDateString('fr-FR', {year: 'numeric', timeZone: timeZone});
395
+ //const sqlDate = this.getSqlDate(timestamp, timeZone);
396
+ //return parseInt(sqlDate.substring(0, 4));
397
+ return parseInt(this.parse(timestamp).toLocaleDateString('fr-FR', {year: 'numeric', timeZone: timeZone}));
396
398
  }
397
399
  static getMonth(timestamp, timeZone="Europe/Paris") {
398
- return this.parse(timestamp).toLocaleDateString('fr-FR', {month: 'numeric', timeZone: timeZone});
400
+ //const sqlDate = this.getSqlDate(timestamp, timeZone);
401
+ //return parseInt(sqlDate.substring(5, 7));
402
+ return parseInt(this.parse(timestamp).toLocaleDateString('fr-FR', {month: 'numeric', timeZone: timeZone}));
399
403
  }
400
404
  static getDayOfMonth(timestamp, timeZone="Europe/Paris") {
401
- return this.parse(timestamp).toLocaleDateString('fr-FR', {day: 'numeric', timeZone: timeZone});
405
+ //const sqlDate = this.getSqlDate(timestamp, timeZone);
406
+ //return parseInt(sqlDate.substring(8, 10));
407
+ return parseInt(this.parse(timestamp).toLocaleDateString('fr-FR', {day: 'numeric', timeZone: timeZone}));
402
408
  }
403
409
 
404
410
  static getHour(timestamp, timeZone="Europe/Paris") {
405
- return this.parse(timestamp).toLocaleTimeString('en-GB', {hour: 'numeric', timeZone: timeZone});
411
+ const sqlTime = this.getSqlTime(timestamp, timeZone);
412
+ return parseInt(sqlTime.substring(0, 2));
406
413
  }
407
414
  static getMinute(timestamp, timeZone="Europe/Paris") {
408
- return this.parse(timestamp).toLocaleTimeString('en-GB', {minute: 'numeric', timeZone: timeZone});
415
+ const sqlTime = this.getSqlTime(timestamp, timeZone);
416
+ return parseInt(sqlTime.substring(3, 5));
409
417
  }
410
418
  static getSecond(timestamp, timeZone="Europe/Paris") {
411
- return this.parse(timestamp).toLocaleTimeString('en-GB', {second: 'numeric', timeZone: timeZone});
419
+ const sqlTime = this.getSqlTime(timestamp, timeZone);
420
+ return parseInt(sqlTime.substring(6, 8));
412
421
  }
413
422
 
414
423
  static getSqlDateTime(timestamp, timeZone="Europe/Paris") {
@@ -556,18 +565,18 @@ class SqlDateTime {
556
565
  return DateTime.getSqlDateTime(new Date());
557
566
  }
558
567
 
559
- static getSqlDate(sqlDateTime) {
568
+ static getSqlDate(sqlDateTime, timeZone="UTC") {
560
569
  if (sqlDateTime == null) {
561
570
  return null;
562
571
  }
563
- return DateTime.getSqlDate(this.parse(sqlDateTime));
572
+ return DateTime.getSqlDate(this.parse(sqlDateTime), timeZone);
564
573
  }
565
574
 
566
- static getSqlTime(sqlDateTime) {
575
+ static getSqlTime(sqlDateTime, timeZone="UTC") {
567
576
  if (sqlDateTime == null) {
568
577
  return null;
569
578
  }
570
- return DateTime.getSqlTime(this.parse(sqlDateTime));
579
+ return DateTime.getSqlTime(this.parse(sqlDateTime), timeZone);
571
580
  }
572
581
 
573
582
  static parse(sqlDateTime) {
package/draw.js CHANGED
@@ -1,6 +1,7 @@
1
1
  class HexColor {
2
2
  static check(hexColor) {
3
- return hexColor.match(/^[0-9a-fA-F]{6}$/).length;
3
+ const match = hexColor.match(/^[0-9a-fA-F]{6}$/);
4
+ return match ? match.length > 0 : false;
4
5
  }
5
6
 
6
7
  static convertToRgb(hexColor) {
@@ -9,7 +10,7 @@ class HexColor {
9
10
  }
10
11
 
11
12
  if (hexColor.length === 3) {
12
- hexColor = hexColor+hexColor;
13
+ hexColor = hexColor[0]+hexColor[0]+hexColor[1]+hexColor[1]+hexColor[2]+hexColor[2];
13
14
  }
14
15
 
15
16
  if (!HexColor.check(hexColor)) {
package/duration.js CHANGED
@@ -72,30 +72,24 @@ class Duration {
72
72
  if (withMinutes) {
73
73
  let nbMinutes = this.getNbMinutesRemainingOfDurationInSeconds(durationInSeconds);
74
74
  strMinute += ' ';
75
- //strMinute += sprintf('%02d', nbMinutes);
76
- strMinute += nbMinutes.toString().padStart(2, '0');
77
- if (withMinuteLabel) {
78
- if (fullLabel) {
79
- strMinute += ' minute'+(nbMinutes>1?'s':'');
80
- }
81
- else {
82
- strMinute += 'min';
83
- }
75
+ if (fullLabel) {
76
+ strMinute += nbMinutes.toString()+(withMinuteLabel ? ' minute'+(nbMinutes>1?'s':'') : '');
77
+ }
78
+ else {
79
+ strMinute += nbMinutes.toString().padStart(2, '0')+(withMinuteLabel ? 'min' : '');
84
80
  }
85
81
  }
86
-
82
+
87
83
  // Secondes
88
84
  let strSeconde = '';
89
85
  if (withSeconds) {
90
86
  let nbSecondes = this.getNbSecondsRemainingOfDurationInSeconds(durationInSeconds);
91
87
  strSeconde += ' ';
92
- //strSeconde += sprintf('%02d', nbSecondes);
93
- strSeconde += nbSecondes.toString().padStart(2, '0');
94
88
  if (fullLabel) {
95
- strSeconde += ' seconde'+(nbSecondes>1?'s':'');
89
+ strSeconde += nbSecondes.toString()+' seconde'+(nbSecondes>1?'s':'');
96
90
  }
97
91
  else {
98
- strSeconde += 's';
92
+ strSeconde += nbSecondes.toString().padStart(2, '0')+'s';
99
93
  }
100
94
  }
101
95
 
@@ -191,7 +185,10 @@ class Duration {
191
185
  }
192
186
 
193
187
  static getNbMinutesOfHundredthOfAnHour(durationAsHundredthOfAnHour) {
194
- return Math.floor(Math.getDecimals(Number.roundDecimal(durationAsHundredthOfAnHour, 2)) / 100 * 60);
188
+ // Extraire la partie décimale et convertir en minutes
189
+ // Ex: 1.5h -> 0.5 * 60 = 30 minutes
190
+ const decimalPart = durationAsHundredthOfAnHour - Math.floor(durationAsHundredthOfAnHour);
191
+ return Math.floor(decimalPart * 60);
195
192
  }
196
193
 
197
194
 
package/event_bus.js CHANGED
@@ -30,9 +30,9 @@ class EventBus {
30
30
 
31
31
  if (!!handlers === false) {
32
32
  return;
33
- }
33
+ }
34
34
 
35
- handlers.splice(handlers.indexOf(handler));
35
+ handlers.splice(handlers.indexOf(handler), 1);
36
36
  }
37
37
  }
38
38
 
package/file.js CHANGED
@@ -52,7 +52,7 @@ class File {
52
52
  fileSizeInBytes = fileSizeInBytes / 1024;
53
53
  i++;
54
54
  }
55
- while (fileSizeInBytes > 1024);
55
+ while (fileSizeInBytes >= 1024);
56
56
  //var size = Math.max(fileSizeInBytes, 0.1).toFixed(fractionDigits);
57
57
  let size = Math.max(fileSizeInBytes, 0.1);
58
58
  return (new Intl.NumberFormat(locale, {
package/form_helper.js CHANGED
@@ -294,7 +294,7 @@ class FormHelper {
294
294
  if (typeof errors[property] == 'function') {
295
295
  continue;
296
296
  }
297
- if (typeof errors[property]['error_description'] !== 'undefined') {
297
+ if (errors[property] != null && typeof errors[property]['error_description'] !== 'undefined') {
298
298
  errorLabels.push(errors[property]['error_description']);
299
299
  continue;
300
300
  }
package/http_client.js CHANGED
@@ -1,3 +1,5 @@
1
+ const { JwtSession } = require('./jwt');
2
+ const { UrlAndQueryString } = require('./network');
1
3
 
2
4
  class HTTPClient {
3
5
  static setAuthorizationToken(authorizationToken) {
package/jwt.js CHANGED
@@ -2,9 +2,15 @@ class JwtToken {
2
2
  static parseJwt (token) {
3
3
  let base64Url = token.split('.')[1];
4
4
  let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
5
- let jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
6
- return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
7
- }).join(''));
5
+ let jsonPayload;
6
+ try {
7
+ jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
8
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
9
+ }).join(''));
10
+ } catch (e) {
11
+ // Fallback for characters that can't be decoded
12
+ jsonPayload = atob(base64);
13
+ }
8
14
 
9
15
  return JSON.parse(jsonPayload);
10
16
  }
@@ -75,7 +81,9 @@ class JwtSession {
75
81
  static login(data, redirectUrl, onComplete) {
76
82
  console.log('JwtSession.login()');
77
83
  JwtSession.setToken(data['access_token'] || data['token']);
78
- JwtSession.setRefreshToken(data['refresh_token']);
84
+ if (data['refresh_token'] != null) {
85
+ JwtSession.setRefreshToken(data['refresh_token']);
86
+ }
79
87
 
80
88
  JwtSession.removeItemInStorage('real_users');
81
89
 
@@ -191,7 +199,9 @@ class JwtSession {
191
199
 
192
200
  // on enregistre la session de l'utilisateur simulé
193
201
  JwtSession.setToken(loginData['access_token'] || loginData['token']);
194
- JwtSession.setRefreshToken(loginData['refresh_token']);
202
+ if (loginData['refresh_token'] != null) {
203
+ JwtSession.setRefreshToken(loginData['refresh_token']);
204
+ }
195
205
 
196
206
  if (typeof onComplete == 'function') {
197
207
  onComplete();
@@ -216,7 +226,9 @@ class JwtSession {
216
226
  JwtSession.setItemInStorage('real_users', JSON.stringify(realUsers));
217
227
 
218
228
  JwtSession.setToken(loginData['access_token'] || loginData['token']);
219
- JwtSession.setRefreshToken(loginData['refresh_token']);
229
+ if (loginData['refresh_token'] != null) {
230
+ JwtSession.setRefreshToken(loginData['refresh_token']);
231
+ }
220
232
 
221
233
  if (typeof onComplete == 'function') {
222
234
  onComplete();
package/media.js CHANGED
@@ -137,7 +137,7 @@ class VideoMedia {
137
137
  //Source : https://www.npmjs.com/package/mic-check
138
138
  class UserMedia {
139
139
  static hasGetUserMedia() {
140
- return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia);
140
+ return !!(typeof navigator !== 'undefined' && (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia));
141
141
  }
142
142
 
143
143
  /** SystemPermissionDenied => (macOS) browser does not have permission to access cam/mic */
package/number.js CHANGED
@@ -78,11 +78,10 @@ Number.padLeft2 = Number.padLeft2 || function(n) {
78
78
  return n > 9 ? "" + n : "0" + n;
79
79
  }
80
80
 
81
- Number.prototype.roundDecimal = Number.prototype.roundDecimal || function(precision) {
81
+ Number.prototype.roundDecimal = Number.prototype.roundDecimal || function(precision=2) {
82
82
  return Number.roundDecimal(this, precision);
83
83
  }
84
- Number.roundDecimal = Number.roundDecimal || function(number, precision) {
85
- precision = precision || 2;
84
+ Number.roundDecimal = Number.roundDecimal || function(number, precision=2) {
86
85
  let tmp = Math.pow(10, precision);
87
86
  return Math.round(number*tmp) / tmp;
88
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@osimatic/helpers-js",
3
- "version": "1.4.24",
3
+ "version": "1.4.25",
4
4
  "main": "main.js",
5
5
  "scripts": {
6
6
  "test": "jest",
@@ -16,7 +16,8 @@
16
16
  "ilib": "^14.16.0"
17
17
  },
18
18
  "devDependencies": {
19
- "jest": "^30.2.0"
19
+ "jest": "^30.2.0",
20
+ "whatwg-fetch": "^3.6.20"
20
21
  },
21
22
  "jest": {
22
23
  "testEnvironment": "node",
package/social_network.js CHANGED
@@ -1,3 +1,5 @@
1
+ require('./string');
2
+
1
3
  class SocialNetwork {
2
4
 
3
5
  // ---------- Facebook ----------
@@ -40,6 +42,9 @@ class SocialNetwork {
40
42
  /*
41
43
  <a target="_blank" title="Twitter" href="https://twitter.com/share?url=<?php the_permalink(); ?>&text=<?php the_title(); ?>&via=korben" rel="nofollow" onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=400,width=700');return false;"><img src="http://korben.info/wp-content/themes/korben2013/hab/twitter_icon.png" alt="Twitter" /></a>
42
44
  */
45
+ if (typeof text === 'string' && typeof text.escapeHtml === 'function') {
46
+ text = text.escapeHtml();
47
+ }
43
48
  return ''
44
49
  +'<a href="https://twitter.com/share" class="twitter-share-button" data-url="'+url+'" data-text="'+text+'" data-lang="'+lang+'" data-hashtags="'+hashtags+'">Tweeter</a>'
45
50
  +'<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>'
package/string.js CHANGED
@@ -31,7 +31,7 @@ String.prototype.reverseString = String.prototype.reverseString || function() {
31
31
 
32
32
  String.prototype.truncateOnWord = String.prototype.truncateOnWord || function(limit, fromLeft) {
33
33
  if (fromLeft) {
34
- return this.reverseString(this.truncateOnWord(this.reverseString(), limit));
34
+ return this.reverseString().truncateOnWord(limit, false).reverseString();
35
35
  }
36
36
  let TRIM_CHARS = '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF';
37
37
  let words = this.split(RegExp('(?=['+TRIM_CHARS+'])'));
@@ -62,15 +62,24 @@ String.prototype.truncateString = String.prototype.truncateString || function(le
62
62
  switch(from) {
63
63
  case 'left':
64
64
  str2 = split ? this.truncateOnWord(length, true) : this.slice(this.length - length);
65
+ if (split) {
66
+ str2 = str2.trimStart();
67
+ return ellipsis + ' ' + str2;
68
+ }
65
69
  return ellipsis + str2;
66
70
  case 'middle':
67
71
  len1 = Math.ceil(length / 2);
68
72
  len2 = Math.floor(length / 2);
69
73
  str1 = split ? this.truncateOnWord(len1) : this.slice(0, len1);
70
74
  str2 = split ? this.truncateOnWord(len2, true) : this.slice(this.length - len2);
71
- return str1 + ellipsis + str2;
75
+ // Enlever les espaces de fin de str1 et de début de str2 pour éviter les doubles espaces
76
+ return str1.trimEnd() + ' ' + ellipsis + ' ' + str2.trimStart();
72
77
  default:
73
78
  str1 = split ? this.truncateOnWord(length) : this.slice(0, length);
79
+ if (split) {
80
+ str1 = str1.trimEnd();
81
+ return str1 + ' ' + ellipsis;
82
+ }
74
83
  return str1 + ellipsis;
75
84
  }
76
85
  };
@@ -0,0 +1,273 @@
1
+ const { Chartjs } = require('../chartjs');
2
+
3
+ describe('Chartjs', () => {
4
+ describe('groupByPeriod', () => {
5
+ test('should group data by day (default)', () => {
6
+ const data = {
7
+ '2024-01-15': { views: 10, clicks: 5 },
8
+ '2024-01-16': { views: 20, clicks: 8 },
9
+ '2024-01-17': { views: 15, clicks: 6 }
10
+ };
11
+ const metrics = ['views', 'clicks'];
12
+
13
+ const result = Chartjs.groupByPeriod(data, 'day', metrics);
14
+
15
+ expect(result).toHaveLength(3);
16
+ expect(result[0]).toEqual({ label: '2024-01-15', views: 10, clicks: 5 });
17
+ expect(result[1]).toEqual({ label: '2024-01-16', views: 20, clicks: 8 });
18
+ expect(result[2]).toEqual({ label: '2024-01-17', views: 15, clicks: 6 });
19
+ });
20
+
21
+ test('should group data by month', () => {
22
+ const data = {
23
+ '2024-01-15': { views: 10, clicks: 5 },
24
+ '2024-01-20': { views: 20, clicks: 8 },
25
+ '2024-02-05': { views: 15, clicks: 6 }
26
+ };
27
+ const metrics = ['views', 'clicks'];
28
+
29
+ const result = Chartjs.groupByPeriod(data, 'month', metrics);
30
+
31
+ expect(result).toHaveLength(2);
32
+ expect(result[0].label).toBe('2024-01');
33
+ expect(result[0].views).toBe(15); // Average of 10 and 20
34
+ expect(result[0].clicks).toBe(6.5); // Average of 5 and 8
35
+ expect(result[1].label).toBe('2024-02');
36
+ expect(result[1].views).toBe(15);
37
+ expect(result[1].clicks).toBe(6);
38
+ });
39
+
40
+ test('should group data by week', () => {
41
+ const data = {
42
+ '2024-01-01': { views: 10 },
43
+ '2024-01-02': { views: 20 },
44
+ '2024-01-08': { views: 15 },
45
+ '2024-01-09': { views: 25 }
46
+ };
47
+ const metrics = ['views'];
48
+
49
+ const result = Chartjs.groupByPeriod(data, 'week', metrics);
50
+
51
+ // Results should be grouped by weeks
52
+ expect(result.length).toBeGreaterThan(0);
53
+ // Check that labels contain week format (YYYY-SX)
54
+ result.forEach(item => {
55
+ expect(item.label).toMatch(/^\d{4}-S\d+$/);
56
+ });
57
+ });
58
+
59
+ test('should handle single metric', () => {
60
+ const data = {
61
+ '2024-01-15': { views: 10 },
62
+ '2024-01-16': { views: 20 }
63
+ };
64
+ const metrics = ['views'];
65
+
66
+ const result = Chartjs.groupByPeriod(data, 'day', metrics);
67
+
68
+ expect(result).toHaveLength(2);
69
+ expect(result[0]).toEqual({ label: '2024-01-15', views: 10 });
70
+ expect(result[1]).toEqual({ label: '2024-01-16', views: 20 });
71
+ });
72
+
73
+ test('should handle multiple metrics', () => {
74
+ const data = {
75
+ '2024-01-15': { views: 10, clicks: 5, conversions: 2 }
76
+ };
77
+ const metrics = ['views', 'clicks', 'conversions'];
78
+
79
+ const result = Chartjs.groupByPeriod(data, 'day', metrics);
80
+
81
+ expect(result).toHaveLength(1);
82
+ expect(result[0]).toEqual({
83
+ label: '2024-01-15',
84
+ views: 10,
85
+ clicks: 5,
86
+ conversions: 2
87
+ });
88
+ });
89
+
90
+ test('should handle empty data', () => {
91
+ const data = {};
92
+ const metrics = ['views'];
93
+
94
+ const result = Chartjs.groupByPeriod(data, 'day', metrics);
95
+
96
+ expect(result).toEqual([]);
97
+ });
98
+
99
+ test('should average values when grouping by month', () => {
100
+ const data = {
101
+ '2024-01-10': { score: 100 },
102
+ '2024-01-15': { score: 200 },
103
+ '2024-01-20': { score: 300 }
104
+ };
105
+ const metrics = ['score'];
106
+
107
+ const result = Chartjs.groupByPeriod(data, 'month', metrics);
108
+
109
+ expect(result).toHaveLength(1);
110
+ expect(result[0].label).toBe('2024-01');
111
+ expect(result[0].score).toBe(200); // Average of 100, 200, 300
112
+ });
113
+
114
+ test('should handle missing metric values', () => {
115
+ const data = {
116
+ '2024-01-15': { views: 10 },
117
+ '2024-01-16': { views: 20, clicks: 5 }
118
+ };
119
+ const metrics = ['views', 'clicks'];
120
+
121
+ const result = Chartjs.groupByPeriod(data, 'day', metrics);
122
+
123
+ expect(result).toHaveLength(2);
124
+ expect(result[0]).toEqual({ label: '2024-01-15', views: 10, clicks: NaN });
125
+ expect(result[1]).toEqual({ label: '2024-01-16', views: 20, clicks: 5 });
126
+ });
127
+
128
+ test('should handle dates across different years', () => {
129
+ const data = {
130
+ '2023-12-30': { count: 5 },
131
+ '2024-01-02': { count: 10 }
132
+ };
133
+ const metrics = ['count'];
134
+
135
+ const result = Chartjs.groupByPeriod(data, 'month', metrics);
136
+
137
+ expect(result).toHaveLength(2);
138
+ expect(result[0].label).toBe('2023-12');
139
+ expect(result[1].label).toBe('2024-01');
140
+ });
141
+ });
142
+
143
+ describe('getAutoGranularity', () => {
144
+ test('should return day_of_month for data spanning 30 days or less', () => {
145
+ const data = {
146
+ '2024-01-01': {},
147
+ '2024-01-15': {},
148
+ '2024-01-30': {}
149
+ };
150
+
151
+ const result = Chartjs.getAutoGranularity(data);
152
+
153
+ expect(result).toBe('day_of_month');
154
+ });
155
+
156
+ test('should return week for data spanning 31-90 days', () => {
157
+ const data = {
158
+ '2024-01-01': {},
159
+ '2024-03-15': {} // 74 days
160
+ };
161
+
162
+ const result = Chartjs.getAutoGranularity(data);
163
+
164
+ expect(result).toBe('week');
165
+ });
166
+
167
+ test('should return month for data spanning more than 90 days', () => {
168
+ const data = {
169
+ '2024-01-01': {},
170
+ '2024-05-01': {} // 121 days
171
+ };
172
+
173
+ const result = Chartjs.getAutoGranularity(data);
174
+
175
+ expect(result).toBe('month');
176
+ });
177
+
178
+ test('should return day_of_month for single day', () => {
179
+ const data = {
180
+ '2024-01-01': {}
181
+ };
182
+
183
+ const result = Chartjs.getAutoGranularity(data);
184
+
185
+ expect(result).toBe('day_of_month');
186
+ });
187
+
188
+ test('should return day_of_month for 2 days', () => {
189
+ const data = {
190
+ '2024-01-01': {},
191
+ '2024-01-02': {}
192
+ };
193
+
194
+ const result = Chartjs.getAutoGranularity(data);
195
+
196
+ expect(result).toBe('day_of_month');
197
+ });
198
+
199
+ test('should return week for exactly 31 days', () => {
200
+ const data = {
201
+ '2024-01-01': {},
202
+ '2024-02-01': {} // 31 days
203
+ };
204
+
205
+ const result = Chartjs.getAutoGranularity(data);
206
+
207
+ expect(result).toBe('week');
208
+ });
209
+
210
+ test('should return week for exactly 90 days', () => {
211
+ const data = {
212
+ '2024-01-01': {},
213
+ '2024-03-31': {} // 90 days
214
+ };
215
+
216
+ const result = Chartjs.getAutoGranularity(data);
217
+
218
+ expect(result).toBe('week');
219
+ });
220
+
221
+ test('should return month for exactly 91 days', () => {
222
+ const data = {
223
+ '2024-01-01': {},
224
+ '2024-04-01': {} // 91 days
225
+ };
226
+
227
+ const result = Chartjs.getAutoGranularity(data);
228
+
229
+ expect(result).toBe('month');
230
+ });
231
+
232
+ test('should handle dates in random order', () => {
233
+ const data = {
234
+ '2024-03-01': {},
235
+ '2024-01-01': {},
236
+ '2024-02-01': {}
237
+ };
238
+
239
+ const result = Chartjs.getAutoGranularity(data);
240
+
241
+ // Should calculate from first to last date (Jan 1 to Mar 1 = ~59 days)
242
+ expect(result).toBe('week');
243
+ });
244
+
245
+ test('should return month for data spanning a full year', () => {
246
+ const data = {
247
+ '2024-01-01': {},
248
+ '2024-12-31': {} // 365 days
249
+ };
250
+
251
+ const result = Chartjs.getAutoGranularity(data);
252
+
253
+ expect(result).toBe('month');
254
+ });
255
+ });
256
+
257
+ describe('Chartjs class structure', () => {
258
+ test('should be a class', () => {
259
+ expect(typeof Chartjs).toBe('function');
260
+ });
261
+
262
+ test('should have all expected static methods', () => {
263
+ expect(typeof Chartjs.init).toBe('function');
264
+ expect(typeof Chartjs.createStackedChart).toBe('function');
265
+ expect(typeof Chartjs.createBarChart).toBe('function');
266
+ expect(typeof Chartjs.createLineChart).toBe('function');
267
+ expect(typeof Chartjs.createDoughnutChart).toBe('function');
268
+ expect(typeof Chartjs.groupByPeriod).toBe('function');
269
+ expect(typeof Chartjs.getPeriodLabels).toBe('function');
270
+ expect(typeof Chartjs.getAutoGranularity).toBe('function');
271
+ });
272
+ });
273
+ });