fix(frontend): a11y on clickable rows + correct c2 source field + pill metric alignment (sprint 8 design-review)
F1: add tabIndex/role/onKeyDown/aria-expanded to C2TasksPanel expander rows and
C2CallbackPicker callback rows; focus-visible ring via Tailwind utilities
F2: add source:'mimic'|'import' to C2TaskListItem; C2TasksPanel reads task.source
instead of mapping_applied for the Source badge label
F3: align C2TaskStatusBadge and C2CallbackPicker Active/Inactive pill metrics to
py-[6px] text-[14px] font-medium (matches SimulationStatusBadge / StatusBadge)
F4: replace hand-rolled Source pill class string with badge-pill-outline recipe
Tests: 212/212 passing (+3 new: Enter/Space key on expander, Enter key on callback row)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -388,6 +388,7 @@ describe('SimulationFormPage — C2 tasks panel visibility', () => {
|
||||
completed: true,
|
||||
output: 'SYSTEM',
|
||||
mapping_applied: false,
|
||||
source: 'import',
|
||||
created_at: '2026-06-10T10:00:00',
|
||||
completed_at: '2026-06-10T10:00:05',
|
||||
},
|
||||
|
||||
@@ -152,6 +152,7 @@ describe('getC2Tasks', () => {
|
||||
completed: true,
|
||||
output: 'NT AUTHORITY\\SYSTEM',
|
||||
mapping_applied: true,
|
||||
source: 'mimic',
|
||||
created_at: '2026-06-10T10:00:00',
|
||||
completed_at: '2026-06-10T10:00:05',
|
||||
},
|
||||
|
||||
@@ -28,6 +28,7 @@ const COMPLETED_TASK = {
|
||||
completed: true,
|
||||
output: 'NT AUTHORITY\\SYSTEM',
|
||||
mapping_applied: true,
|
||||
source: 'mimic' as const,
|
||||
created_at: '2026-06-10T10:00:00',
|
||||
completed_at: '2026-06-10T10:00:05',
|
||||
};
|
||||
@@ -42,6 +43,7 @@ const PENDING_TASK = {
|
||||
completed: false,
|
||||
output: null,
|
||||
mapping_applied: false,
|
||||
source: 'import' as const,
|
||||
created_at: '2026-06-10T10:00:10',
|
||||
completed_at: null,
|
||||
};
|
||||
@@ -91,14 +93,14 @@ describe('C2TasksPanel — populated rows', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('shows MIMIC source badge for mapping_applied=true', async () => {
|
||||
it('shows MIMIC source badge for source=mimic', async () => {
|
||||
renderWithProviders(<C2TasksPanel simulationId={7} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('MIMIC')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows IMPORT source badge for mapping_applied=false', async () => {
|
||||
it('shows IMPORT source badge for source=import', async () => {
|
||||
renderWithProviders(<C2TasksPanel simulationId={7} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('IMPORT')).toBeInTheDocument();
|
||||
@@ -163,6 +165,28 @@ describe('C2TasksPanel — expand on click', () => {
|
||||
fireEvent.click(screen.getByTestId('c2-task-row'));
|
||||
expect(screen.queryByTestId('c2-task-output')).toBeNull();
|
||||
});
|
||||
|
||||
it('Enter key on completed row toggles output (a11y)', async () => {
|
||||
renderWithProviders(<C2TasksPanel simulationId={7} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByTestId('c2-task-row')).toHaveLength(1);
|
||||
});
|
||||
const row = screen.getByTestId('c2-task-row');
|
||||
fireEvent.keyDown(row, { key: 'Enter' });
|
||||
expect(screen.getByTestId('c2-task-output')).toBeInTheDocument();
|
||||
fireEvent.keyDown(row, { key: 'Enter' });
|
||||
expect(screen.queryByTestId('c2-task-output')).toBeNull();
|
||||
});
|
||||
|
||||
it('Space key on completed row toggles output (a11y)', async () => {
|
||||
renderWithProviders(<C2TasksPanel simulationId={7} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByTestId('c2-task-row')).toHaveLength(1);
|
||||
});
|
||||
const row = screen.getByTestId('c2-task-row');
|
||||
fireEvent.keyDown(row, { key: ' ' });
|
||||
expect(screen.getByTestId('c2-task-output')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('C2TasksPanel — refresh indicator', () => {
|
||||
|
||||
@@ -157,4 +157,15 @@ describe('ExecuteViaC2Modal', () => {
|
||||
const textarea = screen.getByTestId('c2-commands-textarea') as HTMLTextAreaElement;
|
||||
expect(textarea.value).toBe('net user\nwhoami /all');
|
||||
});
|
||||
|
||||
it('Enter key on callback row selects it (a11y)', async () => {
|
||||
renderModal('whoami');
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByTestId('c2-callback-row')).toHaveLength(2);
|
||||
});
|
||||
const firstRow = screen.getAllByTestId('c2-callback-row')[0];
|
||||
fireEvent.keyDown(firstRow, { key: 'Enter' });
|
||||
// Row is now selected → Launch button should be enabled
|
||||
expect(screen.getByTestId('c2-launch-btn')).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user