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,152 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { extractAttributesFromNode, applyAttributesToNode } from './attributeNodeUtils';
|
|
3
|
+
import type { ComponentNode } from './types';
|
|
4
|
+
|
|
5
|
+
describe('attributeNodeUtils', () => {
|
|
6
|
+
describe('extractAttributesFromNode', () => {
|
|
7
|
+
test('returns empty object for null/undefined node', () => {
|
|
8
|
+
expect(extractAttributesFromNode(null as any)).toEqual({});
|
|
9
|
+
expect(extractAttributesFromNode(undefined as any)).toEqual({});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('returns empty object for node without attributes', () => {
|
|
13
|
+
const node: ComponentNode = { type: 'html', tag: 'div' };
|
|
14
|
+
expect(extractAttributesFromNode(node)).toEqual({});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('extracts attributes from node with attributes', () => {
|
|
18
|
+
const node: ComponentNode = {
|
|
19
|
+
type: 'html',
|
|
20
|
+
tag: 'div',
|
|
21
|
+
attributes: { id: 'test', disabled: true, tabIndex: 0 }
|
|
22
|
+
};
|
|
23
|
+
expect(extractAttributesFromNode(node)).toEqual({
|
|
24
|
+
id: 'test',
|
|
25
|
+
disabled: true,
|
|
26
|
+
tabIndex: 0
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('returns empty object for node with empty attributes object', () => {
|
|
31
|
+
const node: ComponentNode = {
|
|
32
|
+
type: 'html',
|
|
33
|
+
tag: 'div',
|
|
34
|
+
attributes: {}
|
|
35
|
+
};
|
|
36
|
+
expect(extractAttributesFromNode(node)).toEqual({});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('handles various attribute types correctly', () => {
|
|
40
|
+
const node: ComponentNode = {
|
|
41
|
+
type: 'node',
|
|
42
|
+
tag: 'input',
|
|
43
|
+
attributes: {
|
|
44
|
+
type: 'text',
|
|
45
|
+
required: true,
|
|
46
|
+
maxLength: 100,
|
|
47
|
+
placeholder: 'Enter text'
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
expect(extractAttributesFromNode(node)).toEqual({
|
|
51
|
+
type: 'text',
|
|
52
|
+
required: true,
|
|
53
|
+
maxLength: 100,
|
|
54
|
+
placeholder: 'Enter text'
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('returns empty object for non-object node', () => {
|
|
59
|
+
expect(extractAttributesFromNode('string' as any)).toEqual({});
|
|
60
|
+
expect(extractAttributesFromNode(123 as any)).toEqual({});
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('applyAttributesToNode', () => {
|
|
65
|
+
test('returns node unchanged when attributes are null', () => {
|
|
66
|
+
const node: ComponentNode = { type: 'node', tag: 'div' };
|
|
67
|
+
expect(applyAttributesToNode(node, null)).toEqual(node);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('returns node unchanged when attributes are undefined', () => {
|
|
71
|
+
const node: ComponentNode = { type: 'node', tag: 'div' };
|
|
72
|
+
expect(applyAttributesToNode(node, undefined)).toEqual(node);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('returns node unchanged when attributes are empty', () => {
|
|
76
|
+
const node: ComponentNode = { type: 'node', tag: 'div' };
|
|
77
|
+
expect(applyAttributesToNode(node, {})).toEqual(node);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('applies attributes to html node', () => {
|
|
81
|
+
const node: ComponentNode = { type: 'node', tag: 'div' };
|
|
82
|
+
const result = applyAttributesToNode(node, { id: 'test', className: 'btn' });
|
|
83
|
+
expect(result).toEqual({
|
|
84
|
+
type: 'node',
|
|
85
|
+
tag: 'div',
|
|
86
|
+
attributes: { id: 'test', className: 'btn' }
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('merges with existing attributes', () => {
|
|
91
|
+
const node: ComponentNode = {
|
|
92
|
+
type: 'node',
|
|
93
|
+
tag: 'div',
|
|
94
|
+
attributes: { id: 'original', disabled: true }
|
|
95
|
+
};
|
|
96
|
+
const result = applyAttributesToNode(node, { className: 'new', tabIndex: 0 });
|
|
97
|
+
expect(result).toEqual({
|
|
98
|
+
type: 'node',
|
|
99
|
+
tag: 'div',
|
|
100
|
+
attributes: {
|
|
101
|
+
id: 'original',
|
|
102
|
+
disabled: true,
|
|
103
|
+
className: 'new',
|
|
104
|
+
tabIndex: 0
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('overwrites existing attributes with same key', () => {
|
|
110
|
+
const node: ComponentNode = {
|
|
111
|
+
type: 'node',
|
|
112
|
+
tag: 'div',
|
|
113
|
+
attributes: { id: 'original', className: 'old' }
|
|
114
|
+
};
|
|
115
|
+
const result = applyAttributesToNode(node, { className: 'new' });
|
|
116
|
+
expect(result.attributes).toEqual({
|
|
117
|
+
id: 'original',
|
|
118
|
+
className: 'new'
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('works with component nodes', () => {
|
|
123
|
+
const node: ComponentNode = {
|
|
124
|
+
type: 'component',
|
|
125
|
+
name: 'Button'
|
|
126
|
+
};
|
|
127
|
+
const result = applyAttributesToNode(node, { disabled: true });
|
|
128
|
+
expect(result).toEqual({
|
|
129
|
+
type: 'component',
|
|
130
|
+
name: 'Button',
|
|
131
|
+
attributes: { disabled: true }
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('returns node unchanged for text nodes', () => {
|
|
136
|
+
const node: ComponentNode = { type: 'text', value: 'Hello' };
|
|
137
|
+
const result = applyAttributesToNode(node, { className: 'test' });
|
|
138
|
+
expect(result).toEqual(node);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('does not mutate original node', () => {
|
|
142
|
+
const node: ComponentNode = {
|
|
143
|
+
type: 'node',
|
|
144
|
+
tag: 'div',
|
|
145
|
+
attributes: { id: 'original' }
|
|
146
|
+
};
|
|
147
|
+
const original = JSON.stringify(node);
|
|
148
|
+
applyAttributesToNode(node, { className: 'new' });
|
|
149
|
+
expect(JSON.stringify(node)).toEqual(original);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for working with attributes in ComponentNode structures
|
|
3
|
+
* Provides helper functions for extracting and applying attributes based on node type
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ComponentNode } from './types';
|
|
7
|
+
import { isComponentNode, isHtmlNode } from './nodeUtils';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extract attributes from a ComponentNode
|
|
11
|
+
* Attributes are stored at the top level of the node (like styles)
|
|
12
|
+
* Returns a record of attributes ready for React props
|
|
13
|
+
*/
|
|
14
|
+
export function extractAttributesFromNode(
|
|
15
|
+
node: ComponentNode
|
|
16
|
+
): Record<string, string | number | boolean> {
|
|
17
|
+
if (!node || typeof node !== 'object') {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Attributes are stored at top level for both HTML nodes and component instances
|
|
22
|
+
if ('attributes' in node && node.attributes) {
|
|
23
|
+
return node.attributes as Record<string, string | number | boolean>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Apply attributes to a ComponentNode
|
|
31
|
+
* Attributes go at top level (node.attributes)
|
|
32
|
+
*/
|
|
33
|
+
export function applyAttributesToNode(
|
|
34
|
+
node: ComponentNode,
|
|
35
|
+
attributes: Record<string, string | number | boolean> | null | undefined
|
|
36
|
+
): ComponentNode {
|
|
37
|
+
if (!attributes || Object.keys(attributes).length === 0) {
|
|
38
|
+
return node;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (isComponentNode(node) || isHtmlNode(node)) {
|
|
42
|
+
return {
|
|
43
|
+
...node,
|
|
44
|
+
attributes: { ...(node.attributes || {}), ...attributes }
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return node;
|
|
49
|
+
}
|
|
50
|
+
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { getAllBreakpointNames, getBreakpointName, DEFAULT_BREAKPOINTS } from './breakpoints';
|
|
3
|
+
import type { BreakpointConfig } from './breakpoints';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* breakpoints Tests
|
|
7
|
+
* Tests responsive breakpoint utilities
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
describe('breakpoints', () => {
|
|
11
|
+
describe('DEFAULT_BREAKPOINTS', () => {
|
|
12
|
+
test('should have tablet and mobile breakpoints', () => {
|
|
13
|
+
expect(DEFAULT_BREAKPOINTS.tablet).toBe(1024);
|
|
14
|
+
expect(DEFAULT_BREAKPOINTS.mobile).toBe(540);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('getAllBreakpointNames', () => {
|
|
19
|
+
test('should return base plus default breakpoints', () => {
|
|
20
|
+
const names = getAllBreakpointNames();
|
|
21
|
+
expect(names).toContain('base');
|
|
22
|
+
expect(names).toContain('tablet');
|
|
23
|
+
expect(names).toContain('mobile');
|
|
24
|
+
expect(names.length).toBe(3);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should have base as first element', () => {
|
|
28
|
+
const names = getAllBreakpointNames();
|
|
29
|
+
expect(names[0]).toBe('base');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should sort breakpoints by value descending', () => {
|
|
33
|
+
// tablet (1024) should come before mobile (540)
|
|
34
|
+
const names = getAllBreakpointNames();
|
|
35
|
+
const tabletIndex = names.indexOf('tablet');
|
|
36
|
+
const mobileIndex = names.indexOf('mobile');
|
|
37
|
+
expect(tabletIndex).toBeLessThan(mobileIndex);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('should handle custom breakpoints', () => {
|
|
41
|
+
const customBreakpoints: BreakpointConfig = {
|
|
42
|
+
large: 1440,
|
|
43
|
+
medium: 960,
|
|
44
|
+
small: 480,
|
|
45
|
+
};
|
|
46
|
+
const names = getAllBreakpointNames(customBreakpoints);
|
|
47
|
+
expect(names).toEqual(['base', 'large', 'medium', 'small']);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('should sort custom breakpoints correctly', () => {
|
|
51
|
+
const customBreakpoints: BreakpointConfig = {
|
|
52
|
+
xs: 320,
|
|
53
|
+
xl: 1920,
|
|
54
|
+
md: 768,
|
|
55
|
+
sm: 480,
|
|
56
|
+
lg: 1280,
|
|
57
|
+
};
|
|
58
|
+
const names = getAllBreakpointNames(customBreakpoints);
|
|
59
|
+
expect(names).toEqual(['base', 'xl', 'lg', 'md', 'sm', 'xs']);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('should handle empty breakpoints config', () => {
|
|
63
|
+
const names = getAllBreakpointNames({});
|
|
64
|
+
expect(names).toEqual(['base']);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('should handle single breakpoint', () => {
|
|
68
|
+
const names = getAllBreakpointNames({ mobile: 640 });
|
|
69
|
+
expect(names).toEqual(['base', 'mobile']);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('should handle breakpoints with same values', () => {
|
|
73
|
+
const customBreakpoints: BreakpointConfig = {
|
|
74
|
+
a: 1024,
|
|
75
|
+
b: 1024,
|
|
76
|
+
c: 540,
|
|
77
|
+
};
|
|
78
|
+
const names = getAllBreakpointNames(customBreakpoints);
|
|
79
|
+
expect(names).toContain('base');
|
|
80
|
+
expect(names).toContain('a');
|
|
81
|
+
expect(names).toContain('b');
|
|
82
|
+
expect(names).toContain('c');
|
|
83
|
+
expect(names.length).toBe(4);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('getBreakpointName', () => {
|
|
88
|
+
test('should return base for large viewport', () => {
|
|
89
|
+
expect(getBreakpointName(1920)).toBe('base');
|
|
90
|
+
expect(getBreakpointName(1440)).toBe('base');
|
|
91
|
+
expect(getBreakpointName(1200)).toBe('base');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('should return tablet for tablet viewport', () => {
|
|
95
|
+
expect(getBreakpointName(1024)).toBe('tablet');
|
|
96
|
+
expect(getBreakpointName(1000)).toBe('tablet');
|
|
97
|
+
expect(getBreakpointName(768)).toBe('tablet');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('should return mobile for mobile viewport', () => {
|
|
101
|
+
expect(getBreakpointName(540)).toBe('mobile');
|
|
102
|
+
expect(getBreakpointName(480)).toBe('mobile');
|
|
103
|
+
expect(getBreakpointName(320)).toBe('mobile');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('should handle edge cases at breakpoint boundaries', () => {
|
|
107
|
+
// At exactly the breakpoint value
|
|
108
|
+
expect(getBreakpointName(1024)).toBe('tablet');
|
|
109
|
+
expect(getBreakpointName(540)).toBe('mobile');
|
|
110
|
+
|
|
111
|
+
// Just above breakpoint
|
|
112
|
+
expect(getBreakpointName(1025)).toBe('base');
|
|
113
|
+
expect(getBreakpointName(541)).toBe('tablet');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('should work with custom breakpoints', () => {
|
|
117
|
+
const customBreakpoints: BreakpointConfig = {
|
|
118
|
+
large: 1440,
|
|
119
|
+
medium: 960,
|
|
120
|
+
small: 480,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
expect(getBreakpointName(1920, customBreakpoints)).toBe('base');
|
|
124
|
+
expect(getBreakpointName(1440, customBreakpoints)).toBe('large');
|
|
125
|
+
expect(getBreakpointName(1200, customBreakpoints)).toBe('large');
|
|
126
|
+
expect(getBreakpointName(960, customBreakpoints)).toBe('medium');
|
|
127
|
+
expect(getBreakpointName(800, customBreakpoints)).toBe('medium');
|
|
128
|
+
expect(getBreakpointName(480, customBreakpoints)).toBe('small');
|
|
129
|
+
expect(getBreakpointName(320, customBreakpoints)).toBe('small');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('should handle empty breakpoints config', () => {
|
|
133
|
+
expect(getBreakpointName(1024, {})).toBe('base');
|
|
134
|
+
expect(getBreakpointName(320, {})).toBe('base');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('should handle very small viewport', () => {
|
|
138
|
+
expect(getBreakpointName(1)).toBe('mobile');
|
|
139
|
+
expect(getBreakpointName(100)).toBe('mobile');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('should handle very large viewport', () => {
|
|
143
|
+
expect(getBreakpointName(3840)).toBe('base');
|
|
144
|
+
expect(getBreakpointName(10000)).toBe('base');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('should handle zero viewport', () => {
|
|
148
|
+
expect(getBreakpointName(0)).toBe('mobile');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('should find correct breakpoint when multiple are similar', () => {
|
|
152
|
+
const customBreakpoints: BreakpointConfig = {
|
|
153
|
+
a: 1000,
|
|
154
|
+
b: 999,
|
|
155
|
+
c: 500,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
expect(getBreakpointName(1001, customBreakpoints)).toBe('base');
|
|
159
|
+
expect(getBreakpointName(1000, customBreakpoints)).toBe('a');
|
|
160
|
+
expect(getBreakpointName(999, customBreakpoints)).toBe('b');
|
|
161
|
+
expect(getBreakpointName(998, customBreakpoints)).toBe('b');
|
|
162
|
+
expect(getBreakpointName(500, customBreakpoints)).toBe('c');
|
|
163
|
+
expect(getBreakpointName(499, customBreakpoints)).toBe('c');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Breakpoint Configuration
|
|
3
|
+
* Defines breakpoints for responsive design
|
|
4
|
+
* Supports dynamic breakpoints from project.config.json
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_BREAKPOINTS = {
|
|
8
|
+
tablet: 1024,
|
|
9
|
+
mobile: 540,
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
// BreakpointConfig now supports any breakpoint names from config
|
|
13
|
+
export type BreakpointConfig = Record<string, number>;
|
|
14
|
+
|
|
15
|
+
// BreakpointName is now a string to support dynamic breakpoints
|
|
16
|
+
export type BreakpointName = string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get all breakpoint names from the breakpoint configuration
|
|
20
|
+
* Always includes 'base' plus all keys from the config
|
|
21
|
+
* Order: base (desktop) -> breakpoints sorted by value descending (largest to smallest viewport)
|
|
22
|
+
*/
|
|
23
|
+
export function getAllBreakpointNames(
|
|
24
|
+
breakpoints: BreakpointConfig = DEFAULT_BREAKPOINTS
|
|
25
|
+
): BreakpointName[] {
|
|
26
|
+
// Base is always included first
|
|
27
|
+
const names: BreakpointName[] = ['base'];
|
|
28
|
+
|
|
29
|
+
// Get all breakpoint names from config and sort by value (descending)
|
|
30
|
+
// This ensures proper order: largest viewport first
|
|
31
|
+
const breakpointEntries = Object.entries(breakpoints);
|
|
32
|
+
breakpointEntries.sort((a, b) => b[1] - a[1]); // Sort descending by value
|
|
33
|
+
|
|
34
|
+
// Add breakpoint names in sorted order
|
|
35
|
+
for (const [name] of breakpointEntries) {
|
|
36
|
+
names.push(name);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return names;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get active breakpoint name based on viewport width
|
|
44
|
+
* Returns the smallest breakpoint that the viewport width is less than or equal to
|
|
45
|
+
* If viewport is larger than all breakpoints, returns 'base'
|
|
46
|
+
*/
|
|
47
|
+
export function getBreakpointName(
|
|
48
|
+
viewportWidth: number,
|
|
49
|
+
breakpoints: BreakpointConfig = DEFAULT_BREAKPOINTS
|
|
50
|
+
): BreakpointName {
|
|
51
|
+
// Sort breakpoints by value (ascending) to find the smallest one that matches
|
|
52
|
+
const breakpointEntries = Object.entries(breakpoints);
|
|
53
|
+
breakpointEntries.sort((a, b) => a[1] - b[1]); // Sort ascending by value
|
|
54
|
+
|
|
55
|
+
// Find the smallest breakpoint that viewport width is <= to
|
|
56
|
+
for (const [name, value] of breakpointEntries) {
|
|
57
|
+
if (viewportWidth <= value) {
|
|
58
|
+
return name;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// If viewport is larger than all breakpoints, return 'base'
|
|
63
|
+
return 'base';
|
|
64
|
+
}
|
|
65
|
+
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { isColorProperty, getColorProperties } from './colorProperties';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* colorProperties Tests
|
|
6
|
+
* Tests color property identification and utilities
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
describe('colorProperties', () => {
|
|
10
|
+
describe('isColorProperty', () => {
|
|
11
|
+
test('should identify standard color properties', () => {
|
|
12
|
+
expect(isColorProperty('color')).toBe(true);
|
|
13
|
+
expect(isColorProperty('backgroundColor')).toBe(true);
|
|
14
|
+
expect(isColorProperty('borderColor')).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('should identify border color properties', () => {
|
|
18
|
+
expect(isColorProperty('borderTopColor')).toBe(true);
|
|
19
|
+
expect(isColorProperty('borderRightColor')).toBe(true);
|
|
20
|
+
expect(isColorProperty('borderBottomColor')).toBe(true);
|
|
21
|
+
expect(isColorProperty('borderLeftColor')).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('should identify SVG color properties', () => {
|
|
25
|
+
expect(isColorProperty('fill')).toBe(true);
|
|
26
|
+
expect(isColorProperty('stroke')).toBe(true);
|
|
27
|
+
expect(isColorProperty('floodColor')).toBe(true);
|
|
28
|
+
expect(isColorProperty('lightingColor')).toBe(true);
|
|
29
|
+
expect(isColorProperty('stopColor')).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should identify misc color properties', () => {
|
|
33
|
+
expect(isColorProperty('outlineColor')).toBe(true);
|
|
34
|
+
expect(isColorProperty('textDecorationColor')).toBe(true);
|
|
35
|
+
expect(isColorProperty('caretColor')).toBe(true);
|
|
36
|
+
expect(isColorProperty('columnRuleColor')).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('should return false for non-color properties', () => {
|
|
40
|
+
expect(isColorProperty('width')).toBe(false);
|
|
41
|
+
expect(isColorProperty('height')).toBe(false);
|
|
42
|
+
expect(isColorProperty('fontSize')).toBe(false);
|
|
43
|
+
expect(isColorProperty('padding')).toBe(false);
|
|
44
|
+
expect(isColorProperty('margin')).toBe(false);
|
|
45
|
+
expect(isColorProperty('display')).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('should be case-sensitive', () => {
|
|
49
|
+
expect(isColorProperty('Color')).toBe(false);
|
|
50
|
+
expect(isColorProperty('COLOR')).toBe(false);
|
|
51
|
+
expect(isColorProperty('BackgroundColor')).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('should return false for empty string', () => {
|
|
55
|
+
expect(isColorProperty('')).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('should return false for undefined-like values', () => {
|
|
59
|
+
expect(isColorProperty('undefined')).toBe(false);
|
|
60
|
+
expect(isColorProperty('null')).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('getColorProperties', () => {
|
|
65
|
+
test('should return an array of color properties', () => {
|
|
66
|
+
const properties = getColorProperties();
|
|
67
|
+
expect(Array.isArray(properties)).toBe(true);
|
|
68
|
+
expect(properties.length).toBeGreaterThan(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('should include standard color properties', () => {
|
|
72
|
+
const properties = getColorProperties();
|
|
73
|
+
expect(properties).toContain('color');
|
|
74
|
+
expect(properties).toContain('backgroundColor');
|
|
75
|
+
expect(properties).toContain('borderColor');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('should include all border color properties', () => {
|
|
79
|
+
const properties = getColorProperties();
|
|
80
|
+
expect(properties).toContain('borderTopColor');
|
|
81
|
+
expect(properties).toContain('borderRightColor');
|
|
82
|
+
expect(properties).toContain('borderBottomColor');
|
|
83
|
+
expect(properties).toContain('borderLeftColor');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('should include SVG color properties', () => {
|
|
87
|
+
const properties = getColorProperties();
|
|
88
|
+
expect(properties).toContain('fill');
|
|
89
|
+
expect(properties).toContain('stroke');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('should return a new array each time', () => {
|
|
93
|
+
const properties1 = getColorProperties();
|
|
94
|
+
const properties2 = getColorProperties();
|
|
95
|
+
expect(properties1).not.toBe(properties2);
|
|
96
|
+
expect(properties1).toEqual(properties2);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('should have expected number of properties', () => {
|
|
100
|
+
const properties = getColorProperties();
|
|
101
|
+
// Based on the implementation, there should be 16 color properties
|
|
102
|
+
expect(properties.length).toBe(16);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('should return unique properties', () => {
|
|
106
|
+
const properties = getColorProperties();
|
|
107
|
+
const uniqueProperties = [...new Set(properties)];
|
|
108
|
+
expect(properties.length).toBe(uniqueProperties.length);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color Property Utilities
|
|
3
|
+
* Helps identify and work with CSS properties that accept color values
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* List of CSS properties that accept color values
|
|
8
|
+
*/
|
|
9
|
+
const COLOR_PROPERTIES = new Set([
|
|
10
|
+
'color',
|
|
11
|
+
'backgroundColor',
|
|
12
|
+
'borderColor',
|
|
13
|
+
'borderTopColor',
|
|
14
|
+
'borderRightColor',
|
|
15
|
+
'borderBottomColor',
|
|
16
|
+
'borderLeftColor',
|
|
17
|
+
'outlineColor',
|
|
18
|
+
'fill',
|
|
19
|
+
'stroke',
|
|
20
|
+
'textDecorationColor',
|
|
21
|
+
'caretColor',
|
|
22
|
+
'columnRuleColor',
|
|
23
|
+
'floodColor',
|
|
24
|
+
'lightingColor',
|
|
25
|
+
'stopColor',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if a CSS property is a color property
|
|
30
|
+
*/
|
|
31
|
+
export function isColorProperty(propertyName: string): boolean {
|
|
32
|
+
return COLOR_PROPERTIES.has(propertyName);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get all color properties
|
|
37
|
+
*/
|
|
38
|
+
export function getColorProperties(): string[] {
|
|
39
|
+
return Array.from(COLOR_PROPERTIES);
|
|
40
|
+
}
|