2026-05-26 11:13:14 +02:00
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
|
import { screen, waitFor, act } from '@testing-library/react';
|
|
|
|
|
import userEvent from '@testing-library/user-event';
|
|
|
|
|
import MockAdapter from 'axios-mock-adapter';
|
|
|
|
|
import { apiClient } from '@/api/client';
|
|
|
|
|
import { MitreTechniquePicker } from '@/components/MitreTechniquePicker';
|
|
|
|
|
import { renderWithProviders } from './utils';
|
|
|
|
|
import type { MitreTechnique } from '@/api/types';
|
|
|
|
|
|
|
|
|
|
const TECHNIQUES: MitreTechnique[] = [
|
|
|
|
|
{ id: 'T1059', name: 'Command and Scripting Interpreter', tactics: ['execution'] },
|
|
|
|
|
{ id: 'T1059.001', name: 'PowerShell', tactics: ['execution'] },
|
|
|
|
|
{ id: 'T1021', name: 'Remote Services', tactics: ['lateral-movement'] },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
describe('MitreTechniquePicker', () => {
|
|
|
|
|
let mock: MockAdapter;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
mock = new MockAdapter(apiClient);
|
|
|
|
|
vi.useFakeTimers({ shouldAdvanceTime: true });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
mock.restore();
|
|
|
|
|
vi.useRealTimers();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders input with placeholder', () => {
|
|
|
|
|
vi.useRealTimers();
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
renderWithProviders(<MitreTechniquePicker onSelect={vi.fn()} />);
|
2026-05-26 11:13:14 +02:00
|
|
|
expect(screen.getByRole('combobox')).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByPlaceholderText(/Search by ID or name/i)).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('is disabled when disabled prop is true', () => {
|
|
|
|
|
vi.useRealTimers();
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
renderWithProviders(<MitreTechniquePicker onSelect={vi.fn()} disabled />);
|
2026-05-26 11:13:14 +02:00
|
|
|
expect(screen.getByRole('combobox')).toBeDisabled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('debounces search: no request fires before 200ms', async () => {
|
|
|
|
|
mock.onGet('/mitre/techniques').reply(200, TECHNIQUES);
|
|
|
|
|
const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });
|
|
|
|
|
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
renderWithProviders(<MitreTechniquePicker onSelect={vi.fn()} />);
|
2026-05-26 11:13:14 +02:00
|
|
|
|
|
|
|
|
const input = screen.getByRole('combobox');
|
|
|
|
|
await user.click(input);
|
|
|
|
|
await user.type(input, 'T');
|
|
|
|
|
|
|
|
|
|
expect(mock.history.get.length).toBe(0);
|
|
|
|
|
|
|
|
|
|
act(() => { vi.advanceTimersByTime(300); });
|
|
|
|
|
|
|
|
|
|
await waitFor(() => expect(mock.history.get.length).toBeGreaterThan(0));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('displays results in dropdown after debounce', async () => {
|
|
|
|
|
mock.onGet('/mitre/techniques').reply(200, TECHNIQUES);
|
|
|
|
|
const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });
|
|
|
|
|
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
renderWithProviders(<MitreTechniquePicker onSelect={vi.fn()} />);
|
2026-05-26 11:13:14 +02:00
|
|
|
|
|
|
|
|
const input = screen.getByRole('combobox');
|
|
|
|
|
await user.click(input);
|
|
|
|
|
await user.type(input, 'T1059');
|
|
|
|
|
act(() => { vi.advanceTimersByTime(300); });
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const options = screen.getAllByRole('option');
|
|
|
|
|
expect(options.length).toBeGreaterThan(0);
|
|
|
|
|
expect(options[0].textContent).toContain('T1059');
|
|
|
|
|
});
|
|
|
|
|
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
it('selecting a result calls onSelect with technique object', async () => {
|
2026-05-26 11:13:14 +02:00
|
|
|
mock.onGet('/mitre/techniques').reply(200, TECHNIQUES);
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
const onSelect = vi.fn();
|
2026-05-26 11:13:14 +02:00
|
|
|
const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });
|
|
|
|
|
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
renderWithProviders(<MitreTechniquePicker onSelect={onSelect} />);
|
2026-05-26 11:13:14 +02:00
|
|
|
|
|
|
|
|
const input = screen.getByRole('combobox');
|
|
|
|
|
await user.click(input);
|
|
|
|
|
await user.type(input, 'T1059');
|
|
|
|
|
act(() => { vi.advanceTimersByTime(300); });
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByRole('listbox'));
|
|
|
|
|
|
|
|
|
|
const options = screen.getAllByRole('option');
|
|
|
|
|
await user.click(options[0]);
|
|
|
|
|
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
expect(onSelect).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({ id: 'T1059', name: 'Command and Scripting Interpreter' }),
|
|
|
|
|
);
|
2026-05-26 11:13:14 +02:00
|
|
|
});
|
|
|
|
|
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
it('resets input to empty after selection (one-shot)', async () => {
|
2026-05-26 11:13:14 +02:00
|
|
|
mock.onGet('/mitre/techniques').reply(200, TECHNIQUES);
|
|
|
|
|
const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });
|
|
|
|
|
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
renderWithProviders(<MitreTechniquePicker onSelect={vi.fn()} />);
|
2026-05-26 11:13:14 +02:00
|
|
|
|
|
|
|
|
const input = screen.getByRole('combobox') as HTMLInputElement;
|
|
|
|
|
await user.click(input);
|
|
|
|
|
await user.type(input, 'T1059');
|
|
|
|
|
act(() => { vi.advanceTimersByTime(300); });
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByRole('listbox'));
|
|
|
|
|
|
|
|
|
|
const options = screen.getAllByRole('option');
|
|
|
|
|
await user.click(options[0]);
|
|
|
|
|
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
// Input must be reset after selection
|
|
|
|
|
expect(input.value).toBe('');
|
2026-05-26 11:13:14 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('keyboard ArrowDown + Enter selects item', async () => {
|
|
|
|
|
mock.onGet('/mitre/techniques').reply(200, TECHNIQUES);
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
const onSelect = vi.fn();
|
2026-05-26 11:13:14 +02:00
|
|
|
const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });
|
|
|
|
|
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
renderWithProviders(<MitreTechniquePicker onSelect={onSelect} />);
|
2026-05-26 11:13:14 +02:00
|
|
|
|
|
|
|
|
const input = screen.getByRole('combobox');
|
|
|
|
|
await user.click(input);
|
|
|
|
|
await user.type(input, 'T105');
|
|
|
|
|
act(() => { vi.advanceTimersByTime(300); });
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByRole('listbox'));
|
|
|
|
|
|
|
|
|
|
await user.keyboard('{ArrowDown}');
|
|
|
|
|
await user.keyboard('{Enter}');
|
|
|
|
|
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
expect(onSelect).toHaveBeenCalled();
|
2026-05-26 11:13:14 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('Escape closes the dropdown', async () => {
|
|
|
|
|
mock.onGet('/mitre/techniques').reply(200, TECHNIQUES);
|
|
|
|
|
const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });
|
|
|
|
|
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
renderWithProviders(<MitreTechniquePicker onSelect={vi.fn()} />);
|
2026-05-26 11:13:14 +02:00
|
|
|
|
|
|
|
|
const input = screen.getByRole('combobox');
|
|
|
|
|
await user.click(input);
|
|
|
|
|
await user.type(input, 'T1059');
|
|
|
|
|
act(() => { vi.advanceTimersByTime(300); });
|
|
|
|
|
|
|
|
|
|
await waitFor(() => screen.getByRole('listbox'));
|
|
|
|
|
|
|
|
|
|
await user.keyboard('{Escape}');
|
|
|
|
|
|
|
|
|
|
expect(screen.queryByRole('listbox')).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows inline error when API returns 503', async () => {
|
|
|
|
|
mock.onGet('/mitre/techniques').reply(503, { error: 'mitre bundle not loaded' });
|
|
|
|
|
const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });
|
|
|
|
|
|
feat(frontend): sprint 3 — multi-technique MITRE selection + matrix modal
- types: replace mitre_technique_id/name scalars with techniques:MitreTechnique[]
on Simulation; add MitreTactic/MitreMatrixTechnique/MitreMatrixSubtechnique;
SimulationPatchInput now uses technique_ids:string[]
- api/mitre.ts: add getMitreMatrix() → GET /api/mitre/matrix
- hooks/useMitre: add useMitreMatrix(enabled) with staleTime:Infinity
- MitreTechniquePicker: clean rewrite — onSelect(technique) one-shot, resets
input after selection, no incoming value props
- MitreTechniqueTag: chip component with id+name and × remove button
- MitreMatrixModal: tactic columns (220px fixed), expand/collapse subtechniques,
search filter (auto-expands parent on sub match), selection state, focus trap
(Tab wrap, Escape, search autofocus), backdrop click cancel, Apply N techniques
- MitreTechniquesField: orchestrates tags+picker+matrix with auto-save PATCH on
every add/remove/Apply, dedup guard, disabled read-only mode for SOC
- SimulationFormPage: swap MitreTechniquePicker for MitreTechniquesField; remove
technique state from RT form (techniques have independent auto-save cycle)
- SimulationList: MITRE column → T1059 +2 counter format, — when empty
- Tests: 84 passing (13 test files); new suites for Tag, Field, Modal;
MitreTechniquePicker + SimulationFormPage + SimulationList adapted to new API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 04:04:23 +02:00
|
|
|
renderWithProviders(<MitreTechniquePicker onSelect={vi.fn()} />);
|
2026-05-26 11:13:14 +02:00
|
|
|
|
|
|
|
|
const input = screen.getByRole('combobox');
|
|
|
|
|
await user.click(input);
|
|
|
|
|
await user.type(input, 'T1059');
|
|
|
|
|
act(() => { vi.advanceTimersByTime(300); });
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(screen.getByRole('alert')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|