@pie-lib/editable-html-tip-tap 1.2.0-next.12 → 1.2.0-next.13
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.
- package/CHANGELOG.md +6 -0
- package/lib/components/CharacterPicker.js +1 -0
- package/lib/components/CharacterPicker.js.map +1 -1
- package/lib/components/EditableHtml.js +7 -0
- package/lib/components/EditableHtml.js.map +1 -1
- package/lib/components/MenuBar.js +40 -38
- package/lib/components/MenuBar.js.map +1 -1
- package/lib/components/respArea/InlineDropdown.js +8 -2
- package/lib/components/respArea/InlineDropdown.js.map +1 -1
- package/lib/extensions/math.js +1 -0
- package/lib/extensions/math.js.map +1 -1
- package/lib/extensions/responseArea.js +1 -1
- package/lib/extensions/responseArea.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/EditableHtml.test.jsx +35 -0
- package/src/components/CharacterPicker.jsx +1 -0
- package/src/components/EditableHtml.jsx +8 -0
- package/src/components/MenuBar.jsx +25 -22
- package/src/components/__tests__/CharacterPicker.test.jsx +22 -0
- package/src/components/__tests__/InlineDropdown.test.jsx +149 -0
- package/src/components/__tests__/MenuBar.test.jsx +32 -0
- package/src/components/respArea/InlineDropdown.jsx +10 -1
- package/src/extensions/__tests__/math.test.js +327 -0
- package/src/extensions/__tests__/responseArea.test.js +157 -0
- package/src/extensions/math.js +1 -0
- package/src/extensions/responseArea.js +1 -1
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, waitFor, fireEvent } from '@testing-library/react';
|
|
3
|
+
import { MathNode, MathNodeView } from '../math';
|
|
4
|
+
|
|
5
|
+
jest.mock('@tiptap/react', () => ({
|
|
6
|
+
NodeViewWrapper: ({ children, ...props }) => (
|
|
7
|
+
<div data-testid="node-view-wrapper" {...props}>
|
|
8
|
+
{children}
|
|
9
|
+
</div>
|
|
10
|
+
),
|
|
11
|
+
ReactNodeViewRenderer: jest.fn((component) => component),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
jest.mock('react-dom', () => ({
|
|
15
|
+
...jest.requireActual('react-dom'),
|
|
16
|
+
createPortal: (node) => node,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
jest.mock('@pie-lib/math-toolbar', () => {
|
|
20
|
+
const React = require('react');
|
|
21
|
+
return {
|
|
22
|
+
MathPreview: ({ latex }) => <div data-testid="math-preview">{latex}</div>,
|
|
23
|
+
MathToolbar: ({ latex, onChange, onDone }) => {
|
|
24
|
+
const [localLatex, setLocalLatex] = React.useState(latex);
|
|
25
|
+
return (
|
|
26
|
+
<div data-testid="math-toolbar">
|
|
27
|
+
<input
|
|
28
|
+
data-testid="math-input"
|
|
29
|
+
value={localLatex}
|
|
30
|
+
onChange={(e) => {
|
|
31
|
+
setLocalLatex(e.target.value);
|
|
32
|
+
onChange(e.target.value);
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
<button data-testid="done-button" onClick={() => onDone(localLatex)}>
|
|
36
|
+
Done
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
jest.mock('@pie-lib/math-rendering', () => ({
|
|
45
|
+
wrapMath: (latex, wrapper) => latex,
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
jest.mock('@tiptap/core', () => ({
|
|
49
|
+
Node: {
|
|
50
|
+
create: jest.fn((config) => config),
|
|
51
|
+
},
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
jest.mock('prosemirror-state', () => ({
|
|
55
|
+
Plugin: jest.fn(function (config) {
|
|
56
|
+
return config;
|
|
57
|
+
}),
|
|
58
|
+
PluginKey: jest.fn(function (key) {
|
|
59
|
+
this.key = key;
|
|
60
|
+
}),
|
|
61
|
+
TextSelection: {
|
|
62
|
+
create: jest.fn((doc, pos) => ({ type: 'text', pos })),
|
|
63
|
+
},
|
|
64
|
+
NodeSelection: {
|
|
65
|
+
create: jest.fn((doc, pos) => ({ type: 'node', pos })),
|
|
66
|
+
},
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
describe('MathNode', () => {
|
|
70
|
+
describe('configuration', () => {
|
|
71
|
+
it('has correct name', () => {
|
|
72
|
+
expect(MathNode.name).toBe('math');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('is inline', () => {
|
|
76
|
+
expect(MathNode.inline).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('is in inline group', () => {
|
|
80
|
+
expect(MathNode.group).toBe('inline');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('is atomic', () => {
|
|
84
|
+
expect(MathNode.atom).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('addAttributes', () => {
|
|
89
|
+
it('returns required attributes', () => {
|
|
90
|
+
const attributes = MathNode.addAttributes();
|
|
91
|
+
|
|
92
|
+
expect(attributes).toHaveProperty('latex');
|
|
93
|
+
expect(attributes).toHaveProperty('wrapper');
|
|
94
|
+
expect(attributes).toHaveProperty('html');
|
|
95
|
+
|
|
96
|
+
expect(attributes.latex).toEqual({ default: '' });
|
|
97
|
+
expect(attributes.wrapper).toEqual({ default: null });
|
|
98
|
+
expect(attributes.html).toEqual({ default: null });
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('parseHTML', () => {
|
|
103
|
+
it('returns parsing rules for latex', () => {
|
|
104
|
+
const rules = MathNode.parseHTML();
|
|
105
|
+
|
|
106
|
+
expect(Array.isArray(rules)).toBe(true);
|
|
107
|
+
expect(rules).toHaveLength(2);
|
|
108
|
+
expect(rules[0]).toHaveProperty('tag', 'span[data-latex]');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('returns parsing rules for mathml', () => {
|
|
112
|
+
const rules = MathNode.parseHTML();
|
|
113
|
+
expect(rules[1]).toHaveProperty('tag', 'span[data-type="mathml"]');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('renderHTML', () => {
|
|
118
|
+
it('renders mathml when html attribute is present', () => {
|
|
119
|
+
const result = MathNode.renderHTML({
|
|
120
|
+
HTMLAttributes: {
|
|
121
|
+
html: '<math><mi>x</mi></math>',
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
expect(result[0]).toBe('span');
|
|
126
|
+
expect(result[1]).toHaveProperty('data-type', 'mathml');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('renders latex when html attribute is not present', () => {
|
|
130
|
+
const result = MathNode.renderHTML({
|
|
131
|
+
HTMLAttributes: {
|
|
132
|
+
latex: 'x^2',
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(result[0]).toBe('span');
|
|
137
|
+
expect(result[1]).toHaveProperty('data-latex', '');
|
|
138
|
+
expect(result[1]).toHaveProperty('data-raw', 'x^2');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('addCommands', () => {
|
|
143
|
+
it('returns insertMath command', () => {
|
|
144
|
+
const commands = MathNode.addCommands();
|
|
145
|
+
|
|
146
|
+
expect(commands).toHaveProperty('insertMath');
|
|
147
|
+
expect(typeof commands.insertMath).toBe('function');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('addNodeView', () => {
|
|
152
|
+
it('returns ReactNodeViewRenderer result', () => {
|
|
153
|
+
const result = MathNode.addNodeView();
|
|
154
|
+
|
|
155
|
+
expect(result).toBeDefined();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('MathNodeView', () => {
|
|
161
|
+
const createMockEditor = () => ({
|
|
162
|
+
state: {
|
|
163
|
+
selection: {
|
|
164
|
+
from: 0,
|
|
165
|
+
to: 1,
|
|
166
|
+
},
|
|
167
|
+
tr: {
|
|
168
|
+
setSelection: jest.fn().mockReturnThis(),
|
|
169
|
+
},
|
|
170
|
+
doc: {},
|
|
171
|
+
},
|
|
172
|
+
view: {
|
|
173
|
+
coordsAtPos: jest.fn(() => ({ top: 100, left: 50 })),
|
|
174
|
+
dispatch: jest.fn(),
|
|
175
|
+
},
|
|
176
|
+
commands: {
|
|
177
|
+
focus: jest.fn(),
|
|
178
|
+
},
|
|
179
|
+
instanceId: 'editor-123',
|
|
180
|
+
_toolbarOpened: false,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const mockNode = {
|
|
184
|
+
attrs: {
|
|
185
|
+
latex: 'x^2',
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
let defaultProps;
|
|
190
|
+
|
|
191
|
+
beforeAll(() => {
|
|
192
|
+
Object.defineProperty(document.body, 'getBoundingClientRect', {
|
|
193
|
+
value: jest.fn(() => ({ top: 0, left: 0 })),
|
|
194
|
+
configurable: true,
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
beforeEach(() => {
|
|
199
|
+
jest.clearAllMocks();
|
|
200
|
+
defaultProps = {
|
|
201
|
+
node: mockNode,
|
|
202
|
+
updateAttributes: jest.fn(),
|
|
203
|
+
editor: createMockEditor(),
|
|
204
|
+
selected: false,
|
|
205
|
+
options: {},
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('renders without crashing', () => {
|
|
210
|
+
const { container } = render(<MathNodeView {...defaultProps} />);
|
|
211
|
+
expect(container).toBeInTheDocument();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('renders NodeViewWrapper', () => {
|
|
215
|
+
const { getByTestId } = render(<MathNodeView {...defaultProps} />);
|
|
216
|
+
expect(getByTestId('node-view-wrapper')).toBeInTheDocument();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('displays math preview', () => {
|
|
220
|
+
const { getByTestId } = render(<MathNodeView {...defaultProps} />);
|
|
221
|
+
expect(getByTestId('math-preview')).toBeInTheDocument();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('shows toolbar when selected', async () => {
|
|
225
|
+
const { getByTestId } = render(<MathNodeView {...defaultProps} selected={true} />);
|
|
226
|
+
await waitFor(() => {
|
|
227
|
+
expect(getByTestId('math-toolbar')).toBeInTheDocument();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('does not show toolbar when not selected', () => {
|
|
232
|
+
const { queryByTestId } = render(<MathNodeView {...defaultProps} selected={false} />);
|
|
233
|
+
expect(queryByTestId('math-toolbar')).not.toBeInTheDocument();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('adds data-toolbar-for attribute with editor instanceId', async () => {
|
|
237
|
+
const { container } = render(<MathNodeView {...defaultProps} selected={true} />);
|
|
238
|
+
await waitFor(() => {
|
|
239
|
+
const toolbar = container.querySelector('[data-toolbar-for]');
|
|
240
|
+
expect(toolbar).toHaveAttribute('data-toolbar-for', 'editor-123');
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('renders toolbar with correct position', async () => {
|
|
245
|
+
const { container } = render(<MathNodeView {...defaultProps} selected={true} />);
|
|
246
|
+
await waitFor(() => {
|
|
247
|
+
const toolbar = container.querySelector('[data-toolbar-for]');
|
|
248
|
+
expect(toolbar).toHaveStyle({ position: 'absolute' });
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('calls updateAttributes when latex changes', async () => {
|
|
253
|
+
const { getByTestId } = render(<MathNodeView {...defaultProps} selected={true} />);
|
|
254
|
+
await waitFor(() => {
|
|
255
|
+
const input = getByTestId('math-input');
|
|
256
|
+
fireEvent.change(input, { target: { value: 'y^2' } });
|
|
257
|
+
});
|
|
258
|
+
expect(defaultProps.updateAttributes).toHaveBeenCalledWith({ latex: 'y^2' });
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('closes toolbar and updates attributes when done', async () => {
|
|
262
|
+
const updateAttributes = jest.fn();
|
|
263
|
+
const { getByTestId } = render(
|
|
264
|
+
<MathNodeView {...defaultProps} updateAttributes={updateAttributes} selected={true} />,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
await waitFor(() => {
|
|
268
|
+
expect(getByTestId('done-button')).toBeInTheDocument();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const doneButton = getByTestId('done-button');
|
|
272
|
+
fireEvent.click(doneButton);
|
|
273
|
+
|
|
274
|
+
await waitFor(() => {
|
|
275
|
+
expect(updateAttributes).toHaveBeenCalledWith({ latex: 'x^2' });
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('sets editor._toolbarOpened when toolbar is shown', async () => {
|
|
280
|
+
const { getByTestId } = render(<MathNodeView {...defaultProps} selected={true} />);
|
|
281
|
+
await waitFor(() => {
|
|
282
|
+
expect(getByTestId('math-toolbar')).toBeInTheDocument();
|
|
283
|
+
expect(defaultProps.editor._toolbarOpened).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('unsets editor._toolbarOpened when toolbar is closed', async () => {
|
|
288
|
+
const { getByTestId } = render(<MathNodeView {...defaultProps} selected={true} />);
|
|
289
|
+
|
|
290
|
+
await waitFor(() => {
|
|
291
|
+
expect(getByTestId('done-button')).toBeInTheDocument();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const doneButton = getByTestId('done-button');
|
|
295
|
+
fireEvent.click(doneButton);
|
|
296
|
+
|
|
297
|
+
await waitFor(() => {
|
|
298
|
+
expect(defaultProps.editor._toolbarOpened).toBe(false);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('closes toolbar on outside click', async () => {
|
|
303
|
+
const { queryByTestId } = render(<MathNodeView {...defaultProps} selected={true} />);
|
|
304
|
+
|
|
305
|
+
await waitFor(() => {
|
|
306
|
+
expect(queryByTestId('math-toolbar')).toBeInTheDocument();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
fireEvent.mouseDown(document.body);
|
|
310
|
+
|
|
311
|
+
await waitFor(() => {
|
|
312
|
+
expect(queryByTestId('math-toolbar')).not.toBeInTheDocument();
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('renders with empty latex', () => {
|
|
317
|
+
const nodeWithEmptyLatex = { attrs: { latex: '' } };
|
|
318
|
+
const { getByTestId } = render(<MathNodeView {...defaultProps} node={nodeWithEmptyLatex} />);
|
|
319
|
+
expect(getByTestId('math-preview')).toBeInTheDocument();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('has correct styling on NodeViewWrapper', () => {
|
|
323
|
+
const { getByTestId } = render(<MathNodeView {...defaultProps} />);
|
|
324
|
+
const wrapper = getByTestId('node-view-wrapper');
|
|
325
|
+
expect(wrapper).toHaveStyle({ display: 'inline-flex', cursor: 'pointer' });
|
|
326
|
+
});
|
|
327
|
+
});
|
|
@@ -101,6 +101,163 @@ describe('ResponseAreaExtension', () => {
|
|
|
101
101
|
expect(commands).toHaveProperty('insertResponseArea');
|
|
102
102
|
expect(typeof commands.insertResponseArea).toBe('function');
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
it('returns refreshResponseArea command', () => {
|
|
106
|
+
const commands = ResponseAreaExtension.addCommands();
|
|
107
|
+
|
|
108
|
+
expect(commands).toHaveProperty('refreshResponseArea');
|
|
109
|
+
expect(typeof commands.refreshResponseArea).toBe('function');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('refreshResponseArea handles node with attrs safely', () => {
|
|
113
|
+
const context = {
|
|
114
|
+
options: {
|
|
115
|
+
type: 'explicit-constructed-response',
|
|
116
|
+
maxResponseAreas: 5,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const commands = ResponseAreaExtension.addCommands.call(context);
|
|
121
|
+
const refreshCommand = commands.refreshResponseArea();
|
|
122
|
+
|
|
123
|
+
// Mock transaction and state
|
|
124
|
+
const mockNode = {
|
|
125
|
+
attrs: {
|
|
126
|
+
index: '0',
|
|
127
|
+
value: 'test',
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const mockTr = {
|
|
132
|
+
setNodeMarkup: jest.fn(),
|
|
133
|
+
setSelection: jest.fn(),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const mockState = {
|
|
137
|
+
selection: {
|
|
138
|
+
from: 0,
|
|
139
|
+
$from: {
|
|
140
|
+
nodeAfter: mockNode,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
tr: mockTr,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const mockCommands = {
|
|
147
|
+
focus: jest.fn(),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const mockDispatch = jest.fn();
|
|
151
|
+
|
|
152
|
+
refreshCommand({
|
|
153
|
+
tr: mockTr,
|
|
154
|
+
state: mockState,
|
|
155
|
+
commands: mockCommands,
|
|
156
|
+
dispatch: mockDispatch,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(mockTr.setNodeMarkup).toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('refreshResponseArea handles node without attrs safely (optional chaining)', () => {
|
|
163
|
+
const context = {
|
|
164
|
+
options: {
|
|
165
|
+
type: 'explicit-constructed-response',
|
|
166
|
+
maxResponseAreas: 5,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const commands = ResponseAreaExtension.addCommands.call(context);
|
|
171
|
+
const refreshCommand = commands.refreshResponseArea();
|
|
172
|
+
|
|
173
|
+
// Mock transaction and state with node that has no attrs
|
|
174
|
+
const mockNode = null;
|
|
175
|
+
|
|
176
|
+
const mockTr = {
|
|
177
|
+
setNodeMarkup: jest.fn(),
|
|
178
|
+
setSelection: jest.fn(),
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const mockState = {
|
|
182
|
+
selection: {
|
|
183
|
+
from: 0,
|
|
184
|
+
$from: {
|
|
185
|
+
nodeAfter: mockNode,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
tr: mockTr,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const mockCommands = {
|
|
192
|
+
focus: jest.fn(),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const mockDispatch = jest.fn();
|
|
196
|
+
|
|
197
|
+
// This should not throw an error due to optional chaining on node?.attrs
|
|
198
|
+
expect(() => {
|
|
199
|
+
refreshCommand({
|
|
200
|
+
tr: mockTr,
|
|
201
|
+
state: mockState,
|
|
202
|
+
commands: mockCommands,
|
|
203
|
+
dispatch: mockDispatch,
|
|
204
|
+
});
|
|
205
|
+
}).not.toThrow();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('refreshResponseArea updates timestamp in node attributes', () => {
|
|
209
|
+
const context = {
|
|
210
|
+
options: {
|
|
211
|
+
type: 'explicit-constructed-response',
|
|
212
|
+
maxResponseAreas: 5,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const commands = ResponseAreaExtension.addCommands.call(context);
|
|
217
|
+
const refreshCommand = commands.refreshResponseArea();
|
|
218
|
+
|
|
219
|
+
const mockNode = {
|
|
220
|
+
attrs: {
|
|
221
|
+
index: '0',
|
|
222
|
+
value: 'test',
|
|
223
|
+
updated: '1234567890',
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const mockTr = {
|
|
228
|
+
setNodeMarkup: jest.fn((pos, type, attrs) => {
|
|
229
|
+
// Verify that updated timestamp is being set
|
|
230
|
+
expect(attrs.updated).toBeDefined();
|
|
231
|
+
expect(attrs.updated).not.toBe('1234567890');
|
|
232
|
+
}),
|
|
233
|
+
setSelection: jest.fn(),
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const mockState = {
|
|
237
|
+
selection: {
|
|
238
|
+
from: 0,
|
|
239
|
+
$from: {
|
|
240
|
+
nodeAfter: mockNode,
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
tr: mockTr,
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const mockCommands = {
|
|
247
|
+
focus: jest.fn(),
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const mockDispatch = jest.fn();
|
|
251
|
+
|
|
252
|
+
refreshCommand({
|
|
253
|
+
tr: mockTr,
|
|
254
|
+
state: mockState,
|
|
255
|
+
commands: mockCommands,
|
|
256
|
+
dispatch: mockDispatch,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(mockTr.setNodeMarkup).toHaveBeenCalled();
|
|
260
|
+
});
|
|
104
261
|
});
|
|
105
262
|
});
|
|
106
263
|
|
package/src/extensions/math.js
CHANGED
|
@@ -209,7 +209,7 @@ export const ResponseAreaExtension = Extension.create({
|
|
|
209
209
|
const node = selection.$from.nodeAfter;
|
|
210
210
|
const nodePos = selection.from;
|
|
211
211
|
|
|
212
|
-
tr.setNodeMarkup(nodePos, undefined, { ...node
|
|
212
|
+
tr.setNodeMarkup(nodePos, undefined, { ...node?.attrs, updated: `${Date.now()}` });
|
|
213
213
|
tr.setSelection(NodeSelection.create(tr.doc, nodePos));
|
|
214
214
|
|
|
215
215
|
if (dispatch) {
|