@pie-lib/mask-markup 2.0.0-beta.2 → 2.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.
Files changed (69) hide show
  1. package/CHANGELOG.json +1 -871
  2. package/CHANGELOG.md +296 -2
  3. package/LICENSE.md +5 -0
  4. package/NEXT.CHANGELOG.json +1 -0
  5. package/lib/choices/choice.js +99 -118
  6. package/lib/choices/choice.js.map +1 -1
  7. package/lib/choices/index.js +23 -19
  8. package/lib/choices/index.js.map +1 -1
  9. package/lib/componentize.js +1 -2
  10. package/lib/componentize.js.map +1 -1
  11. package/lib/components/blank.js +315 -221
  12. package/lib/components/blank.js.map +1 -1
  13. package/lib/components/correct-input.js +39 -42
  14. package/lib/components/correct-input.js.map +1 -1
  15. package/lib/components/dropdown.js +393 -124
  16. package/lib/components/dropdown.js.map +1 -1
  17. package/lib/components/input.js +1 -2
  18. package/lib/components/input.js.map +1 -1
  19. package/lib/constructed-response.js +82 -26
  20. package/lib/constructed-response.js.map +1 -1
  21. package/lib/customizable.js +44 -0
  22. package/lib/customizable.js.map +1 -0
  23. package/lib/drag-in-the-blank.js +154 -61
  24. package/lib/drag-in-the-blank.js.map +1 -1
  25. package/lib/index.js +7 -0
  26. package/lib/index.js.map +1 -1
  27. package/lib/inline-dropdown.js +4 -3
  28. package/lib/inline-dropdown.js.map +1 -1
  29. package/lib/mask.js +89 -56
  30. package/lib/mask.js.map +1 -1
  31. package/lib/serialization.js +30 -42
  32. package/lib/serialization.js.map +1 -1
  33. package/lib/with-mask.js +48 -20
  34. package/lib/with-mask.js.map +1 -1
  35. package/package.json +26 -15
  36. package/src/__tests__/drag-in-the-blank.test.js +111 -0
  37. package/src/__tests__/index.test.js +39 -0
  38. package/src/__tests__/mask.test.js +187 -0
  39. package/src/__tests__/serialization.test.js +54 -0
  40. package/src/__tests__/utils.js +1 -0
  41. package/src/__tests__/with-mask.test.js +76 -0
  42. package/src/choices/__tests__/index.test.js +75 -0
  43. package/src/choices/choice.jsx +83 -96
  44. package/src/choices/index.jsx +11 -5
  45. package/src/components/__tests__/blank.test.js +138 -0
  46. package/src/components/__tests__/correct-input.test.js +90 -0
  47. package/src/components/__tests__/dropdown.test.js +93 -0
  48. package/src/components/__tests__/input.test.js +102 -0
  49. package/src/components/blank.jsx +316 -204
  50. package/src/components/correct-input.jsx +37 -38
  51. package/src/components/dropdown.jsx +371 -125
  52. package/src/constructed-response.jsx +80 -18
  53. package/src/customizable.jsx +35 -0
  54. package/src/drag-in-the-blank.jsx +152 -40
  55. package/src/index.js +10 -1
  56. package/src/inline-dropdown.jsx +2 -0
  57. package/src/mask.jsx +71 -25
  58. package/src/serialization.js +22 -34
  59. package/src/with-mask.jsx +43 -3
  60. package/README.md +0 -14
  61. package/lib/new-serialization.js +0 -267
  62. package/lib/new-serialization.js.map +0 -1
  63. package/lib/parse-html.js +0 -17
  64. package/lib/parse-html.js.map +0 -1
  65. package/lib/test-serializer.js +0 -164
  66. package/lib/test-serializer.js.map +0 -1
  67. package/src/new-serialization.jsx +0 -291
  68. package/src/parse-html.js +0 -8
  69. package/src/test-serializer.js +0 -163
@@ -1 +1 @@
1
- {"version":3,"file":"with-mask.js","names":["_react","_interopRequireDefault","require","_propTypes","_mask","_componentize2","_serialization","_createSuper","Derived","hasNativeReflectConstruct","_isNativeReflectConstruct","_createSuperInternal","Super","_getPrototypeOf2","result","NewTarget","constructor","Reflect","construct","arguments","apply","_possibleConstructorReturn2","sham","Proxy","Boolean","prototype","valueOf","call","e","buildLayoutFromMarkup","markup","type","_componentize","componentize","processed","value","deserialize","exports","withMask","renderChildren","_class","_React$Component","_inherits2","WithMask","_super","_classCallCheck2","_createClass2","key","render","_this$props","props","layout","onChange","maskLayout","createElement","React","Component","_defineProperty2","PropTypes","string","object","func"],"sources":["../src/with-mask.jsx"],"sourcesContent":["import React from 'react';\nimport PropTypes from 'prop-types';\nimport Mask from './mask';\nimport componentize from './componentize';\nimport { deserialize } from './serialization';\n\nexport const buildLayoutFromMarkup = (markup, type) => {\n const { markup: processed } = componentize(markup, type);\n const value = deserialize(processed);\n return value;\n};\n\nexport const withMask = (type, renderChildren) => {\n return class WithMask extends React.Component {\n static propTypes = {\n /**\n * At the start we'll probably work with markup\n */\n markup: PropTypes.string,\n /**\n * Once we start authoring, it may make sense for use to us layout, which will be a simple js object that maps to `slate.Value`.\n */\n layout: PropTypes.object,\n value: PropTypes.object,\n onChange: PropTypes.func,\n };\n\n render() {\n const { markup, layout, value, onChange } = this.props;\n\n const maskLayout = layout ? layout : buildLayoutFromMarkup(markup, type);\n return <Mask layout={maskLayout} value={value} onChange={onChange} renderChildren={renderChildren(this.props)} />;\n }\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"with-mask.js","names":["_react","_interopRequireDefault","require","_propTypes","_mask","_componentize2","_serialization","_callSuper","t","o","e","_getPrototypeOf2","_possibleConstructorReturn2","_isNativeReflectConstruct","Reflect","construct","constructor","apply","Boolean","prototype","valueOf","call","buildLayoutFromMarkup","exports","markup","type","_componentize","componentize","processed","value","deserialize","document","withMask","renderChildren","_WithMask","_React$Component","WithMask","props","_this","_classCallCheck2","containerRef","React","createRef","_inherits2","_createClass2","key","componentDidUpdate","prevProps","domNode","current","mathElements","querySelectorAll","forEach","el","mjxContainer","querySelector","removeChild","latexCode","getAttribute","innerHTML","removeAttribute","render","_this$props","layout","onChange","elementType","maskLayout","createElement","Component","_defineProperty2","PropTypes","string","object","func","customMarkMarkupComponent"],"sources":["../src/with-mask.jsx"],"sourcesContent":["import React from 'react';\nimport PropTypes from 'prop-types';\nimport Mask from './mask';\nimport componentize from './componentize';\nimport { deserialize } from './serialization';\n\nexport const buildLayoutFromMarkup = (markup, type) => {\n const { markup: processed } = componentize(markup, type);\n const value = deserialize(processed);\n return value.document;\n};\n\nexport const withMask = (type, renderChildren) => {\n return class WithMask extends React.Component {\n static propTypes = {\n /**\n * At the start we'll probably work with markup\n */\n markup: PropTypes.string,\n /**\n * Once we start authoring, it may make sense for use to us layout, which will be a simple js object that maps to `slate.Value`.\n */\n layout: PropTypes.object,\n value: PropTypes.object,\n onChange: PropTypes.func,\n customMarkMarkupComponent: PropTypes.func,\n elementType: PropTypes.string,\n };\n\n constructor(props) {\n super(props);\n this.containerRef = React.createRef();\n }\n\n componentDidUpdate(prevProps) {\n if (this.props.markup !== prevProps.markup) {\n const domNode = this.containerRef.current;\n const mathElements = domNode && domNode.querySelectorAll('[data-latex][data-math-handled=\"true\"]');\n\n // Clean up for fresh MathJax processing\n (mathElements || []).forEach((el) => {\n // Remove the MathJax container to allow for clean updates\n const mjxContainer = el.querySelector('mjx-container');\n\n if (mjxContainer) {\n el.removeChild(mjxContainer);\n }\n\n // Update the innerHTML to match the raw LaTeX data, ensuring it is reprocessed correctly\n const latexCode = el.getAttribute('data-raw');\n el.innerHTML = latexCode;\n\n // Remove the attribute to signal that MathJax should reprocess this element\n el.removeAttribute('data-math-handled');\n });\n }\n }\n\n render() {\n const { markup, layout, value, onChange, elementType } = this.props;\n\n const maskLayout = layout ? layout : buildLayoutFromMarkup(markup, type);\n return (\n <Mask\n containerRef={this.containerRef}\n elementType={elementType}\n layout={maskLayout}\n value={value}\n onChange={onChange}\n renderChildren={renderChildren(this.props)}\n />\n );\n }\n };\n};\n"],"mappings":"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","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-lib/mask-markup",
3
- "version": "2.0.0-beta.2",
3
+ "version": "2.0.0-next.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "module": "src/index.js",
@@ -8,25 +8,36 @@
8
8
  "access": "public"
9
9
  },
10
10
  "dependencies": {
11
- "@material-ui/core": "^3.9.3",
12
- "@pie-lib/drag": "^2.2.1",
13
- "@pie-lib/math-rendering": "^2.5.17",
14
- "@pie-lib/render-ui": "^4.14.28",
11
+ "@emotion/react": "^11.14.0",
12
+ "@emotion/style": "^0.8.0",
13
+ "@mui/icons-material": "^7.3.4",
14
+ "@mui/material": "^7.3.4",
15
+ "@pie-lib/drag": "2.22.3-next.15",
16
+ "@pie-lib/editable-html": "11.21.3-next.15",
17
+ "@pie-lib/math-rendering": "3.22.2-next.16",
18
+ "@pie-lib/render-ui": "4.35.3-next.15",
15
19
  "classnames": "^2.2.6",
16
20
  "debug": "^4.1.1",
17
- "escape-html": "^1.0.3",
18
- "immutable": ">=3.8.1",
21
+ "lodash": "^4.17.11",
19
22
  "prop-types": "^15.7.2",
20
- "react": "^16.8.1",
21
- "react-dnd-html5-backend": "^14.0.2",
22
- "slate": "^0.94.1",
23
- "slate-html-serializer": "^0.8.13",
24
- "slate-hyperscript": "^0.77.0",
25
- "slate-prop-types": "^0.5.44",
26
- "slate-react": "^0.97.2",
23
+ "react": "^18.2.0",
24
+ "react-dom": "^18.2.0",
25
+ "slate": "^0.36.2",
26
+ "slate-html-serializer": "^0.6.12",
27
+ "slate-prop-types": "^0.4.38",
28
+ "slate-react": "^0.14.3",
27
29
  "to-style": "^1.3.3"
28
30
  },
29
31
  "keywords": [],
30
32
  "author": "",
31
- "license": "ISC"
33
+ "license": "ISC",
34
+ "gitHead": "e0f8f2250addd014938f4a631ab86cafe16b2730",
35
+ "exports": {
36
+ ".": {
37
+ "require": "./lib/index.js",
38
+ "import": "./src/index.js",
39
+ "default": "./lib/index.js"
40
+ },
41
+ "./esm": "./esm/index.js"
42
+ }
32
43
  }
@@ -0,0 +1,111 @@
1
+ import * as React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import DragInTheBlank from '../drag-in-the-blank';
4
+
5
+ const markup = `<div>
6
+ <img src="https://image.shutterstock.com/image-vector/cow-jumped-over-moon-traditional-260nw-1152899330.jpg"></img>
7
+ <h5>Hey Diddle Diddle <i>by ?</i></h5>
8
+ <p>1: Hey, diddle, diddle,</p>
9
+ <p>2: The cat and the fiddle,</p>
10
+ <p>3: The cow {{0}} over the moon;</p>
11
+ <p>4: The little dog {{1}},</p>
12
+ <p>5: To see such sport,</p>
13
+ <p>6: And the dish ran away with the {{2}}.</p>
14
+ </div>`;
15
+ const choice = (v, id) => ({ value: v, id });
16
+
17
+ // Mock DragProvider and DragDroppablePlaceholder to avoid DndContext requirement
18
+ jest.mock('@pie-lib/drag', () => ({
19
+ DragProvider: ({ children, onDragStart, onDragEnd }) => {
20
+ // Simple wrapper that doesn't require DndContext
21
+ return <div data-testid="drag-provider">{children}</div>;
22
+ },
23
+ DragDroppablePlaceholder: ({ children, disabled, instanceId }) => {
24
+ // Simple wrapper that doesn't require useDroppable
25
+ return <div data-testid="drag-droppable-placeholder">{children}</div>;
26
+ },
27
+ }));
28
+
29
+ // Mock @dnd-kit/core components and hooks used by DragInTheBlank and child components
30
+ jest.mock('@dnd-kit/core', () => ({
31
+ DragOverlay: ({ children }) => <div data-testid="drag-overlay">{children}</div>,
32
+ closestCenter: jest.fn(),
33
+ useDraggable: jest.fn(() => ({
34
+ attributes: {},
35
+ listeners: {},
36
+ setNodeRef: jest.fn(),
37
+ transform: null,
38
+ isDragging: false,
39
+ })),
40
+ useDroppable: jest.fn(() => ({
41
+ setNodeRef: jest.fn(),
42
+ isOver: false,
43
+ active: null,
44
+ })),
45
+ }));
46
+
47
+ jest.mock('@dnd-kit/utilities', () => ({
48
+ CSS: {
49
+ Translate: {
50
+ toString: jest.fn(() => 'translate3d(0, 0, 0)'),
51
+ },
52
+ },
53
+ }));
54
+
55
+ describe('DragInTheBlank', () => {
56
+ const defaultProps = {
57
+ disabled: false,
58
+ feedback: {},
59
+ markup,
60
+ choices: [
61
+ choice('Jumped', '0'),
62
+ choice('Laughed', '1'),
63
+ choice('Spoon', '2'),
64
+ choice('Fork', '3'),
65
+ choice('Bumped', '4'),
66
+ choice('Smiled', '5'),
67
+ ],
68
+
69
+ value: {
70
+ 0: undefined,
71
+ },
72
+ };
73
+
74
+ describe('render', () => {
75
+ it('renders correctly with default props', () => {
76
+ const { container } = render(<DragInTheBlank {...defaultProps} />);
77
+ expect(container.firstChild).toBeInTheDocument();
78
+ // Check that markup content is rendered
79
+ expect(screen.getByText(/Hey Diddle Diddle/)).toBeInTheDocument();
80
+ expect(screen.getByText(/Hey, diddle, diddle,/)).toBeInTheDocument();
81
+ });
82
+
83
+ it('renders correctly with disabled prop as true', () => {
84
+ const { container } = render(<DragInTheBlank {...defaultProps} disabled={true} />);
85
+ expect(container.firstChild).toBeInTheDocument();
86
+ });
87
+
88
+ it('renders correctly with feedback', () => {
89
+ const { container } = render(
90
+ <DragInTheBlank
91
+ {...defaultProps}
92
+ feedback={{
93
+ 0: {
94
+ value: 'Jumped',
95
+ correct: 'Jumped',
96
+ },
97
+ 1: {
98
+ value: 'Laughed',
99
+ correct: 'Laughed',
100
+ },
101
+ 2: {
102
+ value: 'Spoon',
103
+ correct: 'Spoon',
104
+ },
105
+ }}
106
+ />
107
+ );
108
+ expect(container.firstChild).toBeInTheDocument();
109
+ });
110
+ });
111
+ });
@@ -0,0 +1,39 @@
1
+ import * as React from 'react';
2
+ import componentize from '../componentize';
3
+ import { deserialize } from '../serialization';
4
+
5
+ describe('index', () => {
6
+ describe('componentize', () => {
7
+ it('should return an array with the appropriate markup', () => {
8
+ const dropDownMarkup = componentize('{{0}} foo {{1}}', 'dropdown');
9
+
10
+ expect(dropDownMarkup).toEqual({
11
+ markup:
12
+ '<span data-component="dropdown" data-id="0"></span> foo <span data-component="dropdown" data-id="1"></span>',
13
+ });
14
+ });
15
+ });
16
+
17
+ describe('serialization', () => {
18
+ it('should have default node a span', () => {
19
+ expect(deserialize('something')).toEqual(
20
+ expect.objectContaining({
21
+ object: 'value',
22
+ document: {
23
+ object: 'document',
24
+ data: {},
25
+ nodes: [
26
+ {
27
+ object: 'block',
28
+ data: {},
29
+ isVoid: false,
30
+ type: 'span',
31
+ nodes: [{ object: 'text', leaves: [{ text: 'something' }] }],
32
+ },
33
+ ],
34
+ },
35
+ }),
36
+ );
37
+ });
38
+ });
39
+ });
@@ -0,0 +1,187 @@
1
+ import * as React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import Mask from '../mask';
4
+
5
+ describe('Mask', () => {
6
+ // Don't mock renderChildren - let the component render naturally
7
+ const onChange = jest.fn();
8
+ const defaultProps = {
9
+ onChange,
10
+ layout: {
11
+ nodes: [
12
+ {
13
+ object: 'text',
14
+ leaves: [
15
+ {
16
+ text: 'Foo',
17
+ },
18
+ ],
19
+ },
20
+ ],
21
+ },
22
+ value: {},
23
+ };
24
+
25
+ beforeEach(() => {
26
+ onChange.mockClear();
27
+ });
28
+
29
+ describe('rendering', () => {
30
+ it('renders with default props', () => {
31
+ const { container } = render(<Mask {...defaultProps} />);
32
+ expect(container.firstChild).toBeInTheDocument();
33
+ });
34
+
35
+ it('renders text content', () => {
36
+ render(<Mask {...defaultProps} />);
37
+ expect(screen.getByText('Foo')).toBeInTheDocument();
38
+ });
39
+
40
+ it('renders a paragraph element', () => {
41
+ const { container } = render(
42
+ <Mask
43
+ {...defaultProps}
44
+ layout={{
45
+ nodes: [
46
+ {
47
+ type: 'p',
48
+ nodes: [
49
+ {
50
+ object: 'text',
51
+ leaves: [
52
+ {
53
+ text: 'Foo',
54
+ },
55
+ ],
56
+ },
57
+ ],
58
+ },
59
+ ],
60
+ }}
61
+ />,
62
+ );
63
+
64
+ // Paragraph is rendered as a styled div, not a <p> tag
65
+ expect(screen.getByText('Foo')).toBeInTheDocument();
66
+ });
67
+
68
+ it('renders nested div and paragraph', () => {
69
+ const { container } = render(
70
+ <Mask
71
+ {...defaultProps}
72
+ layout={{
73
+ nodes: [
74
+ {
75
+ type: 'div',
76
+ data: {
77
+ attributes: {},
78
+ },
79
+ nodes: [
80
+ {
81
+ type: 'p',
82
+ data: {
83
+ attributes: {},
84
+ },
85
+ nodes: [
86
+ {
87
+ object: 'text',
88
+ leaves: [
89
+ {
90
+ text: 'Foo',
91
+ },
92
+ ],
93
+ },
94
+ ],
95
+ },
96
+ ],
97
+ },
98
+ ],
99
+ }}
100
+ />,
101
+ );
102
+
103
+ expect(container.querySelector('div')).toBeInTheDocument();
104
+ // Paragraph is rendered as a styled div, not a <p> tag
105
+ expect(screen.getByText('Foo')).toBeInTheDocument();
106
+ });
107
+
108
+ it('renders text with italic marks', () => {
109
+ const { container } = render(
110
+ <Mask
111
+ {...defaultProps}
112
+ layout={{
113
+ nodes: [
114
+ {
115
+ leaves: [{ text: 'Foo ' }],
116
+ object: 'text',
117
+ },
118
+ {
119
+ leaves: [
120
+ {
121
+ marks: [
122
+ {
123
+ data: undefined,
124
+ type: 'italic',
125
+ },
126
+ ],
127
+ text: 'x',
128
+ },
129
+ ],
130
+ object: 'text',
131
+ },
132
+ {
133
+ leaves: [{ text: ' bar' }],
134
+ object: 'text',
135
+ },
136
+ ],
137
+ object: 'block',
138
+ type: 'div',
139
+ }}
140
+ />,
141
+ );
142
+
143
+ // Text "Foo " is split with spaces, use regex
144
+ expect(screen.getByText(/Foo/)).toBeInTheDocument();
145
+ expect(screen.getByText('x')).toBeInTheDocument();
146
+ expect(screen.getByText(/bar/)).toBeInTheDocument();
147
+ // Check for italic/em element
148
+ const em = container.querySelector('em, i');
149
+ expect(em).toBeInTheDocument();
150
+ expect(em.textContent).toBe('x');
151
+ });
152
+
153
+ it('renders tbody without extra space', () => {
154
+ const da = () => ({ data: { attributes: {} } });
155
+ const { container } = render(
156
+ <Mask
157
+ {...defaultProps}
158
+ layout={{
159
+ nodes: [
160
+ {
161
+ type: 'table',
162
+ ...da(),
163
+ nodes: [
164
+ {
165
+ type: 'tbody',
166
+ ...da(),
167
+ nodes: [
168
+ {
169
+ object: 'text',
170
+ leaves: [{ text: ' ' }],
171
+ },
172
+ { type: 'tr', ...da(), nodes: [] },
173
+ ],
174
+ },
175
+ ],
176
+ },
177
+ ],
178
+ }}
179
+ />,
180
+ );
181
+
182
+ expect(container.querySelector('table')).toBeInTheDocument();
183
+ expect(container.querySelector('tbody')).toBeInTheDocument();
184
+ expect(container.querySelector('tr')).toBeInTheDocument();
185
+ });
186
+ });
187
+ });
@@ -0,0 +1,54 @@
1
+ import { deserialize } from '../serialization';
2
+
3
+ describe('serialization', () => {
4
+ it('ignores comments', () => {
5
+ const out = deserialize(`<!-- hi -->`);
6
+ expect(out.document.nodes[0]).toEqual(expect.objectContaining({ type: 'span' }));
7
+ });
8
+
9
+ it('ignores comments', () => {
10
+ const out = deserialize(`<!-- hi --><div>foo</div>`);
11
+ expect(out.document.nodes[0]).toEqual(
12
+ expect.objectContaining({
13
+ type: 'div',
14
+ nodes: [
15
+ expect.objectContaining({
16
+ object: 'text',
17
+ leaves: [{ text: 'foo' }],
18
+ }),
19
+ ],
20
+ }),
21
+ );
22
+ });
23
+
24
+ it('deserializes an em', () => {
25
+ const out = deserialize(`<!-- hi --><div> <em>x</em> </div>`);
26
+ expect(out.document.nodes[0]).toEqual(
27
+ expect.objectContaining({
28
+ type: 'div',
29
+ nodes: [
30
+ expect.objectContaining({
31
+ object: 'text',
32
+ }),
33
+ expect.objectContaining({
34
+ leaves: [
35
+ {
36
+ marks: [
37
+ {
38
+ data: undefined,
39
+ type: 'italic',
40
+ },
41
+ ],
42
+ text: 'x',
43
+ },
44
+ ],
45
+ object: 'text',
46
+ }),
47
+ expect.objectContaining({
48
+ object: 'text',
49
+ }),
50
+ ],
51
+ }),
52
+ );
53
+ });
54
+ });
@@ -0,0 +1 @@
1
+ export const choice = (v, id) => ({ label: v, value: v, id });
@@ -0,0 +1,76 @@
1
+ import * as React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { withMask } from '../with-mask';
5
+
6
+ describe('WithMask', () => {
7
+ const onChange = jest.fn();
8
+ const defaultProps = {
9
+ markup: '<p>Foo bar {{0}} over the moon;</p>',
10
+ value: {
11
+ 0: 'blank',
12
+ },
13
+ onChange,
14
+ };
15
+
16
+ const Masked = withMask('foo', (props) => (node) => {
17
+ const dataset = node.data ? node.data.dataset || {} : {};
18
+
19
+ if (dataset.component === 'foo') {
20
+ return <input type="text" data-testid="masked-input" defaultValue="Foo" onChange={props.onChange} />;
21
+ }
22
+ });
23
+
24
+ beforeEach(() => {
25
+ onChange.mockClear();
26
+ });
27
+
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();
44
+ });
45
+ });
46
+
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
+ }
59
+ });
60
+
61
+ it('passes event to onChange', async () => {
62
+ const user = userEvent.setup();
63
+ render(<Masked {...defaultProps} />);
64
+
65
+ const input = screen.queryByTestId('masked-input');
66
+ if (input) {
67
+ await user.clear(input);
68
+ await user.type(input, 'test');
69
+
70
+ expect(onChange).toHaveBeenCalled();
71
+ const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1][0];
72
+ expect(lastCall).toHaveProperty('target');
73
+ }
74
+ });
75
+ });
76
+ });
@@ -0,0 +1,75 @@
1
+ import * as React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import Choice from '../choice';
4
+ import { choice } from '../../__tests__/utils';
5
+ import Choices from '../index';
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
+
22
+ describe('index', () => {
23
+ describe('Choices', () => {
24
+ const defaultProps = {
25
+ disabled: false,
26
+ choices: [choice('Jumped', '0'), choice('Laughed', '1'), choice('Spoon', '2')],
27
+ choicePosition: 'below',
28
+ instanceId: 'test-instance',
29
+ };
30
+
31
+ it('renders correctly with default props', () => {
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();
37
+ });
38
+
39
+ it('renders correctly with disabled prop as true', () => {
40
+ const { container } = render(<Choices {...defaultProps} disabled={true} />);
41
+ expect(container.firstChild).toBeInTheDocument();
42
+ });
43
+
44
+ it('renders without duplicates', () => {
45
+ const { container } = render(<Choices {...defaultProps} duplicates={undefined} value={{ 0: '0', 1: '1' }} />);
46
+ expect(container.firstChild).toBeInTheDocument();
47
+ });
48
+
49
+ it('renders with duplicates', () => {
50
+ const { container } = render(<Choices {...defaultProps} duplicates={true} value={{ 0: '0', 1: '1' }} />);
51
+ expect(container.firstChild).toBeInTheDocument();
52
+ });
53
+ });
54
+
55
+ describe('Choice', () => {
56
+ const defaultProps = {
57
+ disabled: false,
58
+ choice: choice('Label', '1'),
59
+ instanceId: 'test-instance',
60
+ };
61
+
62
+ describe('render', () => {
63
+ it('renders correctly with default props', () => {
64
+ const { container } = render(<Choice {...defaultProps} />);
65
+ expect(container.firstChild).toBeInTheDocument();
66
+ expect(screen.getByText('Label')).toBeInTheDocument();
67
+ });
68
+
69
+ it('renders correctly with disabled prop as true', () => {
70
+ const { container } = render(<Choice {...defaultProps} disabled={true} />);
71
+ expect(container.firstChild).toBeInTheDocument();
72
+ });
73
+ });
74
+ });
75
+ });