// @vitest-environment happy-dom // Retest r4 Pitsline (06/10): Documentos fiscales → Facturas se quedó sin // «Crear» (#1048 lo oculta: FiscalDocument no tiene formulario de factura). Un // tipo con `create_model` delega el alta a otro modelo (customers.Invoice): el // host abre esa página con su alta abierta. import { afterEach, describe, expect, it, vi } from 'vitest' import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react' vi.mock('@tanstack/react-router', () => ({ useNavigate: () => () => {} })) vi.mock('react-i18next', () => { const i18n = { language: 'es' } const t = (k: string, o?: { defaultValue?: string }) => o?.defaultValue ?? k const value = { t, i18n } return { useTranslation: () => value } }) import { DocumentFormDialog, delegatedCreate, scopeDocumentFormsToFilter, withoutDelegatedTypes, } from '../document-form-dialog' import { DynamicCRUDPage } from '../dynamic-crud-page' import { useMetadataCache } from '../metadata-cache' import { ApiProvider, type ApiClient } from '../api-context' import type { DocumentFormsManifest } from '../types' afterEach(cleanup) // Forma de fiscal_mexico FiscalDocument: el REP se crea aquí, la factura en customers. const fiscalForms: DocumentFormsManifest = { type_field: 'document_type', types: [ { key: 'payment_complement', label: 'Complemento de pago', value: 'payment_complement', fields: [{ key: 'amount', label: 'Monto', type: 'number', required: true }], }, { key: 'invoice', label: 'Factura', value: 'invoice', fields: [], create_model: 'customers.Invoice' }, ], } function client(): ApiClient { return { get: vi.fn().mockResolvedValue({ data: {} }), post: vi.fn().mockResolvedValue({ data: { success: true } }), put: vi.fn(), delete: vi.fn(), } as unknown as ApiClient } describe('create_model: alta delegada', () => { it('la vista Facturas (filtro document_type=invoice) conserva el tipo delegado y no oculta Crear', () => { const scoped = scopeDocumentFormsToFilter(fiscalForms, { document_type: 'eq:invoice' }) expect(scoped.hideCreate).toBe(false) expect(delegatedCreate(scoped.forms)).toMatchObject({ model: 'customers.Invoice', type: { key: 'invoice' } }) // REP sigue igual: tipo con formulario propio, sin delegación. expect(delegatedCreate(scopeDocumentFormsToFilter(fiscalForms, { document_type: 'payment_complement' }).forms)).toBeUndefined() }) it('sin host que sepa navegar, el tipo delegado no se ofrece (vista acotada a él = sin Crear)', () => { const forms = withoutDelegatedTypes(fiscalForms) expect(forms?.types.map((x) => x.key)).toEqual(['payment_complement']) expect(scopeDocumentFormsToFilter(forms, { document_type: 'invoice' }).hideCreate).toBe(true) }) it('el diálogo con el tipo delegado no pinta formulario: llama al host y se cierra', async () => { const onDelegateCreate = vi.fn() const onOpenChange = vi.fn() render( , ) await waitFor(() => expect(onDelegateCreate).toHaveBeenCalledTimes(1)) expect(onDelegateCreate.mock.calls[0][0]).toMatchObject({ model: 'customers.Invoice', type: { key: 'invoice' } }) expect(onOpenChange).toHaveBeenCalledWith(false) expect(document.querySelector('[data-slot="document-form-dialog"]')).toBeNull() }) it('con varios tipos, elegir la tarjeta delegada navega; sin callback la tarjeta no aparece', async () => { const onDelegateCreate = vi.fn() render( {}} model="fiscal_documents" forms={fiscalForms} onDelegateCreate={onDelegateCreate} /> , ) fireEvent.click(screen.getByText('Factura')) await waitFor(() => expect(onDelegateCreate).toHaveBeenCalled()) cleanup() render( {}} model="fiscal_documents" forms={fiscalForms} /> , ) // Un solo tipo restante (REP): se salta el selector. expect(screen.queryByText('Factura')).toBeNull() expect(screen.getByText('Monto')).toBeTruthy() }) it('DynamicCRUDPage: en Facturas «Crear» delega al host', async () => { const m = { title: 'Documentos fiscales', endpoint: '/data/x', columns: [], actions: [], perPageOptions: [10], defaultPerPage: 10, searchPlaceholder: 'Buscar', enableCRUDActions: true, hasActions: false, canExport: false, canImport: false, document_forms: fiscalForms, } as any useMetadataCache.getState().setMetadata('fiscal_documents', m) const api = { get: vi.fn(async (url: string) => url.startsWith('/metadata/') ? { data: { success: true, data: m } } : { data: { success: true, data: [], meta: { total: 0 } } }, ), post: vi.fn(), put: vi.fn(), delete: vi.fn(), } as unknown as ApiClient const onDelegateCreate = vi.fn() render( , ) fireEvent.click((await screen.findByText(/New Documentos/)).closest('button')!) await waitFor(() => expect(onDelegateCreate).toHaveBeenCalled()) expect(onDelegateCreate.mock.calls[0][0].model).toBe('customers.Invoice') }) })