create-appraisejs 0.1.10-alpha.8 → 0.2.0-alpha.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 (414) hide show
  1. package/README.md +52 -52
  2. package/dist/cli.e2e.test.d.ts +2 -0
  3. package/dist/cli.e2e.test.d.ts.map +1 -0
  4. package/dist/cli.e2e.test.js +73 -0
  5. package/dist/cli.e2e.test.js.map +1 -0
  6. package/dist/config.test.d.ts +2 -0
  7. package/dist/config.test.d.ts.map +1 -0
  8. package/dist/config.test.js +65 -0
  9. package/dist/config.test.js.map +1 -0
  10. package/dist/copy-template.test.d.ts +2 -0
  11. package/dist/copy-template.test.d.ts.map +1 -0
  12. package/dist/copy-template.test.js +71 -0
  13. package/dist/copy-template.test.js.map +1 -0
  14. package/dist/download-repo.test.d.ts +2 -0
  15. package/dist/download-repo.test.d.ts.map +1 -0
  16. package/dist/download-repo.test.js +14 -0
  17. package/dist/download-repo.test.js.map +1 -0
  18. package/dist/install.test.d.ts +2 -0
  19. package/dist/install.test.d.ts.map +1 -0
  20. package/dist/install.test.js +119 -0
  21. package/dist/install.test.js.map +1 -0
  22. package/dist/prepare-template-utils.d.ts.map +1 -1
  23. package/dist/prepare-template-utils.js +5 -0
  24. package/dist/prepare-template-utils.js.map +1 -1
  25. package/dist/prompts.test.d.ts +2 -0
  26. package/dist/prompts.test.d.ts.map +1 -0
  27. package/dist/prompts.test.js +58 -0
  28. package/dist/prompts.test.js.map +1 -0
  29. package/dist/scaffold-gitignore.d.ts +1 -0
  30. package/dist/scaffold-gitignore.d.ts.map +1 -1
  31. package/dist/scaffold-gitignore.js +3 -0
  32. package/dist/scaffold-gitignore.js.map +1 -1
  33. package/package.json +3 -2
  34. package/templates/default/.appraise-template-meta.json +2 -2
  35. package/templates/default/.env.example +2 -2
  36. package/templates/default/.vscode/settings.json +2 -1
  37. package/templates/default/README.md +53 -53
  38. package/templates/default/automation/mapping/locator-map.json +1 -14
  39. package/templates/default/automation/steps/actions/click.step.ts +58 -58
  40. package/templates/default/automation/steps/actions/hover.step.ts +27 -27
  41. package/templates/default/automation/steps/actions/input.step.ts +84 -84
  42. package/templates/default/automation/steps/actions/navigation.step.ts +70 -70
  43. package/templates/default/automation/steps/actions/random_data.step.ts +142 -142
  44. package/templates/default/automation/steps/actions/store.step.ts +86 -86
  45. package/templates/default/automation/steps/actions/wait.step.ts +90 -90
  46. package/templates/default/automation/steps/validations/active_state_assertion.step.ts +30 -30
  47. package/templates/default/automation/steps/validations/navigation_assertion.step.ts +22 -22
  48. package/templates/default/automation/steps/validations/text_assertion.step.ts +107 -107
  49. package/templates/default/automation/steps/validations/visibility_assertion.step.ts +26 -26
  50. package/templates/default/components.json +24 -24
  51. package/templates/default/cucumber.mjs +16 -16
  52. package/templates/default/eslint.config.mjs +16 -16
  53. package/templates/default/gitignore +3 -0
  54. package/templates/default/next-env.d.ts +6 -6
  55. package/templates/default/next.config.ts +11 -11
  56. package/templates/default/package-lock.json +146 -75
  57. package/templates/default/package.json +2 -1
  58. package/templates/default/packages/cucumber-runtime/package.json +13 -13
  59. package/templates/default/packages/cucumber-runtime/src/cache.util.ts +93 -93
  60. package/templates/default/packages/cucumber-runtime/src/cli.ts +68 -68
  61. package/templates/default/packages/cucumber-runtime/src/environment.util.ts +21 -21
  62. package/templates/default/packages/cucumber-runtime/src/executor.ts +32 -32
  63. package/templates/default/packages/cucumber-runtime/src/hooks.ts +25 -3
  64. package/templates/default/packages/cucumber-runtime/src/index.ts +17 -17
  65. package/templates/default/packages/cucumber-runtime/src/locator.util.ts +234 -234
  66. package/templates/default/packages/cucumber-runtime/src/parameter-types.ts +7 -7
  67. package/templates/default/packages/cucumber-runtime/src/paths.ts +66 -1
  68. package/templates/default/packages/cucumber-runtime/src/random-data.util.ts +35 -35
  69. package/templates/default/packages/cucumber-runtime/src/types.ts +13 -13
  70. package/templates/default/packages/cucumber-runtime/src/world.ts +44 -44
  71. package/templates/default/packages/cucumber-runtime/tsconfig.json +11 -11
  72. package/templates/default/packages/locator-picker-companion/dist/cli.d.ts +1 -0
  73. package/templates/default/packages/locator-picker-companion/dist/cli.js +302 -0
  74. package/templates/default/packages/locator-picker-companion/dist/index.d.ts +3 -0
  75. package/templates/default/packages/locator-picker-companion/dist/index.js +3 -0
  76. package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.d.ts +1 -0
  77. package/templates/default/packages/locator-picker-companion/dist/injected-picker-script.js +615 -0
  78. package/templates/default/packages/locator-picker-companion/dist/launcher.d.ts +11 -0
  79. package/templates/default/packages/locator-picker-companion/dist/launcher.js +59 -0
  80. package/templates/default/packages/locator-picker-companion/dist/selector-generator.d.ts +3 -0
  81. package/templates/default/packages/locator-picker-companion/dist/selector-generator.js +257 -0
  82. package/templates/default/packages/locator-picker-companion/dist/session-file.d.ts +22 -0
  83. package/templates/default/packages/locator-picker-companion/dist/session-file.js +150 -0
  84. package/templates/default/packages/locator-picker-companion/dist/types.d.ts +31 -0
  85. package/templates/default/packages/locator-picker-companion/dist/types.js +1 -0
  86. package/templates/default/packages/locator-picker-companion/package.json +17 -0
  87. package/templates/default/packages/locator-picker-companion/src/cli.ts +428 -0
  88. package/templates/default/packages/locator-picker-companion/src/index.ts +3 -0
  89. package/templates/default/packages/locator-picker-companion/src/injected-picker-script.ts +771 -0
  90. package/templates/default/packages/locator-picker-companion/src/launcher.ts +77 -0
  91. package/templates/default/packages/locator-picker-companion/src/selector-generator.ts +339 -0
  92. package/templates/default/packages/locator-picker-companion/src/session-file.ts +226 -0
  93. package/templates/default/packages/locator-picker-companion/src/types.ts +42 -0
  94. package/templates/default/packages/locator-picker-companion/tsconfig.json +14 -0
  95. package/templates/default/postcss.config.mjs +8 -8
  96. package/templates/default/prisma/dev.db +0 -0
  97. package/templates/default/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -16
  98. package/templates/default/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -27
  99. package/templates/default/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -17
  100. package/templates/default/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -12
  101. package/templates/default/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -28
  102. package/templates/default/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -12
  103. package/templates/default/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -10
  104. package/templates/default/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -10
  105. package/templates/default/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -108
  106. package/templates/default/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -55
  107. package/templates/default/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -27
  108. package/templates/default/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -59
  109. package/templates/default/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -34
  110. package/templates/default/prisma/migrations/20260313093000_add_report_step_screenshot_path/migration.sql +1 -0
  111. package/templates/default/prisma/migrations/20260318120000_add_test_suite_context_to_test_run_test_case/migration.sql +20 -0
  112. package/templates/default/prisma/migrations/20260318173512_add_support_of_test_suite_level_runs/migration.sql +2 -0
  113. package/templates/default/prisma/schema.prisma +4 -0
  114. package/templates/default/scripts/install-playwright.ts +20 -0
  115. package/templates/default/scripts/protect-seeded-files.ts +38 -38
  116. package/templates/default/scripts/regenerate-features.ts +94 -94
  117. package/templates/default/scripts/setup-env.ts +19 -19
  118. package/templates/default/scripts/sync-all.ts +22 -66
  119. package/templates/default/scripts/sync-appraise-base-template.ts +57 -19
  120. package/templates/default/scripts/sync-environments.ts +40 -21
  121. package/templates/default/scripts/sync-locator-groups.ts +410 -410
  122. package/templates/default/scripts/sync-locators.ts +398 -398
  123. package/templates/default/scripts/sync-modules.ts +341 -341
  124. package/templates/default/scripts/sync-tags.ts +2 -3
  125. package/templates/default/scripts/sync-template-step-groups.ts +0 -3
  126. package/templates/default/scripts/sync-template-steps.ts +37 -11
  127. package/templates/default/scripts/sync-test-cases.ts +68 -16
  128. package/templates/default/scripts/sync-test-suites.ts +7 -5
  129. package/templates/default/src/actions/conflict/conflict.action.ts +33 -33
  130. package/templates/default/src/actions/dashboard/dashboard-actions.ts +240 -240
  131. package/templates/default/src/actions/environments/environment-actions.ts +188 -188
  132. package/templates/default/src/actions/locator/locator-actions.ts +490 -490
  133. package/templates/default/src/actions/locator-groups/locator-group-actions.ts +6 -3
  134. package/templates/default/src/actions/locator-picker/locator-picker-actions.ts +243 -0
  135. package/templates/default/src/actions/modules/module-actions.ts +135 -135
  136. package/templates/default/src/actions/reports/report-actions.ts +51 -49
  137. package/templates/default/src/actions/review/review-actions.ts +147 -147
  138. package/templates/default/src/actions/settings/sync-actions.ts +46 -0
  139. package/templates/default/src/actions/tags/tag-actions.ts +107 -107
  140. package/templates/default/src/actions/template-step/template-step-actions.ts +38 -4
  141. package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +10 -3
  142. package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +238 -238
  143. package/templates/default/src/actions/test-case/test-case-actions.ts +356 -356
  144. package/templates/default/src/actions/test-run/test-run-actions.ts +222 -129
  145. package/templates/default/src/actions/test-suite/test-suite-actions.ts +95 -17
  146. package/templates/default/src/actions/user/user-actions.ts +13 -13
  147. package/templates/default/src/app/(base)/environments/create/page.tsx +28 -28
  148. package/templates/default/src/app/(base)/environments/environment-form.tsx +219 -219
  149. package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -96
  150. package/templates/default/src/app/(base)/environments/environment-table.tsx +24 -24
  151. package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +46 -46
  152. package/templates/default/src/app/(base)/environments/page.tsx +59 -59
  153. package/templates/default/src/app/(base)/layout.tsx +10 -10
  154. package/templates/default/src/app/(base)/locator-groups/create/page.tsx +44 -44
  155. package/templates/default/src/app/(base)/locator-groups/locator-group-form.tsx +215 -215
  156. package/templates/default/src/app/(base)/locator-groups/locator-group-table-columns.tsx +77 -77
  157. package/templates/default/src/app/(base)/locator-groups/locator-group-table.tsx +28 -28
  158. package/templates/default/src/app/(base)/locator-groups/modify/[id]/page.tsx +46 -46
  159. package/templates/default/src/app/(base)/locator-groups/page.tsx +20 -8
  160. package/templates/default/src/app/(base)/locators/create/create-locator-workspace.tsx +542 -0
  161. package/templates/default/src/app/(base)/locators/create/page.tsx +32 -19
  162. package/templates/default/src/app/(base)/locators/locator-form.tsx +163 -163
  163. package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +73 -73
  164. package/templates/default/src/app/(base)/locators/locator-table.tsx +28 -28
  165. package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +56 -27
  166. package/templates/default/src/app/(base)/locators/page.tsx +13 -13
  167. package/templates/default/src/app/(base)/locators/picker/page.tsx +7 -0
  168. package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +66 -66
  169. package/templates/default/src/app/(base)/modules/create/page.tsx +34 -34
  170. package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +46 -46
  171. package/templates/default/src/app/(base)/modules/module-form.tsx +126 -126
  172. package/templates/default/src/app/(base)/modules/module-table-columns.tsx +85 -85
  173. package/templates/default/src/app/(base)/modules/module-table.tsx +24 -24
  174. package/templates/default/src/app/(base)/modules/page.tsx +59 -59
  175. package/templates/default/src/app/(base)/reports/[id]/page.tsx +40 -33
  176. package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -33
  177. package/templates/default/src/app/(base)/reports/feature-chart.tsx +78 -78
  178. package/templates/default/src/app/(base)/reports/overview-chart.tsx +49 -46
  179. package/templates/default/src/app/(base)/reports/page.tsx +98 -98
  180. package/templates/default/src/app/(base)/reports/report-metric-card.tsx +78 -16
  181. package/templates/default/src/app/(base)/reports/report-table-columns.tsx +189 -189
  182. package/templates/default/src/app/(base)/reports/report-table.tsx +72 -72
  183. package/templates/default/src/app/(base)/reports/report-view-table-columns.tsx +12 -1
  184. package/templates/default/src/app/(base)/reports/report-view-table.tsx +1 -0
  185. package/templates/default/src/app/(base)/reports/test-cases/page.tsx +40 -42
  186. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +115 -115
  187. package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table.tsx +27 -27
  188. package/templates/default/src/app/(base)/reports/test-suites/page.tsx +42 -42
  189. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table-columns.tsx +79 -79
  190. package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table.tsx +27 -27
  191. package/templates/default/src/app/(base)/reports/view-logs-button.tsx +58 -60
  192. package/templates/default/src/app/(base)/reviews/create/page.tsx +26 -26
  193. package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +15 -15
  194. package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +26 -26
  195. package/templates/default/src/app/(base)/reviews/page.tsx +26 -26
  196. package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +26 -26
  197. package/templates/default/src/app/(base)/reviews/review-form.tsx +11 -11
  198. package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +9 -9
  199. package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +9 -9
  200. package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +15 -15
  201. package/templates/default/src/app/(base)/settings/page.tsx +30 -0
  202. package/templates/default/src/app/(base)/settings/settings-sync-panel.tsx +261 -0
  203. package/templates/default/src/app/(base)/tags/create/page.tsx +39 -39
  204. package/templates/default/src/app/(base)/tags/modify/[id]/page.tsx +50 -50
  205. package/templates/default/src/app/(base)/tags/page.tsx +58 -58
  206. package/templates/default/src/app/(base)/tags/tag-form.tsx +147 -147
  207. package/templates/default/src/app/(base)/tags/tag-table-columns.tsx +63 -63
  208. package/templates/default/src/app/(base)/tags/tag-table.tsx +29 -29
  209. package/templates/default/src/app/(base)/template-step-groups/create/page.tsx +28 -28
  210. package/templates/default/src/app/(base)/template-step-groups/modify/[id]/page.tsx +45 -45
  211. package/templates/default/src/app/(base)/template-step-groups/page.tsx +60 -60
  212. package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +167 -167
  213. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table-columns.tsx +89 -89
  214. package/templates/default/src/app/(base)/template-step-groups/template-step-group-table.tsx +32 -32
  215. package/templates/default/src/app/(base)/template-steps/create/page.tsx +37 -37
  216. package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +49 -49
  217. package/templates/default/src/app/(base)/template-steps/page.tsx +58 -58
  218. package/templates/default/src/app/(base)/template-steps/paramChip.tsx +213 -213
  219. package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +384 -384
  220. package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +158 -158
  221. package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +24 -24
  222. package/templates/default/src/app/(base)/template-test-cases/create/page.tsx +56 -56
  223. package/templates/default/src/app/(base)/template-test-cases/modify/[id]/page.tsx +89 -89
  224. package/templates/default/src/app/(base)/template-test-cases/page.tsx +58 -58
  225. package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +84 -84
  226. package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +262 -262
  227. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table-columns.tsx +76 -76
  228. package/templates/default/src/app/(base)/template-test-cases/template-test-case-table.tsx +32 -32
  229. package/templates/default/src/app/(base)/test-cases/create/page.tsx +76 -76
  230. package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +96 -96
  231. package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +38 -38
  232. package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +73 -73
  233. package/templates/default/src/app/(base)/test-cases/modify/[id]/page.tsx +106 -106
  234. package/templates/default/src/app/(base)/test-cases/page.tsx +2 -2
  235. package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +82 -82
  236. package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +395 -395
  237. package/templates/default/src/app/(base)/test-cases/test-case-table-columns.tsx +6 -71
  238. package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +2 -2
  239. package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +4 -1
  240. package/templates/default/src/app/(base)/test-runs/create/page.tsx +11 -9
  241. package/templates/default/src/app/(base)/test-runs/page.tsx +60 -60
  242. package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +37 -44
  243. package/templates/default/src/app/(base)/test-runs/test-run-table-columns.tsx +229 -229
  244. package/templates/default/src/app/(base)/test-runs/test-run-table.tsx +127 -127
  245. package/templates/default/src/app/(base)/test-suites/create/page.tsx +3 -2
  246. package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +2 -1
  247. package/templates/default/src/app/(base)/test-suites/page.tsx +57 -21
  248. package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +16 -17
  249. package/templates/default/src/app/(base)/test-suites/test-suite-table-columns.tsx +2 -1
  250. package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +29 -29
  251. package/templates/default/src/app/(dashboard-components)/app-drawer.tsx +187 -187
  252. package/templates/default/src/app/(dashboard-components)/data-card-grid.tsx +12 -12
  253. package/templates/default/src/app/(dashboard-components)/data-card.tsx +26 -26
  254. package/templates/default/src/app/(dashboard-components)/execution-health-panel.tsx +56 -56
  255. package/templates/default/src/app/(dashboard-components)/ongoing-test-runs-card.tsx +87 -87
  256. package/templates/default/src/app/(dashboard-components)/quick-actions-drawer.tsx +44 -44
  257. package/templates/default/src/app/api/reports/steps/[stepId]/screenshot/route.ts +52 -0
  258. package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +97 -34
  259. package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +4 -1
  260. package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +146 -147
  261. package/templates/default/src/app/globals.css +147 -147
  262. package/templates/default/src/app/layout.tsx +4 -0
  263. package/templates/default/src/app/page.tsx +2 -0
  264. package/templates/default/src/assets/icons/empty-tube.tsx +23 -23
  265. package/templates/default/src/assets/icons/tube-plus.tsx +29 -29
  266. package/templates/default/src/components/base-node.tsx +21 -21
  267. package/templates/default/src/components/chart/pie-chart.tsx +73 -73
  268. package/templates/default/src/components/data-extraction/locator-inspector.tsx +460 -460
  269. package/templates/default/src/components/data-state/empty-state.tsx +40 -40
  270. package/templates/default/src/components/data-visualization/info-card.tsx +70 -70
  271. package/templates/default/src/components/data-visualization/info-grid.tsx +22 -22
  272. package/templates/default/src/components/diagram/button-edge.tsx +54 -54
  273. package/templates/default/src/components/diagram/dynamic-parameters.tsx +474 -474
  274. package/templates/default/src/components/diagram/edit-header-option.tsx +36 -36
  275. package/templates/default/src/components/diagram/flow-diagram.tsx +470 -470
  276. package/templates/default/src/components/diagram/node-form.tsx +262 -262
  277. package/templates/default/src/components/diagram/options-header-node.tsx +57 -57
  278. package/templates/default/src/components/diagram/template-step-combobox.tsx +155 -155
  279. package/templates/default/src/components/form/error-message.tsx +7 -7
  280. package/templates/default/src/components/kokonutui/smooth-tab.tsx +453 -453
  281. package/templates/default/src/components/loading-skeleton/data-table/data-table-skeleton.tsx +30 -30
  282. package/templates/default/src/components/loading-skeleton/form/button-skeleton.tsx +8 -8
  283. package/templates/default/src/components/loading-skeleton/form/icon-button-skeleton.tsx +8 -8
  284. package/templates/default/src/components/loading-skeleton/form/text-input-skeleton.tsx +8 -8
  285. package/templates/default/src/components/loading-skeleton/visualization/table-skeleton.tsx +14 -14
  286. package/templates/default/src/components/logo.tsx +15 -15
  287. package/templates/default/src/components/navigation/command-badge.tsx +34 -34
  288. package/templates/default/src/components/navigation/command-chain-input.tsx +51 -51
  289. package/templates/default/src/components/navigation/entity-search-command.tsx +116 -116
  290. package/templates/default/src/components/navigation/nav-card.tsx +31 -31
  291. package/templates/default/src/components/navigation/nav-command.tsx +13 -0
  292. package/templates/default/src/components/navigation/nav-link.tsx +60 -60
  293. package/templates/default/src/components/navigation/nav-menu-card-deck.tsx +112 -112
  294. package/templates/default/src/components/node-header.tsx +159 -159
  295. package/templates/default/src/components/reports/test-case-logs-modal.tsx +310 -253
  296. package/templates/default/src/components/table/table-actions.tsx +172 -172
  297. package/templates/default/src/components/test-case/test-case-columns.tsx +76 -0
  298. package/templates/default/src/components/test-case/test-case-picker.tsx +244 -0
  299. package/templates/default/src/components/test-run/download-logs-button.tsx +92 -99
  300. package/templates/default/src/components/test-run/log-viewer.tsx +1 -14
  301. package/templates/default/src/components/test-run/test-run-details.tsx +15 -2
  302. package/templates/default/src/components/test-run/test-run-header.tsx +149 -149
  303. package/templates/default/src/components/test-run/view-report-button.tsx +102 -102
  304. package/templates/default/src/components/test-suite/test-suite-picker.tsx +444 -0
  305. package/templates/default/src/components/theme/mode-toggle.tsx +54 -54
  306. package/templates/default/src/components/theme/theme-provider.tsx +8 -8
  307. package/templates/default/src/components/typography/page-header-subtitle.tsx +7 -7
  308. package/templates/default/src/components/typography/page-header.tsx +7 -7
  309. package/templates/default/src/components/ui/alert-dialog.tsx +106 -106
  310. package/templates/default/src/components/ui/alert.tsx +43 -43
  311. package/templates/default/src/components/ui/avatar.tsx +40 -40
  312. package/templates/default/src/components/ui/badge.tsx +29 -29
  313. package/templates/default/src/components/ui/button.tsx +47 -47
  314. package/templates/default/src/components/ui/calendar.tsx +158 -158
  315. package/templates/default/src/components/ui/card.tsx +43 -43
  316. package/templates/default/src/components/ui/checkbox.tsx +28 -28
  317. package/templates/default/src/components/ui/command.tsx +135 -135
  318. package/templates/default/src/components/ui/data-table-column-header.tsx +61 -61
  319. package/templates/default/src/components/ui/data-table-pagination.tsx +87 -87
  320. package/templates/default/src/components/ui/data-table-view-options.tsx +50 -50
  321. package/templates/default/src/components/ui/data-table.tsx +261 -261
  322. package/templates/default/src/components/ui/dialog.tsx +97 -97
  323. package/templates/default/src/components/ui/dropdown-menu.tsx +182 -182
  324. package/templates/default/src/components/ui/input.tsx +22 -22
  325. package/templates/default/src/components/ui/kbd.tsx +28 -28
  326. package/templates/default/src/components/ui/label.tsx +19 -19
  327. package/templates/default/src/components/ui/loading.tsx +12 -12
  328. package/templates/default/src/components/ui/multi-select-with-preview.tsx +116 -116
  329. package/templates/default/src/components/ui/multi-select.tsx +142 -142
  330. package/templates/default/src/components/ui/navigation-menu.tsx +120 -120
  331. package/templates/default/src/components/ui/popover.tsx +33 -33
  332. package/templates/default/src/components/ui/progress.tsx +25 -25
  333. package/templates/default/src/components/ui/radio-group.tsx +44 -44
  334. package/templates/default/src/components/ui/scroll-area.tsx +40 -40
  335. package/templates/default/src/components/ui/select.tsx +151 -151
  336. package/templates/default/src/components/ui/separator.tsx +22 -22
  337. package/templates/default/src/components/ui/skeleton.tsx +7 -7
  338. package/templates/default/src/components/ui/table.tsx +76 -76
  339. package/templates/default/src/components/ui/tabs.tsx +55 -55
  340. package/templates/default/src/components/ui/textarea.tsx +21 -21
  341. package/templates/default/src/components/ui/toast.tsx +113 -113
  342. package/templates/default/src/components/ui/toaster.tsx +26 -26
  343. package/templates/default/src/components/user-prompt/delete-prompt.tsx +87 -87
  344. package/templates/default/src/config/db-config.ts +10 -10
  345. package/templates/default/src/constants/form-opts/diagram/node-form.ts +30 -30
  346. package/templates/default/src/constants/form-opts/environment-form-opts.ts +24 -24
  347. package/templates/default/src/constants/form-opts/locator-form-opts.ts +20 -20
  348. package/templates/default/src/constants/form-opts/locator-group-form-opts.ts +28 -28
  349. package/templates/default/src/constants/form-opts/module-form-opts.ts +21 -21
  350. package/templates/default/src/constants/form-opts/review-form-opts.ts +23 -23
  351. package/templates/default/src/constants/form-opts/tag-form-opts.ts +42 -42
  352. package/templates/default/src/constants/form-opts/template-selection-form-opts.ts +16 -16
  353. package/templates/default/src/constants/form-opts/template-step-group-form-opts.ts +24 -24
  354. package/templates/default/src/constants/form-opts/template-test-case-form-opts.ts +39 -39
  355. package/templates/default/src/constants/form-opts/template-test-step-form-opts.ts +36 -36
  356. package/templates/default/src/constants/form-opts/test-case-form-opts.ts +43 -43
  357. package/templates/default/src/constants/form-opts/test-run-form-opts.ts +5 -3
  358. package/templates/default/src/constants/form-opts/test-suite-form-opts.ts +24 -24
  359. package/templates/default/src/hooks/use-toast.ts +187 -187
  360. package/templates/default/src/lib/automation/paths.ts +42 -12
  361. package/templates/default/src/lib/automation/projection-service.ts +2 -1
  362. package/templates/default/src/lib/bidirectional-sync.ts +432 -432
  363. package/templates/default/src/lib/database-sync.ts +2 -7
  364. package/templates/default/src/lib/environment-file-utils.ts +184 -184
  365. package/templates/default/src/lib/executor/local-executor-adapter.ts +24 -25
  366. package/templates/default/src/lib/executor/types.ts +2 -2
  367. package/templates/default/src/lib/feature-file-generator.ts +5 -66
  368. package/templates/default/src/lib/gherkin-parser.ts +259 -259
  369. package/templates/default/src/lib/locator-group-file-utils.ts +307 -307
  370. package/templates/default/src/lib/locator-picker/session-manager.ts +452 -0
  371. package/templates/default/src/lib/locator-picker/suggestions.ts +124 -0
  372. package/templates/default/src/lib/metrics/metric-calculator.ts +5 -0
  373. package/templates/default/src/lib/module-hierarchy-builder.ts +205 -205
  374. package/templates/default/src/lib/path-helpers/module-path.ts +71 -71
  375. package/templates/default/src/lib/process/task-spawner.ts +39 -2
  376. package/templates/default/src/lib/sync/projected-feature-utils.ts +113 -0
  377. package/templates/default/src/lib/sync/sync-executor.ts +130 -0
  378. package/templates/default/src/lib/sync/sync-pending-counts.test.ts +239 -0
  379. package/templates/default/src/lib/sync/sync-pending-counts.ts +1358 -0
  380. package/templates/default/src/lib/sync/sync-registry.ts +149 -0
  381. package/templates/default/src/lib/tag-utils.ts +28 -0
  382. package/templates/default/src/lib/template-sync-utils.d.ts +6 -6
  383. package/templates/default/src/lib/template-sync-utils.js +46 -46
  384. package/templates/default/src/lib/template-sync-utils.ts +63 -63
  385. package/templates/default/src/lib/test-case-utils.ts +6 -6
  386. package/templates/default/src/lib/test-run/log-formatter.ts +83 -83
  387. package/templates/default/src/lib/test-run/matching.ts +117 -0
  388. package/templates/default/src/lib/test-run/process-manager.ts +2 -0
  389. package/templates/default/src/lib/test-run/report-parser.ts +352 -316
  390. package/templates/default/src/lib/test-run/test-run-executor.ts +13 -15
  391. package/templates/default/src/lib/test-run/winston-logger.ts +64 -66
  392. package/templates/default/src/lib/test-suite-identifier-service.ts +76 -0
  393. package/templates/default/src/lib/test-suite-utils.ts +6 -0
  394. package/templates/default/src/lib/transformers/gherkin-converter.ts +42 -42
  395. package/templates/default/src/lib/transformers/key-to-icon-transformer.tsx +95 -95
  396. package/templates/default/src/lib/transformers/template-test-case-converter.ts +160 -160
  397. package/templates/default/src/lib/utils/node-param-validation.ts +81 -81
  398. package/templates/default/src/lib/utils/template-step-file-generator.ts +104 -104
  399. package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +0 -8
  400. package/templates/default/src/lib/utils/template-step-file-manager.ts +166 -166
  401. package/templates/default/src/lib/utils.ts +31 -31
  402. package/templates/default/src/types/diagram/diagram.ts +34 -34
  403. package/templates/default/src/types/diagram/template-step.ts +11 -11
  404. package/templates/default/src/types/executor/browser.type.ts +1 -1
  405. package/templates/default/src/types/form/actionHandler.ts +6 -6
  406. package/templates/default/src/types/locator/locator.type.ts +11 -11
  407. package/templates/default/src/types/locator-picker.ts +65 -0
  408. package/templates/default/src/types/step/step.type.ts +1 -1
  409. package/templates/default/src/types/table/data-table.ts +6 -6
  410. package/templates/default/src/types/test-case-picker.ts +6 -0
  411. package/templates/default/src/types/test-suite-picker.ts +18 -0
  412. package/templates/default/tailwind.config.ts +62 -62
  413. package/templates/default/tsconfig.json +4 -1
  414. package/templates/default/appraisejs.config.json +0 -1
@@ -1,474 +1,474 @@
1
- 'use client'
2
-
3
- import { useState, useMemo, forwardRef, useImperativeHandle, useEffect } from 'react'
4
- import { Calendar } from '@/components/ui/calendar'
5
- import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
6
- import { Button } from '@/components/ui/button'
7
- import { Input } from '@/components/ui/input'
8
- import { Label } from '@/components/ui/label'
9
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
10
- import { CalendarIcon } from 'lucide-react'
11
- import { cn } from '@/lib/utils'
12
- import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
13
- import { StepParameterType, TemplateStepParameter, Locator, LocatorGroup } from '@prisma/client'
14
- import { format } from 'date-fns'
15
- import ErrorMessage from '@/components/form/error-message'
16
-
17
- interface DynamicFormFieldsProps {
18
- templateStepParams: TemplateStepParameter[]
19
- locators: Locator[]
20
- locatorGroups: LocatorGroup[]
21
- defaultValueInput?: boolean
22
- onChange?: (
23
- values: {
24
- name: string
25
- value: string
26
- type: StepParameterType
27
- order: number
28
- }[],
29
- ) => void
30
- initialParameterValues?: {
31
- name: string
32
- value: string
33
- type: StepParameterType
34
- order: number
35
- }[]
36
- }
37
-
38
- export interface DynamicFormFieldsRef {
39
- validate: () => boolean
40
- }
41
-
42
- const DynamicFormFields = forwardRef<DynamicFormFieldsRef, DynamicFormFieldsProps>((props, ref) => {
43
- const {
44
- templateStepParams,
45
- locators,
46
- locatorGroups,
47
- defaultValueInput = false,
48
- onChange,
49
- initialParameterValues,
50
- } = props
51
-
52
- const resetKey = useMemo(() => {
53
- return JSON.stringify({
54
- params: templateStepParams.map(p => ({ name: p.name, type: p.type })),
55
- initialParameterValues,
56
- })
57
- }, [templateStepParams, initialParameterValues])
58
-
59
- // Create initial values only once when component mounts
60
- const initialValues = useMemo(() => {
61
- const values: { [key: string]: string | number | boolean | Date } = {}
62
- // Build a map for quick lookup of initial values by name
63
- const initialValueMap: Record<string, { value: string; type: StepParameterType }> = {}
64
- initialParameterValues?.forEach(v => {
65
- initialValueMap[v.name] = { value: v.value, type: v.type }
66
- })
67
- templateStepParams.forEach(param => {
68
- const initial = initialValueMap[param.name]
69
- if (initial) {
70
- switch (param.type) {
71
- case 'NUMBER':
72
- values[param.name] = Number(initial.value)
73
- break
74
- case 'STRING':
75
- case 'LOCATOR':
76
- values[param.name] = initial.value
77
- break
78
- case 'DATE':
79
- // Try to parse date from string
80
- const date = new Date(initial.value)
81
- values[param.name] = isNaN(date.getTime()) ? new Date() : date
82
- break
83
- case 'BOOLEAN':
84
- values[param.name] = initial.value === 'true'
85
- break
86
- }
87
- } else {
88
- // fallback to default
89
- switch (param.type) {
90
- case 'NUMBER':
91
- values[param.name] = 0
92
- break
93
- case 'STRING':
94
- values[param.name] = ''
95
- break
96
- case 'DATE':
97
- values[param.name] = new Date()
98
- break
99
- case 'BOOLEAN':
100
- values[param.name] = false
101
- break
102
- case 'LOCATOR':
103
- values[param.name] = ''
104
- break
105
- }
106
- }
107
- })
108
- return values
109
- }, [templateStepParams, initialParameterValues])
110
-
111
- // Derive initial locator groups from initialParameterValues (locator name -> group id via locators lookup)
112
- const initialSelectedLocatorGroups = useMemo(() => {
113
- const groups: Record<string, string> = {}
114
- const initialValueMap: Record<string, string> = {}
115
- initialParameterValues?.forEach(v => {
116
- initialValueMap[v.name] = v.value
117
- })
118
- templateStepParams.forEach(param => {
119
- if (param.type === 'LOCATOR') {
120
- const locatorName = initialValueMap[param.name]
121
- if (locatorName) {
122
- const locator = locators.find(l => l.name === locatorName)
123
- if (locator?.locatorGroupId) {
124
- groups[param.name] = locator.locatorGroupId
125
- }
126
- }
127
- }
128
- })
129
- return groups
130
- }, [templateStepParams, initialParameterValues, locators])
131
-
132
- // Initialize state with initial values
133
- const [values, setValues] = useState<{
134
- [key: string]: string | number | boolean | Date
135
- }>(initialValues)
136
- const [errors, setErrors] = useState<Record<string, string>>({})
137
-
138
- // State for locator group selection (initialized from initial data so edit restores group + locator)
139
- const [selectedLocatorGroups, setSelectedLocatorGroups] = useState<Record<string, string>>(
140
- initialSelectedLocatorGroups,
141
- )
142
-
143
- useEffect(() => {
144
- queueMicrotask(() => setErrors({}))
145
- }, [templateStepParams])
146
-
147
- // Sync state when initial data changes (e.g. opening edit for a different node)
148
- useEffect(() => {
149
- queueMicrotask(() => {
150
- setValues(initialValues)
151
- setSelectedLocatorGroups(initialSelectedLocatorGroups)
152
- })
153
- }, [initialValues, initialSelectedLocatorGroups])
154
-
155
- useImperativeHandle(ref, () => ({
156
- validate: () => {
157
- // Skip all validation if defaultValueInput is true (all fields are optional)
158
- if (defaultValueInput) {
159
- setErrors({})
160
- return true
161
- }
162
-
163
- const newErrors: Record<string, string> = {}
164
- templateStepParams.forEach(param => {
165
- const value = values[param.name]
166
-
167
- if (param.type === 'LOCATOR') {
168
- const selectedGroup = selectedLocatorGroups[param.name]
169
- if (!selectedGroup) {
170
- newErrors[param.name] = 'Locator group is required'
171
- } else if (!value) {
172
- newErrors[param.name] = 'Locator is required'
173
- }
174
- }
175
- if (param.type === 'STRING' && !value) {
176
- newErrors[param.name] = 'This field is required'
177
- }
178
- if (param.type === 'NUMBER' && !value) {
179
- newErrors[param.name] = 'This field is required'
180
- }
181
- // Add other validation rules here if needed
182
- })
183
- setErrors(newErrors)
184
- return Object.keys(newErrors).length === 0
185
- },
186
- }))
187
-
188
- // Update values when an input changes
189
- const handleInputChange = (name: string, value: string | number | boolean | Date) => {
190
- const newValues = {
191
- ...values,
192
- [name]: value,
193
- }
194
-
195
- setValues(newValues)
196
-
197
- // Clear error for the field being edited
198
- if (errors[name]) {
199
- const newErrors = { ...errors }
200
- delete newErrors[name]
201
- setErrors(newErrors)
202
- }
203
-
204
- // Notify parent component of changes
205
- if (onChange) {
206
- // We need to calculate formatted values based on the new state
207
- const formattedValues = templateStepParams.map(param => {
208
- let stringValue = ''
209
- // Use the new value if it's the one that changed, otherwise use the current state
210
- const currentValue = param.name === name ? value : values[param.name]
211
-
212
- switch (param.type) {
213
- case 'NUMBER':
214
- stringValue = currentValue !== undefined && currentValue !== null ? String(currentValue) : ''
215
- break
216
- case 'STRING':
217
- stringValue = currentValue !== undefined && currentValue !== null ? (currentValue as string) : ''
218
- break
219
- case 'DATE':
220
- if (
221
- currentValue &&
222
- currentValue instanceof Date &&
223
- typeof currentValue.getTime === 'function' &&
224
- !Number.isNaN(currentValue.getTime())
225
- ) {
226
- stringValue = format(currentValue as Date, 'PPP')
227
- } else {
228
- stringValue = ''
229
- }
230
- break
231
- case 'BOOLEAN':
232
- stringValue = currentValue !== undefined && currentValue !== null ? String(currentValue) : ''
233
- break
234
- case 'LOCATOR':
235
- stringValue = currentValue !== undefined && currentValue !== null ? (currentValue as string) : ''
236
- break
237
- }
238
-
239
- return {
240
- name: param.name,
241
- value: stringValue,
242
- type: param.type,
243
- order: param.order,
244
- }
245
- })
246
-
247
- onChange(formattedValues)
248
- }
249
- }
250
-
251
- // Handle locator group selection
252
- const handleLocatorGroupChange = (paramName: string, groupId: string) => {
253
- setSelectedLocatorGroups(prev => ({
254
- ...prev,
255
- [paramName]: groupId,
256
- }))
257
-
258
- // Clear the locator selection when group changes
259
- setValues(prev => ({
260
- ...prev,
261
- [paramName]: '',
262
- }))
263
-
264
- // Clear errors for this field
265
- if (errors[paramName]) {
266
- const newErrors = { ...errors }
267
- delete newErrors[paramName]
268
- setErrors(newErrors)
269
- }
270
- }
271
-
272
- // Get locators for a specific group
273
- const getLocatorsForGroup = (groupId: string) => {
274
- return locators.filter(locator => locator.locatorGroupId === groupId)
275
- }
276
-
277
- // Render the appropriate input field based on the parameter type
278
- const renderInputField = (param: TemplateStepParameter) => {
279
- const { name, type } = param
280
- const errorMessage = errors[name]
281
-
282
- switch (type) {
283
- case 'NUMBER':
284
- return (
285
- <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
286
- <Label htmlFor={`input-${name}`} className="text-primary">
287
- {defaultValueInput ? `Default ${name}` : name}{' '}
288
- {!defaultValueInput && <span className="text-red-500">*</span>}
289
- </Label>
290
- <Input
291
- id={`input-${name}`}
292
- type="number"
293
- value={typeof values[name] === 'number' ? values[name] : 0}
294
- onChange={e => handleInputChange(name, Number(e.target.value))}
295
- className="w-full"
296
- />
297
- <ErrorMessage message={errorMessage || ''} visible={!!errorMessage} />
298
- </div>
299
- )
300
-
301
- case 'STRING':
302
- return (
303
- <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
304
- <Label htmlFor={`input-${name}`} className="text-primary">
305
- {defaultValueInput ? `Default ${name}` : name}{' '}
306
- {!defaultValueInput && <span className="text-red-500">*</span>}
307
- </Label>
308
- <Input
309
- id={`input-${name}`}
310
- type="text"
311
- value={typeof values[name] === 'string' ? values[name] : ''}
312
- onChange={e => handleInputChange(name, e.target.value)}
313
- className="w-full"
314
- />
315
- <ErrorMessage message={errorMessage || ''} visible={!!errorMessage} />
316
- </div>
317
- )
318
-
319
- case 'DATE':
320
- return (
321
- <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
322
- <Label className="text-primary">
323
- {defaultValueInput ? `Default ${name}` : name}{' '}
324
- {!defaultValueInput && <span className="text-red-500">*</span>}
325
- </Label>
326
- <Popover>
327
- <PopoverTrigger asChild>
328
- <Button
329
- variant="outline"
330
- className={cn('w-full justify-start text-left font-normal', !values[name] && 'text-muted-foreground')}
331
- aria-required={!defaultValueInput}
332
- >
333
- <CalendarIcon className="mr-2 h-4 w-4" />
334
- {values[name] instanceof Date ? (
335
- format(values[name] as Date, 'PPP')
336
- ) : (
337
- <span className={defaultValueInput ? 'text-muted-foreground' : 'text-red-500'}>
338
- {defaultValueInput ? 'Pick a date (optional)' : 'Pick a date *'}
339
- </span>
340
- )}
341
- </Button>
342
- </PopoverTrigger>
343
- <PopoverContent className="w-auto p-0" align="start">
344
- <Calendar
345
- mode="single"
346
- selected={values[name] instanceof Date ? (values[name] as Date) : undefined}
347
- onSelect={(date: Date | undefined) => handleInputChange(name, date as Date)}
348
- initialFocus
349
- required={!defaultValueInput}
350
- />
351
- </PopoverContent>
352
- </Popover>
353
- </div>
354
- )
355
-
356
- case 'BOOLEAN':
357
- return (
358
- <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
359
- <Label htmlFor={`select-${name}`} className="text-primary">
360
- {defaultValueInput ? `Default ${name}` : name}{' '}
361
- {!defaultValueInput && <span className="text-red-500">*</span>}
362
- </Label>
363
- <Select
364
- value={typeof values[name] === 'boolean' ? String(values[name]) : 'false'}
365
- onValueChange={value => handleInputChange(name, value === 'true')}
366
- required={!defaultValueInput}
367
- >
368
- <SelectTrigger id={`select-${name}`} className="w-full">
369
- <SelectValue placeholder={defaultValueInput ? 'Select a value (optional)' : 'Select a value *'} />
370
- </SelectTrigger>
371
- <SelectContent>
372
- <SelectItem value="true">True</SelectItem>
373
- <SelectItem value="false">False</SelectItem>
374
- </SelectContent>
375
- </Select>
376
- </div>
377
- )
378
-
379
- case 'LOCATOR':
380
- const selectedGroupId = selectedLocatorGroups[name] || ''
381
- const availableLocators = selectedGroupId ? getLocatorsForGroup(selectedGroupId) : []
382
-
383
- return (
384
- <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
385
- <Label htmlFor={`select-${name}`} className="text-primary">
386
- {defaultValueInput ? `Default ${name}` : name}{' '}
387
- {!defaultValueInput && <span className="text-red-500">*</span>}
388
- </Label>
389
-
390
- {/* Locator Group Selection */}
391
- <div className="mb-2">
392
- <Label htmlFor={`group-${name}`} className="text-sm text-muted-foreground">
393
- Locator Group
394
- </Label>
395
- <Select
396
- value={selectedGroupId}
397
- onValueChange={value => handleLocatorGroupChange(name, value)}
398
- required={!defaultValueInput}
399
- >
400
- <SelectTrigger id={`group-${name}`} className="w-full">
401
- <SelectValue placeholder="Select a locator group" />
402
- </SelectTrigger>
403
- <SelectContent isEmpty={locatorGroups.length === 0}>
404
- {locatorGroups.map(group => (
405
- <SelectItem key={group.id} value={group.id}>
406
- {group.name}
407
- </SelectItem>
408
- ))}
409
- </SelectContent>
410
- </Select>
411
- </div>
412
-
413
- {/* Locator Selection */}
414
- <div>
415
- <Label htmlFor={`select-${name}`} className="text-sm text-muted-foreground">
416
- Locator
417
- </Label>
418
- <Select
419
- value={typeof values[name] === 'string' ? values[name] : ''}
420
- onValueChange={value => handleInputChange(name, value)}
421
- required={!defaultValueInput}
422
- disabled={!selectedGroupId}
423
- >
424
- <SelectTrigger id={`select-${name}`} className="w-full">
425
- <SelectValue
426
- placeholder={
427
- !selectedGroupId
428
- ? 'Select a locator group first'
429
- : defaultValueInput
430
- ? 'Select a locator (optional)'
431
- : 'Select a locator *'
432
- }
433
- />
434
- </SelectTrigger>
435
- <SelectContent isEmpty={availableLocators.length === 0}>
436
- {availableLocators.map(locator => (
437
- <SelectItem key={locator.id} value={locator.name}>
438
- {locator.name}
439
- </SelectItem>
440
- ))}
441
- </SelectContent>
442
- </Select>
443
- </div>
444
- <ErrorMessage message={errorMessage || ''} visible={!!errorMessage} />
445
- </div>
446
- )
447
-
448
- default:
449
- return null
450
- }
451
- }
452
-
453
- // Guard: do not render if no parameters
454
- if (!templateStepParams || templateStepParams.length === 0) {
455
- return null
456
- }
457
-
458
- return (
459
- <Card className="border-gray-700 bg-transparent shadow-none" key={resetKey}>
460
- <CardHeader className="py-3">
461
- <CardTitle className="text-xs font-bold text-primary">Parameters</CardTitle>
462
- </CardHeader>
463
- <CardContent>
464
- <div className="space-y-6">
465
- {templateStepParams.map(param => (
466
- <div key={param.name}>{renderInputField(param)}</div>
467
- ))}
468
- </div>
469
- </CardContent>
470
- </Card>
471
- )
472
- })
473
- DynamicFormFields.displayName = 'DynamicFormFields'
474
- export default DynamicFormFields
1
+ 'use client'
2
+
3
+ import { useState, useMemo, forwardRef, useImperativeHandle, useEffect } from 'react'
4
+ import { Calendar } from '@/components/ui/calendar'
5
+ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
6
+ import { Button } from '@/components/ui/button'
7
+ import { Input } from '@/components/ui/input'
8
+ import { Label } from '@/components/ui/label'
9
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
10
+ import { CalendarIcon } from 'lucide-react'
11
+ import { cn } from '@/lib/utils'
12
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
13
+ import { StepParameterType, TemplateStepParameter, Locator, LocatorGroup } from '@prisma/client'
14
+ import { format } from 'date-fns'
15
+ import ErrorMessage from '@/components/form/error-message'
16
+
17
+ interface DynamicFormFieldsProps {
18
+ templateStepParams: TemplateStepParameter[]
19
+ locators: Locator[]
20
+ locatorGroups: LocatorGroup[]
21
+ defaultValueInput?: boolean
22
+ onChange?: (
23
+ values: {
24
+ name: string
25
+ value: string
26
+ type: StepParameterType
27
+ order: number
28
+ }[],
29
+ ) => void
30
+ initialParameterValues?: {
31
+ name: string
32
+ value: string
33
+ type: StepParameterType
34
+ order: number
35
+ }[]
36
+ }
37
+
38
+ export interface DynamicFormFieldsRef {
39
+ validate: () => boolean
40
+ }
41
+
42
+ const DynamicFormFields = forwardRef<DynamicFormFieldsRef, DynamicFormFieldsProps>((props, ref) => {
43
+ const {
44
+ templateStepParams,
45
+ locators,
46
+ locatorGroups,
47
+ defaultValueInput = false,
48
+ onChange,
49
+ initialParameterValues,
50
+ } = props
51
+
52
+ const resetKey = useMemo(() => {
53
+ return JSON.stringify({
54
+ params: templateStepParams.map(p => ({ name: p.name, type: p.type })),
55
+ initialParameterValues,
56
+ })
57
+ }, [templateStepParams, initialParameterValues])
58
+
59
+ // Create initial values only once when component mounts
60
+ const initialValues = useMemo(() => {
61
+ const values: { [key: string]: string | number | boolean | Date } = {}
62
+ // Build a map for quick lookup of initial values by name
63
+ const initialValueMap: Record<string, { value: string; type: StepParameterType }> = {}
64
+ initialParameterValues?.forEach(v => {
65
+ initialValueMap[v.name] = { value: v.value, type: v.type }
66
+ })
67
+ templateStepParams.forEach(param => {
68
+ const initial = initialValueMap[param.name]
69
+ if (initial) {
70
+ switch (param.type) {
71
+ case 'NUMBER':
72
+ values[param.name] = Number(initial.value)
73
+ break
74
+ case 'STRING':
75
+ case 'LOCATOR':
76
+ values[param.name] = initial.value
77
+ break
78
+ case 'DATE':
79
+ // Try to parse date from string
80
+ const date = new Date(initial.value)
81
+ values[param.name] = isNaN(date.getTime()) ? new Date() : date
82
+ break
83
+ case 'BOOLEAN':
84
+ values[param.name] = initial.value === 'true'
85
+ break
86
+ }
87
+ } else {
88
+ // fallback to default
89
+ switch (param.type) {
90
+ case 'NUMBER':
91
+ values[param.name] = 0
92
+ break
93
+ case 'STRING':
94
+ values[param.name] = ''
95
+ break
96
+ case 'DATE':
97
+ values[param.name] = new Date()
98
+ break
99
+ case 'BOOLEAN':
100
+ values[param.name] = false
101
+ break
102
+ case 'LOCATOR':
103
+ values[param.name] = ''
104
+ break
105
+ }
106
+ }
107
+ })
108
+ return values
109
+ }, [templateStepParams, initialParameterValues])
110
+
111
+ // Derive initial locator groups from initialParameterValues (locator name -> group id via locators lookup)
112
+ const initialSelectedLocatorGroups = useMemo(() => {
113
+ const groups: Record<string, string> = {}
114
+ const initialValueMap: Record<string, string> = {}
115
+ initialParameterValues?.forEach(v => {
116
+ initialValueMap[v.name] = v.value
117
+ })
118
+ templateStepParams.forEach(param => {
119
+ if (param.type === 'LOCATOR') {
120
+ const locatorName = initialValueMap[param.name]
121
+ if (locatorName) {
122
+ const locator = locators.find(l => l.name === locatorName)
123
+ if (locator?.locatorGroupId) {
124
+ groups[param.name] = locator.locatorGroupId
125
+ }
126
+ }
127
+ }
128
+ })
129
+ return groups
130
+ }, [templateStepParams, initialParameterValues, locators])
131
+
132
+ // Initialize state with initial values
133
+ const [values, setValues] = useState<{
134
+ [key: string]: string | number | boolean | Date
135
+ }>(initialValues)
136
+ const [errors, setErrors] = useState<Record<string, string>>({})
137
+
138
+ // State for locator group selection (initialized from initial data so edit restores group + locator)
139
+ const [selectedLocatorGroups, setSelectedLocatorGroups] = useState<Record<string, string>>(
140
+ initialSelectedLocatorGroups,
141
+ )
142
+
143
+ useEffect(() => {
144
+ queueMicrotask(() => setErrors({}))
145
+ }, [templateStepParams])
146
+
147
+ // Sync state when initial data changes (e.g. opening edit for a different node)
148
+ useEffect(() => {
149
+ queueMicrotask(() => {
150
+ setValues(initialValues)
151
+ setSelectedLocatorGroups(initialSelectedLocatorGroups)
152
+ })
153
+ }, [initialValues, initialSelectedLocatorGroups])
154
+
155
+ useImperativeHandle(ref, () => ({
156
+ validate: () => {
157
+ // Skip all validation if defaultValueInput is true (all fields are optional)
158
+ if (defaultValueInput) {
159
+ setErrors({})
160
+ return true
161
+ }
162
+
163
+ const newErrors: Record<string, string> = {}
164
+ templateStepParams.forEach(param => {
165
+ const value = values[param.name]
166
+
167
+ if (param.type === 'LOCATOR') {
168
+ const selectedGroup = selectedLocatorGroups[param.name]
169
+ if (!selectedGroup) {
170
+ newErrors[param.name] = 'Locator group is required'
171
+ } else if (!value) {
172
+ newErrors[param.name] = 'Locator is required'
173
+ }
174
+ }
175
+ if (param.type === 'STRING' && !value) {
176
+ newErrors[param.name] = 'This field is required'
177
+ }
178
+ if (param.type === 'NUMBER' && !value) {
179
+ newErrors[param.name] = 'This field is required'
180
+ }
181
+ // Add other validation rules here if needed
182
+ })
183
+ setErrors(newErrors)
184
+ return Object.keys(newErrors).length === 0
185
+ },
186
+ }))
187
+
188
+ // Update values when an input changes
189
+ const handleInputChange = (name: string, value: string | number | boolean | Date) => {
190
+ const newValues = {
191
+ ...values,
192
+ [name]: value,
193
+ }
194
+
195
+ setValues(newValues)
196
+
197
+ // Clear error for the field being edited
198
+ if (errors[name]) {
199
+ const newErrors = { ...errors }
200
+ delete newErrors[name]
201
+ setErrors(newErrors)
202
+ }
203
+
204
+ // Notify parent component of changes
205
+ if (onChange) {
206
+ // We need to calculate formatted values based on the new state
207
+ const formattedValues = templateStepParams.map(param => {
208
+ let stringValue = ''
209
+ // Use the new value if it's the one that changed, otherwise use the current state
210
+ const currentValue = param.name === name ? value : values[param.name]
211
+
212
+ switch (param.type) {
213
+ case 'NUMBER':
214
+ stringValue = currentValue !== undefined && currentValue !== null ? String(currentValue) : ''
215
+ break
216
+ case 'STRING':
217
+ stringValue = currentValue !== undefined && currentValue !== null ? (currentValue as string) : ''
218
+ break
219
+ case 'DATE':
220
+ if (
221
+ currentValue &&
222
+ currentValue instanceof Date &&
223
+ typeof currentValue.getTime === 'function' &&
224
+ !Number.isNaN(currentValue.getTime())
225
+ ) {
226
+ stringValue = format(currentValue as Date, 'PPP')
227
+ } else {
228
+ stringValue = ''
229
+ }
230
+ break
231
+ case 'BOOLEAN':
232
+ stringValue = currentValue !== undefined && currentValue !== null ? String(currentValue) : ''
233
+ break
234
+ case 'LOCATOR':
235
+ stringValue = currentValue !== undefined && currentValue !== null ? (currentValue as string) : ''
236
+ break
237
+ }
238
+
239
+ return {
240
+ name: param.name,
241
+ value: stringValue,
242
+ type: param.type,
243
+ order: param.order,
244
+ }
245
+ })
246
+
247
+ onChange(formattedValues)
248
+ }
249
+ }
250
+
251
+ // Handle locator group selection
252
+ const handleLocatorGroupChange = (paramName: string, groupId: string) => {
253
+ setSelectedLocatorGroups(prev => ({
254
+ ...prev,
255
+ [paramName]: groupId,
256
+ }))
257
+
258
+ // Clear the locator selection when group changes
259
+ setValues(prev => ({
260
+ ...prev,
261
+ [paramName]: '',
262
+ }))
263
+
264
+ // Clear errors for this field
265
+ if (errors[paramName]) {
266
+ const newErrors = { ...errors }
267
+ delete newErrors[paramName]
268
+ setErrors(newErrors)
269
+ }
270
+ }
271
+
272
+ // Get locators for a specific group
273
+ const getLocatorsForGroup = (groupId: string) => {
274
+ return locators.filter(locator => locator.locatorGroupId === groupId)
275
+ }
276
+
277
+ // Render the appropriate input field based on the parameter type
278
+ const renderInputField = (param: TemplateStepParameter) => {
279
+ const { name, type } = param
280
+ const errorMessage = errors[name]
281
+
282
+ switch (type) {
283
+ case 'NUMBER':
284
+ return (
285
+ <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
286
+ <Label htmlFor={`input-${name}`} className="text-primary">
287
+ {defaultValueInput ? `Default ${name}` : name}{' '}
288
+ {!defaultValueInput && <span className="text-red-500">*</span>}
289
+ </Label>
290
+ <Input
291
+ id={`input-${name}`}
292
+ type="number"
293
+ value={typeof values[name] === 'number' ? values[name] : 0}
294
+ onChange={e => handleInputChange(name, Number(e.target.value))}
295
+ className="w-full"
296
+ />
297
+ <ErrorMessage message={errorMessage || ''} visible={!!errorMessage} />
298
+ </div>
299
+ )
300
+
301
+ case 'STRING':
302
+ return (
303
+ <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
304
+ <Label htmlFor={`input-${name}`} className="text-primary">
305
+ {defaultValueInput ? `Default ${name}` : name}{' '}
306
+ {!defaultValueInput && <span className="text-red-500">*</span>}
307
+ </Label>
308
+ <Input
309
+ id={`input-${name}`}
310
+ type="text"
311
+ value={typeof values[name] === 'string' ? values[name] : ''}
312
+ onChange={e => handleInputChange(name, e.target.value)}
313
+ className="w-full"
314
+ />
315
+ <ErrorMessage message={errorMessage || ''} visible={!!errorMessage} />
316
+ </div>
317
+ )
318
+
319
+ case 'DATE':
320
+ return (
321
+ <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
322
+ <Label className="text-primary">
323
+ {defaultValueInput ? `Default ${name}` : name}{' '}
324
+ {!defaultValueInput && <span className="text-red-500">*</span>}
325
+ </Label>
326
+ <Popover>
327
+ <PopoverTrigger asChild>
328
+ <Button
329
+ variant="outline"
330
+ className={cn('w-full justify-start text-left font-normal', !values[name] && 'text-muted-foreground')}
331
+ aria-required={!defaultValueInput}
332
+ >
333
+ <CalendarIcon className="mr-2 h-4 w-4" />
334
+ {values[name] instanceof Date ? (
335
+ format(values[name] as Date, 'PPP')
336
+ ) : (
337
+ <span className={defaultValueInput ? 'text-muted-foreground' : 'text-red-500'}>
338
+ {defaultValueInput ? 'Pick a date (optional)' : 'Pick a date *'}
339
+ </span>
340
+ )}
341
+ </Button>
342
+ </PopoverTrigger>
343
+ <PopoverContent className="w-auto p-0" align="start">
344
+ <Calendar
345
+ mode="single"
346
+ selected={values[name] instanceof Date ? (values[name] as Date) : undefined}
347
+ onSelect={(date: Date | undefined) => handleInputChange(name, date as Date)}
348
+ initialFocus
349
+ required={!defaultValueInput}
350
+ />
351
+ </PopoverContent>
352
+ </Popover>
353
+ </div>
354
+ )
355
+
356
+ case 'BOOLEAN':
357
+ return (
358
+ <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
359
+ <Label htmlFor={`select-${name}`} className="text-primary">
360
+ {defaultValueInput ? `Default ${name}` : name}{' '}
361
+ {!defaultValueInput && <span className="text-red-500">*</span>}
362
+ </Label>
363
+ <Select
364
+ value={typeof values[name] === 'boolean' ? String(values[name]) : 'false'}
365
+ onValueChange={value => handleInputChange(name, value === 'true')}
366
+ required={!defaultValueInput}
367
+ >
368
+ <SelectTrigger id={`select-${name}`} className="w-full">
369
+ <SelectValue placeholder={defaultValueInput ? 'Select a value (optional)' : 'Select a value *'} />
370
+ </SelectTrigger>
371
+ <SelectContent>
372
+ <SelectItem value="true">True</SelectItem>
373
+ <SelectItem value="false">False</SelectItem>
374
+ </SelectContent>
375
+ </Select>
376
+ </div>
377
+ )
378
+
379
+ case 'LOCATOR':
380
+ const selectedGroupId = selectedLocatorGroups[name] || ''
381
+ const availableLocators = selectedGroupId ? getLocatorsForGroup(selectedGroupId) : []
382
+
383
+ return (
384
+ <div className="grid w-full items-center gap-1.5 rounded-md bg-gray-500/10 p-4">
385
+ <Label htmlFor={`select-${name}`} className="text-primary">
386
+ {defaultValueInput ? `Default ${name}` : name}{' '}
387
+ {!defaultValueInput && <span className="text-red-500">*</span>}
388
+ </Label>
389
+
390
+ {/* Locator Group Selection */}
391
+ <div className="mb-2">
392
+ <Label htmlFor={`group-${name}`} className="text-sm text-muted-foreground">
393
+ Locator Group
394
+ </Label>
395
+ <Select
396
+ value={selectedGroupId}
397
+ onValueChange={value => handleLocatorGroupChange(name, value)}
398
+ required={!defaultValueInput}
399
+ >
400
+ <SelectTrigger id={`group-${name}`} className="w-full">
401
+ <SelectValue placeholder="Select a locator group" />
402
+ </SelectTrigger>
403
+ <SelectContent isEmpty={locatorGroups.length === 0}>
404
+ {locatorGroups.map(group => (
405
+ <SelectItem key={group.id} value={group.id}>
406
+ {group.name}
407
+ </SelectItem>
408
+ ))}
409
+ </SelectContent>
410
+ </Select>
411
+ </div>
412
+
413
+ {/* Locator Selection */}
414
+ <div>
415
+ <Label htmlFor={`select-${name}`} className="text-sm text-muted-foreground">
416
+ Locator
417
+ </Label>
418
+ <Select
419
+ value={typeof values[name] === 'string' ? values[name] : ''}
420
+ onValueChange={value => handleInputChange(name, value)}
421
+ required={!defaultValueInput}
422
+ disabled={!selectedGroupId}
423
+ >
424
+ <SelectTrigger id={`select-${name}`} className="w-full">
425
+ <SelectValue
426
+ placeholder={
427
+ !selectedGroupId
428
+ ? 'Select a locator group first'
429
+ : defaultValueInput
430
+ ? 'Select a locator (optional)'
431
+ : 'Select a locator *'
432
+ }
433
+ />
434
+ </SelectTrigger>
435
+ <SelectContent isEmpty={availableLocators.length === 0}>
436
+ {availableLocators.map(locator => (
437
+ <SelectItem key={locator.id} value={locator.name}>
438
+ {locator.name}
439
+ </SelectItem>
440
+ ))}
441
+ </SelectContent>
442
+ </Select>
443
+ </div>
444
+ <ErrorMessage message={errorMessage || ''} visible={!!errorMessage} />
445
+ </div>
446
+ )
447
+
448
+ default:
449
+ return null
450
+ }
451
+ }
452
+
453
+ // Guard: do not render if no parameters
454
+ if (!templateStepParams || templateStepParams.length === 0) {
455
+ return null
456
+ }
457
+
458
+ return (
459
+ <Card className="border-gray-700 bg-transparent shadow-none" key={resetKey}>
460
+ <CardHeader className="py-3">
461
+ <CardTitle className="text-xs font-bold text-primary">Parameters</CardTitle>
462
+ </CardHeader>
463
+ <CardContent>
464
+ <div className="space-y-6">
465
+ {templateStepParams.map(param => (
466
+ <div key={param.name}>{renderInputField(param)}</div>
467
+ ))}
468
+ </div>
469
+ </CardContent>
470
+ </Card>
471
+ )
472
+ })
473
+ DynamicFormFields.displayName = 'DynamicFormFields'
474
+ export default DynamicFormFields