File size: 5,172 Bytes
766d85d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// 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(<ScopeGroupPicker selected={[]} onChange={vi.fn()} />);
    // 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(<ScopeGroupPicker selected={[]} onChange={vi.fn()} />);
    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(<ScopeGroupPicker selected={[]} onChange={onChange} />);
    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(
      <ScopeGroupPicker selected={[]} onChange={s => captured.push(s)} />
    );
    await user.click(screen.getByRole('button', { name: /select all/i }));
    const allScopes = captured[0];

    // Now rerender with all scopes selected
    rerender(<ScopeGroupPicker selected={allScopes} onChange={vi.fn()} />);
    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(
      <ScopeGroupPicker selected={[]} onChange={s => captured.push(s)} />
    );
    await user.click(screen.getByRole('button', { name: /select all/i }));
    const allScopes = captured[0];

    const onChange = vi.fn();
    rerender(<ScopeGroupPicker selected={allScopes} onChange={onChange} />);
    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(<ScopeGroupPicker selected={[]} onChange={vi.fn()} />);

    // 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(<ScopeGroupPicker selected={[]} onChange={onChange} />);

    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(<ScopeGroupPicker selected={[]} onChange={onChange} />);

    // 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(<ScopeGroupPicker selected={[firstGroupScope]} onChange={vi.fn()} />);
    // Count badge like "(1/N)" should be visible
    expect(screen.getByText(/\(\d+\/\d+\)/)).toBeInTheDocument();
  });
});