Skip to content

Commit 5936631

Browse files
committed
chore: add browser tests
chore: wip
1 parent 8c5e086 commit 5936631

File tree

1 file changed

+238
-5
lines changed

1 file changed

+238
-5
lines changed

test/browser.test.ts

+238-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,240 @@
1-
import { expect, test } from 'bun:test'
1+
import { afterEach, beforeEach, describe, expect, spyOn, test } from 'bun:test'
2+
import { Logger } from '../src'
23

3-
test('set button text', () => {
4-
document.body.innerHTML = `<button>My button</button>`
5-
const button = document.querySelector('button')
6-
expect(button?.innerText).toEqual('My button')
4+
describe('Logger Browser Tests', () => {
5+
// Mock storage implementation
6+
let mockLocalStorage: Record<string, string> = {}
7+
8+
// Store original console methods
9+
const originalConsole = { ...console }
10+
11+
// Store mock calls for tests to use
12+
let logCalls: any[][] = []
13+
14+
beforeEach(() => {
15+
// Reset test data
16+
logCalls = []
17+
mockLocalStorage = {}
18+
19+
// Reset DOM for each test
20+
document.body.innerHTML = '<div id="log-container"></div>'
21+
22+
// Mock localStorage methods individually
23+
if (typeof localStorage !== 'undefined') {
24+
spyOn(localStorage, 'getItem').mockImplementation((key: string) => mockLocalStorage[key] || null)
25+
spyOn(localStorage, 'setItem').mockImplementation((key: string, value: string) => {
26+
mockLocalStorage[key] = value
27+
})
28+
spyOn(localStorage, 'removeItem').mockImplementation((key: string) => {
29+
delete mockLocalStorage[key]
30+
})
31+
spyOn(localStorage, 'clear').mockImplementation(() => {
32+
mockLocalStorage = {}
33+
})
34+
}
35+
36+
// Mock console methods with our own implementation
37+
spyOn(console, 'log').mockImplementation((...args) => {
38+
logCalls.push(args)
39+
originalConsole.log(...args)
40+
})
41+
spyOn(console, 'error')
42+
spyOn(console, 'warn')
43+
})
44+
45+
afterEach(() => {
46+
// Restore original console
47+
Object.keys(originalConsole).forEach((key) => {
48+
(console as any)[key] = originalConsole[key as keyof typeof console]
49+
})
50+
})
51+
52+
test('should detect browser environment correctly', () => {
53+
const logger = new Logger('browser-test')
54+
expect(logger.isBrowser).toBe(true)
55+
expect(logger.isServer).toBe(false)
56+
})
57+
58+
test('should output logs to console in browser environment', async () => {
59+
const logger = new Logger('console-test')
60+
61+
await logger.info('Info message')
62+
await logger.error('Error message')
63+
await logger.warn('Warning message')
64+
await logger.debug('Debug message')
65+
await logger.success('Success message')
66+
67+
// Check if console.log was called
68+
expect(logCalls.length).toBeGreaterThan(0)
69+
70+
// Verify different message types were logged
71+
const messages = logCalls.map(args => args[0]).join('\n')
72+
expect(messages).toContain('Info message')
73+
expect(messages).toContain('Error message')
74+
expect(messages).toContain('Warning message')
75+
})
76+
77+
test('should format logs correctly for browser console', async () => {
78+
const logger = new Logger('format-test', {
79+
format: 'json',
80+
})
81+
82+
await logger.info('Test message')
83+
84+
// Get logs and check formatting
85+
expect(logCalls.length).toBeGreaterThan(0)
86+
87+
// Find the log entry for Test message
88+
const testMessageLog = logCalls.find(args =>
89+
args[0] && typeof args[0] === 'string' && args[0].includes('Test message'),
90+
)
91+
expect(testMessageLog).toBeDefined()
92+
93+
if (testMessageLog) {
94+
// Verify log contains expected properties
95+
expect(testMessageLog[0]).toContain('Test message')
96+
expect(testMessageLog[0]).toContain('format-test')
97+
expect(testMessageLog[0]).toContain('info')
98+
}
99+
})
100+
101+
// Make DOM event test async to properly wait for logs
102+
test('should handle DOM events for logging', async () => {
103+
const logger = new Logger('dom-event-test')
104+
const button = document.createElement('button')
105+
button.textContent = 'Click me'
106+
document.body.appendChild(button)
107+
108+
// Use a Promise to ensure we can wait for the log
109+
const logPromise = new Promise<void>((resolve) => {
110+
button.addEventListener('click', () => {
111+
// Synchronously call info and then resolve the promise
112+
logger.info('Button clicked').then(() => resolve())
113+
})
114+
})
115+
116+
// Simulate click
117+
button.click()
118+
119+
// Wait for the logging to complete
120+
await logPromise
121+
122+
// Check that log was called
123+
expect(logCalls.length).toBeGreaterThan(0)
124+
125+
// Verify the button click log
126+
const buttonClickLog = logCalls.find(args =>
127+
args[0] && typeof args[0] === 'string' && args[0].includes('Button clicked'),
128+
)
129+
expect(buttonClickLog).toBeDefined()
130+
})
131+
132+
test('should handle errors in browser context', async () => {
133+
const logger = new Logger('error-test')
134+
135+
// Directly log an error instead of relying on window.onerror
136+
await logger.error('Window error: Test error')
137+
138+
// Check error was logged
139+
expect(logCalls.length).toBeGreaterThan(0)
140+
141+
// Verify the error log
142+
const errorLog = logCalls.find(args =>
143+
args[0] && typeof args[0] === 'string' && args[0].includes('Window error'),
144+
)
145+
expect(errorLog).toBeDefined()
146+
147+
// Test custom error handling by simulating the process
148+
let customErrorHandled = false
149+
150+
// Set up a direct error handler function that uses our logger
151+
const handleError = (message: string): boolean => {
152+
customErrorHandled = true
153+
// We don't need to await this since we're checking customErrorHandled
154+
void logger.error(`Custom error: ${message}`)
155+
return true
156+
}
157+
158+
// Call the handler directly instead of depending on event dispatching
159+
handleError('Custom test error')
160+
161+
// Verify our handler was invoked
162+
expect(customErrorHandled).toBe(true)
163+
})
164+
165+
test('should correctly use timers for performance tracking', async () => {
166+
const logger = new Logger('timer-test')
167+
const timerLabel = 'test-operation'
168+
169+
// Start timer
170+
const endTimer = logger.time(timerLabel)
171+
172+
// Simulate some work
173+
await new Promise(resolve => setTimeout(resolve, 10))
174+
175+
// End timer and wait for it to complete
176+
await endTimer()
177+
178+
// Check timer logs
179+
expect(logCalls.length).toBeGreaterThan(0)
180+
181+
// Verify timer completion log
182+
const timerCompletionLog = logCalls.find(args =>
183+
args[0] && typeof args[0] === 'string'
184+
&& args[0].includes(timerLabel) && args[0].includes('completed in'),
185+
)
186+
expect(timerCompletionLog).toBeDefined()
187+
})
188+
189+
test('should handle subloggers correctly in browser', async () => {
190+
const mainLogger = new Logger('main-browser')
191+
const subLogger = mainLogger.extend('sub')
192+
193+
await subLogger.info('Sublogger message')
194+
195+
// Check that sublogger prefixed correctly
196+
expect(logCalls.length).toBeGreaterThan(0)
197+
198+
const subLoggerMessageFound = logCalls.some(args =>
199+
args[0] && typeof args[0] === 'string'
200+
&& args[0].includes('main-browser:sub') && args[0].includes('Sublogger message'),
201+
)
202+
expect(subLoggerMessageFound).toBe(true)
203+
})
204+
205+
test('should respect log levels in browser environment', async () => {
206+
// Clear previous logs
207+
logCalls = []
208+
209+
const logger = new Logger('level-test', {
210+
level: 'warning', // Only warning and above
211+
})
212+
213+
await logger.debug('Debug message')
214+
await logger.info('Info message')
215+
await logger.warn('Warning message')
216+
await logger.error('Error message')
217+
218+
// Verify correct logs were captured based on level
219+
const debugFound = logCalls.some(args =>
220+
args[0] && typeof args[0] === 'string' && args[0].includes('Debug message'),
221+
)
222+
const infoFound = logCalls.some(args =>
223+
args[0] && typeof args[0] === 'string' && args[0].includes('Info message'),
224+
)
225+
const warningFound = logCalls.some(args =>
226+
args[0] && typeof args[0] === 'string' && args[0].includes('Warning message'),
227+
)
228+
const errorFound = logCalls.some(args =>
229+
args[0] && typeof args[0] === 'string' && args[0].includes('Error message'),
230+
)
231+
232+
// Debug and info should be filtered out due to log level setting
233+
expect(debugFound).toBe(false)
234+
expect(infoFound).toBe(false)
235+
236+
// Warning and error should be present
237+
expect(warningFound).toBe(true)
238+
expect(errorFound).toBe(true)
239+
})
7240
})

0 commit comments

Comments
 (0)