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.
- package/bin/cli.ts +281 -0
- package/build-static.ts +298 -0
- package/bunfig.toml +39 -0
- package/entries/client-router.tsx +111 -0
- package/entries/server-router.tsx +71 -0
- package/lib/client/ClientInitializer.test.ts +9 -0
- package/lib/client/ClientInitializer.test.ts.skip +92 -0
- package/lib/client/ClientInitializer.ts +60 -0
- package/lib/client/ErrorBoundary.test.tsx +595 -0
- package/lib/client/ErrorBoundary.tsx +230 -0
- package/lib/client/componentRegistry.test.ts +165 -0
- package/lib/client/componentRegistry.ts +18 -0
- package/lib/client/contexts/ThemeContext.tsx +73 -0
- package/lib/client/core/ComponentBuilder.test.ts +677 -0
- package/lib/client/core/ComponentBuilder.ts +660 -0
- package/lib/client/core/ComponentRenderer.test.tsx +176 -0
- package/lib/client/core/ComponentRenderer.tsx +83 -0
- package/lib/client/core/cmsTemplateProcessor.ts +129 -0
- package/lib/client/elementRegistry.ts +81 -0
- package/lib/client/hmr/HMRManager.tsx +179 -0
- package/lib/client/hmr/index.ts +5 -0
- package/lib/client/hmrWebSocket.test.ts +9 -0
- package/lib/client/hmrWebSocket.ts +250 -0
- package/lib/client/hooks/useColorVariables.test.ts +166 -0
- package/lib/client/hooks/useColorVariables.ts +249 -0
- package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
- package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
- package/lib/client/hydration/HydrationUtils.test.ts +154 -0
- package/lib/client/hydration/HydrationUtils.ts +35 -0
- package/lib/client/i18nConfigService.test.ts +74 -0
- package/lib/client/i18nConfigService.ts +78 -0
- package/lib/client/index.ts +56 -0
- package/lib/client/navigation.test.ts +441 -0
- package/lib/client/navigation.ts +23 -0
- package/lib/client/responsiveStyleResolver.test.ts +491 -0
- package/lib/client/responsiveStyleResolver.ts +184 -0
- package/lib/client/routing/RouteLoader.test.ts +635 -0
- package/lib/client/routing/RouteLoader.ts +347 -0
- package/lib/client/routing/Router.tsx +382 -0
- package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
- package/lib/client/scripts/ScriptExecutor.ts +171 -0
- package/lib/client/scripts/formHandler.ts +103 -0
- package/lib/client/styleProcessor.test.ts +126 -0
- package/lib/client/styleProcessor.ts +92 -0
- package/lib/client/styles/StyleInjector.test.ts +354 -0
- package/lib/client/styles/StyleInjector.ts +154 -0
- package/lib/client/templateEngine.test.ts +660 -0
- package/lib/client/templateEngine.ts +667 -0
- package/lib/client/theme.test.ts +173 -0
- package/lib/client/theme.ts +159 -0
- package/lib/client/utils/toast.ts +46 -0
- package/lib/server/createServer.ts +170 -0
- package/lib/server/cssGenerator.test.ts +172 -0
- package/lib/server/cssGenerator.ts +58 -0
- package/lib/server/fileWatcher.ts +134 -0
- package/lib/server/index.ts +55 -0
- package/lib/server/jsonLoader.test.ts +103 -0
- package/lib/server/jsonLoader.ts +350 -0
- package/lib/server/middleware/cors.test.ts +177 -0
- package/lib/server/middleware/cors.ts +69 -0
- package/lib/server/middleware/errorHandler.test.ts +208 -0
- package/lib/server/middleware/errorHandler.ts +63 -0
- package/lib/server/middleware/index.ts +9 -0
- package/lib/server/middleware/logger.test.ts +233 -0
- package/lib/server/middleware/logger.ts +99 -0
- package/lib/server/pageCache.test.ts +167 -0
- package/lib/server/pageCache.ts +97 -0
- package/lib/server/projectContext.ts +51 -0
- package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
- package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
- package/lib/server/providers/fileSystemPageProvider.ts +83 -0
- package/lib/server/routes/api/cms.test.ts +177 -0
- package/lib/server/routes/api/cms.ts +82 -0
- package/lib/server/routes/api/colors.ts +59 -0
- package/lib/server/routes/api/components.ts +70 -0
- package/lib/server/routes/api/config.test.ts +9 -0
- package/lib/server/routes/api/config.ts +28 -0
- package/lib/server/routes/api/core-routes.ts +182 -0
- package/lib/server/routes/api/functions.ts +170 -0
- package/lib/server/routes/api/index.ts +69 -0
- package/lib/server/routes/api/pages.ts +95 -0
- package/lib/server/routes/api/shared.test.ts +81 -0
- package/lib/server/routes/api/shared.ts +31 -0
- package/lib/server/routes/editor.test.ts +9 -0
- package/lib/server/routes/index.ts +104 -0
- package/lib/server/routes/pages.ts +161 -0
- package/lib/server/routes/static.ts +107 -0
- package/lib/server/services/ColorService.ts +193 -0
- package/lib/server/services/cmsService.test.ts +388 -0
- package/lib/server/services/cmsService.ts +296 -0
- package/lib/server/services/componentService.test.ts +276 -0
- package/lib/server/services/componentService.ts +346 -0
- package/lib/server/services/configService.ts +156 -0
- package/lib/server/services/fileWatcherService.ts +67 -0
- package/lib/server/services/index.ts +10 -0
- package/lib/server/services/pageService.test.ts +258 -0
- package/lib/server/services/pageService.ts +240 -0
- package/lib/server/ssrRenderer.test.ts +1005 -0
- package/lib/server/ssrRenderer.ts +878 -0
- package/lib/server/utilityClassGenerator.ts +11 -0
- package/lib/server/utils/index.ts +5 -0
- package/lib/server/utils/jsonLineMapper.test.ts +100 -0
- package/lib/server/utils/jsonLineMapper.ts +166 -0
- package/lib/server/validateStyleCoverage.test.ts +9 -0
- package/lib/server/validateStyleCoverage.ts +167 -0
- package/lib/server/websocketManager.test.ts +9 -0
- package/lib/server/websocketManager.ts +95 -0
- package/lib/shared/attributeNodeUtils.test.ts +152 -0
- package/lib/shared/attributeNodeUtils.ts +50 -0
- package/lib/shared/breakpoints.test.ts +166 -0
- package/lib/shared/breakpoints.ts +65 -0
- package/lib/shared/colorProperties.test.ts +111 -0
- package/lib/shared/colorProperties.ts +40 -0
- package/lib/shared/colorVariableUtils.test.ts +319 -0
- package/lib/shared/colorVariableUtils.ts +97 -0
- package/lib/shared/constants.test.ts +175 -0
- package/lib/shared/constants.ts +116 -0
- package/lib/shared/cssGeneration.ts +481 -0
- package/lib/shared/cssProperties.test.ts +252 -0
- package/lib/shared/cssProperties.ts +338 -0
- package/lib/shared/elementUtils.test.ts +245 -0
- package/lib/shared/elementUtils.ts +90 -0
- package/lib/shared/fontLoader.ts +97 -0
- package/lib/shared/i18n.test.ts +313 -0
- package/lib/shared/i18n.ts +286 -0
- package/lib/shared/index.ts +50 -0
- package/lib/shared/interfaces/contentProvider.test.ts +9 -0
- package/lib/shared/interfaces/contentProvider.ts +121 -0
- package/lib/shared/nodeUtils.test.ts +320 -0
- package/lib/shared/nodeUtils.ts +220 -0
- package/lib/shared/pathArrayUtils.test.ts +315 -0
- package/lib/shared/pathArrayUtils.ts +17 -0
- package/lib/shared/pathUtils.test.ts +260 -0
- package/lib/shared/pathUtils.ts +244 -0
- package/lib/shared/paths/Path.test.ts +74 -0
- package/lib/shared/paths/Path.ts +23 -0
- package/lib/shared/paths/PathConverter.test.ts +232 -0
- package/lib/shared/paths/PathConverter.ts +141 -0
- package/lib/shared/paths/PathUtils.ts +290 -0
- package/lib/shared/paths/PathValidator.test.ts +193 -0
- package/lib/shared/paths/PathValidator.ts +53 -0
- package/lib/shared/paths/index.ts +48 -0
- package/lib/shared/propResolver.test.ts +639 -0
- package/lib/shared/propResolver.ts +124 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
- package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
- package/lib/shared/registry/ClientRegistry.test.ts +26 -0
- package/lib/shared/registry/ClientRegistry.ts +15 -0
- package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
- package/lib/shared/registry/ComponentRegistry.ts +100 -0
- package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
- package/lib/shared/registry/NodeTypeManager.ts +94 -0
- package/lib/shared/registry/RegistryManager.test.ts +58 -0
- package/lib/shared/registry/RegistryManager.ts +60 -0
- package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
- package/lib/shared/registry/SSRRegistry.test.ts +26 -0
- package/lib/shared/registry/SSRRegistry.ts +15 -0
- package/lib/shared/registry/createNodeType.ts +175 -0
- package/lib/shared/registry/defineNodeType.ts +73 -0
- package/lib/shared/registry/fieldPresets.ts +109 -0
- package/lib/shared/registry/index.ts +50 -0
- package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
- package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
- package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
- package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
- package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
- package/lib/shared/registry/nodeTypes/index.ts +75 -0
- package/lib/shared/responsiveScaling.test.ts +268 -0
- package/lib/shared/responsiveScaling.ts +194 -0
- package/lib/shared/responsiveStyleUtils.test.ts +300 -0
- package/lib/shared/responsiveStyleUtils.ts +139 -0
- package/lib/shared/slugTranslator.test.ts +325 -0
- package/lib/shared/slugTranslator.ts +177 -0
- package/lib/shared/styleNodeUtils.test.ts +132 -0
- package/lib/shared/styleNodeUtils.ts +102 -0
- package/lib/shared/styleUtils.test.ts +238 -0
- package/lib/shared/styleUtils.ts +63 -0
- package/lib/shared/themeDefaults.test.ts +113 -0
- package/lib/shared/themeDefaults.ts +103 -0
- package/lib/shared/tree/PathBuilder.ts +383 -0
- package/lib/shared/treePathUtils.test.ts +539 -0
- package/lib/shared/treePathUtils.ts +339 -0
- package/lib/shared/types/api.ts +58 -0
- package/lib/shared/types/cms.ts +95 -0
- package/lib/shared/types/colors.ts +45 -0
- package/lib/shared/types/components.ts +121 -0
- package/lib/shared/types/errors.test.ts +103 -0
- package/lib/shared/types/errors.ts +69 -0
- package/lib/shared/types/index.ts +96 -0
- package/lib/shared/types/nodes.ts +20 -0
- package/lib/shared/types/rendering.ts +61 -0
- package/lib/shared/types/styles.ts +38 -0
- package/lib/shared/types.ts +11 -0
- package/lib/shared/utilityClassConfig.ts +287 -0
- package/lib/shared/utilityClassMapper.test.ts +140 -0
- package/lib/shared/utilityClassMapper.ts +229 -0
- package/lib/shared/utils/fileUtils.test.ts +99 -0
- package/lib/shared/utils/fileUtils.ts +56 -0
- package/lib/shared/utils.test.ts +261 -0
- package/lib/shared/utils.ts +84 -0
- package/lib/shared/validation/index.ts +7 -0
- package/lib/shared/validation/propValidator.test.ts +178 -0
- package/lib/shared/validation/propValidator.ts +238 -0
- package/lib/shared/validation/schemas.test.ts +177 -0
- package/lib/shared/validation/schemas.ts +401 -0
- package/lib/shared/validation/validators.test.ts +109 -0
- package/lib/shared/validation/validators.ts +304 -0
- package/lib/test-utils/dom-setup.ts +55 -0
- package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
- package/lib/test-utils/factories/DomMockFactory.ts +487 -0
- package/lib/test-utils/factories/EventMockFactory.ts +244 -0
- package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
- package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
- package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
- package/lib/test-utils/factories/index.ts +11 -0
- package/lib/test-utils/fixtures.ts +134 -0
- package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
- package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
- package/lib/test-utils/helpers/index.ts +6 -0
- package/lib/test-utils/helpers.test.ts +73 -0
- package/lib/test-utils/helpers.ts +90 -0
- package/lib/test-utils/index.ts +17 -0
- package/lib/test-utils/mockFactories.ts +92 -0
- package/lib/test-utils/mocks.ts +341 -0
- package/package.json +38 -0
- package/templates/index-router.html +34 -0
- package/tsconfig.json +14 -0
- 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
|
+
}
|