@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,80 @@
1
+ /**
2
+ * ViewModel Schema Tests
3
+ *
4
+ * Tests for ViewModelSchema functionality and validation
5
+ */
6
+
7
+ import { ViewModelSchema } from '../../schemas/ViewModelSchema';
8
+
9
+ describe('ViewModelSchema', () => {
10
+ it('should create a valid ViewModelSchema instance', () => {
11
+ const viewModel = new ViewModelSchema();
12
+ expect(viewModel).toBeInstanceOf(ViewModelSchema);
13
+ });
14
+
15
+ it('should have correct schema metadata', () => {
16
+ const schema = ViewModelSchema.getSchema();
17
+ expect(schema.name).toBe('ViewModel');
18
+ expect(schema.version).toBe('1.0.0');
19
+ });
20
+
21
+ it('should validate optional properties correctly', async () => {
22
+ const data = {
23
+ className: 'test-class',
24
+ id: 'test-id',
25
+ hidden: true,
26
+ 'aria-label': 'Test label',
27
+ 'data-testid': 'test-element'
28
+ };
29
+
30
+ const result = await ViewModelSchema.validate(data);
31
+ expect(result.isValid).toBe(true);
32
+ expect(result.errors).toHaveLength(0);
33
+ });
34
+
35
+ it('should validate style property as string', async () => {
36
+ const data = {
37
+ style: '{"color": "red", "margin": "10px"}'
38
+ };
39
+
40
+ const result = await ViewModelSchema.validate(data);
41
+ expect(result.isValid).toBe(true);
42
+ expect(result.errors).toHaveLength(0);
43
+ });
44
+
45
+ it('should handle empty instance without errors', async () => {
46
+ const data = {};
47
+
48
+ const result = await ViewModelSchema.validate(data);
49
+ expect(result.isValid).toBe(true);
50
+ expect(result.errors).toHaveLength(0);
51
+ });
52
+
53
+ it('should create instance with default values', () => {
54
+ const data = {
55
+ className: 'test-class',
56
+ id: 'test-id',
57
+ hidden: true
58
+ };
59
+
60
+ const viewModel = ViewModelSchema.createWithDefaults(data);
61
+ expect(viewModel.className).toBe('test-class');
62
+ expect(viewModel.id).toBe('test-id');
63
+ expect(viewModel.hidden).toBe(true);
64
+ });
65
+
66
+ it('should create instance from JSON data correctly', () => {
67
+ const jsonData = {
68
+ className: 'restored-class',
69
+ id: 'restored-id',
70
+ hidden: false,
71
+ 'aria-label': 'Restored label'
72
+ };
73
+
74
+ const viewModel = ViewModelSchema.createWithDefaults(jsonData);
75
+ expect(viewModel.className).toBe('restored-class');
76
+ expect(viewModel.id).toBe('restored-id');
77
+ expect(viewModel.hidden).toBe(false);
78
+ expect(viewModel['aria-label']).toBe('Restored label');
79
+ });
80
+ });
@@ -0,0 +1,602 @@
1
+ /**
2
+ * Component-Specific Serialization Test Patterns
3
+ *
4
+ * Standardized test patterns for validating serialization behavior
5
+ * of individual components implementing the Serializable interface.
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
+
14
+ /**
15
+ * Generic test pattern for validating component serialization
16
+ * This pattern should be used for all components implementing Serializable
17
+ */
18
+ export function testComponentSerialization<T extends Serializable>(
19
+ componentName: string,
20
+ ComponentClass: SerializableConstructor,
21
+ testCases: {
22
+ name: string;
23
+ props: any;
24
+ expectedData?: any;
25
+ validate?: (element: ReactElement, originalProps: any) => void;
26
+ shouldThrow?: boolean;
27
+ errorMessage?: string;
28
+ }[]
29
+ ) {
30
+ describe(`${componentName} Serialization`, () => {
31
+ beforeEach(() => {
32
+ ComponentTransformer.clearRegistry();
33
+ ComponentTransformer.registerComponent(componentName, ComponentClass);
34
+ });
35
+
36
+ afterEach(() => {
37
+ ComponentTransformer.clearRegistry();
38
+ });
39
+
40
+ testCases.forEach(({ name, props, expectedData, validate, shouldThrow, errorMessage }) => {
41
+ it(`should ${name}`, () => {
42
+ if (shouldThrow) {
43
+ expect(() => {
44
+ const componentData = {
45
+ tag: componentName,
46
+ version: '1.0.0',
47
+ data: props
48
+ };
49
+ ComponentTransformer.deserialize(componentData);
50
+ }).toThrow(errorMessage || '');
51
+ return;
52
+ }
53
+
54
+ const componentData = {
55
+ tag: componentName,
56
+ version: '1.0.0',
57
+ data: props
58
+ };
59
+
60
+ // Test deserialization
61
+ const result = ComponentTransformer.deserialize(componentData);
62
+ expect(React.isValidElement(result)).toBe(true);
63
+
64
+ const element = result as ReactElement;
65
+
66
+ // Test serialization roundtrip if no custom validation
67
+ if (!validate) {
68
+ const serialized = ComponentTransformer.serialize(element);
69
+ const parsed = JSON.parse(serialized);
70
+ expect(parsed.tag).toBe(componentName);
71
+ expect(parsed.version).toBe('1.0.0');
72
+
73
+ if (expectedData) {
74
+ expect(parsed.data).toEqual(expectedData);
75
+ }
76
+
77
+ // Verify roundtrip produces same result
78
+ const roundtrip = ComponentTransformer.deserialize(parsed);
79
+ expect(React.isValidElement(roundtrip)).toBe(true);
80
+ } else {
81
+ validate(element, props);
82
+ }
83
+ });
84
+ });
85
+
86
+ // Standard tests that every component should pass
87
+ it('should handle null props gracefully', () => {
88
+ const componentData = {
89
+ tag: componentName,
90
+ version: '1.0.0',
91
+ data: null
92
+ };
93
+
94
+ expect(() => {
95
+ const result = ComponentTransformer.deserialize(componentData);
96
+ expect(React.isValidElement(result)).toBe(true);
97
+ }).not.toThrow();
98
+ });
99
+
100
+ it('should handle empty props gracefully', () => {
101
+ const componentData = {
102
+ tag: componentName,
103
+ version: '1.0.0',
104
+ data: {}
105
+ };
106
+
107
+ expect(() => {
108
+ const result = ComponentTransformer.deserialize(componentData);
109
+ expect(React.isValidElement(result)).toBe(true);
110
+ }).not.toThrow();
111
+ });
112
+
113
+ it('should preserve all props through roundtrip', () => {
114
+ // Create a comprehensive props object
115
+ const complexProps = {
116
+ stringProp: 'test string',
117
+ numberProp: 42,
118
+ booleanProp: true,
119
+ arrayProp: ['item1', 'item2', 'item3'],
120
+ objectProp: { nested: 'value', deep: { deeper: 'deepValue' } },
121
+ nullProp: null,
122
+ undefinedProp: undefined
123
+ };
124
+
125
+ const componentData = {
126
+ tag: componentName,
127
+ version: '1.0.0',
128
+ data: complexProps
129
+ };
130
+
131
+ const result = ComponentTransformer.deserialize(componentData);
132
+ const serialized = ComponentTransformer.serialize(result);
133
+ const parsed = JSON.parse(serialized);
134
+
135
+ expect(parsed.tag).toBe(componentName);
136
+ expect(parsed.version).toBe('1.0.0');
137
+ // Note: undefined values may be converted to null in JSON
138
+ });
139
+
140
+ it('should handle version compatibility', () => {
141
+ const versionsToTest = ['1.0.0', '1.1.0', '2.0.0'];
142
+
143
+ versionsToTest.forEach(version => {
144
+ const componentData = {
145
+ tag: componentName,
146
+ version,
147
+ data: { testProp: `test-${version}` }
148
+ };
149
+
150
+ expect(() => {
151
+ const result = ComponentTransformer.deserialize(componentData);
152
+ expect(React.isValidElement(result)).toBe(true);
153
+ }).not.toThrow();
154
+ });
155
+ });
156
+ });
157
+ }
158
+
159
+ // Example component implementations for testing patterns
160
+ class TestButton implements Serializable {
161
+ constructor(public props: {
162
+ label?: string;
163
+ variant?: 'primary' | 'secondary' | 'danger';
164
+ disabled?: boolean;
165
+ onClick?: () => void;
166
+ children?: any;
167
+ className?: string;
168
+ 'data-testid'?: string;
169
+ }) {}
170
+
171
+ static fromJson(jsonData: any): ReactElement {
172
+ return React.createElement('button', {
173
+ className: `btn ${jsonData.variant ? `btn-${jsonData.variant}` : 'btn-primary'} ${jsonData.className || ''}`.trim(),
174
+ disabled: jsonData.disabled,
175
+ onClick: jsonData.onClick,
176
+ 'data-testid': jsonData['data-testid'] || 'test-button'
177
+ }, jsonData.label || jsonData.children || 'Button');
178
+ }
179
+
180
+ toJson(): any {
181
+ return {
182
+ label: this.props.label,
183
+ variant: this.props.variant,
184
+ disabled: this.props.disabled,
185
+ onClick: this.props.onClick ? 'function' : undefined,
186
+ children: this.props.children,
187
+ className: this.props.className,
188
+ 'data-testid': this.props['data-testid']
189
+ };
190
+ }
191
+ }
192
+
193
+ class TestCard implements Serializable {
194
+ constructor(public props: {
195
+ title?: string;
196
+ content?: string;
197
+ image?: string;
198
+ imageAlt?: string;
199
+ actions?: any[];
200
+ metadata?: Record<string, any>;
201
+ className?: string;
202
+ elevation?: number;
203
+ }) {}
204
+
205
+ static fromJson(jsonData: any): ReactElement {
206
+ const actions = jsonData.actions ? ComponentTransformer.deserialize(jsonData.actions) : null;
207
+
208
+ return React.createElement('div', {
209
+ className: `card ${jsonData.className || ''}`.trim(),
210
+ 'data-elevation': jsonData.elevation,
211
+ 'data-testid': 'test-card'
212
+ }, [
213
+ jsonData.image ? React.createElement('img', {
214
+ key: 'image',
215
+ src: jsonData.image,
216
+ alt: jsonData.imageAlt || jsonData.title || 'Card image',
217
+ className: 'card-image'
218
+ }) : null,
219
+ React.createElement('div', { key: 'body', className: 'card-body' }, [
220
+ jsonData.title ? React.createElement('h3', { key: 'title', className: 'card-title' }, jsonData.title) : null,
221
+ jsonData.content ? React.createElement('p', { key: 'content', className: 'card-content' }, jsonData.content) : null,
222
+ jsonData.metadata ? React.createElement('div', {
223
+ key: 'metadata',
224
+ className: 'card-metadata',
225
+ 'data-metadata': JSON.stringify(jsonData.metadata)
226
+ }, Object.entries(jsonData.metadata).map(([key, value]) =>
227
+ React.createElement('span', { key }, `${key}: ${value}`)
228
+ )) : null,
229
+ actions ? React.createElement('div', { key: 'actions', className: 'card-actions' }, actions) : null
230
+ ].filter(Boolean))
231
+ ].filter(Boolean));
232
+ }
233
+
234
+ toJson(): any {
235
+ return {
236
+ title: this.props.title,
237
+ content: this.props.content,
238
+ image: this.props.image,
239
+ imageAlt: this.props.imageAlt,
240
+ actions: this.props.actions ? ComponentTransformer.serialize(this.props.actions) : null,
241
+ metadata: this.props.metadata,
242
+ className: this.props.className,
243
+ elevation: this.props.elevation
244
+ };
245
+ }
246
+ }
247
+
248
+ class TestSection implements Serializable {
249
+ constructor(public props: {
250
+ title?: string;
251
+ subtitle?: string;
252
+ children?: any;
253
+ className?: string;
254
+ backgroundColor?: string;
255
+ padding?: string;
256
+ id?: string;
257
+ }) {}
258
+
259
+ static fromJson(jsonData: any): ReactElement {
260
+ const children = jsonData.children ? ComponentTransformer.deserialize(jsonData.children) : null;
261
+
262
+ return React.createElement('section', {
263
+ id: jsonData.id,
264
+ className: `section ${jsonData.className || ''}`.trim(),
265
+ style: {
266
+ backgroundColor: jsonData.backgroundColor,
267
+ padding: jsonData.padding
268
+ },
269
+ 'data-testid': 'test-section'
270
+ }, [
271
+ jsonData.title || jsonData.subtitle ? React.createElement('header', { key: 'header', className: 'section-header' }, [
272
+ jsonData.title ? React.createElement('h2', { key: 'title' }, jsonData.title) : null,
273
+ jsonData.subtitle ? React.createElement('p', { key: 'subtitle', className: 'subtitle' }, jsonData.subtitle) : null
274
+ ].filter(Boolean)) : null,
275
+ children ? React.createElement('div', { key: 'content', className: 'section-content' }, children) : null
276
+ ].filter(Boolean));
277
+ }
278
+
279
+ toJson(): any {
280
+ return {
281
+ title: this.props.title,
282
+ subtitle: this.props.subtitle,
283
+ children: this.props.children ? ComponentTransformer.serialize(this.props.children) : null,
284
+ className: this.props.className,
285
+ backgroundColor: this.props.backgroundColor,
286
+ padding: this.props.padding,
287
+ id: this.props.id
288
+ };
289
+ }
290
+ }
291
+
292
+ class TestCode implements Serializable {
293
+ constructor(public props: {
294
+ code?: string;
295
+ language?: string;
296
+ showLineNumbers?: boolean;
297
+ highlightLines?: number[];
298
+ theme?: 'light' | 'dark';
299
+ maxHeight?: string;
300
+ filename?: string;
301
+ }) {}
302
+
303
+ static fromJson(jsonData: any): ReactElement {
304
+ return React.createElement('div', {
305
+ className: `code-container theme-${jsonData.theme || 'light'}`,
306
+ 'data-testid': 'test-code'
307
+ }, [
308
+ jsonData.filename ? React.createElement('div', {
309
+ key: 'filename',
310
+ className: 'code-filename'
311
+ }, jsonData.filename) : null,
312
+ React.createElement('pre', {
313
+ key: 'pre',
314
+ className: `code-block language-${jsonData.language || 'text'}`,
315
+ style: { maxHeight: jsonData.maxHeight },
316
+ 'data-line-numbers': jsonData.showLineNumbers,
317
+ 'data-highlight-lines': jsonData.highlightLines ? jsonData.highlightLines.join(',') : undefined
318
+ }, React.createElement('code', {}, jsonData.code || ''))
319
+ ].filter(Boolean));
320
+ }
321
+
322
+ toJson(): any {
323
+ return {
324
+ code: this.props.code,
325
+ language: this.props.language,
326
+ showLineNumbers: this.props.showLineNumbers,
327
+ highlightLines: this.props.highlightLines,
328
+ theme: this.props.theme,
329
+ maxHeight: this.props.maxHeight,
330
+ filename: this.props.filename
331
+ };
332
+ }
333
+ }
334
+
335
+ // Test implementations using the pattern
336
+ describe('Component Serialization Pattern Tests', () => {
337
+ // Test Button component
338
+ testComponentSerialization('TestButton', TestButton, [
339
+ {
340
+ name: 'serialize and deserialize basic button correctly',
341
+ props: { label: 'Click Me', variant: 'primary' },
342
+ expectedData: { label: 'Click Me', variant: 'primary', disabled: undefined, onClick: undefined, children: undefined, className: undefined, 'data-testid': undefined }
343
+ },
344
+ {
345
+ name: 'handle button with all props',
346
+ props: {
347
+ label: 'Full Button',
348
+ variant: 'danger',
349
+ disabled: true,
350
+ className: 'custom-btn',
351
+ 'data-testid': 'full-button'
352
+ },
353
+ validate: (element, props) => {
354
+ expect(element.props.className).toContain('btn-danger');
355
+ expect(element.props.className).toContain('custom-btn');
356
+ expect(element.props.disabled).toBe(true);
357
+ expect(element.props['data-testid']).toBe('full-button');
358
+ expect(element.props.children).toBe('Full Button');
359
+ }
360
+ },
361
+ {
362
+ name: 'handle button with children instead of label',
363
+ props: { children: 'Child Content', variant: 'secondary' },
364
+ validate: (element) => {
365
+ expect(element.props.children).toBe('Child Content');
366
+ expect(element.props.className).toContain('btn-secondary');
367
+ }
368
+ }
369
+ ]);
370
+
371
+ // Test Card component
372
+ testComponentSerialization('TestCard', TestCard, [
373
+ {
374
+ name: 'serialize and deserialize basic card correctly',
375
+ props: { title: 'Test Card', content: 'Test content' },
376
+ validate: (element) => {
377
+ expect(element.props['data-testid']).toBe('test-card');
378
+ expect(element.props.className).toBe('card');
379
+ }
380
+ },
381
+ {
382
+ name: 'handle card with image and metadata',
383
+ props: {
384
+ title: 'Image Card',
385
+ content: 'Card with image',
386
+ image: 'https://example.com/image.jpg',
387
+ imageAlt: 'Test image',
388
+ metadata: { author: 'Test Author', date: '2025-01-01' },
389
+ elevation: 2
390
+ },
391
+ validate: (element) => {
392
+ expect(element.props['data-elevation']).toBe(2);
393
+ // Check for image in children
394
+ const imageChild = element.props.children.find((child: any) => child?.type === 'img');
395
+ expect(imageChild).toBeTruthy();
396
+ expect(imageChild.props.src).toBe('https://example.com/image.jpg');
397
+ expect(imageChild.props.alt).toBe('Test image');
398
+ }
399
+ },
400
+ {
401
+ name: 'handle card with nested actions',
402
+ props: {
403
+ title: 'Card with Actions',
404
+ actions: [
405
+ {
406
+ tag: 'TestButton',
407
+ version: '1.0.0',
408
+ data: { label: 'Action 1', variant: 'primary' }
409
+ },
410
+ {
411
+ tag: 'TestButton',
412
+ version: '1.0.0',
413
+ data: { label: 'Action 2', variant: 'secondary' }
414
+ }
415
+ ]
416
+ }
417
+ }
418
+ ]);
419
+
420
+ // Test Section component
421
+ testComponentSerialization('TestSection', TestSection, [
422
+ {
423
+ name: 'serialize and deserialize basic section correctly',
424
+ props: { title: 'Test Section', subtitle: 'Section subtitle' },
425
+ validate: (element) => {
426
+ expect(element.props['data-testid']).toBe('test-section');
427
+ expect(element.props.className).toBe('section');
428
+ }
429
+ },
430
+ {
431
+ name: 'handle section with styling and children',
432
+ props: {
433
+ title: 'Styled Section',
434
+ className: 'hero-section',
435
+ backgroundColor: '#f0f0f0',
436
+ padding: '2rem',
437
+ id: 'hero',
438
+ children: [
439
+ {
440
+ tag: 'TestCard',
441
+ version: '1.0.0',
442
+ data: { title: 'Nested Card', content: 'Inside section' }
443
+ }
444
+ ]
445
+ },
446
+ validate: (element) => {
447
+ expect(element.props.id).toBe('hero');
448
+ expect(element.props.className).toContain('hero-section');
449
+ expect(element.props.style.backgroundColor).toBe('#f0f0f0');
450
+ expect(element.props.style.padding).toBe('2rem');
451
+ }
452
+ }
453
+ ]);
454
+
455
+ // Test Code component
456
+ testComponentSerialization('TestCode', TestCode, [
457
+ {
458
+ name: 'serialize and deserialize basic code block correctly',
459
+ props: { code: 'const x = 1;', language: 'javascript' },
460
+ validate: (element) => {
461
+ expect(element.props['data-testid']).toBe('test-code');
462
+ expect(element.props.className).toContain('theme-light');
463
+ }
464
+ },
465
+ {
466
+ name: 'handle code block with all features',
467
+ props: {
468
+ code: 'function example() {\n return "hello";\n}',
469
+ language: 'javascript',
470
+ showLineNumbers: true,
471
+ highlightLines: [1, 3],
472
+ theme: 'dark',
473
+ maxHeight: '300px',
474
+ filename: 'example.js'
475
+ },
476
+ validate: (element) => {
477
+ expect(element.props.className).toContain('theme-dark');
478
+ // Check for filename display
479
+ const filenameChild = element.props.children.find((child: any) =>
480
+ child?.props?.className === 'code-filename'
481
+ );
482
+ expect(filenameChild).toBeTruthy();
483
+ expect(filenameChild.props.children).toBe('example.js');
484
+
485
+ // Check code block attributes
486
+ const preChild = element.props.children.find((child: any) => child?.type === 'pre');
487
+ expect(preChild.props['data-line-numbers']).toBe(true);
488
+ expect(preChild.props['data-highlight-lines']).toBe('1,3');
489
+ expect(preChild.props.style.maxHeight).toBe('300px');
490
+ }
491
+ },
492
+ {
493
+ name: 'handle empty code block',
494
+ props: { language: 'text' },
495
+ validate: (element) => {
496
+ const preChild = element.props.children.find((child: any) => child?.type === 'pre');
497
+ expect(preChild.props.className).toContain('language-text');
498
+ expect(preChild.props.children.props.children).toBe('');
499
+ }
500
+ }
501
+ ]);
502
+
503
+ describe('Edge Cases and Error Handling', () => {
504
+ beforeEach(() => {
505
+ ComponentTransformer.clearRegistry();
506
+ ComponentTransformer.registerComponent('TestButton', TestButton);
507
+ ComponentTransformer.registerComponent('TestCard', TestCard);
508
+ ComponentTransformer.registerComponent('TestSection', TestSection);
509
+ ComponentTransformer.registerComponent('TestCode', TestCode);
510
+ });
511
+
512
+ it('should handle components with circular references in data', () => {
513
+ const circularData = { name: 'circular' };
514
+ (circularData as any).self = circularData;
515
+
516
+ // This should not crash, though the circular reference may be lost
517
+ expect(() => {
518
+ const componentData = {
519
+ tag: 'TestButton',
520
+ version: '1.0.0',
521
+ data: { label: 'Circular Test', metadata: circularData }
522
+ };
523
+ ComponentTransformer.deserialize(componentData);
524
+ }).not.toThrow();
525
+ });
526
+
527
+ it('should handle components with function props', () => {
528
+ const componentData = {
529
+ tag: 'TestButton',
530
+ version: '1.0.0',
531
+ data: {
532
+ label: 'Function Test',
533
+ onClick: () => console.log('clicked')
534
+ }
535
+ };
536
+
537
+ expect(() => {
538
+ const result = ComponentTransformer.deserialize(componentData);
539
+ expect(React.isValidElement(result)).toBe(true);
540
+ }).not.toThrow();
541
+ });
542
+
543
+ it('should handle components with very large data', () => {
544
+ const largeText = 'Lorem ipsum '.repeat(10000); // ~110KB
545
+
546
+ const componentData = {
547
+ tag: 'TestCode',
548
+ version: '1.0.0',
549
+ data: {
550
+ code: largeText,
551
+ language: 'text'
552
+ }
553
+ };
554
+
555
+ expect(() => {
556
+ const result = ComponentTransformer.deserialize(componentData);
557
+ expect(React.isValidElement(result)).toBe(true);
558
+ }).not.toThrow();
559
+ });
560
+
561
+ it('should handle components with special characters', () => {
562
+ const specialChars = '!@#$%^&*()_+{}[]|\\:";\'<>?,./`~\n\t\r';
563
+
564
+ const componentData = {
565
+ tag: 'TestCard',
566
+ version: '1.0.0',
567
+ data: {
568
+ title: `Special: ${specialChars}`,
569
+ content: `Content with special chars: ${specialChars}`
570
+ }
571
+ };
572
+
573
+ expect(() => {
574
+ const result = ComponentTransformer.deserialize(componentData);
575
+ expect(React.isValidElement(result)).toBe(true);
576
+ }).not.toThrow();
577
+ });
578
+
579
+ it('should handle components with Unicode characters', () => {
580
+ const unicodeText = '🚀 Unicode test: 中文, العربية, Русский, Ψϕθω';
581
+
582
+ const componentData = {
583
+ tag: 'TestCard',
584
+ version: '1.0.0',
585
+ data: {
586
+ title: unicodeText,
587
+ content: `More Unicode: ${unicodeText}`
588
+ }
589
+ };
590
+
591
+ const result = ComponentTransformer.deserialize(componentData);
592
+ expect(React.isValidElement(result)).toBe(true);
593
+
594
+ // Verify Unicode preservation through serialization
595
+ const serialized = ComponentTransformer.serialize(result);
596
+ const parsed = JSON.parse(serialized);
597
+ expect(parsed.data.title).toContain('🚀');
598
+ expect(parsed.data.title).toContain('中文');
599
+ expect(parsed.data.title).toContain('العربية');
600
+ });
601
+ });
602
+ });