mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 02:13:41 +00:00
fix: Resolve TypeScript strict mode errors in hook tests
- Add type assertions to useErrorPopoverState test rerender calls - Use non-null assertions for callback invocations in useBluetoothDeviceListener tests - Fix type inference issues with union types (number | undefined, string | null) - All 91 tests passing with proper TypeScript compliance 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f2b01c59e1
commit
eff8e15179
6 changed files with 132 additions and 80 deletions
|
|
@ -30,13 +30,13 @@ describe("useErrorPopoverState", () => {
|
|||
pyodideError: null,
|
||||
hasError,
|
||||
}),
|
||||
{ initialProps: { machineError: undefined } },
|
||||
{ initialProps: { machineError: undefined as number | undefined } },
|
||||
);
|
||||
|
||||
expect(result.current.isOpen).toBe(false);
|
||||
|
||||
// Error appears
|
||||
rerender({ machineError: 1 });
|
||||
rerender({ machineError: 1 as number | undefined });
|
||||
expect(result.current.isOpen).toBe(true);
|
||||
});
|
||||
|
||||
|
|
@ -49,12 +49,12 @@ describe("useErrorPopoverState", () => {
|
|||
pyodideError: null,
|
||||
hasError,
|
||||
}),
|
||||
{ initialProps: { machineErrorMessage: null } },
|
||||
{ initialProps: { machineErrorMessage: null as string | null } },
|
||||
);
|
||||
|
||||
expect(result.current.isOpen).toBe(false);
|
||||
|
||||
rerender({ machineErrorMessage: "Error occurred" });
|
||||
rerender({ machineErrorMessage: "Error occurred" as string | null });
|
||||
expect(result.current.isOpen).toBe(true);
|
||||
});
|
||||
|
||||
|
|
@ -67,12 +67,12 @@ describe("useErrorPopoverState", () => {
|
|||
pyodideError,
|
||||
hasError,
|
||||
}),
|
||||
{ initialProps: { pyodideError: null } },
|
||||
{ initialProps: { pyodideError: null as string | null } },
|
||||
);
|
||||
|
||||
expect(result.current.isOpen).toBe(false);
|
||||
|
||||
rerender({ pyodideError: "Pyodide error" });
|
||||
rerender({ pyodideError: "Pyodide error" as string | null });
|
||||
expect(result.current.isOpen).toBe(true);
|
||||
});
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ describe("useErrorPopoverState", () => {
|
|||
});
|
||||
|
||||
it("should track manual dismissal", async () => {
|
||||
const { result, rerender } = renderHook(
|
||||
const { result } = renderHook(
|
||||
({ machineError }) =>
|
||||
useErrorPopoverState({
|
||||
machineError,
|
||||
|
|
@ -218,7 +218,15 @@ describe("useErrorPopoverState", () => {
|
|||
|
||||
it("should handle multiple error sources", () => {
|
||||
const { result, rerender } = renderHook(
|
||||
({ machineError, machineErrorMessage, pyodideError }) =>
|
||||
({
|
||||
machineError,
|
||||
machineErrorMessage,
|
||||
pyodideError,
|
||||
}: {
|
||||
machineError: number | undefined;
|
||||
machineErrorMessage: string | null;
|
||||
pyodideError: string | null;
|
||||
}) =>
|
||||
useErrorPopoverState({
|
||||
machineError,
|
||||
machineErrorMessage,
|
||||
|
|
@ -227,9 +235,9 @@ describe("useErrorPopoverState", () => {
|
|||
}),
|
||||
{
|
||||
initialProps: {
|
||||
machineError: undefined,
|
||||
machineErrorMessage: null,
|
||||
pyodideError: null,
|
||||
machineError: undefined as number | undefined,
|
||||
machineErrorMessage: null as string | null,
|
||||
pyodideError: null as string | null,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
@ -238,34 +246,34 @@ describe("useErrorPopoverState", () => {
|
|||
|
||||
// Machine error appears
|
||||
rerender({
|
||||
machineError: 1,
|
||||
machineErrorMessage: null,
|
||||
pyodideError: null,
|
||||
machineError: 1 as number | undefined,
|
||||
machineErrorMessage: null as string | null,
|
||||
pyodideError: null as string | null,
|
||||
});
|
||||
expect(result.current.isOpen).toBe(true);
|
||||
|
||||
// Additional pyodide error
|
||||
rerender({
|
||||
machineError: 1,
|
||||
machineErrorMessage: null,
|
||||
pyodideError: "Pyodide error",
|
||||
machineError: 1 as number | undefined,
|
||||
machineErrorMessage: null as string | null,
|
||||
pyodideError: "Pyodide error" as string | null,
|
||||
});
|
||||
expect(result.current.isOpen).toBe(true);
|
||||
|
||||
// Clear machine error but pyodide error remains
|
||||
rerender({
|
||||
machineError: 0,
|
||||
machineErrorMessage: null,
|
||||
pyodideError: "Pyodide error",
|
||||
machineError: 0 as number | undefined,
|
||||
machineErrorMessage: null as string | null,
|
||||
pyodideError: "Pyodide error" as string | null,
|
||||
});
|
||||
// Should stay open because pyodide error still exists
|
||||
expect(result.current.isOpen).toBe(true);
|
||||
|
||||
// Clear all errors
|
||||
rerender({
|
||||
machineError: 0,
|
||||
machineErrorMessage: null,
|
||||
pyodideError: null,
|
||||
machineError: 0 as number | undefined,
|
||||
machineErrorMessage: null as string | null,
|
||||
pyodideError: null as string | null,
|
||||
});
|
||||
expect(result.current.isOpen).toBe(false);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ describe("useMachinePolling", () => {
|
|||
|
||||
const { result } = renderHook(() =>
|
||||
useMachinePolling({
|
||||
machineStatus: MachineStatus.READY,
|
||||
machineStatus: MachineStatus.IDLE,
|
||||
patternInfo: null,
|
||||
onStatusRefresh,
|
||||
onProgressRefresh,
|
||||
|
|
@ -45,7 +45,7 @@ describe("useMachinePolling", () => {
|
|||
|
||||
const { result } = renderHook(() =>
|
||||
useMachinePolling({
|
||||
machineStatus: MachineStatus.READY,
|
||||
machineStatus: MachineStatus.IDLE,
|
||||
patternInfo: null,
|
||||
onStatusRefresh,
|
||||
onProgressRefresh,
|
||||
|
|
@ -139,7 +139,7 @@ describe("useMachinePolling", () => {
|
|||
const mocks2 = createMocks();
|
||||
const { result: result2 } = renderHook(() =>
|
||||
useMachinePolling({
|
||||
machineStatus: MachineStatus.READY,
|
||||
machineStatus: MachineStatus.IDLE,
|
||||
patternInfo: null,
|
||||
...mocks2,
|
||||
shouldCheckResumablePattern: () => false,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import { renderHook, waitFor, act } from "@testing-library/react";
|
||||
import { useBluetoothDeviceListener } from "./useBluetoothDeviceListener";
|
||||
import type { BluetoothDevice } from "../../types/electron";
|
||||
|
||||
|
|
@ -23,7 +23,11 @@ describe("useBluetoothDeviceListener", () => {
|
|||
|
||||
it("should return isSupported=true when Electron API is available", () => {
|
||||
// Mock Electron API
|
||||
(window as { electronAPI: { onBluetoothDeviceList: () => void } }).electronAPI = {
|
||||
(
|
||||
window as unknown as {
|
||||
electronAPI: { onBluetoothDeviceList: () => void };
|
||||
}
|
||||
).electronAPI = {
|
||||
onBluetoothDeviceList: vi.fn(),
|
||||
};
|
||||
|
||||
|
|
@ -34,7 +38,11 @@ describe("useBluetoothDeviceListener", () => {
|
|||
|
||||
it("should register IPC listener when Electron API is available", () => {
|
||||
const mockListener = vi.fn();
|
||||
(window as { electronAPI: { onBluetoothDeviceList: typeof mockListener } }).electronAPI = {
|
||||
(
|
||||
window as unknown as {
|
||||
electronAPI: { onBluetoothDeviceList: typeof mockListener };
|
||||
}
|
||||
).electronAPI = {
|
||||
onBluetoothDeviceList: mockListener,
|
||||
};
|
||||
|
||||
|
|
@ -45,13 +53,20 @@ describe("useBluetoothDeviceListener", () => {
|
|||
});
|
||||
|
||||
it("should update devices when listener receives data", async () => {
|
||||
let deviceListCallback: ((devices: BluetoothDevice[]) => void) | null = null;
|
||||
let deviceListCallback: ((devices: BluetoothDevice[]) => void) | null =
|
||||
null;
|
||||
|
||||
const mockListener = vi.fn((callback: (devices: BluetoothDevice[]) => void) => {
|
||||
const mockListener = vi.fn(
|
||||
(callback: (devices: BluetoothDevice[]) => void) => {
|
||||
deviceListCallback = callback;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
(window as { electronAPI: { onBluetoothDeviceList: typeof mockListener } }).electronAPI = {
|
||||
(
|
||||
window as unknown as {
|
||||
electronAPI: { onBluetoothDeviceList: typeof mockListener };
|
||||
}
|
||||
).electronAPI = {
|
||||
onBluetoothDeviceList: mockListener,
|
||||
};
|
||||
|
||||
|
|
@ -61,11 +76,14 @@ describe("useBluetoothDeviceListener", () => {
|
|||
|
||||
// Simulate device list update
|
||||
const mockDevices: BluetoothDevice[] = [
|
||||
{ id: "device1", name: "Device 1", address: "00:11:22:33:44:55", paired: false },
|
||||
{ id: "device2", name: "Device 2", address: "AA:BB:CC:DD:EE:FF", paired: true },
|
||||
{ deviceId: "device1", deviceName: "Device 1" },
|
||||
{ deviceId: "device2", deviceName: "Device 2" },
|
||||
];
|
||||
|
||||
deviceListCallback?.(mockDevices);
|
||||
// Trigger the callback
|
||||
act(() => {
|
||||
deviceListCallback!(mockDevices);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.devices).toEqual(mockDevices);
|
||||
|
|
@ -73,20 +91,29 @@ describe("useBluetoothDeviceListener", () => {
|
|||
});
|
||||
|
||||
it("should set isScanning=true when empty device list received", async () => {
|
||||
let deviceListCallback: ((devices: BluetoothDevice[]) => void) | null = null;
|
||||
let deviceListCallback: ((devices: BluetoothDevice[]) => void) | null =
|
||||
null;
|
||||
|
||||
const mockListener = vi.fn((callback: (devices: BluetoothDevice[]) => void) => {
|
||||
const mockListener = vi.fn(
|
||||
(callback: (devices: BluetoothDevice[]) => void) => {
|
||||
deviceListCallback = callback;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
(window as { electronAPI: { onBluetoothDeviceList: typeof mockListener } }).electronAPI = {
|
||||
(
|
||||
window as unknown as {
|
||||
electronAPI: { onBluetoothDeviceList: typeof mockListener };
|
||||
}
|
||||
).electronAPI = {
|
||||
onBluetoothDeviceList: mockListener,
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useBluetoothDeviceListener());
|
||||
|
||||
// Simulate empty device list (scanning in progress)
|
||||
deviceListCallback?.([]);
|
||||
act(() => {
|
||||
deviceListCallback!([]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isScanning).toBe(true);
|
||||
|
|
@ -95,29 +122,40 @@ describe("useBluetoothDeviceListener", () => {
|
|||
});
|
||||
|
||||
it("should set isScanning=false when devices are received", async () => {
|
||||
let deviceListCallback: ((devices: BluetoothDevice[]) => void) | null = null;
|
||||
let deviceListCallback: ((devices: BluetoothDevice[]) => void) | null =
|
||||
null;
|
||||
|
||||
const mockListener = vi.fn((callback: (devices: BluetoothDevice[]) => void) => {
|
||||
const mockListener = vi.fn(
|
||||
(callback: (devices: BluetoothDevice[]) => void) => {
|
||||
deviceListCallback = callback;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
(window as { electronAPI: { onBluetoothDeviceList: typeof mockListener } }).electronAPI = {
|
||||
(
|
||||
window as unknown as {
|
||||
electronAPI: { onBluetoothDeviceList: typeof mockListener };
|
||||
}
|
||||
).electronAPI = {
|
||||
onBluetoothDeviceList: mockListener,
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useBluetoothDeviceListener());
|
||||
|
||||
// First update: empty list (scanning)
|
||||
deviceListCallback?.([]);
|
||||
act(() => {
|
||||
deviceListCallback!([]);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(result.current.isScanning).toBe(true);
|
||||
});
|
||||
|
||||
// Second update: devices found (stop scanning indicator)
|
||||
const mockDevices: BluetoothDevice[] = [
|
||||
{ id: "device1", name: "Device 1", address: "00:11:22:33:44:55", paired: false },
|
||||
{ deviceId: "device1", deviceName: "Device 1" },
|
||||
];
|
||||
deviceListCallback?.(mockDevices);
|
||||
act(() => {
|
||||
deviceListCallback!(mockDevices);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isScanning).toBe(false);
|
||||
|
|
@ -125,14 +163,21 @@ describe("useBluetoothDeviceListener", () => {
|
|||
expect(result.current.devices).toEqual(mockDevices);
|
||||
});
|
||||
|
||||
it("should call optional callback when devices change", () => {
|
||||
let deviceListCallback: ((devices: BluetoothDevice[]) => void) | null = null;
|
||||
it("should call optional callback when devices change", async () => {
|
||||
let deviceListCallback: ((devices: BluetoothDevice[]) => void) | null =
|
||||
null;
|
||||
|
||||
const mockListener = vi.fn((callback: (devices: BluetoothDevice[]) => void) => {
|
||||
const mockListener = vi.fn(
|
||||
(callback: (devices: BluetoothDevice[]) => void) => {
|
||||
deviceListCallback = callback;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
(window as { electronAPI: { onBluetoothDeviceList: typeof mockListener } }).electronAPI = {
|
||||
(
|
||||
window as unknown as {
|
||||
electronAPI: { onBluetoothDeviceList: typeof mockListener };
|
||||
}
|
||||
).electronAPI = {
|
||||
onBluetoothDeviceList: mockListener,
|
||||
};
|
||||
|
||||
|
|
@ -140,11 +185,15 @@ describe("useBluetoothDeviceListener", () => {
|
|||
renderHook(() => useBluetoothDeviceListener(onDevicesChanged));
|
||||
|
||||
const mockDevices: BluetoothDevice[] = [
|
||||
{ id: "device1", name: "Device 1", address: "00:11:22:33:44:55", paired: false },
|
||||
{ deviceId: "device1", deviceName: "Device 1" },
|
||||
];
|
||||
|
||||
deviceListCallback?.(mockDevices);
|
||||
act(() => {
|
||||
deviceListCallback!(mockDevices);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onDevicesChanged).toHaveBeenCalledWith(mockDevices);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,10 +18,9 @@ describe("useAutoScroll", () => {
|
|||
const scrollIntoViewMock = vi.fn();
|
||||
mockElement.scrollIntoView = scrollIntoViewMock;
|
||||
|
||||
const { result, rerender } = renderHook(
|
||||
({ dep }) => useAutoScroll(dep),
|
||||
{ initialProps: { dep: 0 } },
|
||||
);
|
||||
const { result, rerender } = renderHook(({ dep }) => useAutoScroll(dep), {
|
||||
initialProps: { dep: 0 },
|
||||
});
|
||||
|
||||
// Attach mock element to ref
|
||||
(result.current as { current: HTMLElement }).current = mockElement;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { describe, it, expect, vi } from "vitest";
|
||||
import { renderHook } from "@testing-library/react";
|
||||
import { useClickOutside } from "./useClickOutside";
|
||||
import { useRef } from "react";
|
||||
import { useRef, type RefObject } from "react";
|
||||
|
||||
describe("useClickOutside", () => {
|
||||
it("should call handler when clicking outside element", () => {
|
||||
|
|
@ -91,8 +91,10 @@ describe("useClickOutside", () => {
|
|||
const handler = vi.fn();
|
||||
const { result } = renderHook(() => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const excludeRef = useRef<HTMLButtonElement>(null);
|
||||
useClickOutside(ref, handler, { excludeRefs: [excludeRef] });
|
||||
const excludeRef = useRef<HTMLElement>(null);
|
||||
useClickOutside(ref, handler, {
|
||||
excludeRefs: [excludeRef as unknown as RefObject<HTMLElement>],
|
||||
});
|
||||
return { ref, excludeRef };
|
||||
});
|
||||
|
||||
|
|
@ -102,7 +104,7 @@ describe("useClickOutside", () => {
|
|||
document.body.appendChild(excludedElement);
|
||||
|
||||
(result.current.ref as { current: HTMLDivElement }).current = element;
|
||||
(result.current.excludeRef as { current: HTMLButtonElement }).current =
|
||||
(result.current.excludeRef as { current: HTMLElement }).current =
|
||||
excludedElement;
|
||||
|
||||
// Click on excluded element
|
||||
|
|
|
|||
|
|
@ -23,12 +23,9 @@ describe("usePrevious", () => {
|
|||
});
|
||||
|
||||
it("should handle different types of values", () => {
|
||||
const { result, rerender } = renderHook(
|
||||
({ value }) => usePrevious(value),
|
||||
{
|
||||
const { result, rerender } = renderHook(({ value }) => usePrevious(value), {
|
||||
initialProps: { value: "hello" as string | number | null },
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current).toBeUndefined();
|
||||
|
||||
|
|
@ -43,12 +40,9 @@ describe("usePrevious", () => {
|
|||
const obj1 = { name: "first" };
|
||||
const obj2 = { name: "second" };
|
||||
|
||||
const { result, rerender } = renderHook(
|
||||
({ value }) => usePrevious(value),
|
||||
{
|
||||
const { result, rerender } = renderHook(({ value }) => usePrevious(value), {
|
||||
initialProps: { value: obj1 },
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
expect(result.current).toBeUndefined();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue