Skip to content
Closed
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
10 changes: 8 additions & 2 deletions frontend/src/components/common/ConfirmationDialog.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable react/prop-types */
/* eslint-disable no-unused-vars */
// frontend/src/components/common/ConfirmationDialog.jsx
import React from 'react';
Expand All @@ -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 (
Expand All @@ -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
>
<DialogTitle id="confirmation-dialog-title">
{title}
Expand All @@ -36,7 +41,7 @@ const ConfirmationDialog = ({
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onClose} disabled={isProcessing}>
<Button onClick={onClose} disabled={isProcessing} data-testid={cancelButtonTestId}>
{cancelText}
</Button>
{/* Make confirm button stand out, often uses primary or error color */}
Expand All @@ -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}
</Button>
Expand Down
20 changes: 14 additions & 6 deletions frontend/src/components/common/EditableField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, minHeight: '40px', width: '100%', ...containerSx }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, minHeight: '40px', width: '100%', ...containerSx }} {...addTestId('container')}>
{!isEditing ? (
// --- MODIFIED Display Mode ---
<>
Expand All @@ -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}
</Typography>
</Box>
{/* Edit Button */}
<IconButton size="small" onClick={handleEditClick} aria-label={`Edit ${label}`} disabled={isSaving} sx={{mt: 0.5 /* Adjust vertical alignment if needed */}}>
<IconButton size="small" onClick={handleEditClick} aria-label={`Edit ${label}`} disabled={isSaving} sx={{mt: 0.5 /* Adjust vertical alignment if needed */}} {...addTestId('edit-button')}>
<EditIcon fontSize="small" />
</IconButton>
</>
Expand All @@ -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') }}
/>
<IconButton size="small" onClick={handleSaveClick} color="success" aria-label={`Save ${label}`} disabled={isSaving}>
<IconButton size="small" onClick={handleSaveClick} color="success" aria-label={`Save ${label}`} disabled={isSaving} {...addTestId('save-button')}>
<CheckIcon />
</IconButton>
<IconButton size="small" onClick={handleCancelClick} color="error" aria-label={`Cancel ${label} edit`} disabled={isSaving}>
<IconButton size="small" onClick={handleCancelClick} color="error" aria-label={`Cancel ${label} edit`} disabled={isSaving} {...addTestId('cancel-button')}>
<CloseIcon />
</IconButton>
</>
Expand Down
16 changes: 12 additions & 4 deletions frontend/src/components/common/EditableTextArea.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
<Box sx={{ width: '100%', position: 'relative', ...containerSx }}>
<Box sx={{ width: '100%', position: 'relative', ...containerSx }} {...addTestId('container')}>
{!isEditing ? (
// Display Mode
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1 }}>
Expand All @@ -67,11 +71,12 @@ const EditableTextArea = ({
color: !value ? 'text.secondary' : 'inherit',
minHeight: '20px'
}}
{...addTestId('display')}
>
{value?.trim() || placeholder || emptyText}
</Typography>
</Box>
<IconButton size="small" onClick={handleEditClick} /* ... other props ... */ sx={{ mt: 1 }}>
<IconButton size="small" onClick={handleEditClick} /* ... other props ... */ sx={{ mt: 1 }} {...addTestId('edit-button')}>
<EditIcon fontSize="small" />
</IconButton>
</Box>
Expand All @@ -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')
/>
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
<IconButton size="small" onClick={handleSaveClick} color="success" aria-label={`Save ${label}`} disabled={isSaving}>
<IconButton size="small" onClick={handleSaveClick} color="success" aria-label={`Save ${label}`} disabled={isSaving} {...addTestId('save-button')}>
<CheckIcon />
</IconButton>
<IconButton size="small" onClick={handleCancelClick} color="error" aria-label={`Cancel ${label} edit`} disabled={isSaving}>
<IconButton size="small" onClick={handleCancelClick} color="error" aria-label={`Cancel ${label} edit`} disabled={isSaving} {...addTestId('cancel-button')}>
<CloseIcon />
</IconButton>
</Box>
Expand Down
12 changes: 8 additions & 4 deletions frontend/src/components/opportunities/AddOpportunityForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,11 @@ const AddOpportunityForm = ({ open, onClose, onSave, initialData = null, isSavin
};

return (
<Dialog open={open} onClose={() => !isSaving && onClose()} maxWidth="sm" fullWidth> {/* Prevent closing while saving */}
<Dialog open={open} onClose={() => !isSaving && onClose()} maxWidth="sm" fullWidth data-testid="opportunity-form-dialog"> {/* Prevent closing while saving */}
<DialogTitle>{initialData ? 'Edit Opportunity Post' : 'Create New Opportunity Post'}</DialogTitle>
<DialogContent>
<TextField
data-testid="opportunity-title-input" // <<< ADDED
autoFocus
margin="dense"
name="title"
Expand All @@ -110,6 +111,7 @@ const AddOpportunityForm = ({ open, onClose, onSave, initialData = null, isSavin
disabled={isSaving}
/>
<TextField
data-testid="opportunity-description-input" // <<< ADDED
margin="dense"
name="description"
label="Description"
Expand All @@ -125,7 +127,7 @@ const AddOpportunityForm = ({ open, onClose, onSave, initialData = null, isSavin
helperText={formErrors.description}
disabled={isSaving}
/>
<FormControl fullWidth margin="dense" required error={!!formErrors.type} disabled={isSaving}>
<FormControl data-testid="opportunity-type-select" fullWidth margin="dense" required error={!!formErrors.type} disabled={isSaving}>
<InputLabel id="opportunity-type-label">Type</InputLabel>
<Select
labelId="opportunity-type-label"
Expand All @@ -145,6 +147,7 @@ const AddOpportunityForm = ({ open, onClose, onSave, initialData = null, isSavin
{formErrors.type && <FormHelperText>{formErrors.type}</FormHelperText>}
</FormControl>
<TextField
data-testid="opportunity-deadline-input"
margin="dense"
name="deadline"
label="Application Deadline (Optional)"
Expand All @@ -161,6 +164,7 @@ const AddOpportunityForm = ({ open, onClose, onSave, initialData = null, isSavin
<FormControlLabel
control={
<Checkbox
data-testid="opportunity-allow-interest-checkbox"
name="allowInterest"
checked={formData.allowInterest}
onChange={handleChange}
Expand All @@ -174,8 +178,8 @@ const AddOpportunityForm = ({ open, onClose, onSave, initialData = null, isSavin

</DialogContent>
<DialogActions>
<Button onClick={onClose} disabled={isSaving}>Cancel</Button>
<Button onClick={handleSave} variant="contained" disabled={isSaving}>
<Button data-testid="opportunity-form-cancel-button" onClick={onClose} disabled={isSaving}>Cancel</Button>
<Button data-testid="opportunity-form-save-button" onClick={handleSave} variant="contained" disabled={isSaving}>
{isSaving ? <CircularProgress size={24} /> : (initialData ? 'Update Post' : 'Create Post')}
</Button>
</DialogActions>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/opportunities/OpportunityFeed.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ const showSnackbar = (message, severity = 'info') => {

{/* Conditional Title based on view */}
<Typography variant="h6" gutterBottom>
{currentView === 'all' ? 'Available Opportunities' : 'Opportunities You\'re Interested In'}
{currentView === 'all' ? 'Available Opportunities' : 'Your Opportunities'}
</Typography>

{/* Loading Indicator */}
Expand Down
14 changes: 9 additions & 5 deletions frontend/src/components/opportunities/OpportunityListItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const OpportunityListItem = ({
const deadlineString = formatDate(opportunity.deadline);

return (
<Paper elevation={2} sx={{ p: 2, mb: 2, borderRadius: 2 }}>
<Paper elevation={2} sx={{ p: 2, mb: 2, borderRadius: 2 }} data-testid={`opportunity-list-item-${opportunity.id}`}>
{/* --- Top Section: Title + Edit/Delete --- */}
<Box sx={{
display: 'flex',
Expand All @@ -64,18 +64,18 @@ const OpportunityListItem = ({
flexWrap: 'wrap',
mb: 1
}}>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>
<Typography variant="h6" sx={{ fontWeight: 'bold' }} data-testid={`opportunity-item-title-${opportunity.id}`}>
{opportunity.title}
</Typography>
{/* Edit and Delete Icons */}
<Box sx={{ display: 'flex', gap: 1 }}>
{isProfessorView && onEdit && (
<IconButton size="small" onClick={() => onEdit(opportunity)}>
<IconButton size="small" onClick={() => onEdit(opportunity)} data-testid={`opportunity-item-edit-button-${opportunity.id}`}>
<EditIcon fontSize="inherit" />
</IconButton>
)}
{isProfessorView && onDelete && (
<IconButton size="small" onClick={() => onDelete(opportunity.id)}>
<IconButton size="small" onClick={() => onDelete(opportunity.id)} data-testid={`opportunity-item-delete-button-${opportunity.id}`}>
<DeleteIcon fontSize="inherit" color="error" />
</IconButton>
)}
Expand All @@ -96,11 +96,13 @@ const OpportunityListItem = ({
label={opportunity.type || 'General'}
size="small"
color="default"
data-testid={`opportunity-item-type-${opportunity.id}`} // <<< ADDED
/>

{/* View Interested / Mark Interest Buttons */}
{isProfessorView && opportunity.allowInterest && onViewInterested && (
<Button
data-testid={`opportunity-item-view-interested-button-${opportunity.id}`}
size="small"
startIcon={<GroupIcon />}
onClick={() => onViewInterested(opportunity.id)}
Expand All @@ -113,6 +115,7 @@ const OpportunityListItem = ({

{isStudentView && opportunity.allowInterest && !isAlreadyInterested && (
<Button
data-testid={`opportunity-item-mark-interest-button-${opportunity.id}`}
variant="contained"
size="small"
startIcon={isProcessingInterest ? <CircularProgress size={16} color="inherit" /> : <FavoriteBorderIcon fontSize="small" />}
Expand All @@ -126,6 +129,7 @@ const OpportunityListItem = ({

{isStudentView && opportunity.allowInterest && isAlreadyInterested && (
<Button
data-testid={`opportunity-item-remove-interest-button-${opportunity.id}`}
variant="outlined"
size="small"
color="error"
Expand Down Expand Up @@ -165,7 +169,7 @@ const OpportunityListItem = ({
<Divider sx={{ my: 1.5 }} />

{/* --- Description Section --- */}
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap', overflowWrap: 'break-word', mb: 1 }}>
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap', overflowWrap: 'break-word', mb: 1 }} data-testid={`opportunity-item-desc-${opportunity.id}`}>
{opportunity.description}
</Typography>

Expand Down
9 changes: 8 additions & 1 deletion frontend/src/components/profile/ProfileInfoSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import EditableTextArea from './../common/EditableTextArea'; // Adjust path if n
import FileUploadField from './../common/FileUploadField'; // Adjust path if needed

const ProfileInfoSection = ({
testIdPrefixes = {},
professorData,
isSaving,
handleNameSave,
Expand All @@ -22,11 +23,12 @@ const ProfileInfoSection = ({
const resumeLink = professorData?.resumeLink || ''; // Extract for clarity

return (
<Box sx={{ pt: 2, pl: { xs: 0, sm: 1 } }}>
<Box sx={{ pt: 2, pl: { xs: 0, sm: 1 } }} >
{/* --- Render EditableFields --- */}
<Stack spacing={3}>
<Box>
<EditableField
testIdPrefix={testIdPrefixes.name} // <<< Pass down specific prefix
label="Full Name"
value={professorData?.name}
onSave={handleNameSave}
Expand All @@ -37,6 +39,7 @@ const ProfileInfoSection = ({
isSaving={isSaving}
/>
<EditableField
testIdPrefix={testIdPrefixes.headline} // <<< Pass down specific prefix
label="Headline/Title"
value={professorData?.headline}
onSave={handleHeadlineSave}
Expand All @@ -48,6 +51,7 @@ const ProfileInfoSection = ({
isSaving={isSaving}
/>
<EditableField
testIdPrefix={testIdPrefixes.pronouns} // <<< Pass down specific prefix
label="Pronouns"
value={professorData?.pronouns}
onSave={handlePronounsSave}
Expand All @@ -61,6 +65,7 @@ const ProfileInfoSection = ({

{/* +++ Add Department Field +++ */}
<EditableField
testIdPrefix={testIdPrefixes.department} // <<< Pass down specific prefix
label="Department"
value={professorData?.department} // Bind to department field
onSave={handleDepartmentSave} // Connect to the save handler
Expand All @@ -77,6 +82,7 @@ const ProfileInfoSection = ({
<Box sx={{ mb: 3 }}>
<Typography variant="h6" gutterBottom sx={{ fontWeight: 'medium' }}>About</Typography>
<EditableTextArea
testIdPrefix={testIdPrefixes.about} // <<< Pass down specific prefix
label="About"
value={professorData?.about}
onSave={handleAboutSave}
Expand All @@ -89,6 +95,7 @@ const ProfileInfoSection = ({

{/* --- Render FileUploadField for Resume --- */}
<FileUploadField
// testIdPrefix={testIdPrefixes.resume} // Example if needed
label="Resume"
fileLink={resumeLink}
accept="application/pdf"
Expand Down
Loading
Loading