@osimatic/helpers-js 1.5.20 → 1.5.22

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.
@@ -3,8 +3,6 @@
3
3
  */
4
4
  const { Chartjs } = require('../chartjs');
5
5
 
6
- // ─── chart creation helpers ──────────────────────────────────────────────────
7
-
8
6
  let mockChartInstance;
9
7
 
10
8
  beforeEach(() => {
@@ -13,11 +11,14 @@ beforeEach(() => {
13
11
  mockChartInstance.config = config;
14
12
  return mockChartInstance;
15
13
  });
14
+ global.Chart.register = jest.fn();
15
+ Chartjs.initialized = false;
16
16
  HTMLCanvasElement.prototype.getContext = jest.fn(() => ({}));
17
17
  });
18
18
 
19
19
  afterEach(() => {
20
20
  delete global.Chart;
21
+ Chartjs.initialized = false;
21
22
  document.body.innerHTML = '';
22
23
  jest.restoreAllMocks();
23
24
  });
@@ -28,9 +29,150 @@ function makeCanvas() {
28
29
  return canvas;
29
30
  }
30
31
 
31
- // ─── groupByPeriod ───────────────────────────────────────────────────────────
32
-
33
32
  describe('Chartjs', () => {
33
+
34
+ describe('init', () => {
35
+ test('registers centerText plugin on first call', () => {
36
+ Chartjs.init();
37
+ expect(global.Chart.register).toHaveBeenCalledTimes(1);
38
+ expect(global.Chart.register).toHaveBeenCalledWith(
39
+ expect.objectContaining({ id: 'centerText' })
40
+ );
41
+ });
42
+
43
+ test('does not register plugin on subsequent calls', () => {
44
+ Chartjs.init();
45
+ global.Chart.register.mockClear();
46
+ Chartjs.init();
47
+ expect(global.Chart.register).not.toHaveBeenCalled();
48
+ });
49
+
50
+ test('sets Chartjs.initialized to true', () => {
51
+ Chartjs.init();
52
+ expect(Chartjs.initialized).toBe(true);
53
+ });
54
+ });
55
+
56
+ describe('createStackedChart', () => {
57
+ const chartData = { labels: ['A', 'B'], datasets: [{ label: 'X', data: [1, 2] }] };
58
+
59
+ test('clears div and calls Chart constructor with type bar', () => {
60
+ const canvas = makeCanvas();
61
+ canvas.innerHTML = '<span>old</span>';
62
+ Chartjs.createStackedChart(canvas, chartData);
63
+ expect(canvas.innerHTML).toBe('');
64
+ expect(global.Chart).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ type: 'bar' }));
65
+ });
66
+
67
+ test('merges user options', () => {
68
+ Chartjs.createStackedChart(makeCanvas(), chartData, null, { options: { responsive: false } });
69
+ expect(mockChartInstance.config.options.responsive).toBe(false);
70
+ });
71
+
72
+ test('title displayed when provided', () => {
73
+ Chartjs.createStackedChart(makeCanvas(), chartData, 'My Title');
74
+ expect(mockChartInstance.config.options.plugins.title.display).toBe(true);
75
+ expect(mockChartInstance.config.options.plugins.title.text).toBe('My Title');
76
+ });
77
+
78
+ test('title not displayed when null', () => {
79
+ Chartjs.createStackedChart(makeCanvas(), chartData, null);
80
+ expect(mockChartInstance.config.options.plugins.title.display).toBe(false);
81
+ });
82
+
83
+ test('tooltip label callback', () => {
84
+ Chartjs.createStackedChart(makeCanvas(), chartData);
85
+ const fn = mockChartInstance.config.options.plugins.tooltip.callbacks.label;
86
+ expect(fn({ dataset: { label: 'Test' }, parsed: { y: 42 } })).toBe('Test: 42');
87
+ });
88
+
89
+ test('accepts jQuery-like object', () => {
90
+ const canvas = makeCanvas();
91
+ const jq = { jquery: '3.6.0', 0: canvas, length: 1 };
92
+ expect(() => Chartjs.createStackedChart(jq, chartData)).not.toThrow();
93
+ expect(global.Chart).toHaveBeenCalled();
94
+ });
95
+ });
96
+
97
+ describe('createBarChart', () => {
98
+ const chartData = { labels: ['A', 'B'], datasets: [{ label: 'X', data: [1, 2] }] };
99
+
100
+ test('clears div and calls Chart constructor with type bar', () => {
101
+ const canvas = makeCanvas();
102
+ canvas.innerHTML = '<span>old</span>';
103
+ Chartjs.createBarChart(canvas, chartData);
104
+ expect(canvas.innerHTML).toBe('');
105
+ expect(global.Chart).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ type: 'bar' }));
106
+ });
107
+
108
+ test('title displayed when provided', () => {
109
+ Chartjs.createBarChart(makeCanvas(), chartData, 'My Title');
110
+ expect(mockChartInstance.config.options.plugins.title.display).toBe(true);
111
+ expect(mockChartInstance.config.options.plugins.title.text).toBe('My Title');
112
+ });
113
+
114
+ test('title not displayed when null', () => {
115
+ Chartjs.createBarChart(makeCanvas(), chartData, null);
116
+ expect(mockChartInstance.config.options.plugins.title.display).toBe(false);
117
+ });
118
+
119
+ test('tooltip label callback', () => {
120
+ Chartjs.createBarChart(makeCanvas(), chartData);
121
+ const fn = mockChartInstance.config.options.plugins.tooltip.callbacks.label;
122
+ expect(fn({ dataset: { label: 'Test' }, parsed: { y: 10 } })).toBe('Test : 10');
123
+ });
124
+
125
+ test('accepts jQuery-like object', () => {
126
+ const canvas = makeCanvas();
127
+ const jq = { jquery: '3.6.0', 0: canvas, length: 1 };
128
+ expect(() => Chartjs.createBarChart(jq, chartData)).not.toThrow();
129
+ expect(global.Chart).toHaveBeenCalled();
130
+ });
131
+ });
132
+
133
+ describe('createLineChart', () => {
134
+ const chartData = { labels: ['A', 'B'], datasets: [{ label: 'X', data: [1, 2] }] };
135
+
136
+ test('clears div and calls Chart constructor with type line', () => {
137
+ const canvas = makeCanvas();
138
+ Chartjs.createLineChart(canvas, chartData);
139
+ expect(canvas.innerHTML).toBe('');
140
+ expect(global.Chart).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ type: 'line' }));
141
+ });
142
+
143
+ test('tooltip label callback', () => {
144
+ Chartjs.createLineChart(makeCanvas(), chartData);
145
+ const fn = mockChartInstance.config.options.plugins.tooltip.callbacks.label;
146
+ expect(fn({ dataset: { label: 'Line' }, parsed: { y: 7 } })).toBe('Line : 7');
147
+ });
148
+ });
149
+
150
+ describe('createDoughnutChart', () => {
151
+ test('clears div and calls Chart constructor with type doughnut', () => {
152
+ const canvas = makeCanvas();
153
+ Chartjs.createDoughnutChart(canvas, { labels: ['A'], values: [1], colors: ['#f00'] });
154
+ expect(canvas.innerHTML).toBe('');
155
+ expect(global.Chart).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ type: 'doughnut' }));
156
+ });
157
+
158
+ test('uses chartData.values as dataset data', () => {
159
+ Chartjs.createDoughnutChart(makeCanvas(), { labels: ['A', 'B'], values: [30, 70], colors: ['#f00', '#00f'] });
160
+ expect(mockChartInstance.config.data.datasets[0].data).toEqual([30, 70]);
161
+ });
162
+
163
+ test('tooltip label shows percentage', () => {
164
+ Chartjs.createDoughnutChart(makeCanvas(), { labels: ['A', 'B'], values: [30, 70], colors: ['#f00', '#00f'] });
165
+ const fn = mockChartInstance.config.options.plugins.tooltip.callbacks.label;
166
+ expect(fn({ label: 'A', raw: 30, dataset: { data: [30, 70] } })).toBe('A: 30 (30.0%)');
167
+ });
168
+
169
+ test('tooltip label for 100%', () => {
170
+ Chartjs.createDoughnutChart(makeCanvas(), { labels: ['Only'], values: [100], colors: ['#f00'] });
171
+ const fn = mockChartInstance.config.options.plugins.tooltip.callbacks.label;
172
+ expect(fn({ label: 'Only', raw: 100, dataset: { data: [100] } })).toBe('Only: 100 (100.0%)');
173
+ });
174
+ });
175
+
34
176
  describe('groupByPeriod', () => {
35
177
  test('should group data by day (default)', () => {
36
178
  const data = {
@@ -38,10 +180,7 @@ describe('Chartjs', () => {
38
180
  '2024-01-16': { views: 20, clicks: 8 },
39
181
  '2024-01-17': { views: 15, clicks: 6 }
40
182
  };
41
- const metrics = ['views', 'clicks'];
42
-
43
- const result = Chartjs.groupByPeriod(data, 'day', metrics);
44
-
183
+ const result = Chartjs.groupByPeriod(data, 'day', ['views', 'clicks']);
45
184
  expect(result).toHaveLength(3);
46
185
  expect(result[0]).toEqual({ label: '2024-01-15', views: 10, clicks: 5 });
47
186
  expect(result[1]).toEqual({ label: '2024-01-16', views: 20, clicks: 8 });
@@ -54,17 +193,12 @@ describe('Chartjs', () => {
54
193
  '2024-01-20': { views: 20, clicks: 8 },
55
194
  '2024-02-05': { views: 15, clicks: 6 }
56
195
  };
57
- const metrics = ['views', 'clicks'];
58
-
59
- const result = Chartjs.groupByPeriod(data, 'month', metrics);
60
-
196
+ const result = Chartjs.groupByPeriod(data, 'month', ['views', 'clicks']);
61
197
  expect(result).toHaveLength(2);
62
198
  expect(result[0].label).toBe('2024-01');
63
199
  expect(result[0].views).toBe(15);
64
200
  expect(result[0].clicks).toBe(6.5);
65
201
  expect(result[1].label).toBe('2024-02');
66
- expect(result[1].views).toBe(15);
67
- expect(result[1].clicks).toBe(6);
68
202
  });
69
203
 
70
204
  test('should group data by week', () => {
@@ -74,10 +208,7 @@ describe('Chartjs', () => {
74
208
  '2024-01-08': { views: 15 },
75
209
  '2024-01-09': { views: 25 }
76
210
  };
77
- const metrics = ['views'];
78
-
79
- const result = Chartjs.groupByPeriod(data, 'week', metrics);
80
-
211
+ const result = Chartjs.groupByPeriod(data, 'week', ['views']);
81
212
  expect(result.length).toBeGreaterThan(0);
82
213
  result.forEach(item => {
83
214
  expect(item.label).toMatch(/^\d{4}-S\d+$/);
@@ -85,34 +216,20 @@ describe('Chartjs', () => {
85
216
  });
86
217
 
87
218
  test('should handle single metric', () => {
88
- const data = {
89
- '2024-01-15': { views: 10 },
90
- '2024-01-16': { views: 20 }
91
- };
92
- const metrics = ['views'];
93
-
94
- const result = Chartjs.groupByPeriod(data, 'day', metrics);
95
-
219
+ const data = { '2024-01-15': { views: 10 }, '2024-01-16': { views: 20 } };
220
+ const result = Chartjs.groupByPeriod(data, 'day', ['views']);
96
221
  expect(result).toHaveLength(2);
97
222
  expect(result[0]).toEqual({ label: '2024-01-15', views: 10 });
98
- expect(result[1]).toEqual({ label: '2024-01-16', views: 20 });
99
223
  });
100
224
 
101
225
  test('should handle multiple metrics', () => {
102
- const data = {
103
- '2024-01-15': { views: 10, clicks: 5, conversions: 2 }
104
- };
105
- const metrics = ['views', 'clicks', 'conversions'];
106
-
107
- const result = Chartjs.groupByPeriod(data, 'day', metrics);
108
-
109
- expect(result).toHaveLength(1);
226
+ const data = { '2024-01-15': { views: 10, clicks: 5, conversions: 2 } };
227
+ const result = Chartjs.groupByPeriod(data, 'day', ['views', 'clicks', 'conversions']);
110
228
  expect(result[0]).toEqual({ label: '2024-01-15', views: 10, clicks: 5, conversions: 2 });
111
229
  });
112
230
 
113
231
  test('should handle empty data', () => {
114
- const result = Chartjs.groupByPeriod({}, 'day', ['views']);
115
- expect(result).toEqual([]);
232
+ expect(Chartjs.groupByPeriod({}, 'day', ['views'])).toEqual([]);
116
233
  });
117
234
 
118
235
  test('should average values when grouping by month', () => {
@@ -122,37 +239,42 @@ describe('Chartjs', () => {
122
239
  '2024-01-20': { score: 300 }
123
240
  };
124
241
  const result = Chartjs.groupByPeriod(data, 'month', ['score']);
125
-
126
- expect(result).toHaveLength(1);
127
- expect(result[0].label).toBe('2024-01');
128
242
  expect(result[0].score).toBe(200);
129
243
  });
130
244
 
131
245
  test('should handle missing metric values', () => {
132
- const data = {
133
- '2024-01-15': { views: 10 },
134
- '2024-01-16': { views: 20, clicks: 5 }
135
- };
246
+ const data = { '2024-01-15': { views: 10 }, '2024-01-16': { views: 20, clicks: 5 } };
136
247
  const result = Chartjs.groupByPeriod(data, 'day', ['views', 'clicks']);
137
-
138
- expect(result).toHaveLength(2);
139
248
  expect(result[0]).toEqual({ label: '2024-01-15', views: 10, clicks: NaN });
140
249
  expect(result[1]).toEqual({ label: '2024-01-16', views: 20, clicks: 5 });
141
250
  });
142
251
 
143
252
  test('should handle dates across different years', () => {
144
- const data = {
145
- '2023-12-30': { count: 5 },
146
- '2024-01-02': { count: 10 }
147
- };
253
+ const data = { '2023-12-30': { count: 5 }, '2024-01-02': { count: 10 } };
148
254
  const result = Chartjs.groupByPeriod(data, 'month', ['count']);
149
-
150
- expect(result).toHaveLength(2);
151
255
  expect(result[0].label).toBe('2023-12');
152
256
  expect(result[1].label).toBe('2024-01');
153
257
  });
154
258
  });
155
259
 
260
+ describe('getPeriodLabels', () => {
261
+ const { DatePeriod } = require('../date_time');
262
+
263
+ test('delegates to DatePeriod.getPeriodLabels with default locale/timezone', () => {
264
+ const spy = jest.spyOn(DatePeriod, 'getPeriodLabels');
265
+ const data = { '2024-01-01': {}, '2024-02-01': {} };
266
+ Chartjs.getPeriodLabels(data, 'month');
267
+ expect(spy).toHaveBeenCalledWith(Object.keys(data), 'month', 'fr-FR', 'Europe/Paris');
268
+ });
269
+
270
+ test('passes custom locale and timezone', () => {
271
+ const spy = jest.spyOn(DatePeriod, 'getPeriodLabels');
272
+ const data = { '2024-01-01': {} };
273
+ Chartjs.getPeriodLabels(data, 'month', 'en-US', 'America/New_York');
274
+ expect(spy).toHaveBeenCalledWith(Object.keys(data), 'month', 'en-US', 'America/New_York');
275
+ });
276
+ });
277
+
156
278
  describe('getAutoGranularity', () => {
157
279
  test('should return day_of_month for data spanning 30 days or less', () => {
158
280
  expect(Chartjs.getAutoGranularity({ '2024-01-01': {}, '2024-01-15': {}, '2024-01-30': {} })).toBe('day_of_month');
@@ -191,99 +313,4 @@ describe('Chartjs', () => {
191
313
  });
192
314
  });
193
315
 
194
- describe('chart creation', () => {
195
- const chartData = { labels: ['A', 'B'], datasets: [{ label: 'X', data: [1, 2] }] };
196
-
197
- test('createStackedChart clears div and calls Chart constructor', () => {
198
- const canvas = makeCanvas();
199
- canvas.innerHTML = '<span>old</span>';
200
-
201
- Chartjs.createStackedChart(canvas, chartData);
202
-
203
- expect(canvas.innerHTML).toBe('');
204
- expect(global.Chart).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ type: 'bar' }));
205
- });
206
-
207
- test('createStackedChart merges user options', () => {
208
- Chartjs.createStackedChart(makeCanvas(), chartData, null, { options: { responsive: false } });
209
-
210
- expect(mockChartInstance.config.options.responsive).toBe(false);
211
- });
212
-
213
- test('createBarChart clears div and calls Chart constructor', () => {
214
- const canvas = makeCanvas();
215
- canvas.innerHTML = '<span>old</span>';
216
-
217
- Chartjs.createBarChart(canvas, chartData);
218
-
219
- expect(canvas.innerHTML).toBe('');
220
- expect(global.Chart).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ type: 'bar' }));
221
- });
222
-
223
- test('createBarChart sets title when provided', () => {
224
- Chartjs.createBarChart(makeCanvas(), chartData, 'My Title');
225
-
226
- expect(mockChartInstance.config.options.plugins.title.display).toBe(true);
227
- expect(mockChartInstance.config.options.plugins.title.text).toBe('My Title');
228
- });
229
-
230
- test('createLineChart clears div and calls Chart constructor', () => {
231
- const canvas = makeCanvas();
232
- Chartjs.createLineChart(canvas, chartData);
233
-
234
- expect(canvas.innerHTML).toBe('');
235
- expect(global.Chart).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ type: 'line' }));
236
- });
237
-
238
- test('createDoughnutChart clears div and calls Chart constructor', () => {
239
- const canvas = makeCanvas();
240
- Chartjs.createDoughnutChart(canvas, { labels: ['A'], values: [1], colors: ['#f00'] });
241
-
242
- expect(canvas.innerHTML).toBe('');
243
- expect(global.Chart).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ type: 'doughnut' }));
244
- });
245
-
246
- test('createDoughnutChart uses chartData.values as dataset data', () => {
247
- Chartjs.createDoughnutChart(makeCanvas(), { labels: ['A', 'B'], values: [30, 70], colors: ['#f00', '#00f'] });
248
-
249
- expect(mockChartInstance.config.data.datasets[0].data).toEqual([30, 70]);
250
- });
251
-
252
- test('title not displayed when null', () => {
253
- Chartjs.createBarChart(makeCanvas(), chartData, null);
254
-
255
- expect(mockChartInstance.config.options.plugins.title.display).toBe(false);
256
- });
257
-
258
- describe('jQuery compatibility (toEl)', () => {
259
- test('createStackedChart accepts jQuery-like object', () => {
260
- const canvas = makeCanvas();
261
- const jq = { jquery: '3.6.0', 0: canvas, length: 1 };
262
-
263
- expect(() => Chartjs.createStackedChart(jq, chartData)).not.toThrow();
264
- expect(global.Chart).toHaveBeenCalled();
265
- });
266
-
267
- test('createBarChart accepts jQuery-like object', () => {
268
- const canvas = makeCanvas();
269
- const jq = { jquery: '3.6.0', 0: canvas, length: 1 };
270
-
271
- expect(() => Chartjs.createBarChart(jq, chartData)).not.toThrow();
272
- expect(global.Chart).toHaveBeenCalled();
273
- });
274
- });
275
- });
276
-
277
- describe('class structure', () => {
278
- test('should have all expected static methods', () => {
279
- expect(typeof Chartjs.init).toBe('function');
280
- expect(typeof Chartjs.createStackedChart).toBe('function');
281
- expect(typeof Chartjs.createBarChart).toBe('function');
282
- expect(typeof Chartjs.createLineChart).toBe('function');
283
- expect(typeof Chartjs.createDoughnutChart).toBe('function');
284
- expect(typeof Chartjs.groupByPeriod).toBe('function');
285
- expect(typeof Chartjs.getPeriodLabels).toBe('function');
286
- expect(typeof Chartjs.getAutoGranularity).toBe('function');
287
- });
288
- });
289
316
  });