diff --git a/src/cohorts/components/ManageLearners.test.tsx b/src/cohorts/components/ManageLearners.test.tsx
index c5fb89b3..f3e7f606 100644
--- a/src/cohorts/components/ManageLearners.test.tsx
+++ b/src/cohorts/components/ManageLearners.test.tsx
@@ -1,10 +1,12 @@
-import { screen, fireEvent } from '@testing-library/react';
+import { screen } from '@testing-library/react';
import { useParams } from 'react-router-dom';
import { useAddLearnersToCohort } from '@src/cohorts/data/apiHook';
import { useCohortContext } from '@src/cohorts/components/CohortContext';
import ManageLearners from '@src/cohorts/components/ManageLearners';
import messages from '@src/cohorts/messages';
-import { renderWithIntl } from '@src/testUtils';
+import { renderWithAlertAndIntl } from '@src/testUtils';
+import * as AlertProvider from '@src/providers/AlertProvider';
+import userEvent from '@testing-library/user-event';
jest.mock('react-router-dom', () => ({
useParams: jest.fn(),
@@ -18,18 +20,42 @@ jest.mock('@src/cohorts/components/CohortContext', () => ({
useCohortContext: jest.fn(),
}));
+const renderWithAlertProvider = () => renderWithAlertAndIntl();
+
describe('ManageLearners', () => {
const mutateMock = jest.fn();
+ const addAlertMock = jest.fn();
+ const clearAlertsMock = jest.fn();
+ let useAlertSpy: jest.SpyInstance;
beforeEach(() => {
(useParams as jest.Mock).mockReturnValue({ courseId: 'course-v1:edX+Test+2024' });
(useCohortContext as jest.Mock).mockReturnValue({ selectedCohort: { id: 123 } });
(useAddLearnersToCohort as jest.Mock).mockReturnValue({ mutate: mutateMock });
+
+ useAlertSpy = jest.spyOn(AlertProvider, 'useAlert').mockReturnValue({
+ addAlert: addAlertMock,
+ clearAlerts: clearAlertsMock,
+ showToast: jest.fn(),
+ showModal: jest.fn(),
+ showInlineAlert: jest.fn(),
+ dismissInlineAlert: jest.fn(),
+ inlineAlerts: [],
+ alerts: [],
+ removeAlert: jest.fn(),
+ });
+
mutateMock.mockReset();
+ addAlertMock.mockReset();
+ clearAlertsMock.mockReset();
+ });
+
+ afterEach(() => {
+ useAlertSpy.mockRestore();
});
it('render all static texts', () => {
- renderWithIntl();
+ renderWithAlertProvider();
expect(screen.getByRole('heading', { name: messages.addLearnersTitle.defaultMessage })).toBeInTheDocument();
expect(screen.getByText(messages.addLearnersSubtitle.defaultMessage)).toBeInTheDocument();
expect(screen.getByText(messages.addLearnersInstructions.defaultMessage)).toBeInTheDocument();
@@ -38,11 +64,12 @@ describe('ManageLearners', () => {
expect(screen.getByRole('button', { name: /\+ Add Learners/i })).toBeInTheDocument();
});
- it('updates textarea value and calls mutate on button click', () => {
- renderWithIntl();
+ it('updates textarea value and calls mutate on button click', async () => {
+ renderWithAlertProvider();
const textarea = screen.getByPlaceholderText(messages.learnersExample.defaultMessage);
- fireEvent.change(textarea, { target: { value: 'user1@example.com,user2@example.com' } });
- fireEvent.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
+ const user = userEvent.setup();
+ await user.type(textarea, 'user1@example.com,user2@example.com');
+ await user.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
expect(mutateMock).toHaveBeenCalledWith(
['user1@example.com', 'user2@example.com'],
expect.objectContaining({
@@ -52,11 +79,12 @@ describe('ManageLearners', () => {
);
});
- it('handles empty input gracefully', () => {
- renderWithIntl();
- fireEvent.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
+ it('handles empty input gracefully', async () => {
+ renderWithAlertProvider();
+ const user = userEvent.setup();
+ await user.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
expect(mutateMock).toHaveBeenCalledWith(
- [''],
+ [],
expect.objectContaining({
onSuccess: expect.any(Function),
onError: expect.any(Function),
@@ -64,23 +92,205 @@ describe('ManageLearners', () => {
);
});
- it('calls onError if mutate fails', () => {
- renderWithIntl();
+ it('calls onError if mutate fails and shows error alert', async () => {
+ renderWithAlertProvider();
const textarea = screen.getByPlaceholderText(messages.learnersExample.defaultMessage);
- fireEvent.change(textarea, { target: { value: 'user@example.com' } });
- fireEvent.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
+ const user = userEvent.setup();
+ await user.type(textarea, 'user@example.com');
+ await user.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
const callArgs = mutateMock.mock.calls[0][1];
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
callArgs.onError('error!');
+
expect(consoleErrorSpy).toHaveBeenCalledWith('error!');
+ expect(addAlertMock).toHaveBeenCalledWith({
+ type: 'error',
+ message: messages.addLearnersErrorMessage.defaultMessage
+ });
consoleErrorSpy.mockRestore();
});
- it('uses default cohort id 0 if selectedCohort is missing', () => {
+ it('uses default cohort id 0 if selectedCohort is missing', async () => {
(useCohortContext as jest.Mock).mockReturnValue({ selectedCohort: undefined });
- renderWithIntl();
- fireEvent.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
+ renderWithAlertProvider();
+ const user = userEvent.setup();
+ await user.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
expect(mutateMock).toHaveBeenCalled();
});
+
+ it('clears alerts before adding learners', async () => {
+ renderWithAlertProvider();
+ const textarea = screen.getByPlaceholderText(messages.learnersExample.defaultMessage);
+ const user = userEvent.setup();
+ await user.type(textarea, 'user@example.com');
+ await user.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
+
+ expect(clearAlertsMock).toHaveBeenCalled();
+ });
+
+ it('handles different input formats (newlines and commas)', async () => {
+ renderWithAlertProvider();
+ const user = userEvent.setup();
+ const textarea = screen.getByPlaceholderText(messages.learnersExample.defaultMessage);
+
+ // Test with newlines
+ await user.type(textarea, 'user1@example.com\nuser2@example.com\n\nuser3@example.com');
+ await user.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
+
+ expect(mutateMock).toHaveBeenCalledWith(
+ ['user1@example.com', 'user2@example.com', 'user3@example.com'],
+ expect.objectContaining({
+ onSuccess: expect.any(Function),
+ onError: expect.any(Function),
+ })
+ );
+ });
+
+ it('handles mixed separators and extra spaces', async () => {
+ renderWithAlertProvider();
+ const textarea = screen.getByPlaceholderText(messages.learnersExample.defaultMessage);
+ const user = userEvent.setup();
+ await user.type(textarea, ' user1@example.com, user2@example.com \n user3@example.com , \n\n ');
+ await user.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
+
+ expect(mutateMock).toHaveBeenCalledWith(
+ ['user1@example.com', 'user2@example.com', 'user3@example.com'],
+ expect.objectContaining({
+ onSuccess: expect.any(Function),
+ onError: expect.any(Function),
+ })
+ );
+ });
+
+ describe('onSuccess callback with handleAlertMessages', () => {
+ it('shows warning alert for preassigned learners', async () => {
+ renderWithAlertProvider();
+ const user = userEvent.setup();
+ await user.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
+
+ const callArgs = mutateMock.mock.calls[0][1];
+ const response = {
+ added: [],
+ changed: [],
+ preassigned: ['user1@example.com', 'user2@example.com'],
+ present: [],
+ unknown: []
+ };
+
+ callArgs.onSuccess(response);
+
+ expect(addAlertMock).toHaveBeenCalledWith({
+ type: 'warning',
+ message: expect.stringContaining('2'),
+ extraContent: expect.any(Array)
+ });
+ });
+
+ it('shows success alert for added and changed learners', async () => {
+ renderWithAlertProvider();
+ const user = userEvent.setup();
+ await user.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
+
+ const callArgs = mutateMock.mock.calls[0][1];
+ const response = {
+ added: ['user1@example.com'],
+ changed: ['user2@example.com'],
+ preassigned: [],
+ present: ['user3@example.com'],
+ unknown: []
+ };
+
+ callArgs.onSuccess(response);
+
+ expect(addAlertMock).toHaveBeenCalledWith({
+ type: 'success',
+ message: expect.stringContaining('2'),
+ extraContent: expect.any(Object)
+ });
+ });
+
+ it('shows error alert for unknown learners', async () => {
+ renderWithAlertProvider();
+ const user = userEvent.setup();
+ await user.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
+
+ const callArgs = mutateMock.mock.calls[0][1];
+ const response = {
+ added: [],
+ changed: [],
+ preassigned: [],
+ present: [],
+ unknown: ['invaliduser', 'anotherbaduser']
+ };
+
+ callArgs.onSuccess(response);
+
+ expect(addAlertMock).toHaveBeenCalledWith({
+ type: 'error',
+ message: expect.stringContaining('2'),
+ extraContent: expect.any(Array)
+ });
+ });
+
+ it('shows multiple alerts for mixed response types', async () => {
+ renderWithAlertProvider();
+ const user = userEvent.setup();
+ await user.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
+
+ const callArgs = mutateMock.mock.calls[0][1];
+ const response = {
+ added: ['user1@example.com'],
+ changed: [],
+ preassigned: ['user2@example.com'],
+ present: ['user3@example.com'],
+ unknown: ['baduser']
+ };
+
+ callArgs.onSuccess(response);
+
+ // Should call addAlert 3 times (warning, success, error)
+ expect(addAlertMock).toHaveBeenCalledTimes(3);
+
+ // Check for warning alert (preassigned)
+ expect(addAlertMock).toHaveBeenCalledWith({
+ type: 'warning',
+ message: expect.stringContaining('1'),
+ extraContent: expect.any(Array)
+ });
+
+ // Check for success alert (added + changed)
+ expect(addAlertMock).toHaveBeenCalledWith({
+ type: 'success',
+ message: expect.stringContaining('1'),
+ extraContent: expect.any(Object)
+ });
+
+ // Check for error alert (unknown)
+ expect(addAlertMock).toHaveBeenCalledWith({
+ type: 'error',
+ message: expect.stringContaining('1'),
+ extraContent: expect.any(Array)
+ });
+ });
+
+ it('does not show alerts when response arrays are empty', async () => {
+ renderWithAlertProvider();
+ const user = userEvent.setup();
+ await user.click(screen.getByRole('button', { name: /\+ Add Learners/i }));
+
+ const callArgs = mutateMock.mock.calls[0][1];
+ const response = {
+ added: [],
+ changed: [],
+ preassigned: [],
+ present: [],
+ unknown: []
+ };
+
+ callArgs.onSuccess(response);
+
+ expect(addAlertMock).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/src/cohorts/components/ManageLearners.tsx b/src/cohorts/components/ManageLearners.tsx
index 644e5182..bd568542 100644
--- a/src/cohorts/components/ManageLearners.tsx
+++ b/src/cohorts/components/ManageLearners.tsx
@@ -5,6 +5,15 @@ import { Button, FormControl } from '@openedx/paragon';
import { useCohortContext } from '@src/cohorts/components/CohortContext';
import { useAddLearnersToCohort } from '@src/cohorts/data/apiHook';
import messages from '@src/cohorts/messages';
+import { useAlert } from '@src/providers/AlertProvider';
+
+interface AddLearnersResponse {
+ added: string[],
+ changed: string[],
+ preassigned: string[],
+ present: string[],
+ unknown: string[],
+}
const ManageLearners = () => {
const { courseId = '' } = useParams();
@@ -12,13 +21,62 @@ const ManageLearners = () => {
const { selectedCohort } = useCohortContext();
const { mutate: addLearnersToCohort } = useAddLearnersToCohort(courseId, selectedCohort?.id ? Number(selectedCohort.id) : 0);
const [users, setUsers] = useState('');
+ const { addAlert, clearAlerts } = useAlert();
+
+ const handleAlertMessages = (response: AddLearnersResponse) => {
+ const { added, changed, preassigned, present, unknown } = response;
+ if (preassigned.length > 0) {
+ addAlert({
+ type: 'warning',
+ message: intl.formatMessage(messages.addLearnersWarningMessage, {
+ countLearners: preassigned.length,
+ }),
+ extraContent: (
+ preassigned.map((learner: string) => (
+ • {learner}
+ ))
+ )
+ });
+ }
+ if (present.length > 0 || added.length > 0 || changed.length > 0) {
+ addAlert({
+ type: 'success',
+ message: intl.formatMessage(messages.addLearnersSuccessMessage, {
+ countLearners: added.length + changed.length,
+ }),
+ extraContent: (
+ present.length > 0 && (
+ present.map((learner: string) => (
+ • {intl.formatMessage(messages.existingLearner, { learner })}
+ ))
+ ))
+ });
+ }
+ if (unknown.length > 0) {
+ addAlert({
+ type: 'error',
+ message: intl.formatMessage(messages.addLearnersErrorMessage, {
+ countLearners: unknown.length,
+ }),
+ extraContent: (
+ unknown.map((learner: string) => (
+ • {intl.formatMessage(messages.unknownLearner, { learner })}
+ ))
+ )
+ });
+ }
+ };
const handleAddLearners = () => {
- addLearnersToCohort(users.split(','), {
- onSuccess: () => {
- // Handle success (e.g., show a success message)
- },
+ clearAlerts();
+ const usersArray = users.split(/[\n,]+/).map(u => u.trim()).filter(Boolean);
+ addLearnersToCohort(usersArray, {
+ onSuccess: handleAlertMessages,
onError: (error) => {
+ addAlert({
+ type: 'error',
+ message: intl.formatMessage(messages.addLearnersErrorMessage)
+ });
console.error(error);
}
});
diff --git a/src/cohorts/components/SelectedCohortInfo.test.tsx b/src/cohorts/components/SelectedCohortInfo.test.tsx
new file mode 100644
index 00000000..ab2e1593
--- /dev/null
+++ b/src/cohorts/components/SelectedCohortInfo.test.tsx
@@ -0,0 +1,157 @@
+import { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import SelectedCohortInfo from './SelectedCohortInfo';
+import messages from '../messages';
+import dataDownloadsMessages from '@src/dataDownloads/messages';
+import { renderWithAlertAndIntl } from '@src/testUtils';
+import * as CohortContextModule from '@src/cohorts/components/CohortContext';
+import { CohortProvider } from './CohortContext';
+import { useCohorts, useContentGroupsData } from '../data/apiHook';
+import { assignmentTypes } from '../constants';
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useParams: () => ({ courseId: 'course-v1:edX+DemoX+Demo_Course' }),
+}));
+
+const mockCohorts = [
+ {
+ id: 1,
+ name: 'Initial Cohort',
+ assignmentType: assignmentTypes.manual,
+ groupId: 2,
+ userPartitionId: 3,
+ userCount: 0
+ },
+ {
+ id: 2,
+ name: 'Cohort 2',
+ assignmentType: assignmentTypes.automatic,
+ groupId: null,
+ userPartitionId: null,
+ userCount: 5
+ },
+];
+
+const mockContentGroups = [
+ { id: '2', name: 'Group 1' },
+ { id: '3', name: 'Group 2' },
+];
+
+jest.mock('@src/cohorts/data/apiHook', () => ({
+ useCohorts: jest.fn(),
+ useContentGroupsData: jest.fn(),
+ useCreateCohort: () => ({ mutate: jest.fn() }),
+ usePatchCohort: () => ({ mutate: jest.fn() }),
+ useAddLearnersToCohort: () => ({ mutate: jest.fn() }),
+}));
+
+function renderWithProviders() {
+ return renderWithAlertAndIntl(
+
+
+
+ );
+}
+
+describe('SelectedCohortInfo', () => {
+ const mockSetSelectedCohort = jest.fn();
+ const mockClearSelectedCohort = jest.fn();
+ const mockUpdateCohortField = jest.fn();
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ (useCohorts as jest.Mock).mockReturnValue({ data: mockCohorts });
+ (useContentGroupsData as jest.Mock).mockReturnValue({ data: { groups: mockContentGroups, id: 1 } });
+ jest.spyOn(CohortContextModule, 'useCohortContext').mockReturnValue({
+ selectedCohort: mockCohorts[0],
+ setSelectedCohort: mockSetSelectedCohort,
+ clearSelectedCohort: mockClearSelectedCohort,
+ updateCohortField: mockUpdateCohortField,
+ });
+ });
+
+ describe('Basic Rendering with Selected Cohort', () => {
+ it('renders CohortCard when a cohort is selected', () => {
+ renderWithProviders();
+ expect(screen.getByRole('tablist')).toBeInTheDocument();
+ expect(screen.getByRole('tab', { name: messages.manageLearners.defaultMessage })).toBeInTheDocument();
+ });
+
+ it('renders data downloads hyperlink with correct destination', () => {
+ renderWithProviders();
+ const link = screen.getByRole('link', { name: dataDownloadsMessages.pageTitle.defaultMessage });
+ expect(link).toBeInTheDocument();
+ expect(link).toHaveAttribute('href', '/instructor/course-v1:edX+DemoX+Demo_Course/data_downloads');
+ });
+
+ it('renders complete disclaimer paragraph with correct structure', () => {
+ renderWithProviders();
+ const paragraph = screen.getByText(new RegExp(messages.cohortDisclaimer.defaultMessage)).closest('p');
+ expect(paragraph).toBeInTheDocument();
+ expect(paragraph).toHaveTextContent(messages.cohortDisclaimer.defaultMessage);
+ expect(paragraph).toHaveTextContent(dataDownloadsMessages.pageTitle.defaultMessage);
+ expect(paragraph).toHaveTextContent(messages.page.defaultMessage);
+ });
+ });
+
+ describe('No Selected Cohort', () => {
+ it('still renders disclaimer when no cohort is selected', () => {
+ jest.spyOn(CohortContextModule, 'useCohortContext').mockReturnValue({
+ selectedCohort: null,
+ setSelectedCohort: mockSetSelectedCohort,
+ clearSelectedCohort: mockClearSelectedCohort,
+ updateCohortField: mockUpdateCohortField,
+ });
+
+ renderWithProviders();
+ expect(screen.getByText(new RegExp(messages.cohortDisclaimer.defaultMessage))).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: dataDownloadsMessages.pageTitle.defaultMessage })).toBeInTheDocument();
+ });
+
+ it('do not render CohortCard component even when no cohort selected', () => {
+ jest.spyOn(CohortContextModule, 'useCohortContext').mockReturnValue({
+ selectedCohort: null,
+ setSelectedCohort: mockSetSelectedCohort,
+ clearSelectedCohort: mockClearSelectedCohort,
+ updateCohortField: mockUpdateCohortField,
+ });
+
+ renderWithProviders();
+ expect(screen.queryByRole('tablist')).not.toBeInTheDocument();
+ });
+ });
+
+ describe('Integration with CohortCard', () => {
+ it('renders with manual cohort type and content group', async () => {
+ jest.spyOn(CohortContextModule, 'useCohortContext').mockReturnValue({
+ selectedCohort: mockCohorts[0],
+ setSelectedCohort: mockSetSelectedCohort,
+ clearSelectedCohort: mockClearSelectedCohort,
+ updateCohortField: mockUpdateCohortField,
+ });
+
+ renderWithProviders();
+ expect(screen.getByRole('tablist')).toBeInTheDocument();
+ expect(screen.getByText(messages.manualCohortWarning.defaultMessage)).toBeInTheDocument();
+ expect(screen.getByText(new RegExp(messages.cohortDisclaimer.defaultMessage))).toBeInTheDocument();
+ const settingsTab = screen.getByRole('tab', { name: 'Settings' });
+ await userEvent.click(settingsTab);
+ const selectElement = screen.getByRole('combobox');
+ expect(selectElement).toHaveValue('2');
+ });
+
+ it('renders with automatic cohort type and without content group', () => {
+ jest.spyOn(CohortContextModule, 'useCohortContext').mockReturnValue({
+ selectedCohort: mockCohorts[1],
+ setSelectedCohort: mockSetSelectedCohort,
+ clearSelectedCohort: mockClearSelectedCohort,
+ updateCohortField: mockUpdateCohortField,
+ });
+
+ renderWithProviders();
+ expect(screen.getByRole('tablist')).toBeInTheDocument();
+ expect(screen.getByText(messages.automaticCohortWarning.defaultMessage)).toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/cohorts/components/SelectedCohortInfo.tsx b/src/cohorts/components/SelectedCohortInfo.tsx
index 2dbbb9bf..43006727 100644
--- a/src/cohorts/components/SelectedCohortInfo.tsx
+++ b/src/cohorts/components/SelectedCohortInfo.tsx
@@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom';
import CohortCard from './CohortCard';
import messages from '../messages';
import dataDownloadsMessages from '@src/dataDownloads/messages';
+import { Hyperlink } from '@openedx/paragon';
const SelectedCohortInfo = () => {
const intl = useIntl();
@@ -12,7 +13,7 @@ const SelectedCohortInfo = () => {
<>
- {intl.formatMessage(messages.cohortDisclaimer)} {intl.formatMessage(dataDownloadsMessages.pageTitle)} {intl.formatMessage(messages.page)}
+ {intl.formatMessage(messages.cohortDisclaimer)} {intl.formatMessage(dataDownloadsMessages.pageTitle)} {intl.formatMessage(messages.page)}
>
);
diff --git a/src/cohorts/data/api.ts b/src/cohorts/data/api.ts
index 7c00e164..5378c31d 100644
--- a/src/cohorts/data/api.ts
+++ b/src/cohorts/data/api.ts
@@ -28,7 +28,7 @@ export const createCohort = async (courseId: string, cohortDetails: BasicCohortD
};
export const getContentGroups = async (courseId: string) => {
- const url = `${getApiBaseUrl()}/api/instructor/v1/courses/${courseId}/group_configurations`;
+ const url = `${getApiBaseUrl()}/api/cohorts/v2/courses/${courseId}/group_configurations`;
const { data } = await getAuthenticatedHttpClient().get(url);
return camelCaseObject(data);
};
diff --git a/src/cohorts/data/apiHook.ts b/src/cohorts/data/apiHook.ts
index a061d1dc..c4bd2e79 100644
--- a/src/cohorts/data/apiHook.ts
+++ b/src/cohorts/data/apiHook.ts
@@ -43,6 +43,7 @@ export const useContentGroupsData = (courseId: string) => (
useQuery({
queryKey: cohortsQueryKeys.contentGroups(courseId),
queryFn: () => getContentGroups(courseId),
+ enabled: !!courseId,
})
);
diff --git a/src/cohorts/messages.ts b/src/cohorts/messages.ts
index f8457bc0..8bfd2ee7 100644
--- a/src/cohorts/messages.ts
+++ b/src/cohorts/messages.ts
@@ -186,6 +186,36 @@ const messages = defineMessages({
defaultMessage: 'Add Learners',
description: 'Label for the add learners button'
},
+ manualAssignmentDisabledTooltip: {
+ id: 'instruct.cohorts.manualAssignmentDisabledTooltip',
+ defaultMessage: 'There must be one cohort to which students can automatically be assigned.',
+ description: 'Tooltip message when manual assignment is disabled'
+ },
+ addLearnersSuccessMessage: {
+ id: 'instruct.cohorts.addLearnersSuccessMessage',
+ defaultMessage: '{countLearners} learners have been added to this cohort.',
+ description: 'Success message displayed when learners are added to a cohort'
+ },
+ addLearnersErrorMessage: {
+ id: 'instruct.cohorts.addLearnersErrorMessage',
+ defaultMessage: '{countLearners} learners could not be added to this cohort.',
+ description: 'Error message displayed when there is an issue adding learners to a cohort'
+ },
+ addLearnersWarningMessage: {
+ id: 'instruct.cohorts.addLearnersWarningMessage',
+ defaultMessage: '{countLearners} were pre-assigned for this cohort. This learner will automatically be added to the cohort when they enroll in the course.',
+ description: 'Warning message displayed when some learners could not be added to a cohort'
+ },
+ existingLearner: {
+ id: 'instruct.cohorts.existingLearner',
+ defaultMessage: '{learner} learner was already in the cohort.',
+ description: 'Message indicating that a learner is already assigned to a cohort'
+ },
+ unknownLearner: {
+ id: 'instruct.cohorts.unknownLearner',
+ defaultMessage: 'Unknown username or email: {learner}',
+ description: 'Message indicating that a learner is not recognized in the course'
+ },
});
export default messages;