test(ui): add vitest suite for utility functions and custom hooks

This commit is contained in:
josevbrito 2026-03-23 15:09:59 -03:00
parent 20163b14bd
commit 57293bd243
6 changed files with 2211 additions and 3 deletions

View File

@ -0,0 +1,39 @@
// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { renderHook } from '@testing-library/react'
import useDebounce from './useDebounce'
describe('useDebounce', () => {
beforeEach(() => {
// Freeze the system time so we can control the milliseconds
vi.useFakeTimers()
})
afterEach(() => {
// Restore normal time after the test
vi.useRealTimers()
})
it('should debounce function calls', () => {
// renderHook is the safe way to test React hooks in isolation
const { result } = renderHook(() => useDebounce())
const mockFn = vi.fn()
const debouncedFn = result.current.debounce(mockFn, 500)
// Call the function 3 times in a row very quickly
debouncedFn()
debouncedFn()
debouncedFn()
// Advance time by 499ms - The function should not have been executed yet
vi.advanceTimersByTime(499)
expect(mockFn).not.toHaveBeenCalled()
// Advance the remaining 1ms (totaling 500ms)
vi.advanceTimersByTime(1)
// The original function must be called EXACTLY ONCE
expect(mockFn).toHaveBeenCalledTimes(1)
})
})

View File

@ -0,0 +1,59 @@
import { describe, it, expect } from 'vitest'
import { getAllDiskDisplayItems, getPrimaryDiskInfo } from './useDiskDisplayData'
import { NomadDiskInfo } from '../../types/system'
// Mock data to simulate disks
const mockDisks = [
{
name: 'sda', totalSize: 1000, totalUsed: 500, percentUsed: 50,
filesystems: [{ mount: '/boot', fs: '/dev/sda1', used: 10, size: 100, percentUsed: 10 }]
},
{
name: 'nvme0n1', totalSize: 5000, totalUsed: 4000, percentUsed: 80,
filesystems: [{ mount: '/', fs: '/dev/nvme0n1p1', used: 4000, size: 5000, percentUsed: 80 }]
}
] as NomadDiskInfo[]
const mockFsSize = [
{ fs: '/dev/sda1', size: 1000, used: 500, use: 50 },
{ fs: 'tmpfs', size: 200, used: 10, use: 5 }
] as any
describe('useDiskDisplayData', () => {
describe('getAllDiskDisplayItems', () => {
it('should return empty array if no data is provided', () => {
expect(getAllDiskDisplayItems(undefined, undefined)).toEqual([])
})
it('should map NomadDiskInfo correctly and calculate formatBytes', () => {
const result = getAllDiskDisplayItems(mockDisks, undefined)
expect(result).toHaveLength(2)
expect(result[0].label).toBe('sda')
expect(result[1].label).toBe('nvme0n1')
expect(result[1].value).toBe(80)
})
it('should fallback to fsSize if disks array is empty', () => {
const result = getAllDiskDisplayItems([], mockFsSize)
// Should filter out tmpfs and only keep physical devices (/dev/)
expect(result).toHaveLength(1)
expect(result[0].label).toBe('/dev/sda1')
})
})
describe('getPrimaryDiskInfo', () => {
it('should return null if no data is provided', () => {
expect(getPrimaryDiskInfo(undefined, undefined)).toBeNull()
})
it('should return the disk mounted at root (/)', () => {
const result = getPrimaryDiskInfo(mockDisks, undefined)
expect(result).toEqual({ totalSize: 5000, totalUsed: 4000 })
})
it('should fallback to fsSize if disks is empty', () => {
const result = getPrimaryDiskInfo([], mockFsSize)
expect(result).toEqual({ totalSize: 1000, totalUsed: 500 })
})
})
})

View File

@ -0,0 +1,16 @@
import { describe, it, expect } from 'vitest'
import classNames from './classNames'
describe('classNames', () => {
it('should join multiple valid class strings with a space', () => {
expect(classNames('btn', 'btn-primary', 'active')).toBe('btn btn-primary active')
})
it('should filter out undefined, null, and empty values', () => {
expect(classNames('container', undefined, 'mx-auto', null as any, '', 'p-4')).toBe('container mx-auto p-4')
})
it('should handle empty arguments without breaking', () => {
expect(classNames()).toBe('')
})
})

View File

@ -0,0 +1,103 @@
// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach } from 'vitest'
import {
setGlobalNotificationCallback,
capitalizeFirstLetter,
formatBytes,
generateRandomString,
generateUUID,
extractFileName,
catchInternal
} from './util'
describe('util', () => {
describe('capitalizeFirstLetter', () => {
it('should capitalize the first letter of a string', () => {
expect(capitalizeFirstLetter('nomad')).toBe('Nomad')
expect(capitalizeFirstLetter('PROJECT')).toBe('PROJECT')
})
it('should handle empty or null values safely', () => {
expect(capitalizeFirstLetter('')).toBe('')
expect(capitalizeFirstLetter(null)).toBe('')
expect(capitalizeFirstLetter(undefined)).toBe('')
})
})
describe('formatBytes', () => {
it('should format bytes into human readable sizes', () => {
expect(formatBytes(0)).toBe('0 Bytes')
expect(formatBytes(1024)).toBe('1 KB')
expect(formatBytes(1048576)).toBe('1 MB')
})
it('should respect decimal places', () => {
expect(formatBytes(1500, 2)).toBe('1.46 KB')
expect(formatBytes(1500, 0)).toBe('1 KB')
})
})
describe('generateRandomString', () => {
it('should generate a string of the exact specified length', () => {
expect(generateRandomString(10)).toHaveLength(10)
expect(generateRandomString(0)).toBe('')
})
})
describe('generateUUID', () => {
it('should generate a valid UUID v4 format string', () => {
const uuid = generateUUID()
// Regex UUID
expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
})
})
describe('extractFileName', () => {
it('should extract filename from unix-style paths', () => {
expect(extractFileName('/storage/zim/wikipedia.zim')).toBe('wikipedia.zim')
})
it('should extract filename from windows-style paths', () => {
expect(extractFileName('C:\\Users\\nomad\\downloads\\map.pmtiles')).toBe('map.pmtiles')
})
it('should return the original string if no path separators exist', () => {
expect(extractFileName('just-a-file.pdf')).toBe('just-a-file.pdf')
})
})
describe('catchInternal', () => {
beforeEach(() => {
vi.restoreAllMocks()
setGlobalNotificationCallback(null as any)
})
it('should return the result of the wrapped function if successful', async () => {
const fn = vi.fn().mockResolvedValue('success data')
const wrapped = catchInternal(fn)
const result = await wrapped()
expect(result).toBe('success data')
expect(fn).toHaveBeenCalled()
})
it('should catch errors, log to console, and trigger global notification', async () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
const notificationMock = vi.fn()
setGlobalNotificationCallback(notificationMock)
const fn = vi.fn().mockRejectedValue(new Error('API Timeout'))
const wrapped = catchInternal(fn)
const result = await wrapped()
expect(result).toBeUndefined()
expect(consoleSpy).toHaveBeenCalledWith('Internal error caught:', expect.any(Error))
expect(notificationMock).toHaveBeenCalledWith({
message: expect.stringContaining('API Timeout'),
type: 'error',
duration: 5000
})
})
})
})

1990
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,5 +7,10 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Crosstalk Solutions, LLC",
"license": "ISC"
"license": "ISC",
"devDependencies": {
"@testing-library/react": "^16.3.2",
"jsdom": "^29.0.1",
"vitest": "^4.1.1"
}
}