Skip to content
Merged
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
107 changes: 102 additions & 5 deletions src/courseTeam/CourseTeamPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,19 @@ jest.mock('./data/apiHook', () => ({
useAddTeamMember: () => ({ mutate: jest.fn() }),
}));

// Mock the child components, each component should have its own test suite
// Mock the child components but allow passing through props
jest.mock('./components/MembersContent', () => {
return function MembersContent() {
return <div>Members Content</div>;
return function MembersContent({ onEdit }: { onEdit?: (user: any) => void }) {
return (
<div>
Members Content
{onEdit && (
<button onClick={() => onEdit({ username: 'testuser', fullName: 'Test User', email: 'test@example.com', roles: [] })}>
Edit User
</button>
)}
</div>
);
};
});

Expand All @@ -26,8 +35,14 @@ jest.mock('./components/RolesContent', () => {
});

jest.mock('./components/AddTeamMemberModal', () => {
return function AddTeamMemberModal() {
return <div>Add Team Member Modal</div>;
return function AddTeamMemberModal({ isOpen }: { isOpen?: boolean }) {
return isOpen ? <div>Add Team Member Modal</div> : null;
};
});

jest.mock('./components/EditTeamMemberModal', () => {
return function EditTeamMemberModal({ isOpen, user }: { isOpen?: boolean, user?: any }) {
return isOpen && user ? <div>Edit Team Member Modal for {user.username}</div> : null;
};
});

Expand Down Expand Up @@ -74,4 +89,86 @@ describe('CourseTeamPage', () => {
await user.click(rolesTab);
expect(screen.getByText('Roles Content')).toBeInTheDocument();
});

it('opens EditTeamMemberModal when handleEdit is called', async () => {
renderWithAlertAndIntl(<CourseTeamPage />);

// Initially, EditTeamMemberModal should not be visible
expect(screen.queryByText(/Edit Team Member Modal for/)).not.toBeInTheDocument();

// Click the edit button which triggers handleEdit
const editButton = screen.getByRole('button', { name: /Edit User/i });
const user = userEvent.setup();
await user.click(editButton);

// Now EditTeamMemberModal should be visible
expect(screen.getByText('Edit Team Member Modal for testuser')).toBeInTheDocument();
});

it('does not render EditTeamMemberModal initially', () => {
renderWithAlertAndIntl(<CourseTeamPage />);
expect(screen.queryByText(/Edit Team Member Modal for/)).not.toBeInTheDocument();
});

it('initializes with correct state values', () => {
renderWithAlertAndIntl(<CourseTeamPage />);

// Verify initial states by checking that EditTeamMemberModal is not rendered
expect(screen.queryByText(/Edit Team Member Modal for/)).not.toBeInTheDocument();

// Verify the component renders without crashing and shows expected initial content
expect(screen.getByRole('heading', { level: 3 })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /add team member/i })).toBeInTheDocument();
});

it('passes onEdit prop to MembersContent', () => {
renderWithAlertAndIntl(<CourseTeamPage />);
// The mock MembersContent component renders an edit button that uses onEdit
expect(screen.getByRole('button', { name: /Edit User/i })).toBeInTheDocument();
});

it('sets selected user when handleEdit is called', async () => {
renderWithAlertAndIntl(<CourseTeamPage />);

const editButton = screen.getByRole('button', { name: /Edit User/i });
const user = userEvent.setup();
await user.click(editButton);

// Verify the modal shows with the correct user
expect(screen.getByText('Edit Team Member Modal for testuser')).toBeInTheDocument();
});

it('renders both AddTeamMemberModal and EditTeamMemberModal when both are open', async () => {
renderWithAlertAndIntl(<CourseTeamPage />);

// Open AddTeamMemberModal
const addButton = screen.getByRole('button', { name: /add team member/i });
const user = userEvent.setup();
await user.click(addButton);
expect(screen.getByText('Add Team Member Modal')).toBeInTheDocument();

// Open EditTeamMemberModal
const editButton = screen.getByRole('button', { name: /Edit User/i });
await user.click(editButton);
expect(screen.getByText('Edit Team Member Modal for testuser')).toBeInTheDocument();

// Both should be visible
expect(screen.getByText('Add Team Member Modal')).toBeInTheDocument();
expect(screen.getByText('Edit Team Member Modal for testuser')).toBeInTheDocument();
});

it('manages edit modal state correctly through complete cycle', async () => {
renderWithAlertAndIntl(<CourseTeamPage />);
const user = userEvent.setup();

// Initial state - modal not visible
expect(screen.queryByText(/Edit Team Member Modal for/)).not.toBeInTheDocument();

// Open modal by triggering edit
const editButton = screen.getByRole('button', { name: /Edit User/i });
await user.click(editButton);

// Modal should be visible
expect(screen.getByText('Edit Team Member Modal for testuser')).toBeInTheDocument();
});
});
13 changes: 12 additions & 1 deletion src/courseTeam/CourseTeamPage.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { useState } from 'react';
import { useIntl } from '@openedx/frontend-base';
import { Button, Tab, Tabs, useToggle } from '@openedx/paragon';
import { Plus } from '@openedx/paragon/icons';
import AddTeamMemberModal from '@src/courseTeam/components/AddTeamMemberModal';
import EditTeamMemberModal from '@src/courseTeam/components/EditTeamMemberModal';
import MembersContent from '@src/courseTeam/components/MembersContent';
import RolesContent from '@src/courseTeam/components/RolesContent';
import messages from '@src/courseTeam/messages';
import { AlertOutlet } from '@src/providers/AlertProvider';
import { CourseTeamMember } from '@src/courseTeam/types';

const CourseTeamPage = () => {
const intl = useIntl();
const [isOpenAddModal, openAddModal, closeAddModal] = useToggle(false);
const [isOpenEditModal, openEditModal, closeEditModal] = useToggle(false);
const [selectedUser, setSelectedUser] = useState<CourseTeamMember | null>(null);

const handleEdit = (user: CourseTeamMember) => {
setSelectedUser(user);
openEditModal();
};

return (
<>
Expand All @@ -20,13 +30,14 @@ const CourseTeamPage = () => {
<AlertOutlet />
<Tabs>
<Tab eventKey="members" title={intl.formatMessage(messages.membersTab)}>
<MembersContent />
<MembersContent onEdit={handleEdit} />
</Tab>
<Tab eventKey="roles" title={intl.formatMessage(messages.rolesTab)}>
<RolesContent />
</Tab>
</Tabs>
{isOpenAddModal && <AddTeamMemberModal isOpen={isOpenAddModal} onClose={closeAddModal} />}
{isOpenEditModal && selectedUser && <EditTeamMemberModal isOpen={isOpenEditModal} user={selectedUser} onClose={closeEditModal} />}
</>
);
};
Expand Down
6 changes: 3 additions & 3 deletions src/courseTeam/components/AddTeamMemberModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ const AddTeamMemberModal = ({

return (
<ModalDialog isOpen={isOpen} onClose={onClose} title={intl.formatMessage(messages.addNewTeamMember)} isOverflowVisible={false} size="lg">
<ModalDialog.Header>
<ModalDialog.Header className="border-light-700 border-bottom">
<h3 className="text-primary-500">{intl.formatMessage(messages.addNewTeamMember)}</h3>
</ModalDialog.Header>
<ModalDialog.Body>
<ModalDialog.Body className="position-relative overflow-auto">
<p>{intl.formatMessage(messages.addNewTeamMemberDescription, { courseName: displayName })}</p>
<Form.Group>
<Form.Label>{intl.formatMessage(messages.addUsersLabel)}</Form.Label>
Expand All @@ -94,7 +94,7 @@ const AddTeamMemberModal = ({
</Form.Control>
</Form.Group>
</ModalDialog.Body>
<ModalDialog.Footer>
<ModalDialog.Footer className="border-light-700 border-top">
<ActionRow>
<Button variant="tertiary" onClick={onClose}>{intl.formatMessage(messages.cancelButton)}</Button>
<Button variant="primary" onClick={handleSave} disabled={!selectedRole || !inputValue}>{intl.formatMessage(messages.saveButton)}</Button>
Expand Down
Loading
Loading