@pie-lib/config-ui 12.0.0-beta.5 → 12.1.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.
Files changed (139) hide show
  1. package/CHANGELOG.json +8 -1653
  2. package/CHANGELOG.md +345 -4
  3. package/LICENSE.md +5 -0
  4. package/NEXT.CHANGELOG.json +1 -0
  5. package/lib/__tests__/alert-dialog.test.js +262 -0
  6. package/lib/__tests__/checkbox.test.js +227 -0
  7. package/lib/__tests__/choice-utils.test.js +14 -0
  8. package/lib/__tests__/form-section.test.js +252 -0
  9. package/lib/__tests__/help.test.js +270 -0
  10. package/lib/__tests__/input.test.js +268 -0
  11. package/lib/__tests__/langs.test.js +541 -0
  12. package/lib/__tests__/number-text-field-custom.test.js +362 -0
  13. package/lib/__tests__/number-text-field.test.js +421 -0
  14. package/lib/__tests__/radio-with-label.test.js +233 -0
  15. package/lib/__tests__/settings-panel.test.js +184 -0
  16. package/lib/__tests__/settings.test.js +653 -0
  17. package/lib/__tests__/tabs.test.js +211 -0
  18. package/lib/__tests__/two-choice.test.js +124 -0
  19. package/lib/__tests__/with-stateful-model.test.js +221 -0
  20. package/lib/alert-dialog.js +41 -11
  21. package/lib/alert-dialog.js.map +1 -1
  22. package/lib/checkbox.js +59 -49
  23. package/lib/checkbox.js.map +1 -1
  24. package/lib/choice-configuration/__tests__/feedback-menu.test.js +287 -0
  25. package/lib/choice-configuration/__tests__/index.test.js +253 -0
  26. package/lib/choice-configuration/feedback-menu.js +25 -27
  27. package/lib/choice-configuration/feedback-menu.js.map +1 -1
  28. package/lib/choice-configuration/index.js +183 -186
  29. package/lib/choice-configuration/index.js.map +1 -1
  30. package/lib/choice-utils.js +6 -8
  31. package/lib/choice-utils.js.map +1 -1
  32. package/lib/feedback-config/__tests__/feedback-config.test.js +201 -0
  33. package/lib/feedback-config/__tests__/feedback-selector.test.js +177 -0
  34. package/lib/feedback-config/feedback-selector.js +70 -74
  35. package/lib/feedback-config/feedback-selector.js.map +1 -1
  36. package/lib/feedback-config/group.js +23 -26
  37. package/lib/feedback-config/group.js.map +1 -1
  38. package/lib/feedback-config/index.js +42 -45
  39. package/lib/feedback-config/index.js.map +1 -1
  40. package/lib/form-section.js +32 -26
  41. package/lib/form-section.js.map +1 -1
  42. package/lib/help.js +38 -48
  43. package/lib/help.js.map +1 -1
  44. package/lib/index.js +2 -3
  45. package/lib/index.js.map +1 -1
  46. package/lib/input.js +13 -18
  47. package/lib/input.js.map +1 -1
  48. package/lib/inputs.js +59 -68
  49. package/lib/inputs.js.map +1 -1
  50. package/lib/langs.js +57 -71
  51. package/lib/langs.js.map +1 -1
  52. package/lib/layout/__tests__/config.layout.test.js +70 -0
  53. package/lib/layout/__tests__/layout-content.test.js +6 -0
  54. package/lib/layout/config-layout.js +79 -48
  55. package/lib/layout/config-layout.js.map +1 -1
  56. package/lib/layout/index.js +1 -1
  57. package/lib/layout/index.js.map +1 -1
  58. package/lib/layout/layout-contents.js +59 -61
  59. package/lib/layout/layout-contents.js.map +1 -1
  60. package/lib/layout/settings-box.js +26 -34
  61. package/lib/layout/settings-box.js.map +1 -1
  62. package/lib/mui-box/index.js +42 -51
  63. package/lib/mui-box/index.js.map +1 -1
  64. package/lib/number-text-field-custom.js +152 -90
  65. package/lib/number-text-field-custom.js.map +1 -1
  66. package/lib/number-text-field.js +75 -64
  67. package/lib/number-text-field.js.map +1 -1
  68. package/lib/radio-with-label.js +31 -17
  69. package/lib/radio-with-label.js.map +1 -1
  70. package/lib/settings/display-size.js +17 -21
  71. package/lib/settings/display-size.js.map +1 -1
  72. package/lib/settings/index.js +14 -20
  73. package/lib/settings/index.js.map +1 -1
  74. package/lib/settings/panel.js +141 -142
  75. package/lib/settings/panel.js.map +1 -1
  76. package/lib/settings/settings-radio-label.js +30 -17
  77. package/lib/settings/settings-radio-label.js.map +1 -1
  78. package/lib/settings/toggle.js +40 -26
  79. package/lib/settings/toggle.js.map +1 -1
  80. package/lib/tabs/index.js +19 -31
  81. package/lib/tabs/index.js.map +1 -1
  82. package/lib/tags-input/__tests__/index.test.js +183 -0
  83. package/lib/tags-input/index.js +50 -62
  84. package/lib/tags-input/index.js.map +1 -1
  85. package/lib/two-choice.js +34 -44
  86. package/lib/two-choice.js.map +1 -1
  87. package/lib/with-stateful-model.js +9 -13
  88. package/lib/with-stateful-model.js.map +1 -1
  89. package/package.json +14 -11
  90. package/src/__tests__/alert-dialog.test.jsx +283 -0
  91. package/src/__tests__/checkbox.test.jsx +249 -0
  92. package/src/__tests__/choice-utils.test.js +12 -0
  93. package/src/__tests__/form-section.test.jsx +334 -0
  94. package/src/__tests__/help.test.jsx +184 -0
  95. package/src/__tests__/input.test.jsx +192 -0
  96. package/src/__tests__/langs.test.jsx +457 -0
  97. package/src/__tests__/number-text-field-custom.test.jsx +438 -0
  98. package/src/__tests__/number-text-field.test.jsx +341 -0
  99. package/src/__tests__/radio-with-label.test.jsx +259 -0
  100. package/src/__tests__/settings-panel.test.js +187 -0
  101. package/src/__tests__/settings.test.jsx +515 -0
  102. package/src/__tests__/tabs.test.jsx +193 -0
  103. package/src/__tests__/two-choice.test.js +110 -0
  104. package/src/__tests__/with-stateful-model.test.jsx +145 -0
  105. package/src/alert-dialog.jsx +30 -8
  106. package/src/checkbox.jsx +43 -37
  107. package/src/choice-configuration/__tests__/feedback-menu.test.jsx +163 -0
  108. package/src/choice-configuration/__tests__/index.test.jsx +234 -0
  109. package/src/choice-configuration/feedback-menu.jsx +6 -6
  110. package/src/choice-configuration/index.jsx +208 -199
  111. package/src/feedback-config/__tests__/feedback-config.test.jsx +141 -0
  112. package/src/feedback-config/__tests__/feedback-selector.test.jsx +97 -0
  113. package/src/feedback-config/feedback-selector.jsx +50 -55
  114. package/src/feedback-config/group.jsx +21 -22
  115. package/src/feedback-config/index.jsx +27 -29
  116. package/src/form-section.jsx +26 -18
  117. package/src/help.jsx +20 -28
  118. package/src/input.jsx +1 -1
  119. package/src/inputs.jsx +35 -44
  120. package/src/langs.jsx +41 -46
  121. package/src/layout/__tests__/config.layout.test.jsx +59 -0
  122. package/src/layout/__tests__/layout-content.test.jsx +3 -0
  123. package/src/layout/config-layout.jsx +53 -23
  124. package/src/layout/layout-contents.jsx +38 -40
  125. package/src/layout/settings-box.jsx +16 -19
  126. package/src/mui-box/index.jsx +35 -43
  127. package/src/number-text-field-custom.jsx +117 -65
  128. package/src/number-text-field.jsx +51 -34
  129. package/src/radio-with-label.jsx +26 -10
  130. package/src/settings/display-size.jsx +12 -11
  131. package/src/settings/index.js +2 -1
  132. package/src/settings/panel.jsx +101 -92
  133. package/src/settings/settings-radio-label.jsx +26 -10
  134. package/src/settings/toggle.jsx +37 -18
  135. package/src/tabs/index.jsx +8 -8
  136. package/src/tags-input/__tests__/index.test.jsx +113 -0
  137. package/src/tags-input/index.jsx +35 -38
  138. package/src/two-choice.jsx +15 -19
  139. package/README.md +0 -12
@@ -1,115 +1,176 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { withStyles } from '@material-ui/core/styles';
4
- import TextField from '@material-ui/core/TextField';
5
- import classNames from 'classnames';
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-tip-tap';
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
- disableImageAlignmentButtons,
28
- disabled,
29
- spellCheck,
30
- nonEmpty,
31
- toolbarOpts,
32
- error,
33
- maxImageWidth,
34
- maxImageHeight,
35
- uploadSoundSupport,
36
- mathMlOptions = {},
37
- }) => {
38
- const names = classNames(classes.labelContainer, className);
39
12
 
40
- return (
41
- <InputContainer label={label} className={names}>
42
- <div className={classes.editorHolder}>
43
- <EditableHtml
44
- markup={value || ''}
45
- disabled={disabled}
46
- spellCheck={spellCheck}
47
- nonEmpty={nonEmpty}
48
- onChange={onChange}
49
- imageSupport={imageSupport}
50
- disableImageAlignmentButtons={disableImageAlignmentButtons}
51
- className={classes.editor}
52
- toolbarOpts={toolbarOpts}
53
- error={error}
54
- maxImageWidth={maxImageWidth}
55
- maxImageHeight={maxImageHeight}
56
- uploadSoundSupport={uploadSoundSupport}
57
- languageCharactersProps={[{ language: 'spanish' }, { language: 'special' }]}
58
- mathMlOptions={mathMlOptions}
59
- />
60
- </div>
61
- </InputContainer>
62
- );
63
- },
64
- );
65
-
66
- const Feedback = withStyles((theme) => ({
67
- text: {
68
- width: '100%',
69
- },
70
- feedbackContainer: {
71
- position: 'relative',
72
- },
73
- arrowIcon: {
74
- fill: theme.palette.grey[400],
75
- left: -56,
76
- position: 'absolute',
77
- top: 20,
78
- },
79
- }))(({ value, onChange, type, correct, classes, defaults, toolbarOpts, mathMlOptions = {} }) => {
13
+ // - mathquill error window not defined
14
+ let EditableHtml;
15
+ if (typeof window !== 'undefined') {
16
+ EditableHtml = require('@pie-lib/editable-html-tip-tap')['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
+ return (
42
+ <InputContainer label={label} className={className}>
43
+ <StyledEditorHolder>
44
+ <EditableHtml
45
+ markup={value || ''}
46
+ disabled={disabled}
47
+ spellCheck={spellCheck}
48
+ nonEmpty={nonEmpty}
49
+ onChange={onChange}
50
+ imageSupport={imageSupport}
51
+ disableImageAlignmentButtons={disableImageAlignmentButtons}
52
+ pluginProps={pluginOpts || {}}
53
+ toolbarOpts={toolbarOpts}
54
+ error={error}
55
+ maxImageWidth={maxImageWidth}
56
+ maxImageHeight={maxImageHeight}
57
+ uploadSoundSupport={uploadSoundSupport}
58
+ languageCharactersProps={[{ language: 'spanish' }, { language: 'special' }]}
59
+ mathMlOptions={mathMlOptions}
60
+ />
61
+ </StyledEditorHolder>
62
+ </InputContainer>
63
+ );
64
+ };
65
+
66
+ const StyledFeedbackContainer = styled('div')(() => ({
67
+ position: 'relative',
68
+ }));
69
+
70
+ const StyledArrowIcon = styled(ArrowRight)(({ theme }) => ({
71
+ fill: theme.palette.grey[400],
72
+ left: -56,
73
+ position: 'absolute',
74
+ top: 40,
75
+ }));
76
+
77
+ const StyledTextField = styled(TextField)(({ theme }) => ({
78
+ width: '100%',
79
+ marginTop: theme.spacing(2),
80
+ }));
81
+
82
+ const StyledEditableHtmlContainer = styled(EditableHtmlContainer)(({ theme }) => ({
83
+ width: '100%',
84
+ marginTop: theme.spacing(2),
85
+ }));
86
+
87
+ const Feedback = ({ value, onChange, type, correct, defaults, toolbarOpts, mathMlOptions = {} }) => {
80
88
  if (!type || type === 'none') {
81
89
  return null;
82
90
  } else if (type === 'default') {
83
91
  return (
84
- <div className={classes.feedbackContainer}>
85
- <ArrowRight className={classes.arrowIcon} />
86
- <TextField
87
- className={classes.text}
92
+ <StyledFeedbackContainer>
93
+ <StyledArrowIcon />
94
+ <StyledTextField
88
95
  label="Feedback Text"
89
96
  value={correct ? defaults.correct : defaults.incorrect}
97
+ variant="standard"
90
98
  />
91
- </div>
99
+ </StyledFeedbackContainer>
92
100
  );
93
101
  } else {
94
102
  return (
95
- <div className={classes.feedbackContainer}>
96
- <ArrowRight className={classes.arrowIcon} />
97
- <EditableHtmlContainer
98
- className={classes.text}
103
+ <StyledFeedbackContainer>
104
+ <StyledArrowIcon />
105
+ <StyledEditableHtmlContainer
99
106
  label="Feedback Text"
100
107
  value={value}
101
108
  onChange={onChange}
102
109
  toolbarOpts={toolbarOpts}
103
110
  mathMlOptions={mathMlOptions}
104
111
  />
105
- </div>
112
+ </StyledFeedbackContainer>
106
113
  );
107
114
  }
108
- });
115
+ };
116
+
117
+ const StyledIndex = styled('span')(({ theme }) => ({
118
+ paddingRight: theme.spacing(1),
119
+ paddingTop: theme.spacing(3),
120
+ }));
121
+
122
+ const StyledTopRow = styled('div')(() => ({
123
+ display: 'flex',
124
+ alignItems: 'center',
125
+ }));
126
+
127
+ const StyledToggle = styled('div')(({ theme }) => ({
128
+ flex: '0 1 auto',
129
+ paddingTop: theme.spacing(0.5),
130
+ paddingBottom: 0,
131
+ marginRight: 0,
132
+ marginLeft: theme.spacing(1),
133
+ }));
134
+
135
+ const StyledFeedback = styled('div')(({ theme }) => ({
136
+ flex: '0 1 auto',
137
+ paddingTop: theme.spacing(2),
138
+ paddingLeft: 0,
139
+ marginLeft: 0,
140
+ marginRight: theme.spacing(1),
141
+ }));
142
+
143
+ const StyledFeedbackIcon = styled('div')(() => ({
144
+ margin: 0,
145
+ width: 'inherit',
146
+ }));
147
+
148
+ const StyledDeleteIcon = styled('div')(() => ({
149
+ margin: 0,
150
+ width: 'inherit',
151
+ }));
152
+
153
+ const StyledDelete = styled('div')(({ theme }) => ({
154
+ flex: '0 1 auto',
155
+ paddingTop: theme.spacing(2),
156
+ paddingLeft: 0,
157
+ marginLeft: 0,
158
+ }));
159
+
160
+ const StyledMiddleColumn = styled('div')(({ theme }) => ({
161
+ display: 'flex',
162
+ flex: 1,
163
+ flexDirection: 'column',
164
+ marginRight: theme.spacing(1),
165
+ }));
166
+
167
+ const StyledErrorText = styled('div')(({ theme }) => ({
168
+ fontSize: theme.typography.fontSize - 2,
169
+ color: theme.palette.error.main,
170
+ }));
109
171
 
110
172
  export class ChoiceConfiguration extends React.Component {
111
173
  static propTypes = {
112
- classes: PropTypes.object.isRequired,
113
174
  noLabels: PropTypes.bool,
114
175
  useLetterOrdering: PropTypes.bool,
115
176
  className: PropTypes.string,
@@ -139,8 +200,11 @@ export class ChoiceConfiguration extends React.Component {
139
200
  allowDelete: PropTypes.bool,
140
201
  noCorrectAnswerError: PropTypes.string,
141
202
  spellCheck: PropTypes.bool,
203
+ pluginOpts: PropTypes.object,
142
204
  toolbarOpts: PropTypes.object,
143
205
  uploadSoundSupport: PropTypes.object,
206
+ maxImageWidth: PropTypes.number,
207
+ maxImageHeight: PropTypes.number,
144
208
  };
145
209
 
146
210
  static defaultProps = {
@@ -196,7 +260,6 @@ export class ChoiceConfiguration extends React.Component {
196
260
  render() {
197
261
  const {
198
262
  data,
199
- classes,
200
263
  mode,
201
264
  onDelete,
202
265
  defaultFeedback,
@@ -211,144 +274,90 @@ export class ChoiceConfiguration extends React.Component {
211
274
  nonEmpty,
212
275
  allowFeedBack,
213
276
  allowDelete,
277
+ pluginOpts,
214
278
  toolbarOpts,
215
279
  error,
216
280
  noCorrectAnswerError,
217
281
  uploadSoundSupport,
282
+ maxImageWidth,
283
+ maxImageHeight,
218
284
  mathMlOptions = {},
219
285
  } = this.props;
220
286
 
221
287
  const InputToggle = mode === 'checkbox' ? InputCheckbox : InputRadio;
222
- const names = classNames(classes.choiceConfiguration, className);
223
288
 
224
289
  return (
225
- <div className={names}>
226
- <div className={classes.topRow}>
227
- {index > 0 && (
228
- <span className={classes.index} type="title">
229
- {useLetterOrdering ? String.fromCharCode(96 + index).toUpperCase() : index}
230
- </span>
231
- )}
290
+ <StyledTopRow>
291
+ {index > 0 && (
292
+ <StyledIndex type="title">
293
+ {useLetterOrdering ? String.fromCharCode(96 + index).toUpperCase() : index}
294
+ </StyledIndex>
295
+ )}
232
296
 
297
+ <StyledToggle>
233
298
  <InputToggle
234
- className={classes.toggle}
235
299
  onChange={this.onCheckedChange}
236
300
  label={!noLabels ? 'Correct' : ''}
237
301
  checked={!!data.correct}
238
302
  error={noCorrectAnswerError}
239
303
  />
304
+ </StyledToggle>
240
305
 
241
- <div className={classes.middleColumn}>
242
- <EditableHtmlContainer
243
- className={classes.input}
244
- label={!noLabels ? 'Label' : ''}
245
- value={data.label}
246
- onChange={this.onLabelChange}
247
- imageSupport={imageSupport}
248
- disableImageAlignmentButtons={disableImageAlignmentButtons}
249
- disabled={disabled}
250
- spellCheck={spellCheck}
251
- nonEmpty={nonEmpty}
306
+ <StyledMiddleColumn>
307
+ <EditableHtmlContainer
308
+ label={!noLabels ? 'Label' : ''}
309
+ value={data.label}
310
+ onChange={this.onLabelChange}
311
+ imageSupport={imageSupport}
312
+ disableImageAlignmentButtons={disableImageAlignmentButtons}
313
+ disabled={disabled}
314
+ spellCheck={spellCheck}
315
+ nonEmpty={nonEmpty}
316
+ pluginOpts={pluginOpts}
317
+ toolbarOpts={toolbarOpts}
318
+ error={error}
319
+ uploadSoundSupport={uploadSoundSupport}
320
+ mathMlOptions={mathMlOptions}
321
+ maxImageWidth={maxImageWidth}
322
+ maxImageHeight={maxImageHeight}
323
+ />
324
+ {error && <StyledErrorText>{error}</StyledErrorText>}
325
+
326
+ {allowFeedBack && (
327
+ <Feedback
328
+ {...data.feedback}
329
+ correct={data.correct}
330
+ defaults={defaultFeedback}
331
+ onChange={this.onFeedbackValueChange}
252
332
  toolbarOpts={toolbarOpts}
253
- error={error}
254
- uploadSoundSupport={uploadSoundSupport}
255
- mathMlOptions={mathMlOptions}
256
333
  />
257
- {error && <div className={classes.errorText}>{error}</div>}
258
-
259
- {allowFeedBack && (
260
- <Feedback
261
- {...data.feedback}
262
- correct={data.correct}
263
- defaults={defaultFeedback}
264
- onChange={this.onFeedbackValueChange}
265
- toolbarOpts={toolbarOpts}
266
- />
267
- )}
268
- </div>
334
+ )}
335
+ </StyledMiddleColumn>
269
336
 
270
- {allowFeedBack && (
271
- <InputContainer className={classes.feedback} label={!noLabels ? 'Feedback' : ''}>
272
- <FeedbackMenu
273
- onChange={this.onFeedbackTypeChange}
274
- value={data.feedback}
275
- classes={{
276
- icon: classes.feedbackIcon,
277
- }}
278
- />
337
+ {allowFeedBack && (
338
+ <StyledFeedback>
339
+ <InputContainer label={!noLabels ? 'Feedback' : ''}>
340
+ <StyledFeedbackIcon>
341
+ <FeedbackMenu onChange={this.onFeedbackTypeChange} value={data.feedback} />
342
+ </StyledFeedbackIcon>
279
343
  </InputContainer>
280
- )}
344
+ </StyledFeedback>
345
+ )}
281
346
 
282
- {allowDelete && (
283
- <InputContainer className={classes.delete} label={!noLabels ? 'Delete' : ''}>
284
- <IconButton aria-label="delete" className={classes.deleteIcon} onClick={onDelete}>
285
- <ActionDelete />
286
- </IconButton>
347
+ {allowDelete && (
348
+ <StyledDelete>
349
+ <InputContainer label={!noLabels ? 'Delete' : ''}>
350
+ <StyledDeleteIcon>
351
+ <IconButton aria-label="delete" onClick={onDelete} size="large">
352
+ <ActionDelete />
353
+ </IconButton>
354
+ </StyledDeleteIcon>
287
355
  </InputContainer>
288
- )}
289
- </div>
290
- </div>
356
+ </StyledDelete>
357
+ )}
358
+ </StyledTopRow>
291
359
  );
292
360
  }
293
361
  }
294
362
 
295
- const styles = (theme) => ({
296
- index: {
297
- paddingRight: theme.spacing.unit,
298
- paddingTop: theme.spacing.unit * 3.5,
299
- },
300
- choiceConfiguration: {},
301
- topRow: {
302
- display: 'flex',
303
- },
304
- value: {
305
- flex: '0.5',
306
- paddingRight: theme.spacing.unit,
307
- },
308
- editorHolder: {
309
- marginTop: theme.spacing.unit * 2,
310
- },
311
- toggle: {
312
- flex: '0 1 auto',
313
- paddingTop: theme.spacing.unit / 2,
314
- paddingBottom: 0,
315
- marginRight: 0,
316
- marginLeft: theme.spacing.unit,
317
- },
318
- feedback: {
319
- flex: '0 1 auto',
320
- paddingTop: theme.spacing.unit * 2,
321
- paddingLeft: 0,
322
- marginLeft: 0,
323
- marginRight: theme.spacing.unit,
324
- },
325
- feedbackIcon: {
326
- margin: 0,
327
- width: 'inherit',
328
- },
329
- deleteIcon: {
330
- margin: 0,
331
- width: 'inherit',
332
- },
333
- delete: {
334
- flex: '0 1 auto',
335
- paddingTop: theme.spacing.unit * 2,
336
- paddingLeft: 0,
337
- marginLeft: 0,
338
- },
339
- middleColumn: {
340
- display: 'flex',
341
- flex: 1,
342
- flexDirection: 'column',
343
- marginRight: theme.spacing.unit,
344
- },
345
- input: {
346
- marginRight: 0,
347
- },
348
- errorText: {
349
- fontSize: theme.typography.fontSize - 2,
350
- color: theme.palette.error.main,
351
- },
352
- });
353
-
354
- export default withStyles(styles)(ChoiceConfiguration);
363
+ export default ChoiceConfiguration;
@@ -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
+ });