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' }); + }); +});