@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,441 @@
|
|
|
1
|
+
const { GoogleRecaptcha } = require('../google_recaptcha');
|
|
2
|
+
|
|
3
|
+
describe('GoogleRecaptcha', () => {
|
|
4
|
+
let mockGrecaptcha;
|
|
5
|
+
let consoleLogSpy;
|
|
6
|
+
let consoleErrorSpy;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
// Mock grecaptcha
|
|
10
|
+
mockGrecaptcha = {
|
|
11
|
+
render: jest.fn((id, config) => {
|
|
12
|
+
return `widget-${id}`;
|
|
13
|
+
}),
|
|
14
|
+
reset: jest.fn()
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
global.grecaptcha = mockGrecaptcha;
|
|
18
|
+
|
|
19
|
+
// Spy on console methods
|
|
20
|
+
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
21
|
+
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
22
|
+
|
|
23
|
+
// Clear static properties
|
|
24
|
+
delete GoogleRecaptcha.config;
|
|
25
|
+
delete GoogleRecaptcha.googleCaptchaRendersCallback;
|
|
26
|
+
delete GoogleRecaptcha.grecaptchaWidgets;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
jest.clearAllMocks();
|
|
31
|
+
consoleLogSpy.mockRestore();
|
|
32
|
+
consoleErrorSpy.mockRestore();
|
|
33
|
+
delete global.grecaptcha;
|
|
34
|
+
delete GoogleRecaptcha.config;
|
|
35
|
+
delete GoogleRecaptcha.googleCaptchaRendersCallback;
|
|
36
|
+
delete GoogleRecaptcha.grecaptchaWidgets;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('setConfig', () => {
|
|
40
|
+
test('should set config', () => {
|
|
41
|
+
const config = {
|
|
42
|
+
sitekey: 'test-site-key',
|
|
43
|
+
theme: 'light'
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
GoogleRecaptcha.setConfig(config);
|
|
47
|
+
|
|
48
|
+
expect(GoogleRecaptcha.config).toBe(config);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('should overwrite existing config', () => {
|
|
52
|
+
const config1 = { sitekey: 'key1' };
|
|
53
|
+
const config2 = { sitekey: 'key2' };
|
|
54
|
+
|
|
55
|
+
GoogleRecaptcha.setConfig(config1);
|
|
56
|
+
expect(GoogleRecaptcha.config).toBe(config1);
|
|
57
|
+
|
|
58
|
+
GoogleRecaptcha.setConfig(config2);
|
|
59
|
+
expect(GoogleRecaptcha.config).toBe(config2);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('onLoad', () => {
|
|
64
|
+
test('should log onLoad message', () => {
|
|
65
|
+
GoogleRecaptcha.onLoad();
|
|
66
|
+
|
|
67
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('GoogleRecaptcha.onLoad');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('should return early when grecaptcha is undefined', () => {
|
|
71
|
+
delete global.grecaptcha;
|
|
72
|
+
|
|
73
|
+
GoogleRecaptcha.onLoad();
|
|
74
|
+
|
|
75
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('var grecaptcha undefined');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('should return early when grecaptcha.render is not a function', () => {
|
|
79
|
+
global.grecaptcha = { render: null };
|
|
80
|
+
|
|
81
|
+
GoogleRecaptcha.onLoad();
|
|
82
|
+
|
|
83
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('var grecaptcha undefined');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('should initialize googleCaptchaRendersCallback if undefined', () => {
|
|
87
|
+
GoogleRecaptcha.onLoad();
|
|
88
|
+
|
|
89
|
+
expect(Array.isArray(GoogleRecaptcha.googleCaptchaRendersCallback)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('should execute all callbacks in googleCaptchaRendersCallback', () => {
|
|
93
|
+
const callback1 = jest.fn();
|
|
94
|
+
const callback2 = jest.fn();
|
|
95
|
+
const callback3 = jest.fn();
|
|
96
|
+
|
|
97
|
+
GoogleRecaptcha.googleCaptchaRendersCallback = [callback1, callback2, callback3];
|
|
98
|
+
|
|
99
|
+
GoogleRecaptcha.onLoad();
|
|
100
|
+
|
|
101
|
+
expect(callback1).toHaveBeenCalled();
|
|
102
|
+
expect(callback2).toHaveBeenCalled();
|
|
103
|
+
expect(callback3).toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('should clear googleCaptchaRendersCallback after execution', () => {
|
|
107
|
+
const callback = jest.fn();
|
|
108
|
+
GoogleRecaptcha.googleCaptchaRendersCallback = [callback];
|
|
109
|
+
|
|
110
|
+
GoogleRecaptcha.onLoad();
|
|
111
|
+
|
|
112
|
+
expect(GoogleRecaptcha.googleCaptchaRendersCallback).toEqual([]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('should handle empty callback array', () => {
|
|
116
|
+
GoogleRecaptcha.googleCaptchaRendersCallback = [];
|
|
117
|
+
|
|
118
|
+
expect(() => {
|
|
119
|
+
GoogleRecaptcha.onLoad();
|
|
120
|
+
}).not.toThrow();
|
|
121
|
+
|
|
122
|
+
expect(GoogleRecaptcha.googleCaptchaRendersCallback).toEqual([]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('should execute callbacks with correct context', () => {
|
|
126
|
+
let contextThis;
|
|
127
|
+
const callback = jest.fn(function() {
|
|
128
|
+
contextThis = this;
|
|
129
|
+
});
|
|
130
|
+
GoogleRecaptcha.googleCaptchaRendersCallback = [callback];
|
|
131
|
+
|
|
132
|
+
GoogleRecaptcha.onLoad();
|
|
133
|
+
|
|
134
|
+
expect(callback).toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('render', () => {
|
|
139
|
+
test('should add render callback and execute it', () => {
|
|
140
|
+
const id = 'recaptcha-1';
|
|
141
|
+
GoogleRecaptcha.setConfig({ sitekey: 'test' });
|
|
142
|
+
|
|
143
|
+
GoogleRecaptcha.render(id);
|
|
144
|
+
|
|
145
|
+
// Callback should have been executed and array cleared
|
|
146
|
+
expect(GoogleRecaptcha.googleCaptchaRendersCallback).toEqual([]);
|
|
147
|
+
// The callback should have called reset which renders the captcha
|
|
148
|
+
expect(mockGrecaptcha.render).toHaveBeenCalledWith(id, { sitekey: 'test' });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('should call reset through callback', () => {
|
|
152
|
+
const id = 'recaptcha-1';
|
|
153
|
+
GoogleRecaptcha.setConfig({ sitekey: 'test' });
|
|
154
|
+
|
|
155
|
+
GoogleRecaptcha.render(id);
|
|
156
|
+
|
|
157
|
+
// The render method should have triggered reset which calls grecaptcha.render
|
|
158
|
+
expect(mockGrecaptcha.render).toHaveBeenCalledWith(id, { sitekey: 'test' });
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('should trigger onLoad', () => {
|
|
162
|
+
const onLoadSpy = jest.spyOn(GoogleRecaptcha, 'onLoad');
|
|
163
|
+
|
|
164
|
+
GoogleRecaptcha.render('recaptcha-1');
|
|
165
|
+
|
|
166
|
+
expect(onLoadSpy).toHaveBeenCalled();
|
|
167
|
+
|
|
168
|
+
onLoadSpy.mockRestore();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('reset', () => {
|
|
173
|
+
beforeEach(() => {
|
|
174
|
+
GoogleRecaptcha.setConfig({ sitekey: 'test-key' });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('should return early when grecaptcha is undefined', () => {
|
|
178
|
+
delete global.grecaptcha;
|
|
179
|
+
|
|
180
|
+
GoogleRecaptcha.reset('recaptcha-1');
|
|
181
|
+
|
|
182
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('var grecaptcha.render undefined');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('should return early when grecaptcha.render is not a function', () => {
|
|
186
|
+
global.grecaptcha = { render: null };
|
|
187
|
+
|
|
188
|
+
GoogleRecaptcha.reset('recaptcha-1');
|
|
189
|
+
|
|
190
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('var grecaptcha.render undefined');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test('should initialize grecaptchaWidgets if undefined', () => {
|
|
194
|
+
GoogleRecaptcha.reset('recaptcha-1');
|
|
195
|
+
|
|
196
|
+
expect(Array.isArray(GoogleRecaptcha.grecaptchaWidgets)).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('should render new widget when widget does not exist', () => {
|
|
200
|
+
const id = 'recaptcha-1';
|
|
201
|
+
|
|
202
|
+
GoogleRecaptcha.reset(id);
|
|
203
|
+
|
|
204
|
+
expect(mockGrecaptcha.render).toHaveBeenCalledWith(id, { sitekey: 'test-key' });
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('should store widget id after rendering', () => {
|
|
208
|
+
const id = 'recaptcha-1';
|
|
209
|
+
|
|
210
|
+
GoogleRecaptcha.reset(id);
|
|
211
|
+
|
|
212
|
+
expect(GoogleRecaptcha.grecaptchaWidgets[id]).toBe('widget-recaptcha-1');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('should reset existing widget instead of creating new one', () => {
|
|
216
|
+
const id = 'recaptcha-1';
|
|
217
|
+
|
|
218
|
+
// First call - creates widget
|
|
219
|
+
GoogleRecaptcha.reset(id);
|
|
220
|
+
expect(mockGrecaptcha.render).toHaveBeenCalledTimes(1);
|
|
221
|
+
|
|
222
|
+
jest.clearAllMocks();
|
|
223
|
+
|
|
224
|
+
// Second call - resets widget
|
|
225
|
+
GoogleRecaptcha.reset(id);
|
|
226
|
+
expect(mockGrecaptcha.render).not.toHaveBeenCalled();
|
|
227
|
+
expect(mockGrecaptcha.reset).toHaveBeenCalledWith('widget-recaptcha-1');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('should handle multiple widgets independently', () => {
|
|
231
|
+
GoogleRecaptcha.reset('recaptcha-1');
|
|
232
|
+
GoogleRecaptcha.reset('recaptcha-2');
|
|
233
|
+
|
|
234
|
+
expect(GoogleRecaptcha.grecaptchaWidgets['recaptcha-1']).toBe('widget-recaptcha-1');
|
|
235
|
+
expect(GoogleRecaptcha.grecaptchaWidgets['recaptcha-2']).toBe('widget-recaptcha-2');
|
|
236
|
+
expect(mockGrecaptcha.render).toHaveBeenCalledTimes(2);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('should catch and log error during grecaptcha.render', () => {
|
|
240
|
+
const error = new Error('Render failed');
|
|
241
|
+
mockGrecaptcha.render.mockImplementation(() => {
|
|
242
|
+
throw error;
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
GoogleRecaptcha.reset('recaptcha-1');
|
|
246
|
+
|
|
247
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Exception during grecaptcha.render', error);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('should not store widget id when render throws error', () => {
|
|
251
|
+
mockGrecaptcha.render.mockImplementation(() => {
|
|
252
|
+
throw new Error('Render failed');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
GoogleRecaptcha.reset('recaptcha-1');
|
|
256
|
+
|
|
257
|
+
expect(GoogleRecaptcha.grecaptchaWidgets['recaptcha-1']).toBeUndefined();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('should use config passed to setConfig', () => {
|
|
261
|
+
const config = {
|
|
262
|
+
sitekey: 'my-site-key',
|
|
263
|
+
theme: 'dark',
|
|
264
|
+
size: 'compact'
|
|
265
|
+
};
|
|
266
|
+
GoogleRecaptcha.setConfig(config);
|
|
267
|
+
|
|
268
|
+
GoogleRecaptcha.reset('recaptcha-1');
|
|
269
|
+
|
|
270
|
+
expect(mockGrecaptcha.render).toHaveBeenCalledWith('recaptcha-1', config);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe('addRenderCallback', () => {
|
|
275
|
+
test('should initialize googleCaptchaRendersCallback if undefined', () => {
|
|
276
|
+
const callback = jest.fn();
|
|
277
|
+
|
|
278
|
+
GoogleRecaptcha.addRenderCallback(callback);
|
|
279
|
+
|
|
280
|
+
expect(Array.isArray(GoogleRecaptcha.googleCaptchaRendersCallback)).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('should add callback to array and execute immediately', () => {
|
|
284
|
+
const callback1 = jest.fn();
|
|
285
|
+
const callback2 = jest.fn();
|
|
286
|
+
|
|
287
|
+
GoogleRecaptcha.addRenderCallback(callback1);
|
|
288
|
+
// Callback is executed immediately and array is cleared
|
|
289
|
+
expect(callback1).toHaveBeenCalled();
|
|
290
|
+
expect(GoogleRecaptcha.googleCaptchaRendersCallback).toEqual([]);
|
|
291
|
+
|
|
292
|
+
GoogleRecaptcha.addRenderCallback(callback2);
|
|
293
|
+
expect(callback2).toHaveBeenCalled();
|
|
294
|
+
expect(GoogleRecaptcha.googleCaptchaRendersCallback).toEqual([]);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('should log addRenderCallback message', () => {
|
|
298
|
+
GoogleRecaptcha.addRenderCallback(jest.fn());
|
|
299
|
+
|
|
300
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('GoogleRecaptcha.addRenderCallback');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('should call onLoad after adding callback', () => {
|
|
304
|
+
const onLoadSpy = jest.spyOn(GoogleRecaptcha, 'onLoad');
|
|
305
|
+
|
|
306
|
+
GoogleRecaptcha.addRenderCallback(jest.fn());
|
|
307
|
+
|
|
308
|
+
expect(onLoadSpy).toHaveBeenCalled();
|
|
309
|
+
|
|
310
|
+
onLoadSpy.mockRestore();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('should execute callback immediately if grecaptcha is loaded', () => {
|
|
314
|
+
const callback = jest.fn();
|
|
315
|
+
|
|
316
|
+
GoogleRecaptcha.addRenderCallback(callback);
|
|
317
|
+
|
|
318
|
+
expect(callback).toHaveBeenCalled();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test('should not execute callback if grecaptcha is not loaded', () => {
|
|
322
|
+
delete global.grecaptcha;
|
|
323
|
+
const callback = jest.fn();
|
|
324
|
+
|
|
325
|
+
GoogleRecaptcha.addRenderCallback(callback);
|
|
326
|
+
|
|
327
|
+
expect(callback).not.toHaveBeenCalled();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test('should keep callback in array if grecaptcha not loaded', () => {
|
|
331
|
+
delete global.grecaptcha;
|
|
332
|
+
const callback = jest.fn();
|
|
333
|
+
|
|
334
|
+
GoogleRecaptcha.addRenderCallback(callback);
|
|
335
|
+
|
|
336
|
+
expect(GoogleRecaptcha.googleCaptchaRendersCallback).toContain(callback);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe('integration scenarios', () => {
|
|
341
|
+
test('should render multiple captchas sequentially', () => {
|
|
342
|
+
GoogleRecaptcha.setConfig({ sitekey: 'test-key' });
|
|
343
|
+
|
|
344
|
+
GoogleRecaptcha.render('captcha-1');
|
|
345
|
+
GoogleRecaptcha.render('captcha-2');
|
|
346
|
+
GoogleRecaptcha.render('captcha-3');
|
|
347
|
+
|
|
348
|
+
// Execute callbacks
|
|
349
|
+
GoogleRecaptcha.googleCaptchaRendersCallback.forEach(cb => cb());
|
|
350
|
+
|
|
351
|
+
expect(mockGrecaptcha.render).toHaveBeenCalledWith('captcha-1', { sitekey: 'test-key' });
|
|
352
|
+
expect(mockGrecaptcha.render).toHaveBeenCalledWith('captcha-2', { sitekey: 'test-key' });
|
|
353
|
+
expect(mockGrecaptcha.render).toHaveBeenCalledWith('captcha-3', { sitekey: 'test-key' });
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test('should handle render and reset lifecycle', () => {
|
|
357
|
+
GoogleRecaptcha.setConfig({ sitekey: 'test-key' });
|
|
358
|
+
|
|
359
|
+
// Render
|
|
360
|
+
GoogleRecaptcha.render('captcha-1');
|
|
361
|
+
GoogleRecaptcha.googleCaptchaRendersCallback.forEach(cb => cb());
|
|
362
|
+
|
|
363
|
+
expect(mockGrecaptcha.render).toHaveBeenCalledTimes(1);
|
|
364
|
+
|
|
365
|
+
jest.clearAllMocks();
|
|
366
|
+
|
|
367
|
+
// Reset should reuse widget
|
|
368
|
+
GoogleRecaptcha.reset('captcha-1');
|
|
369
|
+
expect(mockGrecaptcha.render).not.toHaveBeenCalled();
|
|
370
|
+
expect(mockGrecaptcha.reset).toHaveBeenCalled();
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test('should queue callbacks when grecaptcha not loaded yet', () => {
|
|
374
|
+
delete global.grecaptcha;
|
|
375
|
+
|
|
376
|
+
const callback1 = jest.fn();
|
|
377
|
+
const callback2 = jest.fn();
|
|
378
|
+
|
|
379
|
+
GoogleRecaptcha.addRenderCallback(callback1);
|
|
380
|
+
GoogleRecaptcha.addRenderCallback(callback2);
|
|
381
|
+
|
|
382
|
+
expect(callback1).not.toHaveBeenCalled();
|
|
383
|
+
expect(callback2).not.toHaveBeenCalled();
|
|
384
|
+
|
|
385
|
+
// Simulate grecaptcha loading
|
|
386
|
+
global.grecaptcha = mockGrecaptcha;
|
|
387
|
+
GoogleRecaptcha.onLoad();
|
|
388
|
+
|
|
389
|
+
expect(callback1).toHaveBeenCalled();
|
|
390
|
+
expect(callback2).toHaveBeenCalled();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test('should handle config changes between renders', () => {
|
|
394
|
+
GoogleRecaptcha.setConfig({ sitekey: 'key1' });
|
|
395
|
+
GoogleRecaptcha.reset('captcha-1');
|
|
396
|
+
|
|
397
|
+
expect(mockGrecaptcha.render).toHaveBeenCalledWith('captcha-1', { sitekey: 'key1' });
|
|
398
|
+
|
|
399
|
+
jest.clearAllMocks();
|
|
400
|
+
|
|
401
|
+
GoogleRecaptcha.setConfig({ sitekey: 'key2' });
|
|
402
|
+
GoogleRecaptcha.reset('captcha-2');
|
|
403
|
+
|
|
404
|
+
expect(mockGrecaptcha.render).toHaveBeenCalledWith('captcha-2', { sitekey: 'key2' });
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test('should handle errors gracefully and continue working', () => {
|
|
408
|
+
GoogleRecaptcha.setConfig({ sitekey: 'test-key' });
|
|
409
|
+
|
|
410
|
+
// First render fails
|
|
411
|
+
mockGrecaptcha.render.mockImplementationOnce(() => {
|
|
412
|
+
throw new Error('Render failed');
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
GoogleRecaptcha.reset('captcha-1');
|
|
416
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
417
|
+
|
|
418
|
+
// Second render succeeds
|
|
419
|
+
GoogleRecaptcha.reset('captcha-2');
|
|
420
|
+
expect(GoogleRecaptcha.grecaptchaWidgets['captcha-2']).toBe('widget-captcha-2');
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test('should reset same captcha multiple times', () => {
|
|
424
|
+
GoogleRecaptcha.setConfig({ sitekey: 'test-key' });
|
|
425
|
+
|
|
426
|
+
// First reset - renders
|
|
427
|
+
GoogleRecaptcha.reset('captcha-1');
|
|
428
|
+
expect(mockGrecaptcha.render).toHaveBeenCalledTimes(1);
|
|
429
|
+
|
|
430
|
+
jest.clearAllMocks();
|
|
431
|
+
|
|
432
|
+
// Second reset - resets
|
|
433
|
+
GoogleRecaptcha.reset('captcha-1');
|
|
434
|
+
expect(mockGrecaptcha.reset).toHaveBeenCalledTimes(1);
|
|
435
|
+
|
|
436
|
+
// Third reset - resets again
|
|
437
|
+
GoogleRecaptcha.reset('captcha-1');
|
|
438
|
+
expect(mockGrecaptcha.reset).toHaveBeenCalledTimes(2);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
});
|