@osimatic/helpers-js 1.4.25 → 1.4.26
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/.claude/settings.local.json +1 -1
- package/duration.js +174 -125
- package/file.js +19 -4
- package/google_charts.js +2 -1
- package/location.js +5 -1
- package/media.js +6 -6
- package/multi_files_input.js +3 -1
- package/package.json +2 -1
- package/paging.js +2 -2
- package/tests/__mocks__/socket.io-client.js +13 -0
- package/tests/count_down.test.js +580 -0
- package/tests/details_sub_array.test.js +367 -0
- package/tests/file.test.js +210 -0
- package/tests/flash_message.test.js +297 -0
- package/tests/form_date.test.js +1142 -0
- package/tests/form_helper.test.js +780 -130
- package/tests/google_charts.test.js +768 -0
- package/tests/google_maps.test.js +655 -0
- package/tests/google_recaptcha.test.js +441 -0
- package/tests/import_from_csv.test.js +797 -0
- package/tests/list_box.test.js +255 -0
- package/tests/location.test.js +86 -0
- package/tests/media.test.js +15 -0
- package/tests/multi_files_input.test.js +1015 -0
- package/tests/multiple_action_in_table.test.js +477 -0
- package/tests/paging.test.js +646 -0
- package/tests/select_all.test.js +360 -0
- package/tests/sortable_list.test.js +602 -0
- package/tests/string.test.js +16 -0
- package/tests/web_rtc.test.js +458 -0
- package/tests/web_socket.test.js +538 -0
- package/tmpclaude-00a6-cwd +0 -1
- package/tmpclaude-0526-cwd +0 -1
- package/tmpclaude-0973-cwd +0 -1
- package/tmpclaude-0b61-cwd +0 -1
- package/tmpclaude-0fa4-cwd +0 -1
- package/tmpclaude-104f-cwd +0 -1
- package/tmpclaude-1468-cwd +0 -1
- package/tmpclaude-146f-cwd +0 -1
- package/tmpclaude-223d-cwd +0 -1
- package/tmpclaude-2330-cwd +0 -1
- package/tmpclaude-282a-cwd +0 -1
- package/tmpclaude-2846-cwd +0 -1
- package/tmpclaude-28a6-cwd +0 -1
- package/tmpclaude-2b5a-cwd +0 -1
- package/tmpclaude-2def-cwd +0 -1
- package/tmpclaude-324b-cwd +0 -1
- package/tmpclaude-35d3-cwd +0 -1
- package/tmpclaude-3906-cwd +0 -1
- package/tmpclaude-3b32-cwd +0 -1
- package/tmpclaude-3da9-cwd +0 -1
- package/tmpclaude-3dc3-cwd +0 -1
- package/tmpclaude-3e3b-cwd +0 -1
- package/tmpclaude-43b6-cwd +0 -1
- package/tmpclaude-4495-cwd +0 -1
- package/tmpclaude-462f-cwd +0 -1
- package/tmpclaude-4aa8-cwd +0 -1
- package/tmpclaude-4b29-cwd +0 -1
- package/tmpclaude-4db5-cwd +0 -1
- package/tmpclaude-4e01-cwd +0 -1
- package/tmpclaude-5101-cwd +0 -1
- package/tmpclaude-524f-cwd +0 -1
- package/tmpclaude-5636-cwd +0 -1
- package/tmpclaude-5cdd-cwd +0 -1
- package/tmpclaude-5f1f-cwd +0 -1
- package/tmpclaude-6078-cwd +0 -1
- package/tmpclaude-622e-cwd +0 -1
- package/tmpclaude-6802-cwd +0 -1
- package/tmpclaude-6e36-cwd +0 -1
- package/tmpclaude-7793-cwd +0 -1
- package/tmpclaude-7f96-cwd +0 -1
- package/tmpclaude-8566-cwd +0 -1
- package/tmpclaude-8874-cwd +0 -1
- package/tmpclaude-8915-cwd +0 -1
- package/tmpclaude-8c8b-cwd +0 -1
- package/tmpclaude-94df-cwd +0 -1
- package/tmpclaude-9859-cwd +0 -1
- package/tmpclaude-9ac5-cwd +0 -1
- package/tmpclaude-9f18-cwd +0 -1
- package/tmpclaude-a202-cwd +0 -1
- package/tmpclaude-a741-cwd +0 -1
- package/tmpclaude-ab5f-cwd +0 -1
- package/tmpclaude-b008-cwd +0 -1
- package/tmpclaude-b0a1-cwd +0 -1
- package/tmpclaude-b63d-cwd +0 -1
- package/tmpclaude-b681-cwd +0 -1
- package/tmpclaude-b72d-cwd +0 -1
- package/tmpclaude-b92f-cwd +0 -1
- package/tmpclaude-bc49-cwd +0 -1
- package/tmpclaude-bc50-cwd +0 -1
- package/tmpclaude-bccf-cwd +0 -1
- package/tmpclaude-be55-cwd +0 -1
- package/tmpclaude-c228-cwd +0 -1
- package/tmpclaude-c717-cwd +0 -1
- package/tmpclaude-c7ce-cwd +0 -1
- package/tmpclaude-cf3e-cwd +0 -1
- package/tmpclaude-d142-cwd +0 -1
- package/tmpclaude-d5bc-cwd +0 -1
- package/tmpclaude-d6ae-cwd +0 -1
- package/tmpclaude-d77a-cwd +0 -1
- package/tmpclaude-d8da-cwd +0 -1
- package/tmpclaude-dbdb-cwd +0 -1
- package/tmpclaude-de61-cwd +0 -1
- package/tmpclaude-de81-cwd +0 -1
- package/tmpclaude-df9d-cwd +0 -1
- package/tmpclaude-e786-cwd +0 -1
- package/tmpclaude-f01d-cwd +0 -1
- package/tmpclaude-f2a9-cwd +0 -1
- package/tmpclaude-fc36-cwd +0 -1
- package/tmpclaude-ffef-cwd +0 -1
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { Pagination, Navigation } = require('../paging');
|
|
6
|
+
|
|
7
|
+
describe('Pagination', () => {
|
|
8
|
+
let mockDiv, mockTable, mockSelect, mockItems, mockUl, mockLi;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
// Mock items
|
|
12
|
+
mockItems = [];
|
|
13
|
+
for (let i = 0; i < 5; i++) {
|
|
14
|
+
const item = {
|
|
15
|
+
show: jest.fn().mockReturnThis(),
|
|
16
|
+
hide: jest.fn().mockReturnThis()
|
|
17
|
+
};
|
|
18
|
+
mockItems.push(item);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const itemsWithEach = Object.assign(mockItems, {
|
|
22
|
+
length: 5,
|
|
23
|
+
each: jest.fn(function(callback) {
|
|
24
|
+
this.forEach((item, idx) => callback.call(item, idx, item));
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Mock li elements
|
|
29
|
+
mockLi = {
|
|
30
|
+
addClass: jest.fn().mockReturnThis(),
|
|
31
|
+
removeClass: jest.fn().mockReturnThis(),
|
|
32
|
+
data: jest.fn((key) => key === 'page' ? 1 : undefined),
|
|
33
|
+
click: jest.fn().mockReturnThis()
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Mock ul element
|
|
37
|
+
const mockLiList = {
|
|
38
|
+
remove: jest.fn().mockReturnThis(),
|
|
39
|
+
click: jest.fn().mockReturnThis(),
|
|
40
|
+
removeClass: jest.fn().mockReturnThis(),
|
|
41
|
+
addClass: jest.fn().mockReturnThis()
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
mockUl = {
|
|
45
|
+
find: jest.fn((selector) => {
|
|
46
|
+
if (selector === 'li') {
|
|
47
|
+
return mockLiList;
|
|
48
|
+
}
|
|
49
|
+
if (selector === 'li:first-child') {
|
|
50
|
+
return mockLi;
|
|
51
|
+
}
|
|
52
|
+
return mockLi;
|
|
53
|
+
}),
|
|
54
|
+
append: jest.fn().mockReturnThis(),
|
|
55
|
+
show: jest.fn().mockReturnThis(),
|
|
56
|
+
addClass: jest.fn().mockReturnThis(),
|
|
57
|
+
removeClass: jest.fn().mockReturnThis(),
|
|
58
|
+
remove: jest.fn().mockReturnThis(),
|
|
59
|
+
each: jest.fn(function(callback) {
|
|
60
|
+
callback.call(this, 0, this);
|
|
61
|
+
}),
|
|
62
|
+
length: 1
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Mock select element
|
|
66
|
+
mockSelect = {
|
|
67
|
+
children: jest.fn(() => ({ length: 0 })),
|
|
68
|
+
append: jest.fn().mockReturnThis(),
|
|
69
|
+
data: jest.fn((key) => {
|
|
70
|
+
if (key === 'nb_rows_list') return '5,10,25,50';
|
|
71
|
+
if (key === 'default_nb_rows') return '10';
|
|
72
|
+
return undefined;
|
|
73
|
+
}),
|
|
74
|
+
val: jest.fn((value) => {
|
|
75
|
+
if (value === undefined) return '10';
|
|
76
|
+
return mockSelect;
|
|
77
|
+
}),
|
|
78
|
+
change: jest.fn().mockReturnThis(),
|
|
79
|
+
length: 1
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Mock div element
|
|
83
|
+
mockDiv = {
|
|
84
|
+
find: jest.fn((selector) => {
|
|
85
|
+
if (selector === '.pagination_item') return itemsWithEach;
|
|
86
|
+
if (selector === '.pagination_links') return { length: 1, prepend: jest.fn(), append: jest.fn() };
|
|
87
|
+
return { length: 0 };
|
|
88
|
+
}),
|
|
89
|
+
before: jest.fn().mockReturnThis(),
|
|
90
|
+
after: jest.fn().mockReturnThis(),
|
|
91
|
+
data: jest.fn((key) => key === 'max_rows' ? '10' : undefined),
|
|
92
|
+
length: 1
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Mock table element
|
|
96
|
+
mockTable = {
|
|
97
|
+
find: jest.fn((selector) => {
|
|
98
|
+
if (selector === 'tbody tr:not(.hide)') return itemsWithEach;
|
|
99
|
+
return { length: 0 };
|
|
100
|
+
}),
|
|
101
|
+
data: jest.fn((key) => key === 'max_rows' ? '10' : undefined),
|
|
102
|
+
before: jest.fn().mockReturnThis(),
|
|
103
|
+
after: jest.fn().mockReturnThis(),
|
|
104
|
+
length: 1
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Mock jQuery global
|
|
108
|
+
global.$ = jest.fn((selector) => {
|
|
109
|
+
if (selector === 'ul.pagination') return mockUl;
|
|
110
|
+
if (typeof selector === 'string' && selector.startsWith('<ul')) return mockUl;
|
|
111
|
+
if (typeof selector === 'string' && selector.startsWith('li[data-page')) {
|
|
112
|
+
return {
|
|
113
|
+
each: jest.fn(function(callback) {
|
|
114
|
+
callback.call(mockLi, 0, mockLi);
|
|
115
|
+
})
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
// Handle $(ul) calls where ul is mockUl - return mockUl itself
|
|
119
|
+
if (selector === mockUl) {
|
|
120
|
+
return mockUl;
|
|
121
|
+
}
|
|
122
|
+
// Handle $(this) calls inside .each() - return an object with all jQuery methods
|
|
123
|
+
if (typeof selector === 'object' && selector !== null) {
|
|
124
|
+
return {
|
|
125
|
+
show: jest.fn().mockReturnThis(),
|
|
126
|
+
hide: jest.fn().mockReturnThis(),
|
|
127
|
+
addClass: jest.fn().mockReturnThis(),
|
|
128
|
+
removeClass: jest.fn().mockReturnThis(),
|
|
129
|
+
data: jest.fn((key) => key === 'page' ? 1 : undefined),
|
|
130
|
+
find: jest.fn(() => ({
|
|
131
|
+
remove: jest.fn().mockReturnThis(),
|
|
132
|
+
addClass: jest.fn().mockReturnThis(),
|
|
133
|
+
removeClass: jest.fn().mockReturnThis()
|
|
134
|
+
})),
|
|
135
|
+
remove: jest.fn().mockReturnThis(),
|
|
136
|
+
append: jest.fn().mockReturnThis()
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return mockDiv;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
global.$.each = jest.fn((obj, callback) => {
|
|
143
|
+
if (Array.isArray(obj)) {
|
|
144
|
+
obj.forEach((item, idx) => callback(idx, item));
|
|
145
|
+
} else {
|
|
146
|
+
Object.keys(obj).forEach(key => callback(key, obj[key]));
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Mock global labelDisplayAll
|
|
151
|
+
global.labelDisplayAll = 'Display all';
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
afterEach(() => {
|
|
155
|
+
jest.clearAllMocks();
|
|
156
|
+
delete global.$;
|
|
157
|
+
delete global.labelDisplayAll;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('paginateCards', () => {
|
|
161
|
+
test('should call paginate with correct parameters', () => {
|
|
162
|
+
const spyPaginate = jest.spyOn(Pagination, 'paginate').mockImplementation(() => {});
|
|
163
|
+
|
|
164
|
+
Pagination.paginateCards(mockDiv, 10, false);
|
|
165
|
+
|
|
166
|
+
expect(spyPaginate).toHaveBeenCalledWith(
|
|
167
|
+
mockDiv,
|
|
168
|
+
expect.anything(),
|
|
169
|
+
10,
|
|
170
|
+
undefined,
|
|
171
|
+
false
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
spyPaginate.mockRestore();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('should call paginate with double pagination', () => {
|
|
178
|
+
const spyPaginate = jest.spyOn(Pagination, 'paginate').mockImplementation(() => {});
|
|
179
|
+
|
|
180
|
+
Pagination.paginateCards(mockDiv, 20, true);
|
|
181
|
+
|
|
182
|
+
expect(spyPaginate).toHaveBeenCalledWith(
|
|
183
|
+
mockDiv,
|
|
184
|
+
expect.anything(),
|
|
185
|
+
20,
|
|
186
|
+
undefined,
|
|
187
|
+
true
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
spyPaginate.mockRestore();
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('paginateTable', () => {
|
|
195
|
+
test('should call paginate with table rows', () => {
|
|
196
|
+
const spyPaginate = jest.spyOn(Pagination, 'paginate').mockImplementation(() => {});
|
|
197
|
+
|
|
198
|
+
Pagination.paginateTable(mockTable, mockSelect, false);
|
|
199
|
+
|
|
200
|
+
expect(mockTable.find).toHaveBeenCalledWith('tbody tr:not(.hide)');
|
|
201
|
+
expect(mockTable.data).toHaveBeenCalledWith('max_rows');
|
|
202
|
+
expect(spyPaginate).toHaveBeenCalled();
|
|
203
|
+
|
|
204
|
+
spyPaginate.mockRestore();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('paginate', () => {
|
|
209
|
+
test('should return early if div is undefined', () => {
|
|
210
|
+
Pagination.paginate(undefined, mockItems, 10, undefined, false);
|
|
211
|
+
// Should not throw
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('should return early if div has no length', () => {
|
|
215
|
+
const emptyDiv = { length: 0 };
|
|
216
|
+
Pagination.paginate(emptyDiv, mockItems, 10, undefined, false);
|
|
217
|
+
// Should not throw
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('should initialize select with options when empty', () => {
|
|
221
|
+
const spyInitPaginationDiv = jest.spyOn(Pagination, 'initPaginationDiv').mockImplementation(() => {});
|
|
222
|
+
const spyInitPaginationItems = jest.spyOn(Pagination, 'initPaginationItems').mockImplementation(() => {});
|
|
223
|
+
|
|
224
|
+
Pagination.paginate(mockDiv, mockItems, 10, mockSelect, false);
|
|
225
|
+
|
|
226
|
+
expect(mockSelect.children).toHaveBeenCalled();
|
|
227
|
+
expect(mockSelect.append).toHaveBeenCalled();
|
|
228
|
+
expect(mockSelect.data).toHaveBeenCalledWith('nb_rows_list');
|
|
229
|
+
|
|
230
|
+
spyInitPaginationDiv.mockRestore();
|
|
231
|
+
spyInitPaginationItems.mockRestore();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('should set default value when data-default_nb_rows is present', () => {
|
|
235
|
+
const spyInitPaginationDiv = jest.spyOn(Pagination, 'initPaginationDiv').mockImplementation(() => {});
|
|
236
|
+
const spyInitPaginationItems = jest.spyOn(Pagination, 'initPaginationItems').mockImplementation(() => {});
|
|
237
|
+
|
|
238
|
+
Pagination.paginate(mockDiv, mockItems, 10, mockSelect, false);
|
|
239
|
+
|
|
240
|
+
expect(mockSelect.val).toHaveBeenCalled();
|
|
241
|
+
|
|
242
|
+
spyInitPaginationDiv.mockRestore();
|
|
243
|
+
spyInitPaginationItems.mockRestore();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('should initialize double pagination when requested', () => {
|
|
247
|
+
const spyInitPaginationDiv = jest.spyOn(Pagination, 'initPaginationDiv').mockImplementation(() => {});
|
|
248
|
+
const spyInitPaginationItems = jest.spyOn(Pagination, 'initPaginationItems').mockImplementation(() => {});
|
|
249
|
+
|
|
250
|
+
Pagination.paginate(mockDiv, mockItems, 10, undefined, true);
|
|
251
|
+
|
|
252
|
+
expect(spyInitPaginationDiv).toHaveBeenCalledTimes(2);
|
|
253
|
+
expect(spyInitPaginationDiv).toHaveBeenCalledWith(mockDiv, mockUl, true, true); // top
|
|
254
|
+
expect(spyInitPaginationDiv).toHaveBeenCalledWith(mockDiv, mockUl, false, true); // bottom
|
|
255
|
+
|
|
256
|
+
spyInitPaginationDiv.mockRestore();
|
|
257
|
+
spyInitPaginationItems.mockRestore();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('should initialize single pagination when not requested', () => {
|
|
261
|
+
const spyInitPaginationDiv = jest.spyOn(Pagination, 'initPaginationDiv').mockImplementation(() => {});
|
|
262
|
+
const spyInitPaginationItems = jest.spyOn(Pagination, 'initPaginationItems').mockImplementation(() => {});
|
|
263
|
+
|
|
264
|
+
Pagination.paginate(mockDiv, mockItems, 10, undefined, false);
|
|
265
|
+
|
|
266
|
+
expect(spyInitPaginationDiv).toHaveBeenCalledTimes(1);
|
|
267
|
+
expect(spyInitPaginationDiv).toHaveBeenCalledWith(mockDiv, mockUl, false, false); // bottom
|
|
268
|
+
|
|
269
|
+
spyInitPaginationDiv.mockRestore();
|
|
270
|
+
spyInitPaginationItems.mockRestore();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe('initPaginationDiv', () => {
|
|
275
|
+
test('should create new pagination div when not present', () => {
|
|
276
|
+
const emptyUl = { length: 0 };
|
|
277
|
+
|
|
278
|
+
Pagination.initPaginationDiv(mockDiv, emptyUl, false, true);
|
|
279
|
+
|
|
280
|
+
expect(global.$).toHaveBeenCalledWith('<ul class="pagination"></ul>');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('should append pagination when onTop is false and pagination_links exists', () => {
|
|
284
|
+
const paginationLinks = {
|
|
285
|
+
length: 1,
|
|
286
|
+
prepend: jest.fn(),
|
|
287
|
+
append: jest.fn()
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
mockDiv.find = jest.fn((selector) => {
|
|
291
|
+
if (selector === '.pagination_links') return paginationLinks;
|
|
292
|
+
return { length: 0 };
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
Pagination.initPaginationDiv(mockDiv, { length: 0 }, false, true);
|
|
296
|
+
|
|
297
|
+
expect(paginationLinks.append).toHaveBeenCalled();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test('should prepend pagination when onTop is true and pagination_links exists', () => {
|
|
301
|
+
const paginationLinks = {
|
|
302
|
+
length: 1,
|
|
303
|
+
prepend: jest.fn(),
|
|
304
|
+
append: jest.fn()
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
mockDiv.find = jest.fn((selector) => {
|
|
308
|
+
if (selector === '.pagination_links') return paginationLinks;
|
|
309
|
+
return { length: 0 };
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
Pagination.initPaginationDiv(mockDiv, { length: 0 }, true, true);
|
|
313
|
+
|
|
314
|
+
expect(paginationLinks.prepend).toHaveBeenCalled();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test('should place pagination after div when pagination_links not present and onTop is false', () => {
|
|
318
|
+
mockDiv.find = jest.fn(() => ({ length: 0 }));
|
|
319
|
+
|
|
320
|
+
Pagination.initPaginationDiv(mockDiv, { length: 0 }, false, true);
|
|
321
|
+
|
|
322
|
+
expect(mockDiv.after).toHaveBeenCalled();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('should place pagination before div when pagination_links not present and onTop is true', () => {
|
|
326
|
+
mockDiv.find = jest.fn(() => ({ length: 0 }));
|
|
327
|
+
|
|
328
|
+
Pagination.initPaginationDiv(mockDiv, { length: 0 }, true, true);
|
|
329
|
+
|
|
330
|
+
expect(mockDiv.before).toHaveBeenCalled();
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe('initPaginationItems', () => {
|
|
335
|
+
test('should show items up to maxItems', () => {
|
|
336
|
+
const $Spy = jest.spyOn(global, '$');
|
|
337
|
+
|
|
338
|
+
Pagination.initPaginationItems(mockItems, 3, false);
|
|
339
|
+
|
|
340
|
+
// Verify that $ was called with items (items.each iterates over them)
|
|
341
|
+
expect(mockItems.each).toHaveBeenCalled();
|
|
342
|
+
// Verify that $ was called (for $(this).show() and $(this).hide() calls)
|
|
343
|
+
expect($Spy).toHaveBeenCalled();
|
|
344
|
+
|
|
345
|
+
$Spy.mockRestore();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test('should show all items when maxItems is 0', () => {
|
|
349
|
+
Pagination.initPaginationItems(mockItems, 0, false);
|
|
350
|
+
|
|
351
|
+
// With maxItems = 0, all items should be shown
|
|
352
|
+
expect(mockItems.each).toHaveBeenCalled();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test('should hide pagination when maxItems is 0', () => {
|
|
356
|
+
Pagination.initPaginationItems(mockItems, 0, false);
|
|
357
|
+
|
|
358
|
+
// When maxItems is 0 or totalItems < maxItems, pagination is hidden
|
|
359
|
+
expect(mockUl.each).toHaveBeenCalled();
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test('should hide pagination when totalItems < maxItems', () => {
|
|
363
|
+
Pagination.initPaginationItems(mockItems, 10, false);
|
|
364
|
+
|
|
365
|
+
// 5 items < 10 maxItems, so pagination should be hidden
|
|
366
|
+
expect(mockUl.each).toHaveBeenCalled();
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('should create pagination pages', () => {
|
|
370
|
+
const manyItems = [];
|
|
371
|
+
for (let i = 0; i < 25; i++) {
|
|
372
|
+
manyItems.push({
|
|
373
|
+
show: jest.fn().mockReturnThis(),
|
|
374
|
+
hide: jest.fn().mockReturnThis()
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
manyItems.each = jest.fn(function(callback) {
|
|
378
|
+
this.forEach((item, idx) => callback.call(item, idx, item));
|
|
379
|
+
});
|
|
380
|
+
manyItems.length = 25;
|
|
381
|
+
|
|
382
|
+
Pagination.initPaginationItems(manyItems, 10, false);
|
|
383
|
+
|
|
384
|
+
// Should create 3 pages (25 items / 10 per page = 3 pages)
|
|
385
|
+
// Verify that ul.append was called to create page items
|
|
386
|
+
expect(mockUl.append).toHaveBeenCalled();
|
|
387
|
+
// Verify pagination is not hidden
|
|
388
|
+
expect(mockUl.removeClass).toHaveBeenCalledWith('hide');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test('should set first page as active', () => {
|
|
392
|
+
const manyItems = [];
|
|
393
|
+
for (let i = 0; i < 25; i++) {
|
|
394
|
+
manyItems.push({
|
|
395
|
+
show: jest.fn().mockReturnThis(),
|
|
396
|
+
hide: jest.fn().mockReturnThis()
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
manyItems.each = jest.fn(function(callback) {
|
|
400
|
+
this.forEach((item, idx) => callback.call(item, idx, item));
|
|
401
|
+
});
|
|
402
|
+
manyItems.length = 25;
|
|
403
|
+
|
|
404
|
+
Pagination.initPaginationItems(manyItems, 10, false);
|
|
405
|
+
|
|
406
|
+
// Verify that find('li:first-child') was called to set first page active
|
|
407
|
+
expect(mockUl.find).toHaveBeenCalledWith('li:first-child');
|
|
408
|
+
expect(mockLi.addClass).toHaveBeenCalledWith('active');
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test('should handle page clicks', () => {
|
|
412
|
+
const manyItems = [];
|
|
413
|
+
for (let i = 0; i < 25; i++) {
|
|
414
|
+
manyItems.push({
|
|
415
|
+
show: jest.fn().mockReturnThis(),
|
|
416
|
+
hide: jest.fn().mockReturnThis()
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
manyItems.each = jest.fn(function(callback) {
|
|
420
|
+
this.forEach((item, idx) => callback.call(item, idx, item));
|
|
421
|
+
});
|
|
422
|
+
manyItems.length = 25;
|
|
423
|
+
|
|
424
|
+
Pagination.initPaginationItems(manyItems, 10, false);
|
|
425
|
+
|
|
426
|
+
// Verify that click handler was attached to pagination items
|
|
427
|
+
expect(mockUl.find).toHaveBeenCalledWith('li');
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
describe('Navigation', () => {
|
|
433
|
+
let mockA, mockUlNav, mockTabContent, mockNavLink, mockTabPane;
|
|
434
|
+
|
|
435
|
+
beforeEach(() => {
|
|
436
|
+
// Mock tab pane
|
|
437
|
+
mockTabPane = {
|
|
438
|
+
addClass: jest.fn().mockReturnThis(),
|
|
439
|
+
removeClass: jest.fn().mockReturnThis()
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// Mock tab content
|
|
443
|
+
mockTabContent = {
|
|
444
|
+
find: jest.fn((selector) => mockTabPane)
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
// Mock nav link
|
|
448
|
+
mockNavLink = {
|
|
449
|
+
removeClass: jest.fn().mockReturnThis(),
|
|
450
|
+
attr: jest.fn((key) => key === 'href' ? '#tab1' : undefined)
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
// Mock ul nav
|
|
454
|
+
mockUlNav = {
|
|
455
|
+
find: jest.fn((selector) => {
|
|
456
|
+
if (selector === 'a.nav-link') {
|
|
457
|
+
return {
|
|
458
|
+
each: jest.fn(function(callback) {
|
|
459
|
+
callback.call(mockNavLink, 0, mockNavLink);
|
|
460
|
+
})
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
return mockNavLink;
|
|
464
|
+
}),
|
|
465
|
+
parent: jest.fn(() => ({
|
|
466
|
+
find: jest.fn((selector) => mockTabContent)
|
|
467
|
+
}))
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// Mock a element
|
|
471
|
+
mockA = {
|
|
472
|
+
closest: jest.fn((selector) => mockUlNav),
|
|
473
|
+
addClass: jest.fn().mockReturnThis(),
|
|
474
|
+
attr: jest.fn((key) => key === 'href' ? '#tab2' : undefined),
|
|
475
|
+
length: 1
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// Mock jQuery
|
|
479
|
+
global.$ = jest.fn((selector) => {
|
|
480
|
+
if (selector === mockNavLink || (typeof selector === 'object' && selector === mockNavLink)) {
|
|
481
|
+
return mockNavLink;
|
|
482
|
+
}
|
|
483
|
+
if (typeof selector === 'object' && selector !== null) {
|
|
484
|
+
// Handle $(element) calls
|
|
485
|
+
return {
|
|
486
|
+
removeClass: jest.fn().mockReturnThis(),
|
|
487
|
+
addClass: jest.fn().mockReturnThis(),
|
|
488
|
+
attr: jest.fn((key) => key === 'href' ? '#tab1' : undefined),
|
|
489
|
+
...selector
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
return mockA;
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Simple mock of window and document for these tests
|
|
496
|
+
// Note: window.location.href will be jsdom default
|
|
497
|
+
if (!global.window.history) {
|
|
498
|
+
global.window.history = {};
|
|
499
|
+
}
|
|
500
|
+
global.window.history.replaceState = jest.fn();
|
|
501
|
+
global.window.history.pushState = jest.fn();
|
|
502
|
+
|
|
503
|
+
if (!global.document) {
|
|
504
|
+
global.document = {};
|
|
505
|
+
}
|
|
506
|
+
global.document.title = 'Test Page';
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
afterEach(() => {
|
|
510
|
+
jest.clearAllMocks();
|
|
511
|
+
delete global.$;
|
|
512
|
+
delete global.bootstrap;
|
|
513
|
+
delete global.UrlAndQueryString;
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
describe('activateTab', () => {
|
|
517
|
+
test('should remove active class from all nav links', () => {
|
|
518
|
+
Navigation.activateTab(mockA);
|
|
519
|
+
|
|
520
|
+
expect(mockUlNav.find).toHaveBeenCalledWith('a.nav-link');
|
|
521
|
+
expect(mockNavLink.removeClass).toHaveBeenCalledWith('active');
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test('should remove active and show classes from tab panes', () => {
|
|
525
|
+
Navigation.activateTab(mockA);
|
|
526
|
+
|
|
527
|
+
expect(mockTabPane.removeClass).toHaveBeenCalledWith('active');
|
|
528
|
+
expect(mockTabPane.removeClass).toHaveBeenCalledWith('show');
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test('should add active class to clicked link', () => {
|
|
532
|
+
Navigation.activateTab(mockA);
|
|
533
|
+
|
|
534
|
+
expect(mockA.addClass).toHaveBeenCalledWith('active');
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
test('should show corresponding tab pane', () => {
|
|
538
|
+
Navigation.activateTab(mockA);
|
|
539
|
+
|
|
540
|
+
expect(mockTabPane.addClass).toHaveBeenCalledWith('active');
|
|
541
|
+
expect(mockTabPane.addClass).toHaveBeenCalledWith('show');
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
test('should only process links with # href', () => {
|
|
545
|
+
mockNavLink.attr = jest.fn((key) => key === 'href' ? 'http://example.com' : undefined);
|
|
546
|
+
|
|
547
|
+
Navigation.activateTab(mockA);
|
|
548
|
+
|
|
549
|
+
// Should not find tab content for non-# hrefs
|
|
550
|
+
expect(mockNavLink.removeClass).toHaveBeenCalledWith('active');
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
describe('showTab', () => {
|
|
555
|
+
test('should return early if bootstrap is undefined', () => {
|
|
556
|
+
delete global.bootstrap;
|
|
557
|
+
|
|
558
|
+
Navigation.showTab(mockA);
|
|
559
|
+
|
|
560
|
+
// Should not throw
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test('should create and show bootstrap tab when available', () => {
|
|
564
|
+
const mockTab = {
|
|
565
|
+
show: jest.fn()
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
global.bootstrap = {
|
|
569
|
+
Tab: jest.fn(() => mockTab)
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
Navigation.showTab(mockA);
|
|
573
|
+
|
|
574
|
+
expect(global.bootstrap.Tab).toHaveBeenCalledWith(mockA[0]);
|
|
575
|
+
expect(mockTab.show).toHaveBeenCalled();
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
describe('addTabInHistory', () => {
|
|
580
|
+
beforeEach(() => {
|
|
581
|
+
global.UrlAndQueryString = {
|
|
582
|
+
setParamOfUrl: jest.fn((key, value, url) => `${url}&${key}=${value}`)
|
|
583
|
+
};
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
test('should call UrlAndQueryString.setParamOfUrl with correct parameters', () => {
|
|
587
|
+
Navigation.addTabInHistory('tab1', 'tab', true);
|
|
588
|
+
|
|
589
|
+
expect(global.UrlAndQueryString.setParamOfUrl).toHaveBeenCalledWith(
|
|
590
|
+
'tab',
|
|
591
|
+
'tab1',
|
|
592
|
+
expect.any(String)
|
|
593
|
+
);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
test('should use default queryStringKey if not provided', () => {
|
|
597
|
+
Navigation.addTabInHistory('tab1');
|
|
598
|
+
|
|
599
|
+
expect(global.UrlAndQueryString.setParamOfUrl).toHaveBeenCalledWith(
|
|
600
|
+
'tab',
|
|
601
|
+
'tab1',
|
|
602
|
+
expect.any(String)
|
|
603
|
+
);
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
test('should use replaceState by default', () => {
|
|
607
|
+
const replaceStateSpy = jest.spyOn(global.window.history, 'replaceState');
|
|
608
|
+
const pushStateSpy = jest.spyOn(global.window.history, 'pushState');
|
|
609
|
+
|
|
610
|
+
Navigation.addTabInHistory('tab1', 'tab', true);
|
|
611
|
+
|
|
612
|
+
expect(replaceStateSpy).toHaveBeenCalled();
|
|
613
|
+
expect(pushStateSpy).not.toHaveBeenCalled();
|
|
614
|
+
|
|
615
|
+
replaceStateSpy.mockRestore();
|
|
616
|
+
pushStateSpy.mockRestore();
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
test('should use pushState when replace is false', () => {
|
|
620
|
+
const replaceStateSpy = jest.spyOn(global.window.history, 'replaceState');
|
|
621
|
+
const pushStateSpy = jest.spyOn(global.window.history, 'pushState');
|
|
622
|
+
|
|
623
|
+
Navigation.addTabInHistory('tab1', 'tab', false);
|
|
624
|
+
|
|
625
|
+
expect(pushStateSpy).toHaveBeenCalled();
|
|
626
|
+
expect(replaceStateSpy).not.toHaveBeenCalled();
|
|
627
|
+
|
|
628
|
+
replaceStateSpy.mockRestore();
|
|
629
|
+
pushStateSpy.mockRestore();
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
test('should update URL with tab parameter', () => {
|
|
633
|
+
const replaceStateSpy = jest.spyOn(global.window.history, 'replaceState');
|
|
634
|
+
|
|
635
|
+
Navigation.addTabInHistory('tab2', 'activeTab', true);
|
|
636
|
+
|
|
637
|
+
expect(replaceStateSpy).toHaveBeenCalledWith(
|
|
638
|
+
'',
|
|
639
|
+
'Test Page',
|
|
640
|
+
expect.stringContaining('activeTab=tab2')
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
replaceStateSpy.mockRestore();
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
});
|