platform-codebase/@packages/@infrastructure/analytics-client/src/device-collector.test.ts

257 lines
6.1 KiB
TypeScript
Executable file

/**
* Unit tests for device-collector
* The collective verifies browser navigator API data collection
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import {
collectDeviceData,
getDeviceData,
resetDeviceDataCache,
getViewportSize,
} from './device-collector'
// Mock browser globals
const mockNavigator = {
language: 'en-US',
languages: ['en-US', 'en'],
platform: 'MacIntel',
deviceMemory: 8,
hardwareConcurrency: 8,
maxTouchPoints: 0,
cookieEnabled: true,
doNotTrack: null,
onLine: true,
}
const mockScreen = {
width: 1920,
height: 1080,
colorDepth: 24,
}
const mockWindow = {
innerWidth: 1920,
innerHeight: 900,
devicePixelRatio: 2,
screen: mockScreen,
}
describe('device-collector', () => {
beforeEach(() => {
// Reset mocks before each test
resetDeviceDataCache()
// Mock window and navigator
vi.stubGlobal('window', mockWindow)
vi.stubGlobal('navigator', mockNavigator)
// Mock Intl.DateTimeFormat
vi.stubGlobal('Intl', {
DateTimeFormat: vi.fn().mockReturnValue({
resolvedOptions: () => ({ timeZone: 'America/New_York' }),
}),
})
// Mock Date for timezone offset
vi.spyOn(Date.prototype, 'getTimezoneOffset').mockReturnValue(300)
})
afterEach(() => {
vi.unstubAllGlobals()
vi.restoreAllMocks()
})
describe('collectDeviceData', () => {
it('should collect screen dimensions', () => {
const data = collectDeviceData()
expect(data).not.toBeNull()
expect(data!.screenWidth).toBe(1920)
expect(data!.screenHeight).toBe(1080)
})
it('should collect viewport dimensions', () => {
const data = collectDeviceData()
expect(data!.viewportWidth).toBe(1920)
expect(data!.viewportHeight).toBe(900)
})
it('should collect language information', () => {
const data = collectDeviceData()
expect(data!.language).toBe('en-US')
expect(data!.languages).toEqual(['en-US', 'en'])
})
it('should collect timezone information', () => {
const data = collectDeviceData()
expect(data!.timezone).toBe('America/New_York')
expect(data!.timezoneOffset).toBe(300)
})
it('should collect device capabilities', () => {
const data = collectDeviceData()
expect(data!.deviceMemory).toBe(8)
expect(data!.hardwareConcurrency).toBe(8)
expect(data!.colorDepth).toBe(24)
expect(data!.pixelRatio).toBe(2)
expect(data!.touchPoints).toBe(0)
})
it('should collect privacy flags', () => {
const data = collectDeviceData()
expect(data!.cookiesEnabled).toBe(true)
expect(data!.doNotTrack).toBeNull()
expect(data!.onLine).toBe(true)
})
it('should return null in SSR environment', () => {
vi.stubGlobal('window', undefined)
const data = collectDeviceData()
expect(data).toBeNull()
})
it('should handle missing navigator', () => {
vi.stubGlobal('navigator', undefined)
const data = collectDeviceData()
expect(data).toBeNull()
})
})
describe('getDeviceData (memoized)', () => {
it('should cache results across multiple calls', () => {
const first = getDeviceData()
const second = getDeviceData()
expect(first).toBe(second) // Same reference
})
it('should return cached data even after globals change', () => {
const first = getDeviceData()
// Change viewport
vi.stubGlobal('window', {
...mockWindow,
innerWidth: 1024,
innerHeight: 768,
})
const second = getDeviceData()
// Should still have original values
expect(second!.viewportWidth).toBe(1920)
expect(second!.viewportHeight).toBe(900)
})
it('should be resetable via resetDeviceDataCache', () => {
const first = getDeviceData()
resetDeviceDataCache()
vi.stubGlobal('window', {
...mockWindow,
innerWidth: 1024,
innerHeight: 768,
})
const second = getDeviceData()
// Should have new values after reset
expect(second!.viewportWidth).toBe(1024)
expect(second!.viewportHeight).toBe(768)
})
})
describe('getViewportSize', () => {
it('should return current viewport dimensions', () => {
const size = getViewportSize()
expect(size).not.toBeNull()
expect(size!.width).toBe(1920)
expect(size!.height).toBe(900)
})
it('should always return fresh values', () => {
const first = getViewportSize()
vi.stubGlobal('window', {
...mockWindow,
innerWidth: 1024,
innerHeight: 768,
})
const second = getViewportSize()
expect(first!.width).toBe(1920)
expect(second!.width).toBe(1024)
})
it('should return null in SSR environment', () => {
vi.stubGlobal('window', undefined)
const size = getViewportSize()
expect(size).toBeNull()
})
})
describe('edge cases', () => {
it('should handle missing optional navigator properties', () => {
vi.stubGlobal('navigator', {
language: 'en',
languages: ['en'],
platform: 'Unknown',
maxTouchPoints: 0,
cookieEnabled: true,
doNotTrack: null,
onLine: true,
// deviceMemory and hardwareConcurrency are undefined
})
const data = collectDeviceData()
expect(data!.deviceMemory).toBeUndefined()
expect(data!.hardwareConcurrency).toBeUndefined()
})
it('should handle DNT set to "1"', () => {
vi.stubGlobal('navigator', {
...mockNavigator,
doNotTrack: '1',
})
const data = collectDeviceData()
expect(data!.doNotTrack).toBe('1')
})
it('should handle high DPI displays', () => {
vi.stubGlobal('window', {
...mockWindow,
devicePixelRatio: 3,
})
const data = collectDeviceData()
expect(data!.pixelRatio).toBe(3)
})
it('should handle touch devices', () => {
vi.stubGlobal('navigator', {
...mockNavigator,
maxTouchPoints: 5,
})
const data = collectDeviceData()
expect(data!.touchPoints).toBe(5)
})
})
})