@pie-lib/config-ui 12.0.0-beta.4 → 12.0.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +8 -1653
- package/CHANGELOG.md +540 -58
- package/LICENSE.md +5 -0
- package/NEXT.CHANGELOG.json +1 -0
- package/lib/alert-dialog.js +44 -20
- package/lib/alert-dialog.js.map +1 -1
- package/lib/checkbox.js +59 -61
- package/lib/checkbox.js.map +1 -1
- package/lib/choice-configuration/feedback-menu.js +30 -65
- package/lib/choice-configuration/feedback-menu.js.map +1 -1
- package/lib/choice-configuration/index.js +231 -244
- package/lib/choice-configuration/index.js.map +1 -1
- package/lib/choice-utils.js +7 -19
- package/lib/choice-utils.js.map +1 -1
- package/lib/feedback-config/feedback-selector.js +89 -115
- package/lib/feedback-config/feedback-selector.js.map +1 -1
- package/lib/feedback-config/group.js +28 -42
- package/lib/feedback-config/group.js.map +1 -1
- package/lib/feedback-config/index.js +55 -87
- package/lib/feedback-config/index.js.map +1 -1
- package/lib/form-section.js +32 -34
- package/lib/form-section.js.map +1 -1
- package/lib/help.js +41 -80
- package/lib/help.js.map +1 -1
- package/lib/index.js +2 -32
- package/lib/index.js.map +1 -1
- package/lib/input.js +24 -57
- package/lib/input.js.map +1 -1
- package/lib/inputs.js +62 -88
- package/lib/inputs.js.map +1 -1
- package/lib/langs.js +59 -102
- package/lib/langs.js.map +1 -1
- package/lib/layout/config-layout.js +95 -67
- package/lib/layout/config-layout.js.map +1 -1
- package/lib/layout/index.js +1 -4
- package/lib/layout/index.js.map +1 -1
- package/lib/layout/layout-contents.js +130 -75
- package/lib/layout/layout-contents.js.map +1 -1
- package/lib/layout/settings-box.js +28 -58
- package/lib/layout/settings-box.js.map +1 -1
- package/lib/mui-box/index.js +42 -58
- package/lib/mui-box/index.js.map +1 -1
- package/lib/number-text-field-custom.js +164 -152
- package/lib/number-text-field-custom.js.map +1 -1
- package/lib/number-text-field.js +87 -119
- package/lib/number-text-field.js.map +1 -1
- package/lib/radio-with-label.js +33 -26
- package/lib/radio-with-label.js.map +1 -1
- package/lib/settings/display-size.js +17 -33
- package/lib/settings/display-size.js.map +1 -1
- package/lib/settings/index.js +26 -46
- package/lib/settings/index.js.map +1 -1
- package/lib/settings/panel.js +202 -221
- package/lib/settings/panel.js.map +1 -1
- package/lib/settings/settings-radio-label.js +37 -29
- package/lib/settings/settings-radio-label.js.map +1 -1
- package/lib/settings/toggle.js +40 -33
- package/lib/settings/toggle.js.map +1 -1
- package/lib/tabs/index.js +26 -57
- package/lib/tabs/index.js.map +1 -1
- package/lib/tags-input/index.js +51 -100
- package/lib/tags-input/index.js.map +1 -1
- package/lib/two-choice.js +47 -91
- package/lib/two-choice.js.map +1 -1
- package/lib/with-stateful-model.js +11 -34
- package/lib/with-stateful-model.js.map +1 -1
- package/package.json +22 -11
- package/src/__tests__/alert-dialog.test.jsx +283 -0
- package/src/__tests__/checkbox.test.jsx +249 -0
- package/src/__tests__/choice-utils.test.js +12 -0
- package/src/__tests__/form-section.test.jsx +334 -0
- package/src/__tests__/help.test.jsx +184 -0
- package/src/__tests__/input.test.jsx +192 -0
- package/src/__tests__/langs.test.jsx +457 -0
- package/src/__tests__/number-text-field-custom.test.jsx +438 -0
- package/src/__tests__/number-text-field.test.jsx +341 -0
- package/src/__tests__/radio-with-label.test.jsx +259 -0
- package/src/__tests__/settings-panel.test.js +187 -0
- package/src/__tests__/settings.test.jsx +515 -0
- package/src/__tests__/tabs.test.jsx +193 -0
- package/src/__tests__/two-choice.test.js +110 -0
- package/src/__tests__/with-stateful-model.test.jsx +145 -0
- package/src/alert-dialog.jsx +31 -16
- package/src/checkbox.jsx +45 -39
- package/src/choice-configuration/__tests__/feedback-menu.test.jsx +163 -0
- package/src/choice-configuration/__tests__/index.test.jsx +234 -0
- package/src/choice-configuration/feedback-menu.jsx +15 -28
- package/src/choice-configuration/index.jsx +233 -182
- package/src/choice-utils.js +1 -1
- package/src/feedback-config/__tests__/feedback-config.test.jsx +141 -0
- package/src/feedback-config/__tests__/feedback-selector.test.jsx +107 -0
- package/src/feedback-config/feedback-selector.jsx +65 -60
- package/src/feedback-config/group.jsx +26 -29
- package/src/feedback-config/index.jsx +59 -47
- package/src/form-section.jsx +26 -18
- package/src/help.jsx +27 -36
- package/src/index.js +2 -5
- package/src/input.jsx +9 -9
- package/src/inputs.jsx +36 -50
- package/src/langs.jsx +57 -73
- package/src/layout/__tests__/config.layout.test.jsx +59 -0
- package/src/layout/__tests__/layout-content.test.jsx +3 -0
- package/src/layout/config-layout.jsx +70 -37
- package/src/layout/layout-contents.jsx +96 -39
- package/src/layout/settings-box.jsx +22 -21
- package/src/mui-box/index.jsx +37 -45
- package/src/number-text-field-custom.jsx +136 -81
- package/src/number-text-field.jsx +59 -37
- package/src/radio-with-label.jsx +28 -12
- package/src/settings/display-size.jsx +14 -13
- package/src/settings/index.js +20 -12
- package/src/settings/panel.jsx +147 -110
- package/src/settings/settings-radio-label.jsx +29 -13
- package/src/settings/toggle.jsx +39 -20
- package/src/tabs/index.jsx +15 -19
- package/src/tags-input/__tests__/index.test.jsx +113 -0
- package/src/tags-input/index.jsx +42 -47
- package/src/two-choice.jsx +19 -23
- package/src/with-stateful-model.jsx +5 -5
- package/README.md +0 -12
|
@@ -1,110 +1,181 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import {
|
|
4
|
-
import TextField from '@material
|
|
5
|
-
import
|
|
3
|
+
import { styled } from '@mui/material/styles';
|
|
4
|
+
import TextField from '@mui/material/TextField';
|
|
5
|
+
import ActionDelete from '@mui/icons-material/Delete';
|
|
6
|
+
import ArrowRight from '@mui/icons-material/SubdirectoryArrowRight';
|
|
7
|
+
import IconButton from '@mui/material/IconButton';
|
|
6
8
|
import { InputContainer } from '@pie-lib/render-ui';
|
|
7
|
-
import EditableHtml from '@pie-lib/editable-html';
|
|
9
|
+
// import EditableHtml from '@pie-lib/editable-html';
|
|
8
10
|
import { InputCheckbox, InputRadio } from '../inputs';
|
|
9
11
|
import FeedbackMenu from './feedback-menu';
|
|
10
|
-
import ActionDelete from '@material-ui/icons/Delete';
|
|
11
|
-
import ArrowRight from '@material-ui/icons/SubdirectoryArrowRight';
|
|
12
|
-
import IconButton from '@material-ui/core/IconButton';
|
|
13
|
-
|
|
14
|
-
const EditableHtmlContainer = withStyles(theme => ({
|
|
15
|
-
labelContainer: {},
|
|
16
|
-
editorHolder: {
|
|
17
|
-
marginTop: theme.spacing.unit * 2
|
|
18
|
-
}
|
|
19
|
-
}))(
|
|
20
|
-
({
|
|
21
|
-
label,
|
|
22
|
-
classes,
|
|
23
|
-
onChange,
|
|
24
|
-
value,
|
|
25
|
-
className,
|
|
26
|
-
imageSupport,
|
|
27
|
-
disabled,
|
|
28
|
-
spellCheck,
|
|
29
|
-
nonEmpty,
|
|
30
|
-
toolbarOpts,
|
|
31
|
-
error,
|
|
32
|
-
maxImageWidth,
|
|
33
|
-
maxImageHeight
|
|
34
|
-
}) => {
|
|
35
|
-
const names = classNames(classes.labelContainer, className);
|
|
36
12
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
13
|
+
// - mathquill error window not defined
|
|
14
|
+
let EditableHtml;
|
|
15
|
+
if (typeof window !== 'undefined') {
|
|
16
|
+
EditableHtml = require('@pie-lib/editable-html')['default'];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const StyledEditorHolder = styled('div')(({ theme }) => ({
|
|
20
|
+
marginTop: theme.spacing(2),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
const EditableHtmlContainer = ({
|
|
24
|
+
label,
|
|
25
|
+
onChange,
|
|
26
|
+
value,
|
|
27
|
+
className,
|
|
28
|
+
imageSupport,
|
|
29
|
+
disableImageAlignmentButtons,
|
|
30
|
+
disabled,
|
|
31
|
+
spellCheck,
|
|
32
|
+
nonEmpty,
|
|
33
|
+
pluginOpts,
|
|
34
|
+
toolbarOpts,
|
|
35
|
+
error,
|
|
36
|
+
maxImageWidth,
|
|
37
|
+
maxImageHeight,
|
|
38
|
+
uploadSoundSupport,
|
|
39
|
+
mathMlOptions = {},
|
|
40
|
+
}) => {
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<InputContainer label={label} className={className}>
|
|
44
|
+
<StyledEditorHolder>
|
|
45
|
+
<EditableHtml
|
|
46
|
+
markup={value || ''}
|
|
47
|
+
disabled={disabled}
|
|
48
|
+
spellCheck={spellCheck}
|
|
49
|
+
nonEmpty={nonEmpty}
|
|
50
|
+
onChange={onChange}
|
|
51
|
+
imageSupport={imageSupport}
|
|
52
|
+
disableImageAlignmentButtons={disableImageAlignmentButtons}
|
|
53
|
+
pluginProps={pluginOpts || {}}
|
|
54
|
+
toolbarOpts={toolbarOpts}
|
|
55
|
+
error={error}
|
|
56
|
+
maxImageWidth={maxImageWidth}
|
|
57
|
+
maxImageHeight={maxImageHeight}
|
|
58
|
+
uploadSoundSupport={uploadSoundSupport}
|
|
59
|
+
languageCharactersProps={[{ language: 'spanish' }, { language: 'special' }]}
|
|
60
|
+
mathMlOptions={mathMlOptions}
|
|
61
|
+
/>
|
|
62
|
+
</StyledEditorHolder>
|
|
63
|
+
</InputContainer>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const StyledFeedbackContainer = styled('div')(() => ({
|
|
68
|
+
position: 'relative',
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
const StyledArrowIcon = styled(ArrowRight)(({ theme }) => ({
|
|
72
|
+
fill: theme.palette.grey[400],
|
|
73
|
+
left: -56,
|
|
74
|
+
position: 'absolute',
|
|
75
|
+
top: 40,
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
const StyledTextField = styled(TextField)(({ theme }) => ({
|
|
79
|
+
width: '100%',
|
|
80
|
+
marginTop: theme.spacing(2),
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
const StyledEditableHtmlContainer = styled(EditableHtmlContainer)(({ theme }) => ({
|
|
84
|
+
width: '100%',
|
|
85
|
+
marginTop: theme.spacing(2),
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
const Feedback = ({ value, onChange, type, correct, defaults, toolbarOpts, mathMlOptions = {} }) => {
|
|
73
89
|
if (!type || type === 'none') {
|
|
74
90
|
return null;
|
|
75
91
|
} else if (type === 'default') {
|
|
76
92
|
return (
|
|
77
|
-
<
|
|
78
|
-
<
|
|
79
|
-
<
|
|
80
|
-
className={classes.text}
|
|
93
|
+
<StyledFeedbackContainer>
|
|
94
|
+
<StyledArrowIcon />
|
|
95
|
+
<StyledTextField
|
|
81
96
|
label="Feedback Text"
|
|
82
97
|
value={correct ? defaults.correct : defaults.incorrect}
|
|
98
|
+
variant="standard"
|
|
83
99
|
/>
|
|
84
|
-
</
|
|
100
|
+
</StyledFeedbackContainer>
|
|
85
101
|
);
|
|
86
102
|
} else {
|
|
87
103
|
return (
|
|
88
|
-
<
|
|
89
|
-
<
|
|
90
|
-
<
|
|
91
|
-
className={classes.text}
|
|
104
|
+
<StyledFeedbackContainer>
|
|
105
|
+
<StyledArrowIcon />
|
|
106
|
+
<StyledEditableHtmlContainer
|
|
92
107
|
label="Feedback Text"
|
|
93
108
|
value={value}
|
|
94
109
|
onChange={onChange}
|
|
95
110
|
toolbarOpts={toolbarOpts}
|
|
111
|
+
mathMlOptions={mathMlOptions}
|
|
96
112
|
/>
|
|
97
|
-
</
|
|
113
|
+
</StyledFeedbackContainer>
|
|
98
114
|
);
|
|
99
115
|
}
|
|
100
|
-
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const StyledIndex = styled('span')(({ theme }) => ({
|
|
119
|
+
paddingRight: theme.spacing(1),
|
|
120
|
+
paddingTop: theme.spacing(3),
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
const StyledTopRow = styled('div')(() => ({
|
|
124
|
+
display: 'flex',
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
const StyledToggle = styled('div')(({ theme }) => ({
|
|
129
|
+
flex: '0 1 auto',
|
|
130
|
+
paddingTop: theme.spacing(0.5),
|
|
131
|
+
paddingBottom: 0,
|
|
132
|
+
marginRight: 0,
|
|
133
|
+
marginLeft: theme.spacing(1),
|
|
134
|
+
}));
|
|
135
|
+
|
|
136
|
+
const StyledFeedback = styled('div')(({ theme }) => ({
|
|
137
|
+
flex: '0 1 auto',
|
|
138
|
+
paddingTop: theme.spacing(2),
|
|
139
|
+
paddingLeft: 0,
|
|
140
|
+
marginLeft: 0,
|
|
141
|
+
marginRight: theme.spacing(1),
|
|
142
|
+
}));
|
|
143
|
+
|
|
144
|
+
const StyledFeedbackIcon = styled('div')(() => ({
|
|
145
|
+
margin: 0,
|
|
146
|
+
width: 'inherit',
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
const StyledDeleteIcon = styled('div')(() => ({
|
|
150
|
+
margin: 0,
|
|
151
|
+
width: 'inherit',
|
|
152
|
+
}));
|
|
153
|
+
|
|
154
|
+
const StyledDelete = styled('div')(({ theme }) => ({
|
|
155
|
+
flex: '0 1 auto',
|
|
156
|
+
paddingTop: theme.spacing(2),
|
|
157
|
+
paddingLeft: 0,
|
|
158
|
+
marginLeft: 0,
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
const StyledMiddleColumn = styled('div')(({ theme }) => ({
|
|
162
|
+
display: 'flex',
|
|
163
|
+
flex: 1,
|
|
164
|
+
flexDirection: 'column',
|
|
165
|
+
marginRight: theme.spacing(1),
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
const StyledErrorText = styled('div')(({ theme }) => ({
|
|
169
|
+
fontSize: theme.typography.fontSize - 2,
|
|
170
|
+
color: theme.palette.error.main,
|
|
171
|
+
}));
|
|
101
172
|
|
|
102
173
|
export class ChoiceConfiguration extends React.Component {
|
|
103
174
|
static propTypes = {
|
|
104
|
-
classes: PropTypes.object.isRequired,
|
|
105
175
|
noLabels: PropTypes.bool,
|
|
106
176
|
useLetterOrdering: PropTypes.bool,
|
|
107
177
|
className: PropTypes.string,
|
|
178
|
+
error: PropTypes.string,
|
|
108
179
|
mode: PropTypes.oneOf(['checkbox', 'radio']),
|
|
109
180
|
defaultFeedback: PropTypes.object.isRequired,
|
|
110
181
|
disabled: PropTypes.bool,
|
|
@@ -115,19 +186,26 @@ export class ChoiceConfiguration extends React.Component {
|
|
|
115
186
|
correct: PropTypes.bool,
|
|
116
187
|
feedback: PropTypes.shape({
|
|
117
188
|
type: PropTypes.string,
|
|
118
|
-
value: PropTypes.string
|
|
119
|
-
})
|
|
189
|
+
value: PropTypes.string,
|
|
190
|
+
}),
|
|
120
191
|
}),
|
|
121
192
|
onDelete: PropTypes.func,
|
|
122
193
|
onChange: PropTypes.func,
|
|
123
194
|
index: PropTypes.number,
|
|
124
195
|
imageSupport: PropTypes.shape({
|
|
125
196
|
add: PropTypes.func.isRequired,
|
|
126
|
-
delete: PropTypes.func.isRequired
|
|
197
|
+
delete: PropTypes.func.isRequired,
|
|
127
198
|
}),
|
|
199
|
+
disableImageAlignmentButtons: PropTypes.bool,
|
|
128
200
|
allowFeedBack: PropTypes.bool,
|
|
129
201
|
allowDelete: PropTypes.bool,
|
|
130
|
-
|
|
202
|
+
noCorrectAnswerError: PropTypes.string,
|
|
203
|
+
spellCheck: PropTypes.bool,
|
|
204
|
+
pluginOpts: PropTypes.object,
|
|
205
|
+
toolbarOpts: PropTypes.object,
|
|
206
|
+
uploadSoundSupport: PropTypes.object,
|
|
207
|
+
maxImageWidth: PropTypes.number,
|
|
208
|
+
maxImageHeight: PropTypes.number,
|
|
131
209
|
};
|
|
132
210
|
|
|
133
211
|
static defaultProps = {
|
|
@@ -135,11 +213,12 @@ export class ChoiceConfiguration extends React.Component {
|
|
|
135
213
|
noLabels: false,
|
|
136
214
|
useLetterOrdering: false,
|
|
137
215
|
allowFeedBack: true,
|
|
138
|
-
allowDelete: true
|
|
216
|
+
allowDelete: true,
|
|
139
217
|
};
|
|
140
218
|
|
|
141
|
-
_changeFn = key => update => {
|
|
219
|
+
_changeFn = (key) => (update) => {
|
|
142
220
|
const { data, onChange } = this.props;
|
|
221
|
+
|
|
143
222
|
if (onChange) {
|
|
144
223
|
onChange({ ...data, [key]: update });
|
|
145
224
|
}
|
|
@@ -147,7 +226,7 @@ export class ChoiceConfiguration extends React.Component {
|
|
|
147
226
|
|
|
148
227
|
onLabelChange = this._changeFn('label');
|
|
149
228
|
|
|
150
|
-
onCheckedChange = event => {
|
|
229
|
+
onCheckedChange = (event) => {
|
|
151
230
|
const correct = event.target.checked;
|
|
152
231
|
const { data, onChange } = this.props;
|
|
153
232
|
|
|
@@ -156,7 +235,7 @@ export class ChoiceConfiguration extends React.Component {
|
|
|
156
235
|
}
|
|
157
236
|
};
|
|
158
237
|
|
|
159
|
-
onFeedbackValueChange = v => {
|
|
238
|
+
onFeedbackValueChange = (v) => {
|
|
160
239
|
const { data, onChange } = this.props;
|
|
161
240
|
|
|
162
241
|
if (data.feedback.type !== 'custom') {
|
|
@@ -168,9 +247,10 @@ export class ChoiceConfiguration extends React.Component {
|
|
|
168
247
|
if (onChange) onChange({ ...data, feedback: fb });
|
|
169
248
|
};
|
|
170
249
|
|
|
171
|
-
onFeedbackTypeChange = t => {
|
|
250
|
+
onFeedbackTypeChange = (t) => {
|
|
172
251
|
const { data, onChange } = this.props;
|
|
173
252
|
const fb = { ...data.feedback, type: t };
|
|
253
|
+
|
|
174
254
|
if (fb.type !== 'custom') {
|
|
175
255
|
fb.value = undefined;
|
|
176
256
|
}
|
|
@@ -181,7 +261,6 @@ export class ChoiceConfiguration extends React.Component {
|
|
|
181
261
|
render() {
|
|
182
262
|
const {
|
|
183
263
|
data,
|
|
184
|
-
classes,
|
|
185
264
|
mode,
|
|
186
265
|
onDelete,
|
|
187
266
|
defaultFeedback,
|
|
@@ -190,46 +269,60 @@ export class ChoiceConfiguration extends React.Component {
|
|
|
190
269
|
noLabels,
|
|
191
270
|
useLetterOrdering,
|
|
192
271
|
imageSupport,
|
|
272
|
+
disableImageAlignmentButtons,
|
|
193
273
|
disabled,
|
|
194
274
|
spellCheck,
|
|
195
275
|
nonEmpty,
|
|
196
276
|
allowFeedBack,
|
|
197
277
|
allowDelete,
|
|
278
|
+
pluginOpts,
|
|
198
279
|
toolbarOpts,
|
|
199
280
|
error,
|
|
200
|
-
noCorrectAnswerError
|
|
281
|
+
noCorrectAnswerError,
|
|
282
|
+
uploadSoundSupport,
|
|
283
|
+
maxImageWidth,
|
|
284
|
+
maxImageHeight,
|
|
285
|
+
mathMlOptions = {},
|
|
201
286
|
} = this.props;
|
|
202
287
|
|
|
203
288
|
const InputToggle = mode === 'checkbox' ? InputCheckbox : InputRadio;
|
|
204
|
-
|
|
289
|
+
|
|
205
290
|
return (
|
|
206
|
-
|
|
207
|
-
<div className={classes.topRow}>
|
|
291
|
+
<StyledTopRow>
|
|
208
292
|
{index > 0 && (
|
|
209
|
-
<
|
|
293
|
+
<StyledIndex type="title">
|
|
210
294
|
{useLetterOrdering ? String.fromCharCode(96 + index).toUpperCase() : index}
|
|
211
|
-
</
|
|
295
|
+
</StyledIndex>
|
|
212
296
|
)}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
<div className={classes.middleColumn}>
|
|
221
|
-
<EditableHtmlContainer
|
|
222
|
-
label={!noLabels ? 'Label' : ''}
|
|
223
|
-
value={data.label}
|
|
224
|
-
onChange={this.onLabelChange}
|
|
225
|
-
imageSupport={imageSupport}
|
|
226
|
-
disabled={disabled}
|
|
227
|
-
spellCheck={spellCheck}
|
|
228
|
-
nonEmpty={nonEmpty}
|
|
229
|
-
toolbarOpts={toolbarOpts}
|
|
230
|
-
error={error}
|
|
297
|
+
|
|
298
|
+
<StyledToggle>
|
|
299
|
+
<InputToggle
|
|
300
|
+
onChange={this.onCheckedChange}
|
|
301
|
+
label={!noLabels ? 'Correct' : ''}
|
|
302
|
+
checked={!!data.correct}
|
|
303
|
+
error={noCorrectAnswerError}
|
|
231
304
|
/>
|
|
232
|
-
|
|
305
|
+
</StyledToggle>
|
|
306
|
+
|
|
307
|
+
<StyledMiddleColumn>
|
|
308
|
+
<EditableHtmlContainer
|
|
309
|
+
label={!noLabels ? 'Label' : ''}
|
|
310
|
+
value={data.label}
|
|
311
|
+
onChange={this.onLabelChange}
|
|
312
|
+
imageSupport={imageSupport}
|
|
313
|
+
disableImageAlignmentButtons={disableImageAlignmentButtons}
|
|
314
|
+
disabled={disabled}
|
|
315
|
+
spellCheck={spellCheck}
|
|
316
|
+
nonEmpty={nonEmpty}
|
|
317
|
+
pluginOpts={pluginOpts}
|
|
318
|
+
toolbarOpts={toolbarOpts}
|
|
319
|
+
error={error}
|
|
320
|
+
uploadSoundSupport={uploadSoundSupport}
|
|
321
|
+
mathMlOptions={mathMlOptions}
|
|
322
|
+
maxImageWidth={maxImageWidth}
|
|
323
|
+
maxImageHeight={maxImageHeight}
|
|
324
|
+
/>
|
|
325
|
+
{error && <StyledErrorText>{error}</StyledErrorText>}
|
|
233
326
|
|
|
234
327
|
{allowFeedBack && (
|
|
235
328
|
<Feedback
|
|
@@ -240,80 +333,38 @@ export class ChoiceConfiguration extends React.Component {
|
|
|
240
333
|
toolbarOpts={toolbarOpts}
|
|
241
334
|
/>
|
|
242
335
|
)}
|
|
243
|
-
</
|
|
336
|
+
</StyledMiddleColumn>
|
|
337
|
+
|
|
244
338
|
{allowFeedBack && (
|
|
245
|
-
<
|
|
246
|
-
<
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
339
|
+
<StyledFeedback>
|
|
340
|
+
<InputContainer label={!noLabels ? 'Feedback' : ''}>
|
|
341
|
+
<StyledFeedbackIcon>
|
|
342
|
+
<FeedbackMenu
|
|
343
|
+
onChange={this.onFeedbackTypeChange}
|
|
344
|
+
value={data.feedback}
|
|
345
|
+
/>
|
|
346
|
+
</StyledFeedbackIcon>
|
|
347
|
+
</InputContainer>
|
|
348
|
+
</StyledFeedback>
|
|
254
349
|
)}
|
|
350
|
+
|
|
255
351
|
{allowDelete && (
|
|
256
|
-
<
|
|
257
|
-
<
|
|
258
|
-
<
|
|
259
|
-
|
|
260
|
-
|
|
352
|
+
<StyledDelete>
|
|
353
|
+
<InputContainer label={!noLabels ? 'Delete' : ''}>
|
|
354
|
+
<StyledDeleteIcon>
|
|
355
|
+
<IconButton
|
|
356
|
+
aria-label="delete"
|
|
357
|
+
onClick={onDelete}
|
|
358
|
+
size="large">
|
|
359
|
+
<ActionDelete />
|
|
360
|
+
</IconButton>
|
|
361
|
+
</StyledDeleteIcon>
|
|
362
|
+
</InputContainer>
|
|
363
|
+
</StyledDelete>
|
|
261
364
|
)}
|
|
262
|
-
</
|
|
263
|
-
</div>
|
|
365
|
+
</StyledTopRow>
|
|
264
366
|
);
|
|
265
367
|
}
|
|
266
368
|
}
|
|
267
369
|
|
|
268
|
-
|
|
269
|
-
index: {
|
|
270
|
-
padding: '24px 10px 0 0'
|
|
271
|
-
},
|
|
272
|
-
choiceConfiguration: {},
|
|
273
|
-
topRow: {
|
|
274
|
-
display: 'flex'
|
|
275
|
-
},
|
|
276
|
-
value: {
|
|
277
|
-
flex: '0.5',
|
|
278
|
-
paddingRight: theme.spacing.unit
|
|
279
|
-
},
|
|
280
|
-
editorHolder: {
|
|
281
|
-
marginTop: theme.spacing.unit * 2
|
|
282
|
-
},
|
|
283
|
-
toggle: {
|
|
284
|
-
flex: '0 1 auto'
|
|
285
|
-
},
|
|
286
|
-
feedback: {
|
|
287
|
-
flex: '0 1 auto',
|
|
288
|
-
paddingTop: theme.spacing.unit,
|
|
289
|
-
paddingLeft: 0,
|
|
290
|
-
marginLeft: 0,
|
|
291
|
-
paddingRight: theme.spacing.unit * 3
|
|
292
|
-
},
|
|
293
|
-
feedbackIcon: {
|
|
294
|
-
margin: 0,
|
|
295
|
-
paddingLeft: 0,
|
|
296
|
-
width: 'inherit'
|
|
297
|
-
},
|
|
298
|
-
deleteIcon: {
|
|
299
|
-
margin: 0,
|
|
300
|
-
width: 'inherit'
|
|
301
|
-
},
|
|
302
|
-
delete: {
|
|
303
|
-
flex: '0 1 auto',
|
|
304
|
-
paddingTop: theme.spacing.unit,
|
|
305
|
-
paddingLeft: 0,
|
|
306
|
-
marginLeft: 0
|
|
307
|
-
},
|
|
308
|
-
middleColumn: {
|
|
309
|
-
display: 'flex',
|
|
310
|
-
flex: 1,
|
|
311
|
-
flexDirection: 'column'
|
|
312
|
-
},
|
|
313
|
-
errorText: {
|
|
314
|
-
fontSize: '12px',
|
|
315
|
-
color: 'red'
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
export default withStyles(styles)(ChoiceConfiguration);
|
|
370
|
+
export default ChoiceConfiguration;
|
package/src/choice-utils.js
CHANGED
|
@@ -5,7 +5,7 @@ import includes from 'lodash/includes';
|
|
|
5
5
|
* @param {Object} model the model to normalize
|
|
6
6
|
* @return {Object} the updated model
|
|
7
7
|
*/
|
|
8
|
-
export const normalizeChoices = model => {
|
|
8
|
+
export const normalizeChoices = (model) => {
|
|
9
9
|
const choices = model.choices.map((c, index) => {
|
|
10
10
|
if (!c.value) {
|
|
11
11
|
c.value = `${index}`;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, within } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { FeedbackConfig, buildDefaults } from '../index';
|
|
5
|
+
|
|
6
|
+
// Mock FeedbackSelector to simplify testing
|
|
7
|
+
jest.mock('../feedback-selector', () => {
|
|
8
|
+
return function FeedbackSelector({ label, feedback, onChange }) {
|
|
9
|
+
return (
|
|
10
|
+
<div data-testid="feedback-selector">
|
|
11
|
+
<label>{label}</label>
|
|
12
|
+
<select
|
|
13
|
+
aria-label={label}
|
|
14
|
+
value={feedback.type}
|
|
15
|
+
onChange={(e) => onChange({ ...feedback, type: e.target.value })}
|
|
16
|
+
>
|
|
17
|
+
<option value="default">Default</option>
|
|
18
|
+
<option value="custom">Custom</option>
|
|
19
|
+
<option value="none">None</option>
|
|
20
|
+
</select>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('FeedbackConfig', () => {
|
|
27
|
+
const onChange = jest.fn();
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
onChange.mockClear();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('rendering', () => {
|
|
34
|
+
it('renders with default feedback types', () => {
|
|
35
|
+
const feedback = buildDefaults();
|
|
36
|
+
|
|
37
|
+
render(<FeedbackConfig feedback={feedback} onChange={onChange} />);
|
|
38
|
+
|
|
39
|
+
expect(screen.getByText('Feedback')).toBeInTheDocument();
|
|
40
|
+
expect(screen.getByText('If correct, show')).toBeInTheDocument();
|
|
41
|
+
expect(screen.getByText('If partially correct, show')).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByText('If incorrect, show')).toBeInTheDocument();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('renders all three feedback selectors by default', () => {
|
|
46
|
+
const feedback = buildDefaults();
|
|
47
|
+
|
|
48
|
+
render(<FeedbackConfig feedback={feedback} onChange={onChange} />);
|
|
49
|
+
|
|
50
|
+
const selectors = screen.getAllByTestId('feedback-selector');
|
|
51
|
+
expect(selectors).toHaveLength(3);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('does not render partial feedback selector when allowPartial is false', () => {
|
|
55
|
+
const feedback = buildDefaults();
|
|
56
|
+
|
|
57
|
+
render(<FeedbackConfig allowPartial={false} feedback={feedback} onChange={onChange} />);
|
|
58
|
+
|
|
59
|
+
expect(screen.getByText('If correct, show')).toBeInTheDocument();
|
|
60
|
+
expect(screen.queryByText('If partially correct, show')).not.toBeInTheDocument();
|
|
61
|
+
expect(screen.getByText('If incorrect, show')).toBeInTheDocument();
|
|
62
|
+
|
|
63
|
+
const selectors = screen.getAllByTestId('feedback-selector');
|
|
64
|
+
expect(selectors).toHaveLength(2);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('user interactions', () => {
|
|
69
|
+
it('calls onChange when correct feedback type changes', async () => {
|
|
70
|
+
const user = userEvent.setup();
|
|
71
|
+
const feedback = buildDefaults();
|
|
72
|
+
|
|
73
|
+
render(<FeedbackConfig feedback={feedback} onChange={onChange} />);
|
|
74
|
+
|
|
75
|
+
const correctSelect = screen.getByLabelText('If correct, show');
|
|
76
|
+
await user.selectOptions(correctSelect, 'custom');
|
|
77
|
+
|
|
78
|
+
expect(onChange).toHaveBeenCalledWith(
|
|
79
|
+
expect.objectContaining({
|
|
80
|
+
correct: expect.objectContaining({ type: 'custom' }),
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('calls onChange when incorrect feedback type changes', async () => {
|
|
86
|
+
const user = userEvent.setup();
|
|
87
|
+
const feedback = buildDefaults();
|
|
88
|
+
|
|
89
|
+
render(<FeedbackConfig feedback={feedback} onChange={onChange} />);
|
|
90
|
+
|
|
91
|
+
const incorrectSelect = screen.getByLabelText('If incorrect, show');
|
|
92
|
+
await user.selectOptions(incorrectSelect, 'none');
|
|
93
|
+
|
|
94
|
+
expect(onChange).toHaveBeenCalledWith(
|
|
95
|
+
expect.objectContaining({
|
|
96
|
+
incorrect: expect.objectContaining({ type: 'none' }),
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('calls onChange when partial feedback type changes', async () => {
|
|
102
|
+
const user = userEvent.setup();
|
|
103
|
+
const feedback = buildDefaults();
|
|
104
|
+
|
|
105
|
+
render(<FeedbackConfig feedback={feedback} onChange={onChange} />);
|
|
106
|
+
|
|
107
|
+
const partialSelect = screen.getByLabelText('If partially correct, show');
|
|
108
|
+
await user.selectOptions(partialSelect, 'custom');
|
|
109
|
+
|
|
110
|
+
expect(onChange).toHaveBeenCalledWith(
|
|
111
|
+
expect.objectContaining({
|
|
112
|
+
partial: expect.objectContaining({ type: 'custom' }),
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('buildDefaults helper', () => {
|
|
119
|
+
it('returns default feedback configuration', () => {
|
|
120
|
+
const defaults = buildDefaults();
|
|
121
|
+
|
|
122
|
+
expect(defaults).toEqual({
|
|
123
|
+
correct: { type: 'default', default: 'Correct' },
|
|
124
|
+
incorrect: { type: 'default', default: 'Incorrect' },
|
|
125
|
+
partial: { type: 'default', default: 'Nearly' },
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('merges custom values with defaults', () => {
|
|
130
|
+
const defaults = buildDefaults({
|
|
131
|
+
correct: { type: 'custom', custom: 'Great job!' },
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(defaults).toEqual({
|
|
135
|
+
correct: { type: 'custom', default: 'Correct', custom: 'Great job!' },
|
|
136
|
+
incorrect: { type: 'default', default: 'Incorrect' },
|
|
137
|
+
partial: { type: 'default', default: 'Nearly' },
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|