diff --git a/src/components/BulkManagementHistoryView/HistoryTable.test.jsx b/src/components/BulkManagementHistoryView/HistoryTable.test.jsx
index 5fa40b9..35369af 100644
--- a/src/components/BulkManagementHistoryView/HistoryTable.test.jsx
+++ b/src/components/BulkManagementHistoryView/HistoryTable.test.jsx
@@ -1,108 +1,189 @@
-/* eslint-disable import/no-named-as-default */
import React from 'react';
-import { shallow } from '@edx/react-unit-test-utils';
+import { render, screen, initializeMocks } from 'testUtilsExtra';
import { DataTable } from '@openedx/paragon';
import selectors from 'data/selectors';
import { bulkManagementColumns } from 'data/constants/app';
+import { HistoryTable, mapHistoryRows, mapStateToProps } from './HistoryTable';
import ResultsSummary from './ResultsSummary';
-import { HistoryTable, mapStateToProps } from './HistoryTable';
-jest.mock('@openedx/paragon', () => ({ DataTable: () => 'DataTable' }));
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
+initializeMocks();
-jest.mock('@edx/frontend-platform/i18n', () => ({
- defineMessages: m => m,
- FormattedMessage: () => 'FormattedMessage',
+jest.mock('@openedx/paragon', () => ({
+ DataTable: jest.fn(() =>
DataTable
),
}));
+jest.mock('./ResultsSummary', () => jest.fn(() => ResultsSummary
));
jest.mock('data/selectors', () => ({
__esModule: true,
default: {
grades: {
- bulkManagementHistoryEntries: jest.fn(state => ({ historyEntries: state })),
+ bulkManagementHistoryEntries: jest.fn(),
},
},
}));
-jest.mock('./ResultsSummary', () => 'ResultsSummary');
describe('HistoryTable', () => {
- describe('component', () => {
- const entry1 = {
- originalFilename: 'blue.png',
- user: 'Eifel',
- timeUploaded: '65',
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ const mockBulkManagementHistory = [
+ {
+ originalFilename: 'test-file-1.csv',
+ user: 'test-user-1',
+ timeUploaded: '2025-01-01T10:00:00Z',
resultsSummary: {
- rowId: 12,
- courseId: 'Da Bu Dee',
- text: 'Da ba daa',
+ rowId: 1,
+ text: 'Download results 1',
},
- };
- const entry2 = {
- originalFilename: 'allStar.jpg',
- user: 'Smashmouth',
- timeUploaded: '2000s?',
+ },
+ {
+ originalFilename: 'test-file-2.csv',
+ user: 'test-user-2',
+ timeUploaded: '2025-01-02T10:00:00Z',
resultsSummary: {
- courseId: 'rockstar',
rowId: 2,
- text: 'all that glitters is gold',
+ text: 'Download results 2',
},
+ },
+ ];
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('mapHistoryRows', () => {
+ const mockRow = {
+ resultsSummary: {
+ rowId: 1,
+ text: 'Download results',
+ },
+ originalFilename: 'test-file.csv',
+ user: 'test-user',
+ timeUploaded: '2025-01-01T10:00:00Z',
};
- const props = {
- bulkManagementHistory: [entry1, entry2],
- };
- let el;
- describe('snapshot', () => {
- beforeEach(() => {
- el = shallow();
- });
- test('snapshot - loads formatted table', () => {
- expect(el.snapshot).toMatchSnapshot();
- });
- describe('history table', () => {
- let table;
- beforeEach(() => {
- table = el.instance.findByType(DataTable);
- });
- describe('data (from bulkManagementHistory.map(this.formatHistoryRow)', () => {
- const fieldAssertions = [
- 'maps resultsSummay to ResultsSummary',
- 'wraps filename and user',
- 'forwards the rest',
- ];
- test(`snapshot: ${fieldAssertions.join(', ')}`, () => {
- expect(table[0].props.data).toMatchSnapshot();
- });
- test(fieldAssertions.join(', '), () => {
- const rows = table[0].props.data;
- expect(rows[0].resultsSummary).toEqual();
- expect(rows[0].user).toEqual({entry1.user});
- expect(
- rows[0].filename,
- ).toEqual({entry1.originalFilename});
- expect(rows[1].resultsSummary).toEqual();
- expect(rows[1].user).toEqual({entry2.user});
- expect(
- rows[1].filename,
- ).toEqual({entry2.originalFilename});
- });
- });
- test('columns from bulkManagementColumns', () => {
- expect(table[0].props.columns).toEqual(bulkManagementColumns);
- });
- });
+
+ it('transforms row data correctly', () => {
+ const result = mapHistoryRows(mockRow);
+
+ expect(result).toHaveProperty('resultsSummary');
+ expect(result).toHaveProperty('filename');
+ expect(result).toHaveProperty('user');
+ expect(result).toHaveProperty('timeUploaded');
+ expect(result.timeUploaded).toBe('2025-01-01T10:00:00Z');
+ });
+
+ it('wraps filename in span with correct class', () => {
+ const result = mapHistoryRows(mockRow);
+ render({result.filename}
);
+
+ const filenameSpan = screen.getByText('test-file.csv');
+ expect(filenameSpan).toBeInTheDocument();
+ expect(filenameSpan).toHaveClass('wrap-text-in-cell');
+ });
+
+ it('wraps user in span with correct class', () => {
+ const result = mapHistoryRows(mockRow);
+ render({result.user}
);
+
+ const userSpan = screen.getByText('test-user');
+ expect(userSpan).toBeInTheDocument();
+ expect(userSpan).toHaveClass('wrap-text-in-cell');
+ });
+
+ it('renders ResultsSummary component with correct props', () => {
+ const result = mapHistoryRows(mockRow);
+ render({result.resultsSummary}
);
+
+ expect(ResultsSummary).toHaveBeenCalledWith(mockRow.resultsSummary, {});
+ expect(screen.getByTestId('results-summary')).toBeInTheDocument();
+ });
+ });
+
+ describe('component', () => {
+ it('renders DataTable with empty data when no history provided', () => {
+ render();
+
+ expect(DataTable).toHaveBeenCalledWith(
+ {
+ data: [],
+ hasFixedColumnWidths: true,
+ columns: bulkManagementColumns,
+ className: 'table-striped',
+ itemCount: 0,
+ },
+ {},
+ );
+ expect(screen.getByTestId('data-table')).toBeInTheDocument();
+ });
+
+ it('renders DataTable with mapped history data', () => {
+ render(
+ ,
+ );
+
+ expect(DataTable).toHaveBeenCalledWith(
+ {
+ data: expect.arrayContaining([
+ expect.objectContaining({
+ filename: expect.any(Object),
+ user: expect.any(Object),
+ resultsSummary: expect.any(Object),
+ timeUploaded: '2025-01-01T10:00:00Z',
+ }),
+ expect.objectContaining({
+ filename: expect.any(Object),
+ user: expect.any(Object),
+ resultsSummary: expect.any(Object),
+ timeUploaded: '2025-01-02T10:00:00Z',
+ }),
+ ]),
+ hasFixedColumnWidths: true,
+ columns: bulkManagementColumns,
+ className: 'table-striped',
+ itemCount: 2,
+ },
+ {},
+ );
+ });
+
+ it('passes correct props to DataTable', () => {
+ render(
+ ,
+ );
+
+ const dataTableCall = DataTable.mock.calls[0][0];
+ expect(dataTableCall.hasFixedColumnWidths).toBe(true);
+ expect(dataTableCall.columns).toBe(bulkManagementColumns);
+ expect(dataTableCall.className).toBe('table-striped');
+ expect(dataTableCall.itemCount).toBe(mockBulkManagementHistory.length);
});
});
describe('mapStateToProps', () => {
- const testState = { a: 'simple', test: 'state' };
- let mapped;
+ const mockState = { test: 'state' };
+ const mockHistoryEntries = [
+ { originalFilename: 'file1.csv', user: 'user1' },
+ { originalFilename: 'file2.csv', user: 'user2' },
+ ];
+
beforeEach(() => {
- mapped = mapStateToProps(testState);
+ selectors.grades.bulkManagementHistoryEntries.mockReturnValue(
+ mockHistoryEntries,
+ );
});
- test('bulkManagementHistory from grades.bulkManagementHistoryEntries', () => {
+
+ it('maps bulkManagementHistory from selector', () => {
+ const result = mapStateToProps(mockState);
+
expect(
- mapped.bulkManagementHistory,
- ).toEqual(selectors.grades.bulkManagementHistoryEntries(testState));
+ selectors.grades.bulkManagementHistoryEntries,
+ ).toHaveBeenCalledWith(mockState);
+ expect(result.bulkManagementHistory).toBe(mockHistoryEntries);
});
});
});
diff --git a/src/components/BulkManagementHistoryView/__snapshots__/HistoryTable.test.jsx.snap b/src/components/BulkManagementHistoryView/__snapshots__/HistoryTable.test.jsx.snap
deleted file mode 100644
index 1dd5c74..0000000
--- a/src/components/BulkManagementHistoryView/__snapshots__/HistoryTable.test.jsx.snap
+++ /dev/null
@@ -1,118 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`HistoryTable component snapshot history table data (from bulkManagementHistory.map(this.formatHistoryRow) snapshot: maps resultsSummay to ResultsSummary, wraps filename and user, forwards the rest 1`] = `
-[
- {
- "filename":
- blue.png
- ,
- "resultsSummary": ,
- "timeUploaded": "65",
- "user":
- Eifel
- ,
- },
- {
- "filename":
- allStar.jpg
- ,
- "resultsSummary": ,
- "timeUploaded": "2000s?",
- "user":
- Smashmouth
- ,
- },
-]
-`;
-
-exports[`HistoryTable component snapshot snapshot - loads formatted table 1`] = `
-
- blue.png
- ,
- "resultsSummary": ,
- "timeUploaded": "65",
- "user":
- Eifel
- ,
- },
- {
- "filename":
- allStar.jpg
- ,
- "resultsSummary": ,
- "timeUploaded": "2000s?",
- "user":
- Smashmouth
- ,
- },
- ]
- }
- hasFixedColumnWidths={true}
- itemCount={2}
-/>
-`;
diff --git a/src/components/GradebookFilters/StudentGroupsFilter/__snapshots__/index.test.jsx.snap b/src/components/GradebookFilters/StudentGroupsFilter/__snapshots__/index.test.jsx.snap
deleted file mode 100644
index 33f137b..0000000
--- a/src/components/GradebookFilters/StudentGroupsFilter/__snapshots__/index.test.jsx.snap
+++ /dev/null
@@ -1,72 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`StudentGroupsFilter component render snapshot 1`] = `
-
-
- Track-All
- ,
- ,
- ,
- ,
- ,
- ]
- }
- value="test-track"
- />
-
- Cohort-All
- ,
- ,
- ,
- ,
- ]
- }
- value="test-cohort"
- />
-
-`;
diff --git a/src/components/GradebookFilters/StudentGroupsFilter/index.test.jsx b/src/components/GradebookFilters/StudentGroupsFilter/index.test.jsx
index 7f2fa52..5992272 100644
--- a/src/components/GradebookFilters/StudentGroupsFilter/index.test.jsx
+++ b/src/components/GradebookFilters/StudentGroupsFilter/index.test.jsx
@@ -1,84 +1,167 @@
import React from 'react';
-import { shallow } from '@edx/react-unit-test-utils';
-import { useIntl } from '@edx/frontend-platform/i18n';
+import { render, screen, initializeMocks } from 'testUtilsExtra';
import SelectGroup from '../SelectGroup';
+import { StudentGroupsFilter } from './index';
import useStudentGroupsFilterData from './hooks';
-import StudentGroupsFilter from '.';
-jest.mock('../SelectGroup', () => 'SelectGroup');
-jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
+jest.mock('../SelectGroup', () => jest.fn(() => SelectGroup
));
+jest.mock('./hooks', () => jest.fn());
-const props = {
- cohorts: {
- value: 'test-cohort',
+initializeMocks();
+
+describe('StudentGroupsFilter', () => {
+ const mockUpdateQueryParams = jest.fn();
+
+ const mockTracksData = {
+ value: 'test-track-value',
entries: [
- { value: 'v1', name: 'n1' },
- { value: 'v2', name: 'n2' },
- { value: 'v3', name: 'n3' },
+ { value: 'track1', name: 'Track 1' },
+ { value: 'track2', name: 'Track 2' },
+ ],
+ handleChange: jest.fn(),
+ };
+
+ const mockCohortsData = {
+ value: 'test-cohort-value',
+ entries: [
+ { value: 'cohort1', name: 'Cohort 1' },
+ { value: 'cohort2', name: 'Cohort 2' },
],
handleChange: jest.fn(),
isDisabled: false,
- },
- tracks: {
- value: 'test-track',
- entries: [
- { value: 'v1', name: 'n1' },
- { value: 'v2', name: 'n2' },
- { value: 'v3', name: 'n3' },
- { value: 'v4', name: 'n4' },
- ],
- handleChange: jest.fn(),
- },
-};
-useStudentGroupsFilterData.mockReturnValue(props);
-const updateQueryParams = jest.fn();
+ };
-let el;
-describe('StudentGroupsFilter component', () => {
- beforeAll(() => {
+ beforeEach(() => {
jest.clearAllMocks();
- el = shallow();
- });
- describe('behavior', () => {
- it('initializes hooks', () => {
- expect(useStudentGroupsFilterData).toHaveBeenCalledWith({ updateQueryParams });
- expect(useIntl).toHaveBeenCalledWith();
+ useStudentGroupsFilterData.mockReturnValue({
+ tracks: mockTracksData,
+ cohorts: mockCohortsData,
});
});
- describe('render', () => {
- test('snapshot', () => {
- expect(el.snapshot).toMatchSnapshot();
+
+ it('calls useStudentGroupsFilterData hook with updateQueryParams', () => {
+ render();
+
+ expect(useStudentGroupsFilterData).toHaveBeenCalledWith({
+ updateQueryParams: mockUpdateQueryParams,
});
- test('track options', () => {
- const {
- options,
- onChange,
- value,
- } = el.instance.findByType(SelectGroup)[0].props;
- expect(value).toEqual(props.tracks.value);
- expect(onChange).toEqual(props.tracks.handleChange);
- expect(options.length).toEqual(5);
- const testEntry = props.tracks.entries[0];
- const optionProps = options[1].props;
- expect(optionProps.value).toEqual(testEntry.value);
- expect(optionProps.children).toEqual(testEntry.name);
+ });
+
+ it('renders two SelectGroup components', () => {
+ render();
+
+ expect(SelectGroup).toHaveBeenCalledTimes(2);
+ expect(screen.getAllByTestId('select-group')).toHaveLength(2);
+ });
+
+ describe('tracks SelectGroup', () => {
+ it('renders tracks SelectGroup with correct props', () => {
+ render();
+
+ const tracksCall = SelectGroup.mock.calls[0][0];
+ expect(tracksCall.id).toBe('Tracks');
+ expect(tracksCall.value).toBe(mockTracksData.value);
+ expect(tracksCall.onChange).toBe(mockTracksData.handleChange);
});
- test('cohort options', () => {
- const {
- options,
- onChange,
- disabled,
- value,
- } = el.instance.findByType(SelectGroup)[1].props;
- expect(value).toEqual(props.cohorts.value);
- expect(disabled).toEqual(false);
- expect(onChange).toEqual(props.cohorts.handleChange);
- expect(options.length).toEqual(4);
- const testEntry = props.cohorts.entries[0];
- const optionProps = options[1].props;
- expect(optionProps.value).toEqual(testEntry.value);
- expect(optionProps.children).toEqual(testEntry.name);
+
+ it('includes trackAll option in tracks SelectGroup', () => {
+ render();
+
+ const tracksCall = SelectGroup.mock.calls[0][0];
+ const { options } = tracksCall;
+
+ expect(options).toHaveLength(3);
+ expect(options[0].props.value).toBeDefined();
+ expect(options[0].props.children).toBeDefined();
+ });
+
+ it('includes track entries in tracks SelectGroup options', () => {
+ render();
+
+ const tracksCall = SelectGroup.mock.calls[0][0];
+ const { options } = tracksCall;
+
+ expect(options[1].props.value).toBe('track1');
+ expect(options[1].props.children).toBe('Track 1');
+ expect(options[2].props.value).toBe('track2');
+ expect(options[2].props.children).toBe('Track 2');
+ });
+ });
+
+ describe('cohorts SelectGroup', () => {
+ it('renders cohorts SelectGroup with correct props', () => {
+ render();
+
+ const cohortsCall = SelectGroup.mock.calls[1][0];
+ expect(cohortsCall.id).toBe('Cohorts');
+ expect(cohortsCall.value).toBe(mockCohortsData.value);
+ expect(cohortsCall.onChange).toBe(mockCohortsData.handleChange);
+ expect(cohortsCall.disabled).toBe(mockCohortsData.isDisabled);
+ });
+
+ it('includes cohortAll option in cohorts SelectGroup', () => {
+ render();
+
+ const cohortsCall = SelectGroup.mock.calls[1][0];
+ const { options } = cohortsCall;
+
+ expect(options).toHaveLength(3);
+ expect(options[0].props.value).toBeDefined();
+ expect(options[0].props.children).toBeDefined();
+ });
+
+ it('includes cohort entries in cohorts SelectGroup options', () => {
+ render();
+
+ const cohortsCall = SelectGroup.mock.calls[1][0];
+ const { options } = cohortsCall;
+
+ expect(options[1].props.value).toBe('cohort1');
+ expect(options[1].props.children).toBe('Cohort 1');
+ expect(options[2].props.value).toBe('cohort2');
+ expect(options[2].props.children).toBe('Cohort 2');
+ });
+
+ it('passes disabled state to cohorts SelectGroup', () => {
+ useStudentGroupsFilterData.mockReturnValue({
+ tracks: mockTracksData,
+ cohorts: { ...mockCohortsData, isDisabled: true },
+ });
+
+ render();
+
+ const cohortsCall = SelectGroup.mock.calls[1][0];
+ expect(cohortsCall.disabled).toBe(true);
+ });
+ });
+
+ describe('with empty entries', () => {
+ it('handles empty tracks entries', () => {
+ useStudentGroupsFilterData.mockReturnValue({
+ tracks: { ...mockTracksData, entries: [] },
+ cohorts: mockCohortsData,
+ });
+
+ render();
+
+ const tracksCall = SelectGroup.mock.calls[0][0];
+ expect(tracksCall.options).toHaveLength(1);
+ });
+
+ it('handles empty cohorts entries', () => {
+ useStudentGroupsFilterData.mockReturnValue({
+ tracks: mockTracksData,
+ cohorts: { ...mockCohortsData, entries: [] },
+ });
+
+ render();
+
+ const cohortsCall = SelectGroup.mock.calls[1][0];
+ expect(cohortsCall.options).toHaveLength(1);
});
});
});
diff --git a/src/components/GradebookHeader/__snapshots__/index.test.jsx.snap b/src/components/GradebookHeader/__snapshots__/index.test.jsx.snap
deleted file mode 100644
index bf0c9e2..0000000
--- a/src/components/GradebookHeader/__snapshots__/index.test.jsx.snap
+++ /dev/null
@@ -1,139 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`GradebookHeader component render default view shapshot 1`] = `
-
-`;
-
-exports[`GradebookHeader component render frozen grades snapshot: show frozen warning 1`] = `
-
-
-
- <<
-
- Back to Dashboard
-
-
- Gradebook
-
-
-
- test-course-id
-
-
-
- The grades for this course are now frozen. Editing of grades is no longer allowed.
-
-
-`;
-
-exports[`GradebookHeader component render show bulk management snapshot: show toggle view message button with handleToggleViewClick method 1`] = `
-
-`;
-
-exports[`GradebookHeader component render user cannot view gradebook snapshot: show unauthorized warning 1`] = `
-
-`;
diff --git a/src/components/GradebookHeader/index.test.jsx b/src/components/GradebookHeader/index.test.jsx
index 52d0a62..0f93f95 100644
--- a/src/components/GradebookHeader/index.test.jsx
+++ b/src/components/GradebookHeader/index.test.jsx
@@ -1,77 +1,303 @@
import React from 'react';
-import { shallow } from '@edx/react-unit-test-utils';
-import { useIntl } from '@edx/frontend-platform/i18n';
-import { Button } from '@openedx/paragon';
+import { render, screen, initializeMocks } from 'testUtilsExtra';
+import userEvent from '@testing-library/user-event';
-import { formatMessage } from 'testUtils';
import { instructorDashboardUrl } from 'data/services/lms/urls';
+import { GradebookHeader } from './index';
import useGradebookHeaderData from './hooks';
-import GradebookHeader from '.';
+import messages from './messages';
-jest.mock('./hooks', () => ({ __esModule: true, default: jest.fn() }));
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
jest.mock('data/services/lms/urls', () => ({
instructorDashboardUrl: jest.fn(),
}));
+jest.mock('./hooks', () => jest.fn());
-instructorDashboardUrl.mockReturnValue('test-dashboard-url');
+initializeMocks();
-const hookProps = {
- areGradesFrozen: false,
- canUserViewGradebook: true,
- courseId: 'test-course-id',
- handleToggleViewClick: jest.fn().mockName('hooks.handleToggleViewClick'),
- showBulkManagement: false,
- toggleViewMessage: { defaultMessage: 'toggle-view-message' },
-};
-useGradebookHeaderData.mockReturnValue(hookProps);
+describe('GradebookHeader', () => {
+ const mockHandleToggleViewClick = jest.fn();
-let el;
-describe('GradebookHeader component', () => {
- beforeAll(() => {
- el = shallow();
+ beforeEach(() => {
+ jest.clearAllMocks();
+ instructorDashboardUrl.mockReturnValue('https://example.com/dashboard');
});
- describe('behavior', () => {
- it('initializes hooks', () => {
- expect(useGradebookHeaderData).toHaveBeenCalledWith();
- expect(useIntl).toHaveBeenCalledWith();
+
+ describe('basic rendering', () => {
+ beforeEach(() => {
+ useGradebookHeaderData.mockReturnValue({
+ areGradesFrozen: false,
+ canUserViewGradebook: true,
+ courseId: 'course-v1:TestU+CS101+2024',
+ handleToggleViewClick: mockHandleToggleViewClick,
+ showBulkManagement: false,
+ toggleViewMessage: messages.toActivityLog,
+ });
+ });
+
+ it('renders the main header container', () => {
+ render();
+ const header = screen.getByText('Gradebook').closest('.gradebook-header');
+ expect(header).toHaveClass('gradebook-header');
+ });
+
+ it('renders back to dashboard link', () => {
+ render();
+ const dashboardLink = screen.getByRole('link');
+ expect(dashboardLink).toHaveAttribute(
+ 'href',
+ 'https://example.com/dashboard',
+ );
+ expect(dashboardLink).toHaveClass('mb-3');
+ expect(dashboardLink).toHaveTextContent('Back to Dashboard');
+ });
+
+ it('renders gradebook title', () => {
+ render();
+ const title = screen.getByRole('heading', { level: 1 });
+ expect(title).toHaveTextContent('Gradebook');
+ });
+
+ it('renders course ID subtitle', () => {
+ render();
+ const subtitle = screen.getByRole('heading', { level: 2 });
+ expect(subtitle).toHaveTextContent('course-v1:TestU+CS101+2024');
+ expect(subtitle).toHaveClass('text-break');
+ });
+
+ it('renders subtitle row with correct classes', () => {
+ render();
+ const subtitleRow = screen.getByRole('heading', {
+ level: 2,
+ }).parentElement;
+ expect(subtitleRow).toHaveClass(
+ 'subtitle-row',
+ 'd-flex',
+ 'justify-content-between',
+ 'align-items-center',
+ );
+ });
+
+ it('calls instructorDashboardUrl to get dashboard URL', () => {
+ render();
+ expect(instructorDashboardUrl).toHaveBeenCalled();
+ });
+
+ it('calls useGradebookHeaderData hook', () => {
+ render();
+ expect(useGradebookHeaderData).toHaveBeenCalled();
});
});
- describe('render', () => {
- describe('default view', () => {
- test('shapshot', () => {
- expect(el.snapshot).toMatchSnapshot();
+
+ describe('bulk management toggle button', () => {
+ describe('when showBulkManagement is true', () => {
+ beforeEach(() => {
+ useGradebookHeaderData.mockReturnValue({
+ areGradesFrozen: false,
+ canUserViewGradebook: true,
+ courseId: 'course-v1:TestU+CS101+2024',
+ handleToggleViewClick: mockHandleToggleViewClick,
+ showBulkManagement: true,
+ toggleViewMessage: messages.toActivityLog,
+ });
+ });
+
+ it('renders toggle view button', () => {
+ render();
+ expect(screen.getByRole('button')).toBeInTheDocument();
+ });
+
+ it('displays correct button text from toggleViewMessage', () => {
+ render();
+ const toggleButton = screen.getByRole('button');
+ expect(toggleButton).toHaveTextContent('View Bulk Management History');
+ });
+
+ it('calls handleToggleViewClick when button is clicked', async () => {
+ render();
+ const user = userEvent.setup();
+ const toggleButton = screen.getByRole('button');
+
+ await user.click(toggleButton);
+ expect(mockHandleToggleViewClick).toHaveBeenCalledTimes(1);
+ });
+
+ it('displays correct message from toggleViewMessage', () => {
+ useGradebookHeaderData.mockReturnValue({
+ areGradesFrozen: false,
+ canUserViewGradebook: true,
+ courseId: 'course-v1:TestU+CS101+2024',
+ handleToggleViewClick: mockHandleToggleViewClick,
+ showBulkManagement: true,
+ toggleViewMessage: messages.toGradesView,
+ });
+
+ render();
+ const toggleButton = screen.getByRole('button');
+ expect(toggleButton).toHaveTextContent('Return to Gradebook');
});
});
- describe('show bulk management', () => {
+
+ describe('when showBulkManagement is false', () => {
beforeEach(() => {
- useGradebookHeaderData.mockReturnValueOnce({ ...hookProps, showBulkManagement: true });
- el = shallow();
+ useGradebookHeaderData.mockReturnValue({
+ areGradesFrozen: false,
+ canUserViewGradebook: true,
+ courseId: 'course-v1:TestU+CS101+2024',
+ handleToggleViewClick: mockHandleToggleViewClick,
+ showBulkManagement: false,
+ toggleViewMessage: messages.toActivityLog,
+ });
});
- test('snapshot: show toggle view message button with handleToggleViewClick method', () => {
- expect(el.snapshot).toMatchSnapshot();
- const { onClick } = el.instance.findByType(Button)[0].props;
- expect(onClick).toEqual(hookProps.handleToggleViewClick);
- expect(el.instance.findByType(Button)[0].children[0].el).toEqual(formatMessage(hookProps.toggleViewMessage));
+
+ it('does not render toggle view button', () => {
+ render();
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
});
});
- describe('frozen grades', () => {
+ });
+
+ describe('frozen grades warning', () => {
+ describe('when areGradesFrozen is true', () => {
beforeEach(() => {
- useGradebookHeaderData.mockReturnValueOnce({ ...hookProps, areGradesFrozen: true });
- el = shallow();
+ useGradebookHeaderData.mockReturnValue({
+ areGradesFrozen: true,
+ canUserViewGradebook: true,
+ courseId: 'course-v1:TestU+CS101+2024',
+ handleToggleViewClick: mockHandleToggleViewClick,
+ showBulkManagement: false,
+ toggleViewMessage: messages.toActivityLog,
+ });
});
- test('snapshot: show frozen warning', () => {
- expect(el.snapshot).toMatchSnapshot();
+
+ it('renders frozen warning alert', () => {
+ render();
+ const alert = screen.getByRole('alert');
+ expect(alert).toHaveClass('alert', 'alert-warning');
+ expect(alert).toHaveTextContent(
+ 'The grades for this course are now frozen. Editing of grades is no longer allowed.',
+ );
});
});
- describe('user cannot view gradebook', () => {
+
+ describe('when areGradesFrozen is false', () => {
beforeEach(() => {
- useGradebookHeaderData.mockReturnValueOnce({ ...hookProps, canUserViewGradebook: false });
- el = shallow();
+ useGradebookHeaderData.mockReturnValue({
+ areGradesFrozen: false,
+ canUserViewGradebook: true,
+ courseId: 'course-v1:TestU+CS101+2024',
+ handleToggleViewClick: mockHandleToggleViewClick,
+ showBulkManagement: false,
+ toggleViewMessage: messages.toActivityLog,
+ });
});
- test('snapshot: show unauthorized warning', () => {
- expect(el.snapshot).toMatchSnapshot();
+
+ it('does not render frozen warning alert', () => {
+ render();
+ expect(
+ screen.queryByText(
+ 'The grades for this course are now frozen. Editing of grades is no longer allowed.',
+ ),
+ ).not.toBeInTheDocument();
});
});
});
+
+ describe('unauthorized warning', () => {
+ describe('when canUserViewGradebook is false', () => {
+ beforeEach(() => {
+ useGradebookHeaderData.mockReturnValue({
+ areGradesFrozen: false,
+ canUserViewGradebook: false,
+ courseId: 'course-v1:TestU+CS101+2024',
+ handleToggleViewClick: mockHandleToggleViewClick,
+ showBulkManagement: false,
+ toggleViewMessage: messages.toActivityLog,
+ });
+ });
+
+ it('renders unauthorized warning alert', () => {
+ render();
+ const alert = screen.getByRole('alert');
+ expect(alert).toHaveClass('alert', 'alert-warning');
+ expect(alert).toHaveTextContent(
+ 'You are not authorized to view the gradebook for this course.',
+ );
+ });
+ });
+
+ describe('when canUserViewGradebook is true', () => {
+ beforeEach(() => {
+ useGradebookHeaderData.mockReturnValue({
+ areGradesFrozen: false,
+ canUserViewGradebook: true,
+ courseId: 'course-v1:TestU+CS101+2024',
+ handleToggleViewClick: mockHandleToggleViewClick,
+ showBulkManagement: false,
+ toggleViewMessage: messages.toActivityLog,
+ });
+ });
+
+ it('does not render unauthorized warning alert', () => {
+ render();
+ expect(
+ screen.queryByText(
+ 'You are not authorized to view the gradebook for this course.',
+ ),
+ ).not.toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('multiple warnings', () => {
+ it('renders both frozen and unauthorized warnings when both conditions are true', () => {
+ useGradebookHeaderData.mockReturnValue({
+ areGradesFrozen: true,
+ canUserViewGradebook: false,
+ courseId: 'course-v1:TestU+CS101+2024',
+ handleToggleViewClick: mockHandleToggleViewClick,
+ showBulkManagement: false,
+ toggleViewMessage: messages.toActivityLog,
+ });
+
+ render();
+ const alerts = screen.getAllByRole('alert');
+ expect(alerts).toHaveLength(2);
+
+ expect(
+ screen.getByText(
+ 'The grades for this course are now frozen. Editing of grades is no longer allowed.',
+ ),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText(
+ 'You are not authorized to view the gradebook for this course.',
+ ),
+ ).toBeInTheDocument();
+ });
+ });
+
+ describe('complete integration', () => {
+ it('renders all elements when showBulkManagement is true', () => {
+ useGradebookHeaderData.mockReturnValue({
+ areGradesFrozen: false,
+ canUserViewGradebook: true,
+ courseId: 'course-v1:TestU+CS101+2024',
+ handleToggleViewClick: mockHandleToggleViewClick,
+ showBulkManagement: true,
+ toggleViewMessage: messages.toActivityLog,
+ });
+
+ render();
+
+ expect(screen.getByRole('link')).toBeInTheDocument();
+ expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument();
+ expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument();
+ expect(screen.getByRole('button')).toBeInTheDocument();
+ expect(screen.queryByRole('alert')).not.toBeInTheDocument();
+ });
+ });
});
diff --git a/src/components/GradesView/BulkManagementControls/__snapshots__/index.test.jsx.snap b/src/components/GradesView/BulkManagementControls/__snapshots__/index.test.jsx.snap
deleted file mode 100644
index 64299ed..0000000
--- a/src/components/GradesView/BulkManagementControls/__snapshots__/index.test.jsx.snap
+++ /dev/null
@@ -1,19 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`BulkManagementControls render snapshot - show - network and import buttons 1`] = `
-
-
-
-
-`;
diff --git a/src/components/GradesView/BulkManagementControls/index.test.jsx b/src/components/GradesView/BulkManagementControls/index.test.jsx
index 3a7ca39..27fde69 100644
--- a/src/components/GradesView/BulkManagementControls/index.test.jsx
+++ b/src/components/GradesView/BulkManagementControls/index.test.jsx
@@ -1,32 +1,163 @@
import React from 'react';
-import { shallow } from '@edx/react-unit-test-utils';
+import { render, screen, initializeMocks } from 'testUtilsExtra';
+import NetworkButton from 'components/NetworkButton';
+import ImportGradesButton from '../ImportGradesButton';
+
+import { BulkManagementControls } from './index';
import useBulkManagementControlsData from './hooks';
-import BulkManagementControls from '.';
-
-jest.mock('../ImportGradesButton', () => 'ImportGradesButton');
-jest.mock('components/NetworkButton', () => 'NetworkButton');
+import messages from './messages';
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
+jest.mock('components/NetworkButton', () => jest.fn(() => NetworkButton
));
+jest.mock('../ImportGradesButton', () => jest.fn(() => (
+ ImportGradesButton
+)));
jest.mock('./hooks', () => jest.fn());
-const hookProps = {
- show: true,
- handleClickExportGrades: jest.fn(),
-};
-useBulkManagementControlsData.mockReturnValue(hookProps);
+initializeMocks();
describe('BulkManagementControls', () => {
- describe('behavior', () => {
- shallow();
- expect(useBulkManagementControlsData).toHaveBeenCalledWith();
+ const mockHandleClickExportGrades = jest.fn();
+
+ beforeEach(() => {
+ jest.clearAllMocks();
});
- describe('render', () => {
- test('snapshot - show - network and import buttons', () => {
- expect(shallow().snapshot).toMatchSnapshot();
+
+ describe('when show is false', () => {
+ beforeEach(() => {
+ useBulkManagementControlsData.mockReturnValue({
+ show: false,
+ handleClickExportGrades: mockHandleClickExportGrades,
+ });
});
- test('snapshot - empty if show is not truthy', () => {
- useBulkManagementControlsData.mockReturnValueOnce({ ...hookProps, show: false });
- expect(shallow().isEmptyRender()).toEqual(true);
+
+ it('renders nothing when show is false', () => {
+ render();
+ expect(screen.queryByTestId('network-button')).not.toBeInTheDocument();
+ expect(
+ screen.queryByTestId('import-grades-button'),
+ ).not.toBeInTheDocument();
+ });
+
+ it('does not render NetworkButton when show is false', () => {
+ render();
+ expect(NetworkButton).not.toHaveBeenCalled();
+ });
+
+ it('does not render ImportGradesButton when show is false', () => {
+ render();
+ expect(ImportGradesButton).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when show is true', () => {
+ beforeEach(() => {
+ useBulkManagementControlsData.mockReturnValue({
+ show: true,
+ handleClickExportGrades: mockHandleClickExportGrades,
+ });
+ });
+
+ it('renders the container div with correct class when show is true', () => {
+ render();
+ const containerDiv = screen.getByTestId('network-button').parentElement;
+ expect(containerDiv).toHaveClass('d-flex');
+ });
+
+ it('renders NetworkButton with correct props', () => {
+ render();
+
+ expect(NetworkButton).toHaveBeenCalledWith(
+ {
+ label: messages.downloadGradesBtn,
+ onClick: mockHandleClickExportGrades,
+ },
+ {},
+ );
+ expect(screen.getByTestId('network-button')).toBeInTheDocument();
+ });
+
+ it('renders ImportGradesButton', () => {
+ render();
+
+ expect(ImportGradesButton).toHaveBeenCalledWith({}, {});
+ expect(screen.getByTestId('import-grades-button')).toBeInTheDocument();
+ });
+
+ it('calls handleClickExportGrades when NetworkButton is clicked', () => {
+ render();
+
+ const networkButtonCall = NetworkButton.mock.calls[0][0];
+ const { onClick } = networkButtonCall;
+
+ onClick();
+ expect(mockHandleClickExportGrades).toHaveBeenCalledTimes(1);
+ });
+
+ it('passes correct label to NetworkButton', () => {
+ render();
+
+ const networkButtonCall = NetworkButton.mock.calls[0][0];
+ expect(networkButtonCall.label).toBe(messages.downloadGradesBtn);
+ });
+
+ it('renders both buttons in the correct order', () => {
+ render();
+
+ expect(NetworkButton).toHaveBeenCalled();
+ expect(ImportGradesButton).toHaveBeenCalled();
+
+ const networkButton = screen.getByTestId('network-button');
+ const importButton = screen.getByTestId('import-grades-button');
+
+ expect(networkButton).toBeInTheDocument();
+ expect(importButton).toBeInTheDocument();
+ });
+ });
+
+ describe('hook integration', () => {
+ it('calls useBulkManagementControlsData hook', () => {
+ useBulkManagementControlsData.mockReturnValue({
+ show: true,
+ handleClickExportGrades: mockHandleClickExportGrades,
+ });
+
+ render();
+ expect(useBulkManagementControlsData).toHaveBeenCalledTimes(1);
+ });
+
+ it('uses the show value from hook to determine rendering', () => {
+ useBulkManagementControlsData.mockReturnValue({
+ show: false,
+ handleClickExportGrades: mockHandleClickExportGrades,
+ });
+
+ render();
+ expect(screen.queryByTestId('network-button')).not.toBeInTheDocument();
+
+ useBulkManagementControlsData.mockReturnValue({
+ show: true,
+ handleClickExportGrades: mockHandleClickExportGrades,
+ });
+
+ render();
+ expect(screen.getByTestId('network-button')).toBeInTheDocument();
+ });
+
+ it('passes handleClickExportGrades from hook to NetworkButton', () => {
+ const customHandler = jest.fn();
+ useBulkManagementControlsData.mockReturnValue({
+ show: true,
+ handleClickExportGrades: customHandler,
+ });
+
+ render();
+
+ const networkButtonCall = NetworkButton.mock.calls[0][0];
+ expect(networkButtonCall.onClick).toBe(customHandler);
});
});
});
diff --git a/src/components/GradesView/EditModal/HistoryHeader.test.jsx b/src/components/GradesView/EditModal/HistoryHeader.test.jsx
index 63b2865..034026c 100644
--- a/src/components/GradesView/EditModal/HistoryHeader.test.jsx
+++ b/src/components/GradesView/EditModal/HistoryHeader.test.jsx
@@ -1,17 +1,104 @@
import React from 'react';
-import { shallow } from '@edx/react-unit-test-utils';
+import { render, screen, initializeMocks } from 'testUtilsExtra';
import HistoryHeader from './HistoryHeader';
+jest.unmock('@openedx/paragon');
+jest.unmock('react');
+jest.unmock('@edx/frontend-platform/i18n');
+
+initializeMocks();
+
describe('HistoryHeader', () => {
- const props = {
- id: 'water',
- label: 'Brita',
- value: 'hydration',
+ const defaultProps = {
+ id: 'test-id',
+ label: 'Test Label',
+ value: 'Test Value',
};
- describe('Component', () => {
- test('snapshot', () => {
- expect(shallow().snapshot).toMatchSnapshot();
- });
+
+ it('renders header with label and value', () => {
+ render();
+
+ expect(screen.getByText('Test Label:')).toBeInTheDocument();
+ expect(screen.getByText('Test Value')).toBeInTheDocument();
+ });
+
+ it('renders header element with correct classes', () => {
+ render();
+
+ const headerElement = screen.getByText('Test Label:');
+ expect(headerElement).toHaveClass('grade-history-header');
+ expect(headerElement).toHaveClass('grade-history-test-id');
+ });
+
+ it('renders with string value', () => {
+ const props = {
+ ...defaultProps,
+ value: 'String Value',
+ };
+
+ render();
+ expect(screen.getByText('String Value')).toBeInTheDocument();
+ });
+
+ it('renders with number value', () => {
+ const props = {
+ ...defaultProps,
+ value: 85,
+ };
+
+ render();
+ expect(screen.getByText('85')).toBeInTheDocument();
+ });
+
+ it('renders with null value (default prop)', () => {
+ const props = {
+ id: 'test-id',
+ label: 'Test Label',
+ };
+
+ render();
+ expect(screen.getByText('Test Label:')).toBeInTheDocument();
+
+ const valueDiv = screen.getByText('Test Label:').nextSibling;
+ expect(valueDiv).toBeInTheDocument();
+ expect(valueDiv).toBeEmptyDOMElement();
+ });
+
+ it('renders with React node as label', () => {
+ const props = {
+ ...defaultProps,
+ label: Bold Label,
+ };
+
+ render();
+ const strongElement = screen.getByText('Bold Label');
+ expect(strongElement.tagName).toBe('STRONG');
+ });
+
+ it('generates correct class name based on id', () => {
+ const props = {
+ ...defaultProps,
+ id: 'assignment-name',
+ };
+
+ render();
+ const headerElement = screen.getByText('Test Label:');
+ expect(headerElement).toHaveClass('grade-history-assignment-name');
+ });
+
+ it('renders container structure correctly', () => {
+ render();
+
+ const headerElement = screen.getByText('Test Label:');
+ const valueElement = screen.getByText('Test Value');
+
+ expect(headerElement).toBeInTheDocument();
+ expect(valueElement).toBeInTheDocument();
+
+ expect(headerElement).toHaveClass(
+ 'grade-history-header',
+ 'grade-history-test-id',
+ );
});
});
diff --git a/src/components/GradesView/EditModal/__snapshots__/HistoryHeader.test.jsx.snap b/src/components/GradesView/EditModal/__snapshots__/HistoryHeader.test.jsx.snap
deleted file mode 100644
index f006b2c..0000000
--- a/src/components/GradesView/EditModal/__snapshots__/HistoryHeader.test.jsx.snap
+++ /dev/null
@@ -1,15 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`HistoryHeader Component snapshot 1`] = `
-
-
- Brita
- :
-
-
- hydration
-
-
-`;