11// src/features/hardware/hooks/useHardwareData.test.ts
22import { act , renderHook , waitFor } from "@testing-library/react" ;
33import { Provider , useAtom } from "jotai" ;
4- import { beforeEach , describe , expect , it , type Mock , vi } from "vitest" ;
4+ import {
5+ afterEach ,
6+ beforeEach ,
7+ describe ,
8+ expect ,
9+ it ,
10+ type Mock ,
11+ vi ,
12+ } from "vitest" ;
513
614import { chartConfig } from "@/features/hardware/consts/chart" ;
715// Hook groups to test
@@ -13,6 +21,7 @@ import {
1321 cpuUsageHistoryAtom ,
1422 gpuFanSpeedAtom ,
1523 gpuTempAtom ,
24+ gpuUsageSourceAtom ,
1625 graphicUsageHistoryAtom ,
1726 memoryUsageHistoryAtom ,
1827 processorsUsageHistoryAtom ,
@@ -234,3 +243,99 @@ describe("useUsageUpdater processors", () => {
234243 } ) ;
235244 } ) ;
236245} ) ;
246+
247+ describe ( "useUsageUpdater – error and GpuUsageResult branches" , ( ) => {
248+ beforeEach ( ( ) => {
249+ vi . clearAllMocks ( ) ;
250+ } ) ;
251+
252+ it ( "early-returns without updating atom when result is an error" , async ( ) => {
253+ // Covers line 69: isResult(result) && isError(result) → return
254+ ( commands . getGpuUsage as Mock ) . mockResolvedValue ( {
255+ status : "error" ,
256+ error : "gpu not found" ,
257+ } ) ;
258+
259+ const { result } = renderHook (
260+ ( ) => {
261+ useUsageUpdater ( "gpu" ) ;
262+ const [ history ] = useAtom ( graphicUsageHistoryAtom ) ;
263+ return history ;
264+ } ,
265+ { wrapper : Provider } ,
266+ ) ;
267+
268+ await act ( async ( ) => {
269+ await Promise . resolve ( ) ;
270+ } ) ;
271+
272+ // Atom should stay empty because early return prevented any update
273+ expect ( result . current ) . toEqual ( [ ] ) ;
274+ } ) ;
275+
276+ it ( "extracts usage and source from GpuUsageResult object" , async ( ) => {
277+ // Covers lines 84-86: rawData is { usage, source } object
278+ ( commands . getGpuUsage as Mock ) . mockResolvedValue ( {
279+ status : "ok" ,
280+ data : { usage : 60 , source : "nvidia_smi" } ,
281+ } ) ;
282+
283+ const { result } = renderHook (
284+ ( ) => {
285+ useUsageUpdater ( "gpu" ) ;
286+ const [ history ] = useAtom ( graphicUsageHistoryAtom ) ;
287+ const [ source ] = useAtom ( gpuUsageSourceAtom ) ;
288+ return { history, source } ;
289+ } ,
290+ { wrapper : Provider } ,
291+ ) ;
292+
293+ await waitFor ( ( ) => {
294+ const { history, source } = result . current ;
295+ expect ( history [ history . length - 1 ] ) . toEqual ( 60 ) ;
296+ expect ( source ) . toEqual ( "nvidia_smi" ) ;
297+ } ) ;
298+ } ) ;
299+ } ) ;
300+
301+ describe ( "useHardwareUpdater – interval re-fetch" , ( ) => {
302+ beforeEach ( ( ) => {
303+ vi . clearAllMocks ( ) ;
304+ vi . useFakeTimers ( ) ;
305+ } ) ;
306+
307+ afterEach ( ( ) => {
308+ vi . useRealTimers ( ) ;
309+ } ) ;
310+
311+ it ( "re-fetches gpu fan data after interval tick" , async ( ) => {
312+ // Covers line 177: the setInterval callback inside useHardwareUpdater
313+ ( commands . getNvidiaGpuCooler as Mock ) . mockResolvedValue ( {
314+ status : "ok" ,
315+ data : [ { name : "fan1" , value : 1200 } ] ,
316+ } ) ;
317+
318+ renderHook (
319+ ( ) => {
320+ useHardwareUpdater ( "gpu" , "fan" ) ;
321+ return useAtom ( gpuFanSpeedAtom ) ;
322+ } ,
323+ { wrapper : Provider } ,
324+ ) ;
325+
326+ // Initial fetch
327+ await act ( async ( ) => {
328+ await Promise . resolve ( ) ;
329+ } ) ;
330+
331+ expect ( commands . getNvidiaGpuCooler ) . toHaveBeenCalledTimes ( 1 ) ;
332+
333+ // Advance by interval (10 s) to trigger re-fetch
334+ await act ( async ( ) => {
335+ vi . advanceTimersByTime ( 10000 ) ;
336+ await Promise . resolve ( ) ;
337+ } ) ;
338+
339+ expect ( commands . getNvidiaGpuCooler ) . toHaveBeenCalledTimes ( 2 ) ;
340+ } ) ;
341+ } ) ;
0 commit comments