@pie-lib/editable-html-tip-tap 2.1.2-next.29 → 2.1.2-next.30

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 (276) hide show
  1. package/dist/components/CharacterPicker.d.ts +31 -0
  2. package/dist/components/CharacterPicker.js +131 -0
  3. package/dist/components/EditableHtml.d.ts +11 -0
  4. package/dist/components/EditableHtml.js +291 -0
  5. package/dist/components/MenuBar.d.ts +11 -0
  6. package/dist/components/MenuBar.js +462 -0
  7. package/dist/components/TiptapContainer.d.ts +11 -0
  8. package/dist/components/TiptapContainer.js +154 -0
  9. package/dist/components/characters/characterUtils.d.ts +35 -0
  10. package/dist/components/characters/characterUtils.js +465 -0
  11. package/dist/components/characters/custom-popper.d.ts +14 -0
  12. package/dist/components/characters/custom-popper.js +32 -0
  13. package/dist/components/common/done-button.d.ts +30 -0
  14. package/dist/components/common/done-button.js +26 -0
  15. package/dist/components/common/toolbar-buttons.d.ts +38 -0
  16. package/dist/components/common/toolbar-buttons.js +91 -0
  17. package/dist/components/icons/CssIcon.d.ts +11 -0
  18. package/dist/components/icons/CssIcon.js +14 -0
  19. package/dist/components/icons/RespArea.d.ts +26 -0
  20. package/dist/components/icons/RespArea.js +42 -0
  21. package/dist/components/icons/TableIcons.d.ts +14 -0
  22. package/dist/components/icons/TableIcons.js +32 -0
  23. package/dist/components/icons/TextAlign.d.ts +18 -0
  24. package/dist/components/icons/TextAlign.js +134 -0
  25. package/dist/components/image/AltDialog.d.ts +22 -0
  26. package/dist/components/image/AltDialog.js +61 -0
  27. package/dist/components/image/ImageToolbar.d.ts +24 -0
  28. package/dist/components/image/ImageToolbar.js +80 -0
  29. package/dist/components/image/InsertImageHandler.d.ts +32 -0
  30. package/dist/components/image/InsertImageHandler.js +53 -0
  31. package/dist/components/media/MediaDialog.d.ts +43 -0
  32. package/dist/components/media/MediaDialog.js +389 -0
  33. package/dist/components/media/MediaToolbar.d.ts +19 -0
  34. package/dist/components/media/MediaToolbar.js +41 -0
  35. package/dist/components/media/MediaWrapper.d.ts +19 -0
  36. package/dist/components/respArea/DragInTheBlank/DragInTheBlank.d.ts +23 -0
  37. package/dist/components/respArea/DragInTheBlank/DragInTheBlank.js +58 -0
  38. package/dist/components/respArea/DragInTheBlank/choice.d.ts +56 -0
  39. package/dist/components/respArea/DragInTheBlank/choice.js +156 -0
  40. package/dist/components/respArea/ExplicitConstructedResponse.d.ts +20 -0
  41. package/dist/components/respArea/ExplicitConstructedResponse.js +83 -0
  42. package/dist/components/respArea/InlineDropdown.d.ts +18 -0
  43. package/dist/components/respArea/InlineDropdown.js +119 -0
  44. package/dist/components/respArea/MathTemplated.d.ts +19 -0
  45. package/dist/components/respArea/MathTemplated.js +97 -0
  46. package/dist/components/respArea/ToolbarIcon.d.ts +14 -0
  47. package/dist/components/respArea/ToolbarIcon.js +17 -0
  48. package/dist/components/respArea/inlineDropdownUtils.d.ts +15 -0
  49. package/dist/components/respArea/inlineDropdownUtils.js +15 -0
  50. package/dist/constants.d.ts +13 -0
  51. package/dist/constants.js +4 -0
  52. package/dist/extensions/css.d.ts +11 -0
  53. package/dist/extensions/css.js +115 -0
  54. package/dist/extensions/custom-toolbar-wrapper.d.ts +11 -0
  55. package/dist/extensions/custom-toolbar-wrapper.js +61 -0
  56. package/dist/extensions/div-node.d.ts +10 -0
  57. package/dist/extensions/div-node.js +42 -0
  58. package/dist/extensions/ensure-empty-root-div.d.ts +14 -0
  59. package/dist/extensions/ensure-empty-root-div.js +24 -0
  60. package/dist/extensions/ensure-list-item-content-is-div.d.ts +15 -0
  61. package/dist/extensions/ensure-list-item-content-is-div.js +31 -0
  62. package/dist/extensions/extended-list-item.d.ts +13 -0
  63. package/dist/extensions/extended-list-item.js +5 -0
  64. package/dist/extensions/extended-table-cell.d.ts +10 -0
  65. package/dist/extensions/extended-table-cell.js +6 -0
  66. package/dist/extensions/extended-table.d.ts +17 -0
  67. package/dist/extensions/extended-table.js +34 -0
  68. package/dist/extensions/heading-paragraph.d.ts +17 -0
  69. package/dist/extensions/heading-paragraph.js +30 -0
  70. package/dist/extensions/image-component.d.ts +22 -0
  71. package/dist/extensions/image-component.js +220 -0
  72. package/dist/extensions/image.d.ts +10 -0
  73. package/dist/extensions/image.js +68 -0
  74. package/dist/extensions/index.d.ts +16 -0
  75. package/dist/extensions/index.js +64 -0
  76. package/dist/extensions/math.d.ts +15 -0
  77. package/dist/extensions/math.js +158 -0
  78. package/dist/extensions/media.d.ts +19 -0
  79. package/dist/extensions/media.js +149 -0
  80. package/dist/extensions/responseArea.d.ts +27 -0
  81. package/dist/extensions/responseArea.js +259 -0
  82. package/dist/index.d.ts +13 -0
  83. package/dist/index.js +7 -0
  84. package/dist/node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.js +16 -0
  85. package/dist/styles/editorContainerStyles.d.ts +134 -0
  86. package/dist/theme.d.ts +9 -0
  87. package/dist/utils/helper.d.ts +9 -0
  88. package/dist/utils/helper.js +27 -0
  89. package/dist/utils/size.d.ts +9 -0
  90. package/dist/utils/size.js +14 -0
  91. package/package.json +40 -24
  92. package/CHANGELOG.json +0 -32
  93. package/CHANGELOG.md +0 -2532
  94. package/LICENSE.md +0 -5
  95. package/lib/components/CharacterPicker.js +0 -201
  96. package/lib/components/CharacterPicker.js.map +0 -1
  97. package/lib/components/EditableHtml.js +0 -376
  98. package/lib/components/EditableHtml.js.map +0 -1
  99. package/lib/components/MenuBar.js +0 -696
  100. package/lib/components/MenuBar.js.map +0 -1
  101. package/lib/components/TiptapContainer.js +0 -234
  102. package/lib/components/TiptapContainer.js.map +0 -1
  103. package/lib/components/characters/characterUtils.js +0 -378
  104. package/lib/components/characters/characterUtils.js.map +0 -1
  105. package/lib/components/characters/custom-popper.js +0 -44
  106. package/lib/components/characters/custom-popper.js.map +0 -1
  107. package/lib/components/common/done-button.js +0 -34
  108. package/lib/components/common/done-button.js.map +0 -1
  109. package/lib/components/common/toolbar-buttons.js +0 -144
  110. package/lib/components/common/toolbar-buttons.js.map +0 -1
  111. package/lib/components/icons/CssIcon.js +0 -25
  112. package/lib/components/icons/CssIcon.js.map +0 -1
  113. package/lib/components/icons/RespArea.js +0 -72
  114. package/lib/components/icons/RespArea.js.map +0 -1
  115. package/lib/components/icons/TableIcons.js +0 -53
  116. package/lib/components/icons/TableIcons.js.map +0 -1
  117. package/lib/components/icons/TextAlign.js +0 -157
  118. package/lib/components/icons/TextAlign.js.map +0 -1
  119. package/lib/components/image/AltDialog.js +0 -98
  120. package/lib/components/image/AltDialog.js.map +0 -1
  121. package/lib/components/image/ImageToolbar.js +0 -137
  122. package/lib/components/image/ImageToolbar.js.map +0 -1
  123. package/lib/components/image/InsertImageHandler.js +0 -135
  124. package/lib/components/image/InsertImageHandler.js.map +0 -1
  125. package/lib/components/media/MediaDialog.js +0 -594
  126. package/lib/components/media/MediaDialog.js.map +0 -1
  127. package/lib/components/media/MediaToolbar.js +0 -74
  128. package/lib/components/media/MediaToolbar.js.map +0 -1
  129. package/lib/components/media/MediaWrapper.js +0 -67
  130. package/lib/components/media/MediaWrapper.js.map +0 -1
  131. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js +0 -84
  132. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js.map +0 -1
  133. package/lib/components/respArea/DragInTheBlank/choice.js +0 -250
  134. package/lib/components/respArea/DragInTheBlank/choice.js.map +0 -1
  135. package/lib/components/respArea/ExplicitConstructedResponse.js +0 -136
  136. package/lib/components/respArea/ExplicitConstructedResponse.js.map +0 -1
  137. package/lib/components/respArea/InlineDropdown.js +0 -209
  138. package/lib/components/respArea/InlineDropdown.js.map +0 -1
  139. package/lib/components/respArea/MathTemplated.js +0 -130
  140. package/lib/components/respArea/MathTemplated.js.map +0 -1
  141. package/lib/components/respArea/ToolbarIcon.js +0 -81
  142. package/lib/components/respArea/ToolbarIcon.js.map +0 -1
  143. package/lib/components/respArea/inlineDropdownUtils.js +0 -67
  144. package/lib/components/respArea/inlineDropdownUtils.js.map +0 -1
  145. package/lib/constants.js +0 -11
  146. package/lib/constants.js.map +0 -1
  147. package/lib/extensions/css.js +0 -217
  148. package/lib/extensions/css.js.map +0 -1
  149. package/lib/extensions/custom-toolbar-wrapper.js +0 -92
  150. package/lib/extensions/custom-toolbar-wrapper.js.map +0 -1
  151. package/lib/extensions/div-node.js +0 -83
  152. package/lib/extensions/div-node.js.map +0 -1
  153. package/lib/extensions/ensure-empty-root-div.js +0 -48
  154. package/lib/extensions/ensure-empty-root-div.js.map +0 -1
  155. package/lib/extensions/ensure-list-item-content-is-div.js +0 -64
  156. package/lib/extensions/ensure-list-item-content-is-div.js.map +0 -1
  157. package/lib/extensions/extended-list-item.js +0 -15
  158. package/lib/extensions/extended-list-item.js.map +0 -1
  159. package/lib/extensions/extended-table-cell.js +0 -22
  160. package/lib/extensions/extended-table-cell.js.map +0 -1
  161. package/lib/extensions/extended-table.js +0 -75
  162. package/lib/extensions/extended-table.js.map +0 -1
  163. package/lib/extensions/heading-paragraph.js +0 -61
  164. package/lib/extensions/heading-paragraph.js.map +0 -1
  165. package/lib/extensions/image-component.js +0 -348
  166. package/lib/extensions/image-component.js.map +0 -1
  167. package/lib/extensions/image.js +0 -134
  168. package/lib/extensions/image.js.map +0 -1
  169. package/lib/extensions/index.js +0 -46
  170. package/lib/extensions/index.js.map +0 -1
  171. package/lib/extensions/math.js +0 -342
  172. package/lib/extensions/math.js.map +0 -1
  173. package/lib/extensions/media.js +0 -243
  174. package/lib/extensions/media.js.map +0 -1
  175. package/lib/extensions/responseArea.js +0 -446
  176. package/lib/extensions/responseArea.js.map +0 -1
  177. package/lib/index.js +0 -37
  178. package/lib/index.js.map +0 -1
  179. package/lib/styles/editorContainerStyles.js +0 -137
  180. package/lib/styles/editorContainerStyles.js.map +0 -1
  181. package/lib/theme.js +0 -8
  182. package/lib/theme.js.map +0 -1
  183. package/lib/utils/helper.js +0 -73
  184. package/lib/utils/helper.js.map +0 -1
  185. package/lib/utils/size.js +0 -26
  186. package/lib/utils/size.js.map +0 -1
  187. package/src/__tests__/EditableHtml.test.jsx +0 -554
  188. package/src/__tests__/constants.test.js +0 -19
  189. package/src/__tests__/div-to-paragraph-conversion.test.jsx +0 -125
  190. package/src/__tests__/extensions.test.js +0 -208
  191. package/src/__tests__/index.test.jsx +0 -154
  192. package/src/__tests__/size-utils.test.js +0 -64
  193. package/src/__tests__/theme.test.js +0 -17
  194. package/src/components/CharacterPicker.jsx +0 -207
  195. package/src/components/EditableHtml.jsx +0 -440
  196. package/src/components/MenuBar.jsx +0 -554
  197. package/src/components/TiptapContainer.jsx +0 -219
  198. package/src/components/__tests__/AltDialog.test.jsx +0 -147
  199. package/src/components/__tests__/CharacterPicker.test.jsx +0 -261
  200. package/src/components/__tests__/CssIcon.test.jsx +0 -46
  201. package/src/components/__tests__/DragInTheBlank.test.jsx +0 -255
  202. package/src/components/__tests__/ExplicitConstructedResponse.test.jsx +0 -204
  203. package/src/components/__tests__/ImageToolbar.test.jsx +0 -128
  204. package/src/components/__tests__/InlineDropdown.test.jsx +0 -388
  205. package/src/components/__tests__/InsertImageHandler.test.js +0 -161
  206. package/src/components/__tests__/MediaDialog.test.jsx +0 -293
  207. package/src/components/__tests__/MediaToolbar.test.jsx +0 -74
  208. package/src/components/__tests__/MediaWrapper.test.jsx +0 -81
  209. package/src/components/__tests__/MenuBar.test.jsx +0 -250
  210. package/src/components/__tests__/RespArea.test.jsx +0 -122
  211. package/src/components/__tests__/TableIcons.test.jsx +0 -149
  212. package/src/components/__tests__/TextAlign.test.jsx +0 -167
  213. package/src/components/__tests__/TiptapContainer.test.jsx +0 -138
  214. package/src/components/__tests__/characterUtils.test.js +0 -166
  215. package/src/components/__tests__/choice.test.jsx +0 -171
  216. package/src/components/__tests__/custom-popper.test.jsx +0 -82
  217. package/src/components/__tests__/done-button.test.jsx +0 -54
  218. package/src/components/__tests__/toolbar-buttons.test.jsx +0 -234
  219. package/src/components/characters/characterUtils.js +0 -447
  220. package/src/components/characters/custom-popper.js +0 -38
  221. package/src/components/common/done-button.jsx +0 -27
  222. package/src/components/common/toolbar-buttons.jsx +0 -122
  223. package/src/components/icons/CssIcon.jsx +0 -15
  224. package/src/components/icons/RespArea.jsx +0 -71
  225. package/src/components/icons/TableIcons.jsx +0 -52
  226. package/src/components/icons/TextAlign.jsx +0 -114
  227. package/src/components/image/AltDialog.jsx +0 -82
  228. package/src/components/image/ImageToolbar.jsx +0 -99
  229. package/src/components/image/InsertImageHandler.js +0 -107
  230. package/src/components/media/MediaDialog.jsx +0 -596
  231. package/src/components/media/MediaToolbar.jsx +0 -49
  232. package/src/components/media/MediaWrapper.jsx +0 -39
  233. package/src/components/respArea/DragInTheBlank/DragInTheBlank.jsx +0 -76
  234. package/src/components/respArea/DragInTheBlank/choice.jsx +0 -256
  235. package/src/components/respArea/ExplicitConstructedResponse.jsx +0 -135
  236. package/src/components/respArea/InlineDropdown.jsx +0 -220
  237. package/src/components/respArea/MathTemplated.jsx +0 -124
  238. package/src/components/respArea/ToolbarIcon.jsx +0 -66
  239. package/src/components/respArea/__tests__/MathTemplated.test.jsx +0 -210
  240. package/src/components/respArea/inlineDropdownUtils.js +0 -79
  241. package/src/constants.js +0 -5
  242. package/src/extensions/__tests__/css.test.js +0 -196
  243. package/src/extensions/__tests__/custom-toolbar-wrapper.test.jsx +0 -180
  244. package/src/extensions/__tests__/divNode.test.js +0 -87
  245. package/src/extensions/__tests__/ensure-empty-root-div.test.js +0 -57
  246. package/src/extensions/__tests__/ensure-list-item-content-is-div.test.js +0 -44
  247. package/src/extensions/__tests__/extended-list-item.test.js +0 -13
  248. package/src/extensions/__tests__/extended-table-cell.test.js +0 -22
  249. package/src/extensions/__tests__/extended-table.test.js +0 -183
  250. package/src/extensions/__tests__/image-component.test.jsx +0 -345
  251. package/src/extensions/__tests__/image.test.js +0 -237
  252. package/src/extensions/__tests__/math.test.js +0 -603
  253. package/src/extensions/__tests__/media-node-view.test.jsx +0 -298
  254. package/src/extensions/__tests__/media.test.js +0 -271
  255. package/src/extensions/__tests__/responseArea.test.js +0 -601
  256. package/src/extensions/css.js +0 -220
  257. package/src/extensions/custom-toolbar-wrapper.jsx +0 -78
  258. package/src/extensions/div-node.js +0 -86
  259. package/src/extensions/ensure-empty-root-div.js +0 -47
  260. package/src/extensions/ensure-list-item-content-is-div.js +0 -62
  261. package/src/extensions/extended-list-item.js +0 -10
  262. package/src/extensions/extended-table-cell.js +0 -19
  263. package/src/extensions/extended-table.js +0 -60
  264. package/src/extensions/heading-paragraph.js +0 -53
  265. package/src/extensions/image-component.jsx +0 -338
  266. package/src/extensions/image.js +0 -109
  267. package/src/extensions/index.js +0 -81
  268. package/src/extensions/math.js +0 -326
  269. package/src/extensions/media.js +0 -188
  270. package/src/extensions/responseArea.js +0 -401
  271. package/src/index.jsx +0 -5
  272. package/src/styles/editorContainerStyles.js +0 -145
  273. package/src/theme.js +0 -1
  274. package/src/utils/__tests__/helper.test.js +0 -126
  275. package/src/utils/helper.js +0 -69
  276. package/src/utils/size.js +0 -32
@@ -1,603 +0,0 @@
1
- import React from 'react';
2
- import { render, waitFor, fireEvent } from '@testing-library/react';
3
- import { EnsureTextAfterMathPlugin, MathNode, MathNodeView, ZeroWidthSpaceHandlingPlugin } 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
- const mockCreatePortal = jest.fn((node) => node);
15
- jest.mock('react-dom', () => ({
16
- ...jest.requireActual('react-dom'),
17
- createPortal: (...args) => mockCreatePortal(...args),
18
- }));
19
-
20
- jest.mock('@pie-lib/math-toolbar', () => {
21
- const React = require('react');
22
- return {
23
- MathPreview: ({ latex }) => <div data-testid="math-preview">{latex}</div>,
24
- MathToolbar: ({ latex, onChange, onDone }) => {
25
- const [localLatex, setLocalLatex] = React.useState(latex);
26
- return (
27
- <div data-testid="math-toolbar">
28
- <input
29
- data-testid="math-input"
30
- value={localLatex}
31
- onChange={(e) => {
32
- setLocalLatex(e.target.value);
33
- onChange(e.target.value);
34
- }}
35
- />
36
- <button data-testid="done-button" onClick={() => onDone(localLatex)}>
37
- Done
38
- </button>
39
- </div>
40
- );
41
- },
42
- };
43
- });
44
-
45
- jest.mock('@pie-lib/math-rendering', () => ({
46
- wrapMath: (latex, wrapper) => latex,
47
- }));
48
-
49
- jest.mock('@tiptap/core', () => ({
50
- Node: {
51
- create: jest.fn((config) => config),
52
- },
53
- }));
54
-
55
- jest.mock('prosemirror-state', () => ({
56
- Plugin: jest.fn(function (config) {
57
- return config;
58
- }),
59
- PluginKey: jest.fn(function (key) {
60
- this.key = key;
61
- }),
62
- TextSelection: {
63
- create: jest.fn((doc, pos) => ({ type: 'text', pos })),
64
- },
65
- NodeSelection: {
66
- create: jest.fn((doc, pos) => ({ type: 'node', pos })),
67
- },
68
- }));
69
-
70
- describe('MathNode', () => {
71
- describe('configuration', () => {
72
- it('has correct name', () => {
73
- expect(MathNode.name).toBe('math');
74
- });
75
-
76
- it('is inline', () => {
77
- expect(MathNode.inline).toBe(true);
78
- });
79
-
80
- it('is in inline group', () => {
81
- expect(MathNode.group).toBe('inline');
82
- });
83
-
84
- it('is atomic', () => {
85
- expect(MathNode.atom).toBe(true);
86
- });
87
- });
88
-
89
- describe('addAttributes', () => {
90
- it('returns required attributes', () => {
91
- const attributes = MathNode.addAttributes();
92
-
93
- expect(attributes).toHaveProperty('latex');
94
- expect(attributes).toHaveProperty('wrapper');
95
- expect(attributes).toHaveProperty('html');
96
-
97
- expect(attributes.latex).toEqual({ default: '' });
98
- expect(attributes.wrapper).toEqual({ default: null });
99
- expect(attributes.html).toEqual({ default: null });
100
- });
101
- });
102
-
103
- describe('parseHTML', () => {
104
- it('returns parsing rules for latex', () => {
105
- const rules = MathNode.parseHTML();
106
-
107
- expect(Array.isArray(rules)).toBe(true);
108
- expect(rules).toHaveLength(2);
109
- expect(rules[0]).toHaveProperty('tag', 'span[data-latex]');
110
- });
111
-
112
- it('returns parsing rules for mathml', () => {
113
- const rules = MathNode.parseHTML();
114
- expect(rules[1]).toHaveProperty('tag', 'span[data-type="mathml"]');
115
- });
116
- });
117
-
118
- describe('renderHTML', () => {
119
- it('renders mathml when html attribute is present', () => {
120
- const result = MathNode.renderHTML({
121
- HTMLAttributes: {
122
- html: '<math><mi>x</mi></math>',
123
- },
124
- });
125
-
126
- expect(result[0]).toBe('span');
127
- expect(result[1]).toHaveProperty('data-type', 'mathml');
128
- });
129
-
130
- it('renders latex when html attribute is not present', () => {
131
- const result = MathNode.renderHTML({
132
- HTMLAttributes: {
133
- latex: 'x^2',
134
- },
135
- });
136
-
137
- expect(result[0]).toBe('span');
138
- expect(result[1]).toHaveProperty('data-latex', '');
139
- expect(result[1]).toHaveProperty('data-raw', 'x^2');
140
- });
141
- });
142
-
143
- describe('addCommands', () => {
144
- it('returns insertMath command', () => {
145
- const commands = MathNode.addCommands();
146
-
147
- expect(commands).toHaveProperty('insertMath');
148
- expect(typeof commands.insertMath).toBe('function');
149
- });
150
- });
151
-
152
- describe('addNodeView', () => {
153
- it('returns ReactNodeViewRenderer result', () => {
154
- const result = MathNode.addNodeView();
155
-
156
- expect(result).toBeDefined();
157
- });
158
- });
159
-
160
- describe('addProseMirrorPlugins', () => {
161
- it('registers ensure-text-after-math and zero-width-space plugins', () => {
162
- const plugins = MathNode.addProseMirrorPlugins();
163
-
164
- expect(plugins).toHaveLength(2);
165
- expect(plugins[0].appendTransaction).toBeDefined();
166
- expect(plugins[1].props.handleKeyDown).toBeDefined();
167
- });
168
- });
169
- });
170
-
171
- describe('EnsureTextAfterMathPlugin', () => {
172
- it('inserts a zero-width space after a math node when no text follows', () => {
173
- const plugin = EnsureTextAfterMathPlugin('math');
174
- const textNode = { type: { name: 'text' } };
175
- const mathNode = { type: { name: 'math' }, nodeSize: 3 };
176
- const tr = { insert: jest.fn() };
177
-
178
- const newState = {
179
- schema: { text: jest.fn((value) => ({ type: textNode.type, text: value })) },
180
- tr,
181
- doc: {
182
- descendants: (cb) => cb(mathNode, 5),
183
- nodeAt: jest.fn(() => null),
184
- },
185
- };
186
-
187
- const result = plugin.appendTransaction([{ docChanged: true }], {}, newState);
188
-
189
- expect(tr.insert).toHaveBeenCalledWith(8, expect.anything());
190
- expect(result).toBe(tr);
191
- });
192
-
193
- it('does not insert when text already follows the math node', () => {
194
- const plugin = EnsureTextAfterMathPlugin('math');
195
- const tr = { insert: jest.fn() };
196
- const mathNode = { type: { name: 'math' }, nodeSize: 3 };
197
-
198
- const newState = {
199
- schema: { text: jest.fn() },
200
- tr,
201
- doc: {
202
- descendants: (cb) => cb(mathNode, 5),
203
- nodeAt: jest.fn(() => ({ type: { name: 'text' } })),
204
- },
205
- };
206
-
207
- const result = plugin.appendTransaction([{ docChanged: true }], {}, newState);
208
-
209
- expect(tr.insert).not.toHaveBeenCalled();
210
- expect(result).toBeNull();
211
- });
212
-
213
- it('returns null when the document did not change', () => {
214
- const plugin = EnsureTextAfterMathPlugin('math');
215
-
216
- const result = plugin.appendTransaction([{ docChanged: false }], {}, {});
217
- expect(result).toBeNull();
218
- });
219
- });
220
-
221
- describe('ZeroWidthSpaceHandlingPlugin', () => {
222
- const createDefaultDoc = () => ({
223
- textBetween: jest.fn(() => '\u200b'),
224
- resolve: jest.fn(() => ({
225
- nodeAfter: null,
226
- nodeBefore: null,
227
- })),
228
- });
229
-
230
- const createView = ({ state: stateOverrides = {} } = {}) => {
231
- const dispatch = jest.fn();
232
- const tr = {
233
- delete: jest.fn().mockReturnThis(),
234
- setSelection: jest.fn().mockReturnThis(),
235
- };
236
-
237
- return {
238
- state: {
239
- selection: { from: 2, empty: true },
240
- doc: createDefaultDoc(),
241
- tr,
242
- ...stateOverrides,
243
- doc: { ...createDefaultDoc(), ...stateOverrides.doc },
244
- },
245
- dispatch,
246
- };
247
- };
248
-
249
- it('deletes math and zero-width space on Backspace', () => {
250
- const view = createView();
251
- const event = { key: 'Backspace' };
252
- const handled = ZeroWidthSpaceHandlingPlugin.props.handleKeyDown(view, event);
253
-
254
- expect(handled).toBe(true);
255
- expect(view.state.tr.delete).toHaveBeenCalledWith(0, 2);
256
- expect(view.dispatch).toHaveBeenCalledWith(view.state.tr);
257
- });
258
-
259
- it('selects the math node on ArrowLeft before a zero-width space', () => {
260
- const mathNode = { nodeSize: 3 };
261
- const view = createView({
262
- state: {
263
- doc: {
264
- resolve: jest
265
- .fn()
266
- .mockReturnValueOnce({ nodeAfter: mathNode, nodeBefore: null })
267
- .mockReturnValueOnce({ pos: 4 }),
268
- },
269
- },
270
- });
271
- const { NodeSelection } = require('prosemirror-state');
272
-
273
- const handled = ZeroWidthSpaceHandlingPlugin.props.handleKeyDown(view, { key: 'ArrowLeft' });
274
-
275
- expect(handled).toBe(true);
276
- expect(NodeSelection.create).toHaveBeenCalledWith(view.state.doc, 4);
277
- expect(view.dispatch).toHaveBeenCalled();
278
- });
279
-
280
- it('moves the text cursor before the zero-width space when no inline node precedes it', () => {
281
- const view = createView();
282
- const { TextSelection } = require('prosemirror-state');
283
-
284
- const handled = ZeroWidthSpaceHandlingPlugin.props.handleKeyDown(view, { key: 'ArrowLeft' });
285
-
286
- expect(handled).toBe(true);
287
- expect(TextSelection.create).toHaveBeenCalledWith(view.state.doc, 0);
288
- expect(view.dispatch).toHaveBeenCalled();
289
- });
290
-
291
- it('returns false for unrelated keys', () => {
292
- const view = createView({
293
- state: {
294
- doc: {
295
- textBetween: jest.fn(() => 'a'),
296
- resolve: jest.fn(),
297
- },
298
- },
299
- });
300
-
301
- const handled = ZeroWidthSpaceHandlingPlugin.props.handleKeyDown(view, { key: 'Enter' });
302
- expect(handled).toBe(false);
303
- });
304
- });
305
-
306
- describe('MathNodeView', () => {
307
- const createMockEditor = () => ({
308
- state: {
309
- selection: {
310
- from: 0,
311
- to: 1,
312
- },
313
- tr: {
314
- setSelection: jest.fn().mockReturnThis(),
315
- },
316
- doc: {},
317
- },
318
- view: {
319
- coordsAtPos: jest.fn(() => ({ top: 100, left: 50 })),
320
- dispatch: jest.fn(),
321
- },
322
- commands: {
323
- focus: jest.fn(),
324
- },
325
- instanceId: 'editor-123',
326
- _toolbarOpened: false,
327
- });
328
-
329
- const mockNode = {
330
- attrs: {
331
- latex: 'x^2',
332
- },
333
- };
334
-
335
- let defaultProps;
336
-
337
- beforeAll(() => {
338
- Object.defineProperty(document.body, 'getBoundingClientRect', {
339
- value: jest.fn(() => ({ top: 0, left: 0 })),
340
- configurable: true,
341
- });
342
- });
343
-
344
- beforeEach(() => {
345
- jest.clearAllMocks();
346
- mockCreatePortal.mockImplementation((node) => node);
347
- defaultProps = {
348
- node: mockNode,
349
- updateAttributes: jest.fn(),
350
- editor: createMockEditor(),
351
- selected: false,
352
- options: {},
353
- };
354
- });
355
-
356
- it('renders without crashing', () => {
357
- const { container } = render(<MathNodeView {...defaultProps} />);
358
- expect(container).toBeInTheDocument();
359
- });
360
-
361
- it('renders NodeViewWrapper', () => {
362
- const { getByTestId } = render(<MathNodeView {...defaultProps} />);
363
- expect(getByTestId('node-view-wrapper')).toBeInTheDocument();
364
- });
365
-
366
- it('displays math preview', () => {
367
- const { getByTestId } = render(<MathNodeView {...defaultProps} />);
368
- expect(getByTestId('math-preview')).toBeInTheDocument();
369
- });
370
-
371
- it('shows toolbar when selected', async () => {
372
- const { getByTestId } = render(<MathNodeView {...defaultProps} selected={true} />);
373
- await waitFor(() => {
374
- expect(getByTestId('math-toolbar')).toBeInTheDocument();
375
- });
376
- });
377
-
378
- it('does not show toolbar when not selected', () => {
379
- const { queryByTestId } = render(<MathNodeView {...defaultProps} selected={false} />);
380
- expect(queryByTestId('math-toolbar')).not.toBeInTheDocument();
381
- });
382
-
383
- it('adds data-toolbar-for attribute with editor instanceId', async () => {
384
- const { container } = render(<MathNodeView {...defaultProps} selected={true} />);
385
- await waitFor(() => {
386
- const toolbar = container.querySelector('[data-toolbar-for]');
387
- expect(toolbar).toHaveAttribute('data-toolbar-for', 'editor-123');
388
- });
389
- });
390
-
391
- describe('toolbar positioning', () => {
392
- it('uses a fixed top offset and horizontal position from coordsAtPos', async () => {
393
- const { container } = render(<MathNodeView {...defaultProps} selected={true} />);
394
- await waitFor(() => {
395
- const toolbar = container.querySelector('[data-toolbar-for]');
396
- expect(toolbar).toBeInTheDocument();
397
- expect(toolbar.style.top).toBe('40px');
398
- expect(toolbar.style.left).toBe('50px');
399
- });
400
- });
401
-
402
- it('keeps the fixed top offset when the editor container is scrolled', async () => {
403
- const containerEl = document.createElement('div');
404
- containerEl.getBoundingClientRect = jest.fn(() => ({ top: -200, left: 0, width: 600, height: 400 }));
405
-
406
- const editor = {
407
- ...defaultProps.editor,
408
- _tiptapContainerEl: containerEl,
409
- };
410
-
411
- const { container } = render(<MathNodeView {...defaultProps} editor={editor} selected={true} />);
412
- await waitFor(() => {
413
- const toolbar = container.querySelector('[data-toolbar-for]');
414
- expect(toolbar).toBeInTheDocument();
415
- expect(toolbar.style.top).toBe('40px');
416
- expect(toolbar.style.left).toBe('50px');
417
- });
418
- });
419
-
420
- it('applies absolute positioning style to toolbar', async () => {
421
- const { container } = render(<MathNodeView {...defaultProps} selected={true} />);
422
- await waitFor(() => {
423
- const toolbar = container.querySelector('[data-toolbar-for]');
424
- expect(toolbar).toBeInTheDocument();
425
- expect(toolbar.style.position).toBe('absolute');
426
- });
427
- });
428
-
429
- it('updates horizontal position from coordsAtPos when selection changes', async () => {
430
- const editor = {
431
- ...defaultProps.editor,
432
- view: {
433
- ...defaultProps.editor.view,
434
- coordsAtPos: jest.fn(() => ({ top: 200, left: 150 })),
435
- dispatch: jest.fn(),
436
- },
437
- };
438
-
439
- const { container } = render(<MathNodeView {...defaultProps} editor={editor} selected={true} />);
440
- await waitFor(() => {
441
- const toolbar = container.querySelector('[data-toolbar-for]');
442
- expect(toolbar).toBeInTheDocument();
443
- expect(toolbar.style.top).toBe('40px');
444
- expect(toolbar.style.left).toBe('150px');
445
- });
446
- });
447
-
448
- it('portals toolbar into _tiptapContainerEl when available', async () => {
449
- const containerEl = document.createElement('div');
450
- containerEl.getBoundingClientRect = jest.fn(() => ({ top: 0, left: 0, width: 600, height: 400 }));
451
-
452
- const editor = {
453
- ...defaultProps.editor,
454
- _tiptapContainerEl: containerEl,
455
- };
456
-
457
- mockCreatePortal.mockClear();
458
- render(<MathNodeView {...defaultProps} editor={editor} selected={true} />);
459
- await waitFor(() => {
460
- expect(mockCreatePortal).toHaveBeenCalled();
461
- const lastCall = mockCreatePortal.mock.calls[mockCreatePortal.mock.calls.length - 1];
462
- expect(lastCall[1]).toBe(containerEl);
463
- });
464
- });
465
-
466
- it('portals toolbar into document.body when _tiptapContainerEl is not set', async () => {
467
- const editor = {
468
- ...defaultProps.editor,
469
- _tiptapContainerEl: undefined,
470
- };
471
-
472
- mockCreatePortal.mockClear();
473
- render(<MathNodeView {...defaultProps} editor={editor} selected={true} />);
474
- await waitFor(() => {
475
- expect(mockCreatePortal).toHaveBeenCalled();
476
- const lastCall = mockCreatePortal.mock.calls[mockCreatePortal.mock.calls.length - 1];
477
- expect(lastCall[1]).toBe(document.body);
478
- });
479
- });
480
- });
481
-
482
- it('calls updateAttributes when latex changes', async () => {
483
- const { getByTestId } = render(<MathNodeView {...defaultProps} selected={true} />);
484
- await waitFor(() => {
485
- const input = getByTestId('math-input');
486
- fireEvent.change(input, { target: { value: 'y^2' } });
487
- });
488
- expect(defaultProps.updateAttributes).toHaveBeenCalledWith({ latex: 'y^2' });
489
- });
490
-
491
- it('closes toolbar and updates attributes when done', async () => {
492
- const updateAttributes = jest.fn();
493
- const { getByTestId } = render(
494
- <MathNodeView {...defaultProps} updateAttributes={updateAttributes} selected={true} />,
495
- );
496
-
497
- await waitFor(() => {
498
- expect(getByTestId('done-button')).toBeInTheDocument();
499
- });
500
-
501
- const doneButton = getByTestId('done-button');
502
- fireEvent.click(doneButton);
503
-
504
- await waitFor(() => {
505
- expect(updateAttributes).toHaveBeenCalledWith({ latex: 'x^2' });
506
- });
507
- });
508
-
509
- it('sets editor._toolbarOpened when toolbar is shown', async () => {
510
- const { getByTestId } = render(<MathNodeView {...defaultProps} selected={true} />);
511
- await waitFor(() => {
512
- expect(getByTestId('math-toolbar')).toBeInTheDocument();
513
- expect(defaultProps.editor._toolbarOpened).toBe(true);
514
- });
515
- });
516
-
517
- it('unsets editor._toolbarOpened when toolbar is closed', async () => {
518
- const { getByTestId } = render(<MathNodeView {...defaultProps} selected={true} />);
519
-
520
- await waitFor(() => {
521
- expect(getByTestId('done-button')).toBeInTheDocument();
522
- });
523
-
524
- const doneButton = getByTestId('done-button');
525
- fireEvent.click(doneButton);
526
-
527
- await waitFor(() => {
528
- expect(defaultProps.editor._toolbarOpened).toBe(false);
529
- });
530
- });
531
-
532
- it('closes toolbar on outside click and runs handleDone', async () => {
533
- const updateAttributes = jest.fn();
534
- const editor = createMockEditor();
535
- const { TextSelection } = require('prosemirror-state');
536
- const { queryByTestId } = render(
537
- <MathNodeView {...defaultProps} editor={editor} updateAttributes={updateAttributes} selected={true} />,
538
- );
539
-
540
- await waitFor(() => {
541
- expect(queryByTestId('math-toolbar')).toBeInTheDocument();
542
- });
543
-
544
- fireEvent.click(document.body);
545
-
546
- await waitFor(() => {
547
- expect(queryByTestId('math-toolbar')).not.toBeInTheDocument();
548
- expect(updateAttributes).toHaveBeenCalledWith({ latex: 'x^2' });
549
- expect(TextSelection.create).toHaveBeenCalledWith(editor.state.doc, 1);
550
- expect(editor.state.tr.setSelection).toHaveBeenCalled();
551
- expect(editor.view.dispatch).toHaveBeenCalledWith(editor.state.tr);
552
- expect(editor.commands.focus).toHaveBeenCalled();
553
- expect(editor._toolbarOpened).toBe(false);
554
- });
555
- });
556
-
557
- it('does not close toolbar when clicking the math node preview', async () => {
558
- const { getByTestId, queryByTestId } = render(<MathNodeView {...defaultProps} selected={true} />);
559
-
560
- await waitFor(() => {
561
- expect(queryByTestId('math-toolbar')).toBeInTheDocument();
562
- });
563
-
564
- fireEvent.click(getByTestId('math-preview'));
565
-
566
- await waitFor(() => {
567
- expect(queryByTestId('math-toolbar')).toBeInTheDocument();
568
- });
569
- });
570
-
571
- it('does not close toolbar when clicking equation editor dropdown', async () => {
572
- const { queryByTestId } = render(<MathNodeView {...defaultProps} selected={true} />);
573
-
574
- await waitFor(() => {
575
- expect(queryByTestId('math-toolbar')).toBeInTheDocument();
576
- });
577
-
578
- // Simulate MUI Select's portal dropdown container.
579
- const dropdown = document.createElement('div');
580
- dropdown.id = 'equation-editor-select-listbox';
581
- document.body.appendChild(dropdown);
582
-
583
- fireEvent.click(dropdown);
584
-
585
- await waitFor(() => {
586
- expect(queryByTestId('math-toolbar')).toBeInTheDocument();
587
- });
588
-
589
- document.body.removeChild(dropdown);
590
- });
591
-
592
- it('renders with empty latex', () => {
593
- const nodeWithEmptyLatex = { attrs: { latex: '' } };
594
- const { getByTestId } = render(<MathNodeView {...defaultProps} node={nodeWithEmptyLatex} />);
595
- expect(getByTestId('math-preview')).toBeInTheDocument();
596
- });
597
-
598
- it('has correct styling on NodeViewWrapper', () => {
599
- const { getByTestId } = render(<MathNodeView {...defaultProps} />);
600
- const wrapper = getByTestId('node-view-wrapper');
601
- expect(wrapper).toHaveStyle({ display: 'inline-flex', cursor: 'pointer' });
602
- });
603
- });