import { act, render, renderHook } from "@testing-library/react"
import { beforeEach, describe, expect, it, vi } from "vitest"
import { AIIntelligenceProvider, useAIIntelligence } from "../ai-intelligence-provider"
// Mock XState
vi.mock("xstate", () => ({
createActor: vi.fn(() => ({
start: vi.fn(),
stop: vi.fn(),
send: vi.fn(),
subscribe: vi.fn(() => ({ unsubscribe: vi.fn() })),
getSnapshot: vi.fn(() => ({ value: "idle" })),
})),
Actor: vi.fn(),
}))
// Mock the state machine
vi.mock("../../shared/services/ai-intelligence-machine", () => ({
aiIntelligenceMachine: {
id: "ai-intelligence",
initial: "idle",
states: {
idle: {},
analyzing: {},
complete: {},
error: {},
},
},
}))
describe("AIIntelligenceProvider", () => {
beforeEach(() => {
vi.clearAllMocks()
})
describe("Provider Component", () => {
it("should render children", () => {
const { getByTestId } = render(
Test Child
,
)
const child = getByTestId("child")
expect(child).toBeTruthy()
expect(child.textContent).toBe("Test Child")
})
it("should create and start actor on mount", async () => {
const { createActor } = await import("xstate")
const mockActor = {
start: vi.fn(),
stop: vi.fn(),
}
;(createActor as any).mockReturnValue(mockActor)
renderHook(() => null, {
wrapper: AIIntelligenceProvider,
})
expect(createActor).toHaveBeenCalledTimes(1)
expect(mockActor.start).toHaveBeenCalledTimes(1)
})
it("should stop actor on unmount", async () => {
const { createActor } = await import("xstate")
const mockActor = {
start: vi.fn(),
stop: vi.fn(),
}
;(createActor as any).mockReturnValue(mockActor)
const { unmount } = renderHook(() => null, {
wrapper: AIIntelligenceProvider,
})
unmount()
expect(mockActor.stop).toHaveBeenCalledTimes(1)
})
it("should only initialize actor once", async () => {
const { createActor } = await import("xstate")
const mockActor = {
start: vi.fn(),
stop: vi.fn(),
}
;(createActor as any).mockReturnValue(mockActor)
const { rerender } = renderHook(() => null, {
wrapper: AIIntelligenceProvider,
})
// Rerender multiple times
rerender()
rerender()
rerender()
// Should still only create and start once
expect(createActor).toHaveBeenCalledTimes(1)
expect(mockActor.start).toHaveBeenCalledTimes(1)
})
})
describe("useAIIntelligence Hook", () => {
it("should throw error when used outside provider", () => {
const { result } = renderHook(() => {
try {
useAIIntelligence()
} catch (error) {
return error
}
})
expect(result.current).toBeInstanceOf(Error)
expect((result.current as Error).message).toBe("useAIIntelligence must be used within AIIntelligenceProvider")
})
it("should return context with actor when used inside provider", async () => {
const { createActor } = await import("xstate")
const mockActor = {
start: vi.fn(),
stop: vi.fn(),
send: vi.fn(),
subscribe: vi.fn(() => ({ unsubscribe: vi.fn() })),
getSnapshot: vi.fn(() => ({ value: "idle" })),
}
;(createActor as any).mockReturnValue(mockActor)
const { result } = renderHook(() => useAIIntelligence(), {
wrapper: AIIntelligenceProvider,
})
// Wait for state update
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0))
})
expect(result.current).toBeDefined()
expect(result.current.actor).toBe(mockActor)
})
it("should handle null actor during initialization", () => {
// The provider starts with null actor and updates it asynchronously
const TestComponent = () => {
const { actor } = useAIIntelligence()
return
{actor ? "ready" : "loading"}
}
const { getByTestId } = render(
,
)
// The actor is immediately available in our mock, so it shows "ready"
const state = getByTestId("actor-state")
expect(state.textContent).toBe("ready")
})
it("should update when actor becomes available", async () => {
const { createActor } = await import("xstate")
const mockActor = {
start: vi.fn(),
stop: vi.fn(),
send: vi.fn(),
subscribe: vi.fn(() => ({ unsubscribe: vi.fn() })),
getSnapshot: vi.fn(() => ({ value: "idle" })),
}
;(createActor as any).mockReturnValue(mockActor)
const { result, rerender } = renderHook(() => useAIIntelligence(), {
wrapper: AIIntelligenceProvider,
})
// Wait for state update
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0))
})
rerender()
expect(result.current.actor).toBe(mockActor)
})
})
describe("Integration", () => {
it("should provide working actor to child components", async () => {
const { createActor } = await import("xstate")
const mockActor = {
start: vi.fn(),
stop: vi.fn(),
send: vi.fn(),
subscribe: vi.fn(() => ({ unsubscribe: vi.fn() })),
getSnapshot: vi.fn(() => ({ value: "idle" })),
}
;(createActor as any).mockReturnValue(mockActor)
function TestComponent() {
const { actor } = useAIIntelligence()
return (
{actor ? "ready" : "loading"}
)
}
const { getByTestId, getByText } = render(
,
)
// Wait for actor to be available
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 0))
})
const status = getByTestId("actor-status")
expect(status.textContent).toBe("ready")
// Test interaction
const button = getByText("Start")
act(() => {
button.click()
})
expect(mockActor.send).toHaveBeenCalledWith({ type: "START_ANALYSIS" })
})
})
})