refactor: remove remaining injectIntl(), ban it using eslint (#2585)

This finished the removal of `injectIntl` from this codebase, and configures a new eslint rule to ban it completely.
This commit is contained in:
Braden MacDonald
2025-10-30 18:10:52 -07:00
committed by GitHub
parent 2dc087f87a
commit 36c9eba66d
18 changed files with 109 additions and 126 deletions

View File

@@ -14,6 +14,15 @@ module.exports = createConfig(
'no-restricted-exports': 'off', 'no-restricted-exports': 'off',
// There is no reason to disallow this syntax anymore; we don't use regenerator-runtime in new browsers // There is no reason to disallow this syntax anymore; we don't use regenerator-runtime in new browsers
'no-restricted-syntax': 'off', 'no-restricted-syntax': 'off',
'no-restricted-imports': ['error', {
patterns: [
{
group: ['@edx/frontend-platform/i18n'],
importNames: ['injectIntl'],
message: "Use 'useIntl' hook instead of injectIntl.",
},
],
}],
}, },
settings: { settings: {
// Import URLs should be resolved using aliases // Import URLs should be resolved using aliases

View File

@@ -30,9 +30,9 @@ We're trying to move away from some deprecated patterns in this codebase. Please
check if your PR meets these recommendations before asking for a review: check if your PR meets these recommendations before asking for a review:
- [ ] Any _new_ files are using TypeScript (`.ts`, `.tsx`). - [ ] Any _new_ files are using TypeScript (`.ts`, `.tsx`).
- [ ] Deprecated `propTypes`, `defaultProps`, and `injectIntl` patterns are not used in any new or modified code. - [ ] Avoid `propTypes` and `defaultProps` in any new or modified code.
- [ ] Tests should use the helpers in `src/testUtils.tsx` (specifically `initializeMocks`) - [ ] Tests should use the helpers in `src/testUtils.tsx` (specifically `initializeMocks`)
- [ ] Do not add new fields to the Redux state/store. Use React Context to share state among multiple components. - [ ] Do not add new fields to the Redux state/store. Use React Context to share state among multiple components.
- [ ] Use React Query to load data from REST APIs. See any `apiHooks.ts` in this repo for examples. - [ ] Use React Query to load data from REST APIs. See any `apiHooks.ts` in this repo for examples.
- [ ] All new i18n messages in `messages.ts` files have a `description` for translators to use. - [ ] All new i18n messages in `messages.ts` files have a `description` for translators to use.
- [ ] Imports avoid using `../`. To import from parent folders, use `@src`, e.g. `import { initializeMocks } from '@src/testUtils';` instead of `from '../../../../testUtils'` - [ ] Avoid using `../` in import paths. To import from parent folders, use `@src`, e.g. `import { initializeMocks } from '@src/testUtils';` instead of `from '../../../../testUtils'`

View File

@@ -6,7 +6,7 @@ import MockAdapter from 'axios-mock-adapter';
import { initializeMockApp, mergeConfig } from '@edx/frontend-platform'; import { initializeMockApp, mergeConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react'; import { AppProvider } from '@edx/frontend-platform/react';
import StudioApiService from 'CourseAuthoring/data/services/StudioApiService'; import StudioApiService from 'CourseAuthoring/data/services/StudioApiService';
@@ -20,7 +20,6 @@ const defaultProps = {
courseId, courseId,
onClose: () => {}, onClose: () => {},
}; };
const IntlProctoredExamSettings = injectIntl(ProctoredExamSettings);
let store; let store;
const intlWrapper = children => ( const intlWrapper = children => (
@@ -102,7 +101,7 @@ describe('ProctoredExamSettings', () => {
describe('Field dependencies', () => { describe('Field dependencies', () => {
beforeEach(async () => { beforeEach(async () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
}); });
it('Updates Zendesk ticket field if proctortrack is provider', async () => { it('Updates Zendesk ticket field if proctortrack is provider', async () => {
@@ -152,7 +151,7 @@ describe('ProctoredExamSettings', () => {
course_start_date: '2070-01-01T00:00:00Z', course_start_date: '2070-01-01T00:00:00Z',
}); });
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
await waitFor(() => { await waitFor(() => {
screen.getByText('Proctored exams'); screen.getByText('Proctored exams');
}); });
@@ -225,7 +224,7 @@ describe('ProctoredExamSettings', () => {
StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
).reply(200, {}); ).reply(200, {});
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
}); });
proctoringProvidersRequiringEscalationEmail.forEach(provider => { proctoringProvidersRequiringEscalationEmail.forEach(provider => {
@@ -409,7 +408,7 @@ describe('ProctoredExamSettings', () => {
const isAdmin = false; const isAdmin = false;
setupApp(isAdmin); setupApp(isAdmin);
mockCourseData(mockGetPastCourseData); mockCourseData(mockGetPastCourseData);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const providerOption = screen.getByTestId('proctortrack'); const providerOption = screen.getByTestId('proctortrack');
expect(providerOption.hasAttribute('disabled')).toEqual(true); expect(providerOption.hasAttribute('disabled')).toEqual(true);
}); });
@@ -418,7 +417,7 @@ describe('ProctoredExamSettings', () => {
const isAdmin = false; const isAdmin = false;
setupApp(isAdmin); setupApp(isAdmin);
mockCourseData(mockGetFutureCourseData); mockCourseData(mockGetFutureCourseData);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const providerOption = screen.getByTestId('proctortrack'); const providerOption = screen.getByTestId('proctortrack');
expect(providerOption.hasAttribute('disabled')).toEqual(false); expect(providerOption.hasAttribute('disabled')).toEqual(false);
}); });
@@ -428,7 +427,7 @@ describe('ProctoredExamSettings', () => {
const org = 'test-org'; const org = 'test-org';
setupApp(isAdmin, org); setupApp(isAdmin, org);
mockCourseData(mockGetFutureCourseData); mockCourseData(mockGetFutureCourseData);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const providerOption = screen.getByTestId('proctortrack'); const providerOption = screen.getByTestId('proctortrack');
expect(providerOption.hasAttribute('disabled')).toEqual(false); expect(providerOption.hasAttribute('disabled')).toEqual(false);
}); });
@@ -437,7 +436,7 @@ describe('ProctoredExamSettings', () => {
const isAdmin = true; const isAdmin = true;
setupApp(isAdmin); setupApp(isAdmin);
mockCourseData(mockGetPastCourseData); mockCourseData(mockGetPastCourseData);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const providerOption = screen.getByTestId('proctortrack'); const providerOption = screen.getByTestId('proctortrack');
expect(providerOption.hasAttribute('disabled')).toEqual(false); expect(providerOption.hasAttribute('disabled')).toEqual(false);
}); });
@@ -446,7 +445,7 @@ describe('ProctoredExamSettings', () => {
const isAdmin = true; const isAdmin = true;
setupApp(isAdmin); setupApp(isAdmin);
mockCourseData(mockGetFutureCourseData); mockCourseData(mockGetFutureCourseData);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const providerOption = screen.getByTestId('proctortrack'); const providerOption = screen.getByTestId('proctortrack');
expect(providerOption.hasAttribute('disabled')).toEqual(false); expect(providerOption.hasAttribute('disabled')).toEqual(false);
}); });
@@ -457,7 +456,7 @@ describe('ProctoredExamSettings', () => {
available_proctoring_providers: ['lti_external', 'proctortrack', 'mockproc'], available_proctoring_providers: ['lti_external', 'proctortrack', 'mockproc'],
}; };
mockCourseData(courseData); mockCourseData(courseData);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
await waitFor(() => { await waitFor(() => {
screen.getByDisplayValue('mockproc'); screen.getByDisplayValue('mockproc');
}); });
@@ -470,7 +469,7 @@ describe('ProctoredExamSettings', () => {
available_proctoring_providers: ['lti_external', 'proctortrack', 'mockproc'], available_proctoring_providers: ['lti_external', 'proctortrack', 'mockproc'],
}; };
mockCourseData(courseData); mockCourseData(courseData);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
await waitFor(() => { await waitFor(() => {
screen.getByDisplayValue('mockproc'); screen.getByDisplayValue('mockproc');
}); });
@@ -483,7 +482,7 @@ describe('ProctoredExamSettings', () => {
const isAdmin = true; const isAdmin = true;
setupApp(isAdmin); setupApp(isAdmin);
mockCourseData(mockGetFutureCourseData); mockCourseData(mockGetFutureCourseData);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
await waitFor(() => { await waitFor(() => {
screen.getByDisplayValue('mockproc'); screen.getByDisplayValue('mockproc');
}); });
@@ -497,7 +496,7 @@ describe('ProctoredExamSettings', () => {
EXAMS_BASE_URL: null, EXAMS_BASE_URL: null,
}, 'CourseAuthoringConfig'); }, 'CourseAuthoringConfig');
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
await waitFor(() => { await waitFor(() => {
screen.getByDisplayValue('mockproc'); screen.getByDisplayValue('mockproc');
}); });
@@ -516,7 +515,7 @@ describe('ProctoredExamSettings', () => {
).reply(200, { ).reply(200, {
provider: 'test_lti', provider: 'test_lti',
}); });
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
await waitFor(() => { await waitFor(() => {
screen.getByText('Proctoring provider'); screen.getByText('Proctoring provider');
}); });
@@ -529,14 +528,14 @@ describe('ProctoredExamSettings', () => {
describe('Toggles field visibility based on user permissions', () => { describe('Toggles field visibility based on user permissions', () => {
it('Hides opting out and zendesk tickets for non edX staff', async () => { it('Hides opting out and zendesk tickets for non edX staff', async () => {
setupApp(false); setupApp(false);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
expect(screen.queryByTestId('allowOptingOutYes')).toBeNull(); expect(screen.queryByTestId('allowOptingOutYes')).toBeNull();
expect(screen.queryByTestId('createZendeskTicketsYes')).toBeNull(); expect(screen.queryByTestId('createZendeskTicketsYes')).toBeNull();
}); });
it('Shows opting out and zendesk tickets for edX staff', async () => { it('Shows opting out and zendesk tickets for edX staff', async () => {
setupApp(true); setupApp(true);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
expect(screen.queryByTestId('allowOptingOutYes')).not.toBeNull(); expect(screen.queryByTestId('allowOptingOutYes')).not.toBeNull();
expect(screen.queryByTestId('createZendeskTicketsYes')).not.toBeNull(); expect(screen.queryByTestId('createZendeskTicketsYes')).not.toBeNull();
}); });
@@ -544,7 +543,7 @@ describe('ProctoredExamSettings', () => {
describe('Connection states', () => { describe('Connection states', () => {
it('Shows the spinner before the connection is complete', async () => { it('Shows the spinner before the connection is complete', async () => {
render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />)); render(intlWrapper(<ProctoredExamSettings {...defaultProps} />));
const spinner = await screen.findByRole('status'); const spinner = await screen.findByRole('status');
expect(spinner.textContent).toEqual('Loading...'); expect(spinner.textContent).toEqual('Loading...');
}); });
@@ -554,7 +553,7 @@ describe('ProctoredExamSettings', () => {
StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
).reply(500); ).reply(500);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const connectionError = screen.getByTestId('connectionErrorAlert'); const connectionError = screen.getByTestId('connectionErrorAlert');
expect(connectionError.textContent).toEqual( expect(connectionError.textContent).toEqual(
expect.stringContaining('We encountered a technical error when loading this page.'), expect.stringContaining('We encountered a technical error when loading this page.'),
@@ -566,7 +565,7 @@ describe('ProctoredExamSettings', () => {
`${ExamsApiService.getExamsBaseUrl()}/api/v1/providers`, `${ExamsApiService.getExamsBaseUrl()}/api/v1/providers`,
).reply(500); ).reply(500);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const connectionError = screen.getByTestId('connectionErrorAlert'); const connectionError = screen.getByTestId('connectionErrorAlert');
expect(connectionError.textContent).toEqual( expect(connectionError.textContent).toEqual(
expect.stringContaining('We encountered a technical error when loading this page.'), expect.stringContaining('We encountered a technical error when loading this page.'),
@@ -578,7 +577,7 @@ describe('ProctoredExamSettings', () => {
StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
).reply(403); ).reply(403);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const permissionError = screen.getByTestId('permissionDeniedAlert'); const permissionError = screen.getByTestId('permissionDeniedAlert');
expect(permissionError.textContent).toEqual( expect(permissionError.textContent).toEqual(
expect.stringContaining('You are not authorized to view this page'), expect.stringContaining('You are not authorized to view this page'),
@@ -597,7 +596,7 @@ describe('ProctoredExamSettings', () => {
}); });
it('Disable button while submitting', async () => { it('Disable button while submitting', async () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
let submitButton = screen.getByTestId('submissionButton'); let submitButton = screen.getByTestId('submissionButton');
expect(screen.queryByTestId('saveInProgress')).toBeFalsy(); expect(screen.queryByTestId('saveInProgress')).toBeFalsy();
fireEvent.click(submitButton); fireEvent.click(submitButton);
@@ -607,7 +606,7 @@ describe('ProctoredExamSettings', () => {
}); });
it('Makes API call successfully with proctoring_escalation_email if proctortrack', async () => { it('Makes API call successfully with proctoring_escalation_email if proctortrack', async () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
// Make a change to the provider to proctortrack and set the email // Make a change to the provider to proctortrack and set the email
const selectElement = screen.getByDisplayValue('mockproc'); const selectElement = screen.getByDisplayValue('mockproc');
fireEvent.change(selectElement, { target: { value: 'proctortrack' } }); fireEvent.change(selectElement, { target: { value: 'proctortrack' } });
@@ -638,7 +637,7 @@ describe('ProctoredExamSettings', () => {
}); });
it('Makes API call successfully without proctoring_escalation_email if not proctortrack', async () => { it('Makes API call successfully without proctoring_escalation_email if not proctortrack', async () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
// make sure we have not selected proctortrack as the proctoring provider // make sure we have not selected proctortrack as the proctoring provider
expect(screen.getByDisplayValue('mockproc')).toBeDefined(); expect(screen.getByDisplayValue('mockproc')).toBeDefined();
@@ -665,7 +664,7 @@ describe('ProctoredExamSettings', () => {
}); });
it('Successfully updates exam configuration and studio provider is set to "lti_external" for lti providers', async () => { it('Successfully updates exam configuration and studio provider is set to "lti_external" for lti providers', async () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
// Make a change to the provider to test_lti and set the email // Make a change to the provider to test_lti and set the email
const selectElement = screen.getByDisplayValue('mockproc'); const selectElement = screen.getByDisplayValue('mockproc');
fireEvent.change(selectElement, { target: { value: 'test_lti' } }); fireEvent.change(selectElement, { target: { value: 'test_lti' } });
@@ -706,7 +705,7 @@ describe('ProctoredExamSettings', () => {
}); });
it('Sets exam service provider to null if a non-lti provider is selected', async () => { it('Sets exam service provider to null if a non-lti provider is selected', async () => {
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const submitButton = screen.getByTestId('submissionButton'); const submitButton = screen.getByTestId('submissionButton');
fireEvent.click(submitButton); fireEvent.click(submitButton);
// update exam service config // update exam service config
@@ -750,7 +749,7 @@ describe('ProctoredExamSettings', () => {
course_start_date: '2070-01-01T00:00:00Z', course_start_date: '2070-01-01T00:00:00Z',
}); });
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const submitButton = screen.getByTestId('submissionButton'); const submitButton = screen.getByTestId('submissionButton');
fireEvent.click(submitButton); fireEvent.click(submitButton);
// does not update exam service config // does not update exam service config
@@ -780,7 +779,7 @@ describe('ProctoredExamSettings', () => {
StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
).reply(500); ).reply(500);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const submitButton = screen.getByTestId('submissionButton'); const submitButton = screen.getByTestId('submissionButton');
fireEvent.click(submitButton); fireEvent.click(submitButton);
expect(axiosMock.history.post.length).toBe(1); expect(axiosMock.history.post.length).toBe(1);
@@ -798,7 +797,7 @@ describe('ProctoredExamSettings', () => {
`${ExamsApiService.getExamsBaseUrl()}/api/v1/configs/course_id/${defaultProps.courseId}`, `${ExamsApiService.getExamsBaseUrl()}/api/v1/configs/course_id/${defaultProps.courseId}`,
).reply(500, 'error'); ).reply(500, 'error');
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const submitButton = screen.getByTestId('submissionButton'); const submitButton = screen.getByTestId('submissionButton');
fireEvent.click(submitButton); fireEvent.click(submitButton);
expect(axiosMock.history.post.length).toBe(1); expect(axiosMock.history.post.length).toBe(1);
@@ -816,7 +815,7 @@ describe('ProctoredExamSettings', () => {
`${ExamsApiService.getExamsBaseUrl()}/api/v1/configs/course_id/${defaultProps.courseId}`, `${ExamsApiService.getExamsBaseUrl()}/api/v1/configs/course_id/${defaultProps.courseId}`,
).reply(403, 'error'); ).reply(403, 'error');
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const submitButton = screen.getByTestId('submissionButton'); const submitButton = screen.getByTestId('submissionButton');
fireEvent.click(submitButton); fireEvent.click(submitButton);
expect(axiosMock.history.post.length).toBe(1); expect(axiosMock.history.post.length).toBe(1);
@@ -835,7 +834,7 @@ describe('ProctoredExamSettings', () => {
StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId), StudioApiService.getProctoredExamSettingsUrl(defaultProps.courseId),
).reply(500); ).reply(500);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
const submitButton = screen.getByTestId('submissionButton'); const submitButton = screen.getByTestId('submissionButton');
fireEvent.click(submitButton); fireEvent.click(submitButton);
expect(axiosMock.history.post.length).toBe(1); expect(axiosMock.history.post.length).toBe(1);
@@ -868,7 +867,7 @@ describe('ProctoredExamSettings', () => {
const isAdmin = false; const isAdmin = false;
setupApp(isAdmin); setupApp(isAdmin);
await act(async () => render(intlWrapper(<IntlProctoredExamSettings {...defaultProps} />))); await act(async () => render(intlWrapper(<ProctoredExamSettings {...defaultProps} />)));
// Make a change to the proctoring provider // Make a change to the proctoring provider
const selectElement = screen.getByDisplayValue('mockproc'); const selectElement = screen.getByDisplayValue('mockproc');
fireEvent.change(selectElement, { target: { value: 'proctortrack' } }); fireEvent.change(selectElement, { target: { value: 'proctortrack' } });

View File

@@ -1,5 +1,5 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectIntl, FormattedMessage, FormattedNumber } from '@edx/frontend-platform/i18n'; import { FormattedMessage, FormattedNumber } from '@edx/frontend-platform/i18n';
import { Icon } from '@openedx/paragon'; import { Icon } from '@openedx/paragon';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { ModeComment } from '@openedx/paragon/icons'; import { ModeComment } from '@openedx/paragon/icons';
@@ -127,4 +127,4 @@ ChecklistItemComment.propTypes = {
]).isRequired, ]).isRequired,
}; };
export default injectIntl(ChecklistItemComment); export default ChecklistItemComment;

View File

@@ -1,3 +1,4 @@
/* istanbul ignore file */
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
/* eslint-disable import/extensions */ /* eslint-disable import/extensions */
@@ -13,7 +14,7 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Spinner } from '@openedx/paragon'; import { Spinner } from '@openedx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { useIntl } from '@edx/frontend-platform/i18n';
import EditorContainer from '../EditorContainer'; import EditorContainer from '../EditorContainer';
// This 'module' self-import hack enables mocking during tests. // This 'module' self-import hack enables mocking during tests.
@@ -30,7 +31,7 @@ export const hooks = {
}), }),
}; };
export const thumbEditor = ({ export const ThumbEditor = ({
onClose, onClose,
// redux // redux
blockValue, blockValue,
@@ -38,44 +39,46 @@ export const thumbEditor = ({
blockFailed, blockFailed,
blockFinished, blockFinished,
initializeEditor, initializeEditor,
// eslint-disable-next-line react/prop-types
exampleValue, exampleValue,
// inject }) => {
intl, const intl = useIntl();
}) => ( return (
<EditorContainer <EditorContainer
getContent={module.hooks.getContent} getContent={module.hooks.getContent}
onClose={onClose} onClose={onClose}
> >
<div> <div>
{exampleValue} {exampleValue}
</div> </div>
<div className="editor-body h-75 overflow-auto"> <div className="editor-body h-75 overflow-auto">
{!blockFinished {!blockFinished
? ( ? (
<div className="text-center p-6"> <div className="text-center p-6">
<Spinner <Spinner
animation="border" animation="border"
className="m-3" className="m-3"
// Use a messages.js file for intl messages. // Use a messages.js file for intl messages.
screenreadertext={intl.formatMessage('Loading Spinner')} screenreadertext={intl.formatMessage('Loading Spinner')}
/> />
</div> </div>
) )
: ( : (
<p> <p>
Your Editor Goes here. Your Editor Goes here.
You can get at the xblock data with the blockValue field. You can get at the xblock data with the blockValue field.
here is what is in your xblock: {JSON.stringify(blockValue)} here is what is in your xblock: {JSON.stringify(blockValue)}
</p> </p>
)} )}
</div> </div>
</EditorContainer> </EditorContainer>
); );
thumbEditor.defaultProps = { };
ThumbEditor.defaultProps = {
blockValue: null, blockValue: null,
lmsEndpointUrl: null, lmsEndpointUrl: null,
}; };
thumbEditor.propTypes = { ThumbEditor.propTypes = {
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
// redux // redux
blockValue: PropTypes.shape({ blockValue: PropTypes.shape({
@@ -85,8 +88,6 @@ thumbEditor.propTypes = {
blockFailed: PropTypes.bool.isRequired, blockFailed: PropTypes.bool.isRequired,
blockFinished: PropTypes.bool.isRequired, blockFinished: PropTypes.bool.isRequired,
initializeEditor: PropTypes.func.isRequired, initializeEditor: PropTypes.func.isRequired,
// inject
intl: intlShape.isRequired,
}; };
export const mapStateToProps = (state) => ({ export const mapStateToProps = (state) => ({
@@ -103,4 +104,4 @@ export const mapDispatchToProps = {
// TODO fill with dispatches here if needed // TODO fill with dispatches here if needed
}; };
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(thumbEditor)); export default connect(mapStateToProps, mapDispatchToProps)(ThumbEditor);

View File

@@ -13,7 +13,7 @@ import HintsCard from './settingsComponents/HintsCard';
import ResetCard from './settingsComponents/ResetCard'; import ResetCard from './settingsComponents/ResetCard';
import TimerCard from './settingsComponents/TimerCard'; import TimerCard from './settingsComponents/TimerCard';
import TypeCard from './settingsComponents/TypeCard'; import TypeCard from './settingsComponents/TypeCard';
import ToleranceCard from './settingsComponents/Tolerance'; import { ToleranceCard } from './settingsComponents/Tolerance';
import GroupFeedbackCard from './settingsComponents/GroupFeedback/index'; import GroupFeedbackCard from './settingsComponents/GroupFeedback/index';
import SwitchEditorCard from './settingsComponents/SwitchEditorCard'; import SwitchEditorCard from './settingsComponents/SwitchEditorCard';
import messages from './messages'; import messages from './messages';

View File

@@ -1,5 +1,5 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n'; import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { Alert, Form } from '@openedx/paragon'; import { Alert, Form } from '@openedx/paragon';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import SettingsOption from '../../SettingsOption'; import SettingsOption from '../../SettingsOption';
@@ -46,14 +46,13 @@ export const getSummary = ({ tolerance, intl }) => {
} }
}; };
const ToleranceCard = ({ export const ToleranceCard = ({
tolerance, tolerance,
answers, answers,
updateSettings, updateSettings,
correctAnswerCount, correctAnswerCount,
// inject
intl,
}) => { }) => {
const intl = useIntl();
const isAnswerRange = isAnswerRangeSet({ answers }); const isAnswerRange = isAnswerRangeSet({ answers });
const hasMultipleCorrectAnswers = correctAnswerCount > 1; const hasMultipleCorrectAnswers = correctAnswerCount > 1;
let summary = getSummary({ tolerance, intl }); let summary = getSummary({ tolerance, intl });
@@ -141,8 +140,4 @@ ToleranceCard.propTypes = {
unselectedFeedback: PropTypes.string, unselectedFeedback: PropTypes.string,
})).isRequired, })).isRequired,
updateSettings: PropTypes.func.isRequired, updateSettings: PropTypes.func.isRequired,
intl: intlShape.isRequired,
}; };
export const ToleranceCardInternal = ToleranceCard; // For testing only
export default injectIntl(ToleranceCard);

View File

@@ -4,7 +4,7 @@ import {
import React from 'react'; import React from 'react';
import messages from './messages'; import messages from './messages';
import { ToleranceTypes } from './constants'; import { ToleranceTypes } from './constants';
import { ToleranceCardInternal as ToleranceCard } from './index'; import { ToleranceCard } from './index';
import { formatMessage } from '../../../../../../../testUtils'; import { formatMessage } from '../../../../../../../testUtils';
jest.mock('@edx/frontend-platform/i18n', () => ({ jest.mock('@edx/frontend-platform/i18n', () => ({
@@ -13,6 +13,9 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
FormattedMessage: jest.fn(({ defaultMessage }) => ( FormattedMessage: jest.fn(({ defaultMessage }) => (
<div>{ defaultMessage }</div> <div>{ defaultMessage }</div>
)), )),
useIntl: () => ({
formatMessage: (message) => message.defaultMessage,
}),
})); }));
// eslint-disable-next-line react/prop-types // eslint-disable-next-line react/prop-types

View File

@@ -3,8 +3,7 @@ import { connect, useDispatch } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
FormattedMessage, FormattedMessage,
injectIntl, useIntl,
intlShape,
} from '@edx/frontend-platform/i18n'; } from '@edx/frontend-platform/i18n';
import { import {
Image, Image,
@@ -32,14 +31,13 @@ import { ErrorContext } from '../../../../hooks';
* Collapsible Form widget controlling video thumbnail * Collapsible Form widget controlling video thumbnail
*/ */
const ThumbnailWidget = ({ const ThumbnailWidget = ({
// injected
intl,
// redux // redux
isLibrary, isLibrary,
allowThumbnailUpload, allowThumbnailUpload,
thumbnail, thumbnail,
videoId, videoId,
}) => { }) => {
const intl = useIntl();
const dispatch = useDispatch(); const dispatch = useDispatch();
const [error] = React.useContext(ErrorContext).thumbnail; const [error] = React.useContext(ErrorContext).thumbnail;
const imgRef = React.useRef(); const imgRef = React.useRef();
@@ -126,8 +124,6 @@ const ThumbnailWidget = ({
}; };
ThumbnailWidget.propTypes = { ThumbnailWidget.propTypes = {
// injected
intl: intlShape.isRequired,
// redux // redux
isLibrary: PropTypes.bool.isRequired, isLibrary: PropTypes.bool.isRequired,
allowThumbnailUpload: PropTypes.bool.isRequired, allowThumbnailUpload: PropTypes.bool.isRequired,
@@ -144,4 +140,4 @@ export const mapStateToProps = (state) => ({
export const mapDispatchToProps = {}; export const mapDispatchToProps = {};
export const ThumbnailWidgetInternal = ThumbnailWidget; // For testing only export const ThumbnailWidgetInternal = ThumbnailWidget; // For testing only
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ThumbnailWidget)); export default connect(mapStateToProps, mapDispatchToProps)(ThumbnailWidget);

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect, useDispatch } from 'react-redux'; import { connect, useDispatch } from 'react-redux';
import { FormattedMessage, injectIntl } from '@edx/frontend-platform/i18n'; import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Dropdown, Icon, IconButton } from '@openedx/paragon'; import { Dropdown, Icon, IconButton } from '@openedx/paragon';
import { MoreHoriz } from '@openedx/paragon/icons'; import { MoreHoriz } from '@openedx/paragon/icons';
@@ -90,4 +90,4 @@ export const mapDispatchToProps = {
}; };
export const TranscriptActionMenuInternal = TranscriptActionMenu; // For testing only export const TranscriptActionMenuInternal = TranscriptActionMenu; // For testing only
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(TranscriptActionMenu)); export default connect(mapStateToProps, mapDispatchToProps)(TranscriptActionMenu);

View File

@@ -15,13 +15,9 @@ import 'jest-canvas-mock';
jest.mock('@edx/frontend-platform/i18n', () => { jest.mock('@edx/frontend-platform/i18n', () => {
const i18n = jest.requireActual('@edx/frontend-platform/i18n'); const i18n = jest.requireActual('@edx/frontend-platform/i18n');
const PropTypes = jest.requireActual('prop-types');
return { return {
...i18n, ...i18n,
useIntl: () => ({ formatMessage: mockFormatMessage }), useIntl: () => ({ formatMessage: mockFormatMessage }),
intlShape: PropTypes.shape({
formatMessage: PropTypes.func,
}),
defineMessages: m => m, defineMessages: m => m,
getLocale: () => 'getLocale', getLocale: () => 'getLocale',
FormattedDate: () => 'FormattedDate', FormattedDate: () => 'FormattedDate',

View File

@@ -8,7 +8,7 @@ import {
useToggle, useToggle,
} from '@openedx/paragon'; } from '@openedx/paragon';
import { DeleteOutline } from '@openedx/paragon/icons'; import { DeleteOutline } from '@openedx/paragon/icons';
import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n'; import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import LanguageSelect from './LanguageSelect'; import LanguageSelect from './LanguageSelect';
import TranscriptMenu from './TranscriptMenu'; import TranscriptMenu from './TranscriptMenu';
@@ -20,9 +20,8 @@ const Transcript = ({
transcript, transcript,
previousSelection, previousSelection,
handleTranscript, handleTranscript,
// injected
intl,
}) => { }) => {
const intl = useIntl();
const [isConfirmationOpen, openConfirmation, closeConfirmation] = useToggle(); const [isConfirmationOpen, openConfirmation, closeConfirmation] = useToggle();
const [newLanguage, setNewLanguage] = useState(transcript); const [newLanguage, setNewLanguage] = useState(transcript);
const language = transcript; const language = transcript;
@@ -122,8 +121,6 @@ Transcript.propTypes = {
transcript: PropTypes.string.isRequired, transcript: PropTypes.string.isRequired,
previousSelection: PropTypes.arrayOf(PropTypes.string).isRequired, previousSelection: PropTypes.arrayOf(PropTypes.string).isRequired,
handleTranscript: PropTypes.func.isRequired, handleTranscript: PropTypes.func.isRequired,
// injected
intl: intlShape.isRequired,
}; };
export default injectIntl(Transcript); export default Transcript;

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n'; import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import { import {
Form, Form,
Icon, Icon,
@@ -18,9 +18,8 @@ const ThreePlayMediaForm = ({
data, data,
setData, setData,
transcriptionPlan, transcriptionPlan,
// injected
intl,
}) => { }) => {
const intl = useIntl();
if (hasTranscriptCredentials) { if (hasTranscriptCredentials) {
const selectedLanguages = data.preferredLanguages ? data.preferredLanguages : []; const selectedLanguages = data.preferredLanguages ? data.preferredLanguages : [];
const turnaroundOptions = transcriptionPlan.turnaround; const turnaroundOptions = transcriptionPlan.turnaround;
@@ -133,8 +132,6 @@ ThreePlayMediaForm.propTypes = {
translations: PropTypes.shape({}), translations: PropTypes.shape({}),
languages: PropTypes.shape({}), languages: PropTypes.shape({}),
}).isRequired, }).isRequired,
// injected
intl: intlShape.isRequired,
}; };
export default injectIntl(ThreePlayMediaForm); export default ThreePlayMediaForm;

View File

@@ -3,12 +3,6 @@
// (whichever comes first). // (whichever comes first).
declare module '@edx/frontend-platform/i18n' { declare module '@edx/frontend-platform/i18n' {
// eslint-disable-next-line import/no-extraneous-dependencies
import { injectIntl as _injectIntl } from 'react-intl';
/** @deprecated Use useIntl() hook instead. */
export const injectIntl: typeof _injectIntl;
/** @deprecated Use useIntl() hook instead. */
export const intlShape: any;
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
export { export {

View File

@@ -1,6 +1,6 @@
import { initializeMockApp } from '@edx/frontend-platform'; import { initializeMockApp } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { injectIntl, IntlProvider } from '@edx/frontend-platform/i18n'; import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react'; import { AppProvider } from '@edx/frontend-platform/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { import {
@@ -25,7 +25,7 @@ const RootWrapper = () => (
<AppProvider store={store}> <AppProvider store={store}>
<IntlProvider locale="en" messages={{}}> <IntlProvider locale="en" messages={{}}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<GradingSettings intl={injectIntl} courseId={courseId} /> <GradingSettings courseId={courseId} />
</QueryClientProvider> </QueryClientProvider>
</IntlProvider> </IntlProvider>
</AppProvider> </AppProvider>

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; import { IntlProvider } from '@edx/frontend-platform/i18n';
import { initializeMockApp } from '@edx/frontend-platform'; import { initializeMockApp } from '@edx/frontend-platform';
import { render, waitFor, fireEvent } from '@testing-library/react'; import { render, waitFor, fireEvent } from '@testing-library/react';
@@ -19,7 +19,6 @@ const sortedGrades = [
const RootWrapper = () => ( const RootWrapper = () => (
<IntlProvider locale="en" messages={{}}> <IntlProvider locale="en" messages={{}}>
<GradingScale <GradingScale
intl={injectIntl}
gradeCutoffs={gradeCutoffs} gradeCutoffs={gradeCutoffs}
gradeLetters={gradeLetters} gradeLetters={gradeLetters}
sortedGrades={sortedGrades} sortedGrades={sortedGrades}
@@ -103,7 +102,6 @@ describe('<GradingScale />', () => {
const { getAllByTestId } = render( const { getAllByTestId } = render(
<IntlProvider locale="en" messages={{}}> <IntlProvider locale="en" messages={{}}>
<GradingScale <GradingScale
intl={injectIntl}
gradeCutoffs={shortGradeCutoffs} gradeCutoffs={shortGradeCutoffs}
gradeLetters={['A']} gradeLetters={['A']}
sortedGrades={shortSortedGrades} sortedGrades={shortSortedGrades}
@@ -128,7 +126,6 @@ describe('<GradingScale />', () => {
const { getAllByTestId } = render( const { getAllByTestId } = render(
<IntlProvider locale="en" messages={{}}> <IntlProvider locale="en" messages={{}}>
<GradingScale <GradingScale
intl={injectIntl}
gradeCutoffs={gradeCutoffs} gradeCutoffs={gradeCutoffs}
gradeLetters={gradeLetters} gradeLetters={gradeLetters}
sortedGrades={sortedGrades} sortedGrades={sortedGrades}

View File

@@ -1,4 +1,3 @@
import { injectIntl } from '@edx/frontend-platform/i18n';
import { import {
initializeMocks, render, screen, initializeMocks, render, screen,
} from '../../testUtils'; } from '../../testUtils';
@@ -11,7 +10,7 @@ describe('<GradingSidebar />', () => {
}); });
it('renders sidebar text content correctly', async () => { it('renders sidebar text content correctly', async () => {
render(<GradingSidebar intl={injectIntl} courseId="123" />); render(<GradingSidebar courseId="123" />);
expect(await screen.findByText(messages.gradingSidebarTitle.defaultMessage)).toBeInTheDocument(); expect(await screen.findByText(messages.gradingSidebarTitle.defaultMessage)).toBeInTheDocument();
expect(screen.getByText(messages.gradingSidebarAbout1.defaultMessage)).toBeInTheDocument(); expect(screen.getByText(messages.gradingSidebarAbout1.defaultMessage)).toBeInTheDocument();
expect(screen.getByText(messages.gradingSidebarAbout2.defaultMessage)).toBeInTheDocument(); expect(screen.getByText(messages.gradingSidebarAbout2.defaultMessage)).toBeInTheDocument();

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { initializeMockApp } from '@edx/frontend-platform'; import { initializeMockApp } from '@edx/frontend-platform';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react'; import { AppProvider } from '@edx/frontend-platform/react';
import { fireEvent, render, waitFor } from '@testing-library/react'; import { fireEvent, render, waitFor } from '@testing-library/react';
@@ -14,7 +14,7 @@ const courseId = '123';
const RootWrapper = () => ( const RootWrapper = () => (
<AppProvider store={store}> <AppProvider store={store}>
<IntlProvider locale="en" messages={{}}> <IntlProvider locale="en" messages={{}}>
<FileSection intl={injectIntl} courseId={courseId} /> <FileSection courseId={courseId} />
</IntlProvider> </IntlProvider>
</AppProvider> </AppProvider>
); );