Skip to content
184 changes: 179 additions & 5 deletions src/__tests__/components/flow/CodeEditorModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('CodeEditorModal', () => {
expect(screen.getByText(/Edge ID: edge-123/i)).toBeInTheDocument();
});

it('calls onSave and then onClose when Save is clicked', async () => {
it('calls onSave when Save is clicked and modal stays open', async () => {
const onSave = jest.fn().mockResolvedValue(undefined);
const onClose = jest.fn();

Expand All @@ -74,12 +74,13 @@ describe('CodeEditorModal', () => {
/>,
);

fireEvent.click(screen.getByRole('button', { name: /Save/i }));
fireEvent.click(screen.getByRole('button', { name: /💾 Save/i }));

await waitFor(() => {
expect(onSave).toHaveBeenCalledWith('initial code');
});
expect(onClose).toHaveBeenCalled();
// Modal stays open after save - onClose is NOT called automatically
expect(onClose).not.toHaveBeenCalled();
});

it('shows error dialog if save fails', async () => {
Expand All @@ -92,9 +93,182 @@ describe('CodeEditorModal', () => {
/>,
);

fireEvent.click(screen.getByRole('button', { name: /Save/i }));
fireEvent.click(screen.getByRole('button', { name: /💾 Save/i }));

expect(await screen.findByTestId('confirm-Unable to save file')).toBeInTheDocument();
});
});

it('does not render when isOpen is false', () => {
render(<CodeEditorModal {...defaultProps} isOpen={false} />);
expect(screen.queryByText(/Reaction Editor/i)).not.toBeInTheDocument();
});

it('updates code when editor content changes', () => {
render(<CodeEditorModal {...defaultProps} />);

const editor = screen.getByTestId('monaco-editor');
fireEvent.change(editor, { target: { value: 'new code' } });

expect(editor).toHaveValue('new code');
});

it('shows unsaved changes indicator after editing', async () => {
render(<CodeEditorModal {...defaultProps} />);

const editor = screen.getByTestId('monaco-editor');
fireEvent.change(editor, { target: { value: 'modified code' } });

expect(screen.getByText(/Unsaved changes/i)).toBeInTheDocument();
});

it('hides unsaved changes indicator when code matches initial code', () => {
render(<CodeEditorModal {...defaultProps} />);

// Initially no unsaved changes
expect(screen.queryByText(/Unsaved changes/i)).not.toBeInTheDocument();
});

it('shows unsaved changes dialog when closing with unsaved changes', async () => {
const onClose = jest.fn();
render(
<CodeEditorModal
{...defaultProps}
onClose={onClose}
/>,
);

// Modify the code
fireEvent.change(screen.getByTestId('monaco-editor'), {
target: { value: 'modified code' },
});

// Click the X close button
fireEvent.click(screen.getByTitle('Close (Esc)'));

// Should show confirmation dialog, not close immediately
expect(onClose).not.toHaveBeenCalled();
expect(screen.getByTestId('confirm-Unsaved Changes')).toBeInTheDocument();
});

it('closes without saving when confirming unsaved changes dialog', async () => {
const onClose = jest.fn();
render(
<CodeEditorModal
{...defaultProps}
onClose={onClose}
/>,
);

fireEvent.change(screen.getByTestId('monaco-editor'), {
target: { value: 'modified code' },
});

fireEvent.click(screen.getByTitle('Close (Esc)'));

// Click "Discard" / confirm button in dialog
const dialog = screen.getByTestId('confirm-Unsaved Changes');
fireEvent.click(dialog.querySelector('button')!);

expect(onClose).toHaveBeenCalled();
});

it('closes without confirmation when no unsaved changes', () => {
const onClose = jest.fn();
render(
<CodeEditorModal
{...defaultProps}
onClose={onClose}
/>,
);

// Click close without making changes
fireEvent.click(screen.getByTitle('Close (Esc)'));

// Should close immediately without confirmation
expect(onClose).toHaveBeenCalled();
expect(screen.queryByTestId('confirm-Unsaved Changes')).not.toBeInTheDocument();
});

it('hides unsaved changes indicator after successful save', async () => {
const onSave = jest.fn().mockResolvedValue(undefined);
render(
<CodeEditorModal
{...defaultProps}
onSave={onSave}
/>,
);

// Make a change
fireEvent.change(screen.getByTestId('monaco-editor'), {
target: { value: 'modified code' },
});

expect(screen.getByText(/Unsaved changes/i)).toBeInTheDocument();

// Save
fireEvent.click(screen.getByRole('button', { name: /💾 Save/i }));

await waitFor(() => {
expect(screen.queryByText(/Unsaved changes/i)).not.toBeInTheDocument();
});
});

it('calls onDelete and onClose when Delete is clicked and confirmed', async () => {
const onDelete = jest.fn();
const onClose = jest.fn();

render(
<CodeEditorModal
{...defaultProps}
onDelete={onDelete}
onClose={onClose}
/>,
);

fireEvent.click(screen.getByTitle('Delete relation'));

// Confirm the deletion dialog
const dialog = await screen.findByTestId('confirm-Delete Relation');
fireEvent.click(dialog.querySelector('button')!);

expect(onDelete).toHaveBeenCalled();
});

it('does not call onDelete when deletion is cancelled', async () => {
const onDelete = jest.fn();

render(
<CodeEditorModal
{...defaultProps}
onDelete={onDelete}
/>,
);

fireEvent.click(screen.getByTitle('Delete relation'));

const dialog = await screen.findByTestId('confirm-Delete Relation');
const buttons = dialog.querySelectorAll('button');
// Click cancel (second button)
fireEvent.click(buttons[1]);

expect(onDelete).not.toHaveBeenCalled();
});

it('resets code and unsaved state when initialCode prop changes', async () => {
const { rerender } = render(<CodeEditorModal {...defaultProps} />);

// Modify code
fireEvent.change(screen.getByTestId('monaco-editor'), {
target: { value: 'modified code' },
});
expect(screen.getByText(/Unsaved changes/i)).toBeInTheDocument();

// Rerender with new initialCode (simulates reopening editor)
rerender(<CodeEditorModal {...defaultProps} initialCode="new initial code" />);

await waitFor(() => {
expect(screen.getByTestId('monaco-editor')).toHaveValue('new initial code');
expect(screen.queryByText(/Unsaved changes/i)).not.toBeInTheDocument();
});
});
});
Loading
Loading