Feat: raw editor ingress and egress logic (#179)
* feat: conditional rendering of olx editor. * fix: open the raw editor if advanced is chosen * fix: add test fix * feat: add button to switch visual->advanced * fix: add tests + lint for visual->advanced button * feat: revert to advanced if parser fails * fix: improve coverage * feat: add confirm dialog to switch * fix: load settings with advanced * fix: refactor + lint fix
This commit is contained in:
@@ -77,6 +77,11 @@ exports[`SettingsWidget snapshot snapshot: renders Settings widget page 1`] = `
|
||||
>
|
||||
<injectIntl(ShimmedIntlComponent) />
|
||||
</Row>
|
||||
<Row
|
||||
className="my-2"
|
||||
>
|
||||
<injectIntl(ShimmedIntlComponent) />
|
||||
</Row>
|
||||
</Body>
|
||||
</Advanced>
|
||||
</Component>
|
||||
@@ -163,6 +168,11 @@ exports[`SettingsWidget snapshot snapshot: renders Settings widget page advanced
|
||||
>
|
||||
<injectIntl(ShimmedIntlComponent) />
|
||||
</Row>
|
||||
<Row
|
||||
className="my-2"
|
||||
>
|
||||
<injectIntl(ShimmedIntlComponent) />
|
||||
</Row>
|
||||
</Body>
|
||||
</Advanced>
|
||||
</Component>
|
||||
|
||||
@@ -13,6 +13,7 @@ import ResetCard from './settingsComponents/ResetCard';
|
||||
import MatlabCard from './settingsComponents/MatlabCard';
|
||||
import TimerCard from './settingsComponents/TimerCard';
|
||||
import TypeCard from './settingsComponents/TypeCard';
|
||||
import SwitchToAdvancedEditorCard from './settingsComponents/SwitchToAdvancedEditorCard';
|
||||
import messages from './messages';
|
||||
import { showAdvancedSettingsCards } from './hooks';
|
||||
|
||||
@@ -75,6 +76,9 @@ export const SettingsWidget = ({
|
||||
<Row className="my-2">
|
||||
<MatlabCard matLabApiKey={settings.matLabApiKey} updateSettings={updateSettings} />
|
||||
</Row>
|
||||
<Row className="my-2">
|
||||
<SwitchToAdvancedEditorCard />
|
||||
</Row>
|
||||
</Collapsible.Body>
|
||||
</Collapsible.Advanced>
|
||||
</Col>
|
||||
|
||||
@@ -149,6 +149,26 @@ export const messages = {
|
||||
defaultMessage: 'Type',
|
||||
description: 'Type settings card title',
|
||||
},
|
||||
SwitchButtonLabel: {
|
||||
id: 'authoring.problemeditor.settings.switchtoadvancededitor.label',
|
||||
defaultMessage: 'Switch To Advanced Editor',
|
||||
description: 'button to switch to the advanced mode of the editor.',
|
||||
},
|
||||
ConfirmSwitchMessage: {
|
||||
id: 'authoring.problemeditor.settings.switchtoadvancededitor.message',
|
||||
defaultMessage: 'If you use the advanced editor, this problem will be converted to OLX and you will not be able to return to the simple editor.',
|
||||
description: 'message to confirm that a user wants to use the advanced editor',
|
||||
},
|
||||
ConfirmSwitchMessageTitle: {
|
||||
id: 'authoring.problemeditor.settings.switchtoadvancededitor.message',
|
||||
defaultMessage: 'Convert to OLX?',
|
||||
description: 'message to confirm that a user wants to use the advanced editor',
|
||||
},
|
||||
ConfirmSwitchButtonLabel: {
|
||||
id: 'authoring.problemeditor.settings.switchtoadvancededitor.message',
|
||||
defaultMessage: 'Switch To Advanced Editor',
|
||||
description: 'message to confirm that a user wants to use the advanced editor',
|
||||
},
|
||||
};
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { Button, Card } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
import messages from '../messages';
|
||||
import { thunkActions } from '../../../../../../data/redux';
|
||||
import BaseModal from '../../../../../TextEditor/components/BaseModal';
|
||||
|
||||
export const SwitchToAdvancedEditorCard = ({
|
||||
switchToAdvancedEditor,
|
||||
}) => {
|
||||
const [isConfirmOpen, setConfirmOpen] = React.useState(false);
|
||||
return (
|
||||
<Card>
|
||||
<BaseModal
|
||||
isOpen={isConfirmOpen}
|
||||
close={() => { setConfirmOpen(false); }}
|
||||
title={(<FormattedMessage {...messages.ConfirmSwitchMessageTitle} />)}
|
||||
confirmAction={(
|
||||
<Button
|
||||
onClick={switchToAdvancedEditor}
|
||||
>
|
||||
<FormattedMessage {...messages.ConfirmSwitchButtonLabel} />
|
||||
</Button>
|
||||
)}
|
||||
size="md"
|
||||
>
|
||||
<FormattedMessage {...messages.ConfirmSwitchMessage} />
|
||||
</BaseModal>
|
||||
<Button
|
||||
className="my-3 ml-2"
|
||||
variant="link"
|
||||
size="inline"
|
||||
onClick={() => { setConfirmOpen(true); }}
|
||||
>
|
||||
<FormattedMessage {...messages.SwitchButtonLabel} />
|
||||
</Button>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
SwitchToAdvancedEditorCard.propTypes = {
|
||||
switchToAdvancedEditor: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export const mapStateToProps = () => ({
|
||||
});
|
||||
export const mapDispatchToProps = {
|
||||
switchToAdvancedEditor: thunkActions.problem.switchToAdvancedEditor,
|
||||
};
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(SwitchToAdvancedEditorCard));
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { SwitchToAdvancedEditorCard } from './SwitchToAdvancedEditorCard';
|
||||
|
||||
describe('SwitchToAdvancedEditorCard snapshot', () => {
|
||||
const mockSwitchToAdvancedEditor = jest.fn().mockName('switchToAdvancedEditor');
|
||||
test('snapshot: SwitchToAdvancedEditorCard', () => {
|
||||
expect(
|
||||
shallow(<SwitchToAdvancedEditorCard switchToAdvancedEditor={mockSwitchToAdvancedEditor} />),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SwitchToAdvancedEditorCard snapshot snapshot: SwitchToAdvancedEditorCard 1`] = `
|
||||
<Card>
|
||||
<BaseModal
|
||||
close={[Function]}
|
||||
confirmAction={
|
||||
<Button
|
||||
onClick={[MockFunction switchToAdvancedEditor]}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Switch To Advanced Editor"
|
||||
description="message to confirm that a user wants to use the advanced editor"
|
||||
id="authoring.problemeditor.settings.switchtoadvancededitor.message"
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
footerAction={null}
|
||||
isOpen={false}
|
||||
size="md"
|
||||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Convert to OLX?"
|
||||
description="message to confirm that a user wants to use the advanced editor"
|
||||
id="authoring.problemeditor.settings.switchtoadvancededitor.message"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="If you use the advanced editor, this problem will be converted to OLX and you will not be able to return to the simple editor."
|
||||
description="message to confirm that a user wants to use the advanced editor"
|
||||
id="authoring.problemeditor.settings.switchtoadvancededitor.message"
|
||||
/>
|
||||
</BaseModal>
|
||||
<Button
|
||||
className="my-3 ml-2"
|
||||
onClick={[Function]}
|
||||
size="inline"
|
||||
variant="link"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Switch To Advanced Editor"
|
||||
description="button to switch to the advanced mode of the editor."
|
||||
id="authoring.problemeditor.settings.switchtoadvancededitor.label"
|
||||
/>
|
||||
</Button>
|
||||
</Card>
|
||||
`;
|
||||
@@ -10,7 +10,7 @@ import { EditorContainer } from '../../../EditorContainer';
|
||||
import { selectors } from '../../../../data/redux';
|
||||
import ReactStateSettingsParser from '../../data/ReactStateSettingsParser';
|
||||
import ReactStateOLXParser from '../../data/ReactStateOLXParser';
|
||||
import { AdvanceProblemKeys } from '../../../../data/constants/problem';
|
||||
import { ProblemTypeKeys } from '../../../../data/constants/problem';
|
||||
|
||||
export const EditProblemView = ({
|
||||
problemType,
|
||||
@@ -24,8 +24,8 @@ export const EditProblemView = ({
|
||||
olx: reactOLXParser.buildOLX(),
|
||||
};
|
||||
};
|
||||
if (Object.values(AdvanceProblemKeys).includes(problemType)) {
|
||||
return `hello raw editor with ${problemType}`;
|
||||
if (problemType === ProblemTypeKeys.ADVANCED) {
|
||||
return 'hello raw editor';
|
||||
}
|
||||
return (
|
||||
<EditorContainer getContent={parseState(problemState)}>
|
||||
|
||||
@@ -19,7 +19,8 @@ export const selectHooks = () => {
|
||||
|
||||
export const onSelect = (setProblemType, selected, updateField) => () => {
|
||||
if (Object.values(AdvanceProblemKeys).includes(selected)) {
|
||||
updateField({ rawOLX: AdvanceProblems[selected].template });
|
||||
updateField({ problemType: ProblemTypeKeys.ADVANCED, rawOLX: AdvanceProblems[selected].template });
|
||||
return;
|
||||
}
|
||||
setProblemType({ selected });
|
||||
};
|
||||
|
||||
@@ -47,6 +47,7 @@ describe('SelectTypeModal hooks', () => {
|
||||
test('updateField is called with selected templated if selected is an Advanced Problem', () => {
|
||||
module.onSelect(mockSetProblemType, mockAdvancedSelected, mockUpdateField)();
|
||||
expect(mockUpdateField).toHaveBeenCalledWith({
|
||||
problemType: ProblemTypeKeys.ADVANCED,
|
||||
rawOLX: AdvanceProblems[mockAdvancedSelected].template,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -331,10 +331,16 @@ export class OLXParser {
|
||||
getProblemType() {
|
||||
const problemKeys = Object.keys(this.problem);
|
||||
const intersectedProblems = _.intersection(Object.values(ProblemTypeKeys), problemKeys);
|
||||
|
||||
if (intersectedProblems.length === 0) {
|
||||
return null;
|
||||
// a blank problem is a problem which contains only `<problem></problem>` as it's olx.
|
||||
// blank problems are not given types, so that a type may be selected.
|
||||
if (problemKeys.length === 1 && problemKeys[0] === '#text' && this.problem[problemKeys[0]] === '') {
|
||||
return null;
|
||||
}
|
||||
// if we have no matching problem type, the problem is advanced.
|
||||
return ProblemTypeKeys.ADVANCED;
|
||||
}
|
||||
// make sure compound problems are treated as advanced
|
||||
if (intersectedProblems.length > 1) {
|
||||
return ProblemTypeKeys.ADVANCED;
|
||||
}
|
||||
@@ -369,7 +375,10 @@ export class OLXParser {
|
||||
answersObject = this.parseMultipleChoiceAnswers(ProblemTypeKeys.SINGLESELECT, 'choicegroup', 'choice');
|
||||
break;
|
||||
case ProblemTypeKeys.ADVANCED:
|
||||
break;
|
||||
return {
|
||||
problemType,
|
||||
settings: {},
|
||||
};
|
||||
default:
|
||||
// if problem is unset, return null
|
||||
return {};
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
textInputWithFeedbackAndHintsOLX,
|
||||
mutlipleChoiceWithFeedbackAndHintsOLX,
|
||||
textInputWithFeedbackAndHintsOLXWithMultipleAnswers,
|
||||
advancedProblemOlX,
|
||||
blankProblemOLX,
|
||||
} from './mockData/olxTestData';
|
||||
import { ProblemTypeKeys } from '../../../data/constants/problem';
|
||||
|
||||
@@ -36,6 +38,16 @@ describe('Check OLXParser problem type', () => {
|
||||
const problemType = olxparser.getProblemType();
|
||||
expect(problemType).toBe(ProblemTypeKeys.TEXTINPUT);
|
||||
});
|
||||
test('Test Advanced Problem Type', () => {
|
||||
const olxparser = new OLXParser(advancedProblemOlX.rawOLX);
|
||||
const problemType = olxparser.getProblemType();
|
||||
expect(problemType).toBe(ProblemTypeKeys.ADVANCED);
|
||||
});
|
||||
test('Test Blank Problem Type', () => {
|
||||
const olxparser = new OLXParser(blankProblemOLX.rawOLX);
|
||||
const problemType = olxparser.getProblemType();
|
||||
expect(problemType).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Check OLXParser hints', () => {
|
||||
|
||||
@@ -553,3 +553,17 @@ export const numericInputWithFeedbackAndHintsOLXException = {
|
||||
</problem>
|
||||
`,
|
||||
};
|
||||
export const advancedProblemOlX = {
|
||||
rawOLX: `<problem>
|
||||
<formularesponse type="ci" samples="R_1,R_2,R_3@1,2,3:3,4,5#10" answer="R_1*R_2/R_3">
|
||||
<p>You can use this template as a guide to the OLX markup to use for math expression problems. Edit this component to replace the example with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required. Example: Write an expression for the product of R_1, R_2, and the inverse of R_3.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this. Example: To test this example, the correct answer is R_1*R_2/R_3</description>
|
||||
<responseparam type="tolerance" default="0.00001"/>
|
||||
<formulaequationinput size="40"/>
|
||||
</formularesponse>
|
||||
</problem>`,
|
||||
};
|
||||
export const blankProblemOLX = {
|
||||
rawOLX: '<problem></problem>',
|
||||
};
|
||||
|
||||
@@ -17,8 +17,6 @@ export const ProblemEditor = ({
|
||||
blockValue,
|
||||
initializeProblemEditor,
|
||||
}) => {
|
||||
React.useEffect(() => initializeProblemEditor(blockValue), [blockValue]);
|
||||
// TODO: INTL MSG, Add LOAD FAILED ERROR using BLOCKFAILED
|
||||
if (!blockFinished || !studioViewFinished) {
|
||||
return (
|
||||
<div className="text-center p-6">
|
||||
@@ -31,6 +29,8 @@ export const ProblemEditor = ({
|
||||
);
|
||||
}
|
||||
// once data is loaded, init store
|
||||
React.useEffect(() => initializeProblemEditor(blockValue), [blockValue]);
|
||||
// TODO: INTL MSG, Add LOAD FAILED ERROR using BLOCKFAILED
|
||||
|
||||
if (problemType === null) {
|
||||
return (<SelectTypeModal onClose={onClose} />);
|
||||
|
||||
Reference in New Issue
Block a user