feat(frontend): add SubjectSidebar visibility panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Experiments DB Dev
2026-06-01 09:30:51 -04:00
parent 0f7f6fbbe3
commit 7b34c2fa52
2 changed files with 163 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
import React from 'react';
import { render, screen, fireEvent, within } from '@testing-library/react';
import { SubjectSidebar } from '../src/components/SubjectSidebar';
const LINES = [
{ id: 'a1', name: 'Mouse-A1', group: 'Group A', color: '#111' },
{ id: 'a2', name: 'Mouse-A2', group: 'Group A', color: '#222' },
{ id: 'b1', name: 'Mouse-B1', group: 'Group B', color: '#333' },
];
function setup(overrides = {}) {
const props = {
lines: LINES,
hiddenSubjects: new Set(),
onToggleSubject: jest.fn(),
onToggleGroup: jest.fn(),
onShowAll: jest.fn(),
onHighlight: jest.fn(),
...overrides,
};
render(<SubjectSidebar {...props} />);
return props;
}
it('renders each group header with its subject count', () => {
setup();
expect(screen.getByText('Group A')).toBeInTheDocument();
expect(screen.getByText('Group B')).toBeInTheDocument();
expect(screen.getByText('(2)')).toBeInTheDocument();
expect(screen.getByText('(1)')).toBeInTheDocument();
});
it('renders a subject checkbox per subject, checked when visible', () => {
setup({ hiddenSubjects: new Set(['a2']) });
expect(screen.getByLabelText('Mouse-A1').checked).toBe(true);
expect(screen.getByLabelText('Mouse-A2').checked).toBe(false);
});
it('calls onToggleSubject with the id when a subject checkbox is clicked', () => {
const { onToggleSubject } = setup();
fireEvent.click(screen.getByLabelText('Mouse-A1'));
expect(onToggleSubject).toHaveBeenCalledWith('a1');
});
it('shows the group checkbox as indeterminate when the group is mixed', () => {
setup({ hiddenSubjects: new Set(['a1']) });
const groupCheckbox = screen.getByLabelText(/Group A/);
expect(groupCheckbox.indeterminate).toBe(true);
expect(groupCheckbox.checked).toBe(false);
});
it('checks the group checkbox when all members are visible', () => {
setup();
expect(screen.getByLabelText(/Group A/).checked).toBe(true);
});
it('calls onToggleGroup with the group ids and hidden=true when an all-visible group is clicked', () => {
const { onToggleGroup } = setup();
fireEvent.click(screen.getByLabelText(/Group A/));
expect(onToggleGroup).toHaveBeenCalledWith(['a1', 'a2'], true);
});
it('shows the group checkbox unchecked and not indeterminate when all members are hidden', () => {
setup({ hiddenSubjects: new Set(['a1', 'a2']) });
const groupCheckbox = screen.getByLabelText(/Group A/);
expect(groupCheckbox.checked).toBe(false);
expect(groupCheckbox.indeterminate).toBe(false);
});
it('calls onToggleGroup with hidden=false when a fully-hidden group is clicked', () => {
const { onToggleGroup } = setup({ hiddenSubjects: new Set(['a1', 'a2']) });
fireEvent.click(screen.getByLabelText(/Group A/));
expect(onToggleGroup).toHaveBeenCalledWith(['a1', 'a2'], false);
});
it('hides the Show all link when nothing is hidden, shows it otherwise', () => {
const { rerender } = render(
<SubjectSidebar lines={LINES} hiddenSubjects={new Set()}
onToggleSubject={() => {}} onToggleGroup={() => {}}
onShowAll={() => {}} onHighlight={() => {}} />,
);
expect(screen.queryByText('Show all')).toBeNull();
rerender(
<SubjectSidebar lines={LINES} hiddenSubjects={new Set(['a1'])}
onToggleSubject={() => {}} onToggleGroup={() => {}}
onShowAll={() => {}} onHighlight={() => {}} />,
);
expect(screen.getByText('Show all')).toBeInTheDocument();
});
it('calls onShowAll when the Show all link is clicked', () => {
const { onShowAll } = setup({ hiddenSubjects: new Set(['a1']) });
fireEvent.click(screen.getByText('Show all'));
expect(onShowAll).toHaveBeenCalled();
});
it('calls onHighlight on subject row hover enter/leave', () => {
const { onHighlight } = setup();
const row = screen.getByLabelText('Mouse-A1').closest('label');
fireEvent.mouseEnter(row);
expect(onHighlight).toHaveBeenCalledWith('a1');
fireEvent.mouseLeave(row);
expect(onHighlight).toHaveBeenCalledWith(null);
});