diff --git a/frontend/src/components/common/ConfirmationDialog.jsx b/frontend/src/components/common/ConfirmationDialog.jsx index f07a033..d842a89 100644 --- a/frontend/src/components/common/ConfirmationDialog.jsx +++ b/frontend/src/components/common/ConfirmationDialog.jsx @@ -1,3 +1,4 @@ +/* eslint-disable react/prop-types */ /* eslint-disable no-unused-vars */ // frontend/src/components/common/ConfirmationDialog.jsx import React from 'react'; @@ -17,7 +18,10 @@ const ConfirmationDialog = ({ message = "Are you sure you want to proceed? This action cannot be undone.", // Default message confirmText = "Confirm", // Default confirm button text cancelText = "Cancel", // Default cancel button text - isProcessing = false // Optional: disable buttons while processing confirm action + isProcessing = false, // Optional: disable buttons while processing confirm action + dialogTestId = "confirmation-dialog", + confirmButtonTestId = "confirmation-dialog-confirm-button", + cancelButtonTestId = "confirmation-dialog-cancel-button" }) => { return ( @@ -26,6 +30,7 @@ const ConfirmationDialog = ({ onClose={() => !isProcessing && onClose()} // Prevent closing while processing aria-labelledby="confirmation-dialog-title" aria-describedby="confirmation-dialog-description" + data-testid={dialogTestId} // <<< ADDED TEST ID > {title} @@ -36,7 +41,7 @@ const ConfirmationDialog = ({ - {/* Make confirm button stand out, often uses primary or error color */} @@ -46,6 +51,7 @@ const ConfirmationDialog = ({ variant="contained" // Make it more prominent autoFocus // Focus on confirm by default disabled={isProcessing} + data-testid={confirmButtonTestId} > {isProcessing ? "Processing..." : confirmText} diff --git a/frontend/src/components/common/EditableField.jsx b/frontend/src/components/common/EditableField.jsx index 37bf383..966e6c4 100644 --- a/frontend/src/components/common/EditableField.jsx +++ b/frontend/src/components/common/EditableField.jsx @@ -8,6 +8,7 @@ import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; const EditableField = ({ + testIdPrefix, // Optional: Prefix for test IDs (e.g., "student-name") label, // Label for the TextField in edit mode AND display mode now value, // The current value to display onSave, // Async function to call when saving (receives new value) @@ -46,9 +47,12 @@ const EditableField = ({ } }; + // Helper function to generate test ID only if prefix is provided + const addTestId = (suffix) => (testIdPrefix ? { [`data-testid`]: `${testIdPrefix}-${suffix}` } : {}); + return ( // Container - align items to the start (top) for label - + {!isEditing ? ( // --- MODIFIED Display Mode --- <> @@ -68,15 +72,15 @@ const EditableField = ({ color: !value ? 'text.secondary' : 'inherit', // Apply word break for safety, although less likely needed for single-line fields overflowWrap: 'break-word', - lineHeight: 1.4 // Adjust line height if needed - }} + lineHeight: 1.4}} // Adjust line height if needed + {...addTestId('display')} // <<< ADDED TEST ID FOR DISPLAY VALUE > {/* Use value, fallback to emptyText. Placeholder less relevant here */} {value || emptyText} {/* Edit Button */} - + @@ -95,11 +99,15 @@ const EditableField = ({ rows={multiline ? rows : 1} {...textFieldProps} disabled={isSaving} + // Add test ID to the TextField wrapper - Cypress can find input inside + {...addTestId('input-wrapper')} // <<< ADDED TEST ID FOR TEXTFIELD WRAPPER + // Alternatively, add directly to inputProps if needed, but wrapper is often easier + // inputProps={{ ...textFieldProps?.inputProps, ...addTestId('input') }} /> - + - + diff --git a/frontend/src/components/common/EditableTextArea.jsx b/frontend/src/components/common/EditableTextArea.jsx index 5370116..e4f0252 100644 --- a/frontend/src/components/common/EditableTextArea.jsx +++ b/frontend/src/components/common/EditableTextArea.jsx @@ -8,6 +8,7 @@ import CheckIcon from '@mui/icons-material/Check'; import CloseIcon from '@mui/icons-material/Close'; const EditableTextArea = ({ + testIdPrefix, label, // Label for the TextField in edit mode AND display mode now value, // The current value to display onSave, // Async function to call when saving (receives new value) @@ -45,9 +46,12 @@ const EditableTextArea = ({ } }; + // Helper function to generate test ID only if prefix is provided + const addTestId = (suffix) => (testIdPrefix ? { [`data-testid`]: `${testIdPrefix}-${suffix}` } : {}); + return ( // Container remains largely the same - + {!isEditing ? ( // Display Mode @@ -67,11 +71,12 @@ const EditableTextArea = ({ color: !value ? 'text.secondary' : 'inherit', minHeight: '20px' }} + {...addTestId('display')} > {value?.trim() || placeholder || emptyText} - + @@ -90,12 +95,15 @@ const EditableTextArea = ({ autoFocus {...textFieldProps} disabled={isSaving} + {...addTestId('input-wrapper')} // <<< ADDED TEST ID FOR TEXTFIELD WRAPPER + // You might need to target the actual textarea element inside this wrapper in tests + // using .find('textarea') /> - + - + diff --git a/frontend/src/components/directory/FilterControls.jsx b/frontend/src/components/directory/FilterControls.jsx index dd05193..9e66345 100644 --- a/frontend/src/components/directory/FilterControls.jsx +++ b/frontend/src/components/directory/FilterControls.jsx @@ -1,73 +1,113 @@ +/* eslint-disable no-unused-vars */ +/* eslint-disable react/prop-types */ +// frontend/src/components/directory/FilterControls.jsx import React from 'react'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import FilterControls from '../components/directory/FilterControls'; // make sure path is correct +import { Box, Grid, FormControl, InputLabel, Select, MenuItem } from '@mui/material'; -describe('FilterControls Component', () => { - const mockOnRoleChange = jest.fn(); - const mockOnDepartmentChange = jest.fn(); - const mockOnMajorChange = jest.fn(); - const mockOnYearChange = jest.fn(); +const FilterControls = ({ + // Filter values + selectedRole, + selectedDepartment, + selectedMajor, + selectedYear, + // Change handlers + onRoleChange, + onDepartmentChange, + onMajorChange, + onYearChange, + // Options for dropdowns + departmentsList, + majorsList, + yearsList, +}) => { + return ( + {/* Add some margin below filters */} + + {/* Role Filter */} + + + Role + + + - const departmentsList = ['Mathematics', 'Physics']; - const majorsList = ['Software Engineering', 'Computer Science']; - const yearsList = ['Freshman', 'Sophomore', 'Junior', 'Senior']; + {/* Department Filter (Show only if not filtering by Student) */} + {selectedRole !== 'student' && ( + + + Department + + + + )} - beforeEach(() => { - render( - - ); - }); - it('calls correct handler when role changes', async () => { - const roleSelect = screen.getByLabelText('Role'); - await userEvent.click(roleSelect); + {/* Major Filter (Show only if not filtering by Professor) */} + {selectedRole !== 'professor' && ( + + + Major + + + + )} - const studentOption = await screen.findByText('Student'); - await userEvent.click(studentOption); + {/* Year Filter (Show only if not filtering by Professor) */} + {selectedRole !== 'professor' && ( + + + Year + + + + )} - expect(mockOnRoleChange).toHaveBeenCalledTimes(1); - }); + + + ); +}; - it('calls correct handler when major changes', async () => { - const majorSelect = screen.getByLabelText('Major'); - await userEvent.click(majorSelect); - - const majorOption = await screen.findByText('Software Engineering'); - await userEvent.click(majorOption); - - expect(mockOnMajorChange).toHaveBeenCalledTimes(1); - }); - - it('calls correct handler when year changes', async () => { - const yearSelect = screen.getByLabelText('Year'); - await userEvent.click(yearSelect); - - const yearOption = await screen.findByText('Junior'); - await userEvent.click(yearOption); - - expect(mockOnYearChange).toHaveBeenCalledTimes(1); - }); - - it('calls correct handler when department changes', async () => { - const deptSelect = screen.getByLabelText('Department'); - await userEvent.click(deptSelect); - - const deptOption = await screen.findByText('Mathematics'); - await userEvent.click(deptOption); - - expect(mockOnDepartmentChange).toHaveBeenCalledTimes(1); - }); -}); +export default FilterControls; \ No newline at end of file diff --git a/frontend/src/components/opportunities/AddOpportunityForm.jsx b/frontend/src/components/opportunities/AddOpportunityForm.jsx index bfdd70a..c95873a 100644 --- a/frontend/src/components/opportunities/AddOpportunityForm.jsx +++ b/frontend/src/components/opportunities/AddOpportunityForm.jsx @@ -91,10 +91,11 @@ const AddOpportunityForm = ({ open, onClose, onSave, initialData = null, isSavin }; return ( - !isSaving && onClose()} maxWidth="sm" fullWidth> {/* Prevent closing while saving */} + !isSaving && onClose()} maxWidth="sm" fullWidth data-testid="opportunity-form-dialog"> {/* Prevent closing while saving */} {initialData ? 'Edit Opportunity Post' : 'Create New Opportunity Post'} - + Type { - - @@ -305,4 +319,4 @@ const ProfessorCourses = () => { ); }; -export default ProfessorCourses; +export default ProfessorCourses; \ No newline at end of file diff --git a/frontend/src/pages/ProfessorDashboard.jsx b/frontend/src/pages/ProfessorDashboard.jsx index 8bbfb33..7102188 100644 --- a/frontend/src/pages/ProfessorDashboard.jsx +++ b/frontend/src/pages/ProfessorDashboard.jsx @@ -92,6 +92,18 @@ const ProfessorDashboard = () => { const navigate = useNavigate(); const storage = getStorage(app); + + {/* Define the prefixes */} + const profileTestIdPrefixes = { + name: 'prof-profile-name', + headline: 'prof-profile-headline', + pronouns: 'prof-profile-pronouns', + department: 'prof-profile-department', + about: 'prof-profile-about', + // resume: 'prof-profile-resume' // If you add to FileUploadField + }; + + // +++ UPDATED useEffect for Realtime Professor Data +++ useEffect(() => { setUiLoading(true); @@ -487,18 +499,19 @@ const ProfessorDashboard = () => { {isSaving && ( )} - - - - + + + + @@ -511,8 +524,10 @@ const ProfessorDashboard = () => { onEditPhoto={handleTriggerEditPhoto} onViewPhoto={handleTriggerViewPhoto} /> + {/* --- Render ProfileInfoSection --- */} { handleResumeSave={handleResumeSave} handleResumeDelete={handleResumeDelete} handleDepartmentSave={handleDepartmentSave} + // You might need to pass prefixes down if ProfileInfoSection renders EditableFields + // testIdPrefixes={{ + // name: 'prof-profile-name', + // headline: 'prof-profile-headline', + // // etc. + // }} /> @@ -539,7 +560,7 @@ const ProfessorDashboard = () => { gap: 2, mb: 2, }} - > + > My Posted Opportunities @@ -547,6 +568,7 @@ const ProfessorDashboard = () => {