@pie-lib/editable-html-tip-tap 2.1.2-next.0 → 2.1.2-next.104

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 (280) hide show
  1. package/CHANGELOG.json +32 -0
  2. package/CHANGELOG.md +2532 -0
  3. package/LICENSE.md +5 -0
  4. package/lib/components/CharacterPicker.js +226 -0
  5. package/lib/components/CharacterPicker.js.map +1 -0
  6. package/lib/components/EditableHtml.js +388 -0
  7. package/lib/components/EditableHtml.js.map +1 -0
  8. package/lib/components/MenuBar.js +702 -0
  9. package/lib/components/MenuBar.js.map +1 -0
  10. package/lib/components/TiptapContainer.js +242 -0
  11. package/lib/components/TiptapContainer.js.map +1 -0
  12. package/lib/components/characters/characterUtils.js +378 -0
  13. package/lib/components/characters/characterUtils.js.map +1 -0
  14. package/lib/components/characters/custom-popper.js +44 -0
  15. package/lib/components/characters/custom-popper.js.map +1 -0
  16. package/lib/components/common/done-button.js +34 -0
  17. package/lib/components/common/done-button.js.map +1 -0
  18. package/lib/components/common/toolbar-buttons.js +149 -0
  19. package/lib/components/common/toolbar-buttons.js.map +1 -0
  20. package/lib/components/icons/CssIcon.js +25 -0
  21. package/lib/components/icons/CssIcon.js.map +1 -0
  22. package/lib/components/icons/RespArea.js +72 -0
  23. package/lib/components/icons/RespArea.js.map +1 -0
  24. package/lib/components/icons/TableIcons.js +53 -0
  25. package/lib/components/icons/TableIcons.js.map +1 -0
  26. package/lib/components/icons/TextAlign.js +157 -0
  27. package/lib/components/icons/TextAlign.js.map +1 -0
  28. package/lib/components/image/AltDialog.js +98 -0
  29. package/lib/components/image/AltDialog.js.map +1 -0
  30. package/lib/components/image/ImageToolbar.js +137 -0
  31. package/lib/components/image/ImageToolbar.js.map +1 -0
  32. package/lib/components/image/InsertImageHandler.js +135 -0
  33. package/lib/components/image/InsertImageHandler.js.map +1 -0
  34. package/lib/components/media/MediaDialog.js +594 -0
  35. package/lib/components/media/MediaDialog.js.map +1 -0
  36. package/lib/components/media/MediaToolbar.js +74 -0
  37. package/lib/components/media/MediaToolbar.js.map +1 -0
  38. package/lib/components/media/MediaWrapper.js +67 -0
  39. package/lib/components/media/MediaWrapper.js.map +1 -0
  40. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js +84 -0
  41. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js.map +1 -0
  42. package/lib/components/respArea/DragInTheBlank/choice.js +253 -0
  43. package/lib/components/respArea/DragInTheBlank/choice.js.map +1 -0
  44. package/lib/components/respArea/ExplicitConstructedResponse.js +137 -0
  45. package/lib/components/respArea/ExplicitConstructedResponse.js.map +1 -0
  46. package/lib/components/respArea/InlineDropdown.js +223 -0
  47. package/lib/components/respArea/InlineDropdown.js.map +1 -0
  48. package/lib/components/respArea/MathTemplated.js +130 -0
  49. package/lib/components/respArea/MathTemplated.js.map +1 -0
  50. package/lib/components/respArea/ToolbarIcon.js +81 -0
  51. package/lib/components/respArea/ToolbarIcon.js.map +1 -0
  52. package/lib/components/respArea/inlineDropdownUtils.js +67 -0
  53. package/lib/components/respArea/inlineDropdownUtils.js.map +1 -0
  54. package/lib/constants.js +11 -0
  55. package/lib/constants.js.map +1 -0
  56. package/lib/extensions/css.js +227 -0
  57. package/lib/extensions/css.js.map +1 -0
  58. package/lib/extensions/custom-toolbar-wrapper.js +92 -0
  59. package/lib/extensions/custom-toolbar-wrapper.js.map +1 -0
  60. package/lib/extensions/div-node.js +83 -0
  61. package/lib/extensions/div-node.js.map +1 -0
  62. package/lib/extensions/ensure-empty-root-div.js +48 -0
  63. package/lib/extensions/ensure-empty-root-div.js.map +1 -0
  64. package/lib/extensions/ensure-list-item-content-is-div.js +64 -0
  65. package/lib/extensions/ensure-list-item-content-is-div.js.map +1 -0
  66. package/lib/extensions/extended-list-item.js +15 -0
  67. package/lib/extensions/extended-list-item.js.map +1 -0
  68. package/lib/extensions/extended-table-cell.js +22 -0
  69. package/lib/extensions/extended-table-cell.js.map +1 -0
  70. package/lib/extensions/extended-table.js +75 -0
  71. package/lib/extensions/extended-table.js.map +1 -0
  72. package/lib/extensions/heading-paragraph.js +61 -0
  73. package/lib/extensions/heading-paragraph.js.map +1 -0
  74. package/lib/extensions/image-component.js +351 -0
  75. package/lib/extensions/image-component.js.map +1 -0
  76. package/lib/extensions/image.js +134 -0
  77. package/lib/extensions/image.js.map +1 -0
  78. package/lib/extensions/index.js +46 -0
  79. package/lib/extensions/index.js.map +1 -0
  80. package/lib/extensions/math.js +451 -0
  81. package/lib/extensions/math.js.map +1 -0
  82. package/lib/extensions/media.js +243 -0
  83. package/lib/extensions/media.js.map +1 -0
  84. package/lib/extensions/responseArea.js +452 -0
  85. package/lib/extensions/responseArea.js.map +1 -0
  86. package/lib/index.js +37 -0
  87. package/lib/index.js.map +1 -0
  88. package/lib/styles/editorContainerStyles.js +137 -0
  89. package/lib/styles/editorContainerStyles.js.map +1 -0
  90. package/lib/theme.js +8 -0
  91. package/lib/theme.js.map +1 -0
  92. package/lib/utils/helper.js +73 -0
  93. package/lib/utils/helper.js.map +1 -0
  94. package/lib/utils/size.js +26 -0
  95. package/lib/utils/size.js.map +1 -0
  96. package/lib/utils/toolbar.js +19 -0
  97. package/lib/utils/toolbar.js.map +1 -0
  98. package/package.json +22 -35
  99. package/src/__tests__/EditableHtml.test.jsx +588 -0
  100. package/src/__tests__/constants.test.js +19 -0
  101. package/src/__tests__/div-to-paragraph-conversion.test.jsx +125 -0
  102. package/src/__tests__/extensions.test.js +208 -0
  103. package/src/__tests__/index.test.jsx +154 -0
  104. package/src/__tests__/size-utils.test.js +64 -0
  105. package/src/__tests__/theme.test.js +17 -0
  106. package/src/components/CharacterPicker.jsx +237 -0
  107. package/src/components/EditableHtml.jsx +458 -0
  108. package/src/components/MenuBar.jsx +561 -0
  109. package/src/components/TiptapContainer.jsx +235 -0
  110. package/src/components/__tests__/AltDialog.test.jsx +147 -0
  111. package/src/components/__tests__/CharacterPicker.test.jsx +270 -0
  112. package/src/components/__tests__/CssIcon.test.jsx +46 -0
  113. package/src/components/__tests__/DragInTheBlank.test.jsx +255 -0
  114. package/src/components/__tests__/ExplicitConstructedResponse.test.jsx +209 -0
  115. package/src/components/__tests__/ImageToolbar.test.jsx +128 -0
  116. package/src/components/__tests__/InlineDropdown.test.jsx +557 -0
  117. package/src/components/__tests__/InsertImageHandler.test.js +161 -0
  118. package/src/components/__tests__/MediaDialog.test.jsx +293 -0
  119. package/src/components/__tests__/MediaToolbar.test.jsx +74 -0
  120. package/src/components/__tests__/MediaWrapper.test.jsx +81 -0
  121. package/src/components/__tests__/MenuBar.test.jsx +250 -0
  122. package/src/components/__tests__/RespArea.test.jsx +122 -0
  123. package/src/components/__tests__/TableIcons.test.jsx +149 -0
  124. package/src/components/__tests__/TextAlign.test.jsx +167 -0
  125. package/src/components/__tests__/TiptapContainer.test.jsx +138 -0
  126. package/src/components/__tests__/characterUtils.test.js +166 -0
  127. package/src/components/__tests__/choice.test.jsx +171 -0
  128. package/src/components/__tests__/custom-popper.test.jsx +82 -0
  129. package/src/components/__tests__/done-button.test.jsx +54 -0
  130. package/src/components/__tests__/toolbar-buttons.test.jsx +234 -0
  131. package/src/components/characters/characterUtils.js +447 -0
  132. package/src/components/characters/custom-popper.js +38 -0
  133. package/src/components/common/done-button.jsx +27 -0
  134. package/src/components/common/toolbar-buttons.jsx +127 -0
  135. package/src/components/icons/CssIcon.jsx +15 -0
  136. package/src/components/icons/RespArea.jsx +71 -0
  137. package/src/components/icons/TableIcons.jsx +52 -0
  138. package/src/components/icons/TextAlign.jsx +114 -0
  139. package/src/components/image/AltDialog.jsx +82 -0
  140. package/src/components/image/ImageToolbar.jsx +99 -0
  141. package/src/components/image/InsertImageHandler.js +107 -0
  142. package/src/components/media/MediaDialog.jsx +596 -0
  143. package/src/components/media/MediaToolbar.jsx +49 -0
  144. package/src/components/media/MediaWrapper.jsx +39 -0
  145. package/src/components/respArea/DragInTheBlank/DragInTheBlank.jsx +76 -0
  146. package/src/components/respArea/DragInTheBlank/__tests__/choice.test.jsx +72 -0
  147. package/src/components/respArea/DragInTheBlank/choice.jsx +259 -0
  148. package/src/components/respArea/ExplicitConstructedResponse.jsx +136 -0
  149. package/src/components/respArea/InlineDropdown.jsx +240 -0
  150. package/src/components/respArea/MathTemplated.jsx +124 -0
  151. package/src/components/respArea/ToolbarIcon.jsx +66 -0
  152. package/src/components/respArea/__tests__/MathTemplated.test.jsx +210 -0
  153. package/src/components/respArea/inlineDropdownUtils.js +79 -0
  154. package/src/constants.js +5 -0
  155. package/src/extensions/__tests__/css.test.js +196 -0
  156. package/src/extensions/__tests__/custom-toolbar-wrapper.test.jsx +180 -0
  157. package/src/extensions/__tests__/divNode.test.js +87 -0
  158. package/src/extensions/__tests__/ensure-empty-root-div.test.js +57 -0
  159. package/src/extensions/__tests__/ensure-list-item-content-is-div.test.js +44 -0
  160. package/src/extensions/__tests__/extended-list-item.test.js +13 -0
  161. package/src/extensions/__tests__/extended-table-cell.test.js +22 -0
  162. package/src/extensions/__tests__/extended-table.test.js +183 -0
  163. package/src/extensions/__tests__/image-component.test.jsx +346 -0
  164. package/src/extensions/__tests__/image.test.js +237 -0
  165. package/src/extensions/__tests__/math.test.js +898 -0
  166. package/src/extensions/__tests__/media-node-view.test.jsx +298 -0
  167. package/src/extensions/__tests__/media.test.js +271 -0
  168. package/src/extensions/__tests__/responseArea.test.js +655 -0
  169. package/src/extensions/css.js +233 -0
  170. package/src/extensions/custom-toolbar-wrapper.jsx +78 -0
  171. package/src/extensions/div-node.js +86 -0
  172. package/src/extensions/ensure-empty-root-div.js +47 -0
  173. package/src/extensions/ensure-list-item-content-is-div.js +62 -0
  174. package/src/extensions/extended-list-item.js +10 -0
  175. package/src/extensions/extended-table-cell.js +19 -0
  176. package/src/extensions/extended-table.js +60 -0
  177. package/src/extensions/heading-paragraph.js +53 -0
  178. package/src/extensions/image-component.jsx +340 -0
  179. package/src/extensions/image.js +109 -0
  180. package/src/extensions/index.js +81 -0
  181. package/src/extensions/math.js +446 -0
  182. package/src/extensions/media.js +188 -0
  183. package/src/extensions/responseArea.js +407 -0
  184. package/src/index.jsx +5 -0
  185. package/src/styles/editorContainerStyles.js +145 -0
  186. package/src/theme.js +1 -0
  187. package/src/utils/__tests__/helper.test.js +126 -0
  188. package/src/utils/__tests__/toolbar.test.js +43 -0
  189. package/src/utils/helper.js +69 -0
  190. package/src/utils/size.js +32 -0
  191. package/src/utils/toolbar.js +15 -0
  192. package/dist/components/CharacterPicker.d.ts +0 -31
  193. package/dist/components/CharacterPicker.js +0 -131
  194. package/dist/components/EditableHtml.d.ts +0 -11
  195. package/dist/components/EditableHtml.js +0 -291
  196. package/dist/components/MenuBar.d.ts +0 -11
  197. package/dist/components/MenuBar.js +0 -462
  198. package/dist/components/TiptapContainer.d.ts +0 -11
  199. package/dist/components/TiptapContainer.js +0 -154
  200. package/dist/components/characters/characterUtils.d.ts +0 -35
  201. package/dist/components/characters/characterUtils.js +0 -465
  202. package/dist/components/characters/custom-popper.d.ts +0 -14
  203. package/dist/components/characters/custom-popper.js +0 -32
  204. package/dist/components/common/done-button.d.ts +0 -30
  205. package/dist/components/common/done-button.js +0 -26
  206. package/dist/components/common/toolbar-buttons.d.ts +0 -38
  207. package/dist/components/common/toolbar-buttons.js +0 -91
  208. package/dist/components/icons/CssIcon.d.ts +0 -11
  209. package/dist/components/icons/CssIcon.js +0 -14
  210. package/dist/components/icons/RespArea.d.ts +0 -26
  211. package/dist/components/icons/RespArea.js +0 -42
  212. package/dist/components/icons/TableIcons.d.ts +0 -14
  213. package/dist/components/icons/TableIcons.js +0 -32
  214. package/dist/components/icons/TextAlign.d.ts +0 -18
  215. package/dist/components/icons/TextAlign.js +0 -134
  216. package/dist/components/image/AltDialog.d.ts +0 -22
  217. package/dist/components/image/AltDialog.js +0 -61
  218. package/dist/components/image/ImageToolbar.d.ts +0 -24
  219. package/dist/components/image/ImageToolbar.js +0 -80
  220. package/dist/components/image/InsertImageHandler.d.ts +0 -32
  221. package/dist/components/image/InsertImageHandler.js +0 -53
  222. package/dist/components/media/MediaDialog.d.ts +0 -43
  223. package/dist/components/media/MediaDialog.js +0 -389
  224. package/dist/components/media/MediaToolbar.d.ts +0 -19
  225. package/dist/components/media/MediaToolbar.js +0 -41
  226. package/dist/components/media/MediaWrapper.d.ts +0 -19
  227. package/dist/components/respArea/DragInTheBlank/DragInTheBlank.d.ts +0 -23
  228. package/dist/components/respArea/DragInTheBlank/DragInTheBlank.js +0 -58
  229. package/dist/components/respArea/DragInTheBlank/choice.d.ts +0 -56
  230. package/dist/components/respArea/DragInTheBlank/choice.js +0 -156
  231. package/dist/components/respArea/ExplicitConstructedResponse.d.ts +0 -20
  232. package/dist/components/respArea/ExplicitConstructedResponse.js +0 -83
  233. package/dist/components/respArea/InlineDropdown.d.ts +0 -18
  234. package/dist/components/respArea/InlineDropdown.js +0 -119
  235. package/dist/components/respArea/MathTemplated.d.ts +0 -19
  236. package/dist/components/respArea/MathTemplated.js +0 -97
  237. package/dist/components/respArea/ToolbarIcon.d.ts +0 -14
  238. package/dist/components/respArea/ToolbarIcon.js +0 -17
  239. package/dist/components/respArea/inlineDropdownUtils.d.ts +0 -15
  240. package/dist/components/respArea/inlineDropdownUtils.js +0 -15
  241. package/dist/constants.d.ts +0 -13
  242. package/dist/constants.js +0 -4
  243. package/dist/extensions/css.d.ts +0 -11
  244. package/dist/extensions/css.js +0 -115
  245. package/dist/extensions/custom-toolbar-wrapper.d.ts +0 -11
  246. package/dist/extensions/custom-toolbar-wrapper.js +0 -61
  247. package/dist/extensions/div-node.d.ts +0 -10
  248. package/dist/extensions/div-node.js +0 -42
  249. package/dist/extensions/ensure-empty-root-div.d.ts +0 -14
  250. package/dist/extensions/ensure-empty-root-div.js +0 -24
  251. package/dist/extensions/ensure-list-item-content-is-div.d.ts +0 -15
  252. package/dist/extensions/ensure-list-item-content-is-div.js +0 -31
  253. package/dist/extensions/extended-list-item.d.ts +0 -13
  254. package/dist/extensions/extended-list-item.js +0 -5
  255. package/dist/extensions/extended-table-cell.d.ts +0 -10
  256. package/dist/extensions/extended-table-cell.js +0 -6
  257. package/dist/extensions/extended-table.d.ts +0 -17
  258. package/dist/extensions/extended-table.js +0 -34
  259. package/dist/extensions/heading-paragraph.d.ts +0 -17
  260. package/dist/extensions/heading-paragraph.js +0 -30
  261. package/dist/extensions/image-component.d.ts +0 -22
  262. package/dist/extensions/image-component.js +0 -220
  263. package/dist/extensions/image.d.ts +0 -10
  264. package/dist/extensions/image.js +0 -68
  265. package/dist/extensions/index.d.ts +0 -16
  266. package/dist/extensions/index.js +0 -65
  267. package/dist/extensions/math.d.ts +0 -15
  268. package/dist/extensions/math.js +0 -158
  269. package/dist/extensions/media.d.ts +0 -19
  270. package/dist/extensions/media.js +0 -149
  271. package/dist/extensions/responseArea.d.ts +0 -27
  272. package/dist/extensions/responseArea.js +0 -259
  273. package/dist/index.d.ts +0 -13
  274. package/dist/index.js +0 -7
  275. package/dist/styles/editorContainerStyles.d.ts +0 -134
  276. package/dist/theme.d.ts +0 -9
  277. package/dist/utils/helper.d.ts +0 -9
  278. package/dist/utils/helper.js +0 -27
  279. package/dist/utils/size.d.ts +0 -9
  280. package/dist/utils/size.js +0 -14
@@ -0,0 +1,655 @@
1
+ import {
2
+ DragInTheBlankNode,
3
+ ExplicitConstructedResponseNode,
4
+ InlineDropdownNode,
5
+ ResponseAreaExtension,
6
+ } from '../responseArea';
7
+
8
+ jest.mock('@tiptap/core', () => ({
9
+ Extension: { create: jest.fn((config) => config) },
10
+ Node: { create: jest.fn((config) => config) },
11
+ }));
12
+
13
+ jest.mock('@tiptap/react', () => ({
14
+ Node: { create: jest.fn((config) => config) },
15
+ ReactNodeViewRenderer: jest.fn((component) => component),
16
+ }));
17
+
18
+ jest.mock('prosemirror-state', () => ({
19
+ Plugin: jest.fn(function (config) {
20
+ return config;
21
+ }),
22
+ PluginKey: jest.fn(function (key) {
23
+ this.key = key;
24
+ }),
25
+ TextSelection: {
26
+ near: jest.fn((pos, dir) => ({ type: 'text', pos, dir })),
27
+ create: jest.fn((doc, pos) => ({ type: 'text', pos })),
28
+ },
29
+ NodeSelection: {
30
+ create: jest.fn((doc, pos) => ({ type: 'node', pos })),
31
+ },
32
+ }));
33
+
34
+ jest.mock('../../components/respArea/ExplicitConstructedResponse', () => ({
35
+ __esModule: true,
36
+ default: jest.fn(() => <div data-testid="explicit-constructed-response" />),
37
+ }));
38
+
39
+ jest.mock('../../components/respArea/DragInTheBlank/DragInTheBlank', () => ({
40
+ __esModule: true,
41
+ default: jest.fn(() => <div data-testid="drag-in-the-blank" />),
42
+ }));
43
+
44
+ jest.mock('../../components/respArea/InlineDropdown', () => ({
45
+ __esModule: true,
46
+ default: jest.fn(() => <div data-testid="inline-dropdown" />),
47
+ }));
48
+
49
+ describe('ResponseAreaExtension', () => {
50
+ describe('configuration', () => {
51
+ it('has correct name', () => {
52
+ expect(ResponseAreaExtension.name).toBe('responseArea');
53
+ });
54
+ });
55
+
56
+ describe('addOptions', () => {
57
+ it('returns default options', () => {
58
+ const options = ResponseAreaExtension.addOptions();
59
+
60
+ expect(options).toHaveProperty('maxResponseAreas', null);
61
+ expect(options).toHaveProperty('error', null);
62
+ expect(options).toHaveProperty('options', null);
63
+ expect(options).toHaveProperty('respAreaToolbar', null);
64
+ expect(options).toHaveProperty('onHandleAreaChange', null);
65
+ });
66
+ });
67
+
68
+ describe('addProseMirrorPlugins', () => {
69
+ it('returns empty array when no type specified', () => {
70
+ const context = {
71
+ options: {},
72
+ };
73
+
74
+ const plugins = ResponseAreaExtension.addProseMirrorPlugins.call(context);
75
+
76
+ expect(Array.isArray(plugins)).toBe(true);
77
+ expect(plugins).toHaveLength(0);
78
+ });
79
+
80
+ it('returns plugin array when type is specified', () => {
81
+ const context = {
82
+ options: {
83
+ type: 'explicit-constructed-response',
84
+ maxResponseAreas: 5,
85
+ },
86
+ };
87
+
88
+ const plugins = ResponseAreaExtension.addProseMirrorPlugins.call(context);
89
+
90
+ expect(Array.isArray(plugins)).toBe(true);
91
+ expect(plugins).toHaveLength(1);
92
+ expect(plugins[0]).toHaveProperty('key');
93
+ expect(plugins[0]).toHaveProperty('view');
94
+ });
95
+ });
96
+
97
+ describe('addCommands', () => {
98
+ it('returns insertResponseArea command', () => {
99
+ const commands = ResponseAreaExtension.addCommands();
100
+
101
+ expect(commands).toHaveProperty('insertResponseArea');
102
+ expect(typeof commands.insertResponseArea).toBe('function');
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
+ });
261
+
262
+ describe('insertResponseArea', () => {
263
+ beforeEach(() => {
264
+ jest.resetModules();
265
+ });
266
+
267
+ const buildInsertCommand = (type = 'inline-dropdown') => {
268
+ const { ResponseAreaExtension } = require('../responseArea');
269
+ const context = {
270
+ options: {
271
+ type,
272
+ maxResponseAreas: 5,
273
+ },
274
+ };
275
+ const commands = ResponseAreaExtension.addCommands.call(context);
276
+ return commands.insertResponseArea(type);
277
+ };
278
+
279
+ const createDoc = (existingCount, typeName = 'inline_dropdown') => ({
280
+ descendants: jest.fn((callback) => {
281
+ for (let i = 0; i < existingCount; i += 1) {
282
+ callback({ type: { name: typeName } }, i);
283
+ }
284
+ }),
285
+ content: { size: 50 },
286
+ });
287
+
288
+ it('assigns index 1 and id 1 on the first insert', () => {
289
+ const insert = buildInsertCommand();
290
+ const mockInlineNode = { nodeSize: 1 };
291
+ const create = jest.fn(() => mockInlineNode);
292
+ const mockDoc = createDoc(0);
293
+ const mockTr = {
294
+ insert: jest.fn(),
295
+ doc: mockDoc,
296
+ setSelection: jest.fn(),
297
+ };
298
+ const state = {
299
+ schema: {
300
+ nodes: {
301
+ inline_dropdown: { create },
302
+ },
303
+ },
304
+ doc: mockDoc,
305
+ selection: { from: 5 },
306
+ };
307
+ const mockDispatch = jest.fn();
308
+ const mockCommands = { focus: jest.fn() };
309
+
310
+ const result = insert({
311
+ tr: mockTr,
312
+ state,
313
+ dispatch: mockDispatch,
314
+ commands: mockCommands,
315
+ });
316
+
317
+ expect(result).toBe(true);
318
+ expect(create).toHaveBeenCalledWith({
319
+ index: '1',
320
+ id: '1',
321
+ value: '',
322
+ });
323
+ expect(mockTr.insert).toHaveBeenCalledWith(5, mockInlineNode);
324
+ expect(mockDispatch).toHaveBeenCalled();
325
+ });
326
+
327
+ it('assigns consecutive indices on repeated inserts', () => {
328
+ const insert = buildInsertCommand();
329
+ const mockInlineNode = { nodeSize: 1 };
330
+ const create = jest.fn(() => mockInlineNode);
331
+ const mockDoc = createDoc(0);
332
+
333
+ const runOnce = () => {
334
+ const mockTr = {
335
+ insert: jest.fn(),
336
+ doc: mockDoc,
337
+ setSelection: jest.fn(),
338
+ };
339
+ const state = {
340
+ schema: {
341
+ nodes: {
342
+ inline_dropdown: { create },
343
+ },
344
+ },
345
+ doc: mockDoc,
346
+ selection: { from: 1 },
347
+ };
348
+ insert({
349
+ tr: mockTr,
350
+ state,
351
+ dispatch: jest.fn(),
352
+ commands: { focus: jest.fn() },
353
+ });
354
+ };
355
+
356
+ runOnce();
357
+ runOnce();
358
+
359
+ expect(create.mock.calls[0][0]).toEqual({ index: '1', id: '1', value: '' });
360
+ expect(create.mock.calls[1][0]).toEqual({ index: '2', id: '2', value: '' });
361
+ });
362
+
363
+ it('returns false when maxResponseAreas is reached', () => {
364
+ const insert = buildInsertCommand();
365
+ const mockInlineNode = { nodeSize: 1 };
366
+ const create = jest.fn(() => mockInlineNode);
367
+ const mockDoc = createDoc(5);
368
+ const mockTr = {
369
+ insert: jest.fn(),
370
+ doc: mockDoc,
371
+ setSelection: jest.fn(),
372
+ };
373
+ const state = {
374
+ schema: {
375
+ nodes: {
376
+ inline_dropdown: { create },
377
+ },
378
+ },
379
+ doc: mockDoc,
380
+ selection: { from: 5 },
381
+ };
382
+
383
+ const result = insert({
384
+ tr: mockTr,
385
+ state,
386
+ dispatch: jest.fn(),
387
+ commands: { focus: jest.fn() },
388
+ });
389
+
390
+ expect(result).toBe(false);
391
+ expect(create).not.toHaveBeenCalled();
392
+ expect(mockTr.insert).not.toHaveBeenCalled();
393
+ });
394
+
395
+ describe('selection after insert', () => {
396
+ const buildStateAndTr = (typeName, { withResolve = false } = {}) => {
397
+ const mockInlineNode = { nodeSize: 1 };
398
+ const create = jest.fn(() => mockInlineNode);
399
+ const mockDoc = {
400
+ descendants: jest.fn(),
401
+ content: { size: 50 },
402
+ ...(withResolve ? { resolve: jest.fn((pos) => ({ resolved: pos })) } : {}),
403
+ };
404
+ const mockTr = {
405
+ insert: jest.fn(),
406
+ doc: mockDoc,
407
+ setSelection: jest.fn(),
408
+ };
409
+ const state = {
410
+ schema: { nodes: { [typeName]: { create } } },
411
+ doc: mockDoc,
412
+ selection: { from: 5 },
413
+ };
414
+
415
+ return { mockTr, state };
416
+ };
417
+
418
+ it.each(['inline-dropdown', 'explicit-constructed-response'])(
419
+ 'sets a NodeSelection on the inserted node for %s',
420
+ (type) => {
421
+ const insert = buildInsertCommand(type);
422
+ const typeName = type.replace(/-/g, '_');
423
+ const { mockTr, state } = buildStateAndTr(typeName);
424
+ const { NodeSelection, TextSelection } = require('prosemirror-state');
425
+
426
+ insert({ tr: mockTr, state, dispatch: jest.fn(), commands: { focus: jest.fn() } });
427
+
428
+ expect(NodeSelection.create).toHaveBeenCalledWith(mockTr.doc, 5);
429
+ expect(TextSelection.near).not.toHaveBeenCalled();
430
+ expect(mockTr.setSelection).toHaveBeenCalledWith({ type: 'node', pos: 5 });
431
+ },
432
+ );
433
+
434
+ it('sets a TextSelection just after the inserted node for math-templated (no NodeSelection)', () => {
435
+ const insert = buildInsertCommand('math-templated');
436
+ const { mockTr, state } = buildStateAndTr('math_templated', { withResolve: true });
437
+ const { NodeSelection, TextSelection } = require('prosemirror-state');
438
+
439
+ insert({ tr: mockTr, state, dispatch: jest.fn(), commands: { focus: jest.fn() } });
440
+
441
+ expect(NodeSelection.create).not.toHaveBeenCalled();
442
+ // usedPos (5) + nodeSize (1) === 6
443
+ expect(mockTr.doc.resolve).toHaveBeenCalledWith(6);
444
+ expect(TextSelection.near).toHaveBeenCalledWith({ resolved: 6 }, 1);
445
+ expect(mockTr.setSelection).toHaveBeenCalledWith({ type: 'text', pos: { resolved: 6 }, dir: 1 });
446
+ });
447
+ });
448
+ });
449
+ });
450
+ });
451
+
452
+ describe('ExplicitConstructedResponseNode', () => {
453
+ describe('configuration', () => {
454
+ it('has correct name', () => {
455
+ expect(ExplicitConstructedResponseNode.name).toBe('explicit_constructed_response');
456
+ });
457
+
458
+ it('is inline', () => {
459
+ expect(ExplicitConstructedResponseNode.inline).toBe(true);
460
+ });
461
+
462
+ it('is in inline group', () => {
463
+ expect(ExplicitConstructedResponseNode.group).toBe('inline');
464
+ });
465
+
466
+ it('is atomic', () => {
467
+ expect(ExplicitConstructedResponseNode.atom).toBe(true);
468
+ });
469
+ });
470
+
471
+ describe('addAttributes', () => {
472
+ it('returns required attributes', () => {
473
+ const attributes = ExplicitConstructedResponseNode.addAttributes();
474
+
475
+ expect(attributes).toHaveProperty('index');
476
+ expect(attributes).toHaveProperty('value');
477
+ expect(attributes).toHaveProperty('updated');
478
+
479
+ expect(attributes.index).toEqual({ default: null });
480
+ expect(attributes.value).toEqual({ default: '' });
481
+ expect(attributes.updated).toEqual({ default: '' });
482
+ });
483
+ });
484
+
485
+ describe('parseHTML', () => {
486
+ it('returns parsing rules', () => {
487
+ const rules = ExplicitConstructedResponseNode.parseHTML();
488
+
489
+ expect(Array.isArray(rules)).toBe(true);
490
+ expect(rules).toHaveLength(1);
491
+ expect(rules[0]).toHaveProperty('tag');
492
+ });
493
+ });
494
+
495
+ describe('renderHTML', () => {
496
+ it('renders span with attributes', () => {
497
+ const result = ExplicitConstructedResponseNode.renderHTML({
498
+ HTMLAttributes: {
499
+ index: '1',
500
+ id: '1',
501
+ value: 'test',
502
+ },
503
+ });
504
+
505
+ expect(result[0]).toBe('span');
506
+ expect(result[1]).toHaveProperty('data-type', 'explicit_constructed_response');
507
+ });
508
+ });
509
+
510
+ describe('addNodeView', () => {
511
+ it('returns ReactNodeViewRenderer result', () => {
512
+ const result = ExplicitConstructedResponseNode.addNodeView();
513
+
514
+ expect(result).toBeDefined();
515
+ });
516
+ });
517
+ });
518
+
519
+ describe('DragInTheBlankNode', () => {
520
+ describe('configuration', () => {
521
+ it('has correct name', () => {
522
+ expect(DragInTheBlankNode.name).toBe('drag_in_the_blank');
523
+ });
524
+
525
+ it('is inline', () => {
526
+ expect(DragInTheBlankNode.inline).toBe(true);
527
+ });
528
+
529
+ it('is in inline group', () => {
530
+ expect(DragInTheBlankNode.group).toBe('inline');
531
+ });
532
+
533
+ it('is atomic', () => {
534
+ expect(DragInTheBlankNode.atom).toBe(true);
535
+ });
536
+ });
537
+
538
+ describe('addAttributes', () => {
539
+ it('returns required attributes', () => {
540
+ const attributes = DragInTheBlankNode.addAttributes();
541
+
542
+ expect(attributes).toHaveProperty('index');
543
+ expect(attributes).toHaveProperty('id');
544
+ expect(attributes).toHaveProperty('value');
545
+ expect(attributes).toHaveProperty('inTable');
546
+ expect(attributes).toHaveProperty('updated');
547
+
548
+ expect(attributes.index).toEqual({ default: null });
549
+ expect(attributes.id).toEqual({ default: null });
550
+ expect(attributes.value).toEqual({ default: '' });
551
+ expect(attributes.inTable).toEqual({ default: null });
552
+ expect(attributes.updated).toEqual({ default: '' });
553
+ });
554
+ });
555
+
556
+ describe('parseHTML', () => {
557
+ it('returns parsing rules', () => {
558
+ const rules = DragInTheBlankNode.parseHTML();
559
+
560
+ expect(Array.isArray(rules)).toBe(true);
561
+ expect(rules).toHaveLength(1);
562
+ expect(rules[0]).toHaveProperty('tag');
563
+ });
564
+ });
565
+
566
+ describe('renderHTML', () => {
567
+ it('renders span with attributes', () => {
568
+ const result = DragInTheBlankNode.renderHTML({
569
+ HTMLAttributes: {
570
+ index: '1',
571
+ id: '1',
572
+ value: 'test',
573
+ },
574
+ });
575
+
576
+ expect(result[0]).toBe('span');
577
+ expect(result[1]).toHaveProperty('data-type', 'drag_in_the_blank');
578
+ });
579
+ });
580
+
581
+ describe('addNodeView', () => {
582
+ it('returns ReactNodeViewRenderer result', () => {
583
+ const result = DragInTheBlankNode.addNodeView();
584
+
585
+ expect(result).toBeDefined();
586
+ });
587
+ });
588
+ });
589
+
590
+ describe('InlineDropdownNode', () => {
591
+ describe('configuration', () => {
592
+ it('has correct name', () => {
593
+ expect(InlineDropdownNode.name).toBe('inline_dropdown');
594
+ });
595
+
596
+ it('is inline', () => {
597
+ expect(InlineDropdownNode.inline).toBe(true);
598
+ });
599
+
600
+ it('is in inline group', () => {
601
+ expect(InlineDropdownNode.group).toBe('inline');
602
+ });
603
+
604
+ it('is atomic', () => {
605
+ expect(InlineDropdownNode.atom).toBe(true);
606
+ });
607
+ });
608
+
609
+ describe('addAttributes', () => {
610
+ it('returns required attributes', () => {
611
+ const attributes = InlineDropdownNode.addAttributes();
612
+
613
+ expect(attributes).toHaveProperty('index');
614
+ expect(attributes).toHaveProperty('value');
615
+ expect(attributes).toHaveProperty('updated');
616
+
617
+ expect(attributes.index).toEqual({ default: null });
618
+ expect(attributes.value).toEqual({ default: '' });
619
+ expect(attributes.updated).toEqual({ default: '' });
620
+ });
621
+ });
622
+
623
+ describe('parseHTML', () => {
624
+ it('returns parsing rules', () => {
625
+ const rules = InlineDropdownNode.parseHTML();
626
+
627
+ expect(Array.isArray(rules)).toBe(true);
628
+ expect(rules).toHaveLength(1);
629
+ expect(rules[0]).toHaveProperty('tag');
630
+ });
631
+ });
632
+
633
+ describe('renderHTML', () => {
634
+ it('renders span with attributes', () => {
635
+ const result = InlineDropdownNode.renderHTML({
636
+ HTMLAttributes: {
637
+ index: '1',
638
+ id: '1',
639
+ value: 'test',
640
+ },
641
+ });
642
+
643
+ expect(result[0]).toBe('span');
644
+ expect(result[1]).toHaveProperty('data-type', 'inline_dropdown');
645
+ });
646
+ });
647
+
648
+ describe('addNodeView', () => {
649
+ it('returns ReactNodeViewRenderer result', () => {
650
+ const result = InlineDropdownNode.addNodeView();
651
+
652
+ expect(result).toBeDefined();
653
+ });
654
+ });
655
+ });