meno-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/bin/cli.ts +281 -0
  2. package/build-static.ts +298 -0
  3. package/bunfig.toml +39 -0
  4. package/entries/client-router.tsx +111 -0
  5. package/entries/server-router.tsx +71 -0
  6. package/lib/client/ClientInitializer.test.ts +9 -0
  7. package/lib/client/ClientInitializer.test.ts.skip +92 -0
  8. package/lib/client/ClientInitializer.ts +60 -0
  9. package/lib/client/ErrorBoundary.test.tsx +595 -0
  10. package/lib/client/ErrorBoundary.tsx +230 -0
  11. package/lib/client/componentRegistry.test.ts +165 -0
  12. package/lib/client/componentRegistry.ts +18 -0
  13. package/lib/client/contexts/ThemeContext.tsx +73 -0
  14. package/lib/client/core/ComponentBuilder.test.ts +677 -0
  15. package/lib/client/core/ComponentBuilder.ts +660 -0
  16. package/lib/client/core/ComponentRenderer.test.tsx +176 -0
  17. package/lib/client/core/ComponentRenderer.tsx +83 -0
  18. package/lib/client/core/cmsTemplateProcessor.ts +129 -0
  19. package/lib/client/elementRegistry.ts +81 -0
  20. package/lib/client/hmr/HMRManager.tsx +179 -0
  21. package/lib/client/hmr/index.ts +5 -0
  22. package/lib/client/hmrWebSocket.test.ts +9 -0
  23. package/lib/client/hmrWebSocket.ts +250 -0
  24. package/lib/client/hooks/useColorVariables.test.ts +166 -0
  25. package/lib/client/hooks/useColorVariables.ts +249 -0
  26. package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
  27. package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
  28. package/lib/client/hydration/HydrationUtils.test.ts +154 -0
  29. package/lib/client/hydration/HydrationUtils.ts +35 -0
  30. package/lib/client/i18nConfigService.test.ts +74 -0
  31. package/lib/client/i18nConfigService.ts +78 -0
  32. package/lib/client/index.ts +56 -0
  33. package/lib/client/navigation.test.ts +441 -0
  34. package/lib/client/navigation.ts +23 -0
  35. package/lib/client/responsiveStyleResolver.test.ts +491 -0
  36. package/lib/client/responsiveStyleResolver.ts +184 -0
  37. package/lib/client/routing/RouteLoader.test.ts +635 -0
  38. package/lib/client/routing/RouteLoader.ts +347 -0
  39. package/lib/client/routing/Router.tsx +382 -0
  40. package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
  41. package/lib/client/scripts/ScriptExecutor.ts +171 -0
  42. package/lib/client/scripts/formHandler.ts +103 -0
  43. package/lib/client/styleProcessor.test.ts +126 -0
  44. package/lib/client/styleProcessor.ts +92 -0
  45. package/lib/client/styles/StyleInjector.test.ts +354 -0
  46. package/lib/client/styles/StyleInjector.ts +154 -0
  47. package/lib/client/templateEngine.test.ts +660 -0
  48. package/lib/client/templateEngine.ts +667 -0
  49. package/lib/client/theme.test.ts +173 -0
  50. package/lib/client/theme.ts +159 -0
  51. package/lib/client/utils/toast.ts +46 -0
  52. package/lib/server/createServer.ts +170 -0
  53. package/lib/server/cssGenerator.test.ts +172 -0
  54. package/lib/server/cssGenerator.ts +58 -0
  55. package/lib/server/fileWatcher.ts +134 -0
  56. package/lib/server/index.ts +55 -0
  57. package/lib/server/jsonLoader.test.ts +103 -0
  58. package/lib/server/jsonLoader.ts +350 -0
  59. package/lib/server/middleware/cors.test.ts +177 -0
  60. package/lib/server/middleware/cors.ts +69 -0
  61. package/lib/server/middleware/errorHandler.test.ts +208 -0
  62. package/lib/server/middleware/errorHandler.ts +63 -0
  63. package/lib/server/middleware/index.ts +9 -0
  64. package/lib/server/middleware/logger.test.ts +233 -0
  65. package/lib/server/middleware/logger.ts +99 -0
  66. package/lib/server/pageCache.test.ts +167 -0
  67. package/lib/server/pageCache.ts +97 -0
  68. package/lib/server/projectContext.ts +51 -0
  69. package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
  70. package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
  71. package/lib/server/providers/fileSystemPageProvider.ts +83 -0
  72. package/lib/server/routes/api/cms.test.ts +177 -0
  73. package/lib/server/routes/api/cms.ts +82 -0
  74. package/lib/server/routes/api/colors.ts +59 -0
  75. package/lib/server/routes/api/components.ts +70 -0
  76. package/lib/server/routes/api/config.test.ts +9 -0
  77. package/lib/server/routes/api/config.ts +28 -0
  78. package/lib/server/routes/api/core-routes.ts +182 -0
  79. package/lib/server/routes/api/functions.ts +170 -0
  80. package/lib/server/routes/api/index.ts +69 -0
  81. package/lib/server/routes/api/pages.ts +95 -0
  82. package/lib/server/routes/api/shared.test.ts +81 -0
  83. package/lib/server/routes/api/shared.ts +31 -0
  84. package/lib/server/routes/editor.test.ts +9 -0
  85. package/lib/server/routes/index.ts +104 -0
  86. package/lib/server/routes/pages.ts +161 -0
  87. package/lib/server/routes/static.ts +107 -0
  88. package/lib/server/services/ColorService.ts +193 -0
  89. package/lib/server/services/cmsService.test.ts +388 -0
  90. package/lib/server/services/cmsService.ts +296 -0
  91. package/lib/server/services/componentService.test.ts +276 -0
  92. package/lib/server/services/componentService.ts +346 -0
  93. package/lib/server/services/configService.ts +156 -0
  94. package/lib/server/services/fileWatcherService.ts +67 -0
  95. package/lib/server/services/index.ts +10 -0
  96. package/lib/server/services/pageService.test.ts +258 -0
  97. package/lib/server/services/pageService.ts +240 -0
  98. package/lib/server/ssrRenderer.test.ts +1005 -0
  99. package/lib/server/ssrRenderer.ts +878 -0
  100. package/lib/server/utilityClassGenerator.ts +11 -0
  101. package/lib/server/utils/index.ts +5 -0
  102. package/lib/server/utils/jsonLineMapper.test.ts +100 -0
  103. package/lib/server/utils/jsonLineMapper.ts +166 -0
  104. package/lib/server/validateStyleCoverage.test.ts +9 -0
  105. package/lib/server/validateStyleCoverage.ts +167 -0
  106. package/lib/server/websocketManager.test.ts +9 -0
  107. package/lib/server/websocketManager.ts +95 -0
  108. package/lib/shared/attributeNodeUtils.test.ts +152 -0
  109. package/lib/shared/attributeNodeUtils.ts +50 -0
  110. package/lib/shared/breakpoints.test.ts +166 -0
  111. package/lib/shared/breakpoints.ts +65 -0
  112. package/lib/shared/colorProperties.test.ts +111 -0
  113. package/lib/shared/colorProperties.ts +40 -0
  114. package/lib/shared/colorVariableUtils.test.ts +319 -0
  115. package/lib/shared/colorVariableUtils.ts +97 -0
  116. package/lib/shared/constants.test.ts +175 -0
  117. package/lib/shared/constants.ts +116 -0
  118. package/lib/shared/cssGeneration.ts +481 -0
  119. package/lib/shared/cssProperties.test.ts +252 -0
  120. package/lib/shared/cssProperties.ts +338 -0
  121. package/lib/shared/elementUtils.test.ts +245 -0
  122. package/lib/shared/elementUtils.ts +90 -0
  123. package/lib/shared/fontLoader.ts +97 -0
  124. package/lib/shared/i18n.test.ts +313 -0
  125. package/lib/shared/i18n.ts +286 -0
  126. package/lib/shared/index.ts +50 -0
  127. package/lib/shared/interfaces/contentProvider.test.ts +9 -0
  128. package/lib/shared/interfaces/contentProvider.ts +121 -0
  129. package/lib/shared/nodeUtils.test.ts +320 -0
  130. package/lib/shared/nodeUtils.ts +220 -0
  131. package/lib/shared/pathArrayUtils.test.ts +315 -0
  132. package/lib/shared/pathArrayUtils.ts +17 -0
  133. package/lib/shared/pathUtils.test.ts +260 -0
  134. package/lib/shared/pathUtils.ts +244 -0
  135. package/lib/shared/paths/Path.test.ts +74 -0
  136. package/lib/shared/paths/Path.ts +23 -0
  137. package/lib/shared/paths/PathConverter.test.ts +232 -0
  138. package/lib/shared/paths/PathConverter.ts +141 -0
  139. package/lib/shared/paths/PathUtils.ts +290 -0
  140. package/lib/shared/paths/PathValidator.test.ts +193 -0
  141. package/lib/shared/paths/PathValidator.ts +53 -0
  142. package/lib/shared/paths/index.ts +48 -0
  143. package/lib/shared/propResolver.test.ts +639 -0
  144. package/lib/shared/propResolver.ts +124 -0
  145. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
  146. package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
  147. package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
  148. package/lib/shared/registry/ClientRegistry.test.ts +26 -0
  149. package/lib/shared/registry/ClientRegistry.ts +15 -0
  150. package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
  151. package/lib/shared/registry/ComponentRegistry.ts +100 -0
  152. package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
  153. package/lib/shared/registry/NodeTypeManager.ts +94 -0
  154. package/lib/shared/registry/RegistryManager.test.ts +58 -0
  155. package/lib/shared/registry/RegistryManager.ts +60 -0
  156. package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
  157. package/lib/shared/registry/SSRRegistry.test.ts +26 -0
  158. package/lib/shared/registry/SSRRegistry.ts +15 -0
  159. package/lib/shared/registry/createNodeType.ts +175 -0
  160. package/lib/shared/registry/defineNodeType.ts +73 -0
  161. package/lib/shared/registry/fieldPresets.ts +109 -0
  162. package/lib/shared/registry/index.ts +50 -0
  163. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
  164. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
  165. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
  166. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
  167. package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
  168. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
  169. package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
  170. package/lib/shared/registry/nodeTypes/index.ts +75 -0
  171. package/lib/shared/responsiveScaling.test.ts +268 -0
  172. package/lib/shared/responsiveScaling.ts +194 -0
  173. package/lib/shared/responsiveStyleUtils.test.ts +300 -0
  174. package/lib/shared/responsiveStyleUtils.ts +139 -0
  175. package/lib/shared/slugTranslator.test.ts +325 -0
  176. package/lib/shared/slugTranslator.ts +177 -0
  177. package/lib/shared/styleNodeUtils.test.ts +132 -0
  178. package/lib/shared/styleNodeUtils.ts +102 -0
  179. package/lib/shared/styleUtils.test.ts +238 -0
  180. package/lib/shared/styleUtils.ts +63 -0
  181. package/lib/shared/themeDefaults.test.ts +113 -0
  182. package/lib/shared/themeDefaults.ts +103 -0
  183. package/lib/shared/tree/PathBuilder.ts +383 -0
  184. package/lib/shared/treePathUtils.test.ts +539 -0
  185. package/lib/shared/treePathUtils.ts +339 -0
  186. package/lib/shared/types/api.ts +58 -0
  187. package/lib/shared/types/cms.ts +95 -0
  188. package/lib/shared/types/colors.ts +45 -0
  189. package/lib/shared/types/components.ts +121 -0
  190. package/lib/shared/types/errors.test.ts +103 -0
  191. package/lib/shared/types/errors.ts +69 -0
  192. package/lib/shared/types/index.ts +96 -0
  193. package/lib/shared/types/nodes.ts +20 -0
  194. package/lib/shared/types/rendering.ts +61 -0
  195. package/lib/shared/types/styles.ts +38 -0
  196. package/lib/shared/types.ts +11 -0
  197. package/lib/shared/utilityClassConfig.ts +287 -0
  198. package/lib/shared/utilityClassMapper.test.ts +140 -0
  199. package/lib/shared/utilityClassMapper.ts +229 -0
  200. package/lib/shared/utils/fileUtils.test.ts +99 -0
  201. package/lib/shared/utils/fileUtils.ts +56 -0
  202. package/lib/shared/utils.test.ts +261 -0
  203. package/lib/shared/utils.ts +84 -0
  204. package/lib/shared/validation/index.ts +7 -0
  205. package/lib/shared/validation/propValidator.test.ts +178 -0
  206. package/lib/shared/validation/propValidator.ts +238 -0
  207. package/lib/shared/validation/schemas.test.ts +177 -0
  208. package/lib/shared/validation/schemas.ts +401 -0
  209. package/lib/shared/validation/validators.test.ts +109 -0
  210. package/lib/shared/validation/validators.ts +304 -0
  211. package/lib/test-utils/dom-setup.ts +55 -0
  212. package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
  213. package/lib/test-utils/factories/DomMockFactory.ts +487 -0
  214. package/lib/test-utils/factories/EventMockFactory.ts +244 -0
  215. package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
  216. package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
  217. package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
  218. package/lib/test-utils/factories/index.ts +11 -0
  219. package/lib/test-utils/fixtures.ts +134 -0
  220. package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
  221. package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
  222. package/lib/test-utils/helpers/index.ts +6 -0
  223. package/lib/test-utils/helpers.test.ts +73 -0
  224. package/lib/test-utils/helpers.ts +90 -0
  225. package/lib/test-utils/index.ts +17 -0
  226. package/lib/test-utils/mockFactories.ts +92 -0
  227. package/lib/test-utils/mocks.ts +341 -0
  228. package/package.json +38 -0
  229. package/templates/index-router.html +34 -0
  230. package/tsconfig.json +14 -0
  231. package/vite.config.ts +43 -0
@@ -0,0 +1,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
+ }