meno-core 1.0.0

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 (231) hide show
  1. package/bin/cli.ts +281 -0
  2. package/build-static.ts +298 -0
  3. package/bunfig.toml +39 -0
  4. package/entries/client-router.tsx +111 -0
  5. package/entries/server-router.tsx +71 -0
  6. package/lib/client/ClientInitializer.test.ts +9 -0
  7. package/lib/client/ClientInitializer.test.ts.skip +92 -0
  8. package/lib/client/ClientInitializer.ts +60 -0
  9. package/lib/client/ErrorBoundary.test.tsx +595 -0
  10. package/lib/client/ErrorBoundary.tsx +230 -0
  11. package/lib/client/componentRegistry.test.ts +165 -0
  12. package/lib/client/componentRegistry.ts +18 -0
  13. package/lib/client/contexts/ThemeContext.tsx +73 -0
  14. package/lib/client/core/ComponentBuilder.test.ts +677 -0
  15. package/lib/client/core/ComponentBuilder.ts +660 -0
  16. package/lib/client/core/ComponentRenderer.test.tsx +176 -0
  17. package/lib/client/core/ComponentRenderer.tsx +83 -0
  18. package/lib/client/core/cmsTemplateProcessor.ts +129 -0
  19. package/lib/client/elementRegistry.ts +81 -0
  20. package/lib/client/hmr/HMRManager.tsx +179 -0
  21. package/lib/client/hmr/index.ts +5 -0
  22. package/lib/client/hmrWebSocket.test.ts +9 -0
  23. package/lib/client/hmrWebSocket.ts +250 -0
  24. package/lib/client/hooks/useColorVariables.test.ts +166 -0
  25. package/lib/client/hooks/useColorVariables.ts +249 -0
  26. package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
  27. package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
  28. package/lib/client/hydration/HydrationUtils.test.ts +154 -0
  29. package/lib/client/hydration/HydrationUtils.ts +35 -0
  30. package/lib/client/i18nConfigService.test.ts +74 -0
  31. package/lib/client/i18nConfigService.ts +78 -0
  32. package/lib/client/index.ts +56 -0
  33. package/lib/client/navigation.test.ts +441 -0
  34. package/lib/client/navigation.ts +23 -0
  35. package/lib/client/responsiveStyleResolver.test.ts +491 -0
  36. package/lib/client/responsiveStyleResolver.ts +184 -0
  37. package/lib/client/routing/RouteLoader.test.ts +635 -0
  38. package/lib/client/routing/RouteLoader.ts +347 -0
  39. package/lib/client/routing/Router.tsx +382 -0
  40. package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
  41. package/lib/client/scripts/ScriptExecutor.ts +171 -0
  42. package/lib/client/scripts/formHandler.ts +103 -0
  43. package/lib/client/styleProcessor.test.ts +126 -0
  44. package/lib/client/styleProcessor.ts +92 -0
  45. package/lib/client/styles/StyleInjector.test.ts +354 -0
  46. package/lib/client/styles/StyleInjector.ts +154 -0
  47. package/lib/client/templateEngine.test.ts +660 -0
  48. package/lib/client/templateEngine.ts +667 -0
  49. package/lib/client/theme.test.ts +173 -0
  50. package/lib/client/theme.ts +159 -0
  51. package/lib/client/utils/toast.ts +46 -0
  52. package/lib/server/createServer.ts +170 -0
  53. package/lib/server/cssGenerator.test.ts +172 -0
  54. package/lib/server/cssGenerator.ts +58 -0
  55. package/lib/server/fileWatcher.ts +134 -0
  56. package/lib/server/index.ts +55 -0
  57. package/lib/server/jsonLoader.test.ts +103 -0
  58. package/lib/server/jsonLoader.ts +350 -0
  59. package/lib/server/middleware/cors.test.ts +177 -0
  60. package/lib/server/middleware/cors.ts +69 -0
  61. package/lib/server/middleware/errorHandler.test.ts +208 -0
  62. package/lib/server/middleware/errorHandler.ts +63 -0
  63. package/lib/server/middleware/index.ts +9 -0
  64. package/lib/server/middleware/logger.test.ts +233 -0
  65. package/lib/server/middleware/logger.ts +99 -0
  66. package/lib/server/pageCache.test.ts +167 -0
  67. package/lib/server/pageCache.ts +97 -0
  68. package/lib/server/projectContext.ts +51 -0
  69. package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
  70. package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
  71. package/lib/server/providers/fileSystemPageProvider.ts +83 -0
  72. package/lib/server/routes/api/cms.test.ts +177 -0
  73. package/lib/server/routes/api/cms.ts +82 -0
  74. package/lib/server/routes/api/colors.ts +59 -0
  75. package/lib/server/routes/api/components.ts +70 -0
  76. package/lib/server/routes/api/config.test.ts +9 -0
  77. package/lib/server/routes/api/config.ts +28 -0
  78. package/lib/server/routes/api/core-routes.ts +182 -0
  79. package/lib/server/routes/api/functions.ts +170 -0
  80. package/lib/server/routes/api/index.ts +69 -0
  81. package/lib/server/routes/api/pages.ts +95 -0
  82. package/lib/server/routes/api/shared.test.ts +81 -0
  83. package/lib/server/routes/api/shared.ts +31 -0
  84. package/lib/server/routes/editor.test.ts +9 -0
  85. package/lib/server/routes/index.ts +104 -0
  86. package/lib/server/routes/pages.ts +161 -0
  87. package/lib/server/routes/static.ts +107 -0
  88. package/lib/server/services/ColorService.ts +193 -0
  89. package/lib/server/services/cmsService.test.ts +388 -0
  90. package/lib/server/services/cmsService.ts +296 -0
  91. package/lib/server/services/componentService.test.ts +276 -0
  92. package/lib/server/services/componentService.ts +346 -0
  93. package/lib/server/services/configService.ts +156 -0
  94. package/lib/server/services/fileWatcherService.ts +67 -0
  95. package/lib/server/services/index.ts +10 -0
  96. package/lib/server/services/pageService.test.ts +258 -0
  97. package/lib/server/services/pageService.ts +240 -0
  98. package/lib/server/ssrRenderer.test.ts +1005 -0
  99. package/lib/server/ssrRenderer.ts +878 -0
  100. package/lib/server/utilityClassGenerator.ts +11 -0
  101. package/lib/server/utils/index.ts +5 -0
  102. package/lib/server/utils/jsonLineMapper.test.ts +100 -0
  103. package/lib/server/utils/jsonLineMapper.ts +166 -0
  104. package/lib/server/validateStyleCoverage.test.ts +9 -0
  105. package/lib/server/validateStyleCoverage.ts +167 -0
  106. package/lib/server/websocketManager.test.ts +9 -0
  107. package/lib/server/websocketManager.ts +95 -0
  108. package/lib/shared/attributeNodeUtils.test.ts +152 -0
  109. package/lib/shared/attributeNodeUtils.ts +50 -0
  110. package/lib/shared/breakpoints.test.ts +166 -0
  111. package/lib/shared/breakpoints.ts +65 -0
  112. package/lib/shared/colorProperties.test.ts +111 -0
  113. package/lib/shared/colorProperties.ts +40 -0
  114. package/lib/shared/colorVariableUtils.test.ts +319 -0
  115. package/lib/shared/colorVariableUtils.ts +97 -0
  116. package/lib/shared/constants.test.ts +175 -0
  117. package/lib/shared/constants.ts +116 -0
  118. package/lib/shared/cssGeneration.ts +481 -0
  119. package/lib/shared/cssProperties.test.ts +252 -0
  120. package/lib/shared/cssProperties.ts +338 -0
  121. package/lib/shared/elementUtils.test.ts +245 -0
  122. package/lib/shared/elementUtils.ts +90 -0
  123. package/lib/shared/fontLoader.ts +97 -0
  124. package/lib/shared/i18n.test.ts +313 -0
  125. package/lib/shared/i18n.ts +286 -0
  126. package/lib/shared/index.ts +50 -0
  127. package/lib/shared/interfaces/contentProvider.test.ts +9 -0
  128. package/lib/shared/interfaces/contentProvider.ts +121 -0
  129. package/lib/shared/nodeUtils.test.ts +320 -0
  130. package/lib/shared/nodeUtils.ts +220 -0
  131. package/lib/shared/pathArrayUtils.test.ts +315 -0
  132. package/lib/shared/pathArrayUtils.ts +17 -0
  133. package/lib/shared/pathUtils.test.ts +260 -0
  134. package/lib/shared/pathUtils.ts +244 -0
  135. package/lib/shared/paths/Path.test.ts +74 -0
  136. package/lib/shared/paths/Path.ts +23 -0
  137. package/lib/shared/paths/PathConverter.test.ts +232 -0
  138. package/lib/shared/paths/PathConverter.ts +141 -0
  139. package/lib/shared/paths/PathUtils.ts +290 -0
  140. package/lib/shared/paths/PathValidator.test.ts +193 -0
  141. package/lib/shared/paths/PathValidator.ts +53 -0
  142. package/lib/shared/paths/index.ts +48 -0
  143. package/lib/shared/propResolver.test.ts +639 -0
  144. package/lib/shared/propResolver.ts +124 -0
  145. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
  146. package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
  147. package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
  148. package/lib/shared/registry/ClientRegistry.test.ts +26 -0
  149. package/lib/shared/registry/ClientRegistry.ts +15 -0
  150. package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
  151. package/lib/shared/registry/ComponentRegistry.ts +100 -0
  152. package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
  153. package/lib/shared/registry/NodeTypeManager.ts +94 -0
  154. package/lib/shared/registry/RegistryManager.test.ts +58 -0
  155. package/lib/shared/registry/RegistryManager.ts +60 -0
  156. package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
  157. package/lib/shared/registry/SSRRegistry.test.ts +26 -0
  158. package/lib/shared/registry/SSRRegistry.ts +15 -0
  159. package/lib/shared/registry/createNodeType.ts +175 -0
  160. package/lib/shared/registry/defineNodeType.ts +73 -0
  161. package/lib/shared/registry/fieldPresets.ts +109 -0
  162. package/lib/shared/registry/index.ts +50 -0
  163. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
  164. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
  165. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
  166. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
  167. package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
  168. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
  169. package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
  170. package/lib/shared/registry/nodeTypes/index.ts +75 -0
  171. package/lib/shared/responsiveScaling.test.ts +268 -0
  172. package/lib/shared/responsiveScaling.ts +194 -0
  173. package/lib/shared/responsiveStyleUtils.test.ts +300 -0
  174. package/lib/shared/responsiveStyleUtils.ts +139 -0
  175. package/lib/shared/slugTranslator.test.ts +325 -0
  176. package/lib/shared/slugTranslator.ts +177 -0
  177. package/lib/shared/styleNodeUtils.test.ts +132 -0
  178. package/lib/shared/styleNodeUtils.ts +102 -0
  179. package/lib/shared/styleUtils.test.ts +238 -0
  180. package/lib/shared/styleUtils.ts +63 -0
  181. package/lib/shared/themeDefaults.test.ts +113 -0
  182. package/lib/shared/themeDefaults.ts +103 -0
  183. package/lib/shared/tree/PathBuilder.ts +383 -0
  184. package/lib/shared/treePathUtils.test.ts +539 -0
  185. package/lib/shared/treePathUtils.ts +339 -0
  186. package/lib/shared/types/api.ts +58 -0
  187. package/lib/shared/types/cms.ts +95 -0
  188. package/lib/shared/types/colors.ts +45 -0
  189. package/lib/shared/types/components.ts +121 -0
  190. package/lib/shared/types/errors.test.ts +103 -0
  191. package/lib/shared/types/errors.ts +69 -0
  192. package/lib/shared/types/index.ts +96 -0
  193. package/lib/shared/types/nodes.ts +20 -0
  194. package/lib/shared/types/rendering.ts +61 -0
  195. package/lib/shared/types/styles.ts +38 -0
  196. package/lib/shared/types.ts +11 -0
  197. package/lib/shared/utilityClassConfig.ts +287 -0
  198. package/lib/shared/utilityClassMapper.test.ts +140 -0
  199. package/lib/shared/utilityClassMapper.ts +229 -0
  200. package/lib/shared/utils/fileUtils.test.ts +99 -0
  201. package/lib/shared/utils/fileUtils.ts +56 -0
  202. package/lib/shared/utils.test.ts +261 -0
  203. package/lib/shared/utils.ts +84 -0
  204. package/lib/shared/validation/index.ts +7 -0
  205. package/lib/shared/validation/propValidator.test.ts +178 -0
  206. package/lib/shared/validation/propValidator.ts +238 -0
  207. package/lib/shared/validation/schemas.test.ts +177 -0
  208. package/lib/shared/validation/schemas.ts +401 -0
  209. package/lib/shared/validation/validators.test.ts +109 -0
  210. package/lib/shared/validation/validators.ts +304 -0
  211. package/lib/test-utils/dom-setup.ts +55 -0
  212. package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
  213. package/lib/test-utils/factories/DomMockFactory.ts +487 -0
  214. package/lib/test-utils/factories/EventMockFactory.ts +244 -0
  215. package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
  216. package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
  217. package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
  218. package/lib/test-utils/factories/index.ts +11 -0
  219. package/lib/test-utils/fixtures.ts +134 -0
  220. package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
  221. package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
  222. package/lib/test-utils/helpers/index.ts +6 -0
  223. package/lib/test-utils/helpers.test.ts +73 -0
  224. package/lib/test-utils/helpers.ts +90 -0
  225. package/lib/test-utils/index.ts +17 -0
  226. package/lib/test-utils/mockFactories.ts +92 -0
  227. package/lib/test-utils/mocks.ts +341 -0
  228. package/package.json +38 -0
  229. package/templates/index-router.html +34 -0
  230. package/tsconfig.json +14 -0
  231. package/vite.config.ts +43 -0
@@ -0,0 +1,539 @@
1
+ /**
2
+ * Tree Path Utils Tests
3
+ * Tests for tree structure parsing and navigation utilities
4
+ */
5
+
6
+ import { test, expect, describe } from 'bun:test';
7
+ import {
8
+ validateComponentStructure,
9
+ getRootData,
10
+ getParentAndIndexFromPath,
11
+ extractStructureWithoutStyles,
12
+ calculateInsertionIndex,
13
+ isValidNodeLocation,
14
+ isValidPath,
15
+ isPageDataParent,
16
+ isComponentNodeParent,
17
+ getParentChildren,
18
+ setParentChildren,
19
+ } from './treePathUtils';
20
+ import type { ComponentNode, PageData, JSONPage, PageDataWithComponent } from './types';
21
+ import { NODE_TYPE } from './constants';
22
+
23
+ // Helper to create a basic page structure
24
+ function createPageData(root?: Partial<ComponentNode>): JSONPage {
25
+ return {
26
+ root: {
27
+ type: NODE_TYPE.NODE,
28
+ tag: 'div',
29
+ children: [],
30
+ ...root,
31
+ },
32
+ };
33
+ }
34
+
35
+ // Helper to create a component page structure
36
+ function createComponentPageData(structure?: Partial<ComponentNode>): PageDataWithComponent {
37
+ return {
38
+ component: {
39
+ interface: {},
40
+ structure: {
41
+ type: NODE_TYPE.NODE,
42
+ tag: 'div',
43
+ children: [],
44
+ ...structure,
45
+ },
46
+ },
47
+ };
48
+ }
49
+
50
+ // Helper to create a node
51
+ function createNode(overrides?: Partial<ComponentNode>): ComponentNode {
52
+ return {
53
+ type: NODE_TYPE.NODE,
54
+ tag: 'div',
55
+ children: [],
56
+ ...overrides,
57
+ } as ComponentNode;
58
+ }
59
+
60
+ describe('validateComponentStructure', () => {
61
+ test('should return true for valid page with root', () => {
62
+ const pageData = createPageData();
63
+ expect(validateComponentStructure(pageData)).toBe(true);
64
+ });
65
+
66
+ test('should return true for valid component with structure', () => {
67
+ const pageData = createComponentPageData();
68
+ expect(validateComponentStructure(pageData)).toBe(true);
69
+ });
70
+
71
+ test('should return false for null', () => {
72
+ expect(validateComponentStructure(null)).toBe(false);
73
+ });
74
+
75
+ test('should return false for empty object', () => {
76
+ expect(validateComponentStructure({} as PageData)).toBe(false);
77
+ });
78
+
79
+ test('should return false for component without structure', () => {
80
+ const pageData = { component: {} } as PageDataWithComponent;
81
+ expect(validateComponentStructure(pageData)).toBe(false);
82
+ });
83
+ });
84
+
85
+ describe('getRootData', () => {
86
+ test('should return root for page format', () => {
87
+ const pageData = createPageData({ tag: 'main' });
88
+ const root = getRootData(pageData);
89
+ expect(root?.tag).toBe('main');
90
+ });
91
+
92
+ test('should return structure for component format', () => {
93
+ const pageData = createComponentPageData({ tag: 'section' });
94
+ const root = getRootData(pageData);
95
+ expect(root?.tag).toBe('section');
96
+ });
97
+
98
+ test('should return null for null input', () => {
99
+ expect(getRootData(null)).toBeNull();
100
+ });
101
+
102
+ test('should return null for empty object', () => {
103
+ expect(getRootData({} as PageData)).toBeNull();
104
+ });
105
+
106
+ test('should return null for component with missing structure', () => {
107
+ const pageData = { component: {} } as PageDataWithComponent;
108
+ expect(getRootData(pageData)).toBeNull();
109
+ });
110
+ });
111
+
112
+ describe('getParentAndIndexFromPath', () => {
113
+ test('should return root for path [0]', () => {
114
+ const pageData = createPageData({ tag: 'main' });
115
+ const result = getParentAndIndexFromPath([0], pageData);
116
+
117
+ expect(result).not.toBeNull();
118
+ expect(result?.node?.tag).toBe('main');
119
+ expect(result?.index).toBe(0);
120
+ expect(result?.parent).toBe(pageData);
121
+ });
122
+
123
+ test('should return child for path [0, 0]', () => {
124
+ const child = createNode({ tag: 'p' });
125
+ const pageData = createPageData({ children: [child] });
126
+ const result = getParentAndIndexFromPath([0, 0], pageData);
127
+
128
+ expect(result).not.toBeNull();
129
+ expect(result?.node?.tag).toBe('p');
130
+ expect(result?.index).toBe(0);
131
+ });
132
+
133
+ test('should return deeply nested child', () => {
134
+ const deepChild = createNode({ tag: 'span' });
135
+ const pageData = createPageData({
136
+ children: [
137
+ createNode({
138
+ tag: 'section',
139
+ children: [
140
+ createNode({
141
+ tag: 'article',
142
+ children: [deepChild],
143
+ }),
144
+ ],
145
+ }),
146
+ ],
147
+ });
148
+ const result = getParentAndIndexFromPath([0, 0, 0, 0], pageData);
149
+
150
+ expect(result).not.toBeNull();
151
+ expect(result?.node?.tag).toBe('span');
152
+ });
153
+
154
+ test('should return null for invalid path', () => {
155
+ const pageData = createPageData();
156
+ const result = getParentAndIndexFromPath([0, 99], pageData);
157
+
158
+ expect(result).toBeNull();
159
+ });
160
+
161
+ test('should return null for empty children', () => {
162
+ const pageData = createPageData({ children: [] });
163
+ const result = getParentAndIndexFromPath([0, 0], pageData);
164
+
165
+ expect(result).toBeNull();
166
+ });
167
+
168
+ test('should return null for string child at path', () => {
169
+ const pageData = createPageData({ children: ['text content'] });
170
+ const result = getParentAndIndexFromPath([0, 0], pageData);
171
+
172
+ expect(result).toBeNull();
173
+ });
174
+
175
+ test('should handle string path conversion', () => {
176
+ const child = createNode({ tag: 'p' });
177
+ const pageData = createPageData({ children: [child] });
178
+ // stringToPath expects comma-separated format: "0,0" -> [0, 0]
179
+ const result = getParentAndIndexFromPath('0,0', pageData);
180
+
181
+ // Should successfully convert string to path and find the node
182
+ expect(result).not.toBeNull();
183
+ expect(result?.node?.tag).toBe('p');
184
+ });
185
+
186
+ test('should return correct parent for nested path', () => {
187
+ const grandchild = createNode({ tag: 'span' });
188
+ const child = createNode({ tag: 'p', children: [grandchild] });
189
+ const pageData = createPageData({ children: [child] });
190
+ const result = getParentAndIndexFromPath([0, 0, 0], pageData);
191
+
192
+ expect(result).not.toBeNull();
193
+ expect(result?.node?.tag).toBe('span');
194
+ expect((result?.parent as ComponentNode)?.tag).toBe('p');
195
+ });
196
+ });
197
+
198
+ describe('extractStructureWithoutStyles', () => {
199
+ test('should preserve type and tag', () => {
200
+ const node = createNode({ tag: 'div', style: { base: { color: 'red' } } });
201
+ const result = extractStructureWithoutStyles(node) as ComponentNode;
202
+
203
+ expect(result.type).toBe(NODE_TYPE.NODE);
204
+ expect(result.tag).toBe('div');
205
+ });
206
+
207
+ test('should remove style property', () => {
208
+ const node = createNode({ tag: 'div', style: { base: { color: 'red' } } });
209
+ const result = extractStructureWithoutStyles(node) as ComponentNode;
210
+
211
+ expect((result as any).style).toBeUndefined();
212
+ });
213
+
214
+ test('should preserve children structure', () => {
215
+ const node = createNode({
216
+ tag: 'div',
217
+ children: [createNode({ tag: 'p' })],
218
+ });
219
+ const result = extractStructureWithoutStyles(node) as ComponentNode;
220
+
221
+ expect(result.children?.length).toBe(1);
222
+ expect((result.children?.[0] as ComponentNode)?.tag).toBe('p');
223
+ });
224
+
225
+ test('should handle string children', () => {
226
+ const node = createNode({ tag: 'p', children: ['text'] });
227
+ const result = extractStructureWithoutStyles(node) as ComponentNode;
228
+
229
+ expect(result.children).toEqual(['text']);
230
+ });
231
+
232
+ test('should return null for null input', () => {
233
+ expect(extractStructureWithoutStyles(null)).toBeNull();
234
+ });
235
+
236
+ test('should return undefined for undefined input', () => {
237
+ expect(extractStructureWithoutStyles(undefined)).toBeUndefined();
238
+ });
239
+
240
+ test('should return string for string input', () => {
241
+ expect(extractStructureWithoutStyles('text')).toBe('text');
242
+ });
243
+ });
244
+
245
+ describe('calculateInsertionIndex', () => {
246
+ const mockChildren = [
247
+ createNode({ tag: 'p' }),
248
+ createNode({ tag: 'span' }),
249
+ createNode({ tag: 'div' }),
250
+ ];
251
+ const emptyNodeDataMap = new Map<string, ComponentNode | string>();
252
+
253
+ test('should use childIndex when provided', () => {
254
+ const target = { childIndex: 1 };
255
+ expect(calculateInsertionIndex(target, mockChildren, emptyNodeDataMap)).toBe(1);
256
+ });
257
+
258
+ test('should clamp childIndex to array length', () => {
259
+ const target = { childIndex: 99 };
260
+ expect(calculateInsertionIndex(target, mockChildren, emptyNodeDataMap)).toBe(3);
261
+ });
262
+
263
+ test('should handle negative childIndex (implementation returns array length)', () => {
264
+ // Note: Implementation uses childIndex >= 0 check, so negative values fall through
265
+ // to append behavior (returns array length)
266
+ const target = { childIndex: -5 };
267
+ // Actual behavior: negative index fails the >= 0 check, falls through to append
268
+ expect(calculateInsertionIndex(target, mockChildren, emptyNodeDataMap)).toBe(3);
269
+ });
270
+
271
+ test('should use linearIndex as fallback', () => {
272
+ const target = { linearIndex: 2 };
273
+ expect(calculateInsertionIndex(target, mockChildren, emptyNodeDataMap)).toBe(2);
274
+ });
275
+
276
+ test('should return array length when no index provided', () => {
277
+ const target = {};
278
+ expect(calculateInsertionIndex(target, mockChildren, emptyNodeDataMap)).toBe(3);
279
+ });
280
+
281
+ test('should handle childAfterItem with nodeDataMap', () => {
282
+ const nodeDataMap = new Map<string, ComponentNode | string>();
283
+ nodeDataMap.set('path_0', mockChildren[0]);
284
+
285
+ const target = { childAfterItem: { index: 'path_0' } };
286
+ expect(calculateInsertionIndex(target, mockChildren, nodeDataMap)).toBe(1);
287
+ });
288
+
289
+ test('should handle childBeforeItem with nodeDataMap', () => {
290
+ const nodeDataMap = new Map<string, ComponentNode | string>();
291
+ nodeDataMap.set('path_1', mockChildren[1]);
292
+
293
+ const target = { childBeforeItem: { index: 'path_1' } };
294
+ expect(calculateInsertionIndex(target, mockChildren, nodeDataMap)).toBe(1);
295
+ });
296
+ });
297
+
298
+ describe('isValidNodeLocation', () => {
299
+ test('should return true for valid NodeLocation', () => {
300
+ const location = {
301
+ parent: createNode(),
302
+ index: 0,
303
+ node: createNode(),
304
+ };
305
+ expect(isValidNodeLocation(location)).toBe(true);
306
+ });
307
+
308
+ test('should return true for NodeLocation with null node', () => {
309
+ const location = {
310
+ parent: createNode(),
311
+ index: 0,
312
+ node: null,
313
+ };
314
+ expect(isValidNodeLocation(location)).toBe(true);
315
+ });
316
+
317
+ test('should return true for NodeLocation with null parent', () => {
318
+ const location = {
319
+ parent: null,
320
+ index: 0,
321
+ node: createNode(),
322
+ };
323
+ expect(isValidNodeLocation(location)).toBe(true);
324
+ });
325
+
326
+ test('should return false for null', () => {
327
+ expect(isValidNodeLocation(null)).toBe(false);
328
+ });
329
+
330
+ test('should return false for missing index', () => {
331
+ const location = { parent: createNode(), node: createNode() };
332
+ expect(isValidNodeLocation(location)).toBe(false);
333
+ });
334
+
335
+ test('should return false for negative index', () => {
336
+ const location = { parent: createNode(), index: -1, node: createNode() };
337
+ expect(isValidNodeLocation(location)).toBe(false);
338
+ });
339
+ });
340
+
341
+ describe('isValidPath', () => {
342
+ test('should return true for valid path array', () => {
343
+ expect(isValidPath([0])).toBe(true);
344
+ expect(isValidPath([0, 1, 2])).toBe(true);
345
+ });
346
+
347
+ test('should return true for empty array', () => {
348
+ expect(isValidPath([])).toBe(true);
349
+ });
350
+
351
+ test('should return false for non-array', () => {
352
+ expect(isValidPath('0,1,2')).toBe(false);
353
+ expect(isValidPath(null)).toBe(false);
354
+ expect(isValidPath(undefined)).toBe(false);
355
+ });
356
+
357
+ test('should return false for array with non-numbers', () => {
358
+ expect(isValidPath([0, 'a', 2])).toBe(false);
359
+ expect(isValidPath([null])).toBe(false);
360
+ });
361
+
362
+ test('should return false for array with negative numbers', () => {
363
+ expect(isValidPath([0, -1, 2])).toBe(false);
364
+ });
365
+ });
366
+
367
+ describe('isPageDataParent', () => {
368
+ test('should return true for JSONPage', () => {
369
+ const pageData = createPageData();
370
+ expect(isPageDataParent(pageData)).toBe(true);
371
+ });
372
+
373
+ test('should return true for PageDataWithComponent', () => {
374
+ const pageData = createComponentPageData();
375
+ expect(isPageDataParent(pageData)).toBe(true);
376
+ });
377
+
378
+ test('should return false for ComponentNode', () => {
379
+ const node = createNode();
380
+ expect(isPageDataParent(node)).toBe(false);
381
+ });
382
+
383
+ test('should return false for null', () => {
384
+ expect(isPageDataParent(null)).toBe(false);
385
+ });
386
+ });
387
+
388
+ describe('isComponentNodeParent', () => {
389
+ test('should return true for HtmlNode', () => {
390
+ const node = createNode({ tag: 'div' });
391
+ expect(isComponentNodeParent(node)).toBe(true);
392
+ });
393
+
394
+ test('should return true for ComponentInstanceNode', () => {
395
+ const node = { type: NODE_TYPE.COMPONENT, component: 'Button' } as ComponentNode;
396
+ expect(isComponentNodeParent(node)).toBe(true);
397
+ });
398
+
399
+ test('should return true for SlotMarker', () => {
400
+ const node = { type: NODE_TYPE.SLOT } as ComponentNode;
401
+ expect(isComponentNodeParent(node)).toBe(true);
402
+ });
403
+
404
+ test('should return true for ObjectLinkNode', () => {
405
+ const node = { type: NODE_TYPE.OBJECT_LINK } as ComponentNode;
406
+ expect(isComponentNodeParent(node)).toBe(true);
407
+ });
408
+
409
+ test('should return true for EmbedNode', () => {
410
+ const node = { type: NODE_TYPE.EMBED } as ComponentNode;
411
+ expect(isComponentNodeParent(node)).toBe(true);
412
+ });
413
+
414
+ test('should return false for PageData', () => {
415
+ const pageData = createPageData();
416
+ expect(isComponentNodeParent(pageData)).toBe(false);
417
+ });
418
+
419
+ test('should return false for null', () => {
420
+ expect(isComponentNodeParent(null)).toBe(false);
421
+ });
422
+ });
423
+
424
+ describe('getParentChildren', () => {
425
+ test('should return children from ComponentNode', () => {
426
+ const children = [createNode({ tag: 'p' })];
427
+ const node = createNode({ children });
428
+ const result = getParentChildren(node);
429
+
430
+ expect(result).toBe(children);
431
+ });
432
+
433
+ test('should return children from PageData root', () => {
434
+ const children = [createNode({ tag: 'p' })];
435
+ const pageData = createPageData({ children });
436
+ const result = getParentChildren(pageData);
437
+
438
+ expect(result).toBe(children);
439
+ });
440
+
441
+ test('should return null for node without children', () => {
442
+ const node = { type: NODE_TYPE.NODE, tag: 'img' } as ComponentNode;
443
+ const result = getParentChildren(node);
444
+
445
+ expect(result).toBeNull();
446
+ });
447
+
448
+ test('should return null for null input', () => {
449
+ expect(getParentChildren(null)).toBeNull();
450
+ });
451
+ });
452
+
453
+ describe('setParentChildren', () => {
454
+ test('should set children on ComponentNode', () => {
455
+ const node = createNode({ children: [] });
456
+ const newChildren = [createNode({ tag: 'p' })];
457
+
458
+ setParentChildren(node, newChildren);
459
+
460
+ expect(node.children).toBe(newChildren);
461
+ });
462
+
463
+ test('should set children on PageData root', () => {
464
+ const pageData = createPageData({ children: [] });
465
+ const newChildren = [createNode({ tag: 'p' })];
466
+
467
+ setParentChildren(pageData, newChildren);
468
+
469
+ expect(pageData.root?.children).toBe(newChildren);
470
+ });
471
+
472
+ test('should handle null parent gracefully', () => {
473
+ expect(() => setParentChildren(null, [])).not.toThrow();
474
+ });
475
+ });
476
+
477
+ describe('edge cases', () => {
478
+ test('getParentAndIndexFromPath should handle component pageData format', () => {
479
+ const child = createNode({ tag: 'span' });
480
+ const pageData = createComponentPageData({ children: [child] });
481
+ const result = getParentAndIndexFromPath([0, 0], pageData);
482
+
483
+ expect(result).not.toBeNull();
484
+ expect(result?.node?.tag).toBe('span');
485
+ });
486
+
487
+ test('extractStructureWithoutStyles should handle deeply nested structures', () => {
488
+ const deepNode = createNode({
489
+ tag: 'div',
490
+ style: { base: { color: 'red' } },
491
+ children: [
492
+ createNode({
493
+ tag: 'section',
494
+ style: { base: { padding: '10px' } },
495
+ children: [
496
+ createNode({
497
+ tag: 'p',
498
+ style: { base: { fontSize: '16px' } },
499
+ children: ['text'],
500
+ }),
501
+ ],
502
+ }),
503
+ ],
504
+ });
505
+
506
+ const result = extractStructureWithoutStyles(deepNode) as ComponentNode;
507
+
508
+ // Verify nested structure is preserved
509
+ expect(result.tag).toBe('div');
510
+ const section = result.children?.[0] as ComponentNode;
511
+ expect(section.tag).toBe('section');
512
+ const p = section.children?.[0] as ComponentNode;
513
+ expect(p.tag).toBe('p');
514
+ expect(p.children).toEqual(['text']);
515
+
516
+ // Verify no styles remain
517
+ expect((result as any).style).toBeUndefined();
518
+ expect((section as any).style).toBeUndefined();
519
+ expect((p as any).style).toBeUndefined();
520
+ });
521
+
522
+ test('should handle mixed children (nodes and strings)', () => {
523
+ const pageData = createPageData({
524
+ children: [
525
+ 'text before',
526
+ createNode({ tag: 'span' }),
527
+ 'text after',
528
+ ],
529
+ });
530
+
531
+ // Path to span should work
532
+ const result = getParentAndIndexFromPath([0, 1], pageData);
533
+ expect(result?.node?.tag).toBe('span');
534
+
535
+ // Path to string should return null
536
+ const textResult = getParentAndIndexFromPath([0, 0], pageData);
537
+ expect(textResult).toBeNull();
538
+ });
539
+ });