@pie-lib/mask-markup 1.33.3-next.2 → 1.33.3-next.203

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 (61) hide show
  1. package/CHANGELOG.md +13 -67
  2. package/lib/choices/choice.js +98 -203
  3. package/lib/choices/choice.js.map +1 -1
  4. package/lib/choices/index.js +21 -53
  5. package/lib/choices/index.js.map +1 -1
  6. package/lib/componentize.js +1 -5
  7. package/lib/componentize.js.map +1 -1
  8. package/lib/components/blank.js +303 -361
  9. package/lib/components/blank.js.map +1 -1
  10. package/lib/components/correct-input.js +41 -65
  11. package/lib/components/correct-input.js.map +1 -1
  12. package/lib/components/dropdown.js +218 -257
  13. package/lib/components/dropdown.js.map +1 -1
  14. package/lib/components/input.js +10 -17
  15. package/lib/components/input.js.map +1 -1
  16. package/lib/constructed-response.js +40 -54
  17. package/lib/constructed-response.js.map +1 -1
  18. package/lib/customizable.js +5 -9
  19. package/lib/customizable.js.map +1 -1
  20. package/lib/drag-in-the-blank.js +140 -105
  21. package/lib/drag-in-the-blank.js.map +1 -1
  22. package/lib/index.js +0 -7
  23. package/lib/index.js.map +1 -1
  24. package/lib/inline-dropdown.js +4 -12
  25. package/lib/inline-dropdown.js.map +1 -1
  26. package/lib/mask.js +60 -118
  27. package/lib/mask.js.map +1 -1
  28. package/lib/serialization.js +8 -48
  29. package/lib/serialization.js.map +1 -1
  30. package/lib/with-mask.js +30 -58
  31. package/lib/with-mask.js.map +1 -1
  32. package/package.json +12 -20
  33. package/src/__tests__/drag-in-the-blank.test.js +66 -26
  34. package/src/__tests__/mask.test.js +147 -112
  35. package/src/__tests__/with-mask.test.js +44 -19
  36. package/src/choices/__tests__/index.test.js +38 -25
  37. package/src/choices/choice.jsx +86 -153
  38. package/src/choices/index.jsx +9 -3
  39. package/src/components/__tests__/blank.test.js +92 -156
  40. package/src/components/__tests__/correct-input.test.js +60 -19
  41. package/src/components/__tests__/dropdown.test.js +61 -19
  42. package/src/components/__tests__/input.test.js +72 -20
  43. package/src/components/blank.jsx +273 -272
  44. package/src/components/correct-input.jsx +33 -39
  45. package/src/components/dropdown.jsx +173 -161
  46. package/src/constructed-response.jsx +25 -30
  47. package/src/drag-in-the-blank.jsx +131 -42
  48. package/src/mask.jsx +38 -29
  49. package/src/with-mask.jsx +7 -4
  50. package/esm/index.css +0 -847
  51. package/esm/index.js +0 -195939
  52. package/esm/index.js.map +0 -1
  53. package/esm/package.json +0 -3
  54. package/src/__tests__/__snapshots__/drag-in-the-blank.test.js.snap +0 -316
  55. package/src/__tests__/__snapshots__/mask.test.js.snap +0 -55
  56. package/src/__tests__/__snapshots__/with-mask.test.js.snap +0 -62
  57. package/src/choices/__tests__/__snapshots__/index.test.js.snap +0 -209
  58. package/src/components/__tests__/__snapshots__/blank.test.js.snap +0 -111
  59. package/src/components/__tests__/__snapshots__/correct-input.test.js.snap +0 -64
  60. package/src/components/__tests__/__snapshots__/dropdown.test.js.snap +0 -136
  61. package/src/components/__tests__/__snapshots__/input.test.js.snap +0 -34
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
- import { shallow } from 'enzyme';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
3
4
  import { withMask } from '../with-mask';
4
5
 
5
6
  describe('WithMask', () => {
@@ -11,41 +12,65 @@ describe('WithMask', () => {
11
12
  },
12
13
  onChange,
13
14
  };
15
+
14
16
  const Masked = withMask('foo', (props) => (node) => {
15
17
  const dataset = node.data ? node.data.dataset || {} : {};
16
18
 
17
19
  if (dataset.component === 'foo') {
18
- return <input type="text" value="Foo" />;
20
+ return <input type="text" data-testid="masked-input" defaultValue="Foo" onChange={props.onChange} />;
19
21
  }
20
22
  });
21
23
 
22
- let wrapper;
23
-
24
24
  beforeEach(() => {
25
- wrapper = shallow(<Masked {...defaultProps} />);
25
+ onChange.mockClear();
26
26
  });
27
27
 
28
- describe('render', () => {
29
- it('renders correctly with default props', () => {
30
- expect(wrapper).toMatchSnapshot();
28
+ describe('rendering', () => {
29
+ it('renders with default props', () => {
30
+ const { container } = render(<Masked {...defaultProps} />);
31
+ expect(container.firstChild).toBeInTheDocument();
32
+ });
33
+
34
+ it('renders markup content', () => {
35
+ render(<Masked {...defaultProps} />);
36
+ expect(screen.getByText(/Foo bar/)).toBeInTheDocument();
37
+ });
38
+
39
+ it('renders paragraph content', () => {
40
+ const { container } = render(<Masked {...defaultProps} />);
41
+ // Paragraph is rendered as a styled div, not a <p> tag
42
+ expect(container.firstChild).toBeInTheDocument();
43
+ expect(screen.getByText(/Foo bar/)).toBeInTheDocument();
31
44
  });
32
45
  });
33
46
 
34
- describe('onChange', () => {
35
- const event = (value) => ({
36
- target: { value },
47
+ describe('onChange handler', () => {
48
+ it('calls onChange when value changes', async () => {
49
+ const user = userEvent.setup();
50
+ render(<Masked {...defaultProps} />);
51
+
52
+ const input = screen.queryByTestId('masked-input');
53
+ if (input) {
54
+ await user.clear(input);
55
+ await user.type(input, 'ceva');
56
+
57
+ expect(onChange).toHaveBeenCalled();
58
+ }
37
59
  });
38
60
 
39
- it('should call the function', () => {
40
- const e = event('ceva');
61
+ it('passes event to onChange', async () => {
62
+ const user = userEvent.setup();
63
+ render(<Masked {...defaultProps} />);
41
64
 
42
- wrapper.simulate('change', e);
65
+ const input = screen.queryByTestId('masked-input');
66
+ if (input) {
67
+ await user.clear(input);
68
+ await user.type(input, 'test');
43
69
 
44
- expect(onChange).toHaveBeenCalledWith({
45
- target: {
46
- value: 'ceva',
47
- },
48
- });
70
+ expect(onChange).toHaveBeenCalled();
71
+ const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0];
72
+ expect(lastCall).toHaveProperty('target');
73
+ }
49
74
  });
50
75
  });
51
76
  });
@@ -1,61 +1,74 @@
1
1
  import * as React from 'react';
2
- import { shallow } from 'enzyme';
3
- import { BlankContent as Choice } from '../choice';
2
+ import { render, screen } from '@testing-library/react';
3
+ import Choice from '../choice';
4
4
  import { choice } from '../../__tests__/utils';
5
5
  import Choices from '../index';
6
6
 
7
+ // Mock @dnd-kit hooks to avoid DndContext requirement
8
+ jest.mock('@dnd-kit/core', () => ({
9
+ useDraggable: jest.fn(() => ({
10
+ attributes: {},
11
+ listeners: {},
12
+ setNodeRef: jest.fn(),
13
+ isDragging: false,
14
+ })),
15
+ useDroppable: jest.fn(() => ({
16
+ setNodeRef: jest.fn(),
17
+ isOver: false,
18
+ active: null,
19
+ })),
20
+ }));
21
+
7
22
  describe('index', () => {
8
23
  describe('Choices', () => {
9
24
  const defaultProps = {
10
25
  disabled: false,
11
26
  choices: [choice('Jumped', '0'), choice('Laughed', '1'), choice('Spoon', '2')],
27
+ choicePosition: 'below',
28
+ instanceId: 'test-instance',
12
29
  };
13
- let wrapper;
14
-
15
- beforeEach(() => {
16
- wrapper = shallow(<Choices {...defaultProps} />);
17
- });
18
30
 
19
31
  it('renders correctly with default props', () => {
20
- expect(wrapper).toMatchSnapshot();
32
+ const { container } = render(<Choices {...defaultProps} />);
33
+ expect(container.firstChild).toBeInTheDocument();
34
+ expect(screen.getByText('Jumped')).toBeInTheDocument();
35
+ expect(screen.getByText('Laughed')).toBeInTheDocument();
36
+ expect(screen.getByText('Spoon')).toBeInTheDocument();
21
37
  });
22
38
 
23
39
  it('renders correctly with disabled prop as true', () => {
24
- wrapper.setProps({ disabled: true });
25
- expect(wrapper).toMatchSnapshot();
40
+ const { container } = render(<Choices {...defaultProps} disabled={true} />);
41
+ expect(container.firstChild).toBeInTheDocument();
26
42
  });
43
+
27
44
  it('renders without duplicates', () => {
28
- wrapper.setProps({ duplicates: undefined, value: { 0: '0', 1: '1' } });
29
- expect(wrapper).toMatchSnapshot();
45
+ const { container } = render(<Choices {...defaultProps} duplicates={undefined} value={{ 0: '0', 1: '1' }} />);
46
+ expect(container.firstChild).toBeInTheDocument();
30
47
  });
31
48
 
32
49
  it('renders with duplicates', () => {
33
- wrapper.setProps({ duplicates: true, value: { 0: '0', 1: '1' } });
34
- expect(wrapper).toMatchSnapshot();
50
+ const { container } = render(<Choices {...defaultProps} duplicates={true} value={{ 0: '0', 1: '1' }} />);
51
+ expect(container.firstChild).toBeInTheDocument();
35
52
  });
36
53
  });
37
54
 
38
55
  describe('Choice', () => {
39
56
  const defaultProps = {
40
57
  disabled: false,
41
- value: '1',
42
- label: 'Label',
43
- targetId: '1',
58
+ choice: choice('Label', '1'),
59
+ instanceId: 'test-instance',
44
60
  };
45
- let wrapper;
46
-
47
- beforeEach(() => {
48
- wrapper = shallow(<Choice {...defaultProps} />);
49
- });
50
61
 
51
62
  describe('render', () => {
52
63
  it('renders correctly with default props', () => {
53
- expect(wrapper).toMatchSnapshot();
64
+ const { container } = render(<Choice {...defaultProps} />);
65
+ expect(container.firstChild).toBeInTheDocument();
66
+ expect(screen.getByText('Label')).toBeInTheDocument();
54
67
  });
55
68
 
56
69
  it('renders correctly with disabled prop as true', () => {
57
- wrapper.setProps({ disabled: true });
58
- expect(wrapper).toMatchSnapshot();
70
+ const { container } = render(<Choice {...defaultProps} disabled={true} />);
71
+ expect(container.firstChild).toBeInTheDocument();
59
72
  });
60
73
  });
61
74
  });
@@ -1,164 +1,97 @@
1
- import React from 'react';
2
- import ReactDOM from 'react-dom';
1
+ import React, { useEffect, useRef } from 'react';
3
2
  import PropTypes from 'prop-types';
4
- import { withStyles } from '@material-ui/core/styles';
5
- import Chip from '@material-ui/core/Chip';
6
- import classnames from 'classnames';
7
-
3
+ import { useDraggable } from '@dnd-kit/core';
4
+ import { styled } from '@mui/material/styles';
5
+ import Chip from '@mui/material/Chip';
8
6
  import { renderMath } from '@pie-lib/math-rendering';
9
7
  import { color } from '@pie-lib/render-ui';
10
- import { DragSource } from '@pie-lib/drag';
11
8
 
12
9
  export const DRAG_TYPE = 'MaskBlank';
13
10
 
14
- class BlankContentComp extends React.Component {
15
- static propTypes = {
16
- disabled: PropTypes.bool,
17
- choice: PropTypes.object,
18
- classes: PropTypes.object,
19
- connectDragSource: PropTypes.func,
20
- };
21
-
22
- startDrag = () => {
23
- const { connectDragSource, disabled } = this.props;
24
- if (!disabled) {
25
- connectDragSource(this.dragContainerRef);
26
- }
27
- };
28
-
29
- // start drag after 500ms (touch and hold duration) for chromebooks and other touch devices PD-4888
30
- handleTouchStart = (e) => {
31
- e.preventDefault();
32
- this.longPressTimer = setTimeout(() => {
33
- this.startDrag(e);
34
- }, 500);
35
- };
36
-
37
- handleTouchEnd = () => {
38
- clearTimeout(this.longPressTimer);
39
- };
40
-
41
- handleTouchMove = () => {
42
- clearTimeout(this.longPressTimer);
43
- };
44
-
45
- componentDidMount() {
46
- if (this.dragContainerRef) {
47
- this.dragContainerRef.addEventListener('touchstart', this.handleTouchStart, { passive: false });
48
- this.dragContainerRef.addEventListener('touchend', this.handleTouchEnd);
49
- this.dragContainerRef.addEventListener('touchmove', this.handleTouchMove);
50
- }
51
- }
52
-
53
- componentWillUnmount() {
54
- if (this.dragContainerRef) {
55
- this.dragContainerRef.removeEventListener('touchstart', this.handleTouchStart);
56
- this.dragContainerRef.removeEventListener('touchend', this.handleTouchEnd);
57
- this.dragContainerRef.removeEventListener('touchmove', this.handleTouchMove);
58
- }
59
- }
60
-
61
- componentDidUpdate() {
62
- renderMath(this.rootRef);
63
- }
64
-
65
- render() {
66
- const { connectDragSource, choice, classes, disabled } = this.props;
67
-
68
- // TODO the Chip element is causing drag problems on touch devices. Avoid using Chip and consider refactoring the code. Keep in mind that Chip is a span with a button role, which interferes with seamless touch device dragging.
69
-
70
- return connectDragSource(
71
- <span
72
- className={classnames(classes.choice, disabled && classes.disabled)}
73
- ref={(ref) => {
74
- //eslint-disable-next-line
75
- this.dragContainerRef = ReactDOM.findDOMNode(ref);
76
- }}
77
- >
78
- <Chip
79
- clickable={false}
80
- disabled={true}
81
- ref={(ref) => {
82
- //eslint-disable-next-line
83
- this.rootRef = ReactDOM.findDOMNode(ref);
84
- }}
85
- className={classes.chip}
86
- label={
87
- <span
88
- className={classes.chipLabel}
89
- ref={(ref) => {
90
- if (ref) {
91
- ref.innerHTML = choice.value || ' ';
92
- }
93
- }}
94
- >
95
- {' '}
96
- </span>
97
- }
98
- variant={disabled ? 'outlined' : undefined}
99
- />
100
- </span>,
101
- {},
102
- );
103
- }
104
- }
105
-
106
- export const BlankContent = withStyles((theme) => ({
107
- choice: {
108
- border: `solid 0px ${theme.palette.primary.main}`,
109
- borderRadius: theme.spacing.unit * 2,
110
- margin: theme.spacing.unit / 2,
111
- transform: 'translate(0, 0)',
112
- },
113
- chip: {
114
- backgroundColor: color.white(),
115
- border: `1px solid ${color.text()}`,
116
- color: color.text(),
117
- alignItems: 'center',
118
- display: 'inline-flex',
119
- height: 'initial',
120
- minHeight: '32px',
121
- fontSize: 'inherit',
122
- whiteSpace: 'pre-wrap',
123
- maxWidth: '374px',
124
- // Added for touch devices, for image content.
125
- // This will prevent the context menu from appearing and not allowing other interactions with the image.
126
- // If interactions with the image in the token will be requested we should handle only the context Menu.
127
- pointerEvents: 'none',
128
- borderRadius: '3px',
129
- paddingTop: '12px',
130
- paddingBottom: '12px',
131
- },
132
- chipLabel: {
133
- whiteSpace: 'normal',
134
- '& img': {
135
- display: 'block',
136
- padding: '2px 0',
137
- },
138
- '& mjx-frac': {
139
- fontSize: '120% !important',
140
- },
11
+ const StyledChoice = styled('span')(({ theme, disabled }) => ({
12
+ border: `solid 0px ${theme.palette.primary.main}`,
13
+ borderRadius: theme.spacing(2),
14
+ margin: theme.spacing(0.5),
15
+ transform: 'translate(0, 0)',
16
+ display: 'inline-flex',
17
+ ...(disabled && {}),
18
+ }));
19
+
20
+ const StyledChip = styled(Chip)(() => ({
21
+ backgroundColor: color.white(),
22
+ border: `1px solid ${color.text()}`,
23
+ color: color.text(),
24
+ alignItems: 'center',
25
+ display: 'inline-flex',
26
+ height: 'initial',
27
+ minHeight: '32px',
28
+ fontSize: 'inherit',
29
+ whiteSpace: 'pre-wrap',
30
+ maxWidth: '374px',
31
+ // Added for touch devices, for image content.
32
+ // This will prevent the context menu from appearing and not allowing other interactions with the image.
33
+ // If interactions with the image in the token will be requested we should handle only the context Menu.
34
+ pointerEvents: 'none',
35
+ borderRadius: '3px',
36
+ paddingTop: '12px',
37
+ paddingBottom: '12px',
38
+
39
+ '&.Mui-disabled': {
40
+ opacity: 1,
141
41
  },
142
- disabled: {
143
- opacity: 0.6,
144
- },
145
- }))(BlankContentComp);
42
+ }));
146
43
 
147
- const tileSource = {
148
- canDrag(props) {
149
- return !props.disabled;
44
+ const StyledChipLabel = styled('span')(() => ({
45
+ whiteSpace: 'normal',
46
+ '& img': {
47
+ display: 'block',
48
+ padding: '2px 0',
150
49
  },
151
- beginDrag(props) {
152
- return {
153
- choice: props.choice,
154
- instanceId: props.instanceId,
155
- };
50
+ '& mjx-frac': {
51
+ fontSize: '120% !important',
156
52
  },
157
- };
158
-
159
- const DragDropTile = DragSource(DRAG_TYPE, tileSource, (connect, monitor) => ({
160
- connectDragSource: connect.dragSource(),
161
- isDragging: monitor.isDragging(),
162
- }))(BlankContent);
53
+ }));
54
+
55
+ export default function Choice({ choice, disabled, instanceId }) {
56
+ const rootRef = useRef(null);
57
+
58
+ const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
59
+ id: `choice-${choice.id}`,
60
+ data: { choice, instanceId, fromChoice: true, type: DRAG_TYPE },
61
+ disabled,
62
+ });
63
+
64
+ useEffect(() => {
65
+ renderMath(rootRef.current);
66
+ }, [choice.value]);
67
+
68
+ return (
69
+ <StyledChoice
70
+ ref={setNodeRef}
71
+ style={
72
+ isDragging
73
+ ? {
74
+ width: rootRef.current?.offsetWidth,
75
+ height: rootRef.current?.offsetHeight,
76
+ }
77
+ : {}
78
+ }
79
+ disabled={disabled}
80
+ {...listeners}
81
+ {...attributes}
82
+ >
83
+ <StyledChip
84
+ clickable={false}
85
+ disabled={disabled}
86
+ ref={rootRef}
87
+ label={<StyledChipLabel dangerouslySetInnerHTML={{ __html: choice.value }} />}
88
+ />
89
+ </StyledChoice>
90
+ );
91
+ }
163
92
 
164
- export default DragDropTile;
93
+ Choice.propTypes = {
94
+ choice: PropTypes.object.isRequired,
95
+ disabled: PropTypes.bool,
96
+ instanceId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
97
+ };
@@ -11,6 +11,7 @@ export default class Choices extends React.Component {
11
11
  choices: PropTypes.arrayOf(PropTypes.shape({ label: PropTypes.string, value: PropTypes.string })),
12
12
  value: PropTypes.object,
13
13
  choicePosition: PropTypes.string.isRequired,
14
+ instanceId: PropTypes.string, // Added for drag isolation
14
15
  };
15
16
 
16
17
  getStyleForWrapper = () => {
@@ -40,7 +41,7 @@ export default class Choices extends React.Component {
40
41
  };
41
42
 
42
43
  render() {
43
- const { disabled, duplicates, choices, value } = this.props;
44
+ const { disabled, duplicates, choices, value, instanceId } = this.props;
44
45
  const filteredChoices = choices.filter((c) => {
45
46
  if (duplicates === true) {
46
47
  return true;
@@ -52,9 +53,14 @@ export default class Choices extends React.Component {
52
53
 
53
54
  return (
54
55
  <div style={elementStyle}>
55
- <DragDroppablePlaceholder disabled={disabled}>
56
+ <DragDroppablePlaceholder disabled={disabled} instanceId={instanceId}>
56
57
  {filteredChoices.map((c, index) => (
57
- <Choice key={`${c.value}-${index}`} disabled={disabled} choice={c} />
58
+ <Choice
59
+ key={`${c.value}-${index}`}
60
+ disabled={disabled}
61
+ choice={c}
62
+ instanceId={instanceId}
63
+ />
58
64
  ))}
59
65
  </DragDroppablePlaceholder>
60
66
  </div>