diff --git a/src/shared/AlertMessage/tests/index.test.jsx b/src/shared/AlertMessage/tests/index.test.jsx
new file mode 100644
index 0000000..b90091b
--- /dev/null
+++ b/src/shared/AlertMessage/tests/index.test.jsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import AlertMessage from '../index';
+
+describe('AlertMessage Component', () => {
+ it('renders with default props', () => {
+ render();
+
+ expect(screen.getByText('Test Heading')).toBeInTheDocument();
+
+ const alert = screen.getByRole('alert');
+ expect(alert).toHaveClass('alert-danger');
+ });
+
+ it('renders with a custom variant, heading, and message', () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText('Custom Heading')).toBeInTheDocument();
+ expect(screen.getByText('Custom message')).toBeInTheDocument();
+
+ const alert = screen.getByRole('alert');
+ expect(alert).toHaveClass('alert-warning');
+ });
+
+ it('renders children correctly', () => {
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
+ });
+});
diff --git a/src/shared/DashboardLaunchButton/index.jsx b/src/shared/DashboardLaunchButton/index.jsx
index 652a14b..876c2b9 100644
--- a/src/shared/DashboardLaunchButton/index.jsx
+++ b/src/shared/DashboardLaunchButton/index.jsx
@@ -6,7 +6,7 @@ import { Button } from 'react-paragon-topaz';
import AlertMessage from 'shared/AlertMessage';
import { skillableUrl, defaultErrorMessage } from 'constants';
-import { eventManager } from 'helpers';
+import { eventManager } from '../../helpers';
import './index.scss';
diff --git a/src/shared/DashboardLaunchButton/tests/index.test.jsx b/src/shared/DashboardLaunchButton/tests/index.test.jsx
new file mode 100644
index 0000000..3d56b73
--- /dev/null
+++ b/src/shared/DashboardLaunchButton/tests/index.test.jsx
@@ -0,0 +1,89 @@
+import React from 'react';
+import {
+ render,
+ screen,
+ fireEvent,
+ waitFor,
+} from '@testing-library/react';
+import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
+import DashboardLaunchButton from '../index';
+import '@testing-library/jest-dom';
+
+jest.mock('@edx/frontend-platform/auth', () => ({
+ getAuthenticatedHttpClient: jest.fn(),
+}));
+jest.mock('@edx/frontend-platform/logging', () => ({
+ logError: jest.fn(),
+}));
+jest.mock('shared/AlertMessage', () => () =>
Mocked AlertMessage
);
+
+const mockPost = jest.fn();
+const mockCourseId = 'course-v1:edX+DemoX+Demo_Course';
+
+describe('DashboardLaunchButton Component', () => {
+ beforeEach(() => {
+ getAuthenticatedHttpClient.mockReturnValue({ post: mockPost });
+ jest.clearAllMocks();
+ });
+
+ it('renders the component correctly with the given title', () => {
+ render();
+ expect(screen.getByText('Test Title')).toBeInTheDocument();
+ });
+
+ it('does not show the button initially if is_ccx_course is false', async () => {
+ mockPost.mockResolvedValueOnce({ data: { is_ccx_course: false } });
+
+ render();
+
+ await waitFor(() => {
+ expect(screen.queryByRole('button', { name: /Go To Instructor Dashboard/i })).not.toBeInTheDocument();
+ });
+ });
+
+ it('shows the button if is_ccx_course is true', async () => {
+ mockPost.mockResolvedValueOnce({ data: { is_ccx_course: true } });
+
+ render();
+
+ await waitFor(() => {
+ expect(screen.getByRole('button', { name: /Go To Instructor Dashboard/i })).toBeInTheDocument();
+ });
+ });
+
+ it('handles button click and opens the dashboard URL if present', async () => {
+ mockPost.mockResolvedValueOnce({ data: { is_ccx_course: true } });
+ mockPost.mockResolvedValueOnce({ data: { url: 'https://example.com/dashboard' } });
+
+ render();
+
+ await waitFor(() => {
+ expect(screen.getByRole('button', { name: /Go To Instructor Dashboard/i })).toBeInTheDocument();
+ });
+
+ window.open = jest.fn();
+
+ fireEvent.click(screen.getByRole('button', { name: /Go To Instructor Dashboard/i }));
+
+ await waitFor(() => {
+ expect(window.open).toHaveBeenCalledWith('https://example.com/dashboard', '_blank', 'noopener,noreferrer');
+ });
+ });
+
+ it('handles error when button is clicked and no URL is present', async () => {
+ mockPost.mockResolvedValueOnce({ data: { is_ccx_course: true } });
+ mockPost.mockResolvedValueOnce({ data: { url: '', error: 'Error message' } });
+
+ render();
+
+ await waitFor(() => {
+ expect(screen.getByRole('button', { name: /Go To Instructor Dashboard/i })).toBeInTheDocument();
+ });
+
+ fireEvent.click(screen.getByRole('button', { name: /Go To Instructor Dashboard/i }));
+
+ await waitFor(() => {
+ expect(screen.getByText('Mocked AlertMessage')).toBeInTheDocument();
+ });
+ });
+});
diff --git a/src/shared/JsonViewer/tests/index.test.jsx b/src/shared/JsonViewer/tests/index.test.jsx
new file mode 100644
index 0000000..633d88a
--- /dev/null
+++ b/src/shared/JsonViewer/tests/index.test.jsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import JsonViewer from '../index';
+import '@testing-library/jest-dom';
+
+describe('JsonViewer Component', () => {
+ const mockLabName = 'Test Lab';
+ const mockLabData = {
+ key1: 'value1',
+ key2: 2,
+ key3: true,
+ };
+
+ it('renders the component correctly', () => {
+ render();
+
+ expect(screen.getByText('Test Lab')).toBeInTheDocument();
+ });
+
+ it('initializes JSONEditor with correct options and data', () => {
+ render();
+
+ const editorContainer = document.querySelector('.jsoneditor');
+ expect(editorContainer).toBeInTheDocument();
+ });
+
+ it('destroys JSONEditor instance on component unmount', () => {
+ const { unmount } = render();
+
+ unmount();
+
+ const editorContainer = document.querySelector('.jsoneditor');
+ expect(editorContainer).not.toBeInTheDocument();
+ });
+});
diff --git a/src/shared/LabDetailsCard/tests/index.test.jsx b/src/shared/LabDetailsCard/tests/index.test.jsx
new file mode 100644
index 0000000..eba530e
--- /dev/null
+++ b/src/shared/LabDetailsCard/tests/index.test.jsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import LabDetailsCard from '../index';
+
+describe('LabDetailsCard Component', () => {
+ it('renders with default props', () => {
+ render();
+
+ expect(screen.getByText('LAB PROFILE NAME')).toBeInTheDocument();
+ expect(screen.getByText('USER FIRST NAME')).toBeInTheDocument();
+ expect(screen.getByText('USER LAST NAME')).toBeInTheDocument();
+ expect(screen.getByText('START TIME')).toBeInTheDocument();
+ expect(screen.getByText('END TIME')).toBeInTheDocument();
+ expect(screen.getByText('STATE')).toBeInTheDocument();
+ expect(screen.getByText('COMPLETION STATUS')).toBeInTheDocument();
+ expect(screen.getByText('TOTAL RUN TIME')).toBeInTheDocument();
+ expect(screen.getByText('EXAM PASSED')).toBeInTheDocument();
+
+ expect(screen.getAllByText('N/A')).toHaveLength(8);
+ });
+
+ it('renders with provided props', () => {
+ const details = {
+ labProfileName: 'Sample Lab',
+ userFirstName: 'John',
+ userLastName: 'Doe',
+ startTime: '01/01/2023 10:00 AM',
+ endTime: '01/01/2023 12:00 PM',
+ state: 'Completed',
+ completionStatus: 'Success',
+ totalRunTime: '2 hours',
+ examPassed: 'Yes',
+ };
+
+ render();
+
+ expect(screen.getByText('Sample Lab')).toBeInTheDocument();
+ expect(screen.getByText('John')).toBeInTheDocument();
+ expect(screen.getByText('Doe')).toBeInTheDocument();
+ expect(screen.getByText('01/01/2023 10:00 AM')).toBeInTheDocument();
+ expect(screen.getByText('01/01/2023 12:00 PM')).toBeInTheDocument();
+ expect(screen.getByText('Completed')).toBeInTheDocument();
+ expect(screen.getByText('Success')).toBeInTheDocument();
+ expect(screen.getByText('2 hours')).toBeInTheDocument();
+ expect(screen.getByText('Yes')).toBeInTheDocument();
+ });
+
+ it('renders correctly when some props are missing', () => {
+ const details = {
+ labProfileName: 'Partial Lab',
+ userFirstName: 'Jane',
+ };
+
+ render();
+
+ expect(screen.getByText('Partial Lab')).toBeInTheDocument();
+ expect(screen.getByText('Jane')).toBeInTheDocument();
+
+ const emptySpans = document.querySelectorAll('.lab-details-item span:empty');
+ expect(emptySpans.length).toBe(7);
+ });
+});
diff --git a/src/shared/LabDetailsChartCard/tests/index.test.jsx b/src/shared/LabDetailsChartCard/tests/index.test.jsx
new file mode 100644
index 0000000..5367220
--- /dev/null
+++ b/src/shared/LabDetailsChartCard/tests/index.test.jsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import LabDetailsChartCard from '../index';
+
+describe('LabDetailsChartCard Component', () => {
+ it('renders with default props', () => {
+ render();
+
+ expect(screen.getByText('NUMBER OF TASKS')).toBeInTheDocument();
+ expect(screen.getAllByText('COMPLETED TASKS')).toHaveLength(2);
+ expect(screen.getByText('SCORE')).toBeInTheDocument();
+
+ const numberOfTasks = screen.getByText('NUMBER OF TASKS').nextSibling;
+ const completedTasksElements = screen.getAllByText('COMPLETED TASKS');
+ const completedTasksFirstInstance = completedTasksElements[0].closest('.lab-details-item').querySelector('span');
+ const defaultGauges = screen.getAllByRole('meter'); // Get all gauges
+
+ expect(numberOfTasks).toHaveTextContent('0');
+ expect(completedTasksFirstInstance).toHaveTextContent('0');
+ expect(defaultGauges).toHaveLength(2); // Expecting 2 gauges
+ expect(defaultGauges[0]).toHaveAttribute('aria-valuenow', '0');
+ });
+
+ it('renders with provided props', () => {
+ const mockDetails = {
+ NumTasks: 10,
+ NumCompletedTasks: 7,
+ ExamMaxPossibleScore: 100,
+ ExamScore: 85,
+ };
+
+ render();
+
+ expect(screen.getByText('NUMBER OF TASKS')).toBeInTheDocument();
+ expect(screen.getByText('10')).toBeInTheDocument();
+ expect(screen.getAllByText('COMPLETED TASKS')).toHaveLength(2);
+ expect(screen.getByText('SCORE')).toBeInTheDocument();
+ });
+
+ it('renders the Gauge charts correctly', () => {
+ const mockDetails = {
+ NumTasks: 8,
+ NumCompletedTasks: 5,
+ ExamMaxPossibleScore: 50,
+ ExamScore: 30,
+ };
+
+ render();
+
+ // Check for the presence of the Gauge charts by role
+ const gauges = screen.getAllByRole('meter'); // 'meter' is the role for gauge elements
+ expect(gauges).toHaveLength(2);
+ });
+
+ it('handles missing details correctly', () => {
+ const partialDetails = {
+ NumTasks: 5,
+ };
+
+ render();
+
+ expect(screen.getByText('5')).toBeInTheDocument();
+
+ const completedTasksElements = screen.getAllByText('COMPLETED TASKS');
+ const completedTasksItem = completedTasksElements[0].closest('.lab-details-item');
+ const completedTasksSpan = completedTasksItem.querySelector('span');
+
+ const defaultGauges = screen.getAllByRole('meter'); // Get all gauges
+
+ expect(completedTasksItem).not.toBeNull();
+ expect(completedTasksSpan).not.toBeNull();
+ expect(completedTasksSpan).toHaveTextContent('');
+ expect(defaultGauges).toHaveLength(2); // Expecting 2 gauges
+ expect(defaultGauges[0].hasAttribute('aria-valuenow')).toBe(false);
+ });
+});
diff --git a/src/shared/Table/tests/index.test.jsx b/src/shared/Table/tests/index.test.jsx
new file mode 100644
index 0000000..76b3224
--- /dev/null
+++ b/src/shared/Table/tests/index.test.jsx
@@ -0,0 +1,86 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import Table from '../index';
+
+const mockData = [
+ { id: 1, name: 'John Doe', email: 'john@example.com' },
+ { id: 2, name: 'Jane Smith', email: 'jane@example.com' },
+];
+
+const mockColumns = [
+ { Header: 'Name', accessor: 'name' },
+ { Header: 'Email', accessor: 'email' },
+];
+
+const mockHandlePagination = jest.fn();
+
+describe('Table Component', () => {
+ it('renders the table component with data', () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
+ expect(screen.getByText('Jane Smith')).toBeInTheDocument();
+ });
+
+ it('displays loading state when isLoading is true', () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText(/loading/i)).toBeInTheDocument();
+ });
+
+ it('shows empty message when there is no data', () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText('No data available')).toBeInTheDocument();
+ });
+
+ it('handles pagination correctly', () => {
+ render(
+ ,
+ );
+
+ const paginationButton = screen.getByRole('button', { name: /2/i });
+ fireEvent.click(paginationButton);
+
+ expect(mockHandlePagination).toHaveBeenCalledWith(2);
+ });
+});
diff --git a/src/shared/TableFilter/tests/index.test.jsx b/src/shared/TableFilter/tests/index.test.jsx
new file mode 100644
index 0000000..69ec5cd
--- /dev/null
+++ b/src/shared/TableFilter/tests/index.test.jsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import TableFilter from '../index';
+
+const mockOnFilterSubmit = jest.fn();
+
+describe('TableFilter Component', () => {
+ beforeEach(() => {
+ mockOnFilterSubmit.mockClear();
+ });
+
+ it('renders the component with initial state', () => {
+ render();
+
+ expect(screen.getByPlaceholderText('Search student')).toBeInTheDocument();
+ expect(screen.getByText('username')).toBeInTheDocument();
+ expect(screen.getByText('email')).toBeInTheDocument();
+ expect(screen.getByText('Apply')).toBeDisabled();
+ expect(screen.getByText('Reset')).toBeInTheDocument();
+ });
+
+ it('updates the filter value when typing in the input field', () => {
+ render();
+ const input = screen.getByPlaceholderText('Search student');
+
+ fireEvent.change(input, { target: { value: 'john' } });
+
+ expect(input.value).toBe('john');
+ expect(screen.getByText('Apply')).toBeEnabled();
+ });
+
+ it('calls onFilterSubmit with the correct filter value on form submit', () => {
+ render();
+ const input = screen.getByPlaceholderText('Search student');
+ const submitButton = screen.getByText('Apply');
+
+ fireEvent.change(input, { target: { value: 'john' } });
+ fireEvent.click(submitButton);
+
+ expect(mockOnFilterSubmit).toHaveBeenCalledWith({ username: 'john' });
+ });
+
+ it('resets the filter value and error message when the reset button is clicked', () => {
+ render();
+ const input = screen.getByPlaceholderText('Search student');
+ const resetButton = screen.getByText('Reset');
+
+ fireEvent.change(input, { target: { value: 'john' } });
+ fireEvent.click(resetButton);
+
+ expect(input.value).toBe('');
+ expect(screen.queryByText('Please enter a value to search.')).not.toBeInTheDocument();
+ expect(mockOnFilterSubmit).toHaveBeenCalledWith({});
+ });
+
+ it('displays external error message when provided as prop', () => {
+ const errorMessage = 'External error';
+ render();
+
+ expect(screen.getByText(errorMessage)).toBeInTheDocument();
+ });
+
+ it('changes the selected parameter correctly', () => {
+ render();
+ const emailRadio = screen.getByLabelText('email');
+
+ fireEvent.click(emailRadio);
+ const input = screen.getByPlaceholderText('Search student');
+
+ fireEvent.change(input, { target: { value: 'john@example.com' } });
+ const submitButton = screen.getByText('Apply');
+ fireEvent.click(submitButton);
+
+ expect(mockOnFilterSubmit).toHaveBeenCalledWith({ email: 'john@example.com' });
+ });
+});