diff --git a/src/accessibility-page/AccessibilityBody/AccessibilityBody.test.jsx b/src/accessibility-page/AccessibilityBody/AccessibilityBody.test.jsx deleted file mode 100644 index bcb5f3c49..000000000 --- a/src/accessibility-page/AccessibilityBody/AccessibilityBody.test.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import { - render, - screen, -} from '@testing-library/react'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { initializeMockApp } from '@edx/frontend-platform'; -import initializeStore from '../../store'; - -import AccessibilityBody from './index'; - -let store; - -const renderComponent = () => { - render( - - - - - , - ); -}; - -describe('', () => { - describe('renders', () => { - beforeEach(async () => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: false, - roles: [], - }, - }); - store = initializeStore({}); - }); - it('contains links', () => { - renderComponent(); - expect(screen.getAllByTestId('email-element')).toHaveLength(2); - expect(screen.getAllByTestId('accessibility-page-link')).toHaveLength(1); - }); - }); -}); diff --git a/src/accessibility-page/AccessibilityBody/AccessibilityBody.test.tsx b/src/accessibility-page/AccessibilityBody/AccessibilityBody.test.tsx new file mode 100644 index 000000000..106713c25 --- /dev/null +++ b/src/accessibility-page/AccessibilityBody/AccessibilityBody.test.tsx @@ -0,0 +1,29 @@ +import { + initializeMocks, + render, + screen, +} from '@src/testUtils'; + +import AccessibilityBody from './index'; + +const renderComponent = () => { + render( + , + ); +}; + +describe('', () => { + describe('renders', () => { + beforeEach(async () => { + initializeMocks(); + }); + it('contains links', () => { + renderComponent(); + expect(screen.getAllByTestId('email-element')).toHaveLength(2); + expect(screen.getAllByTestId('accessibility-page-link')).toHaveLength(1); + }); + }); +}); diff --git a/src/accessibility-page/AccessibilityBody/AccessibilityBody.jsx b/src/accessibility-page/AccessibilityBody/AccessibilityBody.tsx similarity index 92% rename from src/accessibility-page/AccessibilityBody/AccessibilityBody.jsx rename to src/accessibility-page/AccessibilityBody/AccessibilityBody.tsx index faedcd36e..4991a8b38 100644 --- a/src/accessibility-page/AccessibilityBody/AccessibilityBody.jsx +++ b/src/accessibility-page/AccessibilityBody/AccessibilityBody.tsx @@ -1,5 +1,3 @@ -import React from 'react'; -import PropTypes from 'prop-types'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; import { Hyperlink, MailtoLink, Stack } from '@openedx/paragon'; @@ -8,6 +6,9 @@ import messages from './messages'; const AccessibilityBody = ({ communityAccessibilityLink, email, +}: { + communityAccessibilityLink: string, + email: string, }) => (
@@ -90,9 +91,4 @@ const AccessibilityBody = ({
); -AccessibilityBody.propTypes = { - communityAccessibilityLink: PropTypes.string.isRequired, - email: PropTypes.string.isRequired, -}; - export default AccessibilityBody; diff --git a/src/accessibility-page/AccessibilityBody/index.js b/src/accessibility-page/AccessibilityBody/index.ts similarity index 100% rename from src/accessibility-page/AccessibilityBody/index.js rename to src/accessibility-page/AccessibilityBody/index.ts diff --git a/src/accessibility-page/AccessibilityBody/messages.js b/src/accessibility-page/AccessibilityBody/messages.ts similarity index 100% rename from src/accessibility-page/AccessibilityBody/messages.js rename to src/accessibility-page/AccessibilityBody/messages.ts diff --git a/src/accessibility-page/AccessibilityForm/AccessibilityForm.test.jsx b/src/accessibility-page/AccessibilityForm/AccessibilityForm.test.tsx similarity index 75% rename from src/accessibility-page/AccessibilityForm/AccessibilityForm.test.jsx rename to src/accessibility-page/AccessibilityForm/AccessibilityForm.test.tsx index 968e2e51d..e20efedae 100644 --- a/src/accessibility-page/AccessibilityForm/AccessibilityForm.test.jsx +++ b/src/accessibility-page/AccessibilityForm/AccessibilityForm.test.tsx @@ -1,56 +1,31 @@ import { + initializeMocks, render, screen, -} from '@testing-library/react'; +} from '@src/testUtils'; import userEvent from '@testing-library/user-event'; -import { initializeMockApp } from '@edx/frontend-platform'; -import MockAdapter from 'axios-mock-adapter'; -import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import { AppProvider } from '@edx/frontend-platform/react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; - -import initializeStore from '../../store'; -import { RequestStatus } from '../../data/constants'; import AccessibilityForm from './index'; import { getZendeskrUrl } from '../data/api'; import messages from './messages'; let axiosMock; -let store; const defaultProps = { accessibilityEmail: 'accessibilityTest@test.com', }; -const initialState = { - accessibilityPage: { - savingStatus: '', - }, -}; - const renderComponent = () => { render( - - - - - , + , ); }; describe('', () => { beforeEach(async () => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: false, - roles: [], - }, - }); - store = initializeStore(initialState); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); + const mocks = initializeMocks(); + + axiosMock = mocks.axiosMock; }); describe('renders', () => { @@ -86,14 +61,23 @@ describe('', () => { submitButton = screen.getByText(messages.accessibilityPolicyFormSubmitLabel.defaultMessage); }); + it('renders in progress state', async () => { + axiosMock.onPost(getZendeskrUrl()).reply( + () => new Promise(() => { + // always in pending + }), + ); + + await user.click(submitButton); + + expect(screen.getByRole('button', { name: /submitting/i })).toBeInTheDocument(); + }); + it('shows correct success message', async () => { axiosMock.onPost(getZendeskrUrl()).reply(200); await user.click(submitButton); - const { savingStatus } = store.getState().accessibilityPage; - expect(savingStatus).toEqual(RequestStatus.SUCCESSFUL); - expect(screen.getAllByRole('alert')).toHaveLength(1); expect(screen.getByText(messages.accessibilityPolicyFormSuccess.defaultMessage)).toBeVisible(); @@ -108,9 +92,6 @@ describe('', () => { await user.click(submitButton); - const { savingStatus } = store.getState().accessibilityPage; - expect(savingStatus).toEqual(RequestStatus.FAILED); - expect(screen.getAllByRole('alert')).toHaveLength(1); expect(screen.getByTestId('rate-limit-alert')).toBeVisible(); diff --git a/src/accessibility-page/AccessibilityForm/AccessibilityForm.jsx b/src/accessibility-page/AccessibilityForm/AccessibilityForm.tsx similarity index 84% rename from src/accessibility-page/AccessibilityForm/AccessibilityForm.jsx rename to src/accessibility-page/AccessibilityForm/AccessibilityForm.tsx index a009590c1..f12502d7b 100644 --- a/src/accessibility-page/AccessibilityForm/AccessibilityForm.jsx +++ b/src/accessibility-page/AccessibilityForm/AccessibilityForm.tsx @@ -1,5 +1,3 @@ -import React from 'react'; -import PropTypes from 'prop-types'; import { FormattedMessage, FormattedDate, FormattedTime, useIntl, } from '@edx/frontend-platform/i18n'; @@ -7,26 +5,22 @@ import { ActionRow, Alert, Form, Stack, StatefulButton, } from '@openedx/paragon'; -import { RequestStatus } from '../../data/constants'; -import { STATEFUL_BUTTON_STATES } from '../../constants'; -import submitAccessibilityForm from '../data/thunks'; +import { STATEFUL_BUTTON_STATES } from '@src/constants'; import useAccessibility from './hooks'; import messages from './messages'; -const AccessibilityForm = ({ - accessibilityEmail, -}) => { +const AccessibilityForm = ({ accessibilityEmail }: { accessibilityEmail: string }) => { const intl = useIntl(); const { errors, values, isFormFilled, - dispatch, + mutation, handleBlur, handleChange, hasErrorField, savingStatus, - } = useAccessibility({ name: '', email: '', message: '' }, intl); + } = useAccessibility({ name: '', email: '', message: '' }); const formFields = [ { @@ -55,7 +49,7 @@ const AccessibilityForm = ({ }; const handleSubmit = () => { - dispatch(submitAccessibilityForm(values)); + mutation.mutateAsync(values).catch(() => {}); }; const start = new Date('Mon Jan 29 2018 13:00:00 GMT (UTC)'); @@ -66,7 +60,7 @@ const AccessibilityForm = ({

- {savingStatus === RequestStatus.SUCCESSFUL && ( + {savingStatus === 'success' && (
@@ -86,7 +80,7 @@ const AccessibilityForm = ({ )} - {savingStatus === RequestStatus.FAILED && ( + {savingStatus === 'error' && (
{ - const dispatch = useDispatch(); - const savingStatus = useSelector(state => state.accessibilityPage.savingStatus); +const useAccessibility = (initialValues: AccessibilityFormData) => { + const intl = useIntl(); const [isFormFilled, setFormFilled] = useState(false); const validationSchema = Yup.object().shape({ name: Yup.string().required( @@ -29,29 +29,27 @@ const useAccessibility = (initialValues, intl) => { enableReinitialize: true, validateOnBlur: false, validationSchema, + /* istanbul ignore next */ + onSubmit: () => {}, }); + const mutation = useSubmitAccessibilityForm(handleReset); + useEffect(() => { setFormFilled(Object.values(values).every((i) => i)); }, [values]); - useEffect(() => { - if (savingStatus === RequestStatus.SUCCESSFUL) { - handleReset(); - } - }, [savingStatus]); - const hasErrorField = (fieldName) => !!errors[fieldName] && !!touched[fieldName]; return { errors, values, isFormFilled, - dispatch, + mutation, handleBlur, handleChange, hasErrorField, - savingStatus, + savingStatus: mutation.status, }; }; diff --git a/src/accessibility-page/AccessibilityForm/index.js b/src/accessibility-page/AccessibilityForm/index.ts similarity index 100% rename from src/accessibility-page/AccessibilityForm/index.js rename to src/accessibility-page/AccessibilityForm/index.ts diff --git a/src/accessibility-page/AccessibilityForm/messages.js b/src/accessibility-page/AccessibilityForm/messages.ts similarity index 100% rename from src/accessibility-page/AccessibilityForm/messages.js rename to src/accessibility-page/AccessibilityForm/messages.ts diff --git a/src/accessibility-page/AccessibilityPage.test.jsx b/src/accessibility-page/AccessibilityPage.test.tsx similarity index 97% rename from src/accessibility-page/AccessibilityPage.test.jsx rename to src/accessibility-page/AccessibilityPage.test.tsx index 185b5ce5e..f24116f5d 100644 --- a/src/accessibility-page/AccessibilityPage.test.jsx +++ b/src/accessibility-page/AccessibilityPage.test.tsx @@ -1,4 +1,3 @@ -// @ts-check import { initializeMocks, render, screen } from '../testUtils'; import AccessibilityPage from './index'; diff --git a/src/accessibility-page/AccessibilityPage.jsx b/src/accessibility-page/AccessibilityPage.tsx similarity index 95% rename from src/accessibility-page/AccessibilityPage.jsx rename to src/accessibility-page/AccessibilityPage.tsx index d2bba2a02..48c4dd855 100644 --- a/src/accessibility-page/AccessibilityPage.jsx +++ b/src/accessibility-page/AccessibilityPage.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { useIntl } from '@edx/frontend-platform/i18n'; import { getExternalLinkUrl } from '@edx/frontend-platform'; import { Helmet } from 'react-helmet'; @@ -38,6 +37,4 @@ const AccessibilityPage = () => { ); }; -AccessibilityPage.propTypes = {}; - export default AccessibilityPage; diff --git a/src/accessibility-page/data/api.js b/src/accessibility-page/data/api.ts similarity index 76% rename from src/accessibility-page/data/api.js rename to src/accessibility-page/data/api.ts index 7381384b0..e8ff8ef0c 100644 --- a/src/accessibility-page/data/api.js +++ b/src/accessibility-page/data/api.ts @@ -8,12 +8,20 @@ ensureConfig([ export const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL; export const getZendeskrUrl = () => `${getApiBaseUrl()}/zendesk_proxy/v0`; +export interface AccessibilityFormData { + name: string; + email: string; + message: string; +} + /** * Posts the form data to zendesk endpoint - * @param {string} courseId - * @returns {Promise<[{}]>} */ -export async function postAccessibilityForm({ name, email, message }) { +export async function postAccessibilityForm({ + name, + email, + message, +}: AccessibilityFormData) { const data = { name, tags: ['studio_a11y'], diff --git a/src/accessibility-page/data/apiHooks.ts b/src/accessibility-page/data/apiHooks.ts new file mode 100644 index 000000000..5ebfef819 --- /dev/null +++ b/src/accessibility-page/data/apiHooks.ts @@ -0,0 +1,12 @@ +import { useMutation } from '@tanstack/react-query'; +import { AccessibilityFormData, postAccessibilityForm } from './api'; + +/** + * Mutation to submit accessibility form + */ +export const useSubmitAccessibilityForm = (handleSuccess: (e: any) => void) => ( + useMutation({ + mutationFn: (formData: AccessibilityFormData) => postAccessibilityForm(formData), + onSuccess: handleSuccess, + }) +); diff --git a/src/accessibility-page/data/slice.js b/src/accessibility-page/data/slice.js deleted file mode 100644 index 7d90356f1..000000000 --- a/src/accessibility-page/data/slice.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable no-param-reassign */ -import { createSlice } from '@reduxjs/toolkit'; - -const slice = createSlice({ - name: 'accessibilityPage', - initialState: { - savingStatus: '', - }, - reducers: { - updateSavingStatus: (state, { payload }) => { - state.savingStatus = payload.status; - }, - }, -}); - -export const { - updateLoadingStatus, - updateSavingStatus, -} = slice.actions; - -export const { - reducer, -} = slice; diff --git a/src/accessibility-page/data/thunks.js b/src/accessibility-page/data/thunks.js deleted file mode 100644 index aa628433d..000000000 --- a/src/accessibility-page/data/thunks.js +++ /dev/null @@ -1,24 +0,0 @@ -import { RequestStatus } from '../../data/constants'; -import { postAccessibilityForm } from './api'; -import { updateSavingStatus } from './slice'; - -function submitAccessibilityForm({ email, name, message }) { - return async (dispatch) => { - dispatch(updateSavingStatus({ status: RequestStatus.IN_PROGRESS })); - - try { - await postAccessibilityForm({ email, name, message }); - dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL })); - } catch (error) { - /* istanbul ignore else */ - if (error.response && error.response.status === 429) { - dispatch(updateSavingStatus({ status: RequestStatus.FAILED })); - } else { - /* istanbul ignore next */ - dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL })); - } - } - }; -} - -export default submitAccessibilityForm; diff --git a/src/accessibility-page/index.js b/src/accessibility-page/index.ts similarity index 100% rename from src/accessibility-page/index.js rename to src/accessibility-page/index.ts diff --git a/src/accessibility-page/messages.js b/src/accessibility-page/messages.ts similarity index 100% rename from src/accessibility-page/messages.js rename to src/accessibility-page/messages.ts diff --git a/src/store.ts b/src/store.ts index 6325b719e..930c602cf 100644 --- a/src/store.ts +++ b/src/store.ts @@ -23,7 +23,6 @@ import { reducer as videosReducer } from './files-and-videos/videos-page/data/sl import { reducer as courseOutlineReducer } from './course-outline/data/slice'; import { reducer as courseUnitReducer } from './course-unit/data/slice'; import { reducer as courseChecklistReducer } from './course-checklist/data/slice'; -import { reducer as accessibilityPageReducer } from './accessibility-page/data/slice'; import { reducer as textbooksReducer } from './textbooks/data/slice'; import { reducer as certificatesReducer } from './certificates/data/slice'; import { reducer as groupConfigurationsReducer } from './group-configurations/data/slice'; @@ -55,7 +54,6 @@ export interface DeprecatedReduxState { courseOutline: Record; courseUnit: Record; courseChecklist: Record; - accessibilityPage: Record; certificates: Record; groupConfigurations: InferState; textbooks: Record; @@ -84,7 +82,6 @@ export default function initializeStore(preloadedState: Partial