@pie-lib/editable-html 13.0.13 → 13.0.14-beta.1

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 (29) hide show
  1. package/CHANGELOG.md +0 -50
  2. package/lib/editor.js +3 -3
  3. package/lib/editor.js.map +1 -1
  4. package/lib/plugins/respArea/math-templated/index.js +2 -2
  5. package/lib/plugins/respArea/math-templated/index.js.map +1 -1
  6. package/lib/shared/alert-dialog.js +68 -0
  7. package/package.json +7 -7
  8. package/src/editor.jsx +2 -2
  9. package/src/plugins/respArea/math-templated/index.jsx +1 -1
  10. package/LICENSE.md +0 -5
  11. package/src/__tests__/editor-utils.test.js +0 -162
  12. package/src/__tests__/serialization.test.js +0 -290
  13. package/src/__tests__/utils.js +0 -36
  14. package/src/plugins/image/__tests__/component.test.jsx +0 -73
  15. package/src/plugins/image/__tests__/image-toolbar-logic.test.jsx +0 -90
  16. package/src/plugins/image/__tests__/image-toolbar.test.jsx +0 -12
  17. package/src/plugins/image/__tests__/index.test.js +0 -92
  18. package/src/plugins/image/__tests__/insert-image-handler.test.js +0 -113
  19. package/src/plugins/image/__tests__/mock-change.js +0 -15
  20. package/src/plugins/list/__tests__/index.test.js +0 -52
  21. package/src/plugins/math/__tests__/index.test.jsx +0 -264
  22. package/src/plugins/media/__tests__/index.test.js +0 -75
  23. package/src/plugins/respArea/drag-in-the-blank/__tests__/choice.test.js +0 -71
  24. package/src/plugins/table/__tests__/index.test.jsx +0 -401
  25. package/src/plugins/table/__tests__/table-toolbar.test.jsx +0 -75
  26. package/src/plugins/toolbar/__tests__/default-toolbar.test.jsx +0 -123
  27. package/src/plugins/toolbar/__tests__/editor-and-toolbar.test.jsx +0 -40
  28. package/src/plugins/toolbar/__tests__/toolbar-buttons.test.jsx +0 -97
  29. package/src/plugins/toolbar/__tests__/toolbar.test.jsx +0 -112
@@ -1,90 +0,0 @@
1
- import { render, screen } from '@testing-library/react';
2
- import userEvent from '@testing-library/user-event';
3
- import { ImageToolbar } from '../image-toolbar';
4
- import React from 'react';
5
-
6
- // Mock the alt-dialog component to avoid complex modal rendering in tests
7
- jest.mock('../alt-dialog', () => {
8
- return function AltDialog({ alt, onDone }) {
9
- return (
10
- <div data-testid="alt-dialog">
11
- <input data-testid="alt-input" defaultValue={alt} onChange={(e) => onDone(e.target.value)} />
12
- </div>
13
- );
14
- };
15
- });
16
-
17
- describe('ImageToolbar', () => {
18
- let onChange;
19
-
20
- beforeEach(() => {
21
- onChange = jest.fn();
22
- });
23
-
24
- const renderComponent = (extras = {}) => {
25
- const props = {
26
- onChange,
27
- imageLoaded: true,
28
- ...extras,
29
- };
30
-
31
- return render(<ImageToolbar {...props} />);
32
- };
33
-
34
- describe('rendering', () => {
35
- it('renders alignment buttons', () => {
36
- renderComponent();
37
- expect(screen.getByText('left')).toBeInTheDocument();
38
- expect(screen.getByText('center')).toBeInTheDocument();
39
- expect(screen.getByText('right')).toBeInTheDocument();
40
- });
41
-
42
- it('renders alt text button', () => {
43
- renderComponent();
44
- expect(screen.getByText('Alt text')).toBeInTheDocument();
45
- });
46
-
47
- it('does not render alignment buttons when disabled', () => {
48
- renderComponent({ disableImageAlignmentButtons: true });
49
- expect(screen.queryByText('left')).not.toBeInTheDocument();
50
- expect(screen.queryByText('center')).not.toBeInTheDocument();
51
- expect(screen.queryByText('right')).not.toBeInTheDocument();
52
- });
53
-
54
- it('shows active state for selected alignment', () => {
55
- const { container } = renderComponent({ alignment: 'center' });
56
- // The MarkButton component uses aria-pressed for active state
57
- const centerButton = screen.getByText('center').closest('button');
58
- expect(centerButton).toHaveAttribute('aria-pressed', 'true');
59
- });
60
- });
61
-
62
- describe('user interactions', () => {
63
- it('calls onChange with alignment when left is clicked', async () => {
64
- const user = userEvent.setup();
65
- renderComponent();
66
-
67
- await user.click(screen.getByText('left'));
68
-
69
- expect(onChange).toHaveBeenCalledWith({ alignment: 'left' });
70
- });
71
-
72
- it('calls onChange with alignment when center is clicked', async () => {
73
- const user = userEvent.setup();
74
- renderComponent();
75
-
76
- await user.click(screen.getByText('center'));
77
-
78
- expect(onChange).toHaveBeenCalledWith({ alignment: 'center' });
79
- });
80
-
81
- it('calls onChange with alignment when right is clicked', async () => {
82
- const user = userEvent.setup();
83
- renderComponent();
84
-
85
- await user.click(screen.getByText('right'));
86
-
87
- expect(onChange).toHaveBeenCalledWith({ alignment: 'right' });
88
- });
89
- });
90
- });
@@ -1,12 +0,0 @@
1
- import React from 'react';
2
- import Toolbar from '../image-toolbar';
3
- import { render } from '@testing-library/react';
4
-
5
- it('renders image toolbar', () => {
6
- const classes = { holder: 'holder' };
7
-
8
- const { container } = render(<Toolbar percent={50} classes={classes} onChange={jest.fn()} />);
9
-
10
- // Verify the toolbar is rendered
11
- expect(container.firstChild).toBeInTheDocument();
12
- });
@@ -1,92 +0,0 @@
1
- import ImageToolbar from '../';
2
-
3
- describe('image plugin', () => {
4
- let value = {};
5
-
6
- const imageSupport = {
7
- delete: jest.fn(),
8
- add: jest.fn(),
9
- };
10
-
11
- const imagePlugin = ImageToolbar({
12
- onDelete: (src, done) => {
13
- imageSupport.delete(src, (e) => {
14
- done(e, value);
15
- });
16
- },
17
- insertImageRequested: (node, getHandler) => {
18
- const handler = getHandler(() => value);
19
- imageSupport.add(handler);
20
- },
21
- });
22
-
23
- describe('normalizeNode', () => {
24
- it('should exit the function if the node is not of type document', () => {
25
- const returnValue = imagePlugin.normalizeNode({ object: 'image' });
26
-
27
- expect(returnValue).toEqual(undefined);
28
- });
29
-
30
- it('should exit if the function if there are no changes needed', () => {
31
- const nodes = [
32
- {
33
- object: 'text',
34
- text: 'Before Image',
35
- },
36
- {
37
- type: 'image',
38
- },
39
- {
40
- object: 'text',
41
- text: 'After Image',
42
- },
43
- ];
44
- const returnValue = imagePlugin.normalizeNode({
45
- object: 'document',
46
- findDescendant: jest.fn((callback) => {
47
- nodes.forEach((n) => callback(n));
48
- }),
49
- });
50
- expect(returnValue).toEqual(undefined);
51
- });
52
-
53
- it('should return a function if there is a node with an empty text before an image', () => {
54
- const nodes = [
55
- {
56
- object: 'text',
57
- text: '',
58
- key: '1',
59
- },
60
- {
61
- type: 'image',
62
- key: '2',
63
- },
64
- {
65
- object: 'text',
66
- text: 'After Image',
67
- key: '3',
68
- },
69
- ];
70
- const findDescendant = jest.fn((callback) => {
71
- nodes.forEach((n) => callback(n));
72
- });
73
- const change = {
74
- withoutNormalization: jest.fn((callback) => {
75
- callback();
76
- }),
77
- insertTextByKey: jest.fn(),
78
- };
79
- const returnValue = imagePlugin.normalizeNode({
80
- object: 'document',
81
- findDescendant,
82
- });
83
-
84
- expect(returnValue).toEqual(expect.any(Function));
85
-
86
- returnValue(change);
87
-
88
- expect(change.withoutNormalization).toHaveBeenCalledWith(expect.any(Function));
89
- expect(change.insertTextByKey).toHaveBeenCalledWith('1', 0, ' ');
90
- });
91
- });
92
- });
@@ -1,113 +0,0 @@
1
- import MockChange, { MockDocument } from './mock-change';
2
-
3
- import { Data } from 'slate';
4
- import InsertImageHandler from '../insert-image-handler';
5
-
6
- expect.extend({
7
- toMatchData: (received, argument) => {
8
- const argData = Data.create(argument);
9
- const pass = argData.equals(received.data);
10
- if (pass) {
11
- return {
12
- message: () => `expected ${received.toJSON()} not to be divisible by ${argData.toJSON()}`,
13
- pass: true,
14
- };
15
- } else {
16
- return {
17
- message: () => `expected ${received.toJSON()} to be divisible by ${argData.toJSON()}`,
18
- pass: false,
19
- };
20
- }
21
- },
22
- });
23
- describe('insert image handler', () => {
24
- let change, document, value;
25
- beforeEach(() => {
26
- document = new MockDocument();
27
- change = new MockChange();
28
- value = {
29
- change: () => change,
30
- document,
31
- };
32
- });
33
-
34
- const block = { key: 1 };
35
- const onChange = jest.fn();
36
-
37
- const handler = new InsertImageHandler(
38
- block,
39
- () => {},
40
- () => value,
41
- onChange,
42
- );
43
-
44
- test('it constructs', () => {
45
- expect(handler).not.toEqual(undefined);
46
- });
47
-
48
- describe('fileChosen', () => {
49
- let fileReader;
50
- beforeEach(() => {
51
- fileReader = {
52
- readAsDataURL: jest.fn(),
53
- };
54
-
55
- global.FileReader = () => fileReader;
56
- handler.fileChosen({});
57
- });
58
-
59
- test('calls readAsDataURL', () => {
60
- expect(fileReader.readAsDataURL).toBeCalledWith({});
61
- });
62
-
63
- test('calls onChange with src -> dataUrl', () => {
64
- fileReader.result = 'dataURL';
65
- fileReader.onload();
66
- expect(change.setNodeByKey).toBeCalledWith(block.key, expect.anything());
67
- expect(change.setNodeByKey.mock.calls[0][1]).toMatchData({
68
- src: 'dataURL',
69
- });
70
- expect(onChange).toBeCalledWith(change);
71
- });
72
- });
73
-
74
- describe('progress', () => {
75
- test('calls change w/ percent', () => {
76
- handler.progress(40, 40, 100);
77
- expect(change.setNodeByKey).toBeCalledWith(block.key, expect.anything());
78
-
79
- expect(change.setNodeByKey.mock.calls[0][1].data.toJS()).toMatchObject({
80
- percent: 40,
81
- });
82
- });
83
- });
84
-
85
- describe('done', () => {
86
- test('calls setNodeByKey', () => {
87
- handler.done(null, 'src');
88
-
89
- expect(change.setNodeByKey).toBeCalledWith(block.key, expect.anything());
90
-
91
- expect(change.setNodeByKey.mock.calls[0][1].data.toJS()).toMatchObject({
92
- src: 'src',
93
- loaded: true,
94
- percent: 100,
95
- });
96
- });
97
- });
98
-
99
- describe('cancel', () => {
100
- beforeEach(() => {
101
- document.getChild = jest.fn().mockReturnValue({ data: Data.create({}), key: block.key });
102
- handler.cancel();
103
- });
104
-
105
- test('calls onChange', () => {
106
- expect(onChange).toBeCalled();
107
- });
108
-
109
- test('calls removeNodeByKey', () => {
110
- expect(change.removeNodeByKey).toBeCalledWith(block.key);
111
- });
112
- });
113
- });
@@ -1,15 +0,0 @@
1
- import { Data } from 'slate';
2
-
3
- export default function MockChange() {
4
- this.setNodeByKey = jest.fn().mockReturnValue(this);
5
- this.removeNodeByKey = jest.fn().mockReturnValue(this);
6
- this.insertInline = jest.fn().mockReturnValue(this);
7
- this.moveFocusTo = jest.fn().mockReturnValue(this);
8
- this.moveAnchorTo = jest.fn().mockReturnValue(this);
9
- }
10
-
11
- export function MockDocument() {
12
- this.getChild = jest.fn().mockReturnValue({ data: Data.create({}) });
13
-
14
- this.getDescendant = jest.fn();
15
- }
@@ -1,52 +0,0 @@
1
- import List, { serialization } from '../index';
2
- import debug from 'debug';
3
-
4
- const log = debug('@pie-lib:editable-html:test:plugins:list');
5
-
6
- describe('ListPlugin', () => {
7
- let next;
8
-
9
- describe('deserialize', () => {
10
- next = jest.fn();
11
-
12
- const assertDeserialize = (tagName, expectedType) => {
13
- it(`should deserialize ${tagName} to ${expectedType}`, () => {
14
- const out = serialization.deserialize({ tagName, children: [], childNodes: [] }, next);
15
-
16
- expect(out).toMatchObject({ object: 'block', type: expectedType });
17
- expect(next).toHaveBeenCalledWith([]);
18
- });
19
- };
20
- assertDeserialize('ul', 'ul_list');
21
- assertDeserialize('ol', 'ol_list');
22
- assertDeserialize('li', 'list_item');
23
- });
24
-
25
- describe('serialize', () => {
26
- const assertSerialize = (type, expectedType) => {
27
- it(`should serialize ${type} to ${expectedType}`, () => {
28
- const out = serialization.serialize({ object: 'block', type }, {});
29
- log('out: ', out);
30
- expect(out.type).toMatch(expectedType);
31
- });
32
- };
33
- assertSerialize('ul_list', 'ul');
34
- assertSerialize('ol_list', 'ol');
35
- assertSerialize('list_item', 'li');
36
- });
37
-
38
- describe('renderNode', () => {
39
- let plugin = List({});
40
-
41
- const assertRenderNode = (type, expectedType) => {
42
- it(`should renderNode ${type} to ${expectedType}`, () => {
43
- const out = plugin.renderNode({ node: { type } });
44
- expect(out.type).toMatch(expectedType);
45
- });
46
- };
47
-
48
- assertRenderNode('ul_list', 'ul');
49
- assertRenderNode('ol_list', 'ol');
50
- assertRenderNode('list_item', 'li');
51
- });
52
- });
@@ -1,264 +0,0 @@
1
- import React from 'react';
2
- import debug from 'debug';
3
- import MockChange from '../../image/__tests__/mock-change';
4
- import { Data } from 'slate';
5
- import MathPlugin, { CustomToolbarComp, inlineMath, serialization } from '../index';
6
- import { render, screen } from '@testing-library/react';
7
-
8
- jest.mock('@pie-framework/mathquill', () => ({
9
- StaticMath: jest.fn(),
10
- getInterface: jest.fn().mockReturnThis(),
11
- registerEmbed: jest.fn(),
12
- }));
13
-
14
- let mockMathToolbarOnChange;
15
- let mockMathToolbarOnDone;
16
-
17
- jest.mock('@pie-lib/math-toolbar', () => ({
18
- MathPreview: () => <div data-testid="math-preview" />,
19
- MathToolbar: (props) => {
20
- // Store callbacks for testing
21
- mockMathToolbarOnChange = props.onChange;
22
- mockMathToolbarOnDone = props.onDone;
23
- return (
24
- <div data-testid="math-toolbar" role="toolbar">
25
- <button data-testid="toolbar-change" onClick={() => props.onChange && props.onChange('test-latex')}>
26
- Change
27
- </button>
28
- <button data-testid="toolbar-done" onClick={() => props.onDone && props.onDone('test-latex')}>
29
- Done
30
- </button>
31
- </div>
32
- );
33
- },
34
- }));
35
-
36
- jest.mock('@pie-lib/math-rendering', () => ({
37
- ...jest.requireActual('@pie-lib/math-rendering'),
38
- mmlToLatex: jest.fn(() => {
39
- return '/foobar/latex';
40
- }),
41
- }));
42
- const log = debug('@pie-lib:editable-html:test:math');
43
-
44
- // I believe @andrei is moving this stuff out.
45
- describe('MathPlugin', () => {
46
- describe('toolbar', () => {
47
- describe('onClick', () => {
48
- let plugin, mockChange, value, onChange;
49
- beforeEach(() => {
50
- plugin = MathPlugin({});
51
- mockChange = new MockChange();
52
- value = {
53
- change: jest.fn(() => mockChange),
54
- };
55
- onChange = jest.fn();
56
- plugin.toolbar.onClick(value, onChange);
57
- });
58
-
59
- test('calls insertInline', () => {
60
- expect(mockChange.insertInline).toBeCalledWith(expect.objectContaining({ data: inlineMath().data }));
61
- });
62
-
63
- test('it calls onChange', () => {
64
- expect(onChange).toHaveBeenCalledWith(mockChange);
65
- });
66
- });
67
- });
68
-
69
- describe('renderNode', () => {
70
- test('the component has props', () => {
71
- const plugin = MathPlugin({});
72
- const { props } = plugin.renderNode({ node: { type: 'math' } });
73
- expect(props.node).toEqual({ type: 'math' });
74
- });
75
- });
76
-
77
- describe('serialization', () => {
78
- describe('deserialize', () => {
79
- const assertDeserialize = (html, expected, wrapType) => {
80
- it(`innerHTML: ${html} is deserialized to: ${expected} with wrapType: ${wrapType}`, () => {
81
- const el = {
82
- tagName: 'span',
83
- childNodes: [],
84
- getAttribute: jest.fn(() => ''),
85
- hasAttribute: jest.fn(() => true),
86
- innerHTML: html,
87
- };
88
- const next = jest.fn();
89
-
90
- const out = serialization.deserialize(el, next);
91
- expect(out).toEqual({
92
- object: 'inline',
93
- type: 'math',
94
- isVoid: true,
95
- nodes: [],
96
- data: {
97
- latex: expected,
98
- wrapper: wrapType,
99
- },
100
- });
101
- });
102
- };
103
-
104
- assertDeserialize('$$&lt;$$', '<', MathPlugin.DOLLAR);
105
- assertDeserialize('$&lt;$', '<', MathPlugin.DOLLAR);
106
- assertDeserialize('\\(&lt;\\)', '<', MathPlugin.ROUND_BRACKETS);
107
- assertDeserialize('\\[&lt;\\]', '<', MathPlugin.ROUND_BRACKETS);
108
- assertDeserialize('latex', 'latex', MathPlugin.ROUND_BRACKETS);
109
- assertDeserialize('\\displaystyle foo', 'foo', MathPlugin.ROUND_BRACKETS);
110
-
111
- it('should make mathml editable if MathPlugin.mathMlOptions.mmlEditing is true', () => {
112
- MathPlugin.mathMlOptions.mmlEditing = true;
113
- const el = {
114
- tagName: 'math',
115
- outerHTML:
116
- '<math xmlns="http://www.w3.org/1998/Math/MathML"> <mn>2</mn> <mi>x</mi> <mtext>&#xA0;</mtext> <mo>&#x2264;</mo> <mn>4</mn> <mi>y</mi> <mtext>&#xA0;</mtext> <mo>+</mo> <mtext>&#xA0;</mtext> <mn>8</mn> <msqrt> <mi>h</mi> </msqrt></math>',
117
- };
118
- const next = jest.fn();
119
-
120
- const out = serialization.deserialize(el, next);
121
-
122
- expect(out).toEqual({
123
- object: 'inline',
124
- type: 'math',
125
- isVoid: true,
126
- nodes: [],
127
- data: {
128
- latex: '/foobar/latex',
129
- wrapper: 'round_brackets',
130
- },
131
- });
132
- });
133
-
134
- it('should make mathml readOnly if MathPlugin.mathMlOptions.mmlEditing is false', () => {
135
- MathPlugin.mathMlOptions.mmlEditing = false;
136
- const el = {
137
- tagName: 'math',
138
- outerHTML:
139
- '<math xmlns="http://www.w3.org/1998/Math/MathML"> <mn>2</mn> <mi>x</mi> <mtext>&#xA0;</mtext> <mo>&#x2264;</mo> <mn>4</mn> <mi>y</mi> <mtext>&#xA0;</mtext> <mo>+</mo> <mtext>&#xA0;</mtext> <mn>8</mn> <msqrt> <mi>h</mi> </msqrt></math>',
140
- };
141
- const next = jest.fn();
142
-
143
- const out = serialization.deserialize(el, next);
144
-
145
- expect(out).toEqual({
146
- object: 'inline',
147
- isVoid: true,
148
- type: 'mathml',
149
- data: {
150
- html: el.outerHTML,
151
- },
152
- });
153
- });
154
- });
155
-
156
- describe('serialize', () => {
157
- const assertSerialize = (latex, expectedHtml, wrapper) => {
158
- wrapper = wrapper || MathPlugin.ROUND_BRACKETS;
159
- it(`${latex} is serialized to: ${expectedHtml}`, () => {
160
- const object = {
161
- kind: 'inline',
162
- type: 'math',
163
- isVoid: true,
164
- nodes: [],
165
- data: Data.create({ latex, wrapper }),
166
- };
167
- const children = [];
168
-
169
- const out = serialization.serialize(object, children);
170
- log('out: ', out);
171
- expect(out).toEqual(
172
- <span data-latex="" data-raw={latex}>
173
- {expectedHtml}
174
- </span>,
175
- );
176
- });
177
- };
178
-
179
- assertSerialize('latex', '\\(latex\\)', MathPlugin.ROUND_BRACKETS);
180
- assertSerialize('latex', '\\(latex\\)', MathPlugin.SQUARE_BRACKETS);
181
- assertSerialize('latex', '$latex$', MathPlugin.DOLLAR);
182
- assertSerialize('latex', '$latex$', MathPlugin.DOUBLE_DOLLAR);
183
-
184
- /**
185
- * Note that when this is converted to html it get's escaped - but that's an issue with the slate html-serializer.
186
- */
187
- assertSerialize('<', '\\(<\\)');
188
- assertSerialize('x<y', '\\(x<y\\)');
189
- });
190
- });
191
- });
192
-
193
- describe('CustomToolbarComp', () => {
194
- let onDataChange;
195
- let onToolbarDone;
196
-
197
- const renderComponent = (extras = {}) => {
198
- let mockChange = new MockChange();
199
- const defaults = {
200
- node: {
201
- key: '1',
202
- data: Data.create({ latex: 'foo' }),
203
- equals: () => true,
204
- },
205
- value: {
206
- document: {
207
- getNextText: jest.fn().mockReturnValue({ key: 'nt' }),
208
- },
209
- change: jest.fn().mockReturnValue(mockChange),
210
- },
211
- onDataChange,
212
- onToolbarDone,
213
- };
214
-
215
- const props = {
216
- ...defaults,
217
- ...extras,
218
- };
219
-
220
- return render(<CustomToolbarComp {...props} />);
221
- };
222
-
223
- describe('render', () => {
224
- it('renders toolbar without default keypadMode', () => {
225
- renderComponent();
226
-
227
- // Verify the toolbar is rendered
228
- expect(screen.getByTestId('math-toolbar')).toBeInTheDocument();
229
- expect(screen.getByRole('toolbar')).toBeInTheDocument();
230
- });
231
-
232
- it('renders toolbar with default keypadMode', () => {
233
- renderComponent({ pluginProps: { math: { keypadMode: 'geometry' } } });
234
-
235
- // Verify the toolbar is rendered with keypadMode prop
236
- expect(screen.getByTestId('math-toolbar')).toBeInTheDocument();
237
- expect(screen.getByRole('toolbar')).toBeInTheDocument();
238
- });
239
- });
240
-
241
- describe('onDone', () => {
242
- it('calls onToolbarDone when done callback is triggered', () => {
243
- onToolbarDone = jest.fn();
244
- renderComponent();
245
-
246
- // Trigger the onDone callback that was passed to MathToolbar
247
- mockMathToolbarOnDone('oo');
248
-
249
- expect(onToolbarDone).toHaveBeenCalledWith(expect.anything(), false);
250
- });
251
- });
252
-
253
- describe('onChange', () => {
254
- it('calls onDataChange when change callback is triggered', () => {
255
- onDataChange = jest.fn();
256
- renderComponent();
257
-
258
- // Trigger the onChange callback that was passed to MathToolbar
259
- mockMathToolbarOnChange('oo');
260
-
261
- expect(onDataChange).toHaveBeenCalledWith('1', { latex: 'oo' });
262
- });
263
- });
264
- });