diff --git a/src/main/crdPages/subspace/dialogs/CrdSubspaceIndexDialogConnector.test.tsx b/src/main/crdPages/subspace/dialogs/CrdSubspaceIndexDialogConnector.test.tsx new file mode 100644 index 0000000000..6fb43a07c6 --- /dev/null +++ b/src/main/crdPages/subspace/dialogs/CrdSubspaceIndexDialogConnector.test.tsx @@ -0,0 +1,42 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, test, vi } from 'vitest'; +import { CrdSubspaceIndexDialogConnector } from './CrdSubspaceIndexDialogConnector'; + +const callout = (id: string, state: string, sortOrder: number) => ({ + id, + sortOrder, + classification: { flowState: { tags: [state] } }, + framing: { profile: { displayName: `Post ${id}`, url: `/post/${id}` } }, +}); + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ t: (key: string) => key, i18n: { language: 'en' } }), +})); + +vi.mock('@/domain/collaboration/calloutsSet/useCalloutsSet/useCalloutsSet', () => ({ + default: () => ({ + loading: false, + callouts: [callout('hidden-1', 'Hidden phase', 0), callout('b-1', 'Phase B', 0), callout('a-1', 'Phase A', 0)], + }), +})); + +describe('CrdSubspaceIndexDialogConnector', () => { + test('omits callouts of phases that are not visible, keeping visible phases in flow order', () => { + render( + {}} + calloutsSetId="callouts-set-1" + phaseNames={['Phase A', 'Phase B']} + /> + ); + + expect(screen.queryByText('Hidden phase')).toBeNull(); + expect(screen.queryByText('Post hidden-1')).toBeNull(); + + const headings = screen.getAllByText(/^Phase [AB]$/).map(el => el.textContent); + expect(headings).toEqual(['Phase A', 'Phase B']); + expect(screen.getByText('Post a-1')).toBeTruthy(); + expect(screen.getByText('Post b-1')).toBeTruthy(); + }); +}); diff --git a/src/main/crdPages/subspace/dialogs/CrdSubspaceIndexDialogConnector.tsx b/src/main/crdPages/subspace/dialogs/CrdSubspaceIndexDialogConnector.tsx index 5ac292eabb..0b94c2b344 100644 --- a/src/main/crdPages/subspace/dialogs/CrdSubspaceIndexDialogConnector.tsx +++ b/src/main/crdPages/subspace/dialogs/CrdSubspaceIndexDialogConnector.tsx @@ -7,7 +7,7 @@ type CrdSubspaceIndexDialogConnectorProps = { open: boolean; onOpenChange: (open: boolean) => void; calloutsSetId: string | undefined; - /** Flow state names in innovation-flow order, so the index groups match the tab strip. */ + /** Visible flow state names in innovation-flow order; the index shows only these groups, matching the tab strip. */ phaseNames: string[]; }; @@ -26,28 +26,16 @@ export function CrdSubspaceIndexDialogConnector({ skip: !open || !calloutsSetId, }); - // Group by flow state (phase). Items without a flow state fall under "—". - const groups = (() => { - const map = new Map(); - (callouts ?? []).forEach(c => { - const state = c.classification?.flowState?.tags?.[0] ?? '—'; - const arr = map.get(state) ?? []; - arr.push(c); - map.set(state, arr); - }); - // Groups follow the innovation-flow order shown in the tab strip; any state - // no longer in the flow (or the "—" bucket) trails behind it. - const orderOf = (state: string) => { - const index = phaseNames.indexOf(state); - return index === -1 ? Number.MAX_SAFE_INTEGER : index; - }; - return Array.from(map.entries()) - .map(([state, items]) => ({ - state, - items: [...(items ?? [])].sort((a, b) => a.sortOrder - b.sortOrder), - })) - .sort((a, b) => orderOf(a.state) - orderOf(b.state)); - })(); + // One group per phase shown in the tab strip, in innovation-flow order. Callouts in a + // hidden phase (or any state not in the strip) are left out, matching the tabs. + const groups = phaseNames + .map(state => ({ + state, + items: (callouts ?? []) + .filter(c => c.classification?.flowState?.tags?.[0] === state) + .sort((a, b) => a.sortOrder - b.sortOrder), + })) + .filter(group => group.items.length > 0); return (