diff --git a/src/library-authoring/common/FormGroup.jsx b/src/library-authoring/common/FormGroup.jsx index 43f0c55a..823ec428 100644 --- a/src/library-authoring/common/FormGroup.jsx +++ b/src/library-authoring/common/FormGroup.jsx @@ -29,13 +29,17 @@ const FormGroup = (props) => { {props.helpText && !props.errorMessage && ( - + {props.helpText} )} {props.errorMessage && ( - + {props.errorMessage} )} diff --git a/src/library-authoring/common/index.scss b/src/library-authoring/common/index.scss index c0d3deb8..45bfa5f8 100644 --- a/src/library-authoring/common/index.scss +++ b/src/library-authoring/common/index.scss @@ -3,3 +3,16 @@ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; } } +.library-create-wrapper { + .pgn__form-group { + .pgn__form-control-decorator-group { + margin: 0 + } + } + .pgn__form-text-invalid { + margin-left: -16px; + span.pgn__icon { + visibility: hidden; + } + } +} diff --git a/src/library-authoring/create-library/LibraryCreatePage.jsx b/src/library-authoring/create-library/LibraryCreatePage.jsx index 91682f19..02daf464 100644 --- a/src/library-authoring/create-library/LibraryCreatePage.jsx +++ b/src/library-authoring/create-library/LibraryCreatePage.jsx @@ -9,6 +9,7 @@ import { Alert, AlertModal, ActionRow, + Card, } from '@edx/paragon'; import { Info } from '@edx/paragon/icons'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; @@ -207,11 +208,10 @@ export class LibraryCreatePage extends React.Component { const { intl } = this.props; const { errors } = this.state; const slugRegex = new RegExp(VALID_SLUG_ID_REGEX); - switch (fieldName) { case 'org': if (!value) { - errors[fieldName] = intl.formatMessage(messages['library.form.field.error.empty']); + errors[fieldName] = intl.formatMessage(messages['library.form.field.error.empty.org']); } else if (value && !data.org) { errors[fieldName] = intl.formatMessage(messages['library.form.field.error.mismatch.org']); } else { @@ -220,7 +220,7 @@ export class LibraryCreatePage extends React.Component { break; case 'slug': if (!value) { - errors[fieldName] = intl.formatMessage(messages['library.form.field.error.empty']); + errors[fieldName] = intl.formatMessage(messages['library.form.field.error.empty.slug']); } else if (value && !value.match(slugRegex)) { errors[fieldName] = intl.formatMessage(messages['library.form.field.error.invalid.slug']); } else { @@ -229,7 +229,7 @@ export class LibraryCreatePage extends React.Component { break; default: if (!value) { - errors[fieldName] = intl.formatMessage(messages['library.form.field.error.empty']); + errors[fieldName] = intl.formatMessage(messages['library.form.field.error.empty.title']); } else { errors[fieldName] = ''; } @@ -256,7 +256,7 @@ export class LibraryCreatePage extends React.Component { {errorTitle && {errorTitle}}

{truncateMessage(errorDescription)}

@@ -271,87 +271,93 @@ export class LibraryCreatePage extends React.Component { ]} />
-
-

{intl.formatMessage(messages['library.form.create.library'])}

+
+

{intl.formatMessage(messages['library.form.create.library'])}

-
-
-
    -
  1. - -
  2. -
  3. - -
  4. -
  5. - + + +
    +
      +
    1. + +
    2. +
    3. + +
    4. +
    5. + +
    6. +
    +
    +
    + +
    + + , + }} + labels={{ + disabled: intl.formatMessage(commonMessages['library.common.forms.button.create']), + enabled: intl.formatMessage(commonMessages['library.common.forms.button.create']), + pending: intl.formatMessage(commonMessages['library.common.forms.button.creating']), + }} /> -
  6. -
-
-
- - , - }} - labels={{ - disabled: intl.formatMessage(commonMessages['library.common.forms.button.create']), - enabled: intl.formatMessage(commonMessages['library.common.forms.button.create']), - pending: intl.formatMessage(commonMessages['library.common.forms.button.creating']), - }} - /> -
-
+
+ + + diff --git a/src/library-authoring/create-library/data/slice.js b/src/library-authoring/create-library/data/slice.js index 8f205278..6252edc1 100644 --- a/src/library-authoring/create-library/data/slice.js +++ b/src/library-authoring/create-library/data/slice.js @@ -3,6 +3,7 @@ import { createSlice } from '@reduxjs/toolkit'; import { SUBMISSION_STATUS } from '../../common'; import { STORE_NAMES } from '../../common/data'; +import messages from '../messages'; export const libraryCreateInitialState = { createdLibrary: null, @@ -32,7 +33,11 @@ const slice = createSlice({ }, libraryCreateFailed: (state, { payload }) => { state.errorMessage = payload.errorMessage; - state.errorFields = payload.errorFields; + const errorFields = {}; + Object.keys(payload.errorFields).forEach(field => { + errorFields[field] = messages[`library.form.field.error.empty.${field}`].defaultMessage; + }); + state.errorFields = errorFields; state.status = SUBMISSION_STATUS.FAILED; }, libraryCreateClearError: (state) => { diff --git a/src/library-authoring/create-library/messages.js b/src/library-authoring/create-library/messages.js index 288831f8..ca268a80 100644 --- a/src/library-authoring/create-library/messages.js +++ b/src/library-authoring/create-library/messages.js @@ -17,6 +17,21 @@ const messages = defineMessages({ defaultMessage: 'This field may not be blank.', description: 'Text to display when field is empty.', }, + 'library.form.field.error.empty.title': { + id: 'library.form.field.error.empty.title', + defaultMessage: 'Enter a library name', + description: 'Text to display when library name is empty.', + }, + 'library.form.field.error.empty.org': { + id: 'library.form.field.error.empty.org', + defaultMessage: 'Select an organization', + description: 'Text to display when organization is empty.', + }, + 'library.form.field.error.empty.slug': { + id: 'library.form.field.error.empty.slug', + defaultMessage: 'Enter a library ID', + description: 'Text to display when library ID is empty.', + }, 'library.form.field.error.mismatch.org': { id: 'library.form.field.error.mismatch.org', defaultMessage: 'The organization must be selected from the options list.', @@ -111,7 +126,7 @@ const messages = defineMessages({ }, 'library.organizations.list.empty': { id: 'library.organizations.list.empty', - defaultMessage: 'No options', + defaultMessage: 'No results found', description: 'Text for empty organizations options list.', }, }); diff --git a/src/library-authoring/create-library/specs/LibraryCreatePage.spec.jsx b/src/library-authoring/create-library/specs/LibraryCreatePage.spec.jsx index b4e464dd..57cc4959 100644 --- a/src/library-authoring/create-library/specs/LibraryCreatePage.spec.jsx +++ b/src/library-authoring/create-library/specs/LibraryCreatePage.spec.jsx @@ -6,6 +6,7 @@ import { libraryCreateInitialState } from '../data'; import { SUBMISSION_STATUS, ROUTES } from '../../common'; import { ctxMount } from '../../common/specs/helpers'; import { withNavigate } from '../../utils/hoc'; +import messages from '../messages'; const InjectedLibraryCreatePage = injectIntl(withNavigate(LibraryCreatePage)); const config = { STUDIO_BASE_URL: 'STUDIO_BASE_URL' }; @@ -65,21 +66,44 @@ describe('create-library/LibraryCreatePage.jsx', () => { expect(mockCreateLibrary).toHaveBeenCalled(); }); - it('shows form errors', () => { - const newProps = { ...props, errorFields: { slug: 'Error message' } }; - const container = ctxMount( - - - , - { config }, - ); - container.find('input').at(0).simulate('change'); - container.find('input').at(1).simulate('change', { target: { value: 'org2', name: 'org' } }); - container.find('input').at(1).simulate('blur'); - container.find('input').at(2).simulate('change', { target: { value: '###', name: 'slug' } }); - expect(container.find('.pgn__form-text-invalid').at(0).text()).toEqual('This field may not be blank.'); - expect(container.find('.pgn__form-text-invalid').at(1).text()).toEqual('The organization must be selected from the options list.'); - expect(container.find('.pgn__form-text-invalid').at(2).text()).toEqual('Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or hyphens.'); + describe('form errors', () => { + let container; + beforeEach(() => { + const newProps = { ...props, errorFields: { slug: 'Error message' } }; + container = ctxMount( + + + , + { config }, + ); + }); + + it('shows empty title error', () => { + container.find('input').at(0).simulate('change'); + expect(container.find('.pgn__form-text-invalid').at(0).text()).toEqual(messages['library.form.field.error.empty.title'].defaultMessage); + }); + + it('shows empty org error', () => { + container.find('input').at(1).simulate('change'); + container.find('input').at(1).simulate('blur'); + expect(container.find('.pgn__form-text-invalid').at(0).text()).toEqual(messages['library.form.field.error.empty.org'].defaultMessage); + }); + + it('shows empty slug error', () => { + container.find('input').at(2).simulate('change'); + expect(container.find('.pgn__form-text-invalid').at(0).text()).toEqual(messages['library.form.field.error.empty.slug'].defaultMessage); + }); + + it('shows mismatch org error', () => { + container.find('input').at(1).simulate('change', { target: { value: 'org2', name: 'org' } }); + container.find('input').at(1).simulate('blur'); + expect(container.find('.pgn__form-text-invalid').at(0).text()).toEqual(messages['library.form.field.error.mismatch.org'].defaultMessage); + }); + + it('shows invlaid slug error', () => { + container.find('input').at(2).simulate('change', { target: { value: '###', name: 'slug' } }); + expect(container.find('.pgn__form-text-invalid').at(0).text()).toEqual(messages['library.form.field.error.invalid.slug'].defaultMessage); + }); }); it('shows processing text on button', () => {