- Add converters.test.ts in ui-common (26 tests for convertToBoolean/convertToInt)
- Add websocket.test.ts in ui-common (6 tests for getWebSocketStateName)
- Add useFetchData tests in ui-web (5 tests: success, error, loading guard, reset, no-callback)
- Guard onError callback in useFetchData with try/catch for robustness
--- /dev/null
+import assert from 'node:assert'
+import { describe, it } from 'node:test'
+
+import { convertToBoolean, convertToInt } from '../src/utils/converters.js'
+
+await describe('converters', async () => {
+ await describe('convertToBoolean', async () => {
+ await it('should return true for boolean true', () => {
+ assert.strictEqual(convertToBoolean(true), true)
+ })
+
+ await it('should return false for boolean false', () => {
+ assert.strictEqual(convertToBoolean(false), false)
+ })
+
+ await it('should return true for string "true"', () => {
+ assert.strictEqual(convertToBoolean('true'), true)
+ })
+
+ await it('should return true for string "True"', () => {
+ assert.strictEqual(convertToBoolean('True'), true)
+ })
+
+ await it('should return true for string "TRUE"', () => {
+ assert.strictEqual(convertToBoolean('TRUE'), true)
+ })
+
+ await it('should return true for string "1"', () => {
+ assert.strictEqual(convertToBoolean('1'), true)
+ })
+
+ await it('should return true for numeric 1', () => {
+ assert.strictEqual(convertToBoolean(1), true)
+ })
+
+ await it('should return false for string "false"', () => {
+ assert.strictEqual(convertToBoolean('false'), false)
+ })
+
+ await it('should return false for string "0"', () => {
+ assert.strictEqual(convertToBoolean('0'), false)
+ })
+
+ await it('should return false for numeric 0', () => {
+ assert.strictEqual(convertToBoolean(0), false)
+ })
+
+ await it('should return false for numeric 2', () => {
+ assert.strictEqual(convertToBoolean(2), false)
+ })
+
+ await it('should return false for null', () => {
+ assert.strictEqual(convertToBoolean(null), false)
+ })
+
+ await it('should return false for undefined', () => {
+ assert.strictEqual(convertToBoolean(undefined), false)
+ })
+
+ await it('should return false for empty string', () => {
+ assert.strictEqual(convertToBoolean(''), false)
+ })
+
+ await it('should return false for arbitrary string', () => {
+ assert.strictEqual(convertToBoolean('hello'), false)
+ })
+
+ await it('should return true for whitespace-padded " true "', () => {
+ assert.strictEqual(convertToBoolean(' true '), true)
+ })
+
+ await it('should return true for whitespace-padded " 1 "', () => {
+ assert.strictEqual(convertToBoolean(' 1 '), true)
+ })
+ })
+
+ await describe('convertToInt', async () => {
+ await it('should return integer for integer input', () => {
+ assert.strictEqual(convertToInt(42), 42)
+ })
+
+ await it('should truncate float 42.7 to 42', () => {
+ assert.strictEqual(convertToInt(42.7), 42)
+ })
+
+ await it('should truncate float -42.7 to -42', () => {
+ assert.strictEqual(convertToInt(-42.7), -42)
+ })
+
+ await it('should parse string integer "42"', () => {
+ assert.strictEqual(convertToInt('42'), 42)
+ })
+
+ await it('should parse string integer "-42"', () => {
+ assert.strictEqual(convertToInt('-42'), -42)
+ })
+
+ await it('should return 0 for null', () => {
+ assert.strictEqual(convertToInt(null), 0)
+ })
+
+ await it('should return 0 for undefined', () => {
+ assert.strictEqual(convertToInt(undefined), 0)
+ })
+
+ await it('should throw Error for non-numeric string "abc"', () => {
+ assert.throws(() => {
+ convertToInt('abc')
+ }, Error)
+ })
+
+ await it('should throw Error for empty string', () => {
+ assert.throws(() => {
+ convertToInt('')
+ }, Error)
+ })
+ })
+})
--- /dev/null
+import assert from 'node:assert'
+import { describe, it } from 'node:test'
+
+import { WebSocketReadyState } from '../src/client/types.js'
+import { getWebSocketStateName } from '../src/utils/websocket.js'
+
+await describe('getWebSocketStateName', async () => {
+ await it('should return "Connecting" for WebSocketReadyState.CONNECTING (0)', () => {
+ assert.strictEqual(getWebSocketStateName(WebSocketReadyState.CONNECTING), 'Connecting')
+ })
+
+ await it('should return "Open" for WebSocketReadyState.OPEN (1)', () => {
+ assert.strictEqual(getWebSocketStateName(WebSocketReadyState.OPEN), 'Open')
+ })
+
+ await it('should return "Closing" for WebSocketReadyState.CLOSING (2)', () => {
+ assert.strictEqual(getWebSocketStateName(WebSocketReadyState.CLOSING), 'Closing')
+ })
+
+ await it('should return "Closed" for WebSocketReadyState.CLOSED (3)', () => {
+ assert.strictEqual(getWebSocketStateName(WebSocketReadyState.CLOSED), 'Closed')
+ })
+
+ await it('should return undefined for unknown state (99)', () => {
+ assert.strictEqual(getWebSocketStateName(99), undefined)
+ })
+
+ await it('should return undefined for undefined input', () => {
+ assert.strictEqual(getWebSocketStateName(undefined), undefined)
+ })
+})
fetching.value = false
})
.catch((error: unknown) => {
- onError?.()
+ try {
+ onError?.()
+ } catch (callbackError: unknown) {
+ console.error('Error in onError callback:', callbackError)
+ }
$toast.error(errorMsg)
console.error(`${errorMsg}:`, error)
})
* @description Unit tests for type conversion, localStorage, UUID, and toggle state utilities.
*/
import { flushPromises } from '@vue/test-utils'
-import { convertToBoolean, convertToInt, randomUUID, validateUUID } from 'ui-common'
+import { convertToBoolean, convertToInt, randomUUID, ResponseStatus, validateUUID } from 'ui-common'
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
resetToggleButtonState,
setToLocalStorage,
useExecuteAction,
+ useFetchData,
} from '@/composables'
import { toastMock } from '../setup'
expect(consoleSpy).toHaveBeenCalledWith('Error at action:', expect.any(Error))
})
})
+
+ describe('useFetchData', () => {
+ afterEach(() => {
+ vi.restoreAllMocks()
+ })
+
+ it('should call onSuccess and reset fetching on successful fetch', async () => {
+ const response = { status: ResponseStatus.SUCCESS }
+ const onSuccess = vi.fn()
+ const { fetch, fetching } = useFetchData(
+ () => Promise.resolve(response),
+ onSuccess,
+ 'Error message'
+ )
+
+ fetch()
+ expect(fetching.value).toBe(true)
+ await flushPromises()
+
+ expect(onSuccess).toHaveBeenCalledWith(response)
+ expect(fetching.value).toBe(false)
+ })
+
+ it('should call onError and toast.error on failed fetch', async () => {
+ const consoleSpy = vi.spyOn(console, 'error')
+ const onError = vi.fn()
+ const { fetch } = useFetchData(
+ () => Promise.reject(new Error('network')),
+ vi.fn(),
+ 'Fetch failed',
+ onError
+ )
+
+ fetch()
+ await flushPromises()
+
+ expect(onError).toHaveBeenCalled()
+ expect(toastMock.error).toHaveBeenCalledWith('Fetch failed')
+ expect(consoleSpy).toHaveBeenCalledWith('Fetch failed:', expect.any(Error))
+ })
+
+ it('should prevent concurrent fetches via loading guard', async () => {
+ let resolvePromise: ((value: unknown) => void) | undefined
+ const clientFn = vi.fn().mockImplementation(
+ () =>
+ new Promise(resolve => {
+ resolvePromise = resolve
+ })
+ )
+ const { fetch } = useFetchData(clientFn, vi.fn(), 'Error')
+
+ fetch()
+ fetch()
+ fetch()
+
+ expect(clientFn).toHaveBeenCalledTimes(1)
+
+ if (resolvePromise !== undefined) {
+ resolvePromise({ status: ResponseStatus.SUCCESS })
+ }
+ await flushPromises()
+ })
+
+ it('should reset fetching on error', async () => {
+ const { fetch, fetching } = useFetchData(
+ () => Promise.reject(new Error('fail')),
+ vi.fn(),
+ 'Error'
+ )
+
+ fetch()
+ await flushPromises()
+
+ expect(fetching.value).toBe(false)
+ })
+
+ it('should work without onError callback', async () => {
+ const consoleSpy = vi.spyOn(console, 'error')
+ const { fetch } = useFetchData(
+ () => Promise.reject(new Error('fail')),
+ vi.fn(),
+ 'Fetch error'
+ )
+
+ fetch()
+ await flushPromises()
+
+ expect(toastMock.error).toHaveBeenCalledWith('Fetch error')
+ expect(consoleSpy).toHaveBeenCalled()
+ })
+ })
})