// @vitest-environment happy-dom // // Primitivos federados en React: el dispatcher pinta un modal de // registerFederatedModal sin esperar al host, usa el formulario genérico de // inmediato si el addon del slug no está instalado, y useDocumentContributions // re-pinta al registrar/desmontar un remote. import { afterEach, describe, expect, it, vi } from 'vitest' import { act, cleanup, render, screen } from '@testing-library/react' const USE_TRANSLATION = { t: (_k: string, opts?: { defaultValue?: string }) => opts?.defaultValue ?? _k, i18n: { language: 'es' }, } vi.mock('react-i18next', () => ({ useTranslation: () => USE_TRANSLATION })) vi.mock('@tanstack/react-router', () => ({ useNavigate: () => () => {} })) import { ActionModalDispatcher } from '../action-modal-dispatcher' import { setFederatedActionLoader } from '../federated-action-loader' import { ApiProvider, type ApiClient } from '../api-context' import { InstalledAddonsProvider } from '../installed-addons-context' import { __resetContributions, registerDocumentContribution, registerFederatedModal, type DocumentEditorContext, } from '../primitives/contributions' import { useDocumentContributions } from '../primitives/use-contributions' import type { ActionMetadata, ActionModalProps } from '@asteby/metacore-sdk' afterEach(() => { cleanup() vi.restoreAllMocks() setFederatedActionLoader(null) __resetContributions() }) const noopApi = { get: vi.fn().mockResolvedValue({ data: {} }), post: vi.fn().mockResolvedValue({ data: { success: true } }), delete: vi.fn().mockResolvedValue({ data: { success: true } }), } as unknown as ApiClient const importAction: ActionMetadata = { key: 'import_cfdi', label: 'Importar CFDI', modal: 'fiscal_mexico.import_cfdi', confirm: true, confirmMessage: '¿Importar?', executable: true, } function ImportModal({ action }: ActionModalProps) { return
{action.modal}
} function renderDispatcher(addons?: string[]) { const tree = ( {}} action={importAction} model="Invoice" record={{ id: '1' } as any} endpoint="/data/Invoice" onSuccess={() => {}} /> ) return render( {addons ? {tree} : tree} , ) } describe('ActionModalDispatcher + registerFederatedModal', () => { it('pinta el modal registrado (lazy) sin pedir el remote al host', async () => { const loader = vi.fn() setFederatedActionLoader(loader) registerFederatedModal({ key: 'fiscal_mexico.import_cfdi', addon: 'fiscal_mexico', load: async () => ({ default: ImportModal }) }) renderDispatcher(['fiscal_mexico']) expect((await screen.findByTestId('import-modal')).textContent).toBe('fiscal_mexico.import_cfdi') expect(loader).not.toHaveBeenCalled() expect(screen.queryByText('¿Importar?')).toBeNull() }) it('addon del slug no instalado → formulario genérico de inmediato, sin esperar ni pedir el remote', () => { const loader = vi.fn() setFederatedActionLoader(loader) registerFederatedModal({ key: 'fiscal_mexico.import_cfdi', addon: 'fiscal_mexico', load: async () => ({ default: ImportModal }) }) renderDispatcher(['customers']) expect(screen.getByText('¿Importar?')).toBeTruthy() expect(screen.queryByTestId('federated-action-loading')).toBeNull() expect(loader).not.toHaveBeenCalled() }) it('addon instalado pero modal aún sin registrar → espera al remote (no cae al genérico)', async () => { renderDispatcher(['fiscal_mexico']) expect(screen.getByTestId('federated-action-loading')).toBeTruthy() expect(screen.queryByText('¿Importar?')).toBeNull() act(() => { registerFederatedModal({ key: 'fiscal_mexico.import_cfdi', addon: 'fiscal_mexico', load: async () => ({ default: ImportModal }) }) }) expect(await screen.findByTestId('import-modal')).toBeTruthy() }) it('sin InstalledAddonsProvider se conserva la espera de siempre', () => { renderDispatcher() expect(screen.getByTestId('federated-action-loading')).toBeTruthy() expect(screen.queryByText('¿Importar?')).toBeNull() }) }) describe('useDocumentContributions', () => { const ctx: DocumentEditorContext = { kind: 'invoice', model: 'customers.Invoice', values: {}, mode: 'create' } function Region() { const items = useDocumentContributions(ctx, 'header.fields') return (
{items.map(({ id, component: C }) => ( {}} issues={{ report: () => {} }} /> ))}
) } it('re-pinta al registrar y al desmontar el remote; filtra por addon instalado', async () => { render( , ) expect(screen.getByTestId('region').textContent).toBe('') let off = () => {} act(() => { off = registerDocumentContribution({ id: 'fiscal_mexico.header.cfdi', kinds: ['invoice'], region: 'header.fields', requiresAddon: 'fiscal_mexico', load: async () => ({ default: () => Uso del CFDI }), }) registerDocumentContribution({ id: 'inventory.header.warehouse', kinds: '*', region: 'header.fields', requiresAddon: 'inventory', component: () => Almacén, }) }) expect(await screen.findByText('Uso del CFDI')).toBeTruthy() expect(screen.queryByText('Almacén')).toBeNull() act(() => off()) expect(screen.queryByText('Uso del CFDI')).toBeNull() }) })