js-draw 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +20 -6
  2. package/dist/bundle.js +1 -1
  3. package/dist/cjs/Editor.js +1 -1
  4. package/dist/cjs/Editor.loadFrom.test.d.ts +1 -0
  5. package/dist/cjs/Editor.test.d.ts +1 -0
  6. package/dist/cjs/Editor.toSVG.test.d.ts +1 -0
  7. package/dist/cjs/EditorImage.test.d.ts +1 -0
  8. package/dist/cjs/EventDispatcher.test.d.ts +1 -0
  9. package/dist/cjs/SVGLoader.test.d.ts +1 -0
  10. package/dist/cjs/UndoRedoHistory.test.d.ts +1 -0
  11. package/dist/cjs/commands/uniteCommands.test.d.ts +1 -0
  12. package/dist/cjs/components/AbstractComponent.transformBy.test.d.ts +1 -0
  13. package/dist/cjs/components/BackgroundComponent.test.d.ts +1 -0
  14. package/dist/cjs/components/Stroke.test.d.ts +1 -0
  15. package/dist/cjs/components/TextComponent.test.d.ts +1 -0
  16. package/dist/cjs/components/UnknownSVGObject.test.d.ts +1 -0
  17. package/dist/cjs/components/builders/FreehandLineBuilder.test.d.ts +1 -0
  18. package/dist/cjs/localizations/getLocalizationTable.test.d.ts +1 -0
  19. package/dist/cjs/rendering/RenderingStyle.test.d.ts +1 -0
  20. package/dist/cjs/rendering/caching/CacheRecord.test.d.ts +1 -0
  21. package/dist/cjs/rendering/caching/RenderingCache.test.d.ts +1 -0
  22. package/dist/cjs/rendering/renderers/DummyRenderer.test.d.ts +1 -0
  23. package/dist/cjs/rendering/renderers/TextOnlyRenderer.test.d.ts +1 -0
  24. package/dist/cjs/shortcuts/KeyBinding.test.d.ts +1 -0
  25. package/dist/cjs/shortcuts/KeyboardShortcutManager.test.d.ts +1 -0
  26. package/dist/cjs/toolbar/EdgeToolbar.test.d.ts +1 -0
  27. package/dist/cjs/tools/Eraser.test.d.ts +1 -0
  28. package/dist/cjs/tools/FindTool.test.d.ts +1 -0
  29. package/dist/cjs/tools/InputFilter/InputPipeline.test.d.ts +1 -0
  30. package/dist/cjs/tools/PanZoom.test.d.ts +1 -0
  31. package/dist/cjs/tools/Pen.test.d.ts +1 -0
  32. package/dist/cjs/tools/SelectionTool/SelectionTool.test.d.ts +1 -0
  33. package/dist/cjs/tools/UndoRedoShortcut.test.d.ts +1 -0
  34. package/dist/cjs/util/ReactiveValue.test.d.ts +1 -0
  35. package/dist/cjs/version.js +1 -1
  36. package/dist/cjs/version.test.d.ts +1 -0
  37. package/dist/mjs/Editor.loadFrom.test.d.ts +1 -0
  38. package/dist/mjs/Editor.mjs +1 -1
  39. package/dist/mjs/Editor.test.d.ts +1 -0
  40. package/dist/mjs/Editor.toSVG.test.d.ts +1 -0
  41. package/dist/mjs/EditorImage.test.d.ts +1 -0
  42. package/dist/mjs/EventDispatcher.test.d.ts +1 -0
  43. package/dist/mjs/SVGLoader.test.d.ts +1 -0
  44. package/dist/mjs/UndoRedoHistory.test.d.ts +1 -0
  45. package/dist/mjs/commands/uniteCommands.test.d.ts +1 -0
  46. package/dist/mjs/components/AbstractComponent.transformBy.test.d.ts +1 -0
  47. package/dist/mjs/components/BackgroundComponent.test.d.ts +1 -0
  48. package/dist/mjs/components/Stroke.test.d.ts +1 -0
  49. package/dist/mjs/components/TextComponent.test.d.ts +1 -0
  50. package/dist/mjs/components/UnknownSVGObject.test.d.ts +1 -0
  51. package/dist/mjs/components/builders/FreehandLineBuilder.test.d.ts +1 -0
  52. package/dist/mjs/localizations/getLocalizationTable.test.d.ts +1 -0
  53. package/dist/mjs/rendering/RenderingStyle.test.d.ts +1 -0
  54. package/dist/mjs/rendering/caching/CacheRecord.test.d.ts +1 -0
  55. package/dist/mjs/rendering/caching/RenderingCache.test.d.ts +1 -0
  56. package/dist/mjs/rendering/renderers/DummyRenderer.test.d.ts +1 -0
  57. package/dist/mjs/rendering/renderers/TextOnlyRenderer.test.d.ts +1 -0
  58. package/dist/mjs/shortcuts/KeyBinding.test.d.ts +1 -0
  59. package/dist/mjs/shortcuts/KeyboardShortcutManager.test.d.ts +1 -0
  60. package/dist/mjs/toolbar/EdgeToolbar.test.d.ts +1 -0
  61. package/dist/mjs/tools/Eraser.test.d.ts +1 -0
  62. package/dist/mjs/tools/FindTool.test.d.ts +1 -0
  63. package/dist/mjs/tools/InputFilter/InputPipeline.test.d.ts +1 -0
  64. package/dist/mjs/tools/PanZoom.test.d.ts +1 -0
  65. package/dist/mjs/tools/Pen.test.d.ts +1 -0
  66. package/dist/mjs/tools/SelectionTool/SelectionTool.test.d.ts +1 -0
  67. package/dist/mjs/tools/UndoRedoShortcut.test.d.ts +1 -0
  68. package/dist/mjs/util/ReactiveValue.test.d.ts +1 -0
  69. package/dist/mjs/version.mjs +1 -1
  70. package/dist/mjs/version.test.d.ts +1 -0
  71. package/dist-test/test_imports/package-lock.json +13 -0
  72. package/dist-test/test_imports/package.json +12 -0
  73. package/dist-test/test_imports/test-imports.js +11 -0
  74. package/dist-test/test_imports/test-require.cjs +14 -0
  75. package/package.json +2 -2
  76. package/src/Editor.loadFrom.test.ts +24 -0
  77. package/src/Editor.test.ts +107 -0
  78. package/src/Editor.toSVG.test.ts +294 -0
  79. package/src/Editor.ts +1443 -0
  80. package/src/EditorImage.test.ts +117 -0
  81. package/src/EditorImage.ts +609 -0
  82. package/src/EventDispatcher.test.ts +123 -0
  83. package/src/EventDispatcher.ts +72 -0
  84. package/src/Pointer.ts +183 -0
  85. package/src/SVGLoader.test.ts +114 -0
  86. package/src/SVGLoader.ts +672 -0
  87. package/src/UndoRedoHistory.test.ts +34 -0
  88. package/src/UndoRedoHistory.ts +102 -0
  89. package/src/Viewport.ts +322 -0
  90. package/src/bundle/bundled.ts +7 -0
  91. package/src/commands/Command.ts +45 -0
  92. package/src/commands/Duplicate.ts +75 -0
  93. package/src/commands/Erase.ts +95 -0
  94. package/src/commands/SerializableCommand.ts +49 -0
  95. package/src/commands/UnresolvedCommand.ts +37 -0
  96. package/src/commands/invertCommand.ts +58 -0
  97. package/src/commands/lib.ts +16 -0
  98. package/src/commands/localization.ts +47 -0
  99. package/src/commands/uniteCommands.test.ts +23 -0
  100. package/src/commands/uniteCommands.ts +140 -0
  101. package/src/components/AbstractComponent.transformBy.test.ts +23 -0
  102. package/src/components/AbstractComponent.ts +383 -0
  103. package/src/components/BackgroundComponent.test.ts +44 -0
  104. package/src/components/BackgroundComponent.ts +348 -0
  105. package/src/components/ImageComponent.ts +176 -0
  106. package/src/components/RestylableComponent.ts +161 -0
  107. package/src/components/SVGGlobalAttributesObject.ts +79 -0
  108. package/src/components/Stroke.test.ts +137 -0
  109. package/src/components/Stroke.ts +294 -0
  110. package/src/components/TextComponent.test.ts +202 -0
  111. package/src/components/TextComponent.ts +429 -0
  112. package/src/components/UnknownSVGObject.test.ts +10 -0
  113. package/src/components/UnknownSVGObject.ts +60 -0
  114. package/src/components/builders/ArrowBuilder.ts +106 -0
  115. package/src/components/builders/CircleBuilder.ts +100 -0
  116. package/src/components/builders/FreehandLineBuilder.test.ts +24 -0
  117. package/src/components/builders/FreehandLineBuilder.ts +210 -0
  118. package/src/components/builders/LineBuilder.ts +77 -0
  119. package/src/components/builders/PressureSensitiveFreehandLineBuilder.ts +453 -0
  120. package/src/components/builders/RectangleBuilder.ts +73 -0
  121. package/src/components/builders/types.ts +15 -0
  122. package/src/components/lib.ts +31 -0
  123. package/src/components/localization.ts +24 -0
  124. package/src/components/util/StrokeSmoother.ts +302 -0
  125. package/src/components/util/describeComponentList.ts +18 -0
  126. package/src/dialogs/makeAboutDialog.ts +82 -0
  127. package/src/inputEvents.ts +143 -0
  128. package/src/lib.ts +91 -0
  129. package/src/localization.ts +34 -0
  130. package/src/localizations/de.ts +146 -0
  131. package/src/localizations/en.ts +8 -0
  132. package/src/localizations/es.ts +74 -0
  133. package/src/localizations/getLocalizationTable.test.ts +27 -0
  134. package/src/localizations/getLocalizationTable.ts +74 -0
  135. package/src/rendering/Display.ts +247 -0
  136. package/src/rendering/RenderablePathSpec.ts +88 -0
  137. package/src/rendering/RenderingStyle.test.ts +68 -0
  138. package/src/rendering/RenderingStyle.ts +55 -0
  139. package/src/rendering/TextRenderingStyle.ts +55 -0
  140. package/src/rendering/caching/CacheRecord.test.ts +48 -0
  141. package/src/rendering/caching/CacheRecord.ts +76 -0
  142. package/src/rendering/caching/CacheRecordManager.ts +71 -0
  143. package/src/rendering/caching/RenderingCache.test.ts +43 -0
  144. package/src/rendering/caching/RenderingCache.ts +66 -0
  145. package/src/rendering/caching/RenderingCacheNode.ts +404 -0
  146. package/src/rendering/caching/testUtils.ts +35 -0
  147. package/src/rendering/caching/types.ts +34 -0
  148. package/src/rendering/lib.ts +8 -0
  149. package/src/rendering/localization.ts +20 -0
  150. package/src/rendering/renderers/AbstractRenderer.ts +232 -0
  151. package/src/rendering/renderers/CanvasRenderer.ts +312 -0
  152. package/src/rendering/renderers/DummyRenderer.test.ts +41 -0
  153. package/src/rendering/renderers/DummyRenderer.ts +142 -0
  154. package/src/rendering/renderers/SVGRenderer.ts +434 -0
  155. package/src/rendering/renderers/TextOnlyRenderer.test.ts +34 -0
  156. package/src/rendering/renderers/TextOnlyRenderer.ts +68 -0
  157. package/src/shortcuts/KeyBinding.test.ts +61 -0
  158. package/src/shortcuts/KeyBinding.ts +257 -0
  159. package/src/shortcuts/KeyboardShortcutManager.test.ts +95 -0
  160. package/src/shortcuts/KeyboardShortcutManager.ts +163 -0
  161. package/src/shortcuts/lib.ts +3 -0
  162. package/src/testing/createEditor.ts +11 -0
  163. package/src/testing/getUniquePointerId.ts +18 -0
  164. package/src/testing/lib.ts +3 -0
  165. package/src/testing/sendPenEvent.ts +36 -0
  166. package/src/testing/sendTouchEvent.ts +71 -0
  167. package/src/toolbar/AbstractToolbar.ts +542 -0
  168. package/src/toolbar/DropdownToolbar.ts +220 -0
  169. package/src/toolbar/EdgeToolbar.test.ts +54 -0
  170. package/src/toolbar/EdgeToolbar.ts +543 -0
  171. package/src/toolbar/IconProvider.ts +861 -0
  172. package/src/toolbar/constants.ts +1 -0
  173. package/src/toolbar/lib.ts +6 -0
  174. package/src/toolbar/localization.ts +136 -0
  175. package/src/toolbar/types.ts +13 -0
  176. package/src/toolbar/widgets/ActionButtonWidget.ts +39 -0
  177. package/src/toolbar/widgets/BaseToolWidget.ts +81 -0
  178. package/src/toolbar/widgets/BaseWidget.ts +495 -0
  179. package/src/toolbar/widgets/DocumentPropertiesWidget.ts +250 -0
  180. package/src/toolbar/widgets/EraserToolWidget.ts +84 -0
  181. package/src/toolbar/widgets/HandToolWidget.ts +239 -0
  182. package/src/toolbar/widgets/InsertImageWidget.ts +248 -0
  183. package/src/toolbar/widgets/OverflowWidget.ts +92 -0
  184. package/src/toolbar/widgets/PenToolWidget.ts +369 -0
  185. package/src/toolbar/widgets/SelectionToolWidget.ts +195 -0
  186. package/src/toolbar/widgets/TextToolWidget.ts +149 -0
  187. package/src/toolbar/widgets/components/makeColorInput.ts +184 -0
  188. package/src/toolbar/widgets/components/makeFileInput.ts +128 -0
  189. package/src/toolbar/widgets/components/makeGridSelector.ts +179 -0
  190. package/src/toolbar/widgets/components/makeSeparator.ts +17 -0
  191. package/src/toolbar/widgets/components/makeThicknessSlider.ts +62 -0
  192. package/src/toolbar/widgets/keybindings.ts +19 -0
  193. package/src/toolbar/widgets/layout/DropdownLayoutManager.ts +262 -0
  194. package/src/toolbar/widgets/layout/EdgeToolbarLayoutManager.ts +71 -0
  195. package/src/toolbar/widgets/layout/types.ts +74 -0
  196. package/src/toolbar/widgets/lib.ts +13 -0
  197. package/src/tools/BaseTool.ts +169 -0
  198. package/src/tools/Eraser.test.ts +103 -0
  199. package/src/tools/Eraser.ts +173 -0
  200. package/src/tools/FindTool.test.ts +67 -0
  201. package/src/tools/FindTool.ts +153 -0
  202. package/src/tools/InputFilter/FunctionMapper.ts +17 -0
  203. package/src/tools/InputFilter/InputMapper.ts +41 -0
  204. package/src/tools/InputFilter/InputPipeline.test.ts +41 -0
  205. package/src/tools/InputFilter/InputPipeline.ts +34 -0
  206. package/src/tools/InputFilter/InputStabilizer.ts +254 -0
  207. package/src/tools/InputFilter/StrokeKeyboardControl.ts +104 -0
  208. package/src/tools/PanZoom.test.ts +339 -0
  209. package/src/tools/PanZoom.ts +525 -0
  210. package/src/tools/PasteHandler.ts +94 -0
  211. package/src/tools/Pen.test.ts +260 -0
  212. package/src/tools/Pen.ts +284 -0
  213. package/src/tools/PipetteTool.ts +84 -0
  214. package/src/tools/SelectionTool/SelectAllShortcutHandler.ts +29 -0
  215. package/src/tools/SelectionTool/Selection.ts +647 -0
  216. package/src/tools/SelectionTool/SelectionHandle.ts +142 -0
  217. package/src/tools/SelectionTool/SelectionTool.test.ts +370 -0
  218. package/src/tools/SelectionTool/SelectionTool.ts +510 -0
  219. package/src/tools/SelectionTool/TransformMode.ts +112 -0
  220. package/src/tools/SelectionTool/types.ts +11 -0
  221. package/src/tools/SoundUITool.ts +221 -0
  222. package/src/tools/TextTool.ts +339 -0
  223. package/src/tools/ToolController.ts +224 -0
  224. package/src/tools/ToolEnabledGroup.ts +14 -0
  225. package/src/tools/ToolSwitcherShortcut.ts +39 -0
  226. package/src/tools/ToolbarShortcutHandler.ts +39 -0
  227. package/src/tools/UndoRedoShortcut.test.ts +62 -0
  228. package/src/tools/UndoRedoShortcut.ts +24 -0
  229. package/src/tools/keybindings.ts +85 -0
  230. package/src/tools/lib.ts +22 -0
  231. package/src/tools/localization.ts +76 -0
  232. package/src/types.ts +151 -0
  233. package/src/util/ReactiveValue.test.ts +168 -0
  234. package/src/util/ReactiveValue.ts +241 -0
  235. package/src/util/assertions.ts +55 -0
  236. package/src/util/fileToBase64.ts +18 -0
  237. package/src/util/guessKeyCodeFromKey.ts +36 -0
  238. package/src/util/listPrefixMatch.ts +19 -0
  239. package/src/util/stopPropagationOfScrollingWheelEvents.ts +20 -0
  240. package/src/util/untilNextAnimationFrame.ts +9 -0
  241. package/src/util/waitForAll.ts +18 -0
  242. package/src/util/waitForTimeout.ts +9 -0
  243. package/src/version.test.ts +12 -0
  244. package/src/version.ts +3 -0
@@ -0,0 +1,294 @@
1
+ import { Color4, Mat33, Rect2, TextComponent, EditorImage, Vec2, StrokeComponent, SelectionTool, sendPenEvent, InputEvtType } from './lib';
2
+ import TextRenderingStyle from './rendering/TextRenderingStyle';
3
+ import SVGLoader from './SVGLoader';
4
+ import createEditor from './testing/createEditor';
5
+
6
+ describe('Editor.toSVG', () => {
7
+ it('should correctly nest text objects', async () => {
8
+ const editor = createEditor();
9
+ const textStyle: TextRenderingStyle = {
10
+ fontFamily: 'sans', size: 12, renderingStyle: { fill: Color4.black }
11
+ };
12
+ const text = new TextComponent([
13
+ 'Testing...',
14
+ new TextComponent([ 'Test 2' ], Mat33.translation(Vec2.of(0, 100)), textStyle),
15
+ ], Mat33.identity, textStyle);
16
+ editor.dispatch(EditorImage.addElement(text));
17
+
18
+ const matches = editor.image.getElementsIntersectingRegion(new Rect2(4, -100, 100, 100));
19
+ expect(matches).toHaveLength(1);
20
+ expect(text).not.toBeNull();
21
+
22
+ const asSVG = editor.toSVG();
23
+ const allTSpans = [ ...asSVG.querySelectorAll('tspan') ];
24
+ expect(allTSpans).toHaveLength(1);
25
+ expect(allTSpans[0].getAttribute('x')).toBe('0');
26
+ expect(allTSpans[0].getAttribute('y')).toBe('100');
27
+ expect(allTSpans[0].style.transform).toBe('');
28
+ });
29
+
30
+ it('should preserve empty tspans', async () => {
31
+ const editor = createEditor();
32
+ await editor.loadFrom(SVGLoader.fromString(`
33
+ <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
34
+ <style id="js-draw-style-sheet">
35
+ path {
36
+ stroke-linecap:round;
37
+ stroke-linejoin:round;
38
+ }
39
+ </style>
40
+ <text style="transform: matrix(1, 0, 0, 1, 12, 35); font-family: sans-serif; font-size: 32px; fill: rgb(128, 51, 128);">Testing...<tspan x="3" y="40" style="font-family: sans-serif; font-size: 33px; fill: rgb(128, 51, 128);"></tspan><tspan x="3" y="70">Test 2. ☺</tspan></text>
41
+ </svg>
42
+ `, true));
43
+
44
+ const textNodesInImage = editor.image.getAllElements().filter(elem => elem instanceof TextComponent);
45
+ expect(
46
+ textNodesInImage
47
+ ).toHaveLength(1);
48
+
49
+ const asSVG = editor.toSVG();
50
+ const textObject = asSVG.querySelector('text');
51
+
52
+ if (!textObject) {
53
+ throw new Error('No text object found');
54
+ }
55
+
56
+ const childTextNodes = textObject.querySelectorAll('tspan');
57
+ expect(childTextNodes).toHaveLength(2);
58
+ });
59
+
60
+ it('should preserve text child size/placement while not saving additional properties', async () => {
61
+ const secondLineText = 'This is a test of a thing that has been known to break. Will this test catch the issue?';
62
+ const thirdLineText = 'This is a test of saving/loading multi-line text...';
63
+
64
+ const editor = createEditor();
65
+ await editor.loadFrom(SVGLoader.fromString(`
66
+ <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
67
+ <style id="js-draw-style-sheet">
68
+ path {
69
+ stroke-linecap:round;
70
+ stroke-linejoin:round;
71
+ }
72
+ </style>
73
+ <text style="transform: matrix(1, 0, 0, 1, 12, 35); font-family: sans-serif; font-size: 32px; fill: rgb(128, 51, 128);">Testing...<tspan x="3" y="40" style="font-family: sans-serif; font-size: 33px; fill: rgb(128, 51, 128);">${secondLineText}</tspan><tspan x="0" y="72" style="font-family: sans-serif; font-size: 32px; fill: rgb(128, 51, 128);">${thirdLineText}</tspan><tspan x="0" y="112" style="font-family: sans-serif; font-size: 32px; fill: rgb(128, 51, 128);">Will it pass or fail?</tspan></text>
74
+ </svg>
75
+ `, true));
76
+
77
+ expect(
78
+ editor.image.getAllElements().filter(elem => elem instanceof TextComponent)
79
+ ).toHaveLength(1);
80
+
81
+ const asSVG = editor.toSVG();
82
+ const textObject = asSVG.querySelector('text');
83
+
84
+ if (!textObject) {
85
+ throw new Error('No text object found');
86
+ }
87
+
88
+ expect(textObject.style.transform.replace(/\s+/g, '')).toBe('matrix(1,0,0,1,12,35)');
89
+ expect(textObject.style.fontFamily).toBe('sans-serif');
90
+ expect(textObject.style.fontSize).toBe('32px');
91
+
92
+ const childTextNodes = textObject.querySelectorAll('tspan');
93
+ expect(childTextNodes).toHaveLength(3);
94
+ const firstChild = childTextNodes[0];
95
+
96
+ expect(firstChild.textContent).toBe(secondLineText);
97
+ expect(firstChild.style.transform).toBe('');
98
+ expect(firstChild.style.fontSize).toBe('33px');
99
+ expect(firstChild.getAttribute('x')).toBe('3');
100
+ expect(firstChild.getAttribute('y')).toBe('40');
101
+
102
+ // Should not save a fontSize when not necessary (same fill as parent text node)
103
+ const secondChild = childTextNodes[1];
104
+ expect(secondChild.style.fontSize ?? '').toBe('');
105
+
106
+ // Should not save additional "style" attributes when not necessary
107
+ // TODO: Uncomment before some future major version release. Currently a "fill" is set for every
108
+ // tspan to work around a loading bug.
109
+ //expect(secondChild.outerHTML).toBe(`<tspan x="0" y="72">${thirdLineText}</tspan>`);
110
+ });
111
+
112
+ it('should preserve group elements', async () => {
113
+ const editor = createEditor();
114
+ await editor.loadFrom(SVGLoader.fromString(`
115
+ <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
116
+ <style id="js-draw-style-sheet">
117
+ path {
118
+ stroke-linecap:round;
119
+ stroke-linejoin:round;
120
+ }
121
+ </style>
122
+ <g id='main-group'>
123
+ <g id='sub-group-1'>
124
+ <path d='M0,0 L10,10 0,10' fill='#f00'/>
125
+ <path d='M20,0 L10,10 0,10'/>
126
+ </g>
127
+ <g id='empty-group-2'></g>
128
+ </g>
129
+ <g id='empty-group-3'></g>
130
+
131
+ <!-- Groups without IDs should also be preserved -->
132
+ <g><g><g id='marker-1'></g></g></g>
133
+ <g class='test'><g id='marker-2'/></g>
134
+
135
+ <!-- Groups with duplicate IDs should preserved (though IDs)
136
+ may be changed -->
137
+ <g id='empty-group-2'/>
138
+ <g id='empty-group-2'><g id='empty-group-2'/></g>
139
+ </svg>
140
+ `));
141
+
142
+ // Both paths should exist.
143
+ expect(
144
+ editor.image
145
+ .getElementsIntersectingRegion(new Rect2(-10, -10, 100, 100))
146
+ .filter(elem => elem instanceof StrokeComponent)
147
+ ).toHaveLength(2);
148
+
149
+ const outputSVG = editor.toSVG();
150
+
151
+ // Should still have the expected number of groups
152
+ expect(outputSVG.querySelectorAll('g')).toHaveLength(12);
153
+
154
+ // Should preserve the empty group.
155
+ expect(outputSVG.querySelectorAll('g#empty-group-2')).toHaveLength(1);
156
+
157
+ // The empty group should still have the correct parent
158
+ expect(outputSVG.querySelectorAll('g#main-group > g#empty-group-2')).toHaveLength(1);
159
+
160
+ // Paths should still be children of sub-group-1
161
+ expect(outputSVG.querySelectorAll('g#sub-group-1 > path')).toHaveLength(2);
162
+
163
+ // sub-group-1 should have the correct parent
164
+ expect(outputSVG.querySelectorAll('g#main-group > g#sub-group-1')).toHaveLength(1);
165
+
166
+ // And these should be the only paths.
167
+ expect(outputSVG.querySelectorAll('path')).toHaveLength(2);
168
+
169
+ // Should also preserve groups without IDs
170
+ // Selector ref: https://stackoverflow.com/a/18607777
171
+ expect(outputSVG.querySelectorAll('svg > g > g > g#marker-1')).toHaveLength(1);
172
+ expect(outputSVG.querySelectorAll('svg > g > g#marker-2')).toHaveLength(1);
173
+
174
+ // Should preserve class names on `g` objects:
175
+ expect(outputSVG.querySelectorAll('svg > g.test > g#marker-2')).toHaveLength(1);
176
+
177
+ // Should preserve groups that had duplicate IDs
178
+ expect(outputSVG.querySelectorAll('svg > g#empty-group-2--1')).toHaveLength(1);
179
+ expect(outputSVG.querySelectorAll('svg > g#empty-group-2--2')).toHaveLength(1);
180
+ expect(outputSVG.querySelectorAll('svg > g#empty-group-2--2 > g#empty-group-2--3')).toHaveLength(1);
181
+ });
182
+
183
+ describe('should not preserve group elements when doing so would change the z order', () => {
184
+ it('in an image with few items', async () => {
185
+ const editor = createEditor();
186
+ await editor.loadFrom(SVGLoader.fromString(`
187
+ <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
188
+ <g id='main-group-1'>
189
+ <path d='M0,0 L-10,10 0,10' fill='#f00'/>
190
+ <path d='M20,0 L10,10 0,10'/>
191
+ </g>
192
+ <path d='M40,40 l10,10 0,10'/>
193
+ </svg>
194
+ `));
195
+
196
+ // All paths should exist.
197
+ expect(
198
+ editor.image
199
+ .getElementsIntersectingRegion(new Rect2(-10, -10, 100, 100))
200
+ .filter(elem => elem instanceof StrokeComponent)
201
+ ).toHaveLength(3);
202
+
203
+ // Before modifying, both paths should be within the main-group-1 group
204
+ expect(editor.toSVG().querySelectorAll('svg > g#main-group-1 > path')).toHaveLength(2);
205
+
206
+ const selectionTool = editor.toolController.getMatchingTools(SelectionTool)[0];
207
+ selectionTool.setEnabled(true);
208
+
209
+ // Select the bottommost stroke
210
+ sendPenEvent(editor, InputEvtType.PointerDownEvt, Vec2.of(-11, 9));
211
+ sendPenEvent(editor, InputEvtType.PointerMoveEvt, Vec2.of(-9, 10));
212
+ sendPenEvent(editor, InputEvtType.PointerUpEvt, Vec2.of(-9, 10));
213
+
214
+ // The stroke should be selected
215
+ expect(selectionTool.getSelectedObjects()).toHaveLength(1);
216
+ expect(selectionTool.getSelectedObjects()[0].getBBox())
217
+ .objEq(new Rect2(-10, 0, 10, 10));
218
+
219
+ // Drag the selection (moves the selected item to the top)
220
+ sendPenEvent(editor, InputEvtType.PointerDownEvt, Vec2.of(-11, 9));
221
+ sendPenEvent(editor, InputEvtType.PointerMoveEvt, Vec2.of(0, 0));
222
+ sendPenEvent(editor, InputEvtType.PointerUpEvt, Vec2.of(0, 0));
223
+
224
+ expect(selectionTool.getSelectedObjects()[0].getBBox())
225
+ .not.objEq(new Rect2(-10, 0, 10, 10));
226
+ selectionTool.setEnabled(false);
227
+
228
+ // One of the items should have been moved out of the main group
229
+ const outputSVG = editor.toSVG();
230
+ expect(outputSVG.querySelectorAll('svg > path')).toHaveLength(2);
231
+ expect(outputSVG.querySelectorAll('svg > g#main-group-1 > path')).toHaveLength(1);
232
+ });
233
+
234
+ it('in an image with many items in nested groups', async () => {
235
+ const editor = createEditor();
236
+ await editor.loadFrom(SVGLoader.fromString(`
237
+ <svg viewBox="0 0 500 500" width="500" height="500" version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
238
+ <path d='M-100,-100 l 2,2 0,2'/>
239
+ <g id='group-1'>
240
+ <path d='M0,0 L-10,10 0,10' fill='#f00'/>
241
+ <path d='M20,0 L10,10 0,10'/>
242
+
243
+ <g id='group-2'>
244
+ <path d='M100,100 l 2,2 0,2'/>
245
+ </g>
246
+ </g>
247
+ <path d='M40,40 l10,10 0,10'/>
248
+ </svg>
249
+ `));
250
+
251
+ // .expects that all paths have their original parent groups.
252
+ const expectGroupParentsToBeOriginal = () => {
253
+ expect(
254
+ editor.image
255
+ .getAllElements()
256
+ .filter(elem => elem instanceof StrokeComponent)
257
+ ).toHaveLength(5);
258
+
259
+ const output = editor.toSVG();
260
+ expect(
261
+ output.querySelectorAll('svg > g#group-1 path')
262
+ ).toHaveLength(3);
263
+ expect(
264
+ output.querySelectorAll('svg > g#group-1 > g > path')
265
+ ).toHaveLength(1);
266
+ };
267
+
268
+ expectGroupParentsToBeOriginal();
269
+
270
+ const nudgePathNear = async (pos: Vec2) => {
271
+ const targetElems = editor.image.getElementsIntersectingRegion(Rect2.bboxOf([ pos ], 5));
272
+
273
+ expect(targetElems).toHaveLength(1);
274
+
275
+ // Move the path to the top
276
+ const target = targetElems[0];
277
+ await editor.dispatch(target.transformBy(Mat33.scaling2D(1.01)));
278
+ };
279
+
280
+ // Moving a path that's below all groups should not change group parentings.
281
+ nudgePathNear(Vec2.of(-100, -100));
282
+ expectGroupParentsToBeOriginal();
283
+
284
+ // Moving a path that's within nested groups should move the path out of that group.
285
+ nudgePathNear(Vec2.of(100, 100));
286
+
287
+ const outputSVG = editor.toSVG();
288
+ expect(outputSVG.querySelectorAll('svg > path')).toHaveLength(3);
289
+ expect(outputSVG.querySelectorAll('svg > g#group-1 > path')).toHaveLength(2);
290
+ expect(outputSVG.querySelectorAll('svg > g#group-1 > g')).toHaveLength(1);
291
+ expect(outputSVG.querySelectorAll('svg > g#group-1 > g > *')).toHaveLength(0);
292
+ });
293
+ });
294
+ });