import { QueryClientProvider } from '@tanstack/react-query' import { renderHook, waitFor } from '@testing-library/react-native' import React from 'react' import { buildTestQueryClient } from '../../__utils__/query_client' import * as useApiClientModule from '../../hooks/use_api_client' import { useConversationValidate } from '../../hooks/use_conversation_validate' const createWrapper = () => { const queryClient = buildTestQueryClient() return ({ children }: { children: React.ReactNode }) => ( {children} ) } const mockChatPost = (impl: jest.Mock) => { jest.spyOn(useApiClientModule, 'useApiClient').mockReturnValue({ chat: { post: impl }, } as unknown as ReturnType) return impl } const resolveWith = (warnings: { attribute: string; fullMessage: string }[]) => jest.fn().mockResolvedValue({ data: { type: 'ConversationValidate', id: '1', warnings }, links: {}, meta: {}, }) describe('useConversationValidate', () => { afterEach(() => { jest.restoreAllMocks() }) it('does not fire the request when disabled', () => { const post = mockChatPost(resolveWith([])) const { result } = renderHook( () => useConversationValidate({ payload: 'abc', enabled: false }), { wrapper: createWrapper() } ) expect(post).not.toHaveBeenCalled() expect(result.current.warnings).toEqual([]) expect(result.current.validationPending).toBe(false) }) it('does not fire the request when payload is undefined', () => { const post = mockChatPost(resolveWith([])) const { result } = renderHook( () => useConversationValidate({ payload: undefined, isLoadingPayload: true }), { wrapper: createWrapper() } ) expect(post).not.toHaveBeenCalled() // validationPending stays true while the upstream payload query is still loading expect(result.current.validationPending).toBe(true) }) it('returns warnings from the response and clears validationPending', async () => { const warnings = [{ attribute: 'safety_policy', fullMessage: 'Needs a second adult.' }] mockChatPost(resolveWith(warnings)) const { result } = renderHook(() => useConversationValidate({ payload: 'abc' }), { wrapper: createWrapper(), }) await waitFor(() => expect(result.current.warnings).toEqual(warnings)) expect(result.current.validationPending).toBe(false) }) it('sends gender_id in attributes when provided', async () => { const post = mockChatPost(resolveWith([])) renderHook(() => useConversationValidate({ payload: 'abc', genderId: '42' }), { wrapper: createWrapper(), }) await waitFor(() => expect(post).toHaveBeenCalledTimes(1)) expect(post).toHaveBeenCalledWith( expect.objectContaining({ url: '/me/conversation_validate', data: { data: { type: 'ConversationValidate', attributes: { payload: 'abc', gender_id: '42' }, }, }, }) ) }) it('omits gender_id when not provided', async () => { const post = mockChatPost(resolveWith([])) renderHook(() => useConversationValidate({ payload: 'abc' }), { wrapper: createWrapper(), }) await waitFor(() => expect(post).toHaveBeenCalledTimes(1)) const [call] = post.mock.calls expect(call[0].data.data.attributes).toEqual({ payload: 'abc' }) }) it('falls back to empty warnings and unblocks on request error', async () => { // Documents the current fail-open behavior: if validate errors, the user is allowed to proceed. const post = mockChatPost(jest.fn().mockRejectedValue(new Error('boom'))) const { result } = renderHook(() => useConversationValidate({ payload: 'abc' }), { wrapper: createWrapper(), }) await waitFor(() => expect(post).toHaveBeenCalledTimes(1)) await waitFor(() => expect(result.current.validationPending).toBe(false)) expect(result.current.warnings).toEqual([]) }) })