@qwickapps/react-framework 1.3.4 → 1.4.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 (325) hide show
  1. package/README.md +1688 -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/Content.d.ts.map +1 -1
  28. package/dist/components/blocks/CoverImageHeader.d.ts.map +1 -1
  29. package/dist/components/blocks/FeatureCard.d.ts.map +1 -1
  30. package/dist/components/blocks/FeatureGrid.d.ts.map +1 -1
  31. package/dist/components/blocks/Footer.d.ts.map +1 -1
  32. package/dist/components/blocks/HeroBlock.d.ts +27 -13
  33. package/dist/components/blocks/HeroBlock.d.ts.map +1 -1
  34. package/dist/components/blocks/Image.d.ts +41 -0
  35. package/dist/components/blocks/Image.d.ts.map +1 -0
  36. package/dist/components/blocks/PageBannerHeader.d.ts.map +1 -1
  37. package/dist/components/blocks/ProductCard.d.ts.map +1 -1
  38. package/dist/components/blocks/Section.d.ts +16 -2
  39. package/dist/components/blocks/Section.d.ts.map +1 -1
  40. package/dist/components/blocks/Text.d.ts +41 -0
  41. package/dist/components/blocks/Text.d.ts.map +1 -0
  42. package/dist/components/blocks/index.d.ts +4 -0
  43. package/dist/components/blocks/index.d.ts.map +1 -1
  44. package/dist/components/buttons/Button.d.ts +23 -7
  45. package/dist/components/buttons/Button.d.ts.map +1 -1
  46. package/dist/components/forms/FormBlock.d.ts +19 -13
  47. package/dist/components/forms/FormBlock.d.ts.map +1 -1
  48. package/dist/components/index.d.ts +4 -0
  49. package/dist/components/index.d.ts.map +1 -1
  50. package/dist/components/input/ChoiceInputField.d.ts +17 -11
  51. package/dist/components/input/ChoiceInputField.d.ts.map +1 -1
  52. package/dist/components/input/HtmlInputField.d.ts +17 -11
  53. package/dist/components/input/HtmlInputField.d.ts.map +1 -1
  54. package/dist/components/input/SelectInputField.d.ts +16 -10
  55. package/dist/components/input/SelectInputField.d.ts.map +1 -1
  56. package/dist/components/input/SwitchInputField.d.ts +16 -10
  57. package/dist/components/input/SwitchInputField.d.ts.map +1 -1
  58. package/dist/components/input/TextField.d.ts.map +1 -1
  59. package/dist/components/input/TextInputField.d.ts +16 -11
  60. package/dist/components/input/TextInputField.d.ts.map +1 -1
  61. package/dist/components/layout/GridCell.d.ts +23 -6
  62. package/dist/components/layout/GridCell.d.ts.map +1 -1
  63. package/dist/components/layout/GridLayout.d.ts +24 -23
  64. package/dist/components/layout/GridLayout.d.ts.map +1 -1
  65. package/dist/components/pages/FormPage.d.ts.map +1 -1
  66. package/dist/components/pages/Page.d.ts +49 -87
  67. package/dist/components/pages/Page.d.ts.map +1 -1
  68. package/dist/components/pages/index.d.ts +2 -2
  69. package/dist/components/pages/index.d.ts.map +1 -1
  70. package/dist/config/AppConfig.d.ts +49 -0
  71. package/dist/config/AppConfig.d.ts.map +1 -0
  72. package/dist/config/AppConfigBuilder.d.ts +75 -0
  73. package/dist/config/AppConfigBuilder.d.ts.map +1 -0
  74. package/dist/config/index.d.ts +13 -0
  75. package/dist/config/index.d.ts.map +1 -0
  76. package/dist/config/types.d.ts +130 -0
  77. package/dist/config/types.d.ts.map +1 -0
  78. package/dist/config.d.ts +15 -0
  79. package/dist/config.d.ts.map +1 -0
  80. package/dist/config.esm.js +451 -0
  81. package/dist/config.js +455 -0
  82. package/dist/contexts/PrintModeContext.d.ts +27 -0
  83. package/dist/contexts/PrintModeContext.d.ts.map +1 -0
  84. package/dist/contexts/QwickAppContext.d.ts +2 -2
  85. package/dist/contexts/QwickAppContext.d.ts.map +1 -1
  86. package/dist/contexts/ThemeContext.d.ts.map +1 -1
  87. package/dist/contexts/index.d.ts +2 -0
  88. package/dist/contexts/index.d.ts.map +1 -1
  89. package/dist/hooks/index.d.ts +2 -0
  90. package/dist/hooks/index.d.ts.map +1 -1
  91. package/dist/hooks/usePrintMode.d.ts +39 -0
  92. package/dist/hooks/usePrintMode.d.ts.map +1 -0
  93. package/dist/index.css +1 -1
  94. package/dist/index.d.ts +1 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.esm.css +1 -1
  97. package/dist/index.esm.js +20722 -16021
  98. package/dist/index.js +20725 -16010
  99. package/dist/schemas/CodeSchema.d.ts +2 -1
  100. package/dist/schemas/CodeSchema.d.ts.map +1 -1
  101. package/dist/schemas/CollapsibleLayoutSchema.d.ts +2 -1
  102. package/dist/schemas/CollapsibleLayoutSchema.d.ts.map +1 -1
  103. package/dist/schemas/ContentSchema.d.ts +2 -1
  104. package/dist/schemas/ContentSchema.d.ts.map +1 -1
  105. package/dist/schemas/GridCellSchema.d.ts +25 -0
  106. package/dist/schemas/GridCellSchema.d.ts.map +1 -0
  107. package/dist/schemas/GridLayoutSchema.d.ts +23 -0
  108. package/dist/schemas/GridLayoutSchema.d.ts.map +1 -0
  109. package/dist/schemas/HtmlSchema.d.ts +14 -0
  110. package/dist/schemas/HtmlSchema.d.ts.map +1 -0
  111. package/dist/schemas/ImageSchema.d.ts +32 -0
  112. package/dist/schemas/ImageSchema.d.ts.map +1 -0
  113. package/dist/schemas/LogoSchema.d.ts +35 -0
  114. package/dist/schemas/LogoSchema.d.ts.map +1 -0
  115. package/dist/schemas/MarkdownSchema.d.ts +14 -0
  116. package/dist/schemas/MarkdownSchema.d.ts.map +1 -0
  117. package/dist/schemas/PageTemplateSchema.d.ts +31 -0
  118. package/dist/schemas/PageTemplateSchema.d.ts.map +1 -0
  119. package/dist/schemas/PrintConfigSchema.d.ts +31 -0
  120. package/dist/schemas/PrintConfigSchema.d.ts.map +1 -0
  121. package/dist/schemas/SectionSchema.d.ts +2 -1
  122. package/dist/schemas/SectionSchema.d.ts.map +1 -1
  123. package/dist/schemas/TextSchema.d.ts +37 -0
  124. package/dist/schemas/TextSchema.d.ts.map +1 -0
  125. package/dist/schemas/ViewModelSchema.d.ts +23 -0
  126. package/dist/schemas/ViewModelSchema.d.ts.map +1 -0
  127. package/dist/schemas/index.d.ts +15 -1
  128. package/dist/schemas/index.d.ts.map +1 -1
  129. package/dist/schemas/transformers/ComponentTransformer.d.ts +116 -0
  130. package/dist/schemas/transformers/ComponentTransformer.d.ts.map +1 -0
  131. package/dist/schemas/transformers/ReactNodeTransformer.d.ts +53 -0
  132. package/dist/schemas/transformers/ReactNodeTransformer.d.ts.map +1 -0
  133. package/dist/schemas/transformers/__tests__/MockSerializableComponent.d.ts +66 -0
  134. package/dist/schemas/transformers/__tests__/MockSerializableComponent.d.ts.map +1 -0
  135. package/dist/schemas/transformers/registry.d.ts +15 -0
  136. package/dist/schemas/transformers/registry.d.ts.map +1 -0
  137. package/dist/schemas/types/Serializable.d.ts +46 -0
  138. package/dist/schemas/types/Serializable.d.ts.map +1 -0
  139. package/dist/utils/htmlTransform.d.ts.map +1 -1
  140. package/dist/utils/reactUtils.d.ts +12 -3
  141. package/dist/utils/reactUtils.d.ts.map +1 -1
  142. package/package.json +17 -3
  143. package/src/{components/__tests__ → __tests__/components}/AccessibilityProvider.test.tsx +1 -1
  144. package/src/{components/__tests__ → __tests__/components}/Article.test.tsx +1 -1
  145. package/src/{components/__tests__ → __tests__/components}/Breadcrumbs.test.tsx +1 -1
  146. package/src/{components/__tests__ → __tests__/components}/Button.test.tsx +1 -1
  147. package/src/{components/__tests__ → __tests__/components}/CardListGrid.test.tsx +2 -2
  148. package/src/{components/__tests__ → __tests__/components}/ChoiceInputField.test.tsx +1 -1
  149. package/src/{components/__tests__ → __tests__/components}/Code.test.tsx +1 -1
  150. package/src/{components/__tests__ → __tests__/components}/Content.integration.test.tsx +1 -1
  151. package/src/{components/__tests__ → __tests__/components}/Content.test.tsx +1 -1
  152. package/src/{components/__tests__ → __tests__/components}/CoverImageHeader.test.tsx +2 -2
  153. package/src/{components/__tests__ → __tests__/components}/ErrorBoundary.test.tsx +1 -1
  154. package/src/{components/__tests__ → __tests__/components}/FeatureCard.integration.test.tsx +2 -2
  155. package/src/{components/__tests__ → __tests__/components}/FeatureGrid.integration.test.tsx +2 -2
  156. package/src/{components/__tests__ → __tests__/components}/FeatureGrid.test.tsx +2 -2
  157. package/src/{components/__tests__ → __tests__/components}/Footer.test.tsx +4 -4
  158. package/src/{components/__tests__ → __tests__/components}/FormBlock.test.tsx +1 -1
  159. package/src/{components/__tests__ → __tests__/components}/HeroBlock.integration.test.tsx +2 -2
  160. package/src/{components/__tests__ → __tests__/components}/HeroBlock.test.tsx +233 -7
  161. package/src/{components/__tests__ → __tests__/components}/Html.test.tsx +11 -2
  162. package/src/{components/__tests__ → __tests__/components}/HtmlInputField.test.tsx +3 -3
  163. package/src/__tests__/components/Logo.test.js +3 -3
  164. package/src/{components/__tests__ → __tests__/components}/Markdown.test.tsx +1 -1
  165. package/src/{components/__tests__ → __tests__/components}/PageBannerHeader.test.tsx +3 -3
  166. package/src/{components/__tests__ → __tests__/components}/PaletteSwitcher.test.tsx +3 -3
  167. package/src/{components/__tests__ → __tests__/components}/ProductCard.test.tsx +4 -4
  168. package/src/{components/__tests__ → __tests__/components}/SafeSpan.integration.test.tsx +2 -2
  169. package/src/{components/__tests__ → __tests__/components}/SafeSpan.simple.test.tsx +1 -1
  170. package/src/{components/__tests__ → __tests__/components}/SafeSpan.test.tsx +1 -1
  171. package/src/{components/__tests__ → __tests__/components}/Section.integration.test.tsx +1 -1
  172. package/src/{components/__tests__ → __tests__/components}/Section.test.tsx +1 -1
  173. package/src/{components/__tests__ → __tests__/components}/SelectInputField.test.tsx +1 -1
  174. package/src/{components/__tests__ → __tests__/components}/TextInputField.test.tsx +3 -3
  175. package/src/{components/__tests__ → __tests__/components}/ThemeSwitcher.test.tsx +3 -3
  176. package/src/__tests__/components/base/ModelView.test.tsx +220 -0
  177. package/src/__tests__/components/blocks/Code.performance.test.tsx +625 -0
  178. package/src/__tests__/components/blocks/Code.serialization.test.tsx +507 -0
  179. package/src/__tests__/components/blocks/HeroBlock.serialization.test.tsx +414 -0
  180. package/src/__tests__/components/blocks/Image.serialization.test.tsx +257 -0
  181. package/src/__tests__/components/blocks/Section.serialization.test.tsx +553 -0
  182. package/src/__tests__/components/blocks/Text.performance.test.tsx +442 -0
  183. package/src/__tests__/components/blocks/Text.serialization.test.tsx +491 -0
  184. package/src/__tests__/components/buttons/Button.serialization.test.tsx +443 -0
  185. package/src/__tests__/components/input/FormComponents.serialization.test.tsx +482 -0
  186. package/src/__tests__/components/input/SelectInputField.serialization.test.tsx +439 -0
  187. package/src/__tests__/components/input/TextInputField.serialization.test.tsx +359 -0
  188. package/src/{components/layout/CollapsibleLayout/__tests__ → __tests__/components/layout}/CollapsibleLayout.test.tsx +4 -4
  189. package/src/__tests__/components/layout/GridCell.serialization.test.tsx +403 -0
  190. package/src/__tests__/components/layout/GridLayout.serialization.test.tsx +311 -0
  191. package/src/__tests__/hooks/usePrintMode.test.ts +89 -0
  192. package/src/__tests__/schemas/PageTemplateSchema.test.ts +161 -0
  193. package/src/__tests__/schemas/PrintConfigSchema.test.ts +127 -0
  194. package/src/__tests__/schemas/ViewModelSchema.test.ts +80 -0
  195. package/src/__tests__/schemas/transformers/ComponentSerializationPatterns.test.tsx +602 -0
  196. package/src/__tests__/schemas/transformers/ComponentTransformer.htmlPatterns.test.ts +301 -0
  197. package/src/__tests__/schemas/transformers/ComponentTransformer.test.ts +521 -0
  198. package/src/__tests__/schemas/transformers/CrossBrowserCompatibility.test.ts +586 -0
  199. package/src/__tests__/schemas/transformers/MockSerializableComponent.ts +103 -0
  200. package/src/__tests__/schemas/transformers/RealWorldScenarios.test.tsx +1165 -0
  201. package/src/__tests__/schemas/transformers/SerializationErrorHandling.test.ts +602 -0
  202. package/src/__tests__/schemas/transformers/SerializationIntegration.test.tsx +691 -0
  203. package/src/__tests__/schemas/transformers/SerializationPerformance.test.ts +460 -0
  204. package/src/__tests__/schemas/transformers/TestAutomation.test.ts +597 -0
  205. package/src/{utils/__tests__ → __tests__/utils}/nested-dom-fix.test.tsx +1 -1
  206. package/src/components/ErrorBoundary.tsx +8 -8
  207. package/src/components/Html.tsx +147 -44
  208. package/src/components/Logo.tsx +198 -100
  209. package/src/components/Markdown.tsx +125 -16
  210. package/src/components/QwickApp.tsx +64 -31
  211. package/src/components/QwickIcon.tsx +59 -0
  212. package/src/components/SafeSpan.tsx +65 -10
  213. package/src/components/Scaffold.tsx +2 -8
  214. package/src/components/base/ModelView.tsx +199 -0
  215. package/src/components/base/index.ts +11 -0
  216. package/src/components/blocks/Article.tsx +57 -18
  217. package/src/components/blocks/Code.md +529 -0
  218. package/src/components/blocks/Code.tsx +102 -15
  219. package/src/components/blocks/Content.tsx +25 -77
  220. package/src/components/blocks/CoverImageHeader.tsx +9 -4
  221. package/src/components/blocks/FeatureCard.tsx +1 -2
  222. package/src/components/blocks/FeatureGrid.tsx +19 -1
  223. package/src/components/blocks/Footer.tsx +13 -1
  224. package/src/components/blocks/HeroBlock.tsx +87 -20
  225. package/src/components/blocks/Image.tsx +395 -0
  226. package/src/components/blocks/PageBannerHeader.tsx +14 -12
  227. package/src/components/blocks/ProductCard.tsx +51 -52
  228. package/src/components/blocks/Section.tsx +113 -8
  229. package/src/components/blocks/Text.tsx +285 -0
  230. package/src/components/blocks/index.ts +4 -0
  231. package/src/components/buttons/Button.tsx +184 -15
  232. package/src/components/forms/FormBlock.tsx +70 -17
  233. package/src/components/index.ts +5 -0
  234. package/src/components/input/ChoiceInputField.tsx +48 -18
  235. package/src/components/input/HtmlInputField.tsx +48 -18
  236. package/src/components/input/SelectInputField.tsx +48 -16
  237. package/src/components/input/SwitchInputField.tsx +48 -17
  238. package/src/components/input/TextField.tsx +41 -1
  239. package/src/components/input/TextInputField.tsx +52 -18
  240. package/src/components/layout/GridCell.tsx +118 -9
  241. package/src/components/layout/GridLayout.tsx +125 -24
  242. package/src/components/pages/FormPage.tsx +0 -1
  243. package/src/components/pages/Page.css +304 -332
  244. package/src/components/pages/Page.tsx +307 -255
  245. package/src/components/pages/index.ts +2 -2
  246. package/src/config/AppConfig.ts +133 -0
  247. package/src/config/AppConfigBuilder.ts +421 -0
  248. package/src/config/__tests__/AppConfig.test.ts +385 -0
  249. package/src/config/__tests__/AppConfigBuilder.test.ts +432 -0
  250. package/src/config/index.ts +24 -0
  251. package/src/config/types.ts +170 -0
  252. package/src/config.ts +25 -0
  253. package/src/contexts/PrintModeContext.tsx +332 -0
  254. package/src/contexts/QwickAppContext.tsx +2 -2
  255. package/src/contexts/ThemeContext.tsx +1 -2
  256. package/src/contexts/index.ts +2 -0
  257. package/src/hooks/index.ts +5 -1
  258. package/src/hooks/usePrintMode.ts +73 -0
  259. package/src/index.ts +3 -0
  260. package/src/schemas/CodeSchema.ts +3 -3
  261. package/src/schemas/CollapsibleLayoutSchema.ts +2 -1
  262. package/src/schemas/ContentSchema.ts +2 -1
  263. package/src/schemas/GridCellSchema.ts +164 -0
  264. package/src/schemas/GridLayoutSchema.ts +133 -0
  265. package/src/schemas/HtmlSchema.ts +47 -0
  266. package/src/schemas/ImageSchema.ts +235 -0
  267. package/src/schemas/LogoSchema.ts +241 -0
  268. package/src/schemas/MarkdownSchema.ts +47 -0
  269. package/src/schemas/PageTemplateSchema.ts +186 -0
  270. package/src/schemas/PrintConfigSchema.ts +207 -0
  271. package/src/schemas/README.md +661 -0
  272. package/src/schemas/SectionSchema.ts +2 -1
  273. package/src/schemas/TextSchema.ts +329 -0
  274. package/src/schemas/ViewModelSchema.ts +115 -0
  275. package/src/schemas/index.ts +21 -2
  276. package/src/schemas/transformers/ComponentTransformer.ts +403 -0
  277. package/src/schemas/transformers/ReactNodeTransformer.ts +236 -0
  278. package/src/schemas/transformers/registry.ts +72 -0
  279. package/src/schemas/types/Serializable.ts +51 -0
  280. package/src/stories/AccessibilityProvider.stories.tsx +253 -253
  281. package/src/stories/Article.stories.tsx +433 -433
  282. package/src/stories/Button.stories.tsx +1 -1
  283. package/src/stories/CardListGrid.stories.tsx +451 -451
  284. package/src/stories/ChoiceInputField.stories.tsx +503 -503
  285. package/src/stories/Code.stories.tsx +1 -1
  286. package/src/stories/CollapsibleLayout.stories.tsx +1414 -1414
  287. package/src/stories/Content.stories.tsx +393 -393
  288. package/src/stories/CoverImageHeader.stories.tsx +701 -701
  289. package/src/stories/DataBinding.advanced.stories.tsx +432 -432
  290. package/src/stories/DataProvider.stories.tsx +1192 -1192
  291. package/src/stories/FeatureCard.stories.tsx +557 -557
  292. package/src/stories/FeatureGrid.stories.tsx +594 -594
  293. package/src/stories/Footer.stories.tsx +640 -640
  294. package/src/stories/FormBlock.stories.tsx +760 -760
  295. package/src/stories/FormComponents.stories.tsx +349 -541
  296. package/src/stories/GridCell.stories.tsx +417 -0
  297. package/src/stories/GridLayout.stories.tsx +353 -0
  298. package/src/stories/HeroBlock.stories.tsx +862 -373
  299. package/src/stories/HtmlInputField.stories.tsx +474 -474
  300. package/src/stories/Image.stories.tsx +819 -0
  301. package/src/stories/Introduction.stories.tsx +667 -667
  302. package/src/stories/LayoutBlocks.stories.tsx +324 -324
  303. package/src/stories/Logo.stories.tsx +165 -6
  304. package/src/stories/Markdown.stories.tsx +137 -137
  305. package/src/stories/ModelView.stories.tsx +477 -0
  306. package/src/stories/Page.stories.tsx +688 -688
  307. package/src/stories/PageBannerHeader.stories.tsx +864 -864
  308. package/src/stories/PaletteSwitcher.stories.tsx +119 -119
  309. package/src/stories/ProductCard.stories.tsx +424 -424
  310. package/src/stories/QwickApp.stories.tsx +368 -368
  311. package/src/stories/ResponsiveMenu.stories.tsx +249 -249
  312. package/src/stories/SafeSpan.stories.tsx +531 -531
  313. package/src/stories/Section.stories.tsx +90 -2
  314. package/src/stories/SelectInputField.stories.tsx +524 -524
  315. package/src/stories/Text.stories.tsx +560 -0
  316. package/src/stories/TextInputField.stories.tsx +443 -443
  317. package/src/stories/ThemeSwitcher.stories.tsx +123 -123
  318. package/src/utils/htmlTransform.tsx +74 -53
  319. package/src/utils/reactUtils.tsx +57 -6
  320. package/dist/index.bundled.css +0 -12
  321. /package/src/{hooks/__tests__ → __tests__/hooks}/useDataBinding.test.tsx.disabled +0 -0
  322. /package/src/{schemas/__tests__ → __tests__/schemas}/builders.test.ts +0 -0
  323. /package/src/{utils/__tests__ → __tests__/utils}/createDataDrivenComponent.test.tsx.disabled +0 -0
  324. /package/src/{utils/__tests__ → __tests__/utils}/htmlTransform.test.tsx +0 -0
  325. /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
+ });