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,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store Mock Factory
|
|
3
|
+
* Properly typed mock factories for Zustand stores
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { mock } from 'bun:test';
|
|
7
|
+
import type { ComponentNode, JSONPage, PageData } from '../../shared/types';
|
|
8
|
+
import type { Path } from '../../shared/pathArrayUtils';
|
|
9
|
+
|
|
10
|
+
// ========== FileStore Types ==========
|
|
11
|
+
|
|
12
|
+
export interface FileItem {
|
|
13
|
+
name: string;
|
|
14
|
+
path: string;
|
|
15
|
+
type: 'page' | 'component';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface NavigationContext {
|
|
19
|
+
path: string;
|
|
20
|
+
selectedTreeItem: Path | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ApiError {
|
|
24
|
+
message: string;
|
|
25
|
+
code?: string;
|
|
26
|
+
status?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface MockFileStoreState {
|
|
30
|
+
files: FileItem[];
|
|
31
|
+
selectedFile: FileItem | null;
|
|
32
|
+
navigationHistory: NavigationContext[];
|
|
33
|
+
iframeUrl: string;
|
|
34
|
+
lastError: ApiError | null;
|
|
35
|
+
isRecoverable: boolean;
|
|
36
|
+
pendingOperation: 'loading' | 'saving' | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface MockFileStoreActions {
|
|
40
|
+
setFiles: ReturnType<typeof mock>;
|
|
41
|
+
setSelectedFile: ReturnType<typeof mock>;
|
|
42
|
+
setIframeUrl: ReturnType<typeof mock>;
|
|
43
|
+
pushNavigationHistory: ReturnType<typeof mock>;
|
|
44
|
+
popNavigationHistory: ReturnType<typeof mock>;
|
|
45
|
+
clearNavigationHistory: ReturnType<typeof mock>;
|
|
46
|
+
setLastError: ReturnType<typeof mock>;
|
|
47
|
+
setPendingOperation: ReturnType<typeof mock>;
|
|
48
|
+
clearError: ReturnType<typeof mock>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type TypedMockFileStore = MockFileStoreState & MockFileStoreActions & {
|
|
52
|
+
/** Get current state (for assertions) */
|
|
53
|
+
getState: () => MockFileStoreState;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export function createTypedMockFileStore(
|
|
57
|
+
initialState: Partial<MockFileStoreState> = {}
|
|
58
|
+
): TypedMockFileStore {
|
|
59
|
+
const state: MockFileStoreState = {
|
|
60
|
+
files: [],
|
|
61
|
+
selectedFile: null,
|
|
62
|
+
navigationHistory: [],
|
|
63
|
+
iframeUrl: '/',
|
|
64
|
+
lastError: null,
|
|
65
|
+
isRecoverable: false,
|
|
66
|
+
pendingOperation: null,
|
|
67
|
+
...initialState,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const store: TypedMockFileStore = {
|
|
71
|
+
...state,
|
|
72
|
+
setFiles: mock((files: FileItem[]) => {
|
|
73
|
+
state.files = files;
|
|
74
|
+
store.files = files;
|
|
75
|
+
}),
|
|
76
|
+
setSelectedFile: mock((file: FileItem | null) => {
|
|
77
|
+
state.selectedFile = file;
|
|
78
|
+
store.selectedFile = file;
|
|
79
|
+
}),
|
|
80
|
+
setIframeUrl: mock((url: string) => {
|
|
81
|
+
state.iframeUrl = url;
|
|
82
|
+
store.iframeUrl = url;
|
|
83
|
+
}),
|
|
84
|
+
pushNavigationHistory: mock((context: NavigationContext) => {
|
|
85
|
+
state.navigationHistory.push(context);
|
|
86
|
+
}),
|
|
87
|
+
popNavigationHistory: mock(() => {
|
|
88
|
+
return state.navigationHistory.pop() ?? null;
|
|
89
|
+
}),
|
|
90
|
+
clearNavigationHistory: mock(() => {
|
|
91
|
+
state.navigationHistory = [];
|
|
92
|
+
}),
|
|
93
|
+
setLastError: mock((error: ApiError | null, isRecoverable = false) => {
|
|
94
|
+
state.lastError = error;
|
|
95
|
+
state.isRecoverable = isRecoverable;
|
|
96
|
+
store.lastError = error;
|
|
97
|
+
store.isRecoverable = isRecoverable;
|
|
98
|
+
}),
|
|
99
|
+
setPendingOperation: mock((op: 'loading' | 'saving' | null) => {
|
|
100
|
+
state.pendingOperation = op;
|
|
101
|
+
store.pendingOperation = op;
|
|
102
|
+
}),
|
|
103
|
+
clearError: mock(() => {
|
|
104
|
+
state.lastError = null;
|
|
105
|
+
state.isRecoverable = false;
|
|
106
|
+
store.lastError = null;
|
|
107
|
+
store.isRecoverable = false;
|
|
108
|
+
}),
|
|
109
|
+
getState: () => ({ ...state }),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return store;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ========== SelectionStore Types ==========
|
|
116
|
+
|
|
117
|
+
export interface MockSelectionStoreState {
|
|
118
|
+
selectedTreeItem: Path | null;
|
|
119
|
+
selectedItemData: ComponentNode | null;
|
|
120
|
+
clipboard: ComponentNode | null;
|
|
121
|
+
hoveredPath: Path | null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface MockSelectionStoreActions {
|
|
125
|
+
setSelectedTreeItem: ReturnType<typeof mock>;
|
|
126
|
+
setSelectedItemData: ReturnType<typeof mock>;
|
|
127
|
+
setClipboard: ReturnType<typeof mock>;
|
|
128
|
+
setHoveredPath: ReturnType<typeof mock>;
|
|
129
|
+
clearSelection: ReturnType<typeof mock>;
|
|
130
|
+
copyNode: ReturnType<typeof mock>;
|
|
131
|
+
pasteNode: ReturnType<typeof mock>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export type TypedMockSelectionStore = MockSelectionStoreState &
|
|
135
|
+
MockSelectionStoreActions & {
|
|
136
|
+
getState: () => MockSelectionStoreState;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export function createTypedMockSelectionStore(
|
|
140
|
+
initialState: Partial<MockSelectionStoreState> = {}
|
|
141
|
+
): TypedMockSelectionStore {
|
|
142
|
+
const state: MockSelectionStoreState = {
|
|
143
|
+
selectedTreeItem: null,
|
|
144
|
+
selectedItemData: null,
|
|
145
|
+
clipboard: null,
|
|
146
|
+
hoveredPath: null,
|
|
147
|
+
...initialState,
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const store: TypedMockSelectionStore = {
|
|
151
|
+
...state,
|
|
152
|
+
setSelectedTreeItem: mock((path: Path | null) => {
|
|
153
|
+
state.selectedTreeItem = path;
|
|
154
|
+
store.selectedTreeItem = path;
|
|
155
|
+
}),
|
|
156
|
+
setSelectedItemData: mock((data: ComponentNode | null) => {
|
|
157
|
+
state.selectedItemData = data;
|
|
158
|
+
store.selectedItemData = data;
|
|
159
|
+
}),
|
|
160
|
+
setClipboard: mock((node: ComponentNode | null) => {
|
|
161
|
+
state.clipboard = node;
|
|
162
|
+
store.clipboard = node;
|
|
163
|
+
}),
|
|
164
|
+
setHoveredPath: mock((path: Path | null) => {
|
|
165
|
+
state.hoveredPath = path;
|
|
166
|
+
store.hoveredPath = path;
|
|
167
|
+
}),
|
|
168
|
+
clearSelection: mock(() => {
|
|
169
|
+
state.selectedTreeItem = null;
|
|
170
|
+
state.selectedItemData = null;
|
|
171
|
+
store.selectedTreeItem = null;
|
|
172
|
+
store.selectedItemData = null;
|
|
173
|
+
}),
|
|
174
|
+
copyNode: mock(() => {
|
|
175
|
+
if (state.selectedItemData) {
|
|
176
|
+
state.clipboard = structuredClone(state.selectedItemData);
|
|
177
|
+
store.clipboard = state.clipboard;
|
|
178
|
+
}
|
|
179
|
+
}),
|
|
180
|
+
pasteNode: mock(() => {
|
|
181
|
+
return state.clipboard ? structuredClone(state.clipboard) : null;
|
|
182
|
+
}),
|
|
183
|
+
getState: () => ({ ...state }),
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return store;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ========== NodeStore Types ==========
|
|
190
|
+
|
|
191
|
+
export interface MockNodeStoreState {
|
|
192
|
+
pageData: PageData | null;
|
|
193
|
+
isDirty: boolean;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface MockNodeStoreActions {
|
|
197
|
+
setPageData: ReturnType<typeof mock>;
|
|
198
|
+
updateNode: ReturnType<typeof mock>;
|
|
199
|
+
deleteNode: ReturnType<typeof mock>;
|
|
200
|
+
addNode: ReturnType<typeof mock>;
|
|
201
|
+
moveNode: ReturnType<typeof mock>;
|
|
202
|
+
markDirty: ReturnType<typeof mock>;
|
|
203
|
+
markClean: ReturnType<typeof mock>;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export type TypedMockNodeStore = MockNodeStoreState &
|
|
207
|
+
MockNodeStoreActions & {
|
|
208
|
+
getState: () => MockNodeStoreState;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export function createTypedMockNodeStore(
|
|
212
|
+
initialState: Partial<MockNodeStoreState> = {}
|
|
213
|
+
): TypedMockNodeStore {
|
|
214
|
+
const state: MockNodeStoreState = {
|
|
215
|
+
pageData: null,
|
|
216
|
+
isDirty: false,
|
|
217
|
+
...initialState,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const store: TypedMockNodeStore = {
|
|
221
|
+
...state,
|
|
222
|
+
setPageData: mock((data: PageData | null) => {
|
|
223
|
+
state.pageData = data;
|
|
224
|
+
store.pageData = data;
|
|
225
|
+
state.isDirty = false;
|
|
226
|
+
store.isDirty = false;
|
|
227
|
+
}),
|
|
228
|
+
updateNode: mock((path: Path, updates: Partial<ComponentNode>) => {
|
|
229
|
+
state.isDirty = true;
|
|
230
|
+
store.isDirty = true;
|
|
231
|
+
}),
|
|
232
|
+
deleteNode: mock((path: Path) => {
|
|
233
|
+
state.isDirty = true;
|
|
234
|
+
store.isDirty = true;
|
|
235
|
+
}),
|
|
236
|
+
addNode: mock((parentPath: Path, node: ComponentNode, index?: number) => {
|
|
237
|
+
state.isDirty = true;
|
|
238
|
+
store.isDirty = true;
|
|
239
|
+
}),
|
|
240
|
+
moveNode: mock((fromPath: Path, toPath: Path) => {
|
|
241
|
+
state.isDirty = true;
|
|
242
|
+
store.isDirty = true;
|
|
243
|
+
}),
|
|
244
|
+
markDirty: mock(() => {
|
|
245
|
+
state.isDirty = true;
|
|
246
|
+
store.isDirty = true;
|
|
247
|
+
}),
|
|
248
|
+
markClean: mock(() => {
|
|
249
|
+
state.isDirty = false;
|
|
250
|
+
store.isDirty = false;
|
|
251
|
+
}),
|
|
252
|
+
getState: () => ({ ...state }),
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
return store;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ========== ComponentStore Types ==========
|
|
259
|
+
|
|
260
|
+
export interface MockComponentStoreState {
|
|
261
|
+
components: Map<string, unknown>;
|
|
262
|
+
loading: boolean;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export interface MockComponentStoreActions {
|
|
266
|
+
setComponents: ReturnType<typeof mock>;
|
|
267
|
+
getComponent: ReturnType<typeof mock>;
|
|
268
|
+
hasComponent: ReturnType<typeof mock>;
|
|
269
|
+
setLoading: ReturnType<typeof mock>;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export type TypedMockComponentStore = MockComponentStoreState &
|
|
273
|
+
MockComponentStoreActions & {
|
|
274
|
+
getState: () => MockComponentStoreState;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
export function createTypedMockComponentStore(
|
|
278
|
+
initialState: Partial<MockComponentStoreState> = {}
|
|
279
|
+
): TypedMockComponentStore {
|
|
280
|
+
const state: MockComponentStoreState = {
|
|
281
|
+
components: new Map(),
|
|
282
|
+
loading: false,
|
|
283
|
+
...initialState,
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const store: TypedMockComponentStore = {
|
|
287
|
+
...state,
|
|
288
|
+
setComponents: mock((components: Map<string, unknown>) => {
|
|
289
|
+
state.components = components;
|
|
290
|
+
store.components = components;
|
|
291
|
+
}),
|
|
292
|
+
getComponent: mock((name: string) => {
|
|
293
|
+
return state.components.get(name);
|
|
294
|
+
}),
|
|
295
|
+
hasComponent: mock((name: string) => {
|
|
296
|
+
return state.components.has(name);
|
|
297
|
+
}),
|
|
298
|
+
setLoading: mock((loading: boolean) => {
|
|
299
|
+
state.loading = loading;
|
|
300
|
+
store.loading = loading;
|
|
301
|
+
}),
|
|
302
|
+
getState: () => ({ ...state }),
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
return store;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ========== EditorServices Mock ==========
|
|
309
|
+
|
|
310
|
+
export interface TypedMockEditorServices {
|
|
311
|
+
fileService: {
|
|
312
|
+
loadAllFiles: ReturnType<typeof mock>;
|
|
313
|
+
loadFileData: ReturnType<typeof mock>;
|
|
314
|
+
saveFileData: ReturnType<typeof mock>;
|
|
315
|
+
saveComponentJS: ReturnType<typeof mock>;
|
|
316
|
+
saveComponentCSS: ReturnType<typeof mock>;
|
|
317
|
+
};
|
|
318
|
+
nodeService: {
|
|
319
|
+
addElement: ReturnType<typeof mock>;
|
|
320
|
+
deleteElement: ReturnType<typeof mock>;
|
|
321
|
+
moveElement: ReturnType<typeof mock>;
|
|
322
|
+
copyNode: ReturnType<typeof mock>;
|
|
323
|
+
pasteNode: ReturnType<typeof mock>;
|
|
324
|
+
};
|
|
325
|
+
componentService: {
|
|
326
|
+
loadComponentDefinition: ReturnType<typeof mock>;
|
|
327
|
+
createFromSelection: ReturnType<typeof mock>;
|
|
328
|
+
};
|
|
329
|
+
fileStoreService: {
|
|
330
|
+
loadFiles: ReturnType<typeof mock>;
|
|
331
|
+
selectFile: ReturnType<typeof mock>;
|
|
332
|
+
reloadSelectedFilePreservingSelection: ReturnType<typeof mock>;
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function createTypedMockEditorServices(): TypedMockEditorServices {
|
|
337
|
+
return {
|
|
338
|
+
fileService: {
|
|
339
|
+
loadAllFiles: mock(() =>
|
|
340
|
+
Promise.resolve({ pages: [], components: [] })
|
|
341
|
+
),
|
|
342
|
+
loadFileData: mock(() =>
|
|
343
|
+
Promise.resolve({ component: { type: 'div', children: [] } })
|
|
344
|
+
),
|
|
345
|
+
saveFileData: mock(() => Promise.resolve()),
|
|
346
|
+
saveComponentJS: mock(() => Promise.resolve()),
|
|
347
|
+
saveComponentCSS: mock(() => Promise.resolve()),
|
|
348
|
+
},
|
|
349
|
+
nodeService: {
|
|
350
|
+
addElement: mock(() => Promise.resolve()),
|
|
351
|
+
deleteElement: mock(() => Promise.resolve()),
|
|
352
|
+
moveElement: mock(() => Promise.resolve()),
|
|
353
|
+
copyNode: mock(() => Promise.resolve()),
|
|
354
|
+
pasteNode: mock(() => Promise.resolve()),
|
|
355
|
+
},
|
|
356
|
+
componentService: {
|
|
357
|
+
loadComponentDefinition: mock(() => Promise.resolve()),
|
|
358
|
+
createFromSelection: mock(() => Promise.resolve()),
|
|
359
|
+
},
|
|
360
|
+
fileStoreService: {
|
|
361
|
+
loadFiles: mock(() => Promise.resolve()),
|
|
362
|
+
selectFile: mock(() =>
|
|
363
|
+
Promise.resolve({ component: { type: 'div', children: [] } })
|
|
364
|
+
),
|
|
365
|
+
reloadSelectedFilePreservingSelection: mock(() =>
|
|
366
|
+
Promise.resolve({ component: { type: 'div', children: [] } })
|
|
367
|
+
),
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Factories
|
|
3
|
+
* Re-export all typed mock factories
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from './DomMockFactory';
|
|
7
|
+
export * from './FetchMockFactory';
|
|
8
|
+
export * from './EventMockFactory';
|
|
9
|
+
export * from './StoreMockFactory';
|
|
10
|
+
export * from './ConsoleMockFactory';
|
|
11
|
+
export * from './ServerMockFactory';
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Fixtures
|
|
3
|
+
* Common test data and fixtures for reuse across tests
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Path } from "../shared/pathArrayUtils";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Common test paths
|
|
10
|
+
*/
|
|
11
|
+
export const TEST_PATHS = {
|
|
12
|
+
ROOT: [0] as Path,
|
|
13
|
+
HEADER: [0, 0] as Path,
|
|
14
|
+
CONTENT: [0, 1] as Path,
|
|
15
|
+
FOOTER: [0, 2] as Path,
|
|
16
|
+
BUTTON_PRIMARY: [0, 1, 0] as Path,
|
|
17
|
+
BUTTON_SECONDARY: [0, 1, 1] as Path,
|
|
18
|
+
CARD: [1] as Path,
|
|
19
|
+
CARD_TITLE: [1, 0] as Path,
|
|
20
|
+
CARD_CONTENT: [1, 1] as Path,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Common component names for testing
|
|
25
|
+
*/
|
|
26
|
+
export const TEST_COMPONENTS = {
|
|
27
|
+
BUTTON: "Button",
|
|
28
|
+
CARD: "Card",
|
|
29
|
+
HEADER: "Header",
|
|
30
|
+
FOOTER: "Footer",
|
|
31
|
+
LAYOUT: "Layout",
|
|
32
|
+
GRID: "Grid",
|
|
33
|
+
FORM: "Form",
|
|
34
|
+
INPUT: "Input",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Sample component structures
|
|
39
|
+
*/
|
|
40
|
+
export const SAMPLE_STRUCTURES = {
|
|
41
|
+
button: {
|
|
42
|
+
type: "button",
|
|
43
|
+
props: {
|
|
44
|
+
className: "btn",
|
|
45
|
+
onClick: "handleClick",
|
|
46
|
+
},
|
|
47
|
+
children: ["Click me"],
|
|
48
|
+
},
|
|
49
|
+
card: {
|
|
50
|
+
type: "div",
|
|
51
|
+
props: {
|
|
52
|
+
className: "card",
|
|
53
|
+
},
|
|
54
|
+
children: [
|
|
55
|
+
{
|
|
56
|
+
type: "h3",
|
|
57
|
+
props: { className: "card-title" },
|
|
58
|
+
children: ["Card Title"],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: "div",
|
|
62
|
+
props: { className: "card-content" },
|
|
63
|
+
children: ["Card content here"],
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
layout: {
|
|
68
|
+
type: "div",
|
|
69
|
+
props: {
|
|
70
|
+
className: "layout",
|
|
71
|
+
},
|
|
72
|
+
children: [
|
|
73
|
+
{
|
|
74
|
+
type: "header",
|
|
75
|
+
children: ["Header"],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: "main",
|
|
79
|
+
children: ["Content"],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: "footer",
|
|
83
|
+
children: ["Footer"],
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Sample component props
|
|
91
|
+
*/
|
|
92
|
+
export const SAMPLE_PROPS = {
|
|
93
|
+
buttonPrimary: {
|
|
94
|
+
variant: "primary",
|
|
95
|
+
size: "md",
|
|
96
|
+
disabled: false,
|
|
97
|
+
},
|
|
98
|
+
buttonSecondary: {
|
|
99
|
+
variant: "secondary",
|
|
100
|
+
size: "lg",
|
|
101
|
+
disabled: false,
|
|
102
|
+
},
|
|
103
|
+
cardVariant: {
|
|
104
|
+
variant: "elevated",
|
|
105
|
+
compact: false,
|
|
106
|
+
},
|
|
107
|
+
layoutProps: {
|
|
108
|
+
spacing: "default",
|
|
109
|
+
responsive: true,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Error messages for testing error handling
|
|
115
|
+
*/
|
|
116
|
+
export const ERROR_MESSAGES = {
|
|
117
|
+
RENDER_ERROR: "Component render failed",
|
|
118
|
+
STRUCTURE_MISSING: "Component structure missing",
|
|
119
|
+
INVALID_PROP_TYPE: "Invalid prop type",
|
|
120
|
+
TEMPLATE_ERROR: "Template evaluation error",
|
|
121
|
+
COMPONENT_NOT_FOUND: "Component not found",
|
|
122
|
+
PATH_RESOLUTION: "Failed to resolve path",
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Default test configuration
|
|
127
|
+
*/
|
|
128
|
+
export const DEFAULT_TEST_CONFIG = {
|
|
129
|
+
timeout: 5000,
|
|
130
|
+
scrollBehavior: "smooth" as const,
|
|
131
|
+
outlineColor: "#007acc",
|
|
132
|
+
outlineWidth: "2px",
|
|
133
|
+
outlineStyle: "solid" as const,
|
|
134
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
waitFor,
|
|
4
|
+
flushPromises,
|
|
5
|
+
wait,
|
|
6
|
+
createDeferred,
|
|
7
|
+
retry,
|
|
8
|
+
createCallCounter,
|
|
9
|
+
} from './asyncHelpers';
|
|
10
|
+
|
|
11
|
+
describe('asyncHelpers', () => {
|
|
12
|
+
describe('waitFor', () => {
|
|
13
|
+
test('resolves when condition is true', async () => {
|
|
14
|
+
let value = false;
|
|
15
|
+
setTimeout(() => { value = true; }, 50);
|
|
16
|
+
await waitFor(() => value);
|
|
17
|
+
expect(value).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('throws on timeout', async () => {
|
|
21
|
+
await expect(
|
|
22
|
+
waitFor(() => false, { timeout: 100 })
|
|
23
|
+
).rejects.toThrow('timed out');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('handles async conditions', async () => {
|
|
27
|
+
let value = false;
|
|
28
|
+
setTimeout(() => { value = true; }, 50);
|
|
29
|
+
await waitFor(async () => value);
|
|
30
|
+
expect(value).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('flushPromises', () => {
|
|
35
|
+
test('waits for microtask queue', async () => {
|
|
36
|
+
let resolved = false;
|
|
37
|
+
Promise.resolve().then(() => { resolved = true; });
|
|
38
|
+
expect(resolved).toBe(false);
|
|
39
|
+
await flushPromises();
|
|
40
|
+
expect(resolved).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('wait', () => {
|
|
45
|
+
test('waits for specified time', async () => {
|
|
46
|
+
const start = Date.now();
|
|
47
|
+
await wait(50);
|
|
48
|
+
const elapsed = Date.now() - start;
|
|
49
|
+
expect(elapsed).toBeGreaterThanOrEqual(45);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('createDeferred', () => {
|
|
54
|
+
test('creates promise with external resolve', async () => {
|
|
55
|
+
const deferred = createDeferred<number>();
|
|
56
|
+
setTimeout(() => deferred.resolve(42), 10);
|
|
57
|
+
const result = await deferred.promise;
|
|
58
|
+
expect(result).toBe(42);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('creates promise with external reject', async () => {
|
|
62
|
+
const deferred = createDeferred<number>();
|
|
63
|
+
setTimeout(() => deferred.reject(new Error('Failed')), 10);
|
|
64
|
+
await expect(deferred.promise).rejects.toThrow('Failed');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('retry', () => {
|
|
69
|
+
test('returns result on first success', async () => {
|
|
70
|
+
const fn = () => 42;
|
|
71
|
+
const result = await retry(fn);
|
|
72
|
+
expect(result).toBe(42);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('retries on failure', async () => {
|
|
76
|
+
let attempts = 0;
|
|
77
|
+
const fn = () => {
|
|
78
|
+
attempts++;
|
|
79
|
+
if (attempts < 3) throw new Error('Not yet');
|
|
80
|
+
return 42;
|
|
81
|
+
};
|
|
82
|
+
const result = await retry(fn, { maxAttempts: 3, delay: 10 });
|
|
83
|
+
expect(result).toBe(42);
|
|
84
|
+
expect(attempts).toBe(3);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('throws after max attempts', async () => {
|
|
88
|
+
const fn = () => { throw new Error('Always fails'); };
|
|
89
|
+
await expect(
|
|
90
|
+
retry(fn, { maxAttempts: 2, delay: 10 })
|
|
91
|
+
).rejects.toThrow('Always fails');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('createCallCounter', () => {
|
|
96
|
+
test('resolves after target count', async () => {
|
|
97
|
+
const counter = createCallCounter(3);
|
|
98
|
+
counter.increment();
|
|
99
|
+
counter.increment();
|
|
100
|
+
counter.increment();
|
|
101
|
+
await counter.promise;
|
|
102
|
+
expect(true).toBe(true); // If we get here, it worked
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('resolves immediately if already at target', async () => {
|
|
106
|
+
const counter = createCallCounter(1);
|
|
107
|
+
counter.increment();
|
|
108
|
+
await counter.promise;
|
|
109
|
+
expect(true).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|