]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
test(ui): add tests for converters, websocket utils, and useFetchData
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 17 Apr 2026 01:08:51 +0000 (03:08 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 17 Apr 2026 01:08:51 +0000 (03:08 +0200)
- 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

ui/common/tests/converters.test.ts [new file with mode: 0644]
ui/common/tests/websocket.test.ts [new file with mode: 0644]
ui/web/src/composables/Utils.ts
ui/web/tests/unit/Utils.test.ts

diff --git a/ui/common/tests/converters.test.ts b/ui/common/tests/converters.test.ts
new file mode 100644 (file)
index 0000000..70c85b1
--- /dev/null
@@ -0,0 +1,118 @@
+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)
+    })
+  })
+})
diff --git a/ui/common/tests/websocket.test.ts b/ui/common/tests/websocket.test.ts
new file mode 100644 (file)
index 0000000..0b9fc87
--- /dev/null
@@ -0,0 +1,31 @@
+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)
+  })
+})
index 405d517fa48e695ad2f6fbaf677ce24060dbcb1a..9e001dbc69395786f704fa3fa62e6d748fc75fc2 100644 (file)
@@ -130,7 +130,11 @@ export const useFetchData = (
           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)
         })
index 743fbb884c91651327b4915f9eff532da723b25a..f9e3d4f4d1910b27f2a78693748917a392b7c3b3 100644 (file)
@@ -3,7 +3,7 @@
  * @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 {
@@ -13,6 +13,7 @@ import {
   resetToggleButtonState,
   setToLocalStorage,
   useExecuteAction,
+  useFetchData,
 } from '@/composables'
 
 import { toastMock } from '../setup'
@@ -329,4 +330,95 @@ describe('Utils', () => {
       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()
+    })
+  })
 })