import React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import { render, screen, fireEvent, waitFor } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' // ยง22.4a SLICE-3 โ€” the faceted catalog pane (PUC-3). Mock the api so // getCollection drives the field schema and listRFCs returns { items, facets }. const listRFCs = vi.fn() const getCollection = vi.fn() const bulkEntryMeta = vi.fn() vi.mock('../api', () => ({ listRFCs: (...a) => listRFCs(...a), listProposals: () => Promise.resolve({ items: [] }), getCollection: (...a) => getCollection(...a), bulkEntryMeta: (...a) => bulkEntryMeta(...a), })) vi.mock('./ToastHost.jsx', () => ({ showToast: vi.fn() })) vi.mock('../lib/entryPaths', () => ({ entryPath: () => '/x', proposalPath: () => '/p', useProjectId: () => 'ohm', useCollectionId: () => 'default', })) import Catalog from './Catalog.jsx' const renderCatalog = () => render( {}} version={0} />) describe('Catalog faceted pane', () => { beforeEach(() => { listRFCs.mockReset(); getCollection.mockReset(); bulkEntryMeta.mockReset() }) it('renders facet groups with counts when the collection declares fields', async () => { getCollection.mockResolvedValue({ fields: { priority: { type: 'enum', values: ['P0', 'P1'] } }, viewer: { can_contribute: false }, entry_noun: 'RFC', }) listRFCs.mockResolvedValue({ items: [{ slug: 'a', title: 'A', state: 'active', tags: [], starred_by_me: false }], facets: { priority: { P0: 3, P1: 1 }, state: { active: 4 } }, }) renderCatalog() expect(await screen.findByText('P0')).toBeInTheDocument() expect(screen.getByText('3')).toBeInTheDocument() }) it('re-fetches with the selection when a facet checkbox is toggled', async () => { getCollection.mockResolvedValue({ fields: { priority: { type: 'enum', values: ['P0'] } }, viewer: {}, entry_noun: 'RFC', }) listRFCs.mockResolvedValue({ items: [], facets: { priority: { P0: 2 }, state: {} } }) renderCatalog() const box = await screen.findByText('P0') fireEvent.click(box.closest('label').querySelector('input')) await waitFor(() => { const last = listRFCs.mock.calls.at(-1) expect(last[2].selections.priority).toContain('P0') }) }) it('reveals the bulk action bar when a row is selected (faceted + contributor)', async () => { getCollection.mockResolvedValue({ fields: { priority: { type: 'enum', values: ['P0', 'P1'] } }, viewer: { can_contribute: true }, entry_noun: 'RFC', }) listRFCs.mockResolvedValue({ items: [{ slug: 'a', title: 'A', state: 'active', tags: [], starred_by_me: false }], facets: { priority: { P0: 1 }, state: { active: 1 } }, }) renderCatalog() const checkbox = await screen.findByLabelText(/select A/i) fireEvent.click(checkbox) expect(await screen.findByText(/1 selected/i)).toBeInTheDocument() }) it('sends a bulk request and re-fetches when a set op is applied', async () => { getCollection.mockResolvedValue({ fields: { priority: { type: 'enum', values: ['P0', 'P1'] } }, viewer: { can_contribute: true }, entry_noun: 'RFC', }) listRFCs.mockResolvedValue({ items: [{ slug: 'a', title: 'A', state: 'active', tags: [], starred_by_me: false }], facets: { priority: { P0: 1 }, state: { active: 1 } }, }) bulkEntryMeta.mockResolvedValue({ applied: ['a'], rejected: [], committed: true }) renderCatalog() fireEvent.click(await screen.findByLabelText(/select A/i)) fireEvent.change(await screen.findByLabelText(/set priority/i), { target: { value: 'P1' } }) await waitFor(() => { expect(bulkEntryMeta).toHaveBeenCalledWith('ohm', 'default', { slugs: ['a'], op: 'set', field: 'priority', value: 'P1' }) }) }) it('drops a selection when a filter change hides the row', async () => { getCollection.mockResolvedValue({ fields: { priority: { type: 'enum', values: ['P0', 'P1'] } }, viewer: { can_contribute: true }, entry_noun: 'RFC', }) // Content-driven: unfiltered โ†’ row "a"; once the P0 facet is selected the // server response omits "a" (drives the prune). Robust to extra mount-time // fetches the collection effect triggers. listRFCs.mockImplementation((p, c, opts) => Promise.resolve({ items: opts?.selections?.priority?.includes('P0') ? [{ slug: 'b', title: 'B', state: 'active', tags: [], starred_by_me: false }] : [{ slug: 'a', title: 'A', state: 'active', tags: [], starred_by_me: false }], facets: { priority: { P0: 1 }, state: { active: 1 } }, })) const { container } = renderCatalog() fireEvent.click(await screen.findByLabelText(/select A/i)) expect(await screen.findByText(/1 selected/i)).toBeInTheDocument() // Toggle the P0 facet checkbox โ†’ re-fetch returns a list without "a". fireEvent.click(container.querySelector('.facet-group input[type="checkbox"]')) await waitFor(() => { expect(screen.queryByText(/1 selected/i)).not.toBeInTheDocument() }) }) it('does not offer row selection for a non-contributor', async () => { getCollection.mockResolvedValue({ fields: { priority: { type: 'enum', values: ['P0'] } }, viewer: { can_contribute: false }, entry_noun: 'RFC', }) listRFCs.mockResolvedValue({ items: [{ slug: 'a', title: 'A', state: 'active', tags: [], starred_by_me: false }], facets: { priority: { P0: 1 }, state: { active: 1 } }, }) renderCatalog() await screen.findByText('A') expect(screen.queryByLabelText(/select A/i)).not.toBeInTheDocument() }) it('renders legacy state chips when the collection declares no fields', async () => { getCollection.mockResolvedValue({ fields: null, viewer: {}, entry_noun: 'RFC' }) listRFCs.mockResolvedValue({ items: [], facets: {} }) renderCatalog() expect(await screen.findByText('Super-draft')).toBeInTheDocument() expect(screen.getByText('Active')).toBeInTheDocument() }) })