// 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();
});
});