meno-core 1.0.2 → 1.0.4
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 +63 -4
- package/build-static.ts +19 -3
- package/entries/server-router.tsx +0 -1
- package/lib/client/ErrorBoundary.test.tsx +3 -3
- package/lib/client/componentRegistry.test.ts +81 -35
- package/lib/client/core/ComponentBuilder.test.ts +12 -18
- package/lib/client/core/ComponentBuilder.ts +364 -768
- package/lib/client/core/ComponentRenderer.test.tsx +0 -1
- package/lib/client/core/builders/cmsListBuilder.ts +242 -0
- package/lib/client/core/builders/embedBuilder.ts +140 -0
- package/lib/client/core/builders/index.ts +26 -0
- package/lib/client/core/builders/linkBuilder.ts +136 -0
- package/lib/client/core/builders/localeListBuilder.ts +193 -0
- package/lib/client/core/builders/objectLinkBuilder.ts +138 -0
- package/lib/client/core/builders/types.ts +89 -0
- package/lib/client/core/cmsTemplateProcessor.ts +2 -2
- package/lib/client/hmrWebSocket.ts +6 -2
- package/lib/client/hydration/HydrationUtils.test.ts +1 -1
- package/lib/client/i18nConfigService.test.ts +6 -7
- package/lib/client/responsiveStyleResolver.test.ts +6 -6
- package/lib/client/routing/RouteLoader.test.ts +11 -11
- package/lib/client/routing/RouteLoader.ts +4 -2
- package/lib/client/routing/Router.tsx +21 -12
- package/lib/client/scripts/ScriptExecutor.test.ts +69 -70
- package/lib/client/scripts/ScriptExecutor.ts +81 -42
- package/lib/client/services/PrefetchService.test.ts +3 -3
- package/lib/client/styleProcessor.ts +4 -3
- package/lib/client/styles/StyleInjector.test.ts +6 -2
- package/lib/client/styles/StyleInjector.ts +7 -3
- package/lib/client/templateEngine.test.ts +6 -5
- package/lib/client/templateEngine.ts +20 -167
- package/lib/client/utils/toast.ts +19 -18
- package/lib/server/cssGenerator.test.ts +8 -8
- package/lib/server/errors.ts +65 -0
- package/lib/server/jsonLoader.test.ts +2 -2
- package/lib/server/jsonLoader.ts +5 -0
- package/lib/server/middleware/cors.ts +2 -2
- package/lib/server/middleware/logger.test.ts +9 -9
- package/lib/server/routes/api/pages.ts +1 -1
- package/lib/server/routes/pages.ts +2 -2
- package/lib/server/services/fileWatcherService.ts +3 -1
- package/lib/server/ssr/attributeBuilder.ts +78 -0
- package/lib/server/ssr/cmsSSRProcessor.ts +100 -0
- package/lib/server/ssr/cssCollector.ts +33 -0
- package/lib/server/ssr/htmlGenerator.ts +147 -0
- package/lib/server/ssr/imageMetadata.ts +117 -0
- package/lib/server/ssr/index.ts +33 -0
- package/lib/server/ssr/jsCollector.ts +89 -0
- package/lib/server/ssr/metaTagGenerator.ts +106 -0
- package/lib/server/ssr/ssrRenderer.ts +1133 -0
- package/lib/server/ssrRenderer.test.ts +11 -8
- package/lib/server/ssrRenderer.ts +7 -1527
- package/lib/server/validateStyleCoverage.ts +1 -1
- package/lib/shared/attributeNodeUtils.test.ts +7 -7
- package/lib/shared/constants.test.ts +1 -1
- package/lib/shared/constants.ts +1 -1
- package/lib/shared/cssGeneration.ts +1 -1
- package/lib/shared/errorLogger.test.ts +136 -0
- package/lib/shared/errorLogger.ts +87 -0
- package/lib/shared/errors.test.ts +99 -0
- package/lib/shared/errors.ts +50 -0
- package/lib/shared/expressionEvaluator.ts +161 -0
- package/lib/shared/fontLoader.ts +2 -2
- package/lib/shared/index.ts +43 -2
- package/lib/shared/itemTemplateUtils.test.ts +78 -0
- package/lib/shared/itemTemplateUtils.ts +66 -30
- package/lib/shared/nodeUtils.test.ts +3 -3
- package/lib/shared/propResolver.test.ts +232 -127
- package/lib/shared/propResolver.ts +8 -6
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +5 -5
- package/lib/shared/registry/ClientRegistry.test.ts +1 -1
- package/lib/shared/registry/RegistryManager.test.ts +4 -4
- package/lib/shared/registry/SSRRegistry.test.ts +1 -1
- package/lib/shared/registry/index.ts +0 -1
- package/lib/shared/registry/nodeTypes/index.ts +0 -5
- package/lib/shared/responsiveScaling.ts +3 -2
- package/lib/shared/styleNodeUtils.test.ts +3 -3
- package/lib/shared/styleUtils.test.ts +7 -7
- package/lib/shared/styleUtils.ts +2 -2
- package/lib/shared/treePathUtils.test.ts +17 -17
- package/lib/shared/treePathUtils.ts +26 -20
- package/lib/shared/types/errors.ts +3 -3
- package/lib/shared/types/nodes.ts +0 -1
- package/lib/shared/types/rendering.ts +2 -1
- package/lib/shared/types/styles.ts +3 -1
- package/lib/shared/utilityClassMapper.ts +17 -5
- package/lib/shared/validation/propValidator.test.ts +51 -7
- package/lib/shared/validation/propValidator.ts +11 -6
- package/lib/shared/validation/schemas.ts +15 -9
- package/lib/shared/validation/validators.test.ts +2 -3
- package/lib/test-utils/factories/FetchMockFactory.ts +2 -2
- package/lib/test-utils/index.ts +2 -1
- package/package.json +1 -1
- package/lib/shared/registry/nodeTypes/TextNodeType.ts +0 -52
package/bin/cli.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { resolve, join } from 'path';
|
|
8
|
-
import { existsSync, mkdirSync, writeFileSync, cpSync } from 'fs';
|
|
8
|
+
import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync } from 'fs';
|
|
9
9
|
import { setProjectRoot } from '../lib/server/projectContext';
|
|
10
10
|
|
|
11
11
|
const args = process.argv.slice(2);
|
|
@@ -35,10 +35,66 @@ Examples:
|
|
|
35
35
|
`);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// Parse _headers file (Netlify/Cloudflare format)
|
|
39
|
+
function parseHeadersFile(distPath: string): Map<string, Record<string, string>> {
|
|
40
|
+
const headersPath = join(distPath, '_headers');
|
|
41
|
+
const headers = new Map<string, Record<string, string>>();
|
|
42
|
+
|
|
43
|
+
if (!existsSync(headersPath)) return headers;
|
|
44
|
+
|
|
45
|
+
const content = readFileSync(headersPath, 'utf-8');
|
|
46
|
+
let currentPath = '';
|
|
47
|
+
|
|
48
|
+
for (const line of content.split('\n')) {
|
|
49
|
+
const trimmed = line.trim();
|
|
50
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
51
|
+
|
|
52
|
+
if (!line.startsWith(' ') && !line.startsWith('\t')) {
|
|
53
|
+
// Path line
|
|
54
|
+
currentPath = trimmed;
|
|
55
|
+
if (!headers.has(currentPath)) {
|
|
56
|
+
headers.set(currentPath, {});
|
|
57
|
+
}
|
|
58
|
+
} else if (currentPath && trimmed.includes(':')) {
|
|
59
|
+
// Header line
|
|
60
|
+
const colonIndex = trimmed.indexOf(':');
|
|
61
|
+
const name = trimmed.substring(0, colonIndex).trim();
|
|
62
|
+
const value = trimmed.substring(colonIndex + 1).trim();
|
|
63
|
+
headers.get(currentPath)![name] = value;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return headers;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get headers matching a pathname
|
|
71
|
+
function getMatchingHeaders(
|
|
72
|
+
pathname: string,
|
|
73
|
+
headersMap: Map<string, Record<string, string>>
|
|
74
|
+
): Record<string, string> {
|
|
75
|
+
const result: Record<string, string> = {};
|
|
76
|
+
|
|
77
|
+
headersMap.forEach((headers, pattern) => {
|
|
78
|
+
if (pattern === pathname || pattern === '/*') {
|
|
79
|
+
Object.assign(result, headers);
|
|
80
|
+
} else if (pattern.endsWith('/*')) {
|
|
81
|
+
const prefix = pattern.slice(0, -1);
|
|
82
|
+
if (pathname.startsWith(prefix)) {
|
|
83
|
+
Object.assign(result, headers);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
38
91
|
// Start static file server on port 8080 (non-blocking)
|
|
39
92
|
async function startStaticServer(distPath: string) {
|
|
40
93
|
const { SERVE_PORT } = await import('../lib/shared/constants');
|
|
41
94
|
|
|
95
|
+
// Parse _headers file once on startup
|
|
96
|
+
const headersMap = parseHeadersFile(distPath);
|
|
97
|
+
|
|
42
98
|
const server = Bun.serve({
|
|
43
99
|
port: SERVE_PORT,
|
|
44
100
|
async fetch(req: Request) {
|
|
@@ -50,6 +106,9 @@ async function startStaticServer(distPath: string) {
|
|
|
50
106
|
pathname = '/index.html';
|
|
51
107
|
}
|
|
52
108
|
|
|
109
|
+
// Get custom headers for this path
|
|
110
|
+
const customHeaders = getMatchingHeaders(pathname, headersMap);
|
|
111
|
+
|
|
53
112
|
// Try to serve the file
|
|
54
113
|
const filePath = join(distPath, pathname);
|
|
55
114
|
|
|
@@ -57,7 +116,7 @@ async function startStaticServer(distPath: string) {
|
|
|
57
116
|
if (existsSync(filePath)) {
|
|
58
117
|
const file = Bun.file(filePath);
|
|
59
118
|
if (await file.exists()) {
|
|
60
|
-
return new Response(file);
|
|
119
|
+
return new Response(file, { headers: customHeaders });
|
|
61
120
|
}
|
|
62
121
|
}
|
|
63
122
|
|
|
@@ -65,14 +124,14 @@ async function startStaticServer(distPath: string) {
|
|
|
65
124
|
const htmlPath = filePath.endsWith('.html') ? filePath : `${filePath}.html`;
|
|
66
125
|
if (existsSync(htmlPath)) {
|
|
67
126
|
const file = Bun.file(htmlPath);
|
|
68
|
-
return new Response(file);
|
|
127
|
+
return new Response(file, { headers: customHeaders });
|
|
69
128
|
}
|
|
70
129
|
|
|
71
130
|
// Try index.html in directory
|
|
72
131
|
const indexPath = join(filePath, 'index.html');
|
|
73
132
|
if (existsSync(indexPath)) {
|
|
74
133
|
const file = Bun.file(indexPath);
|
|
75
|
-
return new Response(file);
|
|
134
|
+
return new Response(file, { headers: customHeaders });
|
|
76
135
|
}
|
|
77
136
|
|
|
78
137
|
return new Response('Not Found', { status: 404 });
|
package/build-static.ts
CHANGED
|
@@ -299,11 +299,27 @@ async function buildStaticPages(): Promise<void> {
|
|
|
299
299
|
const functionsDir = projectPaths.functions();
|
|
300
300
|
if (existsSync(functionsDir)) {
|
|
301
301
|
copyDirectory(functionsDir, join(distDir, "functions"));
|
|
302
|
-
console.log("✅ Assets and functions copied\n");
|
|
303
|
-
} else {
|
|
304
|
-
console.log("✅ Assets copied\n");
|
|
305
302
|
}
|
|
306
303
|
|
|
304
|
+
// Copy _headers and _redirects files for static hosting (Netlify, Cloudflare Pages)
|
|
305
|
+
const hostingFiles: string[] = [];
|
|
306
|
+
const headersFile = join(projectPaths.project, '_headers');
|
|
307
|
+
const redirectsFile = join(projectPaths.project, '_redirects');
|
|
308
|
+
|
|
309
|
+
if (existsSync(headersFile)) {
|
|
310
|
+
copyFileSync(headersFile, join(distDir, '_headers'));
|
|
311
|
+
hostingFiles.push('_headers');
|
|
312
|
+
}
|
|
313
|
+
if (existsSync(redirectsFile)) {
|
|
314
|
+
copyFileSync(redirectsFile, join(distDir, '_redirects'));
|
|
315
|
+
hostingFiles.push('_redirects');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const parts = ['Assets'];
|
|
319
|
+
if (existsSync(functionsDir)) parts.push('functions');
|
|
320
|
+
if (hostingFiles.length > 0) parts.push(hostingFiles.join(', '));
|
|
321
|
+
console.log(`✅ ${parts.join(', ')} copied\n`);
|
|
322
|
+
|
|
307
323
|
// Load all global components
|
|
308
324
|
const components = await loadComponentDirectory(projectPaths.components());
|
|
309
325
|
const globalComponents: Record<string, ComponentDefinition> = {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { test, expect, describe, beforeEach, afterEach, mock } from "bun:test";
|
|
2
2
|
import { ErrorBoundary, withErrorBoundary } from "./ErrorBoundary";
|
|
3
|
-
import { createElement as h, Component, useState } from "react";
|
|
3
|
+
import React, { createElement as h, Component, useState } from "react";
|
|
4
4
|
import { createRoot, Root } from "react-dom/client";
|
|
5
5
|
import { flushPromises, wait } from "../test-utils/helpers/asyncHelpers";
|
|
6
6
|
|
|
@@ -33,7 +33,7 @@ function cleanupContainer(container: HTMLElement) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// Component that throws an error when rendered
|
|
36
|
-
function ThrowingComponent({ error }: { error: Error }) {
|
|
36
|
+
function ThrowingComponent({ error }: { error: Error }): React.ReactNode {
|
|
37
37
|
throw error;
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -124,7 +124,7 @@ describe("ErrorBoundary - Error catching", () => {
|
|
|
124
124
|
|
|
125
125
|
// Verify onError callback was called with the error
|
|
126
126
|
expect(onErrorMock).toHaveBeenCalled();
|
|
127
|
-
const callArgs = onErrorMock.mock.calls[0];
|
|
127
|
+
const callArgs = (onErrorMock.mock.calls as unknown as [Error, unknown][])[0];
|
|
128
128
|
expect(callArgs[0]).toBe(testError);
|
|
129
129
|
} finally {
|
|
130
130
|
root.unmount();
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { test, expect, describe, beforeEach } from "bun:test";
|
|
2
2
|
import { ComponentRegistry } from "./componentRegistry";
|
|
3
|
+
import type { ComponentDefinition, ComponentNode, PropDefinition } from "../shared/types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Helper function to create a valid ComponentDefinition for tests
|
|
7
|
+
*/
|
|
8
|
+
function createTestComponentDef(
|
|
9
|
+
structure?: ComponentNode,
|
|
10
|
+
componentInterface?: Record<string, PropDefinition>
|
|
11
|
+
): ComponentDefinition {
|
|
12
|
+
return {
|
|
13
|
+
component: {
|
|
14
|
+
interface: componentInterface || {},
|
|
15
|
+
structure: structure || { type: "node" as const, tag: "div", children: [] }
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
3
19
|
|
|
4
20
|
describe("ComponentRegistry", () => {
|
|
5
21
|
let registry: ComponentRegistry;
|
|
@@ -10,10 +26,9 @@ describe("ComponentRegistry", () => {
|
|
|
10
26
|
|
|
11
27
|
describe("register and get", () => {
|
|
12
28
|
test("should register and retrieve a component", () => {
|
|
13
|
-
const buttonDef =
|
|
14
|
-
type: "button",
|
|
15
|
-
|
|
16
|
-
};
|
|
29
|
+
const buttonDef = createTestComponentDef(
|
|
30
|
+
{ type: "node" as const, tag: "button", children: [], attributes: { className: "btn" } }
|
|
31
|
+
);
|
|
17
32
|
|
|
18
33
|
registry.register("Button", buttonDef);
|
|
19
34
|
const retrieved = registry.get("Button");
|
|
@@ -27,17 +42,25 @@ describe("ComponentRegistry", () => {
|
|
|
27
42
|
});
|
|
28
43
|
|
|
29
44
|
test("should overwrite existing component with same name", () => {
|
|
30
|
-
registry.register("Button",
|
|
31
|
-
|
|
45
|
+
registry.register("Button", createTestComponentDef(
|
|
46
|
+
{ type: "node" as const, tag: "button", children: [], attributes: { color: "red" } }
|
|
47
|
+
));
|
|
48
|
+
registry.register("Button", createTestComponentDef(
|
|
49
|
+
{ type: "node" as const, tag: "button", children: [], attributes: { color: "blue" } }
|
|
50
|
+
));
|
|
32
51
|
|
|
33
52
|
const result = registry.get("Button");
|
|
34
|
-
|
|
53
|
+
const structure = result?.component?.structure;
|
|
54
|
+
const attributes = structure && 'attributes' in structure ? structure.attributes as Record<string, unknown> : undefined;
|
|
55
|
+
expect(attributes?.color).toBe("blue");
|
|
35
56
|
});
|
|
36
57
|
});
|
|
37
58
|
|
|
38
59
|
describe("has", () => {
|
|
39
60
|
test("should return true for registered component", () => {
|
|
40
|
-
registry.register("Card",
|
|
61
|
+
registry.register("Card", createTestComponentDef(
|
|
62
|
+
{ type: "node" as const, tag: "div", children: [] }
|
|
63
|
+
));
|
|
41
64
|
expect(registry.has("Card")).toBe(true);
|
|
42
65
|
});
|
|
43
66
|
|
|
@@ -48,8 +71,12 @@ describe("ComponentRegistry", () => {
|
|
|
48
71
|
|
|
49
72
|
describe("clear", () => {
|
|
50
73
|
test("should clear all components", () => {
|
|
51
|
-
registry.register("Button",
|
|
52
|
-
|
|
74
|
+
registry.register("Button", createTestComponentDef(
|
|
75
|
+
{ type: "node" as const, tag: "button", children: [] }
|
|
76
|
+
));
|
|
77
|
+
registry.register("Card", createTestComponentDef(
|
|
78
|
+
{ type: "node" as const, tag: "div", children: [] }
|
|
79
|
+
));
|
|
53
80
|
|
|
54
81
|
expect(registry.getNames().length).toBe(2);
|
|
55
82
|
|
|
@@ -63,11 +90,13 @@ describe("ComponentRegistry", () => {
|
|
|
63
90
|
|
|
64
91
|
describe("merge", () => {
|
|
65
92
|
test("should merge multiple components", () => {
|
|
66
|
-
registry.register("Button",
|
|
93
|
+
registry.register("Button", createTestComponentDef(
|
|
94
|
+
{ type: "node" as const, tag: "button", children: [] }
|
|
95
|
+
));
|
|
67
96
|
|
|
68
97
|
registry.merge({
|
|
69
|
-
Card: { type: "div" },
|
|
70
|
-
Link: { type: "a" }
|
|
98
|
+
Card: createTestComponentDef({ type: "node" as const, tag: "div", children: [] }),
|
|
99
|
+
Link: createTestComponentDef({ type: "node" as const, tag: "a", children: [] })
|
|
71
100
|
});
|
|
72
101
|
|
|
73
102
|
expect(registry.getNames().length).toBe(3);
|
|
@@ -77,21 +106,31 @@ describe("ComponentRegistry", () => {
|
|
|
77
106
|
});
|
|
78
107
|
|
|
79
108
|
test("should overwrite existing components when merging", () => {
|
|
80
|
-
registry.register("Button",
|
|
109
|
+
registry.register("Button", createTestComponentDef(
|
|
110
|
+
{ type: "node" as const, tag: "button", children: [], attributes: { color: "red" } }
|
|
111
|
+
));
|
|
81
112
|
|
|
82
113
|
registry.merge({
|
|
83
|
-
Button:
|
|
114
|
+
Button: createTestComponentDef(
|
|
115
|
+
{ type: "node" as const, tag: "button", children: [], attributes: { color: "blue" } }
|
|
116
|
+
)
|
|
84
117
|
});
|
|
85
118
|
|
|
86
119
|
const button = registry.get("Button");
|
|
87
|
-
|
|
120
|
+
const structure = button?.component?.structure;
|
|
121
|
+
const attributes = structure && 'attributes' in structure ? structure.attributes as Record<string, unknown> : undefined;
|
|
122
|
+
expect(attributes?.color).toBe("blue");
|
|
88
123
|
});
|
|
89
124
|
});
|
|
90
125
|
|
|
91
126
|
describe("getAll", () => {
|
|
92
127
|
test("should return all registered components", () => {
|
|
93
|
-
registry.register("Button",
|
|
94
|
-
|
|
128
|
+
registry.register("Button", createTestComponentDef(
|
|
129
|
+
{ type: "node" as const, tag: "button", children: [] }
|
|
130
|
+
));
|
|
131
|
+
registry.register("Card", createTestComponentDef(
|
|
132
|
+
{ type: "node" as const, tag: "div", children: [] }
|
|
133
|
+
));
|
|
95
134
|
|
|
96
135
|
const all = registry.getAll();
|
|
97
136
|
|
|
@@ -101,10 +140,14 @@ describe("ComponentRegistry", () => {
|
|
|
101
140
|
});
|
|
102
141
|
|
|
103
142
|
test("should return a copy, not the original registry", () => {
|
|
104
|
-
registry.register("Button",
|
|
143
|
+
registry.register("Button", createTestComponentDef(
|
|
144
|
+
{ type: "node" as const, tag: "button", children: [] }
|
|
145
|
+
));
|
|
105
146
|
|
|
106
147
|
const all = registry.getAll();
|
|
107
|
-
all.NewComponent =
|
|
148
|
+
all.NewComponent = createTestComponentDef(
|
|
149
|
+
{ type: "node" as const, tag: "span", children: [] }
|
|
150
|
+
);
|
|
108
151
|
|
|
109
152
|
expect(registry.has("NewComponent")).toBe(false);
|
|
110
153
|
});
|
|
@@ -112,8 +155,12 @@ describe("ComponentRegistry", () => {
|
|
|
112
155
|
|
|
113
156
|
describe("getNames", () => {
|
|
114
157
|
test("should return list of component names", () => {
|
|
115
|
-
registry.register("Button",
|
|
116
|
-
|
|
158
|
+
registry.register("Button", createTestComponentDef(
|
|
159
|
+
{ type: "node" as const, tag: "button", children: [] }
|
|
160
|
+
));
|
|
161
|
+
registry.register("Card", createTestComponentDef(
|
|
162
|
+
{ type: "node" as const, tag: "div", children: [] }
|
|
163
|
+
));
|
|
117
164
|
|
|
118
165
|
const names = registry.getNames();
|
|
119
166
|
|
|
@@ -128,7 +175,9 @@ describe("ComponentRegistry", () => {
|
|
|
128
175
|
|
|
129
176
|
describe("remove", () => {
|
|
130
177
|
test("should remove a component and return true", () => {
|
|
131
|
-
registry.register("Button",
|
|
178
|
+
registry.register("Button", createTestComponentDef(
|
|
179
|
+
{ type: "node" as const, tag: "button", children: [] }
|
|
180
|
+
));
|
|
132
181
|
|
|
133
182
|
const result = registry.remove("Button");
|
|
134
183
|
|
|
@@ -142,23 +191,20 @@ describe("ComponentRegistry", () => {
|
|
|
142
191
|
});
|
|
143
192
|
});
|
|
144
193
|
|
|
145
|
-
describe("component with
|
|
146
|
-
test("should handle components with
|
|
147
|
-
const buttonDef = {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
defaultVariant: "primary"
|
|
194
|
+
describe("component with category", () => {
|
|
195
|
+
test("should handle components with category definitions", () => {
|
|
196
|
+
const buttonDef: ComponentDefinition = {
|
|
197
|
+
component: {
|
|
198
|
+
interface: {},
|
|
199
|
+
structure: { type: "node" as const, tag: "button", children: [] },
|
|
200
|
+
category: "buttons"
|
|
201
|
+
}
|
|
154
202
|
};
|
|
155
203
|
|
|
156
204
|
registry.register("Button", buttonDef);
|
|
157
205
|
const retrieved = registry.get("Button");
|
|
158
206
|
|
|
159
|
-
expect(retrieved?.
|
|
160
|
-
expect(retrieved?.variants?.primary?.backgroundColor).toBe("blue");
|
|
161
|
-
expect(retrieved?.defaultVariant).toBe("primary");
|
|
207
|
+
expect(retrieved?.component?.category).toBe("buttons");
|
|
162
208
|
});
|
|
163
209
|
});
|
|
164
210
|
});
|
|
@@ -3,22 +3,19 @@ import { ComponentBuilder } from "./ComponentBuilder";
|
|
|
3
3
|
import { ComponentRegistry } from "../componentRegistry";
|
|
4
4
|
import { ElementRegistry } from "../elementRegistry";
|
|
5
5
|
import type { ComponentNode, CMSListNode, CMSItem } from "../../shared/types";
|
|
6
|
-
import type { HighlightManager } from "../highlightManager";
|
|
7
6
|
import { NODE_TYPE } from "../../shared/constants";
|
|
8
|
-
import {
|
|
7
|
+
import { createMockElementRegistry } from "../../test-utils/mocks";
|
|
9
8
|
|
|
10
9
|
// Note: Using typed mocks from test-utils/mocks instead of inline 'as any' casts
|
|
11
10
|
|
|
12
11
|
describe("ComponentBuilder", () => {
|
|
13
12
|
let componentRegistry: ComponentRegistry;
|
|
14
|
-
let hoverHighlightManager: HighlightManager;
|
|
15
13
|
let elementRegistry: ElementRegistry;
|
|
16
14
|
let builder: ComponentBuilder;
|
|
17
15
|
let registeredPaths: Array<{ path: string; tag?: string }>;
|
|
18
16
|
|
|
19
17
|
beforeEach(() => {
|
|
20
18
|
componentRegistry = new ComponentRegistry();
|
|
21
|
-
hoverHighlightManager = createMockHighlightManager();
|
|
22
19
|
registeredPaths = [];
|
|
23
20
|
|
|
24
21
|
// Create mock element registry with path tracking
|
|
@@ -31,7 +28,6 @@ describe("ComponentBuilder", () => {
|
|
|
31
28
|
|
|
32
29
|
builder = new ComponentBuilder({
|
|
33
30
|
componentRegistry,
|
|
34
|
-
hoverHighlightManager,
|
|
35
31
|
elementRegistry,
|
|
36
32
|
});
|
|
37
33
|
});
|
|
@@ -711,7 +707,7 @@ describe("ComponentBuilder", () => {
|
|
|
711
707
|
]);
|
|
712
708
|
|
|
713
709
|
const result = builder.buildComponent({
|
|
714
|
-
node,
|
|
710
|
+
node: node as unknown as ComponentNode,
|
|
715
711
|
collectionItemsMap: { posts: items },
|
|
716
712
|
});
|
|
717
713
|
|
|
@@ -730,7 +726,7 @@ describe("ComponentBuilder", () => {
|
|
|
730
726
|
]);
|
|
731
727
|
|
|
732
728
|
const result = builder.buildComponent({
|
|
733
|
-
node,
|
|
729
|
+
node: node as unknown as ComponentNode,
|
|
734
730
|
collectionItemsMap: { posts: items },
|
|
735
731
|
});
|
|
736
732
|
|
|
@@ -758,7 +754,7 @@ describe("ComponentBuilder", () => {
|
|
|
758
754
|
]);
|
|
759
755
|
|
|
760
756
|
const result = builder.buildComponent({
|
|
761
|
-
node,
|
|
757
|
+
node: node as unknown as ComponentNode,
|
|
762
758
|
collectionItemsMap: { posts: items },
|
|
763
759
|
});
|
|
764
760
|
|
|
@@ -774,7 +770,7 @@ describe("ComponentBuilder", () => {
|
|
|
774
770
|
]);
|
|
775
771
|
|
|
776
772
|
const result = builder.buildComponent({
|
|
777
|
-
node,
|
|
773
|
+
node: node as unknown as ComponentNode,
|
|
778
774
|
collectionItemsMap: { posts: items },
|
|
779
775
|
});
|
|
780
776
|
|
|
@@ -791,7 +787,7 @@ describe("ComponentBuilder", () => {
|
|
|
791
787
|
]);
|
|
792
788
|
|
|
793
789
|
const result = builder.buildComponent({
|
|
794
|
-
node,
|
|
790
|
+
node: node as unknown as ComponentNode,
|
|
795
791
|
collectionItemsMap: { posts: [] },
|
|
796
792
|
});
|
|
797
793
|
|
|
@@ -808,7 +804,7 @@ describe("ComponentBuilder", () => {
|
|
|
808
804
|
]);
|
|
809
805
|
|
|
810
806
|
const result = builder.buildComponent({
|
|
811
|
-
node,
|
|
807
|
+
node: node as unknown as ComponentNode,
|
|
812
808
|
collectionItemsMap: {}, // No posts collection
|
|
813
809
|
});
|
|
814
810
|
|
|
@@ -823,7 +819,7 @@ describe("ComponentBuilder", () => {
|
|
|
823
819
|
], { limit: 2 });
|
|
824
820
|
|
|
825
821
|
const result = builder.buildComponent({
|
|
826
|
-
node,
|
|
822
|
+
node: node as unknown as ComponentNode,
|
|
827
823
|
collectionItemsMap: { posts: items },
|
|
828
824
|
});
|
|
829
825
|
|
|
@@ -840,7 +836,7 @@ describe("ComponentBuilder", () => {
|
|
|
840
836
|
], { offset: 2 });
|
|
841
837
|
|
|
842
838
|
const result = builder.buildComponent({
|
|
843
|
-
node,
|
|
839
|
+
node: node as unknown as ComponentNode,
|
|
844
840
|
collectionItemsMap: { posts: items },
|
|
845
841
|
});
|
|
846
842
|
|
|
@@ -857,7 +853,7 @@ describe("ComponentBuilder", () => {
|
|
|
857
853
|
], { offset: 1, limit: 2 });
|
|
858
854
|
|
|
859
855
|
const result = builder.buildComponent({
|
|
860
|
-
node,
|
|
856
|
+
node: node as unknown as ComponentNode,
|
|
861
857
|
collectionItemsMap: { posts: items },
|
|
862
858
|
});
|
|
863
859
|
|
|
@@ -924,7 +920,6 @@ describe("ComponentBuilder", () => {
|
|
|
924
920
|
// Create builder with page name getter
|
|
925
921
|
const pageBuilder = new ComponentBuilder({
|
|
926
922
|
componentRegistry,
|
|
927
|
-
hoverHighlightManager,
|
|
928
923
|
elementRegistry,
|
|
929
924
|
getCurrentPageName: () => "about",
|
|
930
925
|
getCurrentFileType: () => "page",
|
|
@@ -1005,7 +1000,7 @@ describe("ComponentBuilder", () => {
|
|
|
1005
1000
|
|
|
1006
1001
|
const result1 = builder.buildComponent({
|
|
1007
1002
|
node: node1,
|
|
1008
|
-
|
|
1003
|
+
elementPath: [0],
|
|
1009
1004
|
});
|
|
1010
1005
|
|
|
1011
1006
|
expect(result1).not.toBeNull();
|
|
@@ -1013,7 +1008,7 @@ describe("ComponentBuilder", () => {
|
|
|
1013
1008
|
// Second instance at page path [1, 2, 3]
|
|
1014
1009
|
const result2 = builder.buildComponent({
|
|
1015
1010
|
node: node1,
|
|
1016
|
-
|
|
1011
|
+
elementPath: [1, 2, 3],
|
|
1017
1012
|
});
|
|
1018
1013
|
|
|
1019
1014
|
expect(result2).not.toBeNull();
|
|
@@ -1055,7 +1050,6 @@ describe("ComponentBuilder", () => {
|
|
|
1055
1050
|
// Page element with same name
|
|
1056
1051
|
const pageBuilder = new ComponentBuilder({
|
|
1057
1052
|
componentRegistry,
|
|
1058
|
-
hoverHighlightManager,
|
|
1059
1053
|
elementRegistry,
|
|
1060
1054
|
getCurrentPageName: () => "button",
|
|
1061
1055
|
getCurrentFileType: () => "page",
|