// FE-COMP-SCOPE-001 to FE-COMP-SCOPE-009 import { render, screen, waitFor } from '../../../tests/helpers/render'; import userEvent from '@testing-library/user-event'; import { resetAllStores } from '../../../tests/helpers/store'; import ScopeGroupPicker from './ScopeGroupPicker'; beforeEach(() => { resetAllStores(); }); describe('ScopeGroupPicker', () => { it('FE-COMP-SCOPE-001: renders scope groups', () => { render(); // Several group headers should be visible expect(screen.getAllByRole('button').length).toBeGreaterThan(0); }); it('FE-COMP-SCOPE-002: shows Select All button when nothing selected', () => { render(); expect(screen.getByRole('button', { name: /select all/i })).toBeInTheDocument(); }); it('FE-COMP-SCOPE-003: Select All calls onChange with all scopes', async () => { const user = userEvent.setup(); const onChange = vi.fn(); render(); await user.click(screen.getByRole('button', { name: /select all/i })); expect(onChange).toHaveBeenCalledTimes(1); const called = onChange.mock.calls[0][0] as string[]; expect(called.length).toBeGreaterThan(0); }); it('FE-COMP-SCOPE-004: shows Deselect All button when all selected', async () => { // First collect all scopes by clicking Select All and capturing the callback const user = userEvent.setup(); const captured: string[][] = []; const { rerender } = render( captured.push(s)} /> ); await user.click(screen.getByRole('button', { name: /select all/i })); const allScopes = captured[0]; // Now rerender with all scopes selected rerender(); expect(screen.getByRole('button', { name: /deselect all/i })).toBeInTheDocument(); }); it('FE-COMP-SCOPE-005: Deselect All calls onChange with empty array', async () => { const user = userEvent.setup(); const captured: string[][] = []; // Get all scopes first const { rerender } = render( captured.push(s)} /> ); await user.click(screen.getByRole('button', { name: /select all/i })); const allScopes = captured[0]; const onChange = vi.fn(); rerender(); await user.click(screen.getByRole('button', { name: /deselect all/i })); expect(onChange).toHaveBeenCalledWith([]); }); it('FE-COMP-SCOPE-006: expanding a group reveals individual scope checkboxes', async () => { const user = userEvent.setup(); render(); // Groups are collapsed by default — checkboxes for individual scopes not visible const groupToggles = screen.getAllByRole('button').filter(b => !b.textContent?.toLowerCase().includes('select all') && !b.textContent?.toLowerCase().includes('deselect all') ); // Click the first group expand button await user.click(groupToggles[0]); // Individual scope checkboxes should now appear (more than just group-level ones) const checkboxes = screen.getAllByRole('checkbox'); expect(checkboxes.length).toBeGreaterThan(0); }); it('FE-COMP-SCOPE-007: group checkbox selects all scopes in the group', async () => { const user = userEvent.setup(); const onChange = vi.fn(); render(); const groupCheckboxes = screen.getAllByRole('checkbox'); await user.click(groupCheckboxes[0]); expect(onChange).toHaveBeenCalledTimes(1); const called = onChange.mock.calls[0][0] as string[]; expect(called.length).toBeGreaterThan(0); }); it('FE-COMP-SCOPE-008: individual scope toggle adds/removes that scope', async () => { const user = userEvent.setup(); const onChange = vi.fn(); render(); // Expand first group const groupToggles = screen.getAllByRole('button').filter(b => !b.textContent?.toLowerCase().includes('select all') && !b.textContent?.toLowerCase().includes('deselect all') ); await user.click(groupToggles[0]); // There are now individual scope checkboxes — click the second one (first is group-level) const checkboxes = screen.getAllByRole('checkbox'); await user.click(checkboxes[1]); // individual scope expect(onChange).toHaveBeenCalledTimes(1); }); it('FE-COMP-SCOPE-009: count badge shown when some scopes selected in group', () => { // Get any single scope key from the first group via Select All trick + manual slice // We'll just select a scope by triggering group checkbox and passing it in const firstGroupScope = 'trips:read'; // known scope from SCOPE_GROUPS render(); // Count badge like "(1/N)" should be visible expect(screen.getByText(/\(\d+\/\d+\)/)).toBeInTheDocument(); }); });