@osimatic/helpers-js 1.5.5 → 1.5.6
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.
- package/chartjs.js +15 -16
- package/count_down.js +30 -21
- package/details_sub_array.js +2 -0
- package/file.js +21 -18
- package/form_date.js +0 -102
- package/form_helper.js +24 -0
- package/google_charts.js +3 -0
- package/import_from_csv.js +3 -0
- package/location.js +13 -16
- package/media.js +37 -29
- package/multi_files_input.js +2 -0
- package/multiple_action_in_table.js +5 -112
- package/open_street_map.js +3 -0
- package/package.json +1 -1
- package/paging.js +4 -0
- package/select_all.js +5 -0
- package/sortable_list.js +3 -0
- package/tests/chartjs.test.js +119 -103
- package/tests/count_down.test.js +87 -201
- package/tests/file.test.js +11 -41
- package/util.js +5 -1
package/media.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
const { FormHelper } = require('./form_helper');
|
|
2
|
+
const { HTTPClient } = require('./http_client');
|
|
3
|
+
|
|
1
4
|
class AudioMedia {
|
|
2
5
|
|
|
3
6
|
static getPlayer(playUrl) {
|
|
@@ -6,37 +9,42 @@ class AudioMedia {
|
|
|
6
9
|
|
|
7
10
|
static initPlayLinks(div) {
|
|
8
11
|
// Affiche un lecteur audio
|
|
9
|
-
div.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
div.querySelectorAll('.play_link').forEach(link => {
|
|
13
|
+
link.addEventListener('click', function(e) {
|
|
14
|
+
e.preventDefault();
|
|
15
|
+
const template = document.createElement('template');
|
|
16
|
+
template.innerHTML = AudioMedia.getPlayer(this.dataset.play_url);
|
|
17
|
+
const audio = template.content.firstChild;
|
|
18
|
+
audio.play();
|
|
19
|
+
this.insertAdjacentElement('afterend', audio);
|
|
20
|
+
this.remove();
|
|
21
|
+
});
|
|
15
22
|
});
|
|
16
23
|
|
|
17
|
-
div.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// AudioMedia.playAudioUrl($(this).data('url'), () => button.attr('disabled', false).button('reset'));
|
|
24
|
-
//}
|
|
25
|
-
return false;
|
|
24
|
+
div.querySelectorAll('.play_asynchronously_link').forEach(link => {
|
|
25
|
+
link.addEventListener('click', function(e) {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
let button = FormHelper.buttonLoader(this, 'loading');
|
|
28
|
+
AudioMedia.playAudioUrl(this.dataset.url, () => FormHelper.buttonLoader(button, 'reset'));
|
|
29
|
+
});
|
|
26
30
|
});
|
|
27
31
|
|
|
28
|
-
div.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
div.querySelectorAll('.modal_play_link').forEach(link => {
|
|
33
|
+
link.addEventListener('click', function(e) {
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
const modalEl = document.getElementById('modal_voice_message_play');
|
|
36
|
+
if (!modalEl) return;
|
|
37
|
+
const currentLink = this;
|
|
38
|
+
modalEl.addEventListener('show.bs.modal', function handler(event) {
|
|
39
|
+
modalEl.removeEventListener('show.bs.modal', handler);
|
|
40
|
+
const player = modalEl.querySelector('audio');
|
|
41
|
+
if (player) {
|
|
42
|
+
player.src = (event.relatedTarget || currentLink).dataset.play_url;
|
|
43
|
+
player.play();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
bootstrap.Modal.getOrCreateInstance(modalEl).show(this);
|
|
36
47
|
});
|
|
37
|
-
|
|
38
|
-
$('#modal_voice_message_play').modal('show', $(this));
|
|
39
|
-
return false;
|
|
40
48
|
});
|
|
41
49
|
}
|
|
42
50
|
|
|
@@ -117,12 +125,12 @@ class AudioMedia {
|
|
|
117
125
|
|
|
118
126
|
class VideoMedia {
|
|
119
127
|
static initPlayPauseClick(videoElement) {
|
|
120
|
-
|
|
128
|
+
videoElement.addEventListener('click', function(e) {
|
|
121
129
|
// handle click if not Firefox (Firefox supports this feature natively)
|
|
122
130
|
if (typeof InstallTrigger === 'undefined') {
|
|
123
131
|
// get click position
|
|
124
|
-
let clickY =
|
|
125
|
-
let height =
|
|
132
|
+
let clickY = e.pageY - (this.getBoundingClientRect().top + window.scrollY);
|
|
133
|
+
let height = this.offsetHeight;
|
|
126
134
|
|
|
127
135
|
// avoids interference with controls
|
|
128
136
|
if (clickY > 0.82*height) return;
|
package/multi_files_input.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
require('./string');
|
|
2
2
|
const { FlashMessage } = require('./flash_message');
|
|
3
|
+
const { toEl } = require('./util');
|
|
3
4
|
|
|
4
5
|
class MultiFilesInput {
|
|
5
6
|
static init(fileInput, setFilesList, nbMaxFiles, maxFileSize) {
|
|
7
|
+
fileInput = toEl(fileInput);
|
|
6
8
|
let filesList = [];
|
|
7
9
|
const formGroup = fileInput.closest('.form-group');
|
|
8
10
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { toEl } = require('./util');
|
|
1
2
|
|
|
2
3
|
class MultipleActionInTable {
|
|
3
4
|
|
|
@@ -5,6 +6,7 @@ class MultipleActionInTable {
|
|
|
5
6
|
// Idempotent : sans effet si les colonnes existent déjà.
|
|
6
7
|
// Doit être appelé AVANT l'initialisation DataTable.
|
|
7
8
|
static initCols(table, cellSelector = 'select') {
|
|
9
|
+
table = toEl(table);
|
|
8
10
|
if (!table.classList.contains('table-action_multiple')) {
|
|
9
11
|
return;
|
|
10
12
|
}
|
|
@@ -26,6 +28,7 @@ class MultipleActionInTable {
|
|
|
26
28
|
// Initialise les colonnes (via initCols) puis branche les event handlers.
|
|
27
29
|
// Peut être appelé après l'initialisation DataTable.
|
|
28
30
|
static init(table, options = {}) {
|
|
31
|
+
table = toEl(table);
|
|
29
32
|
const { cellSelector = 'select', imgArrow = '' } = options;
|
|
30
33
|
|
|
31
34
|
if (!table.classList.contains('table-action_multiple')) {
|
|
@@ -141,6 +144,7 @@ class MultipleActionInTable {
|
|
|
141
144
|
class MultipleActionInDivList {
|
|
142
145
|
// init checkbox
|
|
143
146
|
static init(contentDiv, options = {}) {
|
|
147
|
+
contentDiv = toEl(contentDiv);
|
|
144
148
|
const { imgArrow = '' } = options;
|
|
145
149
|
|
|
146
150
|
let buttonsDiv = MultipleActionInDivList.getButtonsDiv(contentDiv);
|
|
@@ -254,115 +258,4 @@ class MultipleActionInDivList {
|
|
|
254
258
|
}
|
|
255
259
|
}
|
|
256
260
|
|
|
257
|
-
module.exports = { MultipleActionInTable, MultipleActionInDivList };
|
|
258
|
-
|
|
259
|
-
/*
|
|
260
|
-
// init checkbox
|
|
261
|
-
function initTableActionMultiple(table) {
|
|
262
|
-
if (!table.hasClass('table-action_multiple')) {
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
var divBtn = tableActionMultipleGetDivBtn(table);
|
|
267
|
-
if (divBtn == null) {
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (table.find('thead tr th[data-key="select"]').length === 0) {
|
|
272
|
-
table.find('thead tr').prepend($('<th class="select no-sort" data-key="select"></th>'));
|
|
273
|
-
}
|
|
274
|
-
table.find('tbody tr:not(.no_items)').each(function(idx, tr) {
|
|
275
|
-
if ($(tr).find('td.select').length === 0) {
|
|
276
|
-
$(tr).prepend($('<td class="select"><input type="checkbox" class="action_multiple_checkbox" name="'+$(tr).data('action_multiple_input_name')+'" value="'+$(tr).data('action_multiple_item_id')+'"></td>'));
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
table.find('input.action_multiple_checkbox').each(function(idx, el) {
|
|
281
|
-
var th = $(el).closest('table').find('thead tr th').first();
|
|
282
|
-
if (th.find('input').length === 0) {
|
|
283
|
-
// console.log(th);
|
|
284
|
-
th.html('<input type="checkbox" class="action_multiple_check_all" />');
|
|
285
|
-
// th.html('Coucou');
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
table.find('input.action_multiple_checkbox').change(function() {
|
|
290
|
-
majCheckbox(table);
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
table.find('input.action_multiple_check_all').off('click').click(function() {
|
|
294
|
-
var table = $(this).closest('table');
|
|
295
|
-
var checkbox = table.find('input.action_multiple_checkbox');
|
|
296
|
-
var checkboxChecked = table.find('input.action_multiple_checkbox:checked');
|
|
297
|
-
if (checkbox.length === checkboxChecked.length) {
|
|
298
|
-
checkbox.prop('checked', false);
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
checkbox.prop('checked', true);
|
|
302
|
-
}
|
|
303
|
-
majCheckbox(table);
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function majCheckbox(table) {
|
|
308
|
-
showButtonsAction(table);
|
|
309
|
-
|
|
310
|
-
var allCheckbox = table.find('input.action_multiple_checkbox');
|
|
311
|
-
var allCheckboxChecked = table.find('input.action_multiple_checkbox:checked');
|
|
312
|
-
var checkboxSelectAll = table.find('thead tr th input.action_multiple_check_all');
|
|
313
|
-
if (allCheckbox.length === allCheckboxChecked.length) {
|
|
314
|
-
checkboxSelectAll.prop('checked', true);
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
checkboxSelectAll.prop('checked', false);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function tableActionMultipleGetDivBtn(table) {
|
|
322
|
-
var divTableResponsive = table.parent();
|
|
323
|
-
var divBtn = divTableResponsive.next();
|
|
324
|
-
if (divBtn.hasClass('action_multiple_buttons')) {
|
|
325
|
-
return divBtn;
|
|
326
|
-
}
|
|
327
|
-
divBtn = divTableResponsive.parent().parent().parent().next();
|
|
328
|
-
if (divBtn.hasClass('action_multiple_buttons')) {
|
|
329
|
-
return divBtn;
|
|
330
|
-
}
|
|
331
|
-
return null;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function showButtonsAction(table) {
|
|
335
|
-
var divBtn = tableActionMultipleGetDivBtn(table);
|
|
336
|
-
if (divBtn == null) {
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// console.log(divBtn);
|
|
341
|
-
//var nbItems = $('input[name="' + checkbox.attr('name') + '"]:checked').length;
|
|
342
|
-
var nbItems = table.find('input.action_multiple_checkbox:checked').length;
|
|
343
|
-
|
|
344
|
-
if (nbItems > 0 && divBtn.is(':hidden')) {
|
|
345
|
-
divBtn.removeClass('hide');
|
|
346
|
-
}
|
|
347
|
-
else if (nbItems === 0 && divBtn.is(':visible')) {
|
|
348
|
-
divBtn.addClass('hide');
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// affichage aucune action possible si aucun bouton n'est visible
|
|
352
|
-
if (divBtn.is(':visible')) {
|
|
353
|
-
divBtn.find('span.no_button').remove();
|
|
354
|
-
if (divBtn.find('button:visible, a:visible').length === 0) {
|
|
355
|
-
divBtn.find('img').after('<span class="no_button"><em>aucune action possible</em></span>');
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
$(function() {
|
|
361
|
-
$('.action_multiple_buttons').prepend($('<img src="'+ROOT_PATH+DOSSIER_IMAGES+'arrow_ltr.png" alt="" /> '));
|
|
362
|
-
$('.action_multiple_buttons').append($('<br/><br/>'));
|
|
363
|
-
|
|
364
|
-
$('table.table-action_multiple').each(function(idx, table) {
|
|
365
|
-
initTableActionMultiple($(table));
|
|
366
|
-
});
|
|
367
|
-
});
|
|
368
|
-
*/
|
|
261
|
+
module.exports = { MultipleActionInTable, MultipleActionInDivList };
|
package/open_street_map.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const L = require('leaflet');
|
|
2
|
+
const { toEl } = require('./util');
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* https://leafletjs.com/
|
|
@@ -8,6 +9,7 @@ const L = require('leaflet');
|
|
|
8
9
|
class OpenStreetMap {
|
|
9
10
|
|
|
10
11
|
constructor(mapContainer, options={}) {
|
|
12
|
+
mapContainer = toEl(mapContainer);
|
|
11
13
|
/*let [lat, lng] = button.data('coordinates').split(',');
|
|
12
14
|
let map = L.map('modal_map_canvas2').setView([lat, lng], 17);
|
|
13
15
|
|
|
@@ -27,6 +29,7 @@ class OpenStreetMap {
|
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
static createMap(mapContainer, options={}) {
|
|
32
|
+
mapContainer = toEl(mapContainer);
|
|
30
33
|
if (!mapContainer.length) {
|
|
31
34
|
return null;
|
|
32
35
|
}
|
package/package.json
CHANGED
package/paging.js
CHANGED
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
//Bootstrap class pagination https://getbootstrap.com/docs/3.3/components/#pagination
|
|
3
3
|
|
|
4
4
|
const { UrlAndQueryString } = require('./network');
|
|
5
|
+
const { toEl } = require('./util');
|
|
5
6
|
|
|
6
7
|
class Pagination {
|
|
7
8
|
static paginateCards(div, nbItemsPerPage) {
|
|
9
|
+
div = toEl(div);
|
|
8
10
|
Pagination.paginate(div, div.querySelectorAll('.pagination_item'), nbItemsPerPage, null);
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
static paginateTable(table, select=null) {
|
|
14
|
+
table = toEl(table); select = toEl(select);
|
|
12
15
|
Pagination.paginate(table, table.querySelectorAll('tbody tr:not(.hide)'), parseInt(table.dataset.max_rows), select);
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
static paginate(div, items, nbItemsPerPage, select=null, labelDisplayAll=null) {
|
|
19
|
+
div = toEl(div); select = toEl(select);
|
|
16
20
|
let maxItems = nbItemsPerPage;
|
|
17
21
|
|
|
18
22
|
if (!div) {
|
package/select_all.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
const { toEl } = require('./util');
|
|
2
|
+
|
|
1
3
|
class SelectAll {
|
|
2
4
|
|
|
3
5
|
// Dans un form-group
|
|
4
6
|
|
|
5
7
|
static initLinkInFormGroup(link) {
|
|
8
|
+
link = toEl(link);
|
|
6
9
|
const linkClone = link.cloneNode(true);
|
|
7
10
|
link.parentElement.replaceChild(linkClone, link);
|
|
8
11
|
linkClone.addEventListener('click', function(e) {
|
|
@@ -43,6 +46,7 @@ class SelectAll {
|
|
|
43
46
|
// Dans tableau
|
|
44
47
|
|
|
45
48
|
static initInTable(table) {
|
|
49
|
+
table = toEl(table);
|
|
46
50
|
const inputCheckAll = table.querySelector('tr input.check_all');
|
|
47
51
|
if (!inputCheckAll) {
|
|
48
52
|
return;
|
|
@@ -78,6 +82,7 @@ class SelectAll {
|
|
|
78
82
|
// Dans un div
|
|
79
83
|
|
|
80
84
|
static initDiv(contentDiv) {
|
|
85
|
+
contentDiv = toEl(contentDiv);
|
|
81
86
|
contentDiv.querySelectorAll('input.check_all').forEach(inputCheckAll => {
|
|
82
87
|
const div = inputCheckAll.closest('div.checkbox_with_check_all');
|
|
83
88
|
|
package/sortable_list.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
const { toEl } = require('./util');
|
|
2
|
+
|
|
1
3
|
class SortableList {
|
|
2
4
|
static init(sortableList, clientYOffset=0) {
|
|
5
|
+
sortableList = toEl(sortableList);
|
|
3
6
|
sortableList.querySelectorAll('[draggable="true"]').forEach(item => {
|
|
4
7
|
item.addEventListener('dragstart', () => {
|
|
5
8
|
// Adding dragging class to an item after a delay
|
package/tests/chartjs.test.js
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
1
4
|
const { Chartjs } = require('../chartjs');
|
|
2
5
|
|
|
6
|
+
// ─── chart creation helpers ──────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
let mockChartInstance;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
mockChartInstance = { data: null, config: null };
|
|
12
|
+
global.Chart = jest.fn((ctx, config) => {
|
|
13
|
+
mockChartInstance.config = config;
|
|
14
|
+
return mockChartInstance;
|
|
15
|
+
});
|
|
16
|
+
HTMLCanvasElement.prototype.getContext = jest.fn(() => ({}));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
delete global.Chart;
|
|
21
|
+
document.body.innerHTML = '';
|
|
22
|
+
jest.restoreAllMocks();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function makeCanvas() {
|
|
26
|
+
const canvas = document.createElement('canvas');
|
|
27
|
+
document.body.appendChild(canvas);
|
|
28
|
+
return canvas;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─── groupByPeriod ───────────────────────────────────────────────────────────
|
|
32
|
+
|
|
3
33
|
describe('Chartjs', () => {
|
|
4
34
|
describe('groupByPeriod', () => {
|
|
5
35
|
test('should group data by day (default)', () => {
|
|
@@ -30,8 +60,8 @@ describe('Chartjs', () => {
|
|
|
30
60
|
|
|
31
61
|
expect(result).toHaveLength(2);
|
|
32
62
|
expect(result[0].label).toBe('2024-01');
|
|
33
|
-
expect(result[0].views).toBe(15);
|
|
34
|
-
expect(result[0].clicks).toBe(6.5);
|
|
63
|
+
expect(result[0].views).toBe(15);
|
|
64
|
+
expect(result[0].clicks).toBe(6.5);
|
|
35
65
|
expect(result[1].label).toBe('2024-02');
|
|
36
66
|
expect(result[1].views).toBe(15);
|
|
37
67
|
expect(result[1].clicks).toBe(6);
|
|
@@ -48,9 +78,7 @@ describe('Chartjs', () => {
|
|
|
48
78
|
|
|
49
79
|
const result = Chartjs.groupByPeriod(data, 'week', metrics);
|
|
50
80
|
|
|
51
|
-
// Results should be grouped by weeks
|
|
52
81
|
expect(result.length).toBeGreaterThan(0);
|
|
53
|
-
// Check that labels contain week format (YYYY-SX)
|
|
54
82
|
result.forEach(item => {
|
|
55
83
|
expect(item.label).toMatch(/^\d{4}-S\d+$/);
|
|
56
84
|
});
|
|
@@ -79,20 +107,11 @@ describe('Chartjs', () => {
|
|
|
79
107
|
const result = Chartjs.groupByPeriod(data, 'day', metrics);
|
|
80
108
|
|
|
81
109
|
expect(result).toHaveLength(1);
|
|
82
|
-
expect(result[0]).toEqual({
|
|
83
|
-
label: '2024-01-15',
|
|
84
|
-
views: 10,
|
|
85
|
-
clicks: 5,
|
|
86
|
-
conversions: 2
|
|
87
|
-
});
|
|
110
|
+
expect(result[0]).toEqual({ label: '2024-01-15', views: 10, clicks: 5, conversions: 2 });
|
|
88
111
|
});
|
|
89
112
|
|
|
90
113
|
test('should handle empty data', () => {
|
|
91
|
-
const
|
|
92
|
-
const metrics = ['views'];
|
|
93
|
-
|
|
94
|
-
const result = Chartjs.groupByPeriod(data, 'day', metrics);
|
|
95
|
-
|
|
114
|
+
const result = Chartjs.groupByPeriod({}, 'day', ['views']);
|
|
96
115
|
expect(result).toEqual([]);
|
|
97
116
|
});
|
|
98
117
|
|
|
@@ -102,13 +121,11 @@ describe('Chartjs', () => {
|
|
|
102
121
|
'2024-01-15': { score: 200 },
|
|
103
122
|
'2024-01-20': { score: 300 }
|
|
104
123
|
};
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
const result = Chartjs.groupByPeriod(data, 'month', metrics);
|
|
124
|
+
const result = Chartjs.groupByPeriod(data, 'month', ['score']);
|
|
108
125
|
|
|
109
126
|
expect(result).toHaveLength(1);
|
|
110
127
|
expect(result[0].label).toBe('2024-01');
|
|
111
|
-
expect(result[0].score).toBe(200);
|
|
128
|
+
expect(result[0].score).toBe(200);
|
|
112
129
|
});
|
|
113
130
|
|
|
114
131
|
test('should handle missing metric values', () => {
|
|
@@ -116,9 +133,7 @@ describe('Chartjs', () => {
|
|
|
116
133
|
'2024-01-15': { views: 10 },
|
|
117
134
|
'2024-01-16': { views: 20, clicks: 5 }
|
|
118
135
|
};
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
const result = Chartjs.groupByPeriod(data, 'day', metrics);
|
|
136
|
+
const result = Chartjs.groupByPeriod(data, 'day', ['views', 'clicks']);
|
|
122
137
|
|
|
123
138
|
expect(result).toHaveLength(2);
|
|
124
139
|
expect(result[0]).toEqual({ label: '2024-01-15', views: 10, clicks: NaN });
|
|
@@ -130,9 +145,7 @@ describe('Chartjs', () => {
|
|
|
130
145
|
'2023-12-30': { count: 5 },
|
|
131
146
|
'2024-01-02': { count: 10 }
|
|
132
147
|
};
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
const result = Chartjs.groupByPeriod(data, 'month', metrics);
|
|
148
|
+
const result = Chartjs.groupByPeriod(data, 'month', ['count']);
|
|
136
149
|
|
|
137
150
|
expect(result).toHaveLength(2);
|
|
138
151
|
expect(result[0].label).toBe('2023-12');
|
|
@@ -142,123 +155,126 @@ describe('Chartjs', () => {
|
|
|
142
155
|
|
|
143
156
|
describe('getAutoGranularity', () => {
|
|
144
157
|
test('should return day_of_month for data spanning 30 days or less', () => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
'2024-01-15': {},
|
|
148
|
-
'2024-01-30': {}
|
|
149
|
-
};
|
|
158
|
+
expect(Chartjs.getAutoGranularity({ '2024-01-01': {}, '2024-01-15': {}, '2024-01-30': {} })).toBe('day_of_month');
|
|
159
|
+
});
|
|
150
160
|
|
|
151
|
-
|
|
161
|
+
test('should return week for data spanning 31-90 days', () => {
|
|
162
|
+
expect(Chartjs.getAutoGranularity({ '2024-01-01': {}, '2024-03-15': {} })).toBe('week');
|
|
163
|
+
});
|
|
152
164
|
|
|
153
|
-
|
|
165
|
+
test('should return month for data spanning more than 90 days', () => {
|
|
166
|
+
expect(Chartjs.getAutoGranularity({ '2024-01-01': {}, '2024-05-01': {} })).toBe('month');
|
|
154
167
|
});
|
|
155
168
|
|
|
156
|
-
test('should return
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
'2024-03-15': {} // 74 days
|
|
160
|
-
};
|
|
169
|
+
test('should return day_of_month for single day', () => {
|
|
170
|
+
expect(Chartjs.getAutoGranularity({ '2024-01-01': {} })).toBe('day_of_month');
|
|
171
|
+
});
|
|
161
172
|
|
|
162
|
-
|
|
173
|
+
test('should return week for exactly 31 days', () => {
|
|
174
|
+
expect(Chartjs.getAutoGranularity({ '2024-01-01': {}, '2024-02-01': {} })).toBe('week');
|
|
175
|
+
});
|
|
163
176
|
|
|
164
|
-
|
|
177
|
+
test('should return week for exactly 90 days', () => {
|
|
178
|
+
expect(Chartjs.getAutoGranularity({ '2024-01-01': {}, '2024-03-31': {} })).toBe('week');
|
|
165
179
|
});
|
|
166
180
|
|
|
167
|
-
test('should return month for
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
'2024-05-01': {} // 121 days
|
|
171
|
-
};
|
|
181
|
+
test('should return month for exactly 91 days', () => {
|
|
182
|
+
expect(Chartjs.getAutoGranularity({ '2024-01-01': {}, '2024-04-01': {} })).toBe('month');
|
|
183
|
+
});
|
|
172
184
|
|
|
173
|
-
|
|
185
|
+
test('should handle dates in random order', () => {
|
|
186
|
+
expect(Chartjs.getAutoGranularity({ '2024-03-01': {}, '2024-01-01': {}, '2024-02-01': {} })).toBe('week');
|
|
187
|
+
});
|
|
174
188
|
|
|
175
|
-
|
|
189
|
+
test('should return month for data spanning a full year', () => {
|
|
190
|
+
expect(Chartjs.getAutoGranularity({ '2024-01-01': {}, '2024-12-31': {} })).toBe('month');
|
|
176
191
|
});
|
|
192
|
+
});
|
|
177
193
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
'2024-01-01': {}
|
|
181
|
-
};
|
|
194
|
+
describe('chart creation', () => {
|
|
195
|
+
const chartData = { labels: ['A', 'B'], datasets: [{ label: 'X', data: [1, 2] }] };
|
|
182
196
|
|
|
183
|
-
|
|
197
|
+
test('createStackedChart clears div and calls Chart constructor', () => {
|
|
198
|
+
const canvas = makeCanvas();
|
|
199
|
+
canvas.innerHTML = '<span>old</span>';
|
|
184
200
|
|
|
185
|
-
|
|
186
|
-
});
|
|
201
|
+
Chartjs.createStackedChart(canvas, chartData);
|
|
187
202
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
'2024-01-02': {}
|
|
192
|
-
};
|
|
203
|
+
expect(canvas.innerHTML).toBe('');
|
|
204
|
+
expect(global.Chart).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ type: 'bar' }));
|
|
205
|
+
});
|
|
193
206
|
|
|
194
|
-
|
|
207
|
+
test('createStackedChart merges user options', () => {
|
|
208
|
+
Chartjs.createStackedChart(makeCanvas(), chartData, null, { options: { responsive: false } });
|
|
195
209
|
|
|
196
|
-
expect(
|
|
210
|
+
expect(mockChartInstance.config.options.responsive).toBe(false);
|
|
197
211
|
});
|
|
198
212
|
|
|
199
|
-
test('
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
'2024-02-01': {} // 31 days
|
|
203
|
-
};
|
|
213
|
+
test('createBarChart clears div and calls Chart constructor', () => {
|
|
214
|
+
const canvas = makeCanvas();
|
|
215
|
+
canvas.innerHTML = '<span>old</span>';
|
|
204
216
|
|
|
205
|
-
|
|
217
|
+
Chartjs.createBarChart(canvas, chartData);
|
|
206
218
|
|
|
207
|
-
expect(
|
|
219
|
+
expect(canvas.innerHTML).toBe('');
|
|
220
|
+
expect(global.Chart).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ type: 'bar' }));
|
|
208
221
|
});
|
|
209
222
|
|
|
210
|
-
test('
|
|
211
|
-
|
|
212
|
-
'2024-01-01': {},
|
|
213
|
-
'2024-03-31': {} // 90 days
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const result = Chartjs.getAutoGranularity(data);
|
|
223
|
+
test('createBarChart sets title when provided', () => {
|
|
224
|
+
Chartjs.createBarChart(makeCanvas(), chartData, 'My Title');
|
|
217
225
|
|
|
218
|
-
expect(
|
|
226
|
+
expect(mockChartInstance.config.options.plugins.title.display).toBe(true);
|
|
227
|
+
expect(mockChartInstance.config.options.plugins.title.text).toBe('My Title');
|
|
219
228
|
});
|
|
220
229
|
|
|
221
|
-
test('
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
+
});
|
|
226
237
|
|
|
227
|
-
|
|
238
|
+
test('createDoughnutChart clears div and calls Chart constructor', () => {
|
|
239
|
+
const canvas = makeCanvas();
|
|
240
|
+
Chartjs.createDoughnutChart(canvas, { labels: ['A'], values: [1], colors: ['#f00'] });
|
|
228
241
|
|
|
229
|
-
expect(
|
|
242
|
+
expect(canvas.innerHTML).toBe('');
|
|
243
|
+
expect(global.Chart).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ type: 'doughnut' }));
|
|
230
244
|
});
|
|
231
245
|
|
|
232
|
-
test('
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
};
|
|
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
|
+
});
|
|
238
251
|
|
|
239
|
-
|
|
252
|
+
test('title not displayed when null', () => {
|
|
253
|
+
Chartjs.createBarChart(makeCanvas(), chartData, null);
|
|
240
254
|
|
|
241
|
-
|
|
242
|
-
expect(result).toBe('week');
|
|
255
|
+
expect(mockChartInstance.config.options.plugins.title.display).toBe(false);
|
|
243
256
|
});
|
|
244
257
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
'
|
|
249
|
-
};
|
|
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 };
|
|
250
262
|
|
|
251
|
-
|
|
263
|
+
expect(() => Chartjs.createStackedChart(jq, chartData)).not.toThrow();
|
|
264
|
+
expect(global.Chart).toHaveBeenCalled();
|
|
265
|
+
});
|
|
252
266
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
267
|
+
test('createBarChart accepts jQuery-like object', () => {
|
|
268
|
+
const canvas = makeCanvas();
|
|
269
|
+
const jq = { jquery: '3.6.0', 0: canvas, length: 1 };
|
|
256
270
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
271
|
+
expect(() => Chartjs.createBarChart(jq, chartData)).not.toThrow();
|
|
272
|
+
expect(global.Chart).toHaveBeenCalled();
|
|
273
|
+
});
|
|
260
274
|
});
|
|
275
|
+
});
|
|
261
276
|
|
|
277
|
+
describe('class structure', () => {
|
|
262
278
|
test('should have all expected static methods', () => {
|
|
263
279
|
expect(typeof Chartjs.init).toBe('function');
|
|
264
280
|
expect(typeof Chartjs.createStackedChart).toBe('function');
|