Skip to content
Open
37 changes: 37 additions & 0 deletions src/app/__tests__/ActionsButtons.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,41 @@ describe('Clear Button', () => {
expect(dummyDispatch).toHaveBeenCalledWith(emptySnapshots());
expect(dummyDispatch).toHaveBeenCalledWith(changeSlider(0));
});

test('opens confirmation dialog when clearing with existing snapshots', () => {
render(
<Provider store={mockStore}>
<ActionContainer snapshots={[{}, {}, {}] as any} />
</Provider>,
);
fireEvent.click(screen.getByText('Clear'));
expect(dummyDispatch).not.toHaveBeenCalledWith(emptySnapshots());
expect(screen.getByText('Clear all snapshots?')).toBeInTheDocument();
});

test('cancel button closes the dialog without clearing', () => {
render(
<Provider store={mockStore}>
<ActionContainer snapshots={[{}, {}, {}] as any} />
</Provider>,
);
fireEvent.click(screen.getByText('Clear'));
fireEvent.click(screen.getByText('Cancel'));
expect(dummyDispatch).not.toHaveBeenCalledWith(emptySnapshots());
});

test('confirm button in dialog dispatches clear actions', () => {
render(
<Provider store={mockStore}>
<ActionContainer snapshots={[{}, {}, {}] as any} />
</Provider>,
);
fireEvent.click(screen.getByText('Clear'));
const dialogClearButton = screen
.getAllByText('Clear')
.find((el) => el.closest('[role="dialog"]'));
fireEvent.click(dialogClearButton!);
expect(dummyDispatch).toHaveBeenCalledWith(emptySnapshots());
expect(dummyDispatch).toHaveBeenCalledWith(changeSlider(0));
});
});
76 changes: 61 additions & 15 deletions src/app/containers/ActionContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import RouteDescription from '../components/Actions/RouteDescription';
import DropDown from '../components/Actions/DropDown';
import ProvConContainer from './ProvConContainer';
import { ActionContainerProps, CurrentTab, MainState, Obj, RootState } from '../FrontendTypes';
import { Button } from '@mui/material';
import {
Button,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
} from '@mui/material';
import RecordButton from '../components/Actions/RecordButton';
import { toast } from 'react-hot-toast';
import { REACTIME_TOAST_DEFAULTS } from '../utils/toastConfig';
Expand All @@ -20,13 +27,34 @@ function ActionContainer(props: ActionContainerProps): JSX.Element {
const [dropdownSelection, setDropdownSelection] = useState('Time Jump');
const actionsEndRef = useRef(null as unknown as HTMLDivElement);
const [expandedIndex, setExpandedIndex] = useState(null as number | null); // Track which snapshot is expanded
const [clearDialogOpen, setClearDialogOpen] = useState(false); // Confirm dialog for Clear

const dispatch = useDispatch();
const { currentTab, tabs, port }: MainState = useSelector((state: RootState) => state.main);

const { currLocation, hierarchy, sliderIndex, viewIndex }: Partial<CurrentTab> = tabs[currentTab]; // we destructure the currentTab object
const { snapshots } = props;
const [recordingActions, setRecordingActions] = useState(true); // We create a local state 'recordingActions' and set it to true

// Clears all snapshots, resets the slider/expanded state, and surfaces toast feedback.
const clearSnapshotsWithFeedback = (): void => {
const clearedCount = snapshots?.length ?? 0;
dispatch(emptySnapshots()); // set slider back to zero, visually
dispatch(changeSlider(0));
setExpandedIndex(null); // Reset expanded state when clearing
if (clearedCount > 0) {
toast.success(`Cleared ${clearedCount} snapshot${clearedCount === 1 ? '' : 's'}`, {
...REACTIME_TOAST_DEFAULTS,
id: 'snapshots-cleared',
});
} else {
toast('No snapshots to clear', {
...REACTIME_TOAST_DEFAULTS,
id: 'snapshots-cleared',
icon: '鈩癸笍',
});
}
};
let actionsArr: JSX.Element[] = []; // we create an array 'actionsArr' that will hold elements we create later on
// we create an array 'hierarchyArr' that will hold objects and numbers
const hierarchyArr: (number | {})[] = [];
Expand Down Expand Up @@ -165,28 +193,46 @@ function ActionContainer(props: ActionContainerProps): JSX.Element {
className='clear-button-modern'
variant='text'
onClick={() => {
const clearedCount = snapshots?.length ?? 0;
dispatch(emptySnapshots()); // set slider back to zero, visually
dispatch(changeSlider(0));
setExpandedIndex(null); // Reset expanded state when clearing
if (clearedCount > 0) {
toast.success(
`Cleared ${clearedCount} snapshot${clearedCount === 1 ? '' : 's'}`,
{ ...REACTIME_TOAST_DEFAULTS, id: 'snapshots-cleared' },
);
// Confirm before discarding a non-trivial recording session; otherwise clear directly.
if (snapshots && snapshots.length > 1) {
setClearDialogOpen(true);
} else {
toast('No snapshots to clear', {
...REACTIME_TOAST_DEFAULTS,
id: 'snapshots-cleared',
icon: '鈩癸笍',
});
clearSnapshotsWithFeedback();
}
}}
type='button'
>
Clear
</Button>
</div>
<Dialog
open={clearDialogOpen}
onClose={() => setClearDialogOpen(false)}
aria-labelledby='clear-snapshots-dialog-title'
aria-describedby='clear-snapshots-dialog-description'
>
<DialogTitle id='clear-snapshots-dialog-title'>Clear all snapshots?</DialogTitle>
<DialogContent>
<DialogContentText id='clear-snapshots-dialog-description'>
This will discard your current recording session and cannot be undone. Export your
snapshots first if you need to keep them.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => setClearDialogOpen(false)} autoFocus>
Cancel
</Button>
<Button
onClick={() => {
clearSnapshotsWithFeedback();
setClearDialogOpen(false);
}}
color='error'
>
Clear
</Button>
</DialogActions>
</Dialog>
<div className='snapshots'>
{dropdownSelection === 'Providers / Consumers' && (
<ProvConContainer currentSnapshot={currLocation.stateSnapshot} />
Expand Down