|
1 | 1 | import { describe, it, expect, vi, beforeEach } from 'vitest'; |
2 | 2 | import { initBrowserEcho } from '../src/client'; |
| 3 | +import * as coreIndex from '../src/index'; |
3 | 4 |
|
4 | 5 | describe('@browser-echo/core', () => { |
5 | 6 | beforeEach(() => { |
@@ -36,4 +37,73 @@ describe('@browser-echo/core', () => { |
36 | 37 | (navigator as any).sendBeacon = originalSendBeacon; |
37 | 38 | } |
38 | 39 | }); |
| 40 | + |
| 41 | + it('uses sendBeacon when available', async () => { |
| 42 | + const originalSendBeacon = (navigator as any).sendBeacon; |
| 43 | + const sendBeaconSpy = vi.fn().mockReturnValue(true); |
| 44 | + (navigator as any).sendBeacon = sendBeaconSpy; |
| 45 | + |
| 46 | + const fetchSpy = vi.spyOn(globalThis, 'fetch' as any).mockResolvedValue({} as any); |
| 47 | + |
| 48 | + initBrowserEcho({ route: '/__client-logs', include: ['log'], preserveConsole: true }); |
| 49 | + console.log('hello beacon'); |
| 50 | + await new Promise(r => setTimeout(r, 350)); |
| 51 | + |
| 52 | + expect(sendBeaconSpy).toHaveBeenCalledTimes(1); |
| 53 | + expect(fetchSpy).not.toHaveBeenCalled(); |
| 54 | + |
| 55 | + fetchSpy.mockRestore(); |
| 56 | + (navigator as any).sendBeacon = originalSendBeacon; |
| 57 | + }); |
| 58 | + |
| 59 | + it('flushes immediately when batch size threshold is reached', async () => { |
| 60 | + const originalSendBeacon = (navigator as any).sendBeacon; |
| 61 | + try { delete (navigator as any).sendBeacon; } catch {} |
| 62 | + |
| 63 | + const fetchSpy = vi.spyOn(globalThis, 'fetch' as any).mockResolvedValue({} as any); |
| 64 | + |
| 65 | + initBrowserEcho({ route: '/__client-logs', include: ['log'], batch: { size: 2, interval: 1000 } }); |
| 66 | + |
| 67 | + console.log('a'); |
| 68 | + console.log('b'); // reaching batch size triggers flush |
| 69 | + |
| 70 | + await new Promise(r => setTimeout(r, 50)); |
| 71 | + expect(fetchSpy).toHaveBeenCalledTimes(1); |
| 72 | + |
| 73 | + fetchSpy.mockRestore(); |
| 74 | + if (originalSendBeacon !== undefined) { |
| 75 | + (navigator as any).sendBeacon = originalSendBeacon; |
| 76 | + } |
| 77 | + }); |
| 78 | + |
| 79 | + it('does not call original console when preserveConsole is false', async () => { |
| 80 | + const originalSendBeacon = (navigator as any).sendBeacon; |
| 81 | + try { delete (navigator as any).sendBeacon; } catch {} |
| 82 | + const fetchSpy = vi.spyOn(globalThis, 'fetch' as any).mockResolvedValue({} as any); |
| 83 | + |
| 84 | + // Install a stub as the original console.log to detect invocations |
| 85 | + const originalConsoleLog = console.log; |
| 86 | + let originalCalled = 0; |
| 87 | + // @ts-ignore |
| 88 | + console.log = function stub() { originalCalled++; } as any; |
| 89 | + |
| 90 | + initBrowserEcho({ route: '/__client-logs', include: ['log'], preserveConsole: false }); |
| 91 | + console.log('should not call original'); |
| 92 | + |
| 93 | + await new Promise(r => setTimeout(r, 50)); |
| 94 | + expect(originalCalled).toBe(0); |
| 95 | + |
| 96 | + // Cleanup |
| 97 | + console.log = originalConsoleLog; |
| 98 | + fetchSpy.mockRestore(); |
| 99 | + if (originalSendBeacon !== undefined) { |
| 100 | + (navigator as any).sendBeacon = originalSendBeacon; |
| 101 | + } |
| 102 | + }); |
| 103 | + |
| 104 | + it('public API exports stay unchanged', () => { |
| 105 | + const keys = Object.keys(coreIndex as any).sort(); |
| 106 | + expect(keys).toEqual(['initBrowserEcho']); |
| 107 | + expect(typeof (coreIndex as any).initBrowserEcho).toBe('function'); |
| 108 | + }); |
39 | 109 | }); |
0 commit comments