diff --git a/django_email_learning/personalised/views.py b/django_email_learning/personalised/views.py
index 309747dd..972eafb0 100644
--- a/django_email_learning/personalised/views.py
+++ b/django_email_learning/personalised/views.py
@@ -335,6 +335,7 @@ def get(self, request, *args, **kwargs) -> HttpResponse: # type: ignore[no-unty
"django_email_learning:api_personalised:submit_certificate_form"
),
"localeMessages": {
+ "form_title": _("Certificate of Completion"),
"form_intro": _(
"Congratulations on completing the course! To issue your certificate, please enter the name you would like displayed on it."
),
diff --git a/frontend/personalised/assignment_public/Assignment.jsx b/frontend/personalised/assignment_public/Assignment.jsx
index 77973a7a..f6d1c329 100644
--- a/frontend/personalised/assignment_public/Assignment.jsx
+++ b/frontend/personalised/assignment_public/Assignment.jsx
@@ -211,4 +211,6 @@ const Assignment = () => {
};
+export { Assignment };
+
render({ children: });
diff --git a/frontend/personalised/certificate/Certificate.jsx b/frontend/personalised/certificate/Certificate.jsx
index 71e9e646..67ad8d67 100644
--- a/frontend/personalised/certificate/Certificate.jsx
+++ b/frontend/personalised/certificate/Certificate.jsx
@@ -167,4 +167,6 @@ const CertificateContent = () => {
}
+export { Certificate };
+
render({children: });
diff --git a/frontend/personalised/certificate_form/CertificateForm.jsx b/frontend/personalised/certificate_form/CertificateForm.jsx
index 2905bd2b..02275293 100644
--- a/frontend/personalised/certificate_form/CertificateForm.jsx
+++ b/frontend/personalised/certificate_form/CertificateForm.jsx
@@ -105,4 +105,6 @@ const CertificateForm = () => {
);
};
+export { CertificateForm };
+
render({children: });
diff --git a/frontend/personalised/quiz_public/Quiz.jsx b/frontend/personalised/quiz_public/Quiz.jsx
index 6783aac1..27970c51 100644
--- a/frontend/personalised/quiz_public/Quiz.jsx
+++ b/frontend/personalised/quiz_public/Quiz.jsx
@@ -196,4 +196,5 @@ const Quiz = () => {
}
+export { Quiz };
render({children: });
diff --git a/frontend/src/test/Assignment.test.jsx b/frontend/src/test/Assignment.test.jsx
new file mode 100644
index 00000000..e0629f65
--- /dev/null
+++ b/frontend/src/test/Assignment.test.jsx
@@ -0,0 +1,146 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { screen, fireEvent, waitFor } from '@testing-library/react';
+import { renderWithProviders } from './test-utils';
+import { Assignment } from '../../personalised/assignment_public/Assignment.jsx';
+
+vi.mock('../render.jsx');
+
+const sampleAssignment = {
+ id: 1,
+ title: 'Write a Report',
+ description: 'Write a short report about React.',
+ requires_text_submission: true,
+ requires_file_submission: false,
+};
+
+const sampleLocaleMessages = {
+ text_submission_label: 'Your Answer',
+ file_submission_label: 'Upload Your File',
+ submission_success: 'Your assignment has been submitted successfully!',
+ submission_error: 'An error occurred while submitting your assignment.',
+ submit: 'Submit',
+ close_window_message: 'You can now close this window!',
+ text_submission_required: 'Text submission is required.',
+ file_submission_required: 'File submission is required.',
+ error: 'Error',
+};
+
+const defaultAppContext = {
+ assignment: sampleAssignment,
+ token: 'test-token',
+ csrfToken: 'csrf-token',
+ apiEndpoint: '/api/assignment/submit/',
+ fileUploadApiEndpoint: '/api/file/upload/',
+ localeMessages: sampleLocaleMessages,
+ direction: 'ltr',
+};
+
+describe('Assignment', () => {
+ beforeEach(() => {
+ global.fetch.mockResolvedValue({
+ ok: true,
+ json: () => Promise.resolve({ message: 'Submitted!' }),
+ });
+ });
+
+ it('renders the assignment title', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByText('Write a Report')).toBeInTheDocument();
+ });
+
+ it('renders the assignment description', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByText('Write a short report about React.')).toBeInTheDocument();
+ });
+
+ it('renders the text submission field when requires_text_submission is true', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByLabelText('Your Answer')).toBeInTheDocument();
+ });
+
+ it('does not render the text submission field when requires_text_submission is false', () => {
+ const ctx = {
+ ...defaultAppContext,
+ assignment: { ...sampleAssignment, requires_text_submission: false },
+ };
+ renderWithProviders(, { appContext: ctx });
+ expect(screen.queryByLabelText('Your Answer')).not.toBeInTheDocument();
+ });
+
+ it('renders the file upload section when requires_file_submission is true', () => {
+ const ctx = {
+ ...defaultAppContext,
+ assignment: {
+ ...sampleAssignment,
+ requires_text_submission: false,
+ requires_file_submission: true,
+ },
+ };
+ renderWithProviders(, { appContext: ctx });
+ const fileLabels = screen.getAllByText('Upload Your File');
+ expect(fileLabels.length).toBeGreaterThan(0);
+ });
+
+ it('renders the submit button', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();
+ });
+
+ it('shows a validation error when text is required but empty', async () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ await waitFor(() =>
+ expect(screen.getByText('Text submission is required.')).toBeInTheDocument()
+ );
+ expect(global.fetch).not.toHaveBeenCalled();
+ });
+
+ it('shows success message after successful submission', async () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.change(screen.getByLabelText('Your Answer'), {
+ target: { value: 'My answer text' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ await waitFor(() =>
+ expect(screen.getByText('Submitted!')).toBeInTheDocument()
+ );
+ expect(screen.getByText('You can now close this window!')).toBeInTheDocument();
+ });
+
+ it('posts the text submission to the api endpoint', async () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.change(screen.getByLabelText('Your Answer'), {
+ target: { value: 'My answer' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ await waitFor(() => expect(global.fetch).toHaveBeenCalledOnce());
+ const [url, options] = global.fetch.mock.calls[0];
+ expect(url).toBe('/api/assignment/submit/');
+ expect(options.method).toBe('POST');
+ expect(options.headers['X-CSRFToken']).toBe('csrf-token');
+ const body = JSON.parse(options.body);
+ expect(body.text_submission).toBe('My answer');
+ expect(body.token).toBe('test-token');
+ });
+
+ it('shows an error alert when submission fails', async () => {
+ global.fetch.mockResolvedValue({
+ ok: false,
+ json: () => Promise.resolve({ error: 'Server error' }),
+ });
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.change(screen.getByLabelText('Your Answer'), {
+ target: { value: 'My answer' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ await waitFor(() => expect(screen.getByText('Server error')).toBeInTheDocument());
+ });
+
+ it('shows error alert when errorMessage is present', () => {
+ renderWithProviders(, {
+ appContext: { ...defaultAppContext, errorMessage: 'Link expired', ref: 'ref-xyz' },
+ });
+ expect(screen.getByText(/Link expired/)).toBeInTheDocument();
+ expect(screen.getByText(/ref-xyz/)).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/test/Certificate.test.jsx b/frontend/src/test/Certificate.test.jsx
new file mode 100644
index 00000000..a77fd0f9
--- /dev/null
+++ b/frontend/src/test/Certificate.test.jsx
@@ -0,0 +1,79 @@
+import { describe, it, expect, vi } from 'vitest';
+import { screen } from '@testing-library/react';
+import { renderWithProviders } from './test-utils';
+import { Certificate } from '../../personalised/certificate/Certificate.jsx';
+
+vi.mock('../render.jsx');
+
+const defaultAppContext = {
+ name: 'Jane Doe',
+ issueDate: 'January 01, 2025',
+ certificateNumber: 'ORG-COURSE-42-abc123',
+ qrcodeUrl: 'https://example.com/qr.png',
+ logoUrl: 'https://example.com/logo.png',
+ localeMessages: {
+ title: 'Certificate of Completion',
+ description: 'This certifies that Jane Doe has successfully completed the React Fundamentals course',
+ issue_date: 'Issued on',
+ certificate_number: 'Certificate Number',
+ organization_team: 'Acme Team',
+ },
+};
+
+describe('Certificate', () => {
+ it('renders the certificate title', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByText('Certificate of Completion')).toBeInTheDocument();
+ });
+
+ it('renders the recipient name in the description', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByText(/Jane Doe/)).toBeInTheDocument();
+ });
+
+ it('renders the issue date', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByText(/Issued on/)).toBeInTheDocument();
+ expect(screen.getByText(/January 01, 2025/)).toBeInTheDocument();
+ });
+
+ it('renders the certificate number', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByText(/Certificate Number/)).toBeInTheDocument();
+ expect(screen.getByText(/ORG-COURSE-42-abc123/)).toBeInTheDocument();
+ });
+
+ it('renders the organization team name', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByText('Acme Team')).toBeInTheDocument();
+ });
+
+ it('renders the QR code image', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ const qrImg = screen.getByAltText('QR Code');
+ expect(qrImg).toBeInTheDocument();
+ expect(qrImg).toHaveAttribute('src', 'https://example.com/qr.png');
+ });
+
+ it('renders the organization logo when provided', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ const logoImg = screen.getByAltText('Organization Logo');
+ expect(logoImg).toBeInTheDocument();
+ expect(logoImg).toHaveAttribute('src', 'https://example.com/logo.png');
+ });
+
+ it('does not render the organization logo when logoUrl is empty', () => {
+ renderWithProviders(, {
+ appContext: { ...defaultAppContext, logoUrl: '' },
+ });
+ expect(screen.queryByAltText('Organization Logo')).not.toBeInTheDocument();
+ });
+
+ it('shows an error alert when errorMessage is present', () => {
+ renderWithProviders(, {
+ appContext: { ...defaultAppContext, errorMessage: 'Certificate not found' },
+ });
+ expect(screen.getByText('Certificate not found')).toBeInTheDocument();
+ expect(screen.queryByText('Certificate of Completion')).not.toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/test/CertificateForm.test.jsx b/frontend/src/test/CertificateForm.test.jsx
new file mode 100644
index 00000000..0fad0cce
--- /dev/null
+++ b/frontend/src/test/CertificateForm.test.jsx
@@ -0,0 +1,123 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { screen, fireEvent, waitFor } from '@testing-library/react';
+import { renderWithProviders } from './test-utils';
+import { CertificateForm } from '../../personalised/certificate_form/CertificateForm.jsx';
+
+vi.mock('../render.jsx');
+
+const sampleLocaleMessages = {
+ form_title: 'Certificate of Completion',
+ form_intro: 'Congratulations! Enter the name you would like on your certificate.',
+ full_name: 'Full Name',
+ full_name_required: 'Full Name is required',
+ error_sending_data: 'An error occurred while sending data. Please try again later.',
+ form_submission_success: 'Your certificate name has been submitted successfully!',
+ submit: 'Submit',
+ view_certificate: 'View Certificate',
+};
+
+const defaultAppContext = {
+ localeMessages: sampleLocaleMessages,
+ apiEndpoint: '/api/certificate/submit/',
+ token: 'test-token',
+ csrfToken: 'csrf-token',
+};
+
+describe('CertificateForm', () => {
+ beforeEach(() => {
+ global.fetch.mockResolvedValue({
+ ok: true,
+ json: () => Promise.resolve({ certificate_url: '/certificates/ORG-COURSE-42-abc123/' }),
+ });
+ });
+
+ it('renders the form title', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByText('Certificate of Completion')).toBeInTheDocument();
+ });
+
+ it('renders the intro text', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(
+ screen.getByText('Congratulations! Enter the name you would like on your certificate.')
+ ).toBeInTheDocument();
+ });
+
+ it('renders the full name input field', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByLabelText(/Full Name/)).toBeInTheDocument();
+ });
+
+ it('renders the submit button', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();
+ });
+
+ it('shows a validation error when name is empty', async () => {
+ const { container } = renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.submit(container.querySelector('form'));
+ await waitFor(() =>
+ expect(screen.getByText('Full Name is required')).toBeInTheDocument()
+ );
+ expect(global.fetch).not.toHaveBeenCalled();
+ });
+
+ it('submits the form with the entered name', async () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.change(screen.getByLabelText(/Full Name/), {
+ target: { value: 'Jane Doe' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ await waitFor(() => expect(global.fetch).toHaveBeenCalledOnce());
+ const [, options] = global.fetch.mock.calls[0];
+ const body = JSON.parse(options.body);
+ expect(body.name).toBe('Jane Doe');
+ expect(body.token).toBe('test-token');
+ expect(options.headers['X-CSRFToken']).toBe('csrf-token');
+ });
+
+ it('shows success alert after successful submission', async () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.change(screen.getByLabelText(/Full Name/), {
+ target: { value: 'Jane Doe' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ await waitFor(() =>
+ expect(
+ screen.getByText('Your certificate name has been submitted successfully!')
+ ).toBeInTheDocument()
+ );
+ });
+
+ it('shows the View Certificate button after successful submission', async () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.change(screen.getByLabelText(/Full Name/), {
+ target: { value: 'Jane Doe' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ await waitFor(() =>
+ expect(screen.getByRole('link', { name: 'View Certificate' })).toBeInTheDocument()
+ );
+ expect(screen.getByRole('link', { name: 'View Certificate' })).toHaveAttribute(
+ 'href',
+ '/certificates/ORG-COURSE-42-abc123/'
+ );
+ });
+
+ it('shows an error alert when the API call fails', async () => {
+ global.fetch.mockResolvedValue({
+ ok: false,
+ json: () => Promise.resolve({}),
+ });
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.change(screen.getByLabelText(/Full Name/), {
+ target: { value: 'Jane Doe' },
+ });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ await waitFor(() =>
+ expect(
+ screen.getByText('An error occurred while sending data. Please try again later.')
+ ).toBeInTheDocument()
+ );
+ });
+});
diff --git a/frontend/src/test/Quiz.test.jsx b/frontend/src/test/Quiz.test.jsx
new file mode 100644
index 00000000..b6a5ac31
--- /dev/null
+++ b/frontend/src/test/Quiz.test.jsx
@@ -0,0 +1,169 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { screen, fireEvent, waitFor } from '@testing-library/react';
+import { renderWithProviders } from './test-utils';
+import { Quiz } from '../../personalised/quiz_public/Quiz.jsx';
+
+vi.mock('../render.jsx');
+
+const sampleQuiz = {
+ id: 1,
+ title: 'Sample Quiz',
+ is_blocking: true,
+ questions: [
+ {
+ id: 10,
+ text: 'What is 2 + 2?',
+ answers: [
+ { id: 100, text: 'Three' },
+ { id: 101, text: 'Four' },
+ ],
+ },
+ {
+ id: 11,
+ text: 'What color is the sky?',
+ answers: [
+ { id: 200, text: 'Blue' },
+ { id: 201, text: 'Green' },
+ ],
+ },
+ ],
+};
+
+const sampleLocaleMessages = {
+ quiz_intro: 'Select all correct answers.',
+ no_answer_warning: 'You have not selected any answers.',
+ your_score: 'Your score',
+ error_loading_quiz: 'Error loading quiz',
+ ready_to_submit: 'Ready to submit?',
+ submit_quiz_note: 'Note about negative marking.',
+ cancel: 'Cancel',
+ submit: 'Submit',
+ try_again: 'Try Again',
+ close_window_message: 'You can now close this window!',
+ non_blocking_quiz_caption: 'This quiz is for practice.',
+ correct_answer: 'Correct answer',
+ error: 'Error',
+};
+
+const defaultAppContext = {
+ quiz: sampleQuiz,
+ token: 'test-token',
+ csrfToken: 'csrf-token',
+ apiEndpoint: '/api/quiz/submit/',
+ localeMessages: sampleLocaleMessages,
+ direction: 'ltr',
+};
+
+describe('Quiz', () => {
+ beforeEach(() => {
+ global.fetch.mockResolvedValue({
+ ok: true,
+ json: () => Promise.resolve({ passed: true, score: 80, message: 'Well done!', is_invalidated: true }),
+ });
+ });
+
+ it('renders the quiz title', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByText('Sample Quiz')).toBeInTheDocument();
+ });
+
+ it('renders all quiz questions', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByText('What is 2 + 2?')).toBeInTheDocument();
+ expect(screen.getByText('What color is the sky?')).toBeInTheDocument();
+ });
+
+ it('renders quiz intro text', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByText('Select all correct answers.')).toBeInTheDocument();
+ });
+
+ it('renders answer checkboxes for each question', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByText('Three')).toBeInTheDocument();
+ expect(screen.getByText('Four')).toBeInTheDocument();
+ expect(screen.getByText('Blue')).toBeInTheDocument();
+ expect(screen.getByText('Green')).toBeInTheDocument();
+ });
+
+ it('renders the submit button', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();
+ });
+
+ it('opens the confirmation dialog when submit is clicked', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ expect(screen.getByText('Ready to submit?')).toBeInTheDocument();
+ });
+
+ it('shows a warning when submitting with no answers selected', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ expect(screen.getByText('You have not selected any answers.')).toBeInTheDocument();
+ });
+
+ it('does not show warning when at least one answer is selected', () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ const checkboxes = screen.getAllByRole('checkbox');
+ fireEvent.click(checkboxes[0]);
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ expect(screen.queryByText('You have not selected any answers.')).not.toBeInTheDocument();
+ });
+
+ it('closes the dialog when Cancel is clicked', async () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ expect(screen.getByText('Ready to submit?')).toBeInTheDocument();
+ fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
+ await waitFor(() =>
+ expect(screen.queryByText('Ready to submit?')).not.toBeInTheDocument()
+ );
+ });
+
+ it('shows score and passed message after successful submission', async () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ fireEvent.click(screen.getAllByRole('button', { name: 'Submit' }).at(-1));
+ await waitFor(() => expect(screen.getByText(/Well done!/)).toBeInTheDocument());
+ expect(screen.getByText(/80%/)).toBeInTheDocument();
+ });
+
+ it('posts to the api endpoint on submission', async () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ fireEvent.click(screen.getAllByRole('button', { name: 'Submit' }).at(-1));
+ await waitFor(() => expect(global.fetch).toHaveBeenCalledOnce());
+ const [url, options] = global.fetch.mock.calls[0];
+ expect(url).toBe('/api/quiz/submit/');
+ expect(options.method).toBe('POST');
+ expect(options.headers['X-CSRFToken']).toBe('csrf-token');
+ });
+
+ it('shows error alert when errorMessage is present', () => {
+ renderWithProviders(, {
+ appContext: { ...defaultAppContext, errorMessage: 'Quiz not found', ref: 'abc123' },
+ });
+ expect(screen.getByText(/Quiz not found/)).toBeInTheDocument();
+ expect(screen.getByText(/abc123/)).toBeInTheDocument();
+ });
+
+ it('shows non-blocking caption after submission for non-blocking quiz', async () => {
+ const nonBlockingContext = {
+ ...defaultAppContext,
+ quiz: { ...sampleQuiz, is_blocking: false },
+ };
+ renderWithProviders(, { appContext: nonBlockingContext });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ fireEvent.click(screen.getAllByRole('button', { name: 'Submit' }).at(-1));
+ await waitFor(() => expect(screen.getByText(/Well done!/)).toBeInTheDocument());
+ expect(screen.getByText('This quiz is for practice.')).toBeInTheDocument();
+ });
+
+ it('shows close window message after invalidated submission', async () => {
+ renderWithProviders(, { appContext: defaultAppContext });
+ fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
+ fireEvent.click(screen.getAllByRole('button', { name: 'Submit' }).at(-1));
+ await waitFor(() => expect(screen.getByText('You can now close this window!')).toBeInTheDocument());
+ });
+});
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
index 35feed9d..37ed23e5 100644
--- a/frontend/vite.config.js
+++ b/frontend/vite.config.js
@@ -34,7 +34,7 @@ export default defineConfig({
'@emotion/styled',
],
// Force pre-bundling for MPA entry pages.
- entries: ['./platform/courses/Courses.jsx', './platform/course/Course.jsx', './platform/organizations/Organizations.jsx', './platform/learners/Learners.jsx', './platform/settings_api_keys/SettingsApiKeys.jsx', './public/organization/Organization.jsx', './personalised/quiz_public/QuizPublic.jsx', './personalised/assignment_public/Assignment.jsx', './personalised/command_result/CommandResult.jsx'],
+ entries: ['./platform/courses/Courses.jsx', './platform/course/Course.jsx', './platform/organizations/Organizations.jsx', './platform/learners/Learners.jsx', './platform/settings_api_keys/SettingsApiKeys.jsx', './public/organization/Organization.jsx', './personalised/quiz_public/Quiz.jsx', './personalised/assignment_public/Assignment.jsx', './personalised/command_result/CommandResult.jsx'],
},
build: {
minify: 'terser',