Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion src/components/Collaboration/JobFormbuilder.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* eslint-disable no-console */
import { useState, useEffect, useMemo } from 'react';
import axios from 'axios';
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { useDispatch, useSelector } from 'react-redux';
import { Prompt } from 'react-router-dom';
import { v4 as uuidv4 } from 'uuid';
Expand Down Expand Up @@ -75,6 +76,7 @@ function JobFormBuilder() {
const [editModalOpen, setEditModalOpen] = useState(false);
const [editingQuestion, setEditingQuestion] = useState(null);
const [editingIndex, setEditingIndex] = useState(null);
const [pendingDeleteIndex, setPendingDeleteIndex] = useState(null);
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);

const markAsSaved = (fields, savedJobTitle) => {
Expand Down Expand Up @@ -187,6 +189,7 @@ function JobFormBuilder() {
};

const deleteField = async index => {
setPendingDeleteIndex(null);
// Update local state immediately
const newFields = [...formFields];
newFields.splice(index, 1);
Expand Down Expand Up @@ -471,7 +474,7 @@ function JobFormBuilder() {
index={index}
totalFields={formFields.length}
onMove={moveField}
onDelete={deleteField}
onDelete={setPendingDeleteIndex}
onEdit={editField}
visible={field.visible}
onVisibilityChange={event => changeVisiblity(event, field)}
Expand Down Expand Up @@ -675,6 +678,36 @@ function JobFormBuilder() {
</div>
</div>
)}
<Modal
isOpen={pendingDeleteIndex !== null}
toggle={() => setPendingDeleteIndex(null)}
labelledBy="delete-question-title"
contentClassName={darkMode ? 'bg-dark text-light' : ''}
returnFocusAfterClose
>
<ModalHeader id="delete-question-title" toggle={() => setPendingDeleteIndex(null)}>
Delete question?
</ModalHeader>
<ModalBody>
<p>Are you sure you want to delete this question?</p>
<p className="font-weight-bold">
{pendingDeleteIndex !== null &&
stripLeadingQuestionNumber(formFields[pendingDeleteIndex]?.questionText || '')}
</p>
<p>
This will remove it from the active form and delete all of its choices.
{currentFormId && ' This change is saved immediately.'}
</p>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={() => setPendingDeleteIndex(null)}>
Cancel
</Button>
<Button color="danger" onClick={() => deleteField(pendingDeleteIndex)}>
Delete question
</Button>
</ModalFooter>
</Modal>
<FormPreviewModal
isOpen={showPreviewModal}
onClose={() => setShowPreviewModal(false)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import axios from 'axios';
import JobFormBuilder from '../JobFormbuilder';
import { ENDPOINTS } from '~/utils/URL';

vi.mock('axios');
vi.mock('../QuestionSetManager', () => ({ default: () => null }));
vi.mock('../QuestionEditModal', () => ({ default: () => null }));
vi.mock('../FormPreviewModal', () => ({ default: () => null }));

const store = {
getState: () => ({ theme: { darkMode: false }, auth: { user: {} } }),
subscribe: () => () => {},
dispatch: () => true,
};

const renderBuilder = () =>
render(
<Provider store={store}>
<MemoryRouter>
<JobFormBuilder />
</MemoryRouter>
</Provider>,
);

const questionLabels = () => screen.getAllByText(/^\d+\.\) /).map(element => element.textContent);

describe('question deletion confirmation', () => {
beforeEach(() => {
vi.clearAllMocks();
axios.get.mockResolvedValue({
data: {
forms: [
{
_id: 'form-1',
title: 'Software Engineer',
questions: [
{ questionText: 'First question', questionType: 'textbox', visible: true },
{
questionText: 'Choose a skill',
questionType: 'dropdown',
options: ['React', 'Node'],
visible: true,
},
{ questionText: 'Last question', questionType: 'textbox', visible: true },
],
},
],
},
});
axios.delete.mockResolvedValue({ data: {} });
});

afterEach(() => {
vi.restoreAllMocks();
});

it('preserves the question, its choices and order when canceled', async () => {
renderBuilder();
await screen.findByText(/Choose a skill/);
const before = questionLabels();

fireEvent.click(screen.getAllByTitle('Delete question')[1]);

expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(axios.delete).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
expect(axios.delete).not.toHaveBeenCalled();
expect(questionLabels()).toEqual(before);
expect(screen.getByRole('option', { name: 'React' })).toBeInTheDocument();
expect(screen.getByRole('option', { name: 'Node' })).toBeInTheDocument();
});

it('deletes only the selected question after confirmation', async () => {
renderBuilder();
await screen.findByText(/Choose a skill/);

fireEvent.click(screen.getAllByTitle('Delete question')[1]);

expect(axios.delete).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole('button', { name: 'Delete question' }));

await waitFor(() => expect(axios.delete).toHaveBeenCalledTimes(1));
expect(axios.delete).toHaveBeenCalledWith(
ENDPOINTS.DELETE_QUESTION('form-1', 1),
expect.any(Object),
);
expect(screen.queryByText(/Choose a skill/)).not.toBeInTheDocument();
expect(questionLabels()).toEqual(['1.) First question', '2.) Last question']);
});
});
Loading