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,244 @@
1
+ /**
2
+ * Event Mock Factory
3
+ * Properly typed mock factories for Keyboard, Mouse, and Custom events
4
+ */
5
+
6
+ import { mock } from 'bun:test';
7
+
8
+ // ========== Types ==========
9
+
10
+ export interface MockKeyboardEventOptions {
11
+ key: string;
12
+ code?: string;
13
+ ctrlKey?: boolean;
14
+ metaKey?: boolean;
15
+ shiftKey?: boolean;
16
+ altKey?: boolean;
17
+ repeat?: boolean;
18
+ bubbles?: boolean;
19
+ cancelable?: boolean;
20
+ }
21
+
22
+ export interface MockMouseEventOptions {
23
+ button?: number;
24
+ buttons?: number;
25
+ clientX?: number;
26
+ clientY?: number;
27
+ screenX?: number;
28
+ screenY?: number;
29
+ ctrlKey?: boolean;
30
+ metaKey?: boolean;
31
+ shiftKey?: boolean;
32
+ altKey?: boolean;
33
+ target?: EventTarget | null;
34
+ bubbles?: boolean;
35
+ cancelable?: boolean;
36
+ }
37
+
38
+ export interface TypedMockKeyboardEvent extends KeyboardEvent {
39
+ preventDefault: ReturnType<typeof mock>;
40
+ stopPropagation: ReturnType<typeof mock>;
41
+ stopImmediatePropagation: ReturnType<typeof mock>;
42
+ }
43
+
44
+ export interface TypedMockMouseEvent extends MouseEvent {
45
+ preventDefault: ReturnType<typeof mock>;
46
+ stopPropagation: ReturnType<typeof mock>;
47
+ stopImmediatePropagation: ReturnType<typeof mock>;
48
+ }
49
+
50
+ // ========== Keyboard Events ==========
51
+
52
+ export function createTypedKeyboardEvent(
53
+ type: 'keydown' | 'keyup' | 'keypress',
54
+ options: MockKeyboardEventOptions
55
+ ): TypedMockKeyboardEvent {
56
+ const event = new KeyboardEvent(type, {
57
+ key: options.key,
58
+ code: options.code ?? options.key,
59
+ ctrlKey: options.ctrlKey ?? false,
60
+ metaKey: options.metaKey ?? false,
61
+ shiftKey: options.shiftKey ?? false,
62
+ altKey: options.altKey ?? false,
63
+ repeat: options.repeat ?? false,
64
+ bubbles: options.bubbles ?? true,
65
+ cancelable: options.cancelable ?? true,
66
+ });
67
+
68
+ // Create mock functions for event methods
69
+ const preventDefaultMock = mock(() => {});
70
+ const stopPropagationMock = mock(() => {});
71
+ const stopImmediatePropagationMock = mock(() => {});
72
+
73
+ // Override methods with mocks
74
+ Object.defineProperties(event, {
75
+ preventDefault: { value: preventDefaultMock, writable: true },
76
+ stopPropagation: { value: stopPropagationMock, writable: true },
77
+ stopImmediatePropagation: { value: stopImmediatePropagationMock, writable: true },
78
+ });
79
+
80
+ return event as TypedMockKeyboardEvent;
81
+ }
82
+
83
+ /**
84
+ * Convenience function for creating common keyboard shortcuts
85
+ */
86
+ export const KeyboardShortcuts = {
87
+ copy: (meta = true) =>
88
+ createTypedKeyboardEvent('keydown', {
89
+ key: 'c',
90
+ metaKey: meta,
91
+ ctrlKey: !meta,
92
+ }),
93
+ paste: (meta = true) =>
94
+ createTypedKeyboardEvent('keydown', {
95
+ key: 'v',
96
+ metaKey: meta,
97
+ ctrlKey: !meta,
98
+ }),
99
+ cut: (meta = true) =>
100
+ createTypedKeyboardEvent('keydown', {
101
+ key: 'x',
102
+ metaKey: meta,
103
+ ctrlKey: !meta,
104
+ }),
105
+ undo: (meta = true) =>
106
+ createTypedKeyboardEvent('keydown', {
107
+ key: 'z',
108
+ metaKey: meta,
109
+ ctrlKey: !meta,
110
+ }),
111
+ redo: (meta = true) =>
112
+ createTypedKeyboardEvent('keydown', {
113
+ key: 'z',
114
+ metaKey: meta,
115
+ ctrlKey: !meta,
116
+ shiftKey: true,
117
+ }),
118
+ delete: () => createTypedKeyboardEvent('keydown', { key: 'Backspace' }),
119
+ enter: () => createTypedKeyboardEvent('keydown', { key: 'Enter' }),
120
+ escape: () => createTypedKeyboardEvent('keydown', { key: 'Escape' }),
121
+ tab: (shift = false) =>
122
+ createTypedKeyboardEvent('keydown', { key: 'Tab', shiftKey: shift }),
123
+ arrowUp: () => createTypedKeyboardEvent('keydown', { key: 'ArrowUp' }),
124
+ arrowDown: () => createTypedKeyboardEvent('keydown', { key: 'ArrowDown' }),
125
+ arrowLeft: () => createTypedKeyboardEvent('keydown', { key: 'ArrowLeft' }),
126
+ arrowRight: () => createTypedKeyboardEvent('keydown', { key: 'ArrowRight' }),
127
+ };
128
+
129
+ // ========== Mouse Events ==========
130
+
131
+ export function createTypedMouseEvent(
132
+ type:
133
+ | 'click'
134
+ | 'dblclick'
135
+ | 'mousedown'
136
+ | 'mouseup'
137
+ | 'mousemove'
138
+ | 'mouseenter'
139
+ | 'mouseleave'
140
+ | 'mouseover'
141
+ | 'mouseout'
142
+ | 'contextmenu',
143
+ options: MockMouseEventOptions = {}
144
+ ): TypedMockMouseEvent {
145
+ const event = new MouseEvent(type, {
146
+ button: options.button ?? 0,
147
+ buttons: options.buttons ?? 0,
148
+ clientX: options.clientX ?? 0,
149
+ clientY: options.clientY ?? 0,
150
+ screenX: options.screenX ?? options.clientX ?? 0,
151
+ screenY: options.screenY ?? options.clientY ?? 0,
152
+ ctrlKey: options.ctrlKey ?? false,
153
+ metaKey: options.metaKey ?? false,
154
+ shiftKey: options.shiftKey ?? false,
155
+ altKey: options.altKey ?? false,
156
+ bubbles: options.bubbles ?? true,
157
+ cancelable: options.cancelable ?? true,
158
+ });
159
+
160
+ const preventDefaultMock = mock(() => {});
161
+ const stopPropagationMock = mock(() => {});
162
+ const stopImmediatePropagationMock = mock(() => {});
163
+
164
+ Object.defineProperties(event, {
165
+ preventDefault: { value: preventDefaultMock, writable: true },
166
+ stopPropagation: { value: stopPropagationMock, writable: true },
167
+ stopImmediatePropagation: { value: stopImmediatePropagationMock, writable: true },
168
+ target: { value: options.target ?? null, writable: true },
169
+ });
170
+
171
+ return event as TypedMockMouseEvent;
172
+ }
173
+
174
+ // ========== Custom Events ==========
175
+
176
+ export function createTypedCustomEvent<T = unknown>(
177
+ type: string,
178
+ detail?: T,
179
+ options: { bubbles?: boolean; cancelable?: boolean } = {}
180
+ ): CustomEvent<T> {
181
+ return new CustomEvent(type, {
182
+ detail,
183
+ bubbles: options.bubbles ?? true,
184
+ cancelable: options.cancelable ?? true,
185
+ });
186
+ }
187
+
188
+ // ========== PopState Event ==========
189
+
190
+ export function createTypedPopStateEvent(state?: unknown): PopStateEvent {
191
+ return new PopStateEvent('popstate', { state });
192
+ }
193
+
194
+ // ========== Message Event ==========
195
+
196
+ export interface MockMessageEventOptions<T = unknown> {
197
+ data: T;
198
+ origin?: string;
199
+ source?: MessageEventSource | null;
200
+ }
201
+
202
+ export function createTypedMessageEvent<T = unknown>(
203
+ options: MockMessageEventOptions<T>
204
+ ): MessageEvent<T> {
205
+ return new MessageEvent('message', {
206
+ data: options.data,
207
+ origin: options.origin ?? 'http://localhost',
208
+ source: options.source ?? null,
209
+ });
210
+ }
211
+
212
+ // ========== Focus Events ==========
213
+
214
+ export function createTypedFocusEvent(
215
+ type: 'focus' | 'blur' | 'focusin' | 'focusout',
216
+ options: { relatedTarget?: EventTarget | null } = {}
217
+ ): FocusEvent {
218
+ return new FocusEvent(type, {
219
+ bubbles: type === 'focusin' || type === 'focusout',
220
+ cancelable: false,
221
+ relatedTarget: options.relatedTarget ?? null,
222
+ });
223
+ }
224
+
225
+ // ========== Input Events ==========
226
+
227
+ export interface MockInputEventOptions {
228
+ data?: string | null;
229
+ inputType?: string;
230
+ isComposing?: boolean;
231
+ }
232
+
233
+ export function createTypedInputEvent(
234
+ type: 'input' | 'beforeinput',
235
+ options: MockInputEventOptions = {}
236
+ ): InputEvent {
237
+ return new InputEvent(type, {
238
+ data: options.data ?? null,
239
+ inputType: options.inputType ?? 'insertText',
240
+ isComposing: options.isComposing ?? false,
241
+ bubbles: true,
242
+ cancelable: type === 'beforeinput',
243
+ });
244
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Fetch Mock Factory
3
+ * Properly typed mock factory for fetch with URL pattern matching
4
+ */
5
+
6
+ import { mock } from 'bun:test';
7
+
8
+ // ========== Types ==========
9
+
10
+ export interface MockFetchResponse {
11
+ status?: number;
12
+ statusText?: string;
13
+ ok?: boolean;
14
+ headers?: Record<string, string>;
15
+ json?: () => Promise<unknown>;
16
+ text?: () => Promise<string>;
17
+ blob?: () => Promise<Blob>;
18
+ }
19
+
20
+ export interface FetchMockConfig {
21
+ /** URL pattern to response mapping */
22
+ responses?: Map<string | RegExp, MockFetchResponse | (() => MockFetchResponse)>;
23
+ /** Default response for unmatched URLs */
24
+ defaultResponse?: MockFetchResponse;
25
+ /** Whether to throw on unmatched URLs */
26
+ throwOnUnmatched?: boolean;
27
+ /** Delay in ms before resolving */
28
+ delay?: number;
29
+ }
30
+
31
+ export interface TypedFetchMock {
32
+ (input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
33
+ /** Recorded calls for assertions */
34
+ _calls: Array<{ url: string; options?: RequestInit }>;
35
+ /** Reset call history */
36
+ _reset: () => void;
37
+ /** Add a response for a URL pattern */
38
+ _addResponse: (
39
+ pattern: string | RegExp,
40
+ response: MockFetchResponse | (() => MockFetchResponse)
41
+ ) => void;
42
+ }
43
+
44
+ // ========== Response Factory ==========
45
+
46
+ function createMockResponse(config: MockFetchResponse): Response {
47
+ const status = config.status ?? 200;
48
+ const ok = config.ok ?? (status >= 200 && status < 300);
49
+
50
+ return {
51
+ status,
52
+ statusText: config.statusText ?? (ok ? 'OK' : 'Error'),
53
+ ok,
54
+ headers: new Headers(config.headers),
55
+ json: config.json ?? (() => Promise.resolve({})),
56
+ text: config.text ?? (() => Promise.resolve('')),
57
+ blob: config.blob ?? (() => Promise.resolve(new Blob())),
58
+ clone: function () {
59
+ return createMockResponse(config);
60
+ },
61
+ arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)),
62
+ formData: () => Promise.resolve(new FormData()),
63
+ body: null,
64
+ bodyUsed: false,
65
+ redirected: false,
66
+ type: 'basic',
67
+ url: '',
68
+ } as Response;
69
+ }
70
+
71
+ // ========== Main Factory ==========
72
+
73
+ export function createTypedFetchMock(config: FetchMockConfig = {}): TypedFetchMock {
74
+ const calls: Array<{ url: string; options?: RequestInit }> = [];
75
+ const responses = new Map(config.responses || []);
76
+
77
+ const mockFn = mock(
78
+ async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
79
+ const url = typeof input === 'string' ? input : input.toString();
80
+ calls.push({ url, options: init });
81
+
82
+ if (config.delay) {
83
+ await new Promise((resolve) => setTimeout(resolve, config.delay));
84
+ }
85
+
86
+ // Find matching response
87
+ let responseConfig:
88
+ | MockFetchResponse
89
+ | (() => MockFetchResponse)
90
+ | undefined;
91
+
92
+ for (const [pattern, response] of responses) {
93
+ if (typeof pattern === 'string' && url === pattern) {
94
+ responseConfig = response;
95
+ break;
96
+ }
97
+ if (typeof pattern === 'string' && url.includes(pattern)) {
98
+ responseConfig = response;
99
+ break;
100
+ }
101
+ if (pattern instanceof RegExp && pattern.test(url)) {
102
+ responseConfig = response;
103
+ break;
104
+ }
105
+ }
106
+
107
+ if (!responseConfig && config.defaultResponse) {
108
+ responseConfig = config.defaultResponse;
109
+ }
110
+
111
+ if (!responseConfig) {
112
+ if (config.throwOnUnmatched) {
113
+ throw new Error(`Unmatched fetch request: ${url}`);
114
+ }
115
+ return createMockResponse({ status: 404, ok: false });
116
+ }
117
+
118
+ const resolvedConfig =
119
+ typeof responseConfig === 'function'
120
+ ? responseConfig()
121
+ : responseConfig;
122
+
123
+ return createMockResponse(resolvedConfig);
124
+ }
125
+ ) as TypedFetchMock;
126
+
127
+ mockFn._calls = calls;
128
+ mockFn._reset = () => {
129
+ calls.length = 0;
130
+ };
131
+ mockFn._addResponse = (pattern, response) => {
132
+ responses.set(pattern, response);
133
+ };
134
+
135
+ return mockFn;
136
+ }
137
+
138
+ // ========== Convenience Factories ==========
139
+
140
+ /**
141
+ * Create a fetch mock that returns JSON data
142
+ */
143
+ export function createJsonFetchMock<T>(
144
+ responseData: T,
145
+ options: Partial<MockFetchResponse> = {}
146
+ ): TypedFetchMock {
147
+ return createTypedFetchMock({
148
+ defaultResponse: {
149
+ status: 200,
150
+ ok: true,
151
+ json: () => Promise.resolve(responseData),
152
+ text: () => Promise.resolve(JSON.stringify(responseData)),
153
+ ...options,
154
+ },
155
+ });
156
+ }
157
+
158
+ /**
159
+ * Create a fetch mock that returns an error
160
+ */
161
+ export function createErrorFetchMock(
162
+ error: Error | string = 'Network error'
163
+ ): TypedFetchMock {
164
+ const mockFn = mock(() =>
165
+ Promise.reject(typeof error === 'string' ? new Error(error) : error)
166
+ ) as TypedFetchMock;
167
+
168
+ mockFn._calls = [];
169
+ mockFn._reset = () => {};
170
+ mockFn._addResponse = () => {};
171
+
172
+ return mockFn;
173
+ }
174
+
175
+ /**
176
+ * Create a fetch mock that returns different responses based on URL
177
+ */
178
+ export function createRoutedFetchMock(
179
+ routes: Record<string, MockFetchResponse | (() => MockFetchResponse)>
180
+ ): TypedFetchMock {
181
+ const responses = new Map<string, MockFetchResponse | (() => MockFetchResponse)>();
182
+ for (const [url, response] of Object.entries(routes)) {
183
+ responses.set(url, response);
184
+ }
185
+ return createTypedFetchMock({ responses });
186
+ }
187
+
188
+ // ========== Global Installation ==========
189
+
190
+ export interface FetchMockInstallation {
191
+ fetch: TypedFetchMock;
192
+ cleanup: () => void;
193
+ }
194
+
195
+ /**
196
+ * Install mock fetch globally and return cleanup function
197
+ */
198
+ export function installMockFetch(config: FetchMockConfig = {}): FetchMockInstallation {
199
+ const originalFetch = globalThis.fetch;
200
+ const mockFetch = createTypedFetchMock(config);
201
+
202
+ globalThis.fetch = mockFetch as unknown as typeof fetch;
203
+
204
+ return {
205
+ fetch: mockFetch,
206
+ cleanup: () => {
207
+ globalThis.fetch = originalFetch;
208
+ },
209
+ };
210
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Server Mock Factory
3
+ * Properly typed mock factories for server-side services
4
+ */
5
+
6
+ import { mock } from 'bun:test';
7
+ import type { PageProvider, ComponentProvider } from '../../shared/interfaces/contentProvider';
8
+ import type { ComponentServiceFs, ComponentLoader } from '../../server/services/componentService';
9
+ import type { ComponentDefinition } from '../../shared/types';
10
+
11
+ // ========== PageProvider Mock ==========
12
+
13
+ export interface TypedMockPageProvider extends PageProvider {
14
+ loadAll: ReturnType<typeof mock>;
15
+ get: ReturnType<typeof mock>;
16
+ save: ReturnType<typeof mock>;
17
+ delete: ReturnType<typeof mock>;
18
+ exists: ReturnType<typeof mock>;
19
+ // Internal state for assertions
20
+ _pages: Record<string, string>;
21
+ }
22
+
23
+ /**
24
+ * Create a typed mock PageProvider
25
+ * @param initialPages - Initial page content map (path -> content)
26
+ */
27
+ export function createTypedMockPageProvider(
28
+ initialPages: Record<string, string> = {}
29
+ ): TypedMockPageProvider {
30
+ const pages = { ...initialPages };
31
+
32
+ const provider: TypedMockPageProvider = {
33
+ loadAll: mock(async () => new Map(Object.entries(pages))),
34
+ get: mock(async (path: string) => pages[path] ?? null),
35
+ save: mock(async (path: string, content: string) => {
36
+ pages[path] = content;
37
+ }),
38
+ delete: mock(async (path: string) => {
39
+ delete pages[path];
40
+ }),
41
+ exists: mock(async (path: string) => path in pages),
42
+ _pages: pages,
43
+ };
44
+
45
+ return provider;
46
+ }
47
+
48
+ // ========== ComponentProvider Mock ==========
49
+
50
+ export interface TypedMockComponentProvider extends ComponentProvider {
51
+ loadAll: ReturnType<typeof mock>;
52
+ get: ReturnType<typeof mock>;
53
+ getJS: ReturnType<typeof mock>;
54
+ getCSS: ReturnType<typeof mock>;
55
+ save: ReturnType<typeof mock>;
56
+ // Internal state for assertions
57
+ _components: Map<string, ComponentDefinition>;
58
+ _js: Map<string, string>;
59
+ _css: Map<string, string>;
60
+ }
61
+
62
+ /**
63
+ * Create a typed mock ComponentProvider
64
+ */
65
+ export function createTypedMockComponentProvider(
66
+ initialComponents: Map<string, ComponentDefinition> = new Map()
67
+ ): TypedMockComponentProvider {
68
+ const components = new Map(initialComponents);
69
+ const js = new Map<string, string>();
70
+ const css = new Map<string, string>();
71
+
72
+ const provider: TypedMockComponentProvider = {
73
+ loadAll: mock(async () => new Map(components)),
74
+ get: mock(async (name: string) => components.get(name) ?? null),
75
+ getJS: mock(async (name: string) => js.get(name) ?? null),
76
+ getCSS: mock(async (name: string) => css.get(name) ?? null),
77
+ save: mock(async (name: string, definition: ComponentDefinition) => {
78
+ components.set(name, definition);
79
+ }),
80
+ _components: components,
81
+ _js: js,
82
+ _css: css,
83
+ };
84
+
85
+ return provider;
86
+ }
87
+
88
+ // ========== ComponentServiceFs Mock ==========
89
+
90
+ export interface TypedMockComponentServiceFs extends ComponentServiceFs {
91
+ writeFile: ReturnType<typeof mock>;
92
+ readFile: ReturnType<typeof mock>;
93
+ // Internal state for assertions
94
+ _files: Record<string, string>;
95
+ }
96
+
97
+ /**
98
+ * Create a typed mock file system for ComponentService
99
+ */
100
+ export function createTypedMockComponentServiceFs(
101
+ initialFiles: Record<string, string> = {}
102
+ ): TypedMockComponentServiceFs {
103
+ const files = { ...initialFiles };
104
+
105
+ const fs: TypedMockComponentServiceFs = {
106
+ writeFile: mock(async (path: string, content: string) => {
107
+ files[path] = content;
108
+ }),
109
+ readFile: mock(async (path: string) => files[path] ?? null),
110
+ _files: files,
111
+ };
112
+
113
+ return fs;
114
+ }
115
+
116
+ // ========== ComponentLoader Mock ==========
117
+
118
+ export interface TypedMockComponentLoader extends ComponentLoader {
119
+ loadDirectory: ReturnType<typeof mock>;
120
+ loadFile: ReturnType<typeof mock>;
121
+ // Internal state for assertions
122
+ _components: Map<string, ComponentDefinition>;
123
+ }
124
+
125
+ /**
126
+ * Create a typed mock component loader
127
+ */
128
+ export function createTypedMockComponentLoader(
129
+ components: Map<string, ComponentDefinition> = new Map()
130
+ ): TypedMockComponentLoader {
131
+ const storedComponents = new Map(components);
132
+
133
+ const loader: TypedMockComponentLoader = {
134
+ loadDirectory: mock(async () => new Map(storedComponents)),
135
+ loadFile: mock(async () => null),
136
+ _components: storedComponents,
137
+ };
138
+
139
+ return loader;
140
+ }
141
+
142
+ // ========== Full-stack file system mock ==========
143
+
144
+ export interface TypedMockFs {
145
+ readFile: ReturnType<typeof mock>;
146
+ writeFile: ReturnType<typeof mock>;
147
+ unlink: ReturnType<typeof mock>;
148
+ readdir: ReturnType<typeof mock>;
149
+ stat: ReturnType<typeof mock>;
150
+ access: ReturnType<typeof mock>;
151
+ mkdir: ReturnType<typeof mock>;
152
+ rm: ReturnType<typeof mock>;
153
+ // Internal state for assertions
154
+ _files: Record<string, string>;
155
+ }
156
+
157
+ /**
158
+ * Create a comprehensive mock file system (similar to fs/promises)
159
+ * @param initialFiles - Initial file content map (path -> content)
160
+ */
161
+ export function createTypedMockFs(
162
+ initialFiles: Record<string, string> = {}
163
+ ): TypedMockFs {
164
+ const files = { ...initialFiles };
165
+
166
+ const makeError = (code: string, message: string) => {
167
+ const error = new Error(message);
168
+ (error as NodeJS.ErrnoException).code = code;
169
+ return error;
170
+ };
171
+
172
+ const fs: TypedMockFs = {
173
+ readFile: mock(async (path: string) => {
174
+ if (path in files) {
175
+ return files[path];
176
+ }
177
+ throw makeError('ENOENT', `ENOENT: no such file or directory, open '${path}'`);
178
+ }),
179
+ writeFile: mock(async (path: string, content: string) => {
180
+ files[path] = content;
181
+ }),
182
+ unlink: mock(async (path: string) => {
183
+ if (!(path in files)) {
184
+ throw makeError('ENOENT', `ENOENT: no such file or directory, unlink '${path}'`);
185
+ }
186
+ delete files[path];
187
+ }),
188
+ readdir: mock(async (dir: string) => {
189
+ const normalizedDir = dir.endsWith('/') ? dir : dir + '/';
190
+ const entries = new Set<string>();
191
+ for (const path of Object.keys(files)) {
192
+ if (path.startsWith(normalizedDir)) {
193
+ const relativePath = path.slice(normalizedDir.length);
194
+ const firstSegment = relativePath.split('/')[0];
195
+ if (firstSegment) {
196
+ entries.add(firstSegment);
197
+ }
198
+ }
199
+ }
200
+ return Array.from(entries);
201
+ }),
202
+ stat: mock(async (path: string) => ({
203
+ isDirectory: () => !path.includes('.') || path.endsWith('/'),
204
+ isFile: () => path.includes('.') && !path.endsWith('/'),
205
+ })),
206
+ access: mock(async (path: string) => {
207
+ if (!(path in files)) {
208
+ throw makeError('ENOENT', `ENOENT: no such file or directory, access '${path}'`);
209
+ }
210
+ }),
211
+ mkdir: mock(async () => {}),
212
+ rm: mock(async (path: string) => {
213
+ for (const filePath of Object.keys(files)) {
214
+ if (filePath.startsWith(path)) {
215
+ delete files[filePath];
216
+ }
217
+ }
218
+ }),
219
+ _files: files,
220
+ };
221
+
222
+ return fs;
223
+ }