diff --git a/src/profile/ProfilePage.jsx b/src/profile/ProfilePage.jsx index f0a140114..2dfe72dda 100644 --- a/src/profile/ProfilePage.jsx +++ b/src/profile/ProfilePage.jsx @@ -183,12 +183,19 @@ class ProfilePage extends React.Component { visibilityBio, requiresParentalConsent, isLoadingProfile, + username, + saveState, + navigate, } = this.props; if (isLoadingProfile) { return ; } + if (!username && saveState === 'error' && navigate) { + navigate('/notfound'); + } + const commonFormProps = { openHandler: this.handleOpen, closeHandler: this.handleClose, @@ -330,6 +337,7 @@ ProfilePage.propTypes = { // Account data requiresParentalConsent: PropTypes.bool, dateJoined: PropTypes.string, + username: PropTypes.string, // Bio form data bio: PropTypes.string, @@ -395,6 +403,7 @@ ProfilePage.propTypes = { openForm: PropTypes.func.isRequired, closeForm: PropTypes.func.isRequired, updateDraft: PropTypes.func.isRequired, + navigate: PropTypes.func.isRequired, // Router params: PropTypes.shape({ @@ -407,6 +416,7 @@ ProfilePage.propTypes = { ProfilePage.defaultProps = { saveState: null, + username: '', savePhotoState: null, photoUploadError: {}, profileImage: {}, diff --git a/src/profile/ProfilePage.test.jsx b/src/profile/ProfilePage.test.jsx index 997a92e86..0b7c6be6e 100644 --- a/src/profile/ProfilePage.test.jsx +++ b/src/profile/ProfilePage.test.jsx @@ -9,6 +9,7 @@ import PropTypes from 'prop-types'; import { Provider } from 'react-redux'; import configureMockStore from 'redux-mock-store'; import thunk from 'redux-thunk'; +import { BrowserRouter, useNavigate } from 'react-router-dom'; import messages from '../i18n'; import ProfilePage from './ProfilePage'; @@ -16,6 +17,7 @@ import ProfilePage from './ProfilePage'; const mockStore = configureMockStore([thunk]); const storeMocks = { loadingApp: require('./__mocks__/loadingApp.mockStore'), + invalidUser: require('./__mocks__/invalidUser.mockStore'), viewOwnProfile: require('./__mocks__/viewOwnProfile.mockStore'), viewOtherProfile: require('./__mocks__/viewOtherProfile.mockStore'), savingEditedBio: require('./__mocks__/savingEditedBio.mockStore'), @@ -65,6 +67,23 @@ beforeEach(() => { analytics.sendTrackingLogEvent.mockReset(); }); +const ProfileWrapper = ({ params, requiresParentalConsent }) => { + const navigate = useNavigate(); + return ( + + ); +}; + +ProfileWrapper.propTypes = { + params: PropTypes.shape({}).isRequired, + requiresParentalConsent: PropTypes.bool.isRequired, +}; + const ProfilePageWrapper = ({ contextValue, store, params, requiresParentalConsent, }) => ( @@ -73,7 +92,12 @@ const ProfilePageWrapper = ({ > - + + + @@ -103,6 +127,16 @@ describe('', () => { expect(tree).toMatchSnapshot(); }); + it('successfully redirected to not found page.', () => { + const contextValue = { + authenticatedUser: { userId: 123, username: 'staff', administrator: true }, + config: getConfig(), + }; + const component = ; + const { container: tree } = render(component); + expect(tree).toMatchSnapshot(); + }); + it('viewing own profile', () => { const contextValue = { authenticatedUser: { userId: 123, username: 'staff', administrator: true }, diff --git a/src/profile/__mocks__/invalidUser.mockStore.js b/src/profile/__mocks__/invalidUser.mockStore.js new file mode 100644 index 000000000..5e45bb2ac --- /dev/null +++ b/src/profile/__mocks__/invalidUser.mockStore.js @@ -0,0 +1,41 @@ +module.exports = { + userAccount: { + loading: false, + error: null, + username: 'staff', + email: null, + bio: null, + name: null, + country: null, + socialLinks: null, + profileImage: { + imageUrlMedium: null, + imageUrlLarge: null + }, + levelOfEducation: null, + learningGoal: null + }, + profilePage: { + errors: {}, + saveState: 'error', + savePhotoState: null, + currentlyEditingField: null, + account: { + username: '', + socialLinks: [] + }, + preferences: {}, + courseCertificates: [], + drafts: {}, + isLoadingProfile: false, + isAuthenticatedUserProfile: true, + }, + router: { + location: { + pathname: '/u/staffTest', + search: '', + hash: '' + }, + action: 'POP' + } +}; \ No newline at end of file diff --git a/src/profile/__snapshots__/ProfilePage.test.jsx.snap b/src/profile/__snapshots__/ProfilePage.test.jsx.snap index bb9c11d0d..7635041d8 100644 --- a/src/profile/__snapshots__/ProfilePage.test.jsx.snap +++ b/src/profile/__snapshots__/ProfilePage.test.jsx.snap @@ -29,6 +29,640 @@ exports[` Renders correctly in various states app loading 1`] = ` `; +exports[` Renders correctly in various states successfully redirected to not found page. 1`] = ` +
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+ +

+ staff +

+
+ + + +
+ Your profile information is only visible to you. Only your username is visible to others on localhost. +
+
+
+
+
+ +
+
+
+
+
+ +

+ staff +

+
+ + + +
+ Your profile information is only visible to you. Only your username is visible to others on localhost. +
+
+
+
+
+ +
+
+
+

+ Full Name +

+
+
+ +
+ + This is the name that appears in your account and on your certificates. + +
+
+
+
+
+

+ Location +

+
+
+ +
+
+
+
+
+
+

+ Primary Language Spoken +

+
+
+ +
+
+
+
+
+
+

+ Education +

+
+
+ +
+
+
+
+
+
+

+ Social Links +

+
+
    +
  • +
    + +
    +
  • +
  • +
    + +
    +
  • +
  • +
    + +
    +
  • +
+
+
+
+
+
+
+
+

+ About Me +

+
+
+ +
+
+
+
+
+
+

+ My Certificates + +

+

+ + + + Everyone on localhost + +

+
+ You don't have any certificates yet. +
+
+
+
+
+
+
+`; + exports[` Renders correctly in various states test country edit with error 1`] = `
{ return { ...state, saveState: 'error', + isLoadingProfile: false, errors: { ...state.errors, ...action.payload.errors }, }; case SAVE_PROFILE.RESET: return { ...state, saveState: null, + isLoadingProfile: false, errors: {}, }; diff --git a/src/profile/data/sagas.js b/src/profile/data/sagas.js index cfece88bb..7c9e409f8 100644 --- a/src/profile/data/sagas.js +++ b/src/profile/data/sagas.js @@ -1,4 +1,3 @@ -import { history } from '@edx/frontend-platform'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import pick from 'lodash.pick'; import { @@ -95,7 +94,11 @@ export function* handleFetchProfile(action) { yield put(fetchProfileReset()); } catch (e) { if (e.response.status === 404) { - history.push('/notfound'); + if (e.processedData && e.processedData.fieldErrors) { + yield put(saveProfileFailure(e.processedData.fieldErrors)); + } else { + yield put(saveProfileFailure(e.customAttributes)); + } } else { throw e; } diff --git a/src/routes/AppRoutes.jsx b/src/routes/AppRoutes.jsx index 1d46786b5..9fa2e22f2 100644 --- a/src/routes/AppRoutes.jsx +++ b/src/routes/AppRoutes.jsx @@ -3,15 +3,19 @@ import { AuthenticatedPageRoute, PageWrap, } from '@edx/frontend-platform/react'; -import { Routes, Route } from 'react-router-dom'; +import { Routes, Route, useNavigate } from 'react-router-dom'; import { ProfilePage, NotFoundPage } from '../profile'; -const AppRoutes = () => ( - - } /> - } /> - } /> - -); +const AppRoutes = () => { + const navigate = useNavigate(); + + return ( + + } /> + } /> + } /> + + ); +}; export default AppRoutes;