Feat alert users with feedback instead of disabling next/save buttons (#68)
* feat: implemented user feedback ErrorAlerts
This commit is contained in:
@@ -11,7 +11,7 @@ export const hooks = {
|
||||
state: {
|
||||
isDismissed: (val) => React.useState(val),
|
||||
},
|
||||
dismissalHooks: ({ isError }) => {
|
||||
dismissalHooks: ({ dismissError, isError }) => {
|
||||
const [isDismissed, setIsDismissed] = hooks.state.isDismissed(false);
|
||||
React.useEffect(() => {
|
||||
setIsDismissed(isDismissed && !isError);
|
||||
@@ -19,16 +19,21 @@ export const hooks = {
|
||||
[isError]);
|
||||
return {
|
||||
isDismissed,
|
||||
dismissAlert: () => setIsDismissed(true),
|
||||
dismissAlert: () => {
|
||||
setIsDismissed(true);
|
||||
dismissError();
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const ErrorAlert = ({
|
||||
dismissError,
|
||||
hideHeading,
|
||||
isError,
|
||||
children,
|
||||
}) => {
|
||||
const { isDismissed, dismissAlert } = hooks.dismissalHooks({ isError });
|
||||
const { isDismissed, dismissAlert } = hooks.dismissalHooks({ dismissError, isError });
|
||||
if (!isError || isDismissed) {
|
||||
return null;
|
||||
}
|
||||
@@ -39,17 +44,25 @@ export const ErrorAlert = ({
|
||||
dismissible
|
||||
onClose={dismissAlert}
|
||||
>
|
||||
<Alert.Heading>
|
||||
<FormattedMessage
|
||||
{...messages.errorTitle}
|
||||
/>
|
||||
</Alert.Heading>
|
||||
{!hideHeading
|
||||
&& (
|
||||
<Alert.Heading>
|
||||
<FormattedMessage {...messages.errorTitle} />
|
||||
</Alert.Heading>
|
||||
)}
|
||||
{children}
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
ErrorAlert.defaultProps = {
|
||||
dismissError: null,
|
||||
hideHeading: false,
|
||||
};
|
||||
|
||||
ErrorAlert.propTypes = {
|
||||
dismissError: PropTypes.func,
|
||||
hideHeading: PropTypes.bool,
|
||||
isError: PropTypes.bool.isRequired,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
|
||||
@@ -28,15 +28,20 @@ describe('ErrorAlert component', () => {
|
||||
beforeEach(() => { state.mock(); });
|
||||
afterEach(() => { state.restore(); });
|
||||
describe('dismissalHooks', () => {
|
||||
const props = {
|
||||
dismissError: jest.fn(),
|
||||
isError: testValue,
|
||||
};
|
||||
beforeEach(() => {
|
||||
hook = module.hooks.dismissalHooks({ isError: testValue });
|
||||
hook = module.hooks.dismissalHooks(props);
|
||||
});
|
||||
it('returns isDismissed value, initialized to false', () => {
|
||||
expect(state.stateVals.isDismissed).toEqual(hook.isDismissed);
|
||||
});
|
||||
test('dismissAlert sets isDismissed to true', () => {
|
||||
test('dismissAlert sets isDismissed to true and calls dismissError', () => {
|
||||
hook.dismissAlert();
|
||||
expect(state.setState.isDismissed).toHaveBeenCalledWith(true);
|
||||
expect(props.dismissError).toHaveBeenCalled();
|
||||
});
|
||||
test('On Render, calls setIsDismissed', () => {
|
||||
expect(React.useEffect.mock.calls.length).toEqual(1);
|
||||
@@ -51,20 +56,27 @@ describe('ErrorAlert component', () => {
|
||||
describe('Component', () => {
|
||||
describe('Snapshots', () => {
|
||||
let props;
|
||||
const msg = <p> An Error Message </p>;
|
||||
beforeAll(() => {
|
||||
props = {
|
||||
isError: false,
|
||||
dismissError: jest.fn(),
|
||||
};
|
||||
jest.spyOn(module.hooks, 'dismissalHooks').mockImplementation((value) => ({ isError: value }));
|
||||
jest.spyOn(module.hooks, 'dismissalHooks').mockImplementation(() => ({
|
||||
isDismissed: false,
|
||||
dismissAlert: jest.fn().mockName('dismissAlert'),
|
||||
}));
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
test('snapshot: is Null when no error (ErrorAlert)', () => {
|
||||
expect(shallow(<ErrorAlert {...props}> <p> An Error Message </p></ErrorAlert>)).toMatchSnapshot();
|
||||
test('snapshot: is Null when no error (ErrorAlert)', () => {
|
||||
expect(shallow(<ErrorAlert {...props}> <p> An Error Message </p> </ErrorAlert>)).toMatchSnapshot();
|
||||
});
|
||||
test('snapshot: Loads children and component when error (ErrorAlert)', () => {
|
||||
expect(shallow(<ErrorAlert {...props} isError> <p> An Error Message </p> </ErrorAlert>)).toMatchSnapshot();
|
||||
expect(shallow(<ErrorAlert {...props} isError hideHeading={false}>{msg}</ErrorAlert>)).toMatchSnapshot();
|
||||
});
|
||||
test('snapshot: Does not load heading when hideHeading is true', () => {
|
||||
expect(shallow(<ErrorAlert {...props} isError hideHeading>{msg}</ErrorAlert>)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ErrorAlert component Component Snapshots snapshot: is Null when no error (ErrorAlert) 1`] = `""`;
|
||||
exports[`ErrorAlert component Component Snapshots snapshot: Does not load heading when hideHeading is true 1`] = `
|
||||
<Alert
|
||||
dismissible={true}
|
||||
onClose={[MockFunction dismissAlert]}
|
||||
variant="danger"
|
||||
>
|
||||
<p>
|
||||
An Error Message
|
||||
</p>
|
||||
</Alert>
|
||||
`;
|
||||
|
||||
exports[`ErrorAlert component Component Snapshots snapshot: Loads children and component when error (ErrorAlert) 1`] = `
|
||||
<Alert
|
||||
dismissible={true}
|
||||
onClose={[MockFunction dismissAlert]}
|
||||
variant="danger"
|
||||
>
|
||||
<Alert.Heading>
|
||||
@@ -14,10 +25,10 @@ exports[`ErrorAlert component Component Snapshots snapshot: Loads children and c
|
||||
id="authoring.texteditor.selectimagemodal.error.errorTitle"
|
||||
/>
|
||||
</Alert.Heading>
|
||||
|
||||
<p>
|
||||
An Error Message
|
||||
</p>
|
||||
|
||||
</Alert>
|
||||
`;
|
||||
|
||||
exports[`ErrorAlert component Component Snapshots snapshot: is Null when no error (ErrorAlert) 1`] = `""`;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
exports[`FetchErrorAlert Snapshots snapshot: is ErrorAlert with Message error (ErrorAlert) 1`] = `
|
||||
<ErrorAlert
|
||||
dismissError={null}
|
||||
hideHeading={false}
|
||||
isError={true}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
exports[`UploadErrorAlert Snapshots snapshot: is ErrorAlert with Message error (ErrorAlert) 1`] = `
|
||||
<ErrorAlert
|
||||
dismissError={null}
|
||||
hideHeading={false}
|
||||
isError={true}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
||||
@@ -9,6 +9,8 @@ import messages from './messages';
|
||||
|
||||
/**
|
||||
* Wrapper for alt-text input and isDecorative checkbox control
|
||||
* @param {obj} errorProps - props for error handling
|
||||
* {bool} isValid - are alt-text fields valid for saving?
|
||||
* @param {bool} isDecorative - is the image decorative?
|
||||
* @param {func} setIsDecorative - handle isDecorative change event
|
||||
* @param {func} setValue - update alt-text value
|
||||
@@ -18,6 +20,7 @@ export const AltTextControls = ({
|
||||
isDecorative,
|
||||
setIsDecorative,
|
||||
setValue,
|
||||
validation,
|
||||
value,
|
||||
// inject
|
||||
intl,
|
||||
@@ -28,15 +31,22 @@ export const AltTextControls = ({
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
className="mt-4.5"
|
||||
disabled={isDecorative}
|
||||
floatingLabel={intl.formatMessage(messages.altTextFloatingLabel)}
|
||||
isInvalid={validation.show}
|
||||
onChange={hooks.onInputChange(setValue)}
|
||||
type="input"
|
||||
value={value}
|
||||
disabled={isDecorative}
|
||||
onChange={hooks.onInputChange(setValue)}
|
||||
floatingLabel={intl.formatMessage(messages.altTextFloatingLabel)}
|
||||
/>
|
||||
{validation.show
|
||||
&& (
|
||||
<Form.Control.Feedback type="invalid">
|
||||
<FormattedMessage {...messages.altTextLocalFeedback} />
|
||||
</Form.Control.Feedback>
|
||||
)}
|
||||
<Form.Checkbox
|
||||
className="mt-4.5 decorative-control-label"
|
||||
checked={isDecorative}
|
||||
className="mt-4.5 decorative-control-label"
|
||||
onChange={hooks.onCheckboxChange(setIsDecorative)}
|
||||
>
|
||||
<Form.Label>
|
||||
@@ -46,10 +56,16 @@ export const AltTextControls = ({
|
||||
</Form.Group>
|
||||
);
|
||||
AltTextControls.propTypes = {
|
||||
error: PropTypes.shape({
|
||||
show: PropTypes.bool,
|
||||
}).isRequired,
|
||||
isDecorative: PropTypes.bool.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
setValue: PropTypes.func.isRequired,
|
||||
setIsDecorative: PropTypes.func.isRequired,
|
||||
validation: PropTypes.shape({
|
||||
show: PropTypes.bool,
|
||||
}).isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
// inject
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
@@ -19,9 +19,14 @@ describe('AltTextControls', () => {
|
||||
beforeEach(() => {
|
||||
props.setValue = jest.fn().mockName('props.setValue');
|
||||
props.setIsDecorative = jest.fn().mockName('props.setIsDecorative');
|
||||
props.validation = { show: true };
|
||||
});
|
||||
describe('render', () => {
|
||||
test('snapshot: isDecorative=true', () => {
|
||||
test('snapshot: isDecorative=true errorProps.showSubmissionError=true', () => {
|
||||
expect(shallow(<AltTextControls {...props} />)).toMatchSnapshot();
|
||||
});
|
||||
test('snapshot: isDecorative=true errorProps.showSubmissionError=false', () => {
|
||||
props.validation.show = false;
|
||||
expect(shallow(<AltTextControls {...props} />)).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AltTextControls render snapshot: isDecorative=true 1`] = `
|
||||
exports[`AltTextControls render snapshot: isDecorative=true errorProps.showSubmissionError=false 1`] = `
|
||||
<Form.Group
|
||||
className="mt-4.5"
|
||||
>
|
||||
@@ -17,6 +17,7 @@ exports[`AltTextControls render snapshot: isDecorative=true 1`] = `
|
||||
className="mt-4.5"
|
||||
disabled={true}
|
||||
floatingLabel="Alt Text"
|
||||
isInvalid={false}
|
||||
onChange={
|
||||
Object {
|
||||
"hooks.onInputChange": [MockFunction props.setValue],
|
||||
@@ -44,3 +45,58 @@ exports[`AltTextControls render snapshot: isDecorative=true 1`] = `
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
`;
|
||||
|
||||
exports[`AltTextControls render snapshot: isDecorative=true errorProps.showSubmissionError=true 1`] = `
|
||||
<Form.Group
|
||||
className="mt-4.5"
|
||||
>
|
||||
<Form.Label
|
||||
as="h4"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Accessibility"
|
||||
description="Label title for accessibility section."
|
||||
id="authoring.texteditor.imagesettingsmodal.accessibilityLabel"
|
||||
/>
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
className="mt-4.5"
|
||||
disabled={true}
|
||||
floatingLabel="Alt Text"
|
||||
isInvalid={true}
|
||||
onChange={
|
||||
Object {
|
||||
"hooks.onInputChange": [MockFunction props.setValue],
|
||||
}
|
||||
}
|
||||
type="input"
|
||||
value="props.value"
|
||||
/>
|
||||
<Form.Control.Feedback
|
||||
type="invalid"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Enter alt text"
|
||||
description="Message feedback for user below the alt text field."
|
||||
id="authoring.texteditor.imagesettingsmodal.error.altTextLocalFeedback"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
<Form.Checkbox
|
||||
checked={true}
|
||||
className="mt-4.5 decorative-control-label"
|
||||
onChange={
|
||||
Object {
|
||||
"hooks.onCheckboxChange": [MockFunction props.setIsDecorative],
|
||||
}
|
||||
}
|
||||
>
|
||||
<Form.Label>
|
||||
<FormattedMessage
|
||||
defaultMessage="This image is decorative (no alt text required)."
|
||||
description="Checkbox label for whether or not an image is decorative."
|
||||
id="authoring.texteditor.imagesettingsmodal.decorativeCheckboxLabel"
|
||||
/>
|
||||
</Form.Label>
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
`;
|
||||
|
||||
@@ -5,18 +5,17 @@ exports[`ImageSettingsModal render snapshot 1`] = `
|
||||
close={[MockFunction props.close]}
|
||||
confirmAction={
|
||||
<Button
|
||||
disabled={
|
||||
Object {
|
||||
"hooks.isSaveDisabled": Object {
|
||||
"isDecorative": false,
|
||||
"value": "alternative Taxes",
|
||||
},
|
||||
}
|
||||
}
|
||||
onClick={
|
||||
Object {
|
||||
"hooks.onSaveClick": Object {
|
||||
"altText": "alternative Taxes",
|
||||
"altText": Object {
|
||||
"error": Object {
|
||||
"dismiss": [MockFunction],
|
||||
"show": "sHoW",
|
||||
},
|
||||
"isDecorative": false,
|
||||
"value": "alternative Taxes",
|
||||
},
|
||||
"dimensions": Object {
|
||||
"height": 13,
|
||||
"width": 12,
|
||||
@@ -39,6 +38,17 @@ exports[`ImageSettingsModal render snapshot 1`] = `
|
||||
isOpen={false}
|
||||
title="Image Settings"
|
||||
>
|
||||
<ErrorAlert
|
||||
dismissError={[MockFunction]}
|
||||
hideHeading={true}
|
||||
isError="sHoW"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Enter alt text or specify that the image is decorative only."
|
||||
description="Message presented to user when user attempts to save unaccepted altText configuration."
|
||||
id="authoring.texteditor.imagesettingsmodal.error.altTextError"
|
||||
/>
|
||||
</ErrorAlert>
|
||||
<Button
|
||||
onClick={[MockFunction props.returnToSelector]}
|
||||
size="inline"
|
||||
@@ -108,6 +118,12 @@ exports[`ImageSettingsModal render snapshot 1`] = `
|
||||
}
|
||||
/>
|
||||
<AltTextControls
|
||||
error={
|
||||
Object {
|
||||
"dismiss": [MockFunction],
|
||||
"show": "sHoW",
|
||||
}
|
||||
}
|
||||
isDecorative={false}
|
||||
value="alternative Taxes"
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,8 @@ import * as module from './hooks';
|
||||
export const state = {
|
||||
altText: (val) => React.useState(val),
|
||||
dimensions: (val) => React.useState(val),
|
||||
showDismissibleError: (val) => React.useState(val),
|
||||
showSubmissionError: (val) => React.useState(val),
|
||||
isDecorative: (val) => React.useState(val),
|
||||
isLocked: (val) => React.useState(val),
|
||||
local: (val) => React.useState(val),
|
||||
@@ -119,6 +121,16 @@ export const dimensionLockHooks = () => {
|
||||
* {func} setWidth - set width
|
||||
* @param {string} - new width string
|
||||
* {func} updateDimensions - set dimensions based on state
|
||||
* {obj} errorProps - props for user feedback error
|
||||
* {bool} isError - true if dimensions are blank
|
||||
* {func} setError - sets isError to true
|
||||
* {func} dismissError - sets isError to false
|
||||
* {bool} isHeightValid - true if height field is ready to save
|
||||
* {func} setHeightValid - sets isHeightValid to true
|
||||
* {func} setHeightNotValid - sets isHeightValid to false
|
||||
* {bool} isWidthValid - true if width field is ready to save
|
||||
* {func} setWidthValid - sets isWidthValid to true
|
||||
* {func} setWidthNotValid - sets isWidthValid to false
|
||||
*/
|
||||
export const dimensionHooks = () => {
|
||||
const [dimensions, setDimensions] = module.state.dimensions(null);
|
||||
@@ -165,16 +177,50 @@ export const dimensionHooks = () => {
|
||||
* @param {string} - new alt text
|
||||
* {bool} isDecorative - is the image decorative?
|
||||
* {func} setIsDecorative - set isDecorative field
|
||||
* @param {bool} isDecorative
|
||||
* {obj} error - error at top of page
|
||||
* {bool} show - is error being displayed?
|
||||
* {func} set - set show to true
|
||||
* {func} dismiss - set show to false
|
||||
* {obj} validation - local alt text error
|
||||
* {bool} show - is validation error being displayed?
|
||||
* {func} set - set validation to true
|
||||
* {func} dismiss - set validation to false
|
||||
*/
|
||||
export const altTextHooks = (savedText) => {
|
||||
const [value, setValue] = module.state.altText(savedText || '');
|
||||
const [isDecorative, setIsDecorative] = module.state.isDecorative(false);
|
||||
const [showDismissibleError, setShowDismissibleError] = module.state.showDismissibleError(false);
|
||||
const [showSubmissionError, setShowSubmissionError] = module.state.showSubmissionError(false);
|
||||
|
||||
const validateAltText = (newVal, newDecorative) => {
|
||||
if (showSubmissionError) {
|
||||
if (newVal || newDecorative) {
|
||||
setShowSubmissionError(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
value,
|
||||
setValue,
|
||||
setValue: (val) => {
|
||||
setValue(val);
|
||||
validateAltText(val, null);
|
||||
},
|
||||
isDecorative,
|
||||
setIsDecorative,
|
||||
setIsDecorative: (decorative) => {
|
||||
setIsDecorative(decorative);
|
||||
validateAltText(null, decorative);
|
||||
},
|
||||
error: {
|
||||
show: showDismissibleError,
|
||||
set: () => setShowDismissibleError(true),
|
||||
dismiss: () => setShowDismissibleError(false),
|
||||
},
|
||||
validation: {
|
||||
show: showSubmissionError,
|
||||
set: () => setShowSubmissionError(true),
|
||||
dismiss: () => setShowSubmissionError(false),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -194,6 +240,25 @@ export const onInputChange = (handleValue) => (e) => handleValue(e.target.value)
|
||||
*/
|
||||
export const onCheckboxChange = (handleValue) => (e) => handleValue(e.target.checked);
|
||||
|
||||
/**
|
||||
* checkFormValidation({ altText, isDecorative, onAltTextFail })
|
||||
* Handle saving the image context to the text editor
|
||||
* @param {string} altText - image alt text
|
||||
* @param {bool} isDecorative - is the image decorative?
|
||||
* @param {func} onAltTextFail - called if alt text validation fails
|
||||
*/
|
||||
export const checkFormValidation = ({
|
||||
altText,
|
||||
isDecorative,
|
||||
onAltTextFail,
|
||||
}) => {
|
||||
if (!isDecorative && altText === '') {
|
||||
onAltTextFail();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* onSave({ altText, dimensions, isDecorative, saveToEditor })
|
||||
* Handle saving the image context to the text editor
|
||||
@@ -207,27 +272,30 @@ export const onSaveClick = ({
|
||||
dimensions,
|
||||
isDecorative,
|
||||
saveToEditor,
|
||||
}) => () => saveToEditor({
|
||||
altText,
|
||||
dimensions,
|
||||
isDecorative,
|
||||
});
|
||||
|
||||
/**
|
||||
* isSaveDisabled(altText)
|
||||
* Returns true the save button should be disabled (altText is missing and not decorative)
|
||||
* @param {object} altText - altText hook object
|
||||
* {bool} isDecorative - is the image decorative?
|
||||
* {string} value - alt text value
|
||||
* @return {bool} - should the save button be disabled?
|
||||
*/
|
||||
export const isSaveDisabled = (altText) => !altText.isDecorative && (altText.value === '');
|
||||
}) => () => {
|
||||
if (module.checkFormValidation({
|
||||
altText: altText.value,
|
||||
isDecorative,
|
||||
onAltTextFail: () => {
|
||||
altText.error.set();
|
||||
altText.validation.set();
|
||||
},
|
||||
})) {
|
||||
altText.error.dismiss();
|
||||
altText.validation.dismiss();
|
||||
saveToEditor({
|
||||
altText: altText.value,
|
||||
dimensions,
|
||||
isDecorative,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
altText: altTextHooks,
|
||||
dimensions: dimensionHooks,
|
||||
isSaveDisabled,
|
||||
onCheckboxChange,
|
||||
onInputChange,
|
||||
onSaveClick,
|
||||
checkFormValidation,
|
||||
};
|
||||
|
||||
@@ -34,6 +34,8 @@ describe('state values', () => {
|
||||
};
|
||||
test('provides altText state value', () => testStateMethod(state.keys.altText));
|
||||
test('provides dimensions state value', () => testStateMethod(state.keys.dimensions));
|
||||
test('provides showDismissibleError state value', () => testStateMethod(state.keys.showDismissibleError));
|
||||
test('provides showSubmissionError state value', () => testStateMethod(state.keys.showSubmissionError));
|
||||
test('provides isDecorative state value', () => testStateMethod(state.keys.isDecorative));
|
||||
test('provides isLocked state value', () => testStateMethod(state.keys.isLocked));
|
||||
test('provides local state value', () => testStateMethod(state.keys.local));
|
||||
@@ -42,6 +44,9 @@ describe('state values', () => {
|
||||
});
|
||||
|
||||
describe('ImageSettingsModal hooks', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('dimensions-related hooks', () => {
|
||||
describe('getValidDimensions', () => {
|
||||
it('returns local dimensions if not locked', () => {
|
||||
@@ -169,12 +174,12 @@ describe('ImageSettingsModal hooks', () => {
|
||||
state.restore();
|
||||
});
|
||||
it('initializes dimension lock hooks with incoming dimension value', () => {
|
||||
state.mockVal(state.keys.dimensions, reducedDims, state.setState.local);
|
||||
state.mockVal(state.keys.dimensions, reducedDims);
|
||||
hook = hooks.dimensionHooks();
|
||||
expect(hooks.dimensionLockHooks).toHaveBeenCalledWith({ dimensions: reducedDims });
|
||||
});
|
||||
test('value is tied to local state', () => {
|
||||
state.mockVal(state.keys.local, simpleDims, state.setState.local);
|
||||
state.mockVal(state.keys.local, simpleDims);
|
||||
hook = hooks.dimensionHooks();
|
||||
expect(hook.value).toEqual(simpleDims);
|
||||
});
|
||||
@@ -200,14 +205,14 @@ describe('ImageSettingsModal hooks', () => {
|
||||
});
|
||||
describe('setHeight', () => {
|
||||
it('sets local height to int value of argument', () => {
|
||||
state.mockVal(state.keys.local, simpleDims, state.setState.local);
|
||||
state.mockVal(state.keys.local, simpleDims);
|
||||
hooks.dimensionHooks().setHeight('23.4');
|
||||
expect(state.setState.local).toHaveBeenCalledWith({ ...simpleDims, height: 23 });
|
||||
});
|
||||
});
|
||||
describe('setWidth', () => {
|
||||
it('sets local width to int value of argument', () => {
|
||||
state.mockVal(state.keys.local, simpleDims, state.setState.local);
|
||||
state.mockVal(state.keys.local, simpleDims);
|
||||
hooks.dimensionHooks().setWidth('34.5');
|
||||
expect(state.setState.local).toHaveBeenCalledWith({ ...simpleDims, width: 34 });
|
||||
});
|
||||
@@ -237,17 +242,67 @@ describe('ImageSettingsModal hooks', () => {
|
||||
});
|
||||
});
|
||||
describe('altTextHooks', () => {
|
||||
it('returns value and isDecorative, along with associated setters', () => {
|
||||
const value = 'myVAL';
|
||||
const isDecorative = true;
|
||||
const showDismissibleError = 'dismiSSiBLE';
|
||||
const showSubmissionError = 'subMISsion';
|
||||
beforeEach(() => {
|
||||
state.mock();
|
||||
const value = 'myVAL';
|
||||
const isDecorative = 'IS WE Decorating?';
|
||||
state.mockVal(state.keys.altText, value, state.setState.altText);
|
||||
state.mockVal(state.keys.isDecorative, isDecorative, state.setState.isDecorative);
|
||||
hook = hooks.altTextHooks();
|
||||
});
|
||||
afterEach(() => {
|
||||
state.restore();
|
||||
});
|
||||
it('returns value and isDecorative', () => {
|
||||
state.mockVal(state.keys.altText, value);
|
||||
state.mockVal(state.keys.isDecorative, isDecorative);
|
||||
hook = hooks.altTextHooks();
|
||||
expect(hook.value).toEqual(value);
|
||||
expect(hook.setValue).toEqual(state.setState.altText);
|
||||
expect(hook.isDecorative).toEqual(isDecorative);
|
||||
expect(hook.setIsDecorative).toEqual(state.setState.isDecorative);
|
||||
});
|
||||
test('setValue sets value', () => {
|
||||
state.mockVal(state.keys.altText, value);
|
||||
hook = hooks.altTextHooks();
|
||||
hook.setValue(value);
|
||||
expect(state.setState.altText).toHaveBeenCalledWith(value);
|
||||
});
|
||||
test('setIsDecorative sets isDecorative', () => {
|
||||
state.mockVal(state.keys.altText, value);
|
||||
hook = hooks.altTextHooks();
|
||||
hook.setIsDecorative(value);
|
||||
expect(state.setState.isDecorative).toHaveBeenCalledWith(value);
|
||||
});
|
||||
describe('error', () => {
|
||||
test('show is initialized to false and returns properly', () => {
|
||||
expect(hook.error.show).toEqual(false);
|
||||
state.mockVal(state.keys.showDismissibleError, showDismissibleError);
|
||||
hook = hooks.altTextHooks();
|
||||
expect(hook.error.show).toEqual(showDismissibleError);
|
||||
});
|
||||
test('set sets showDismissibleError to true', () => {
|
||||
hook.error.set();
|
||||
expect(state.setState.showDismissibleError).toHaveBeenCalledWith(true);
|
||||
});
|
||||
test('dismiss sets showDismissibleError to false', () => {
|
||||
hook.error.dismiss();
|
||||
expect(state.setState.showDismissibleError).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
describe('validation', () => {
|
||||
test('show is initialized to false and returns properly', () => {
|
||||
expect(hook.validation.show).toEqual(false);
|
||||
state.mockVal(state.keys.showSubmissionError, showSubmissionError);
|
||||
hook = hooks.altTextHooks();
|
||||
expect(hook.validation.show).toEqual(showSubmissionError);
|
||||
});
|
||||
test('set sets showSubmissionError to true', () => {
|
||||
hook.validation.set();
|
||||
expect(state.setState.showSubmissionError).toHaveBeenCalledWith(true);
|
||||
});
|
||||
test('dismiss sets showSubmissionError to false', () => {
|
||||
hook.validation.dismiss();
|
||||
expect(state.setState.showSubmissionError).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('onInputChange', () => {
|
||||
@@ -266,31 +321,71 @@ describe('ImageSettingsModal hooks', () => {
|
||||
expect(onChange).toHaveBeenCalledWith(checked);
|
||||
});
|
||||
});
|
||||
describe('onSaveClick', () => {
|
||||
it('calls saveToEditor with dimensions, altText, and isDecorative', () => {
|
||||
const dimensions = simpleDims;
|
||||
const altText = 'What is this?';
|
||||
const isDecorative = 'probably';
|
||||
const saveToEditor = jest.fn();
|
||||
hooks.onSaveClick({
|
||||
altText,
|
||||
dimensions,
|
||||
isDecorative,
|
||||
saveToEditor,
|
||||
})();
|
||||
expect(saveToEditor).toHaveBeenCalledWith({
|
||||
altText,
|
||||
dimensions,
|
||||
isDecorative,
|
||||
});
|
||||
describe('checkFormValidation', () => {
|
||||
const props = {
|
||||
onAltTextFail: jest.fn().mockName('onAltTextFail'),
|
||||
};
|
||||
beforeEach(() => {
|
||||
props.altText = '';
|
||||
props.isDecorative = false;
|
||||
});
|
||||
it('calls onAltTextFail when isDecorative is false and altText is an empty string', () => {
|
||||
hooks.checkFormValidation({ ...props });
|
||||
expect(props.onAltTextFail).toHaveBeenCalled();
|
||||
});
|
||||
it('returns false when isDeocrative is false and altText is an empty string', () => {
|
||||
expect(hooks.checkFormValidation({ ...props })).toEqual(false);
|
||||
});
|
||||
it('returns true when isDecorative is true', () => {
|
||||
props.isDecorative = true;
|
||||
expect(hooks.checkFormValidation({ ...props })).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('isSaveDisabled', () => {
|
||||
it('returns true iff is not decorative and altText value is empty', () => {
|
||||
hook = hooks.isSaveDisabled;
|
||||
expect(hook({ isDecorative: false, value: '' })).toEqual(true);
|
||||
expect(hook({ isDecorative: false, value: 'test' })).toEqual(false);
|
||||
expect(hook({ isDecorative: true, value: '' })).toEqual(false);
|
||||
describe('onSaveClick', () => {
|
||||
const props = {
|
||||
altText: {
|
||||
error: {
|
||||
show: true,
|
||||
set: jest.fn(),
|
||||
dismiss: jest.fn(),
|
||||
},
|
||||
validation: {
|
||||
show: true,
|
||||
set: jest.fn(),
|
||||
dismiss: jest.fn(),
|
||||
},
|
||||
},
|
||||
dimensions: simpleDims,
|
||||
saveToEditor: jest.fn().mockName('saveToEditor'),
|
||||
};
|
||||
beforeEach(() => {
|
||||
props.altText.value = 'What is this?';
|
||||
props.isDecorative = false;
|
||||
});
|
||||
it('calls checkFormValidation', () => {
|
||||
jest.spyOn(hooks, hookKeys.checkFormValidation);
|
||||
hooks.onSaveClick({ ...props })();
|
||||
expect(hooks.checkFormValidation).toHaveBeenCalled();
|
||||
});
|
||||
it('calls saveToEditor with dimensions, altText and isDecorative when checkFormValidation is true', () => {
|
||||
jest.spyOn(hooks, hookKeys.checkFormValidation).mockReturnValueOnce(true);
|
||||
hooks.onSaveClick({ ...props })();
|
||||
expect(props.saveToEditor).toHaveBeenCalledWith({
|
||||
altText: props.altText.value,
|
||||
dimensions: props.dimensions,
|
||||
isDecorative: props.isDecorative,
|
||||
});
|
||||
});
|
||||
it('calls dismissError and sets showSubmissionError to false when checkFormValidation is true', () => {
|
||||
jest.spyOn(hooks, hookKeys.checkFormValidation).mockReturnValueOnce(true);
|
||||
hooks.onSaveClick({ ...props })();
|
||||
expect(props.altText.error.dismiss).toHaveBeenCalled();
|
||||
expect(props.altText.validation.dismiss).toHaveBeenCalled();
|
||||
});
|
||||
it('does not call saveEditor when checkFormValidation is false', () => {
|
||||
jest.spyOn(hooks, hookKeys.checkFormValidation).mockReturnValueOnce(false);
|
||||
hooks.onSaveClick({ ...props })();
|
||||
expect(props.saveToEditor).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Button, Image } from '@edx/paragon';
|
||||
import { ArrowBackIos } from '@edx/paragon/icons';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import BaseModal from '../BaseModal';
|
||||
|
||||
import AltTextControls from './AltTextControls';
|
||||
import DimensionControls from './DimensionControls';
|
||||
import './index.scss';
|
||||
import hooks from './hooks';
|
||||
import messages from './messages';
|
||||
import './index.scss';
|
||||
import BaseModal from '../BaseModal';
|
||||
import AltTextControls from './AltTextControls';
|
||||
import DimensionControls from './DimensionControls';
|
||||
import ErrorAlert from '../ErrorAlerts/ErrorAlert';
|
||||
|
||||
/**
|
||||
* Modal display wrapping the dimension and alt-text controls for image tags
|
||||
@@ -34,10 +35,10 @@ export const ImageSettingsModal = ({
|
||||
const dimensions = hooks.dimensions();
|
||||
const altText = hooks.altText();
|
||||
const onSaveClick = hooks.onSaveClick({
|
||||
saveToEditor,
|
||||
altText,
|
||||
dimensions: dimensions.value,
|
||||
altText: altText.value,
|
||||
isDecorative: altText.isDecorative,
|
||||
saveToEditor,
|
||||
});
|
||||
return (
|
||||
<BaseModal
|
||||
@@ -48,12 +49,18 @@ export const ImageSettingsModal = ({
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onSaveClick}
|
||||
disabled={hooks.isSaveDisabled(altText)}
|
||||
>
|
||||
<FormattedMessage {...messages.saveButtonLabel} />
|
||||
</Button>
|
||||
)}
|
||||
>
|
||||
<ErrorAlert
|
||||
dismissError={altText.error.dismiss}
|
||||
hideHeading
|
||||
isError={altText.error.show}
|
||||
>
|
||||
<FormattedMessage {...messages.altTextError} />
|
||||
</ErrorAlert>
|
||||
<Button
|
||||
onClick={returnToSelection}
|
||||
variant="link"
|
||||
|
||||
@@ -17,9 +17,12 @@ jest.mock('./hooks', () => ({
|
||||
altText: () => ({
|
||||
value: 'alternative Taxes',
|
||||
isDecorative: false,
|
||||
error: {
|
||||
show: 'sHoW',
|
||||
dismiss: jest.fn(),
|
||||
},
|
||||
}),
|
||||
onSaveClick: (args) => ({ 'hooks.onSaveClick': args }),
|
||||
isSaveDisabled: (args) => ({ 'hooks.isSaveDisabled': args }),
|
||||
}));
|
||||
|
||||
describe('ImageSettingsModal', () => {
|
||||
|
||||
@@ -59,6 +59,18 @@ export const messages = {
|
||||
defaultMessage: 'This image is decorative (no alt text required).',
|
||||
description: 'Checkbox label for whether or not an image is decorative.',
|
||||
},
|
||||
|
||||
// User Feedback Errors
|
||||
altTextError: {
|
||||
id: 'authoring.texteditor.imagesettingsmodal.error.altTextError',
|
||||
defaultMessage: 'Enter alt text or specify that the image is decorative only.',
|
||||
description: 'Message presented to user when user attempts to save unaccepted altText configuration.',
|
||||
},
|
||||
altTextLocalFeedback: {
|
||||
id: 'authoring.texteditor.imagesettingsmodal.error.altTextLocalFeedback',
|
||||
defaultMessage: 'Enter alt text',
|
||||
description: 'Message feedback for user below the alt text field.',
|
||||
},
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -32,6 +32,17 @@ exports[`SelectImageModal component snapshot 1`] = `
|
||||
>
|
||||
<FetchErrorAlert />
|
||||
<UploadErrorAlert />
|
||||
<ErrorAlert
|
||||
dismissError={[MockFunction]}
|
||||
hideHeading={true}
|
||||
isError="ShoWERror"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Select an image to continue."
|
||||
description="Message presented to user when clicking Next without selecting an image"
|
||||
id="authoring.texteditor.selectimagemodal.error.selectImageError"
|
||||
/>
|
||||
</ErrorAlert>
|
||||
<Stack
|
||||
gap={3}
|
||||
>
|
||||
|
||||
@@ -6,8 +6,9 @@ import * as module from './hooks';
|
||||
import { sortFunctions, sortKeys } from './utils';
|
||||
|
||||
export const state = {
|
||||
images: (val) => React.useState(val),
|
||||
highlighted: (val) => React.useState(val),
|
||||
images: (val) => React.useState(val),
|
||||
showSelectImageError: (val) => React.useState(val),
|
||||
searchString: (val) => React.useState(val),
|
||||
sortBy: (val) => React.useState(val),
|
||||
};
|
||||
@@ -34,12 +35,13 @@ export const displayList = ({ sortBy, searchString, images }) => module.filtered
|
||||
}).sort(sortFunctions[sortBy in sortKeys ? sortKeys[sortBy] : sortKeys.dateNewest]);
|
||||
|
||||
export const imgListHooks = ({
|
||||
setSelection,
|
||||
searchSortProps,
|
||||
setSelection,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const [images, setImages] = module.state.images({});
|
||||
const [highlighted, setHighlighted] = module.state.highlighted(null);
|
||||
const [showSelectImageError, setShowSelectImageError] = module.state.showSelectImageError(false);
|
||||
const list = module.displayList({ ...searchSortProps, images });
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -47,12 +49,12 @@ export const imgListHooks = ({
|
||||
}, []);
|
||||
|
||||
return {
|
||||
images,
|
||||
// highlight by id
|
||||
selectBtnProps: {
|
||||
disabled: !highlighted,
|
||||
onClick: () => setSelection(images[highlighted]),
|
||||
error: {
|
||||
show: showSelectImageError,
|
||||
set: () => setShowSelectImageError(true),
|
||||
dismiss: () => setShowSelectImageError(false),
|
||||
},
|
||||
images,
|
||||
galleryProps: {
|
||||
galleryIsEmpty: Object.keys(images).length === 0,
|
||||
searchIsEmpty: list.length === 0,
|
||||
@@ -60,6 +62,16 @@ export const imgListHooks = ({
|
||||
highlighted,
|
||||
onHighlightChange: e => setHighlighted(e.target.value),
|
||||
},
|
||||
// highlight by id
|
||||
selectBtnProps: {
|
||||
onClick: () => {
|
||||
if (highlighted) {
|
||||
setSelection(images[highlighted]);
|
||||
} else {
|
||||
setShowSelectImageError(true);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -85,13 +97,18 @@ export const imgHooks = ({ setSelection }) => {
|
||||
const searchSortProps = module.searchAndSortHooks();
|
||||
const imgList = module.imgListHooks({ setSelection, searchSortProps });
|
||||
const fileInput = module.fileInputHooks({ setSelection });
|
||||
const { selectBtnProps, galleryProps } = imgList;
|
||||
|
||||
return {
|
||||
fileInput,
|
||||
const {
|
||||
error,
|
||||
galleryProps,
|
||||
selectBtnProps,
|
||||
} = imgList;
|
||||
|
||||
return {
|
||||
error,
|
||||
fileInput,
|
||||
galleryProps,
|
||||
searchSortProps,
|
||||
selectBtnProps,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -61,8 +61,9 @@ describe('SelectImageModal hooks', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('state hooks', () => {
|
||||
state.testGetter(state.keys.images);
|
||||
state.testGetter(state.keys.highlighted);
|
||||
state.testGetter(state.keys.images);
|
||||
state.testGetter(state.keys.showSelectImageError);
|
||||
state.testGetter(state.keys.searchString);
|
||||
state.testGetter(state.keys.sortBy);
|
||||
});
|
||||
@@ -175,12 +176,6 @@ describe('SelectImageModal hooks', () => {
|
||||
);
|
||||
});
|
||||
describe('selectBtnProps', () => {
|
||||
it('is disabled if nothing is highlighted', () => {
|
||||
expect(hook.selectBtnProps.disabled).toEqual(true);
|
||||
state.mockVal(state.keys.highlighted, { some: 'value' });
|
||||
load();
|
||||
expect(hook.selectBtnProps.disabled).toEqual(false);
|
||||
});
|
||||
test('on click, if sets selection to the image with the same id', () => {
|
||||
const highlighted = 'id1';
|
||||
state.mockVal(state.keys.images, { [highlighted]: testValue });
|
||||
@@ -190,6 +185,14 @@ describe('SelectImageModal hooks', () => {
|
||||
hook.selectBtnProps.onClick();
|
||||
expect(props.setSelection).toHaveBeenCalledWith(testValue);
|
||||
});
|
||||
test('on click, sets showSelectImageError to true if nothing is highlighted', () => {
|
||||
state.mockVal(state.keys.images, { });
|
||||
state.mockVal(state.keys.highlighted, null);
|
||||
load();
|
||||
hook.selectBtnProps.onClick();
|
||||
expect(props.setSelection).not.toHaveBeenCalled();
|
||||
expect(state.setState.showSelectImageError).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
describe('galleryProps', () => {
|
||||
it('returns highlighted value, initialized to null', () => {
|
||||
@@ -197,7 +200,7 @@ describe('SelectImageModal hooks', () => {
|
||||
expect(state.stateVals.highlighted).toEqual(null);
|
||||
});
|
||||
test('onHighlightChange sets highlighted with event target value', () => {
|
||||
expect(hook.galleryProps.onHighlightChange({ target: { value: testValue } }));
|
||||
hook.galleryProps.onHighlightChange({ target: { value: testValue } });
|
||||
expect(state.setState.highlighted).toHaveBeenCalledWith(testValue);
|
||||
});
|
||||
test('displayList returns displayListhook called with searchSortProps and images', () => {
|
||||
@@ -207,6 +210,32 @@ describe('SelectImageModal hooks', () => {
|
||||
}));
|
||||
});
|
||||
});
|
||||
describe('error', () => {
|
||||
test('show is initialized to false and returns properly', () => {
|
||||
const show = 'sHOWSelectiMaGEeRROr';
|
||||
expect(hook.error.show).toEqual(false);
|
||||
state.mockVal(state.keys.showSelectImageError, show);
|
||||
hook = hooks.imgListHooks(props);
|
||||
expect(hook.error.show).toEqual(show);
|
||||
});
|
||||
test('set sets showSelectImageError to true', () => {
|
||||
hook.error.set();
|
||||
expect(state.setState.showSelectImageError).toHaveBeenCalledWith(true);
|
||||
});
|
||||
test('dismiss sets showSelectImageError to false', () => {
|
||||
hook.error.dismiss();
|
||||
expect(state.setState.showSelectImageError).toHaveBeenCalledWith(false);
|
||||
});
|
||||
// TODO
|
||||
// it('returns selectImageError value, initialized to false', () => {
|
||||
// expect(hook.selectImageErrorProps.isError).toEqual(state.stateVals.isSelectImageError);
|
||||
// expect(state.stateVals.isSelectImageError).toEqual(false);
|
||||
// });
|
||||
// test('dismissError sets selectImageError to false', () => {
|
||||
// hook.selectImageErrorProps.dismissError();
|
||||
// expect(state.setState.isSelectImageError).toHaveBeenCalledWith(false);
|
||||
// });
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('fileInputHooks', () => {
|
||||
|
||||
@@ -13,6 +13,7 @@ import Gallery from './Gallery';
|
||||
import FileInput from './FileInput';
|
||||
import FetchErrorAlert from '../ErrorAlerts/FetchErrorAlert';
|
||||
import UploadErrorAlert from '../ErrorAlerts/UploadErrorAlert';
|
||||
import ErrorAlert from '../ErrorAlerts/ErrorAlert';
|
||||
|
||||
export const SelectImageModal = ({
|
||||
isOpen,
|
||||
@@ -22,10 +23,11 @@ export const SelectImageModal = ({
|
||||
intl,
|
||||
}) => {
|
||||
const {
|
||||
searchSortProps,
|
||||
galleryProps,
|
||||
selectBtnProps,
|
||||
error,
|
||||
fileInput,
|
||||
galleryProps,
|
||||
searchSortProps,
|
||||
selectBtnProps,
|
||||
} = hooks.imgHooks({ setSelection });
|
||||
return (
|
||||
<BaseModal
|
||||
@@ -43,8 +45,20 @@ export const SelectImageModal = ({
|
||||
)}
|
||||
title={intl.formatMessage(messages.titleLabel)}
|
||||
>
|
||||
|
||||
{/* Error Alerts */}
|
||||
<FetchErrorAlert />
|
||||
<UploadErrorAlert />
|
||||
|
||||
{/* User Feedback Alerts */}
|
||||
<ErrorAlert
|
||||
dismissError={error.dismiss}
|
||||
hideHeading
|
||||
isError={error.show}
|
||||
>
|
||||
<FormattedMessage {...messages.selectImageError} />
|
||||
</ErrorAlert>
|
||||
|
||||
<Stack gap={3}>
|
||||
<SearchSort {...searchSortProps} />
|
||||
<Gallery {...galleryProps} />
|
||||
|
||||
@@ -18,14 +18,19 @@ jest.mock('../ErrorAlerts/UploadErrorAlert', () => 'UploadErrorAlert');
|
||||
|
||||
jest.mock('./hooks', () => ({
|
||||
imgHooks: jest.fn(() => ({
|
||||
searchSortProps: { search: 'sortProps' },
|
||||
galleryProps: { gallery: 'props' },
|
||||
selectBtnProps: { select: 'btnProps' },
|
||||
error: {
|
||||
show: 'ShoWERror',
|
||||
set: jest.fn(),
|
||||
dismiss: jest.fn(),
|
||||
},
|
||||
fileInput: {
|
||||
addFile: 'imgHooks.fileInput.addFile',
|
||||
click: 'imgHooks.fileInput.click',
|
||||
ref: 'imgHooks.fileInput.ref',
|
||||
},
|
||||
galleryProps: { gallery: 'props' },
|
||||
searchSortProps: { search: 'sortProps' },
|
||||
selectBtnProps: { select: 'btnProps' },
|
||||
})),
|
||||
}));
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ export const messages = {
|
||||
defaultMessage: 'Search',
|
||||
description: 'Placeholder text for search bar',
|
||||
},
|
||||
|
||||
// Sort Dropdown
|
||||
sortByDateNewest: {
|
||||
id: 'authoring.texteditor.selectimagemodal.sort.datenewest.label',
|
||||
defaultMessage: 'By date added (newest)',
|
||||
@@ -39,6 +41,8 @@ export const messages = {
|
||||
defaultMessage: 'By name (descending)',
|
||||
description: 'Dropdown label for sorting by name (descending)',
|
||||
},
|
||||
|
||||
// Gallery
|
||||
addedDate: {
|
||||
id: 'authoring.texteditor.selectimagemodal.addedDate.label',
|
||||
defaultMessage: 'Added {date} at {time}',
|
||||
@@ -49,21 +53,6 @@ export const messages = {
|
||||
defaultMessage: 'loading...',
|
||||
description: 'Gallery loading spinner screen-reader text',
|
||||
},
|
||||
uploadImageError: {
|
||||
id: 'authoring.texteditor.selectimagemodal.error.uploadImageError',
|
||||
defaultMessage: 'Failed to Upload Image. Please Try again.',
|
||||
description: 'Message presented to user when image fails to upload',
|
||||
},
|
||||
fetchImagesError: {
|
||||
id: 'authoring.texteditor.selectimagemodal.error.fetchImagesError',
|
||||
defaultMessage: 'Failed to obtain course Images. Please Try again.',
|
||||
description: 'Message presented to user when images are not found',
|
||||
},
|
||||
errorTitle: {
|
||||
id: 'authoring.texteditor.selectimagemodal.error.errorTitle',
|
||||
defaultMessage: 'Error',
|
||||
description: 'Title of message presented to user when something goes wrong',
|
||||
},
|
||||
emptyGalleryLabel: {
|
||||
id: 'authoring.texteditor.selectimagemodal.emptyGalleryLabel',
|
||||
defaultMessage: 'No images found in your gallery. Please upload an image using the button below.',
|
||||
@@ -74,6 +63,28 @@ export const messages = {
|
||||
defaultMessage: 'No search results.',
|
||||
description: 'Label for when search returns nothing.',
|
||||
},
|
||||
|
||||
// Errors
|
||||
errorTitle: {
|
||||
id: 'authoring.texteditor.selectimagemodal.error.errorTitle',
|
||||
defaultMessage: 'Error',
|
||||
description: 'Title of message presented to user when something goes wrong',
|
||||
},
|
||||
uploadImageError: {
|
||||
id: 'authoring.texteditor.selectimagemodal.error.uploadImageError',
|
||||
defaultMessage: 'Failed to Upload Image. Please Try again.',
|
||||
description: 'Message presented to user when image fails to upload',
|
||||
},
|
||||
fetchImagesError: {
|
||||
id: 'authoring.texteditor.selectimagemodal.error.fetchImagesError',
|
||||
defaultMessage: 'Failed to obtain course Images. Please Try again.',
|
||||
description: 'Message presented to user when images are not found',
|
||||
},
|
||||
selectImageError: {
|
||||
id: 'authoring.texteditor.selectimagemodal.error.selectImageError',
|
||||
defaultMessage: 'Select an image to continue.',
|
||||
description: 'Message presented to user when clicking Next without selecting an image',
|
||||
},
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
Reference in New Issue
Block a user