// @vitest-environment jsdom // // `/admin` overview — renders one card per discovered entity (from the api // capability map), an error banner if the map can't be reached, and a skeleton // while loading. We mock @voltro/client so the three states are driveable. import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' import { act, createElement, type ReactNode } from 'react' import { createRoot, type Root } from 'react-dom/client' import { I18nProvider } from '@voltro/i18n' import enCatalog from '../../locales/en' interface ManifestState { manifest: unknown; loading: boolean; error?: unknown } const capabilityManifest = vi.fn<() => ManifestState>() const entityAdmins = vi.fn<() => Array>>() vi.mock('@voltro/client', () => ({ useCapabilityManifest: () => capabilityManifest(), deriveEntityAdmins: () => entityAdmins(), })) const { default: AdminOverview, meta, renderMode } = await import('./page') ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true let container: HTMLDivElement let root: Root // AdminOverview uses , so wrap renders in the framework's . const render = (node: ReactNode): void => { container = document.createElement('div') document.body.appendChild(container) act(() => { root = createRoot(container) root.render( createElement(I18nProvider, { locale: 'en', messages: enCatalog, defaultLocale: 'en', children: node }), ) }) } beforeEach(() => { capabilityManifest.mockReset() entityAdmins.mockReset() }) afterEach(() => { if (root) act(() => root.unmount()) container?.remove() document.body.innerHTML = '' }) describe('admin overview — config', () => { test('is an SSR page titled Overview', () => { expect(renderMode).toBe('ssr') expect(meta({ locale: 'en' }).title).toBe('Overview') }) }) describe('admin overview — render', () => { const spec = (over: Record): Record => ({ columns: [], serverOnlyColumns: [], sensitiveColumns: [], reactive: false, editable: true, list: {}, create: {}, update: {}, delete: {}, ...over, }) test('renders a linked card per entity with its column count and ops', () => { capabilityManifest.mockReturnValue({ manifest: {}, loading: false }) entityAdmins.mockReturnValue([ spec({ table: 'todos', columns: ['id', 'title'], reactive: true, list: { tag: 'todos.list' }, create: { tag: 'todos.create' } }), spec({ table: 'notes', columns: ['id'], list: { tag: 'notes.list' } }), ]) render(createElement(AdminOverview)) const cards = Array.from(container.querySelectorAll('a.card')) expect(cards.map((c) => c.getAttribute('href'))).toEqual(['/admin/todos', '/admin/notes']) expect(container.textContent).toContain('2 columns · reactive') expect(container.textContent).toContain('1 column') expect(container.textContent).toContain('list · create') }) test('says how many columns are withheld rather than just showing fewer', () => { // `columns` already excludes `.serverOnly()`, so a card that only printed the // count would silently under-report the table. "2 columns" and "2 columns + // 1 hidden" are different facts about the same entity. capabilityManifest.mockReturnValue({ manifest: {}, loading: false }) entityAdmins.mockReturnValue([ spec({ table: 'users', columns: ['id', 'email'], serverOnlyColumns: ['pwHash'], list: { tag: 'users.list' } }), ]) render(createElement(AdminOverview)) expect(container.textContent).toContain('1 server-only column, hidden') }) test('shows the error banner when the capability map is unreachable', () => { capabilityManifest.mockReturnValue({ manifest: undefined, loading: false, error: 'boom' }) entityAdmins.mockReturnValue([]) render(createElement(AdminOverview)) expect(container.querySelector('.banner')!.textContent).toContain('Couldn’t reach') }) test('shows skeleton cards while the manifest is loading', () => { capabilityManifest.mockReturnValue({ manifest: undefined, loading: true }) entityAdmins.mockReturnValue([]) render(createElement(AdminOverview)) expect(container.querySelectorAll('.skeleton').length).toBeGreaterThan(0) }) })