@moda-editor/core 0.1.2 → 0.1.5

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 (247) hide show
  1. package/dist/collab.d.ts +61 -0
  2. package/dist/collab.d.ts.map +1 -0
  3. package/dist/collab.js +67 -0
  4. package/dist/collab.js.map +1 -0
  5. package/dist/commands.d.ts +11 -0
  6. package/dist/commands.d.ts.map +1 -1
  7. package/dist/commands.js +48 -4
  8. package/dist/commands.js.map +1 -1
  9. package/dist/conformance/editor-base.d.ts +7 -0
  10. package/dist/conformance/editor-base.d.ts.map +1 -0
  11. package/dist/conformance/editor-base.js +280 -0
  12. package/dist/conformance/editor-base.js.map +1 -0
  13. package/dist/conformance/editor-codeblock.d.ts +8 -0
  14. package/dist/conformance/editor-codeblock.d.ts.map +1 -0
  15. package/dist/conformance/editor-codeblock.js +402 -0
  16. package/dist/conformance/editor-codeblock.js.map +1 -0
  17. package/dist/conformance/editor-ime.d.ts +7 -0
  18. package/dist/conformance/editor-ime.d.ts.map +1 -0
  19. package/dist/conformance/editor-ime.js +166 -0
  20. package/dist/conformance/editor-ime.js.map +1 -0
  21. package/dist/conformance/editor-input.d.ts +8 -0
  22. package/dist/conformance/editor-input.d.ts.map +1 -0
  23. package/dist/conformance/editor-input.js +676 -0
  24. package/dist/conformance/editor-input.js.map +1 -0
  25. package/dist/conformance/editor-nodeview.d.ts +7 -0
  26. package/dist/conformance/editor-nodeview.d.ts.map +1 -0
  27. package/dist/conformance/editor-nodeview.js +120 -0
  28. package/dist/conformance/editor-nodeview.js.map +1 -0
  29. package/dist/conformance/editor-overlay.d.ts +8 -0
  30. package/dist/conformance/editor-overlay.d.ts.map +1 -0
  31. package/dist/conformance/editor-overlay.js +323 -0
  32. package/dist/conformance/editor-overlay.js.map +1 -0
  33. package/dist/conformance/fixtures.d.ts +4 -0
  34. package/dist/conformance/fixtures.d.ts.map +1 -0
  35. package/dist/conformance/fixtures.js +15 -0
  36. package/dist/conformance/fixtures.js.map +1 -0
  37. package/dist/conformance/index.d.ts +14 -0
  38. package/dist/conformance/index.d.ts.map +1 -0
  39. package/dist/conformance/index.js +28 -0
  40. package/dist/conformance/index.js.map +1 -0
  41. package/dist/conformance/types.d.ts +51 -0
  42. package/dist/conformance/types.d.ts.map +1 -0
  43. package/dist/conformance/types.js +2 -0
  44. package/dist/conformance/types.js.map +1 -0
  45. package/dist/conformance/widget.d.ts +3 -0
  46. package/dist/conformance/widget.d.ts.map +1 -0
  47. package/dist/conformance/widget.js +49 -0
  48. package/dist/conformance/widget.js.map +1 -0
  49. package/dist/conformance.d.ts +1 -58
  50. package/dist/conformance.d.ts.map +1 -1
  51. package/dist/conformance.js +3 -1608
  52. package/dist/conformance.js.map +1 -1
  53. package/dist/editor/extension.d.ts +51 -5
  54. package/dist/editor/extension.d.ts.map +1 -1
  55. package/dist/features/ace.d.ts +4 -0
  56. package/dist/features/ace.d.ts.map +1 -1
  57. package/dist/features/ace.js +57 -25
  58. package/dist/features/ace.js.map +1 -1
  59. package/dist/features/block-dnd.d.ts +7 -0
  60. package/dist/features/block-dnd.d.ts.map +1 -0
  61. package/dist/features/block-dnd.js +207 -0
  62. package/dist/features/block-dnd.js.map +1 -0
  63. package/dist/features/code-block.d.ts +12 -58
  64. package/dist/features/code-block.d.ts.map +1 -1
  65. package/dist/features/code-block.js +155 -630
  66. package/dist/features/code-block.js.map +1 -1
  67. package/dist/features/code-context.d.ts +81 -0
  68. package/dist/features/code-context.d.ts.map +1 -0
  69. package/dist/features/code-context.js +322 -0
  70. package/dist/features/code-context.js.map +1 -0
  71. package/dist/features/code-edit.d.ts +50 -0
  72. package/dist/features/code-edit.d.ts.map +1 -0
  73. package/dist/features/code-edit.js +412 -0
  74. package/dist/features/code-edit.js.map +1 -0
  75. package/dist/features/code-format.d.ts +21 -0
  76. package/dist/features/code-format.d.ts.map +1 -0
  77. package/dist/features/code-format.js +410 -0
  78. package/dist/features/code-format.js.map +1 -0
  79. package/dist/features/doc.d.ts.map +1 -1
  80. package/dist/features/doc.js +1 -1
  81. package/dist/features/doc.js.map +1 -1
  82. package/dist/features/heading.d.ts.map +1 -1
  83. package/dist/features/heading.js.map +1 -1
  84. package/dist/features/image.d.ts +14 -1
  85. package/dist/features/image.d.ts.map +1 -1
  86. package/dist/features/image.js +288 -11
  87. package/dist/features/image.js.map +1 -1
  88. package/dist/features/index.d.ts +7 -0
  89. package/dist/features/index.d.ts.map +1 -1
  90. package/dist/features/index.js +16 -0
  91. package/dist/features/index.js.map +1 -1
  92. package/dist/features/inline-style.d.ts.map +1 -1
  93. package/dist/features/inline-style.js +4 -1
  94. package/dist/features/inline-style.js.map +1 -1
  95. package/dist/features/link.d.ts.map +1 -1
  96. package/dist/features/link.js +1 -1
  97. package/dist/features/link.js.map +1 -1
  98. package/dist/features/markdown.d.ts +15 -0
  99. package/dist/features/markdown.d.ts.map +1 -0
  100. package/dist/features/markdown.js +80 -0
  101. package/dist/features/markdown.js.map +1 -0
  102. package/dist/features/marks.d.ts.map +1 -1
  103. package/dist/features/marks.js +76 -4
  104. package/dist/features/marks.js.map +1 -1
  105. package/dist/features/mention.d.ts +30 -0
  106. package/dist/features/mention.d.ts.map +1 -0
  107. package/dist/features/mention.js +89 -0
  108. package/dist/features/mention.js.map +1 -0
  109. package/dist/features/script.d.ts.map +1 -1
  110. package/dist/features/script.js.map +1 -1
  111. package/dist/features/search.d.ts +73 -0
  112. package/dist/features/search.d.ts.map +1 -0
  113. package/dist/features/search.js +252 -0
  114. package/dist/features/search.js.map +1 -0
  115. package/dist/features/shared.d.ts +69 -0
  116. package/dist/features/shared.d.ts.map +1 -0
  117. package/dist/features/shared.js +272 -0
  118. package/dist/features/shared.js.map +1 -0
  119. package/dist/features/table.d.ts +16 -3
  120. package/dist/features/table.d.ts.map +1 -1
  121. package/dist/features/table.js +309 -8
  122. package/dist/features/table.js.map +1 -1
  123. package/dist/features/testkit.d.ts.map +1 -1
  124. package/dist/features/testkit.js +1 -1
  125. package/dist/features/testkit.js.map +1 -1
  126. package/dist/history.d.ts.map +1 -1
  127. package/dist/history.js +65 -20
  128. package/dist/history.js.map +1 -1
  129. package/dist/index.d.ts +20 -7
  130. package/dist/index.d.ts.map +1 -1
  131. package/dist/index.js +17 -6
  132. package/dist/index.js.map +1 -1
  133. package/dist/model/schema.d.ts.map +1 -1
  134. package/dist/model/schema.js +2 -1
  135. package/dist/model/schema.js.map +1 -1
  136. package/dist/parse/html.d.ts.map +1 -1
  137. package/dist/parse/html.js +38 -6
  138. package/dist/parse/html.js.map +1 -1
  139. package/dist/parse/sanitize.d.ts.map +1 -1
  140. package/dist/parse/sanitize.js +14 -2
  141. package/dist/parse/sanitize.js.map +1 -1
  142. package/dist/presets/highlight.d.ts +17 -0
  143. package/dist/presets/highlight.d.ts.map +1 -1
  144. package/dist/presets/highlight.js +52 -4
  145. package/dist/presets/highlight.js.map +1 -1
  146. package/dist/presets/markdown-parse.d.ts +13 -0
  147. package/dist/presets/markdown-parse.d.ts.map +1 -0
  148. package/dist/presets/markdown-parse.js +398 -0
  149. package/dist/presets/markdown-parse.js.map +1 -0
  150. package/dist/presets/markdown.d.ts +45 -0
  151. package/dist/presets/markdown.d.ts.map +1 -0
  152. package/dist/presets/markdown.js +195 -0
  153. package/dist/presets/markdown.js.map +1 -0
  154. package/dist/state/apply.d.ts.map +1 -1
  155. package/dist/state/apply.js +19 -11
  156. package/dist/state/apply.js.map +1 -1
  157. package/dist/state/delete-ops.d.ts +34 -0
  158. package/dist/state/delete-ops.d.ts.map +1 -0
  159. package/dist/state/delete-ops.js +204 -0
  160. package/dist/state/delete-ops.js.map +1 -0
  161. package/dist/state/input-rules.d.ts +30 -0
  162. package/dist/state/input-rules.d.ts.map +1 -0
  163. package/dist/state/input-rules.js +70 -0
  164. package/dist/state/input-rules.js.map +1 -0
  165. package/dist/state/insert-ops.d.ts +54 -0
  166. package/dist/state/insert-ops.d.ts.map +1 -0
  167. package/dist/state/insert-ops.js +186 -0
  168. package/dist/state/insert-ops.js.map +1 -0
  169. package/dist/state/list-ops.d.ts +46 -0
  170. package/dist/state/list-ops.d.ts.map +1 -0
  171. package/dist/state/list-ops.js +231 -0
  172. package/dist/state/list-ops.js.map +1 -0
  173. package/dist/state/navigate.d.ts.map +1 -1
  174. package/dist/state/navigate.js +3 -1
  175. package/dist/state/navigate.js.map +1 -1
  176. package/dist/state/resolve-insert.d.ts +55 -0
  177. package/dist/state/resolve-insert.d.ts.map +1 -0
  178. package/dist/state/resolve-insert.js +118 -0
  179. package/dist/state/resolve-insert.js.map +1 -0
  180. package/dist/state/slice-ops.d.ts +46 -0
  181. package/dist/state/slice-ops.d.ts.map +1 -0
  182. package/dist/state/slice-ops.js +150 -0
  183. package/dist/state/slice-ops.js.map +1 -0
  184. package/dist/state/state.d.ts +6 -1
  185. package/dist/state/state.d.ts.map +1 -1
  186. package/dist/state/state.js +10 -0
  187. package/dist/state/state.js.map +1 -1
  188. package/dist/state/transaction.d.ts +40 -53
  189. package/dist/state/transaction.d.ts.map +1 -1
  190. package/dist/state/transaction.js +62 -829
  191. package/dist/state/transaction.js.map +1 -1
  192. package/dist/styles.css +472 -46
  193. package/dist/view/adapter.d.ts +84 -0
  194. package/dist/view/adapter.d.ts.map +1 -0
  195. package/dist/view/adapter.js +76 -0
  196. package/dist/view/adapter.js.map +1 -0
  197. package/dist/view/caret.d.ts +25 -0
  198. package/dist/view/caret.d.ts.map +1 -0
  199. package/dist/view/caret.js +43 -0
  200. package/dist/view/caret.js.map +1 -0
  201. package/dist/view/decoration.d.ts +9 -0
  202. package/dist/view/decoration.d.ts.map +1 -0
  203. package/dist/view/decoration.js +214 -0
  204. package/dist/view/decoration.js.map +1 -0
  205. package/dist/view/dirty-keys.d.ts +18 -0
  206. package/dist/view/dirty-keys.d.ts.map +1 -0
  207. package/dist/view/dirty-keys.js +31 -0
  208. package/dist/view/dirty-keys.js.map +1 -0
  209. package/dist/view/dom.d.ts.map +1 -1
  210. package/dist/view/dom.js +69 -27
  211. package/dist/view/dom.js.map +1 -1
  212. package/dist/view/hook-ctx.d.ts +32 -0
  213. package/dist/view/hook-ctx.d.ts.map +1 -0
  214. package/dist/view/hook-ctx.js +64 -0
  215. package/dist/view/hook-ctx.js.map +1 -0
  216. package/dist/view/ime.d.ts +72 -0
  217. package/dist/view/ime.d.ts.map +1 -0
  218. package/dist/view/ime.js +185 -0
  219. package/dist/view/ime.js.map +1 -0
  220. package/dist/view/input.d.ts +12 -12
  221. package/dist/view/input.d.ts.map +1 -1
  222. package/dist/view/input.js +122 -435
  223. package/dist/view/input.js.map +1 -1
  224. package/dist/view/keys.d.ts +25 -0
  225. package/dist/view/keys.d.ts.map +1 -0
  226. package/dist/view/keys.js +106 -0
  227. package/dist/view/keys.js.map +1 -0
  228. package/dist/view/line-nav.d.ts +13 -0
  229. package/dist/view/line-nav.d.ts.map +1 -0
  230. package/dist/view/line-nav.js +114 -0
  231. package/dist/view/line-nav.js.map +1 -0
  232. package/dist/view/mount.d.ts.map +1 -1
  233. package/dist/view/mount.js +48 -32
  234. package/dist/view/mount.js.map +1 -1
  235. package/dist/view/node-selection.d.ts +33 -0
  236. package/dist/view/node-selection.d.ts.map +1 -0
  237. package/dist/view/node-selection.js +107 -0
  238. package/dist/view/node-selection.js.map +1 -0
  239. package/dist/view/render.d.ts +11 -2
  240. package/dist/view/render.d.ts.map +1 -1
  241. package/dist/view/render.js +17 -3
  242. package/dist/view/render.js.map +1 -1
  243. package/dist/view/serialize.d.ts +8 -0
  244. package/dist/view/serialize.d.ts.map +1 -1
  245. package/dist/view/serialize.js +33 -5
  246. package/dist/view/serialize.js.map +1 -1
  247. package/package.json +1 -1
@@ -0,0 +1,402 @@
1
+ import { expect, it } from "vitest";
2
+ import { createEditor } from "../editor/editor.js";
3
+ import { createDoc, createText } from "../model/node.js";
4
+ import { starterExtensions } from "../presets/starter.js";
5
+ import { point, textSelection } from "../state/selection.js";
6
+ /**
7
+ * 코드블록 계약 — Ace식 편집 동작(들여쓰기/자동 쌍/키 편집)과
8
+ * 뷰 데코레이션(.me-cl 줄·.me-ws 공백·.me-tok 토큰)이 렌더러마다
9
+ * 같은 DOM을 출력하는지 검증한다.
10
+ */
11
+ export function editorCodeblockSpec(mount) {
12
+ it("코드블록 Tab/Shift-Tab — 줄 단위 들여쓰기/내어쓰기", async () => {
13
+ const editor = createEditor({
14
+ doc: createDoc([
15
+ {
16
+ type: "codeBlock",
17
+ content: [createText("one\ntwo")],
18
+ },
19
+ ]),
20
+ extensions: starterExtensions(),
21
+ });
22
+ const { el, flush, destroy } = await mount({ editor });
23
+ const doc = el.ownerDocument;
24
+ const editable = el.querySelector(".me-editor");
25
+ // 캐럿을 두 번째 줄(two)에 놓는다 — 하이라이트 토큰 스팬 안의
26
+ // 실제 텍스트 노드로 내려간다 (매핑은 텍스트 누적이라 안전)
27
+ const holder = el.querySelector("pre[data-me-path='0'] > [data-me-path='0,0']");
28
+ const walker = doc.createTreeWalker(holder, 4 /* SHOW_TEXT */);
29
+ // 줄 구조 — "one\ntwo"는 2개 .me-cl, 개행은 줄 끝 텍스트로 보존
30
+ expect(el.querySelectorAll("pre .me-cl").length).toBe(2);
31
+ // 두 번째 줄(two)의 텍스트 노드를 찾아 전체를 선택한다
32
+ let text = null;
33
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
34
+ if (n.textContent === "two") {
35
+ text = n;
36
+ break;
37
+ }
38
+ }
39
+ doc.getSelection().setBaseAndExtent(text, 0, text, 3);
40
+ doc.dispatchEvent(new Event("selectionchange"));
41
+ await flush?.();
42
+ editable.dispatchEvent(new KeyboardEvent("keydown", {
43
+ key: "Tab",
44
+ bubbles: true,
45
+ cancelable: true,
46
+ }));
47
+ await flush?.();
48
+ expect(el.querySelector("pre").textContent).toBe("one\n\ttwo");
49
+ editable.dispatchEvent(new KeyboardEvent("keydown", {
50
+ key: "Tab",
51
+ shiftKey: true,
52
+ bubbles: true,
53
+ cancelable: true,
54
+ }));
55
+ await flush?.();
56
+ expect(el.querySelector("pre").textContent).toBe("one\ntwo");
57
+ destroy();
58
+ });
59
+ it("코드블록 Enter — 자동 들여쓰기(Ace) + 공백 꼬리 Enter 탈출", async () => {
60
+ const editor = createEditor({
61
+ doc: createDoc([
62
+ {
63
+ type: "codeBlock",
64
+ content: [createText(" const a = 1")],
65
+ },
66
+ ]),
67
+ extensions: starterExtensions(),
68
+ });
69
+ const { el, flush, destroy } = await mount({ editor });
70
+ const doc = el.ownerDocument;
71
+ const editable = el.querySelector(".me-editor");
72
+ const fireEnter = () => {
73
+ const holder = editable.querySelector("pre[data-me-path='0'] [data-me-path='0,0']");
74
+ // 마지막 텍스트 노드 끝에 캐럿
75
+ const walker = doc.createTreeWalker(holder, 4);
76
+ let t = null;
77
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
78
+ if (n.textContent)
79
+ t = n;
80
+ }
81
+ const sel = doc.getSelection();
82
+ sel.setBaseAndExtent(t, t.textContent.length, t, t.textContent.length);
83
+ const ev = new Event("beforeinput", {
84
+ bubbles: true,
85
+ cancelable: true,
86
+ });
87
+ Object.defineProperty(ev, "inputType", {
88
+ value: "insertParagraph",
89
+ });
90
+ editable.dispatchEvent(ev);
91
+ };
92
+ fireEnter();
93
+ await flush?.();
94
+ // 들여쓰기 복사 — 새 줄이 같은 선행 공백으로 시작 (Ace 관례)
95
+ expect(el.querySelector("pre").textContent).toBe(" const a = 1\n ");
96
+ // 공백뿐인 꼬리 줄에서 Enter — 꼬리를 지우고 본문으로 탈출
97
+ fireEnter();
98
+ await flush?.();
99
+ expect(el.querySelector("pre").textContent).toBe(" const a = 1");
100
+ expect(el.querySelector("p[data-me-path='1']")).toBeTruthy();
101
+ destroy();
102
+ });
103
+ it("코드블록 자동 쌍·타입오버·쌍 삭제 (Ace behaviours 관례)", async () => {
104
+ const editor = createEditor({
105
+ doc: createDoc([{ type: "codeBlock", content: [createText("x")] }]),
106
+ extensions: starterExtensions(),
107
+ });
108
+ const { el, flush, destroy } = await mount({ editor });
109
+ const doc = el.ownerDocument;
110
+ const editable = el.querySelector(".me-editor");
111
+ const fire = (inputType, data) => {
112
+ const ev = new Event("beforeinput", {
113
+ bubbles: true,
114
+ cancelable: true,
115
+ });
116
+ Object.defineProperties(ev, {
117
+ inputType: { value: inputType },
118
+ data: { value: data },
119
+ });
120
+ editable.dispatchEvent(ev);
121
+ };
122
+ // 캐럿을 첫 텍스트 노드 오프셋에 놓는다
123
+ const caretAt = (off) => {
124
+ const holder = editable.querySelector("pre[data-me-path='0'] [data-me-path='0,0']");
125
+ const walker = doc.createTreeWalker(holder, 4);
126
+ let acc = 0;
127
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
128
+ const len = n.textContent?.length ?? 0;
129
+ if (off <= acc + len) {
130
+ doc.getSelection().setBaseAndExtent(n, off - acc, n, off - acc);
131
+ return;
132
+ }
133
+ acc += len;
134
+ }
135
+ };
136
+ // "(" → "()" — 캐럿은 쌍 안쪽
137
+ caretAt(1);
138
+ fire("insertText", "(");
139
+ await flush?.();
140
+ expect(el.querySelector("pre").textContent).toBe("x()");
141
+ let sel = editor.state.selection;
142
+ if (sel.type === "text")
143
+ expect(sel.anchor.offset).toBe(2);
144
+ // ")" 타입오버 — 같은 닫기가 뒤에 있으면 넘어간다
145
+ caretAt(2);
146
+ fire("insertText", ")");
147
+ await flush?.();
148
+ expect(el.querySelector("pre").textContent).toBe("x()");
149
+ sel = editor.state.selection;
150
+ if (sel.type === "text")
151
+ expect(sel.anchor.offset).toBe(3);
152
+ // 빈 쌍 사이 Backspace — 쌍을 통째로 지운다
153
+ caretAt(2);
154
+ fire("deleteContentBackward");
155
+ await flush?.();
156
+ expect(el.querySelector("pre").textContent).toBe("x");
157
+ destroy();
158
+ });
159
+ it("코드블록 자동 쌍 — 문자열/주석 토큰 안에서는 리터럴 (Ace isCodeLike)", async () => {
160
+ const editor = createEditor({
161
+ doc: createDoc([
162
+ {
163
+ type: "codeBlock",
164
+ attrs: { language: "js" },
165
+ content: [createText("s=''")],
166
+ },
167
+ {
168
+ type: "codeBlock",
169
+ attrs: { language: "js" },
170
+ content: [createText("// note")],
171
+ },
172
+ ]),
173
+ extensions: starterExtensions(),
174
+ });
175
+ const { el, flush, destroy } = await mount({ editor });
176
+ const doc = el.ownerDocument;
177
+ const editable = el.querySelector(".me-editor");
178
+ const caretAt = (block, off) => {
179
+ const holder = editable.querySelector(`[data-me-path='${block},0']`);
180
+ const walker = doc.createTreeWalker(holder, 4);
181
+ let acc = 0;
182
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
183
+ const len = n.textContent?.length ?? 0;
184
+ if (off <= acc + len) {
185
+ doc.getSelection().setBaseAndExtent(n, off - acc, n, off - acc);
186
+ return;
187
+ }
188
+ acc += len;
189
+ }
190
+ };
191
+ const fire = (data) => {
192
+ const ev = new Event("beforeinput", {
193
+ bubbles: true,
194
+ cancelable: true,
195
+ });
196
+ Object.defineProperties(ev, {
197
+ inputType: { value: "insertText" },
198
+ data: { value: data },
199
+ });
200
+ editable.dispatchEvent(ev);
201
+ };
202
+ // 문자열 토큰 안의 { — 리터럴이어야 한다 (s='{' 가 s='{''}'로 깨지지 않게)
203
+ caretAt(0, 3);
204
+ fire("{");
205
+ await flush?.();
206
+ expect(el.querySelector("[data-me-path='0']").textContent).toBe("s='{'");
207
+ // 라인 주석 끝의 { — 주석은 EOL까지 열려있어 리터럴
208
+ caretAt(1, 7);
209
+ fire("{");
210
+ await flush?.();
211
+ expect(el.querySelector("[data-me-path='1']").textContent).toBe("// note{");
212
+ destroy();
213
+ });
214
+ it("코드블록 Enter — {|} 쌍 사이는 들여쓴 가운데 줄로 확장한다", async () => {
215
+ const editor = createEditor({
216
+ doc: createDoc([{ type: "codeBlock", content: [createText(" f(){}")] }]),
217
+ extensions: starterExtensions(),
218
+ });
219
+ const { el, flush, destroy } = await mount({ editor });
220
+ const doc = el.ownerDocument;
221
+ const editable = el.querySelector(".me-editor");
222
+ // 캐럿을 {|} 사이로 — 텍스트 노드에서 offset 6
223
+ const holder = editable.querySelector("pre[data-me-path='0'] [data-me-path='0,0']");
224
+ const walker = doc.createTreeWalker(holder, 4);
225
+ let acc = 0;
226
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
227
+ const len = n.textContent?.length ?? 0;
228
+ if (6 <= acc + len) {
229
+ doc.getSelection().setBaseAndExtent(n, 6 - acc, n, 6 - acc);
230
+ break;
231
+ }
232
+ acc += len;
233
+ }
234
+ const ev = new Event("beforeinput", {
235
+ bubbles: true,
236
+ cancelable: true,
237
+ });
238
+ Object.defineProperty(ev, "inputType", {
239
+ value: "insertParagraph",
240
+ });
241
+ editable.dispatchEvent(ev);
242
+ await flush?.();
243
+ // " f(){|\n" → " f(){\n \t|\n }" — VS Code/Ace 확장
244
+ expect(el.querySelector("pre").textContent).toBe(" f(){\n \t\n }");
245
+ destroy();
246
+ });
247
+ it("코드블록 키 편집 — Mod-/ 주석 · Alt-↓ 줄 이동 · Mod-Shift-D 복제", async () => {
248
+ const editor = createEditor({
249
+ doc: createDoc([
250
+ {
251
+ type: "codeBlock",
252
+ attrs: { language: "js" },
253
+ content: [createText("a\nb")],
254
+ },
255
+ ]),
256
+ extensions: starterExtensions(),
257
+ });
258
+ const { el, flush, destroy } = await mount({ editor });
259
+ const editable = el.querySelector(".me-editor");
260
+ const key = (init) => editable.dispatchEvent(new KeyboardEvent("keydown", {
261
+ bubbles: true,
262
+ cancelable: true,
263
+ ...init,
264
+ }));
265
+ // 캐럿을 첫 줄에 둔다
266
+ editor.dispatch(editor.state.tr().setSelection(textSelection(point([0, 0], 0))));
267
+ await flush?.();
268
+ // Mod-/ → // 주석 (들여쓰기 뒤)
269
+ key({ key: "/", ctrlKey: true });
270
+ await flush?.();
271
+ expect(el.querySelector("pre").textContent).toBe("// a\nb");
272
+ // Mod-/ 재토글 → 해제
273
+ key({ key: "/", ctrlKey: true });
274
+ await flush?.();
275
+ expect(el.querySelector("pre").textContent).toBe("a\nb");
276
+ // Alt-↓ — 첫 줄이 아래로 이동
277
+ key({ key: "ArrowDown", altKey: true });
278
+ await flush?.();
279
+ expect(el.querySelector("pre").textContent).toBe("b\na");
280
+ // Mod-Shift-D — 현재 줄("b") 복제
281
+ editor.dispatch(editor.state.tr().setSelection(textSelection(point([0, 0], 0))));
282
+ await flush?.();
283
+ key({ key: "d", ctrlKey: true, shiftKey: true });
284
+ await flush?.();
285
+ expect(el.querySelector("pre").textContent).toBe("b\nb\na");
286
+ destroy();
287
+ });
288
+ it("코드블록 공백 조각은 .me-ws 스팬으로 분리된다 (showInvisibles 계약)", async () => {
289
+ const editor = createEditor({
290
+ doc: createDoc([
291
+ {
292
+ type: "codeBlock",
293
+ content: [createText("a b\tc")],
294
+ },
295
+ ]),
296
+ extensions: starterExtensions(),
297
+ });
298
+ const { el, destroy } = await mount({ editor });
299
+ // 스페이스 런 → .me-ws, 탭 → .me-ws-tab (텍스트는 원문 그대로)
300
+ const pre = el.querySelector("pre");
301
+ expect(pre.querySelector(".me-ws")?.textContent).toBe(" ");
302
+ expect(pre.querySelector(".me-ws-tab")?.textContent).toBe("\t");
303
+ expect(pre.textContent).toBe("a b\tc");
304
+ destroy();
305
+ });
306
+ it("코드블록 .me-cl 요소 경계 클릭 — 캐럿이 줄 끝이 아닌 그 자리에 간다", async () => {
307
+ const editor = createEditor({
308
+ doc: createDoc([
309
+ {
310
+ type: "codeBlock",
311
+ content: [createText("one\ntwo\nthree")],
312
+ },
313
+ ]),
314
+ extensions: starterExtensions(),
315
+ });
316
+ const { el, flush, destroy } = await mount({ editor });
317
+ const doc = el.ownerDocument;
318
+ const editable = el.querySelector(".me-editor");
319
+ // 두 번째 줄 래퍼(.me-cl)의 요소 경계 (meCl, 0) — 줄 시작 클릭.
320
+ // 래퍼는 뒤쪽 개행을 포함한다 ("one\n" | "two\n" | "three")
321
+ const meCl = editable.querySelectorAll(".me-cl")[1];
322
+ expect(meCl.textContent).toBe("two\n");
323
+ doc.getSelection().setBaseAndExtent(meCl, 0, meCl, 0);
324
+ doc.dispatchEvent(new Event("selectionchange"));
325
+ await flush?.();
326
+ // (meCl,0)은 offset 4 ("one\n" 뒤) — 버그 전엔 요소 전체
327
+ // textContent를 더해 offset 7~8(줄 끝 너머)로 갔다
328
+ const sel = editor.state.selection;
329
+ if (sel.type === "text") {
330
+ expect(sel.anchor.offset).toBe(4);
331
+ }
332
+ // (meCl, 끝) 경계도 자식 인덱스가 아닌 문자 오프셋으로 환산된다
333
+ doc
334
+ .getSelection()
335
+ .setBaseAndExtent(meCl, meCl.childNodes.length, meCl, meCl.childNodes.length);
336
+ doc.dispatchEvent(new Event("selectionchange"));
337
+ await flush?.();
338
+ const sel2 = editor.state.selection;
339
+ if (sel2.type === "text") {
340
+ expect(sel2.anchor.offset).toBe(8);
341
+ }
342
+ destroy();
343
+ });
344
+ it("Shift-Tab — 들여쓰기가 없는 코드 줄에서도 소비된다 (포커스 이탈 방지)", async () => {
345
+ const editor = createEditor({
346
+ doc: createDoc([{ type: "codeBlock", content: [createText("abc")] }]),
347
+ extensions: starterExtensions(),
348
+ });
349
+ const { el, flush, destroy } = await mount({ editor });
350
+ const editable = el.querySelector(".me-editor");
351
+ editor.dispatch(editor.state.tr().setSelection(textSelection(point([0, 0], 1))));
352
+ await flush?.();
353
+ const ev = new KeyboardEvent("keydown", {
354
+ key: "Tab",
355
+ shiftKey: true,
356
+ bubbles: true,
357
+ cancelable: true,
358
+ });
359
+ editable.dispatchEvent(ev);
360
+ // false 폴스루면 브라우저가 포커스를 빼앗는다 — preventDefault 필수
361
+ expect(ev.defaultPrevented).toBe(true);
362
+ expect(el.querySelector("pre").textContent).toBe("abc");
363
+ destroy();
364
+ });
365
+ it("코드블록 붙여넣기 — HTML도 항상 순수 텍스트로 들어간다", async () => {
366
+ const editor = createEditor({
367
+ doc: createDoc([{ type: "codeBlock", content: [createText("x")] }]),
368
+ extensions: starterExtensions(),
369
+ });
370
+ const { el, flush, destroy } = await mount({ editor });
371
+ const doc = el.ownerDocument;
372
+ const editable = el.querySelector(".me-editor");
373
+ const holder = editable.querySelector("[data-me-path='0,0']");
374
+ const walker = doc.createTreeWalker(holder, 4);
375
+ let t = null;
376
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
377
+ if (n.textContent)
378
+ t = n;
379
+ }
380
+ doc
381
+ .getSelection()
382
+ .setBaseAndExtent(t, t.textContent.length, t, t.textContent.length);
383
+ const ev = new Event("beforeinput", {
384
+ bubbles: true,
385
+ cancelable: true,
386
+ });
387
+ const dt = {
388
+ getData: (fmt) => fmt === "text/html" ? "<b>&lt;div&gt;</b>" : "<div>",
389
+ };
390
+ Object.defineProperties(ev, {
391
+ inputType: { value: "insertFromPaste" },
392
+ dataTransfer: { value: dt },
393
+ });
394
+ editable.dispatchEvent(ev);
395
+ await flush?.();
396
+ // 마크업이 노드가 아니라 문자로 들어가야 한다
397
+ expect(el.querySelector("pre").textContent).toBe("x<div>");
398
+ expect(el.querySelector("pre b")).toBeNull();
399
+ destroy();
400
+ });
401
+ }
402
+ //# sourceMappingURL=editor-codeblock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor-codeblock.js","sourceRoot":"","sources":["../../src/conformance/editor-codeblock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { EditorConformanceMount } from "./types.js";
2
+ /**
3
+ * IME 계약 — 조합 텍스트 흡수·커밋 Enter·빈 블록 안정성.
4
+ * 한글/일본어 조합 경로의 렌더러 편차(React 리컨실 크래시 등)를 잡는다.
5
+ */
6
+ export declare function editorImeSpec(mount: EditorConformanceMount): void;
7
+ //# sourceMappingURL=editor-ime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor-ime.d.ts","sourceRoot":"","sources":["../../src/conformance/editor-ime.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI,CAuKjE"}
@@ -0,0 +1,166 @@
1
+ import { expect, it } from "vitest";
2
+ import { createText } from "../model/node.js";
3
+ import { specEditor } from "./fixtures.js";
4
+ /**
5
+ * IME 계약 — 조합 텍스트 흡수·커밋 Enter·빈 블록 안정성.
6
+ * 한글/일본어 조합 경로의 렌더러 편차(React 리컨실 크래시 등)를 잡는다.
7
+ */
8
+ export function editorImeSpec(mount) {
9
+ it("IME 조합 텍스트가 doc에 흡수된다", async () => {
10
+ const editor = specEditor();
11
+ const { el, flush, destroy } = await mount({ editor });
12
+ const editable = el.querySelector(".me-editor");
13
+ const doc = el.ownerDocument;
14
+ const sel = doc.getSelection();
15
+ // 브라우저가 조합 중 하듯: compositionstart → DOM 직접 변경 →
16
+ // 캐럿 이동 → compositionend (조합 중 selectionchange는 무시된다)
17
+ editable.dispatchEvent(new Event("compositionstart"));
18
+ const holder = editable.querySelector("[data-me-path='1,0']");
19
+ // 렌더러마다 빈 텍스트/주석 노드가 섞일 수 있어 내용 있는
20
+ // 텍스트 노드를 찾는다
21
+ const walker = doc.createTreeWalker(holder, 4 /* NodeFilter.SHOW_TEXT */);
22
+ let text = null;
23
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
24
+ if (n.textContent)
25
+ text = n;
26
+ }
27
+ expect(text).toBeTruthy();
28
+ sel.setBaseAndExtent(text, 2, text, 2);
29
+ text.textContent = "둘째한글";
30
+ sel.setBaseAndExtent(text, 4, text, 4);
31
+ editable.dispatchEvent(new Event("compositionend"));
32
+ // 흡수는 다음 태스크 — 타이머 + 어댑터 플러시 대기
33
+ await new Promise((r) => setTimeout(r, 0));
34
+ await flush?.();
35
+ expect(editor.state.doc.content[1]?.content?.[0]?.text).toBe("둘째한글");
36
+ expect(editable.querySelector("[data-me-path='1']").textContent).toBe("둘째한글");
37
+ destroy();
38
+ });
39
+ it("IME 커밋 Enter — 흡수 대기 중 insertParagraph는 doc 분할로 처리한다", async () => {
40
+ // Chromium은 조합 커밋 Enter에 compositionend 직후 insertParagraph
41
+ // beforeinput을 보낸다. composing 플래그가 아직 살아있는 이 창에
42
+ // 이 입력을 그냥 리턴하면 브라우저가 네이티브 split으로
43
+ // data-me-path가 복제된 블록을 만들고 doc과 영구히 어긋난다 —
44
+ // "Enter를 두 번 쳐야 내려간다" 회귀의 원인.
45
+ const editor = specEditor();
46
+ const { el, flush, destroy } = await mount({ editor });
47
+ const editable = el.querySelector(".me-editor");
48
+ const doc = el.ownerDocument;
49
+ const sel = doc.getSelection();
50
+ editable.dispatchEvent(new Event("compositionstart"));
51
+ const holder = editable.querySelector("[data-me-path='1,0']");
52
+ const walker = doc.createTreeWalker(holder, 4);
53
+ let text = null;
54
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
55
+ if (n.textContent)
56
+ text = n;
57
+ }
58
+ text.textContent = "둘째한글";
59
+ sel.setBaseAndExtent(text, 4, text, 4);
60
+ editable.dispatchEvent(new Event("compositionend"));
61
+ // 흡수 타이머가 돌기 전 — 같은 태스크에 도착하는 커밋-Enter의
62
+ // insertParagraph를 시뮬레이션한다
63
+ const ev = new Event("beforeinput", {
64
+ bubbles: true,
65
+ cancelable: true,
66
+ });
67
+ Object.defineProperty(ev, "inputType", {
68
+ value: "insertParagraph",
69
+ });
70
+ Object.defineProperty(ev, "data", { value: null });
71
+ editable.dispatchEvent(ev);
72
+ await flush?.();
73
+ // 1) 커밋 텍스트가 doc에 흡수됐다
74
+ expect(editor.state.doc.content[1]?.content?.[0]?.text).toBe("둘째한글");
75
+ // 2) Enter가 한 번에 새 문단을 만들었다 — 커밋 끝점에서 분할
76
+ const para2 = editor.state.doc.content[2];
77
+ expect(para2?.type).toBe("paragraph");
78
+ expect((para2?.content ?? []).map((c) => c.text ?? "").join("")).toBe("");
79
+ // 3) 네이티브 split 흔적 — 복제된 data-me-path 블록이 없다
80
+ expect(editable.querySelectorAll("[data-me-path='2']").length).toBe(1);
81
+ destroy();
82
+ });
83
+ it("빈 블록의 IME — 브라우저가 br을 지워도 리렌더가 안정적이다", async () => {
84
+ const editor = specEditor();
85
+ const { el, flush, destroy } = await mount({ editor });
86
+ const editable = el.querySelector(".me-editor");
87
+ const doc = el.ownerDocument;
88
+ // 빈 문단 — <br> 자리표시자가 들어간다
89
+ editor.dispatch(editor.state
90
+ .tr()
91
+ .insertNode({ path: [], offset: 2 }, { type: "paragraph", content: [createText("")] }));
92
+ await flush?.();
93
+ const holder = editable.querySelector("[data-me-path='2,0']");
94
+ // 실제 Chromium IME 재현 — 브라우저가 조합 중 <br>을 직접 지우고
95
+ // 조합 텍스트를 넣는다. vnode와 어긋난 DOM 위에서 프레임워크
96
+ // 리컨실이 removeChild 크래시로 터졌던 회귀 (React).
97
+ editable.dispatchEvent(new Event("compositionstart"));
98
+ const text = doc.createTextNode("글씨");
99
+ const br = holder.querySelector("br");
100
+ if (br)
101
+ holder.replaceChild(text, br);
102
+ else
103
+ holder.appendChild(text);
104
+ doc.getSelection().setBaseAndExtent(text, 2, text, 2);
105
+ editable.dispatchEvent(new Event("compositionend"));
106
+ await new Promise((r) => setTimeout(r, 0));
107
+ await flush?.();
108
+ // 예외 없이 doc에 흡수 + DOM 일치까지 완료돼야 한다
109
+ expect(editor.state.doc.content[2]?.content?.[0]?.text).toBe("글씨");
110
+ expect(editable.querySelector("[data-me-path='2']").textContent).toBe("글씨");
111
+ destroy();
112
+ });
113
+ it("크로스-블록 선택 위 IME — 조합 시작이 선택을 먼저 정리한다", async () => {
114
+ // 브라우저는 조합 시작 시 선택 범위를 네이티브로 지운다 — 여러
115
+ // 블록의 DOM이 한꺼번에 바뀌어 홀더 단위 흡수가 못 따라간다.
116
+ // compositionstart에서 선택을 트랜잭션으로 정리해 조합이 한
117
+ // 리프 안에 머물게 한다.
118
+ const editor = specEditor();
119
+ const { el, flush, destroy } = await mount({ editor });
120
+ const editable = el.querySelector(".me-editor");
121
+ const doc = el.ownerDocument;
122
+ const sel = doc.getSelection();
123
+ // 블록0 "안녕"의 1번 오프셋 → 블록1 "둘째"의 1번 오프셋 선택
124
+ const leaf = (p) => {
125
+ const h = editable.querySelector(`[data-me-path='${p}']`);
126
+ const w = doc.createTreeWalker(h, 4);
127
+ let t = null;
128
+ for (let n = w.nextNode(); n; n = w.nextNode()) {
129
+ if (n.textContent)
130
+ return n;
131
+ }
132
+ return null;
133
+ };
134
+ const a = leaf("0,0");
135
+ const f = leaf("1,0");
136
+ sel.setBaseAndExtent(a, 1, f, 1);
137
+ // 조합 시작 — 선택이 트랜잭션으로 정리돼야 한다
138
+ editable.dispatchEvent(new Event("compositionstart"));
139
+ await flush?.();
140
+ // "안" + "째"가 한 문단으로 병합됐다
141
+ expect(editor.state.doc.content).toHaveLength(1);
142
+ expect((editor.state.doc.content[0]?.content ?? [])
143
+ .map((c) => c.text ?? "")
144
+ .join("")).toBe("안째");
145
+ // 조합 텍스트를 anchor 리프에 직접 쓰고 커밋 — 단일 홀더 흡수
146
+ const holder = editable.querySelector("[data-me-path='0,0']");
147
+ const w = doc.createTreeWalker(holder, 4);
148
+ let text = null;
149
+ for (let n = w.nextNode(); n; n = w.nextNode()) {
150
+ if (n.textContent)
151
+ text = n;
152
+ }
153
+ // DOM은 테스트 환경에서 deleteFromDocument가 없을 수 있어
154
+ // 삭제 결과를 수동으로 맞춘 뒤 조합 텍스트를 쓴다
155
+ text.textContent = "안한";
156
+ sel.setBaseAndExtent(text, 2, text, 2);
157
+ editable.dispatchEvent(new Event("compositionend"));
158
+ await new Promise((r) => setTimeout(r, 0));
159
+ await flush?.();
160
+ expect((editor.state.doc.content[0]?.content ?? [])
161
+ .map((c) => c.text ?? "")
162
+ .join("")).toBe("안한째");
163
+ destroy();
164
+ });
165
+ }
166
+ //# sourceMappingURL=editor-ime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor-ime.js","sourceRoot":"","sources":["../../src/conformance/editor-ime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { EditorConformanceMount } from "./types.js";
2
+ /**
3
+ * 입력 계약 — beforeinput 경로(붙여넣기/드롭/삭제/format系/입력 규칙)와
4
+ * 키다운 경로(Enter/Tab/Mod-a)가 모두 트랜잭션으로 수렴하는지 검증한다.
5
+ * 네이티브 DOM 우회를 막는 것이 목적 — doc과 DOM이 어긋나는 회귀 방지.
6
+ */
7
+ export declare function editorInputSpec(mount: EditorConformanceMount): void;
8
+ //# sourceMappingURL=editor-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor-input.d.ts","sourceRoot":"","sources":["../../src/conformance/editor-input.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI,CAirBnE"}