@pie-lib/config-ui 11.30.3-next.2 → 11.30.3-next.205

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 (123) hide show
  1. package/CHANGELOG.md +19 -67
  2. package/lib/alert-dialog.js +35 -42
  3. package/lib/alert-dialog.js.map +1 -1
  4. package/lib/checkbox.js +56 -71
  5. package/lib/checkbox.js.map +1 -1
  6. package/lib/choice-configuration/feedback-menu.js +29 -64
  7. package/lib/choice-configuration/feedback-menu.js.map +1 -1
  8. package/lib/choice-configuration/index.js +202 -262
  9. package/lib/choice-configuration/index.js.map +1 -1
  10. package/lib/choice-utils.js +6 -18
  11. package/lib/choice-utils.js.map +1 -1
  12. package/lib/feedback-config/feedback-selector.js +79 -115
  13. package/lib/feedback-config/feedback-selector.js.map +1 -1
  14. package/lib/feedback-config/group.js +26 -40
  15. package/lib/feedback-config/group.js.map +1 -1
  16. package/lib/feedback-config/index.js +47 -90
  17. package/lib/feedback-config/index.js.map +1 -1
  18. package/lib/form-section.js +31 -33
  19. package/lib/form-section.js.map +1 -1
  20. package/lib/help.js +39 -80
  21. package/lib/help.js.map +1 -1
  22. package/lib/index.js +1 -31
  23. package/lib/index.js.map +1 -1
  24. package/lib/input.js +21 -54
  25. package/lib/input.js.map +1 -1
  26. package/lib/inputs.js +61 -95
  27. package/lib/inputs.js.map +1 -1
  28. package/lib/langs.js +58 -101
  29. package/lib/langs.js.map +1 -1
  30. package/lib/layout/config-layout.js +40 -70
  31. package/lib/layout/config-layout.js.map +1 -1
  32. package/lib/layout/index.js +0 -3
  33. package/lib/layout/index.js.map +1 -1
  34. package/lib/layout/layout-contents.js +72 -103
  35. package/lib/layout/layout-contents.js.map +1 -1
  36. package/lib/layout/settings-box.js +27 -56
  37. package/lib/layout/settings-box.js.map +1 -1
  38. package/lib/mui-box/index.js +41 -57
  39. package/lib/mui-box/index.js.map +1 -1
  40. package/lib/number-text-field-custom.js +79 -161
  41. package/lib/number-text-field-custom.js.map +1 -1
  42. package/lib/number-text-field.js +80 -114
  43. package/lib/number-text-field.js.map +1 -1
  44. package/lib/radio-with-label.js +30 -31
  45. package/lib/radio-with-label.js.map +1 -1
  46. package/lib/settings/display-size.js +16 -32
  47. package/lib/settings/display-size.js.map +1 -1
  48. package/lib/settings/index.js +14 -47
  49. package/lib/settings/index.js.map +1 -1
  50. package/lib/settings/panel.js +159 -229
  51. package/lib/settings/panel.js.map +1 -1
  52. package/lib/settings/settings-radio-label.js +28 -30
  53. package/lib/settings/settings-radio-label.js.map +1 -1
  54. package/lib/settings/toggle.js +35 -46
  55. package/lib/settings/toggle.js.map +1 -1
  56. package/lib/tabs/index.js +22 -57
  57. package/lib/tabs/index.js.map +1 -1
  58. package/lib/tags-input/index.js +50 -99
  59. package/lib/tags-input/index.js.map +1 -1
  60. package/lib/two-choice.js +46 -90
  61. package/lib/two-choice.js.map +1 -1
  62. package/lib/with-stateful-model.js +8 -31
  63. package/lib/with-stateful-model.js.map +1 -1
  64. package/package.json +12 -20
  65. package/src/__tests__/alert-dialog.test.jsx +283 -0
  66. package/src/__tests__/checkbox.test.jsx +249 -0
  67. package/src/__tests__/form-section.test.jsx +334 -0
  68. package/src/__tests__/help.test.jsx +184 -0
  69. package/src/__tests__/input.test.jsx +192 -0
  70. package/src/__tests__/langs.test.jsx +435 -15
  71. package/src/__tests__/number-text-field-custom.test.jsx +438 -0
  72. package/src/__tests__/number-text-field.test.jsx +295 -102
  73. package/src/__tests__/radio-with-label.test.jsx +259 -0
  74. package/src/__tests__/settings-panel.test.js +66 -83
  75. package/src/__tests__/settings.test.jsx +515 -0
  76. package/src/__tests__/tabs.test.jsx +193 -0
  77. package/src/__tests__/two-choice.test.js +104 -18
  78. package/src/__tests__/with-stateful-model.test.jsx +145 -0
  79. package/src/alert-dialog.jsx +21 -19
  80. package/src/checkbox.jsx +42 -46
  81. package/src/choice-configuration/__tests__/feedback-menu.test.jsx +157 -4
  82. package/src/choice-configuration/__tests__/index.test.jsx +198 -56
  83. package/src/choice-configuration/feedback-menu.jsx +6 -6
  84. package/src/choice-configuration/index.jsx +201 -203
  85. package/src/feedback-config/__tests__/feedback-config.test.jsx +130 -60
  86. package/src/feedback-config/__tests__/feedback-selector.test.jsx +81 -44
  87. package/src/feedback-config/feedback-selector.jsx +50 -55
  88. package/src/feedback-config/group.jsx +21 -22
  89. package/src/feedback-config/index.jsx +27 -29
  90. package/src/form-section.jsx +26 -18
  91. package/src/help.jsx +20 -28
  92. package/src/input.jsx +1 -1
  93. package/src/inputs.jsx +34 -50
  94. package/src/langs.jsx +41 -46
  95. package/src/layout/__tests__/config.layout.test.jsx +55 -38
  96. package/src/layout/config-layout.jsx +38 -32
  97. package/src/layout/layout-contents.jsx +38 -39
  98. package/src/layout/settings-box.jsx +16 -19
  99. package/src/mui-box/index.jsx +35 -43
  100. package/src/number-text-field-custom.jsx +30 -36
  101. package/src/number-text-field.jsx +45 -29
  102. package/src/radio-with-label.jsx +25 -13
  103. package/src/settings/display-size.jsx +12 -11
  104. package/src/settings/panel.jsx +97 -91
  105. package/src/settings/settings-radio-label.jsx +25 -13
  106. package/src/settings/toggle.jsx +30 -29
  107. package/src/tabs/index.jsx +8 -8
  108. package/src/tags-input/__tests__/index.test.jsx +88 -37
  109. package/src/tags-input/index.jsx +35 -38
  110. package/src/two-choice.jsx +15 -19
  111. package/esm/index.css +0 -847
  112. package/esm/index.js +0 -213950
  113. package/esm/index.js.map +0 -1
  114. package/esm/package.json +0 -3
  115. package/src/__tests__/__snapshots__/langs.test.jsx.snap +0 -32
  116. package/src/__tests__/__snapshots__/settings-panel.test.js.snap +0 -115
  117. package/src/__tests__/__snapshots__/two-choice.test.js.snap +0 -171
  118. package/src/choice-configuration/__tests__/__snapshots__/feedback-menu.test.jsx.snap +0 -51
  119. package/src/choice-configuration/__tests__/__snapshots__/index.test.jsx.snap +0 -519
  120. package/src/feedback-config/__tests__/__snapshots__/feedback-config.test.jsx.snap +0 -27
  121. package/src/feedback-config/__tests__/__snapshots__/feedback-selector.test.jsx.snap +0 -38
  122. package/src/layout/__tests__/__snapshots__/config.layout.test.jsx.snap +0 -59
  123. package/src/tags-input/__tests__/__snapshots__/index.test.jsx.snap +0 -170
@@ -1,117 +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 ActionDelete from '@material-ui/icons/Delete';
6
- import ArrowRight from '@material-ui/icons/SubdirectoryArrowRight';
7
- import IconButton from '@material-ui/core/IconButton';
8
- 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';
9
8
  import { InputContainer } from '@pie-lib/render-ui';
10
- import EditableHtml from '@pie-lib/editable-html';
9
+ // import EditableHtml from '@pie-lib/editable-html-tip-tap';
11
10
  import { InputCheckbox, InputRadio } from '../inputs';
12
11
  import FeedbackMenu from './feedback-menu';
13
12
 
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
- pluginOpts,
32
- toolbarOpts,
33
- error,
34
- maxImageWidth,
35
- maxImageHeight,
36
- uploadSoundSupport,
37
- mathMlOptions = {},
38
- }) => {
39
- const names = classNames(classes.labelContainer, className);
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
+ }
40
18
 
41
- return (
42
- <InputContainer label={label} className={names}>
43
- <div className={classes.editorHolder}>
44
- <EditableHtml
45
- markup={value || ''}
46
- disabled={disabled}
47
- spellCheck={spellCheck}
48
- nonEmpty={nonEmpty}
49
- onChange={onChange}
50
- imageSupport={imageSupport}
51
- disableImageAlignmentButtons={disableImageAlignmentButtons}
52
- className={classes.editor}
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
- </div>
63
- </InputContainer>
64
- );
65
- },
66
- );
67
-
68
- const Feedback = withStyles((theme) => ({
69
- text: {
70
- width: '100%',
71
- },
72
- feedbackContainer: {
73
- position: 'relative',
74
- },
75
- arrowIcon: {
76
- fill: theme.palette.grey[400],
77
- left: -56,
78
- position: 'absolute',
79
- top: 20,
80
- },
81
- }))(({ value, onChange, type, correct, classes, defaults, toolbarOpts, mathMlOptions = {} }) => {
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 = {} }) => {
82
88
  if (!type || type === 'none') {
83
89
  return null;
84
90
  } else if (type === 'default') {
85
91
  return (
86
- <div className={classes.feedbackContainer}>
87
- <ArrowRight className={classes.arrowIcon} />
88
- <TextField
89
- className={classes.text}
92
+ <StyledFeedbackContainer>
93
+ <StyledArrowIcon />
94
+ <StyledTextField
90
95
  label="Feedback Text"
91
96
  value={correct ? defaults.correct : defaults.incorrect}
97
+ variant="standard"
92
98
  />
93
- </div>
99
+ </StyledFeedbackContainer>
94
100
  );
95
101
  } else {
96
102
  return (
97
- <div className={classes.feedbackContainer}>
98
- <ArrowRight className={classes.arrowIcon} />
99
- <EditableHtmlContainer
100
- className={classes.text}
103
+ <StyledFeedbackContainer>
104
+ <StyledArrowIcon />
105
+ <StyledEditableHtmlContainer
101
106
  label="Feedback Text"
102
107
  value={value}
103
108
  onChange={onChange}
104
109
  toolbarOpts={toolbarOpts}
105
110
  mathMlOptions={mathMlOptions}
106
111
  />
107
- </div>
112
+ </StyledFeedbackContainer>
108
113
  );
109
114
  }
110
- });
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
+ }));
111
171
 
112
172
  export class ChoiceConfiguration extends React.Component {
113
173
  static propTypes = {
114
- classes: PropTypes.object.isRequired,
115
174
  noLabels: PropTypes.bool,
116
175
  useLetterOrdering: PropTypes.bool,
117
176
  className: PropTypes.string,
@@ -201,7 +260,6 @@ export class ChoiceConfiguration extends React.Component {
201
260
  render() {
202
261
  const {
203
262
  data,
204
- classes,
205
263
  mode,
206
264
  onDelete,
207
265
  defaultFeedback,
@@ -227,139 +285,79 @@ export class ChoiceConfiguration extends React.Component {
227
285
  } = this.props;
228
286
 
229
287
  const InputToggle = mode === 'checkbox' ? InputCheckbox : InputRadio;
230
- const names = classNames(classes.choiceConfiguration, className);
231
288
 
232
289
  return (
233
- <div className={names}>
234
- <div className={classes.topRow}>
235
- {index > 0 && (
236
- <span className={classes.index} type="title">
237
- {useLetterOrdering ? String.fromCharCode(96 + index).toUpperCase() : index}
238
- </span>
239
- )}
290
+ <StyledTopRow>
291
+ {index > 0 && (
292
+ <StyledIndex type="title">
293
+ {useLetterOrdering ? String.fromCharCode(96 + index).toUpperCase() : index}
294
+ </StyledIndex>
295
+ )}
240
296
 
297
+ <StyledToggle>
241
298
  <InputToggle
242
- className={classes.toggle}
243
299
  onChange={this.onCheckedChange}
244
300
  label={!noLabels ? 'Correct' : ''}
245
301
  checked={!!data.correct}
246
302
  error={noCorrectAnswerError}
247
303
  />
304
+ </StyledToggle>
305
+
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>}
248
325
 
249
- <div className={classes.middleColumn}>
250
- <EditableHtmlContainer
251
- className={classes.input}
252
- label={!noLabels ? 'Label' : ''}
253
- value={data.label}
254
- onChange={this.onLabelChange}
255
- imageSupport={imageSupport}
256
- disableImageAlignmentButtons={disableImageAlignmentButtons}
257
- disabled={disabled}
258
- spellCheck={spellCheck}
259
- nonEmpty={nonEmpty}
260
- pluginOpts={pluginOpts}
326
+ {allowFeedBack && (
327
+ <Feedback
328
+ {...data.feedback}
329
+ correct={data.correct}
330
+ defaults={defaultFeedback}
331
+ onChange={this.onFeedbackValueChange}
261
332
  toolbarOpts={toolbarOpts}
262
- error={error}
263
- uploadSoundSupport={uploadSoundSupport}
264
- mathMlOptions={mathMlOptions}
265
- maxImageWidth={maxImageWidth}
266
- maxImageHeight={maxImageHeight}
267
333
  />
268
- {error && <div className={classes.errorText}>{error}</div>}
269
-
270
- {allowFeedBack && (
271
- <Feedback
272
- {...data.feedback}
273
- correct={data.correct}
274
- defaults={defaultFeedback}
275
- onChange={this.onFeedbackValueChange}
276
- toolbarOpts={toolbarOpts}
277
- />
278
- )}
279
- </div>
334
+ )}
335
+ </StyledMiddleColumn>
280
336
 
281
- {allowFeedBack && (
282
- <InputContainer className={classes.feedback} label={!noLabels ? 'Feedback' : ''}>
283
- <FeedbackMenu
284
- onChange={this.onFeedbackTypeChange}
285
- value={data.feedback}
286
- classes={{
287
- icon: classes.feedbackIcon,
288
- }}
289
- />
337
+ {allowFeedBack && (
338
+ <StyledFeedback>
339
+ <InputContainer label={!noLabels ? 'Feedback' : ''}>
340
+ <StyledFeedbackIcon>
341
+ <FeedbackMenu onChange={this.onFeedbackTypeChange} value={data.feedback} />
342
+ </StyledFeedbackIcon>
290
343
  </InputContainer>
291
- )}
344
+ </StyledFeedback>
345
+ )}
292
346
 
293
- {allowDelete && (
294
- <InputContainer className={classes.delete} label={!noLabels ? 'Delete' : ''}>
295
- <IconButton aria-label="delete" className={classes.deleteIcon} onClick={onDelete}>
296
- <ActionDelete />
297
- </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>
298
355
  </InputContainer>
299
- )}
300
- </div>
301
- </div>
356
+ </StyledDelete>
357
+ )}
358
+ </StyledTopRow>
302
359
  );
303
360
  }
304
361
  }
305
362
 
306
- const styles = (theme) => ({
307
- index: {
308
- paddingRight: theme.spacing.unit,
309
- paddingTop: theme.spacing.unit * 3.5,
310
- },
311
- choiceConfiguration: {},
312
- topRow: {
313
- display: 'flex',
314
- },
315
- value: {
316
- flex: '0.5',
317
- paddingRight: theme.spacing.unit,
318
- },
319
- editorHolder: {
320
- marginTop: theme.spacing.unit * 2,
321
- },
322
- toggle: {
323
- flex: '0 1 auto',
324
- paddingTop: theme.spacing.unit / 2,
325
- paddingBottom: 0,
326
- marginRight: 0,
327
- marginLeft: theme.spacing.unit,
328
- },
329
- feedback: {
330
- flex: '0 1 auto',
331
- paddingTop: theme.spacing.unit * 2,
332
- paddingLeft: 0,
333
- marginLeft: 0,
334
- marginRight: theme.spacing.unit,
335
- },
336
- feedbackIcon: {
337
- margin: 0,
338
- width: 'inherit',
339
- },
340
- deleteIcon: {
341
- margin: 0,
342
- width: 'inherit',
343
- },
344
- delete: {
345
- flex: '0 1 auto',
346
- paddingTop: theme.spacing.unit * 2,
347
- paddingLeft: 0,
348
- marginLeft: 0,
349
- },
350
- middleColumn: {
351
- display: 'flex',
352
- flex: 1,
353
- flexDirection: 'column',
354
- marginRight: theme.spacing.unit,
355
- },
356
- input: {
357
- marginRight: 0,
358
- },
359
- errorText: {
360
- fontSize: theme.typography.fontSize - 2,
361
- color: theme.palette.error.main,
362
- },
363
- });
364
-
365
- export default withStyles(styles)(ChoiceConfiguration);
363
+ export default ChoiceConfiguration;
@@ -1,70 +1,140 @@
1
- import React, { PropTypes } from 'react';
2
- import Enzyme, { shallow, mount } from 'enzyme';
3
- import { FeedbackConfig } from '../index';
4
- import FeedbackSelector from '../feedback-selector';
5
- import Adapter from 'enzyme-adapter-react-16';
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';
6
5
 
7
- Enzyme.configure({ adapter: new Adapter() });
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
+ });
8
25
 
9
26
  describe('FeedbackConfig', () => {
10
- describe('render', () => {
11
- let component, selectors;
12
- let feedback = {
13
- correctFeedback: undefined,
14
- correctFeedbackType: 'default',
15
- incorrectFeedback: undefined,
16
- incorrectFeedbackType: 'default',
17
- };
18
-
19
- let defaults = {
20
- correct: 'Correct',
21
- incorrect: 'Incorrect',
22
- partial: 'Nearly',
23
- };
24
-
25
- describe('Feedback Config Component', () => {
26
- it('should exist', () => {
27
- component = shallow(
28
- <FeedbackConfig feedback={feedback} defaults={defaults} onChange={jest.fn()} classes={{}} />,
29
- );
30
-
31
- selectors = component.find(FeedbackSelector);
32
-
33
- expect(selectors.length).toEqual(3);
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' },
34
126
  });
127
+ });
35
128
 
36
- describe('props', () => {
37
- it('should not render optionally correct if optional is not needed', () => {
38
- component = shallow(
39
- <FeedbackConfig
40
- allowPartial={false}
41
- feedback={feedback}
42
- defaults={defaults}
43
- onChange={jest.fn()}
44
- classes={{}}
45
- />,
46
- );
47
-
48
- selectors = component.find(FeedbackSelector);
49
-
50
- expect(selectors.length).toEqual(2);
51
- });
129
+ it('merges custom values with defaults', () => {
130
+ const defaults = buildDefaults({
131
+ correct: { type: 'custom', custom: 'Great job!' },
52
132
  });
53
133
 
54
- describe('snapshot', () => {
55
- it('matches the snapshot', () => {
56
- component = shallow(
57
- <FeedbackConfig
58
- allowPartial={false}
59
- feedback={feedback}
60
- defaults={defaults}
61
- onChange={jest.fn()}
62
- classes={{}}
63
- />,
64
- );
65
-
66
- expect(component).toMatchSnapshot();
67
- });
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' },
68
138
  });
69
139
  });
70
140
  });