@qwickapps/react-framework 1.3.5 → 1.4.1

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 (320) hide show
  1. package/README.md +1691 -2
  2. package/dist/__tests__/schemas/transformers/MockSerializableComponent.d.ts +66 -0
  3. package/dist/__tests__/schemas/transformers/MockSerializableComponent.d.ts.map +1 -0
  4. package/dist/components/ErrorBoundary.d.ts +7 -0
  5. package/dist/components/ErrorBoundary.d.ts.map +1 -1
  6. package/dist/components/Html.d.ts +28 -18
  7. package/dist/components/Html.d.ts.map +1 -1
  8. package/dist/components/Logo.d.ts +12 -35
  9. package/dist/components/Logo.d.ts.map +1 -1
  10. package/dist/components/Markdown.d.ts +18 -13
  11. package/dist/components/Markdown.d.ts.map +1 -1
  12. package/dist/components/QwickApp.d.ts +16 -3
  13. package/dist/components/QwickApp.d.ts.map +1 -1
  14. package/dist/components/QwickIcon.d.ts +23 -0
  15. package/dist/components/QwickIcon.d.ts.map +1 -0
  16. package/dist/components/SafeSpan.d.ts +12 -5
  17. package/dist/components/SafeSpan.d.ts.map +1 -1
  18. package/dist/components/Scaffold.d.ts.map +1 -1
  19. package/dist/components/base/ModelView.d.ts +101 -0
  20. package/dist/components/base/ModelView.d.ts.map +1 -0
  21. package/dist/components/base/index.d.ts +11 -0
  22. package/dist/components/base/index.d.ts.map +1 -0
  23. package/dist/components/blocks/Article.d.ts +12 -2
  24. package/dist/components/blocks/Article.d.ts.map +1 -1
  25. package/dist/components/blocks/Code.d.ts +13 -2
  26. package/dist/components/blocks/Code.d.ts.map +1 -1
  27. package/dist/components/blocks/CoverImageHeader.d.ts.map +1 -1
  28. package/dist/components/blocks/FeatureCard.d.ts.map +1 -1
  29. package/dist/components/blocks/FeatureGrid.d.ts.map +1 -1
  30. package/dist/components/blocks/Footer.d.ts.map +1 -1
  31. package/dist/components/blocks/HeroBlock.d.ts +27 -13
  32. package/dist/components/blocks/HeroBlock.d.ts.map +1 -1
  33. package/dist/components/blocks/Image.d.ts +41 -0
  34. package/dist/components/blocks/Image.d.ts.map +1 -0
  35. package/dist/components/blocks/PageBannerHeader.d.ts.map +1 -1
  36. package/dist/components/blocks/Section.d.ts +16 -2
  37. package/dist/components/blocks/Section.d.ts.map +1 -1
  38. package/dist/components/blocks/Text.d.ts +41 -0
  39. package/dist/components/blocks/Text.d.ts.map +1 -0
  40. package/dist/components/blocks/index.d.ts +4 -0
  41. package/dist/components/blocks/index.d.ts.map +1 -1
  42. package/dist/components/buttons/Button.d.ts +23 -7
  43. package/dist/components/buttons/Button.d.ts.map +1 -1
  44. package/dist/components/forms/FormBlock.d.ts +19 -13
  45. package/dist/components/forms/FormBlock.d.ts.map +1 -1
  46. package/dist/components/index.d.ts +4 -0
  47. package/dist/components/index.d.ts.map +1 -1
  48. package/dist/components/input/ChoiceInputField.d.ts +17 -11
  49. package/dist/components/input/ChoiceInputField.d.ts.map +1 -1
  50. package/dist/components/input/HtmlInputField.d.ts +17 -11
  51. package/dist/components/input/HtmlInputField.d.ts.map +1 -1
  52. package/dist/components/input/SelectInputField.d.ts +16 -10
  53. package/dist/components/input/SelectInputField.d.ts.map +1 -1
  54. package/dist/components/input/SwitchInputField.d.ts +16 -10
  55. package/dist/components/input/SwitchInputField.d.ts.map +1 -1
  56. package/dist/components/input/TextField.d.ts.map +1 -1
  57. package/dist/components/input/TextInputField.d.ts +16 -11
  58. package/dist/components/input/TextInputField.d.ts.map +1 -1
  59. package/dist/components/layout/GridCell.d.ts +23 -6
  60. package/dist/components/layout/GridCell.d.ts.map +1 -1
  61. package/dist/components/layout/GridLayout.d.ts +24 -23
  62. package/dist/components/layout/GridLayout.d.ts.map +1 -1
  63. package/dist/components/pages/FormPage.d.ts.map +1 -1
  64. package/dist/components/pages/Page.d.ts +49 -87
  65. package/dist/components/pages/Page.d.ts.map +1 -1
  66. package/dist/components/pages/index.d.ts +2 -2
  67. package/dist/components/pages/index.d.ts.map +1 -1
  68. package/dist/config/AppConfig.d.ts +49 -0
  69. package/dist/config/AppConfig.d.ts.map +1 -0
  70. package/dist/config/AppConfigBuilder.d.ts +75 -0
  71. package/dist/config/AppConfigBuilder.d.ts.map +1 -0
  72. package/dist/config/index.d.ts +13 -0
  73. package/dist/config/index.d.ts.map +1 -0
  74. package/dist/config/types.d.ts +130 -0
  75. package/dist/config/types.d.ts.map +1 -0
  76. package/dist/config.d.ts +15 -0
  77. package/dist/config.d.ts.map +1 -0
  78. package/dist/config.esm.js +451 -0
  79. package/dist/config.js +455 -0
  80. package/dist/contexts/PrintModeContext.d.ts +27 -0
  81. package/dist/contexts/PrintModeContext.d.ts.map +1 -0
  82. package/dist/contexts/QwickAppContext.d.ts +2 -2
  83. package/dist/contexts/QwickAppContext.d.ts.map +1 -1
  84. package/dist/contexts/index.d.ts +2 -0
  85. package/dist/contexts/index.d.ts.map +1 -1
  86. package/dist/hooks/index.d.ts +2 -0
  87. package/dist/hooks/index.d.ts.map +1 -1
  88. package/dist/hooks/usePrintMode.d.ts +39 -0
  89. package/dist/hooks/usePrintMode.d.ts.map +1 -0
  90. package/dist/index.css +1 -1
  91. package/dist/index.d.ts +1 -0
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.esm.css +1 -1
  94. package/dist/index.esm.js +10951 -6238
  95. package/dist/index.js +11014 -6287
  96. package/dist/schemas/CodeSchema.d.ts +2 -1
  97. package/dist/schemas/CodeSchema.d.ts.map +1 -1
  98. package/dist/schemas/CollapsibleLayoutSchema.d.ts +2 -1
  99. package/dist/schemas/CollapsibleLayoutSchema.d.ts.map +1 -1
  100. package/dist/schemas/ContentSchema.d.ts +2 -1
  101. package/dist/schemas/ContentSchema.d.ts.map +1 -1
  102. package/dist/schemas/GridCellSchema.d.ts +25 -0
  103. package/dist/schemas/GridCellSchema.d.ts.map +1 -0
  104. package/dist/schemas/GridLayoutSchema.d.ts +23 -0
  105. package/dist/schemas/GridLayoutSchema.d.ts.map +1 -0
  106. package/dist/schemas/HtmlSchema.d.ts +14 -0
  107. package/dist/schemas/HtmlSchema.d.ts.map +1 -0
  108. package/dist/schemas/ImageSchema.d.ts +32 -0
  109. package/dist/schemas/ImageSchema.d.ts.map +1 -0
  110. package/dist/schemas/LogoSchema.d.ts +35 -0
  111. package/dist/schemas/LogoSchema.d.ts.map +1 -0
  112. package/dist/schemas/MarkdownSchema.d.ts +14 -0
  113. package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
  114. package/dist/schemas/PageTemplateSchema.d.ts +31 -0
  115. package/dist/schemas/PageTemplateSchema.d.ts.map +1 -0
  116. package/dist/schemas/PrintConfigSchema.d.ts +31 -0
  117. package/dist/schemas/PrintConfigSchema.d.ts.map +1 -0
  118. package/dist/schemas/SectionSchema.d.ts +2 -1
  119. package/dist/schemas/SectionSchema.d.ts.map +1 -1
  120. package/dist/schemas/TextSchema.d.ts +37 -0
  121. package/dist/schemas/TextSchema.d.ts.map +1 -0
  122. package/dist/schemas/ViewModelSchema.d.ts +23 -0
  123. package/dist/schemas/ViewModelSchema.d.ts.map +1 -0
  124. package/dist/schemas/index.d.ts +15 -1
  125. package/dist/schemas/index.d.ts.map +1 -1
  126. package/dist/schemas/transformers/ComponentTransformer.d.ts +116 -0
  127. package/dist/schemas/transformers/ComponentTransformer.d.ts.map +1 -0
  128. package/dist/schemas/transformers/ReactNodeTransformer.d.ts +53 -0
  129. package/dist/schemas/transformers/ReactNodeTransformer.d.ts.map +1 -0
  130. package/dist/schemas/transformers/__tests__/MockSerializableComponent.d.ts +66 -0
  131. package/dist/schemas/transformers/__tests__/MockSerializableComponent.d.ts.map +1 -0
  132. package/dist/schemas/transformers/registry.d.ts +15 -0
  133. package/dist/schemas/transformers/registry.d.ts.map +1 -0
  134. package/dist/schemas/types/Serializable.d.ts +46 -0
  135. package/dist/schemas/types/Serializable.d.ts.map +1 -0
  136. package/dist/utils/htmlTransform.d.ts.map +1 -1
  137. package/dist/utils/reactUtils.d.ts +12 -3
  138. package/dist/utils/reactUtils.d.ts.map +1 -1
  139. package/package.json +17 -3
  140. package/src/{components/__tests__ → __tests__/components}/AccessibilityProvider.test.tsx +1 -1
  141. package/src/{components/__tests__ → __tests__/components}/Article.test.tsx +1 -1
  142. package/src/{components/__tests__ → __tests__/components}/Breadcrumbs.test.tsx +1 -1
  143. package/src/{components/__tests__ → __tests__/components}/Button.test.tsx +1 -1
  144. package/src/{components/__tests__ → __tests__/components}/CardListGrid.test.tsx +2 -2
  145. package/src/{components/__tests__ → __tests__/components}/ChoiceInputField.test.tsx +1 -1
  146. package/src/{components/__tests__ → __tests__/components}/Code.test.tsx +1 -1
  147. package/src/{components/__tests__ → __tests__/components}/Content.integration.test.tsx +1 -1
  148. package/src/{components/__tests__ → __tests__/components}/Content.test.tsx +1 -1
  149. package/src/{components/__tests__ → __tests__/components}/CoverImageHeader.test.tsx +2 -2
  150. package/src/{components/__tests__ → __tests__/components}/ErrorBoundary.test.tsx +1 -1
  151. package/src/{components/__tests__ → __tests__/components}/FeatureCard.integration.test.tsx +2 -2
  152. package/src/{components/__tests__ → __tests__/components}/FeatureGrid.integration.test.tsx +2 -2
  153. package/src/{components/__tests__ → __tests__/components}/FeatureGrid.test.tsx +2 -2
  154. package/src/{components/__tests__ → __tests__/components}/Footer.test.tsx +4 -4
  155. package/src/{components/__tests__ → __tests__/components}/FormBlock.test.tsx +1 -1
  156. package/src/{components/__tests__ → __tests__/components}/HeroBlock.integration.test.tsx +2 -2
  157. package/src/{components/__tests__ → __tests__/components}/HeroBlock.test.tsx +233 -7
  158. package/src/{components/__tests__ → __tests__/components}/Html.test.tsx +11 -2
  159. package/src/{components/__tests__ → __tests__/components}/HtmlInputField.test.tsx +3 -3
  160. package/src/__tests__/components/Logo.test.js +3 -3
  161. package/src/{components/__tests__ → __tests__/components}/Markdown.test.tsx +1 -1
  162. package/src/{components/__tests__ → __tests__/components}/PageBannerHeader.test.tsx +3 -3
  163. package/src/{components/__tests__ → __tests__/components}/PaletteSwitcher.test.tsx +3 -3
  164. package/src/{components/__tests__ → __tests__/components}/ProductCard.test.tsx +4 -4
  165. package/src/{components/__tests__ → __tests__/components}/SafeSpan.integration.test.tsx +2 -2
  166. package/src/{components/__tests__ → __tests__/components}/SafeSpan.simple.test.tsx +1 -1
  167. package/src/{components/__tests__ → __tests__/components}/SafeSpan.test.tsx +1 -1
  168. package/src/{components/__tests__ → __tests__/components}/Section.integration.test.tsx +1 -1
  169. package/src/{components/__tests__ → __tests__/components}/Section.test.tsx +1 -1
  170. package/src/{components/__tests__ → __tests__/components}/SelectInputField.test.tsx +1 -1
  171. package/src/{components/__tests__ → __tests__/components}/TextInputField.test.tsx +3 -3
  172. package/src/{components/__tests__ → __tests__/components}/ThemeSwitcher.test.tsx +3 -3
  173. package/src/__tests__/components/base/ModelView.test.tsx +220 -0
  174. package/src/__tests__/components/blocks/Code.performance.test.tsx +625 -0
  175. package/src/__tests__/components/blocks/Code.serialization.test.tsx +507 -0
  176. package/src/__tests__/components/blocks/HeroBlock.serialization.test.tsx +414 -0
  177. package/src/__tests__/components/blocks/Image.serialization.test.tsx +257 -0
  178. package/src/__tests__/components/blocks/Section.serialization.test.tsx +553 -0
  179. package/src/__tests__/components/blocks/Text.performance.test.tsx +442 -0
  180. package/src/__tests__/components/blocks/Text.serialization.test.tsx +491 -0
  181. package/src/__tests__/components/buttons/Button.serialization.test.tsx +443 -0
  182. package/src/__tests__/components/input/FormComponents.serialization.test.tsx +482 -0
  183. package/src/__tests__/components/input/SelectInputField.serialization.test.tsx +439 -0
  184. package/src/__tests__/components/input/TextInputField.serialization.test.tsx +359 -0
  185. package/src/{components/layout/CollapsibleLayout/__tests__ → __tests__/components/layout}/CollapsibleLayout.test.tsx +4 -4
  186. package/src/__tests__/components/layout/GridCell.serialization.test.tsx +403 -0
  187. package/src/__tests__/components/layout/GridLayout.serialization.test.tsx +311 -0
  188. package/src/__tests__/hooks/usePrintMode.test.ts +89 -0
  189. package/src/__tests__/schemas/PageTemplateSchema.test.ts +161 -0
  190. package/src/__tests__/schemas/PrintConfigSchema.test.ts +127 -0
  191. package/src/__tests__/schemas/ViewModelSchema.test.ts +80 -0
  192. package/src/__tests__/schemas/transformers/ComponentSerializationPatterns.test.tsx +602 -0
  193. package/src/__tests__/schemas/transformers/ComponentTransformer.htmlPatterns.test.ts +301 -0
  194. package/src/__tests__/schemas/transformers/ComponentTransformer.test.ts +521 -0
  195. package/src/__tests__/schemas/transformers/CrossBrowserCompatibility.test.ts +586 -0
  196. package/src/__tests__/schemas/transformers/MockSerializableComponent.ts +103 -0
  197. package/src/__tests__/schemas/transformers/RealWorldScenarios.test.tsx +1165 -0
  198. package/src/__tests__/schemas/transformers/SerializationErrorHandling.test.ts +602 -0
  199. package/src/__tests__/schemas/transformers/SerializationIntegration.test.tsx +691 -0
  200. package/src/__tests__/schemas/transformers/SerializationPerformance.test.ts +460 -0
  201. package/src/__tests__/schemas/transformers/TestAutomation.test.ts +597 -0
  202. package/src/{utils/__tests__ → __tests__/utils}/nested-dom-fix.test.tsx +1 -1
  203. package/src/components/ErrorBoundary.tsx +8 -8
  204. package/src/components/Html.tsx +147 -44
  205. package/src/components/Logo.tsx +198 -100
  206. package/src/components/Markdown.tsx +125 -16
  207. package/src/components/QwickApp.tsx +64 -31
  208. package/src/components/QwickIcon.tsx +59 -0
  209. package/src/components/SafeSpan.tsx +65 -10
  210. package/src/components/Scaffold.tsx +2 -8
  211. package/src/components/base/ModelView.tsx +199 -0
  212. package/src/components/base/index.ts +11 -0
  213. package/src/components/blocks/Article.tsx +57 -18
  214. package/src/components/blocks/Code.md +529 -0
  215. package/src/components/blocks/Code.tsx +102 -15
  216. package/src/components/blocks/CoverImageHeader.tsx +9 -4
  217. package/src/components/blocks/FeatureCard.tsx +1 -2
  218. package/src/components/blocks/FeatureGrid.tsx +19 -1
  219. package/src/components/blocks/Footer.tsx +13 -1
  220. package/src/components/blocks/HeroBlock.tsx +87 -20
  221. package/src/components/blocks/Image.tsx +395 -0
  222. package/src/components/blocks/PageBannerHeader.tsx +14 -12
  223. package/src/components/blocks/ProductCard.tsx +1 -1
  224. package/src/components/blocks/Section.tsx +113 -8
  225. package/src/components/blocks/Text.tsx +285 -0
  226. package/src/components/blocks/index.ts +4 -0
  227. package/src/components/buttons/Button.tsx +184 -15
  228. package/src/components/forms/FormBlock.tsx +70 -17
  229. package/src/components/index.ts +5 -0
  230. package/src/components/input/ChoiceInputField.tsx +48 -18
  231. package/src/components/input/HtmlInputField.tsx +48 -18
  232. package/src/components/input/SelectInputField.tsx +48 -16
  233. package/src/components/input/SwitchInputField.tsx +48 -17
  234. package/src/components/input/TextField.tsx +41 -1
  235. package/src/components/input/TextInputField.tsx +52 -18
  236. package/src/components/layout/GridCell.tsx +118 -9
  237. package/src/components/layout/GridLayout.tsx +125 -24
  238. package/src/components/pages/FormPage.tsx +0 -1
  239. package/src/components/pages/Page.css +304 -332
  240. package/src/components/pages/Page.tsx +307 -255
  241. package/src/components/pages/index.ts +2 -2
  242. package/src/config/AppConfig.ts +133 -0
  243. package/src/config/AppConfigBuilder.ts +421 -0
  244. package/src/config/__tests__/AppConfig.test.ts +385 -0
  245. package/src/config/__tests__/AppConfigBuilder.test.ts +432 -0
  246. package/src/config/index.ts +24 -0
  247. package/src/config/types.ts +170 -0
  248. package/src/config.ts +25 -0
  249. package/src/contexts/PrintModeContext.tsx +332 -0
  250. package/src/contexts/QwickAppContext.tsx +2 -2
  251. package/src/contexts/index.ts +2 -0
  252. package/src/hooks/index.ts +5 -1
  253. package/src/hooks/usePrintMode.ts +73 -0
  254. package/src/index.ts +3 -0
  255. package/src/schemas/CodeSchema.ts +3 -3
  256. package/src/schemas/CollapsibleLayoutSchema.ts +2 -1
  257. package/src/schemas/ContentSchema.ts +2 -1
  258. package/src/schemas/GridCellSchema.ts +164 -0
  259. package/src/schemas/GridLayoutSchema.ts +133 -0
  260. package/src/schemas/HtmlSchema.ts +47 -0
  261. package/src/schemas/ImageSchema.ts +235 -0
  262. package/src/schemas/LogoSchema.ts +241 -0
  263. package/src/schemas/MarkdownSchema.ts +47 -0
  264. package/src/schemas/PageTemplateSchema.ts +186 -0
  265. package/src/schemas/PrintConfigSchema.ts +207 -0
  266. package/src/schemas/README.md +661 -0
  267. package/src/schemas/SectionSchema.ts +2 -1
  268. package/src/schemas/TextSchema.ts +329 -0
  269. package/src/schemas/ViewModelSchema.ts +115 -0
  270. package/src/schemas/index.ts +21 -2
  271. package/src/schemas/transformers/ComponentTransformer.ts +403 -0
  272. package/src/schemas/transformers/ReactNodeTransformer.ts +236 -0
  273. package/src/schemas/transformers/registry.ts +72 -0
  274. package/src/schemas/types/Serializable.ts +51 -0
  275. package/src/stories/AccessibilityProvider.stories.tsx +253 -253
  276. package/src/stories/Article.stories.tsx +433 -433
  277. package/src/stories/Button.stories.tsx +1 -1
  278. package/src/stories/CardListGrid.stories.tsx +451 -451
  279. package/src/stories/ChoiceInputField.stories.tsx +503 -503
  280. package/src/stories/Code.stories.tsx +1 -1
  281. package/src/stories/CollapsibleLayout.stories.tsx +1414 -1414
  282. package/src/stories/Content.stories.tsx +393 -393
  283. package/src/stories/CoverImageHeader.stories.tsx +701 -701
  284. package/src/stories/DataBinding.advanced.stories.tsx +432 -432
  285. package/src/stories/DataProvider.stories.tsx +1192 -1192
  286. package/src/stories/FeatureCard.stories.tsx +557 -557
  287. package/src/stories/FeatureGrid.stories.tsx +594 -594
  288. package/src/stories/Footer.stories.tsx +640 -640
  289. package/src/stories/FormBlock.stories.tsx +760 -760
  290. package/src/stories/FormComponents.stories.tsx +349 -541
  291. package/src/stories/GridCell.stories.tsx +417 -0
  292. package/src/stories/GridLayout.stories.tsx +353 -0
  293. package/src/stories/HeroBlock.stories.tsx +862 -373
  294. package/src/stories/HtmlInputField.stories.tsx +474 -474
  295. package/src/stories/Image.stories.tsx +819 -0
  296. package/src/stories/Introduction.stories.tsx +667 -667
  297. package/src/stories/LayoutBlocks.stories.tsx +324 -324
  298. package/src/stories/Logo.stories.tsx +165 -6
  299. package/src/stories/Markdown.stories.tsx +137 -137
  300. package/src/stories/ModelView.stories.tsx +477 -0
  301. package/src/stories/Page.stories.tsx +688 -688
  302. package/src/stories/PageBannerHeader.stories.tsx +864 -864
  303. package/src/stories/PaletteSwitcher.stories.tsx +119 -119
  304. package/src/stories/ProductCard.stories.tsx +424 -424
  305. package/src/stories/QwickApp.stories.tsx +368 -368
  306. package/src/stories/ResponsiveMenu.stories.tsx +249 -249
  307. package/src/stories/SafeSpan.stories.tsx +531 -531
  308. package/src/stories/Section.stories.tsx +90 -2
  309. package/src/stories/SelectInputField.stories.tsx +524 -524
  310. package/src/stories/Text.stories.tsx +560 -0
  311. package/src/stories/TextInputField.stories.tsx +443 -443
  312. package/src/stories/ThemeSwitcher.stories.tsx +123 -123
  313. package/src/utils/htmlTransform.tsx +74 -53
  314. package/src/utils/reactUtils.tsx +57 -6
  315. package/dist/index.bundled.css +0 -12
  316. /package/src/{hooks/__tests__ → __tests__/hooks}/useDataBinding.test.tsx.disabled +0 -0
  317. /package/src/{schemas/__tests__ → __tests__/schemas}/builders.test.ts +0 -0
  318. /package/src/{utils/__tests__ → __tests__/utils}/createDataDrivenComponent.test.tsx.disabled +0 -0
  319. /package/src/{utils/__tests__ → __tests__/utils}/htmlTransform.test.tsx +0 -0
  320. /package/src/{utils/__tests__ → __tests__/utils}/optional-logging.test.ts +0 -0
@@ -0,0 +1,521 @@
1
+ /**
2
+ * ComponentTransformer Tests - Comprehensive test suite
3
+ *
4
+ * Tests the core component serialization system functionality
5
+ * including registration, serialization, deserialization, and error handling.
6
+ *
7
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
8
+ */
9
+
10
+ import React, { ReactElement } from 'react';
11
+ import { ComponentTransformer } from '../ComponentTransformer';
12
+ import { Serializable, SerializableConstructor } from '../../types/Serializable';
13
+ import {
14
+ MockSerializableComponentClass,
15
+ AlternativeMockComponentClass,
16
+ InvalidMockComponentClass
17
+ } from './MockSerializableComponent';
18
+
19
+ // Legacy mock components for testing (updated with self-declaration)
20
+ class MockButton implements Serializable {
21
+ static readonly tagName = 'Button';
22
+ static readonly version = '1.0.0';
23
+
24
+ constructor(public props: { label?: string; variant?: string; onClick?: () => void }) {}
25
+
26
+ static fromJson(jsonData: any): ReactElement {
27
+ return React.createElement('button', {
28
+ className: `btn-${jsonData.variant || 'default'}`,
29
+ onClick: jsonData.onClick
30
+ }, jsonData.label || 'Button');
31
+ }
32
+
33
+ toJson(): any {
34
+ return {
35
+ label: this.props.label,
36
+ variant: this.props.variant,
37
+ onClick: this.props.onClick ? 'function' : undefined
38
+ };
39
+ }
40
+ }
41
+
42
+ class MockCard implements Serializable {
43
+ static readonly tagName = 'Card';
44
+ static readonly version = '1.0.0';
45
+
46
+ constructor(public props: { title?: string; content?: string; children?: any }) {}
47
+
48
+ static fromJson(jsonData: any): ReactElement {
49
+ return React.createElement('div', {
50
+ className: 'card'
51
+ }, [
52
+ React.createElement('h3', { key: 'title' }, jsonData.title),
53
+ React.createElement('p', { key: 'content' }, jsonData.content)
54
+ ]);
55
+ }
56
+
57
+ toJson(): any {
58
+ return {
59
+ title: this.props.title,
60
+ content: this.props.content
61
+ };
62
+ }
63
+ }
64
+
65
+ describe('ComponentTransformer', () => {
66
+ beforeEach(() => {
67
+ // Clear registry before each test
68
+ ComponentTransformer.clearRegistry();
69
+ });
70
+
71
+ afterEach(() => {
72
+ // Clean up after each test
73
+ ComponentTransformer.clearRegistry();
74
+ });
75
+
76
+ describe('Component Registration', () => {
77
+ it('should register a component successfully', () => {
78
+ ComponentTransformer.registerComponent(MockButton as SerializableConstructor);
79
+
80
+ const registered = ComponentTransformer.getRegisteredComponents();
81
+ expect(registered).toContain('Button');
82
+ expect(registered).toHaveLength(1);
83
+ });
84
+
85
+ it('should register multiple components', () => {
86
+ ComponentTransformer.registerComponent(MockButton as SerializableConstructor);
87
+ ComponentTransformer.registerComponent(MockCard as SerializableConstructor);
88
+
89
+ const registered = ComponentTransformer.getRegisteredComponents();
90
+ expect(registered).toContain('Button');
91
+ expect(registered).toContain('Card');
92
+ expect(registered).toHaveLength(2);
93
+ });
94
+
95
+ it('should overwrite existing component registration with same tagName', () => {
96
+ ComponentTransformer.registerComponent(MockButton as SerializableConstructor);
97
+ ComponentTransformer.registerComponent(AlternativeMockComponentClass);
98
+
99
+ const registered = ComponentTransformer.getRegisteredComponents();
100
+ expect(registered).toContain('Button');
101
+ expect(registered).toContain('AlternativeComponent');
102
+ expect(registered).toHaveLength(2);
103
+ });
104
+
105
+ it('should clear registry completely', () => {
106
+ ComponentTransformer.registerComponent(MockButton as SerializableConstructor);
107
+ ComponentTransformer.registerComponent(MockCard as SerializableConstructor);
108
+
109
+ ComponentTransformer.clearRegistry();
110
+
111
+ const registered = ComponentTransformer.getRegisteredComponents();
112
+ expect(registered).toHaveLength(0);
113
+ });
114
+ });
115
+
116
+ describe('Serialization', () => {
117
+ beforeEach(() => {
118
+ ComponentTransformer.registerComponent(MockButton as SerializableConstructor);
119
+ });
120
+
121
+ it('should serialize null to null', () => {
122
+ const result = ComponentTransformer.serialize(null);
123
+ expect(result).toBe('null');
124
+ expect(JSON.parse(result)).toBeNull();
125
+ });
126
+
127
+ it('should serialize undefined to null', () => {
128
+ const result = ComponentTransformer.serialize(undefined);
129
+ expect(result).toBe('null');
130
+ expect(JSON.parse(result)).toBeNull();
131
+ });
132
+
133
+ it('should serialize primitive values', () => {
134
+ expect(JSON.parse(ComponentTransformer.serialize('hello'))).toBe('hello');
135
+ expect(JSON.parse(ComponentTransformer.serialize(42))).toBe(42);
136
+ expect(JSON.parse(ComponentTransformer.serialize(true))).toBe(true);
137
+ expect(JSON.parse(ComponentTransformer.serialize(false))).toBe(false);
138
+ });
139
+
140
+ it('should serialize array of primitives', () => {
141
+ const input = ['hello', 42, true, null];
142
+ const result = ComponentTransformer.serialize(input);
143
+ expect(JSON.parse(result)).toEqual(input);
144
+ });
145
+
146
+ it('should serialize array of mixed content', () => {
147
+ const input = ['text', 123, null, undefined];
148
+ const result = ComponentTransformer.serialize(input);
149
+ const parsed = JSON.parse(result);
150
+ expect(parsed).toEqual(['text', 123, null, null]);
151
+ });
152
+ });
153
+
154
+ describe('Deserialization', () => {
155
+ beforeEach(() => {
156
+ ComponentTransformer.registerComponent(MockButton as SerializableConstructor);
157
+ ComponentTransformer.registerComponent(MockCard as SerializableConstructor);
158
+ });
159
+
160
+ it('should deserialize null correctly', () => {
161
+ expect(ComponentTransformer.deserialize('null')).toBeNull();
162
+ expect(ComponentTransformer.deserialize(null)).toBeNull();
163
+ });
164
+
165
+ it('should deserialize primitive values from strings', () => {
166
+ expect(ComponentTransformer.deserialize('"hello"')).toBe('hello');
167
+ expect(ComponentTransformer.deserialize('42')).toBe(42);
168
+ expect(ComponentTransformer.deserialize('true')).toBe(true);
169
+ expect(ComponentTransformer.deserialize('false')).toBe(false);
170
+ });
171
+
172
+ it('should deserialize primitive values from objects', () => {
173
+ expect(ComponentTransformer.deserialize('hello')).toBe('hello');
174
+ expect(ComponentTransformer.deserialize(42)).toBe(42);
175
+ expect(ComponentTransformer.deserialize(true)).toBe(true);
176
+ expect(ComponentTransformer.deserialize(false)).toBe(false);
177
+ });
178
+
179
+ it('should deserialize arrays', () => {
180
+ const input = ['hello', 42, true, null];
181
+ const result = ComponentTransformer.deserialize(input);
182
+ expect(result).toEqual(input);
183
+ });
184
+
185
+ it('should deserialize registered component from object', () => {
186
+ const componentData = {
187
+ tag: 'Button',
188
+ version: '1.0.0',
189
+ data: {
190
+ label: 'Click Me',
191
+ variant: 'primary'
192
+ }
193
+ };
194
+
195
+ const result = ComponentTransformer.deserialize(componentData);
196
+ expect(React.isValidElement(result)).toBe(true);
197
+
198
+ const element = result as ReactElement;
199
+ expect(element.type).toBe('button');
200
+ expect(element.props.className).toBe('btn-primary');
201
+ expect(element.props.children).toBe('Click Me');
202
+ });
203
+
204
+ it('should deserialize registered component from JSON string', () => {
205
+ const componentData = {
206
+ tag: 'Card',
207
+ version: '1.0.0',
208
+ data: {
209
+ title: 'Test Card',
210
+ content: 'Test content'
211
+ }
212
+ };
213
+
214
+ const jsonString = JSON.stringify(componentData);
215
+ const result = ComponentTransformer.deserialize(jsonString);
216
+ expect(React.isValidElement(result)).toBe(true);
217
+
218
+ const element = result as ReactElement;
219
+ expect(element.type).toBe('div');
220
+ expect(element.props.className).toBe('card');
221
+ expect(Array.isArray(element.props.children)).toBe(true);
222
+ });
223
+
224
+ it('should handle array of components', () => {
225
+ const input = [
226
+ {
227
+ tag: 'Button',
228
+ version: '1.0.0',
229
+ data: { label: 'First Button' }
230
+ },
231
+ {
232
+ tag: 'Button',
233
+ version: '1.0.0',
234
+ data: { label: 'Second Button' }
235
+ }
236
+ ];
237
+
238
+ const result = ComponentTransformer.deserialize(input);
239
+ expect(Array.isArray(result)).toBe(true);
240
+
241
+ const elements = result as ReactElement[];
242
+ expect(elements).toHaveLength(2);
243
+ expect(React.isValidElement(elements[0])).toBe(true);
244
+ expect(React.isValidElement(elements[1])).toBe(true);
245
+ });
246
+ });
247
+
248
+ describe('Error Handling', () => {
249
+ it('should throw error for invalid JSON string', () => {
250
+ expect(() => {
251
+ ComponentTransformer.deserialize('invalid json {');
252
+ }).toThrow('Invalid JSON input');
253
+ });
254
+
255
+ it('should throw error for unknown component', () => {
256
+ const componentData = {
257
+ tag: 'UnknownComponent',
258
+ version: '1.0.0',
259
+ data: {}
260
+ };
261
+
262
+ // With the new fallback system, unknown components should not throw but use ReactNodeTransformer
263
+ const result = ComponentTransformer.deserialize(componentData);
264
+ // Should return a fallback React element rather than throwing
265
+ expect(result).toBeDefined();
266
+ });
267
+
268
+ it('should handle malformed component data gracefully', () => {
269
+ const malformedData = {
270
+ tag: 'Button',
271
+ // Missing version and data
272
+ };
273
+
274
+ // Register a button component first
275
+ ComponentTransformer.registerComponent(MockButton as SerializableConstructor);
276
+
277
+ expect(() => {
278
+ ComponentTransformer.deserialize(malformedData);
279
+ }).toThrow('Malformed component data: missing \'data\' property for component \'Button\'');
280
+ });
281
+ });
282
+
283
+ describe('String vs Object Input Handling', () => {
284
+ beforeEach(() => {
285
+ ComponentTransformer.registerComponent(MockButton as SerializableConstructor);
286
+ });
287
+
288
+ it('should handle string input correctly', () => {
289
+ const componentData = {
290
+ tag: 'Button',
291
+ version: '1.0.0',
292
+ data: { label: 'String Input Test' }
293
+ };
294
+
295
+ const jsonString = JSON.stringify(componentData);
296
+ const result = ComponentTransformer.deserialize(jsonString);
297
+
298
+ expect(React.isValidElement(result)).toBe(true);
299
+ const element = result as ReactElement;
300
+ expect(element.props.children).toBe('String Input Test');
301
+ });
302
+
303
+ it('should handle object input correctly', () => {
304
+ const componentData = {
305
+ tag: 'Button',
306
+ version: '1.0.0',
307
+ data: { label: 'Object Input Test' }
308
+ };
309
+
310
+ const result = ComponentTransformer.deserialize(componentData);
311
+
312
+ expect(React.isValidElement(result)).toBe(true);
313
+ const element = result as ReactElement;
314
+ expect(element.props.children).toBe('Object Input Test');
315
+ });
316
+
317
+ it('should handle array input correctly', () => {
318
+ const componentsData = [
319
+ {
320
+ tag: 'Button',
321
+ version: '1.0.0',
322
+ data: { label: 'Array Item 1' }
323
+ },
324
+ {
325
+ tag: 'Button',
326
+ version: '1.0.0',
327
+ data: { label: 'Array Item 2' }
328
+ }
329
+ ];
330
+
331
+ const result = ComponentTransformer.deserialize(componentsData);
332
+
333
+ expect(Array.isArray(result)).toBe(true);
334
+ const elements = result as ReactElement[];
335
+ expect(elements).toHaveLength(2);
336
+ expect(elements[0].props.children).toBe('Array Item 1');
337
+ expect(elements[1].props.children).toBe('Array Item 2');
338
+ });
339
+ });
340
+
341
+ describe('Nested Component Scenarios', () => {
342
+ beforeEach(() => {
343
+ ComponentTransformer.registerComponent(MockButton as SerializableConstructor);
344
+ ComponentTransformer.registerComponent(MockCard as SerializableConstructor);
345
+ });
346
+
347
+ it('should handle nested primitive content', () => {
348
+ const input = ['text', 42, ['nested', 'array'], { key: 'value' }];
349
+ const serialized = ComponentTransformer.serialize(input);
350
+ const deserialized = ComponentTransformer.deserialize(serialized);
351
+
352
+ expect(deserialized).toEqual(['text', 42, ['nested', 'array'], { key: 'value' }]);
353
+ });
354
+
355
+ it('should handle mixed content arrays', () => {
356
+ const input = [
357
+ 'plain text',
358
+ {
359
+ tag: 'Button',
360
+ version: '1.0.0',
361
+ data: { label: 'Embedded Button' }
362
+ },
363
+ 42,
364
+ null
365
+ ];
366
+
367
+ const result = ComponentTransformer.deserialize(input);
368
+ expect(Array.isArray(result)).toBe(true);
369
+
370
+ const elements = result as any[];
371
+ expect(elements[0]).toBe('plain text');
372
+ expect(React.isValidElement(elements[1])).toBe(true);
373
+ expect(elements[2]).toBe(42);
374
+ expect(elements[3]).toBeNull();
375
+ });
376
+ });
377
+
378
+ describe('Data Structure Requirements', () => {
379
+ beforeEach(() => {
380
+ ComponentTransformer.registerComponent(MockButton as SerializableConstructor);
381
+ });
382
+
383
+ it('should produce correct serialized structure format', () => {
384
+ // This test verifies the data structure requirement:
385
+ // { tag: "ComponentName", version: "1.0.0", data: {...} }
386
+
387
+ // Since we can't easily test serialization of actual React elements
388
+ // without a more complex setup, we'll test the deserialization format
389
+ const expectedStructure = {
390
+ tag: 'Button',
391
+ version: '1.0.0',
392
+ data: {
393
+ label: 'Test Button',
394
+ variant: 'primary'
395
+ }
396
+ };
397
+
398
+ const result = ComponentTransformer.deserialize(expectedStructure);
399
+ expect(React.isValidElement(result)).toBe(true);
400
+ });
401
+
402
+ it('should handle version information correctly', () => {
403
+ const componentWithVersion = {
404
+ tag: 'Button',
405
+ version: '2.1.0',
406
+ data: { label: 'Version Test' }
407
+ };
408
+
409
+ // Should not throw - version handling is delegated to component's fromJson
410
+ expect(() => {
411
+ ComponentTransformer.deserialize(componentWithVersion);
412
+ }).not.toThrow();
413
+ });
414
+ });
415
+
416
+ describe('New Architecture Features', () => {
417
+ it('should validate component self-declaration with tagName and version', () => {
418
+ ComponentTransformer.registerComponent(MockSerializableComponentClass);
419
+
420
+ const registered = ComponentTransformer.getRegisteredComponents();
421
+ expect(registered).toContain('MockComponent');
422
+ });
423
+
424
+ it('should throw error for components missing tagName', () => {
425
+ expect(() => {
426
+ ComponentTransformer.registerComponent(InvalidMockComponentClass);
427
+ }).toThrow("Component class must have a static 'tagName' property");
428
+ });
429
+
430
+ it('should use component declared version in serialization', () => {
431
+ ComponentTransformer.registerComponent(AlternativeMockComponentClass);
432
+
433
+ const componentData = {
434
+ tag: 'AlternativeComponent',
435
+ version: '2.1.0',
436
+ data: { label: 'Version Test', type: 'primary' }
437
+ };
438
+
439
+ const result = ComponentTransformer.deserialize(componentData);
440
+ expect(React.isValidElement(result)).toBe(true);
441
+ });
442
+
443
+ it('should handle unregistered React elements with ReactNodeTransformer fallback', () => {
444
+ const unregisteredElement = React.createElement('span', { className: 'test' }, 'Fallback Test');
445
+ const serialized = ComponentTransformer.serialize(unregisteredElement);
446
+ const parsed = JSON.parse(serialized);
447
+
448
+ // Should use __react_node__ tag for unregistered components
449
+ expect(parsed.tag).toBe('__react_node__');
450
+ expect(parsed.version).toBe('1.0.0');
451
+
452
+ const deserialized = ComponentTransformer.deserialize(parsed);
453
+ expect(deserialized).toBeDefined();
454
+ });
455
+
456
+ it('should handle mixed array of registered and unregistered components', () => {
457
+ ComponentTransformer.registerComponent(MockSerializableComponentClass);
458
+
459
+ const mixedArray = [
460
+ 'Text node',
461
+ {
462
+ tag: 'MockComponent',
463
+ version: '1.0.0',
464
+ data: { title: 'Registered Component' }
465
+ },
466
+ {
467
+ tag: '__react_node__',
468
+ version: '1.0.0',
469
+ data: {
470
+ type: 'react-element',
471
+ elementType: 'div',
472
+ props: { children: 'Unregistered Element' }
473
+ }
474
+ }
475
+ ];
476
+
477
+ const result = ComponentTransformer.deserialize(mixedArray);
478
+ expect(Array.isArray(result)).toBe(true);
479
+
480
+ const elements = result as any[];
481
+ expect(elements).toHaveLength(3);
482
+ expect(elements[0]).toBe('Text node');
483
+ expect(React.isValidElement(elements[1])).toBe(true);
484
+ expect(elements[2]).toBeDefined(); // Fallback content
485
+ });
486
+
487
+ it('should gracefully handle unknown registered components with fallback', () => {
488
+ const unknownComponent = {
489
+ tag: 'NonExistentComponent',
490
+ version: '1.0.0',
491
+ data: { some: 'data' }
492
+ };
493
+
494
+ // Should not throw, but use fallback
495
+ const result = ComponentTransformer.deserialize(unknownComponent);
496
+ expect(result).toBeDefined();
497
+ });
498
+
499
+ it('should preserve all functionality for correctly registered components', () => {
500
+ ComponentTransformer.registerComponent(MockSerializableComponentClass);
501
+
502
+ const componentData = {
503
+ tag: 'MockComponent',
504
+ version: '1.0.0',
505
+ data: {
506
+ title: 'Test Title',
507
+ content: 'Test Content',
508
+ variant: 'primary'
509
+ }
510
+ };
511
+
512
+ const result = ComponentTransformer.deserialize(componentData);
513
+ expect(React.isValidElement(result)).toBe(true);
514
+
515
+ const element = result as ReactElement;
516
+ expect(element.type).toBe('div');
517
+ expect(element.props.className).toBe('mock-component primary');
518
+ expect(element.props['data-testid']).toBe('mock-component');
519
+ });
520
+ });
521
+ });