aed0b20875
- Consolidate duplicate date formatting utilities - Use shared formatters across backend and frontend - Clean up test mocks to use consistent test data - Remove redundant formatting functions
196 lines
5.2 KiB
TypeScript
196 lines
5.2 KiB
TypeScript
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { useMedications } from "../../hooks/useMedications";
|
|
|
|
describe("useMedications", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
ok: true,
|
|
json: () => Promise.resolve([]),
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("initializes with empty state", () => {
|
|
const { result } = renderHook(() => useMedications());
|
|
|
|
expect(result.current.meds).toEqual([]);
|
|
expect(result.current.loading).toBe(false);
|
|
expect(result.current.saving).toBe(false);
|
|
expect(result.current.uploadingImage).toBe(false);
|
|
});
|
|
|
|
it("loads medications from API", async () => {
|
|
const mockMeds = [{ id: 1, name: "TestMed", packCount: 1 }];
|
|
|
|
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
|
ok: true,
|
|
json: () => Promise.resolve(mockMeds),
|
|
});
|
|
|
|
const { result } = renderHook(() => useMedications());
|
|
|
|
act(() => {
|
|
result.current.loadMeds();
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.meds).toEqual(mockMeds);
|
|
});
|
|
|
|
expect(fetch).toHaveBeenCalledWith("/api/medications", { credentials: "include" });
|
|
});
|
|
|
|
it("handles API error gracefully", async () => {
|
|
(global.fetch as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error("Network error"));
|
|
|
|
const { result } = renderHook(() => useMedications());
|
|
|
|
act(() => {
|
|
result.current.loadMeds();
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.meds).toEqual([]);
|
|
});
|
|
|
|
it("handles non-array response", async () => {
|
|
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
|
ok: true,
|
|
json: () => Promise.resolve({ not: "array" }),
|
|
});
|
|
|
|
const { result } = renderHook(() => useMedications());
|
|
|
|
act(() => {
|
|
result.current.loadMeds();
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.meds).toEqual([]);
|
|
});
|
|
|
|
it("deletes medication", async () => {
|
|
const mockMeds = [{ id: 1, name: "TestMed" }];
|
|
(global.fetch as ReturnType<typeof vi.fn>)
|
|
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockMeds) })
|
|
.mockResolvedValueOnce({ ok: true })
|
|
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve([]) });
|
|
|
|
const mockResetForm = vi.fn();
|
|
const { result } = renderHook(() => useMedications());
|
|
|
|
// First load meds
|
|
act(() => {
|
|
result.current.loadMeds();
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.meds).toEqual(mockMeds);
|
|
});
|
|
|
|
// Then delete
|
|
await act(async () => {
|
|
await result.current.deleteMed(1, 1, mockResetForm);
|
|
});
|
|
|
|
expect(fetch).toHaveBeenCalledWith("/api/medications/1", { method: "DELETE", credentials: "include" });
|
|
expect(mockResetForm).toHaveBeenCalled();
|
|
});
|
|
|
|
it("does not call resetForm if editingId does not match", async () => {
|
|
(global.fetch as ReturnType<typeof vi.fn>)
|
|
.mockResolvedValueOnce({ ok: true })
|
|
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve([]) });
|
|
|
|
const mockResetForm = vi.fn();
|
|
const { result } = renderHook(() => useMedications());
|
|
|
|
await act(async () => {
|
|
await result.current.deleteMed(1, 2, mockResetForm);
|
|
});
|
|
|
|
expect(mockResetForm).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("uploads medication image", async () => {
|
|
(global.fetch as ReturnType<typeof vi.fn>)
|
|
.mockResolvedValueOnce({ ok: true })
|
|
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve([]) });
|
|
|
|
const { result } = renderHook(() => useMedications());
|
|
const file = new File(["test"], "test.jpg", { type: "image/jpeg" });
|
|
|
|
await act(async () => {
|
|
await result.current.uploadMedImage(1, file);
|
|
});
|
|
|
|
expect(fetch).toHaveBeenCalledWith(
|
|
"/api/medications/1/image",
|
|
expect.objectContaining({
|
|
method: "POST",
|
|
body: expect.any(FormData),
|
|
})
|
|
);
|
|
});
|
|
|
|
it("handles image upload error", async () => {
|
|
(global.fetch as ReturnType<typeof vi.fn>).mockRejectedValueOnce(new Error("Upload failed"));
|
|
|
|
const { result } = renderHook(() => useMedications());
|
|
const file = new File(["test"], "test.jpg", { type: "image/jpeg" });
|
|
|
|
await act(async () => {
|
|
await result.current.uploadMedImage(1, file);
|
|
});
|
|
|
|
expect(result.current.uploadingImage).toBe(false);
|
|
});
|
|
|
|
it("deletes medication image", async () => {
|
|
(global.fetch as ReturnType<typeof vi.fn>)
|
|
.mockResolvedValueOnce({ ok: true })
|
|
.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve([]) });
|
|
|
|
const { result } = renderHook(() => useMedications());
|
|
|
|
await act(async () => {
|
|
await result.current.deleteMedImage(1);
|
|
});
|
|
|
|
expect(fetch).toHaveBeenCalledWith("/api/medications/1/image", { method: "DELETE", credentials: "include" });
|
|
});
|
|
|
|
it("allows setting meds directly", () => {
|
|
const { result } = renderHook(() => useMedications());
|
|
|
|
const newMeds = [{ id: 1, name: "NewMed" }] as any;
|
|
|
|
act(() => {
|
|
result.current.setMeds(newMeds);
|
|
});
|
|
|
|
expect(result.current.meds).toEqual(newMeds);
|
|
});
|
|
|
|
it("allows setting saving state", () => {
|
|
const { result } = renderHook(() => useMedications());
|
|
|
|
act(() => {
|
|
result.current.setSaving(true);
|
|
});
|
|
|
|
expect(result.current.saving).toBe(true);
|
|
});
|
|
});
|