|
1 | | -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; |
| 1 | +import { beforeEach, describe, expect, it, vi } from 'vitest'; |
2 | 2 |
|
3 | 3 | import { shouldRetryTurnstileErrorCode } from '../captcha/turnstile'; |
4 | 4 | import type { CaptchaOptions } from '../captcha/types'; |
@@ -250,146 +250,3 @@ describe('Nonce support', () => { |
250 | 250 | }); |
251 | 251 | }); |
252 | 252 | }); |
253 | | - |
254 | | -describe('getTurnstileToken container guard', () => { |
255 | | - let mockRender: ReturnType<typeof vi.fn>; |
256 | | - let mockReset: ReturnType<typeof vi.fn>; |
257 | | - let mockRemove: ReturnType<typeof vi.fn>; |
258 | | - |
259 | | - beforeEach(() => { |
260 | | - vi.useFakeTimers(); |
261 | | - vi.resetModules(); |
262 | | - |
263 | | - mockRender = vi.fn(); |
264 | | - mockReset = vi.fn(); |
265 | | - mockRemove = vi.fn(); |
266 | | - |
267 | | - (window as any).turnstile = { |
268 | | - render: mockRender, |
269 | | - reset: mockReset, |
270 | | - remove: mockRemove, |
271 | | - }; |
272 | | - }); |
273 | | - |
274 | | - afterEach(() => { |
275 | | - vi.useRealTimers(); |
276 | | - delete (window as any).turnstile; |
277 | | - document.body.innerHTML = ''; |
278 | | - }); |
279 | | - |
280 | | - const baseOpts: CaptchaOptions = { |
281 | | - siteKey: 'test-site-key', |
282 | | - widgetType: 'invisible', |
283 | | - invisibleSiteKey: 'test-invisible-key', |
284 | | - captchaProvider: 'turnstile', |
285 | | - }; |
286 | | - |
287 | | - it('should reject immediately when container is removed before retry fires', async () => { |
288 | | - const { getTurnstileToken } = await import('../captcha/turnstile'); |
289 | | - |
290 | | - let errorCallback: (code: string) => void; |
291 | | - |
292 | | - mockRender.mockImplementation((_selector: string, opts: any) => { |
293 | | - errorCallback = opts['error-callback']; |
294 | | - return 'widget-1'; |
295 | | - }); |
296 | | - |
297 | | - const tokenPromise = getTurnstileToken(baseOpts); |
298 | | - // Attach handler early to prevent PromiseRejectionHandledWarning |
299 | | - const rejection = tokenPromise.catch(e => e); |
300 | | - // Flush microtask queue so async setup (loadCaptcha, container creation) completes |
301 | | - await vi.advanceTimersByTimeAsync(0); |
302 | | - |
303 | | - // Trigger a retriable error |
304 | | - errorCallback!('300010'); |
305 | | - |
306 | | - // Remove the invisible container before the retry setTimeout fires |
307 | | - const invisibleWidget = document.querySelector('.clerk-invisible-captcha'); |
308 | | - if (invisibleWidget) { |
309 | | - document.body.removeChild(invisibleWidget); |
310 | | - } |
311 | | - |
312 | | - // Advance past the 250ms retry delay |
313 | | - await vi.advanceTimersByTimeAsync(300); |
314 | | - |
315 | | - const error = await rejection; |
316 | | - expect(error).toMatchObject({ |
317 | | - captchaError: expect.stringContaining('300010'), |
318 | | - }); |
319 | | - |
320 | | - expect(mockReset).not.toHaveBeenCalled(); |
321 | | - }); |
322 | | - |
323 | | - it('should proceed with captcha.reset when container still exists', async () => { |
324 | | - const { getTurnstileToken } = await import('../captcha/turnstile'); |
325 | | - |
326 | | - let errorCallback: (code: string) => void; |
327 | | - |
328 | | - mockRender.mockImplementation((_selector: string, opts: any) => { |
329 | | - errorCallback = opts['error-callback']; |
330 | | - return 'widget-1'; |
331 | | - }); |
332 | | - |
333 | | - const tokenPromise = getTurnstileToken(baseOpts); |
334 | | - await vi.advanceTimersByTimeAsync(0); |
335 | | - |
336 | | - // Trigger a retriable error - container still exists |
337 | | - errorCallback!('300010'); |
338 | | - |
339 | | - // Advance past the 250ms retry delay (container is still in the DOM) |
340 | | - await vi.advanceTimersByTimeAsync(300); |
341 | | - |
342 | | - expect(mockReset).toHaveBeenCalledWith('widget-1'); |
343 | | - |
344 | | - // Trigger a non-retriable error to end the test (retries exhausted after 2 more) |
345 | | - errorCallback!('300010'); |
346 | | - await vi.advanceTimersByTimeAsync(300); |
347 | | - errorCallback!('300010'); |
348 | | - |
349 | | - await expect(tokenPromise).rejects.toMatchObject({ |
350 | | - captchaError: expect.stringContaining('300010'), |
351 | | - }); |
352 | | - }); |
353 | | - |
354 | | - it('should include all accumulated error codes when rejecting due to missing container', async () => { |
355 | | - const { getTurnstileToken } = await import('../captcha/turnstile'); |
356 | | - |
357 | | - let errorCallback: (code: string) => void; |
358 | | - |
359 | | - mockRender.mockImplementation((_selector: string, opts: any) => { |
360 | | - errorCallback = opts['error-callback']; |
361 | | - return 'widget-1'; |
362 | | - }); |
363 | | - |
364 | | - const tokenPromise = getTurnstileToken(baseOpts); |
365 | | - const rejection = tokenPromise.catch(e => e); |
366 | | - await vi.advanceTimersByTimeAsync(0); |
367 | | - |
368 | | - // First error triggers retry |
369 | | - errorCallback!('600010'); |
370 | | - |
371 | | - // Let the first retry fire (container still exists) |
372 | | - await vi.advanceTimersByTimeAsync(300); |
373 | | - expect(mockReset).toHaveBeenCalledTimes(1); |
374 | | - |
375 | | - // Second error triggers another retry |
376 | | - errorCallback!('600010'); |
377 | | - |
378 | | - // Remove container before second retry fires |
379 | | - const invisibleWidget = document.querySelector('.clerk-invisible-captcha'); |
380 | | - if (invisibleWidget) { |
381 | | - document.body.removeChild(invisibleWidget); |
382 | | - } |
383 | | - |
384 | | - await vi.advanceTimersByTimeAsync(300); |
385 | | - |
386 | | - // Should reject with both error codes |
387 | | - const error = await rejection; |
388 | | - expect(error).toMatchObject({ |
389 | | - captchaError: expect.stringContaining('600010,600010'), |
390 | | - }); |
391 | | - |
392 | | - // captcha.reset should only have been called once (the first retry) |
393 | | - expect(mockReset).toHaveBeenCalledTimes(1); |
394 | | - }); |
395 | | -}); |
0 commit comments