@morscherlab/mint-sdk 1.0.0-alpha.2

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 (491) hide show
  1. package/README.md +326 -0
  2. package/dist/__stories__/experiment-helpers.d.ts +25 -0
  3. package/dist/__tests__/components/AppLayout.test.d.ts +1 -0
  4. package/dist/__tests__/components/AppSidebar.test.d.ts +1 -0
  5. package/dist/__tests__/components/AppTopBar.test.d.ts +1 -0
  6. package/dist/__tests__/components/BaseInput.test.d.ts +1 -0
  7. package/dist/__tests__/components/BasePill.test.d.ts +1 -0
  8. package/dist/__tests__/components/Calendar.test.d.ts +1 -0
  9. package/dist/__tests__/components/CollapsibleCard.test.d.ts +1 -0
  10. package/dist/__tests__/components/DataFrame.test.d.ts +1 -0
  11. package/dist/__tests__/components/DropdownButton.test.d.ts +1 -0
  12. package/dist/__tests__/composables/formBuilderRegistry.test.d.ts +1 -0
  13. package/dist/__tests__/composables/useAppExperiment.test.d.ts +1 -0
  14. package/dist/__tests__/composables/useAuth.test.d.ts +1 -0
  15. package/dist/__tests__/composables/useAutoGroup.test.d.ts +1 -0
  16. package/dist/__tests__/composables/useExperimentData.test.d.ts +13 -0
  17. package/dist/__tests__/composables/useExperimentSave.test.d.ts +1 -0
  18. package/dist/__tests__/composables/useForm.test.d.ts +1 -0
  19. package/dist/__tests__/composables/useFormBuilder.test.d.ts +1 -0
  20. package/dist/__tests__/composables/usePlatformContext.test.d.ts +1 -0
  21. package/dist/__tests__/composables/usePluginApi.test.d.ts +13 -0
  22. package/dist/__tests__/composables/usePluginConfig.test.d.ts +14 -0
  23. package/dist/__tests__/utils/color.test.d.ts +1 -0
  24. package/dist/auth-BYmxZdJl.js +297 -0
  25. package/dist/auth-BYmxZdJl.js.map +1 -0
  26. package/dist/components/AlertBox.vue.d.ts +34 -0
  27. package/dist/components/AppAvatarMenu.vue.d.ts +58 -0
  28. package/dist/components/AppContainer.vue.d.ts +28 -0
  29. package/dist/components/AppLayout.vue.d.ts +31 -0
  30. package/dist/components/AppPageSelector.vue.d.ts +43 -0
  31. package/dist/components/AppPillNav.vue.d.ts +11 -0
  32. package/dist/components/AppPluginSwitcher.vue.d.ts +38 -0
  33. package/dist/components/AppSidebar.vue.d.ts +47 -0
  34. package/dist/components/AppTopBar.vue.d.ts +111 -0
  35. package/dist/components/AuditTrail.vue.d.ts +38 -0
  36. package/dist/components/AutoGroupModal.vue.d.ts +124 -0
  37. package/dist/components/Avatar.vue.d.ts +14 -0
  38. package/dist/components/BaseButton.vue.d.ts +37 -0
  39. package/dist/components/BaseCheckbox.vue.d.ts +17 -0
  40. package/dist/components/BaseInput.vue.d.ts +34 -0
  41. package/dist/components/BaseModal.vue.d.ts +46 -0
  42. package/dist/components/BasePill.vue.d.ts +57 -0
  43. package/dist/components/BaseRadioGroup.vue.d.ts +21 -0
  44. package/dist/components/BaseSelect.vue.d.ts +20 -0
  45. package/dist/components/BaseSlider.vue.d.ts +22 -0
  46. package/dist/components/BaseTabs.vue.d.ts +14 -0
  47. package/dist/components/BaseTextarea.vue.d.ts +30 -0
  48. package/dist/components/BaseToggle.vue.d.ts +19 -0
  49. package/dist/components/BatchProgressList.vue.d.ts +43 -0
  50. package/dist/components/Breadcrumb.vue.d.ts +33 -0
  51. package/dist/components/Calendar.vue.d.ts +107 -0
  52. package/dist/components/ChartContainer.vue.d.ts +31 -0
  53. package/dist/components/ChemicalFormula.vue.d.ts +8 -0
  54. package/dist/components/CollapsibleCard.vue.d.ts +41 -0
  55. package/dist/components/ColorSlider.vue.d.ts +34 -0
  56. package/dist/components/ConcentrationInput.vue.d.ts +25 -0
  57. package/dist/components/ConfirmDialog.vue.d.ts +42 -0
  58. package/dist/components/DataFrame.vue.d.ts +107 -0
  59. package/dist/components/DatePicker.vue.d.ts +25 -0
  60. package/dist/components/DateTimePicker.vue.d.ts +30 -0
  61. package/dist/components/Divider.vue.d.ts +14 -0
  62. package/dist/components/DoseCalculator.vue.d.ts +19 -0
  63. package/dist/components/DropdownButton.vue.d.ts +47 -0
  64. package/dist/components/EmptyState.vue.d.ts +36 -0
  65. package/dist/components/ExperimentCodeBadge.vue.d.ts +14 -0
  66. package/dist/components/ExperimentDataViewer.vue.d.ts +29 -0
  67. package/dist/components/ExperimentPopover.vue.d.ts +32 -0
  68. package/dist/components/ExperimentSelectorModal.vue.d.ts +28 -0
  69. package/dist/components/ExperimentTimeline.vue.d.ts +44 -0
  70. package/dist/components/FileUploader.vue.d.ts +40 -0
  71. package/dist/components/FitPanel.vue.d.ts +46 -0
  72. package/dist/components/FormActions.vue.d.ts +33 -0
  73. package/dist/components/FormBuilder.vue.d.ts +287 -0
  74. package/dist/components/FormField.vue.d.ts +28 -0
  75. package/dist/components/FormFieldRenderer.vue.d.ts +31 -0
  76. package/dist/components/FormSection.vue.d.ts +43 -0
  77. package/dist/components/FormulaInput.vue.d.ts +25 -0
  78. package/dist/components/GroupAssigner.vue.d.ts +25 -0
  79. package/dist/components/GroupingModal.vue.d.ts +12 -0
  80. package/dist/components/IconButton.vue.d.ts +34 -0
  81. package/dist/components/LoadingSpinner.vue.d.ts +12 -0
  82. package/dist/components/MoleculeInput.vue.d.ts +27 -0
  83. package/dist/components/MultiSelect.vue.d.ts +19 -0
  84. package/dist/components/NumberInput.vue.d.ts +22 -0
  85. package/dist/components/PlateMapEditor.vue.d.ts +50 -0
  86. package/dist/components/ProgressBar.vue.d.ts +23 -0
  87. package/dist/components/ProtocolStepEditor.vue.d.ts +24 -0
  88. package/dist/components/RackEditor.vue.d.ts +40 -0
  89. package/dist/components/ReagentEditor.vue.d.ts +30 -0
  90. package/dist/components/ReagentList.vue.d.ts +32 -0
  91. package/dist/components/ResourceCard.vue.d.ts +50 -0
  92. package/dist/components/SampleHierarchyTree.vue.d.ts +26 -0
  93. package/dist/components/SampleLegend.vue.d.ts +32 -0
  94. package/dist/components/SampleSelector.vue.d.ts +29 -0
  95. package/dist/components/ScheduleCalendar.vue.d.ts +110 -0
  96. package/dist/components/ScientificNumber.vue.d.ts +14 -0
  97. package/dist/components/SegmentedControl.vue.d.ts +20 -0
  98. package/dist/components/SequenceInput.vue.d.ts +54 -0
  99. package/dist/components/SettingsButton.vue.d.ts +30 -0
  100. package/dist/components/SettingsModal.vue.d.ts +36 -0
  101. package/dist/components/Skeleton.vue.d.ts +11 -0
  102. package/dist/components/StatusIndicator.vue.d.ts +13 -0
  103. package/dist/components/StepWizard.vue.d.ts +65 -0
  104. package/dist/components/TagsInput.vue.d.ts +39 -0
  105. package/dist/components/ThemeToggle.vue.d.ts +7 -0
  106. package/dist/components/TimePicker.vue.d.ts +29 -0
  107. package/dist/components/TimeRangeInput.vue.d.ts +27 -0
  108. package/dist/components/ToastNotification.vue.d.ts +2 -0
  109. package/dist/components/Tooltip.vue.d.ts +35 -0
  110. package/dist/components/UnitInput.vue.d.ts +39 -0
  111. package/dist/components/WellEditPopup.vue.d.ts +25 -0
  112. package/dist/components/WellPlate.vue.d.ts +73 -0
  113. package/dist/components/index.d.ts +87 -0
  114. package/dist/components/index.js +3 -0
  115. package/dist/components-CKf-UpGi.js +15089 -0
  116. package/dist/components-CKf-UpGi.js.map +1 -0
  117. package/dist/composables/experiment-utils.d.ts +8 -0
  118. package/dist/composables/formBuilderRegistry.d.ts +13 -0
  119. package/dist/composables/index.d.ts +28 -0
  120. package/dist/composables/index.js +3 -0
  121. package/dist/composables/useApi.d.ts +20 -0
  122. package/dist/composables/useAppExperiment.d.ts +37 -0
  123. package/dist/composables/useAsync.d.ts +128 -0
  124. package/dist/composables/useAuth.d.ts +47 -0
  125. package/dist/composables/useAutoGroup.d.ts +106 -0
  126. package/dist/composables/useChemicalFormula.d.ts +21 -0
  127. package/dist/composables/useConcentrationUnits.d.ts +29 -0
  128. package/dist/composables/useDoseCalculator.d.ts +58 -0
  129. package/dist/composables/useExperimentData.d.ts +18 -0
  130. package/dist/composables/useExperimentSave.d.ts +36 -0
  131. package/dist/composables/useExperimentSelector.d.ts +30 -0
  132. package/dist/composables/useForm.d.ts +92 -0
  133. package/dist/composables/useFormBuilder.d.ts +24 -0
  134. package/dist/composables/usePasskey.d.ts +10 -0
  135. package/dist/composables/usePlatformContext.d.ts +131 -0
  136. package/dist/composables/usePluginApi.d.ts +29 -0
  137. package/dist/composables/usePluginConfig.d.ts +13 -0
  138. package/dist/composables/useProtocolTemplates.d.ts +44 -0
  139. package/dist/composables/useRackEditor.d.ts +31 -0
  140. package/dist/composables/useReagentSeries.d.ts +23 -0
  141. package/dist/composables/useScheduleDrag.d.ts +78 -0
  142. package/dist/composables/useSequenceUtils.d.ts +14 -0
  143. package/dist/composables/useTheme.d.ts +8 -0
  144. package/dist/composables/useTimeUtils.d.ts +29 -0
  145. package/dist/composables/useToast.d.ts +22 -0
  146. package/dist/composables/useWellPlateEditor.d.ts +33 -0
  147. package/dist/composables-D0QfFzq1.js +805 -0
  148. package/dist/composables-D0QfFzq1.js.map +1 -0
  149. package/dist/histoire.setup.d.ts +1 -0
  150. package/dist/index.d.ts +6 -0
  151. package/dist/index.js +7 -0
  152. package/dist/install.d.ts +16 -0
  153. package/dist/install.js +23 -0
  154. package/dist/install.js.map +1 -0
  155. package/dist/stores/auth.d.ts +146 -0
  156. package/dist/stores/index.d.ts +2 -0
  157. package/dist/stores/index.js +2 -0
  158. package/dist/stores/settings.d.ts +75 -0
  159. package/dist/styles.css +29728 -0
  160. package/dist/tailwind.preset.d.ts +58 -0
  161. package/dist/tailwind.preset.js +66 -0
  162. package/dist/tailwind.preset.js.map +1 -0
  163. package/dist/types/auth.d.ts +42 -0
  164. package/dist/types/auto-group.d.ts +34 -0
  165. package/dist/types/components.d.ts +528 -0
  166. package/dist/types/form-builder.d.ts +167 -0
  167. package/dist/types/index.d.ts +5 -0
  168. package/dist/types/index.js +0 -0
  169. package/dist/types/platform.d.ts +75 -0
  170. package/dist/useScheduleDrag-DAJueTbK.js +7181 -0
  171. package/dist/useScheduleDrag-DAJueTbK.js.map +1 -0
  172. package/dist/utils/color.d.ts +24 -0
  173. package/package.json +114 -0
  174. package/src/__stories__/experiment-helpers.ts +83 -0
  175. package/src/__tests__/components/AppLayout.test.ts +163 -0
  176. package/src/__tests__/components/AppSidebar.test.ts +292 -0
  177. package/src/__tests__/components/AppTopBar.test.ts +683 -0
  178. package/src/__tests__/components/BaseInput.test.ts +99 -0
  179. package/src/__tests__/components/BasePill.test.ts +291 -0
  180. package/src/__tests__/components/Calendar.test.ts +566 -0
  181. package/src/__tests__/components/CollapsibleCard.test.ts +524 -0
  182. package/src/__tests__/components/DataFrame.test.ts +767 -0
  183. package/src/__tests__/components/DropdownButton.test.ts +471 -0
  184. package/src/__tests__/composables/formBuilderRegistry.test.ts +187 -0
  185. package/src/__tests__/composables/useAppExperiment.test.ts +560 -0
  186. package/src/__tests__/composables/useAuth.test.ts +188 -0
  187. package/src/__tests__/composables/useAutoGroup.test.ts +860 -0
  188. package/src/__tests__/composables/useExperimentData.test.ts +127 -0
  189. package/src/__tests__/composables/useExperimentSave.test.ts +347 -0
  190. package/src/__tests__/composables/useForm.test.ts +205 -0
  191. package/src/__tests__/composables/useFormBuilder.test.ts +917 -0
  192. package/src/__tests__/composables/usePlatformContext.test.ts +116 -0
  193. package/src/__tests__/composables/usePluginApi.test.ts +81 -0
  194. package/src/__tests__/composables/usePluginConfig.test.ts +176 -0
  195. package/src/__tests__/utils/color.test.ts +96 -0
  196. package/src/components/AlertBox.story.vue +204 -0
  197. package/src/components/AlertBox.vue +88 -0
  198. package/src/components/AppAvatarMenu.story.vue +155 -0
  199. package/src/components/AppAvatarMenu.vue +184 -0
  200. package/src/components/AppContainer.story.vue +104 -0
  201. package/src/components/AppContainer.vue +34 -0
  202. package/src/components/AppLayout.story.vue +292 -0
  203. package/src/components/AppLayout.vue +75 -0
  204. package/src/components/AppPageSelector.vue +159 -0
  205. package/src/components/AppPillNav.vue +66 -0
  206. package/src/components/AppPluginSwitcher.vue +241 -0
  207. package/src/components/AppSidebar.story.vue +309 -0
  208. package/src/components/AppSidebar.vue +119 -0
  209. package/src/components/AppTopBar.story.vue +304 -0
  210. package/src/components/AppTopBar.vue +661 -0
  211. package/src/components/AuditTrail.story.vue +163 -0
  212. package/src/components/AuditTrail.vue +151 -0
  213. package/src/components/AutoGroupModal.story.vue +273 -0
  214. package/src/components/AutoGroupModal.vue +566 -0
  215. package/src/components/Avatar.story.vue +115 -0
  216. package/src/components/Avatar.vue +79 -0
  217. package/src/components/BaseButton.story.vue +96 -0
  218. package/src/components/BaseButton.vue +73 -0
  219. package/src/components/BaseCheckbox.story.vue +73 -0
  220. package/src/components/BaseCheckbox.vue +69 -0
  221. package/src/components/BaseInput.story.vue +98 -0
  222. package/src/components/BaseInput.vue +74 -0
  223. package/src/components/BaseModal.story.vue +237 -0
  224. package/src/components/BaseModal.vue +182 -0
  225. package/src/components/BasePill.story.vue +142 -0
  226. package/src/components/BasePill.vue +89 -0
  227. package/src/components/BaseRadioGroup.story.vue +145 -0
  228. package/src/components/BaseRadioGroup.vue +124 -0
  229. package/src/components/BaseSelect.story.vue +120 -0
  230. package/src/components/BaseSelect.vue +71 -0
  231. package/src/components/BaseSlider.story.vue +122 -0
  232. package/src/components/BaseSlider.vue +126 -0
  233. package/src/components/BaseTabs.story.vue +127 -0
  234. package/src/components/BaseTabs.vue +59 -0
  235. package/src/components/BaseTextarea.story.vue +91 -0
  236. package/src/components/BaseTextarea.vue +62 -0
  237. package/src/components/BaseToggle.story.vue +81 -0
  238. package/src/components/BaseToggle.vue +76 -0
  239. package/src/components/BatchProgressList.story.vue +92 -0
  240. package/src/components/BatchProgressList.vue +184 -0
  241. package/src/components/Breadcrumb.story.vue +106 -0
  242. package/src/components/Breadcrumb.vue +75 -0
  243. package/src/components/Calendar.story.vue +106 -0
  244. package/src/components/Calendar.vue +363 -0
  245. package/src/components/ChartContainer.story.vue +113 -0
  246. package/src/components/ChartContainer.vue +64 -0
  247. package/src/components/ChemicalFormula.story.vue +102 -0
  248. package/src/components/ChemicalFormula.vue +39 -0
  249. package/src/components/CollapsibleCard.story.vue +135 -0
  250. package/src/components/CollapsibleCard.vue +167 -0
  251. package/src/components/ColorSlider.story.vue +120 -0
  252. package/src/components/ColorSlider.vue +164 -0
  253. package/src/components/ConcentrationInput.story.vue +77 -0
  254. package/src/components/ConcentrationInput.vue +185 -0
  255. package/src/components/ConfirmDialog.story.vue +248 -0
  256. package/src/components/ConfirmDialog.vue +93 -0
  257. package/src/components/DataFrame.story.vue +148 -0
  258. package/src/components/DataFrame.vue +419 -0
  259. package/src/components/DatePicker.story.vue +119 -0
  260. package/src/components/DatePicker.vue +330 -0
  261. package/src/components/DateTimePicker.story.vue +112 -0
  262. package/src/components/DateTimePicker.vue +392 -0
  263. package/src/components/Divider.story.vue +80 -0
  264. package/src/components/Divider.vue +49 -0
  265. package/src/components/DoseCalculator.story.vue +68 -0
  266. package/src/components/DoseCalculator.vue +476 -0
  267. package/src/components/DropdownButton.story.vue +102 -0
  268. package/src/components/DropdownButton.vue +181 -0
  269. package/src/components/EmptyState.story.vue +135 -0
  270. package/src/components/EmptyState.vue +69 -0
  271. package/src/components/ExperimentCodeBadge.story.vue +77 -0
  272. package/src/components/ExperimentCodeBadge.vue +64 -0
  273. package/src/components/ExperimentDataViewer.story.vue +174 -0
  274. package/src/components/ExperimentDataViewer.vue +288 -0
  275. package/src/components/ExperimentPopover.story.vue +384 -0
  276. package/src/components/ExperimentPopover.vue +241 -0
  277. package/src/components/ExperimentSelectorModal.story.vue +391 -0
  278. package/src/components/ExperimentSelectorModal.vue +387 -0
  279. package/src/components/ExperimentTimeline.story.vue +161 -0
  280. package/src/components/ExperimentTimeline.vue +382 -0
  281. package/src/components/FileUploader.story.vue +107 -0
  282. package/src/components/FileUploader.vue +386 -0
  283. package/src/components/FitPanel.story.vue +125 -0
  284. package/src/components/FitPanel.vue +120 -0
  285. package/src/components/FormActions.vue +92 -0
  286. package/src/components/FormBuilder.vue +214 -0
  287. package/src/components/FormField.story.vue +132 -0
  288. package/src/components/FormField.vue +59 -0
  289. package/src/components/FormFieldRenderer.vue +58 -0
  290. package/src/components/FormSection.vue +90 -0
  291. package/src/components/FormulaInput.story.vue +96 -0
  292. package/src/components/FormulaInput.vue +125 -0
  293. package/src/components/GroupAssigner.story.vue +83 -0
  294. package/src/components/GroupAssigner.vue +284 -0
  295. package/src/components/GroupingModal.story.vue +52 -0
  296. package/src/components/GroupingModal.vue +422 -0
  297. package/src/components/IconButton.story.vue +135 -0
  298. package/src/components/IconButton.vue +73 -0
  299. package/src/components/LoadingSpinner.story.vue +70 -0
  300. package/src/components/LoadingSpinner.vue +50 -0
  301. package/src/components/MoleculeInput.story.vue +66 -0
  302. package/src/components/MoleculeInput.vue +426 -0
  303. package/src/components/MultiSelect.story.vue +132 -0
  304. package/src/components/MultiSelect.vue +118 -0
  305. package/src/components/NumberInput.story.vue +122 -0
  306. package/src/components/NumberInput.vue +160 -0
  307. package/src/components/PlateMapEditor.story.vue +92 -0
  308. package/src/components/PlateMapEditor.vue +513 -0
  309. package/src/components/ProgressBar.story.vue +148 -0
  310. package/src/components/ProgressBar.vue +114 -0
  311. package/src/components/ProtocolStepEditor.story.vue +69 -0
  312. package/src/components/ProtocolStepEditor.vue +522 -0
  313. package/src/components/RackEditor.story.vue +100 -0
  314. package/src/components/RackEditor.vue +371 -0
  315. package/src/components/ReagentEditor.story.vue +153 -0
  316. package/src/components/ReagentEditor.vue +418 -0
  317. package/src/components/ReagentList.story.vue +137 -0
  318. package/src/components/ReagentList.vue +463 -0
  319. package/src/components/ResourceCard.story.vue +150 -0
  320. package/src/components/ResourceCard.vue +161 -0
  321. package/src/components/SampleHierarchyTree.story.vue +161 -0
  322. package/src/components/SampleHierarchyTree.vue +256 -0
  323. package/src/components/SampleLegend.story.vue +91 -0
  324. package/src/components/SampleLegend.vue +119 -0
  325. package/src/components/SampleSelector.story.vue +111 -0
  326. package/src/components/SampleSelector.vue +1033 -0
  327. package/src/components/ScheduleCalendar.story.vue +195 -0
  328. package/src/components/ScheduleCalendar.vue +569 -0
  329. package/src/components/ScientificNumber.story.vue +127 -0
  330. package/src/components/ScientificNumber.vue +197 -0
  331. package/src/components/SegmentedControl.story.vue +132 -0
  332. package/src/components/SegmentedControl.vue +79 -0
  333. package/src/components/SequenceInput.story.vue +119 -0
  334. package/src/components/SequenceInput.vue +209 -0
  335. package/src/components/SettingsButton.story.vue +58 -0
  336. package/src/components/SettingsButton.vue +76 -0
  337. package/src/components/SettingsModal.story.vue +145 -0
  338. package/src/components/SettingsModal.vue +146 -0
  339. package/src/components/Skeleton.story.vue +141 -0
  340. package/src/components/Skeleton.vue +74 -0
  341. package/src/components/StatusIndicator.story.vue +99 -0
  342. package/src/components/StatusIndicator.vue +40 -0
  343. package/src/components/StepWizard.story.vue +155 -0
  344. package/src/components/StepWizard.vue +223 -0
  345. package/src/components/TagsInput.story.vue +155 -0
  346. package/src/components/TagsInput.vue +265 -0
  347. package/src/components/ThemeToggle.story.vue +36 -0
  348. package/src/components/ThemeToggle.vue +54 -0
  349. package/src/components/TimePicker.story.vue +96 -0
  350. package/src/components/TimePicker.vue +273 -0
  351. package/src/components/TimeRangeInput.story.vue +104 -0
  352. package/src/components/TimeRangeInput.vue +122 -0
  353. package/src/components/ToastNotification.story.vue +157 -0
  354. package/src/components/ToastNotification.vue +62 -0
  355. package/src/components/Tooltip.story.vue +138 -0
  356. package/src/components/Tooltip.vue +119 -0
  357. package/src/components/UnitInput.story.vue +194 -0
  358. package/src/components/UnitInput.vue +213 -0
  359. package/src/components/WellEditPopup.vue +234 -0
  360. package/src/components/WellPlate.story.vue +282 -0
  361. package/src/components/WellPlate.vue +830 -0
  362. package/src/components/index.ts +118 -0
  363. package/src/composables/experiment-utils.ts +57 -0
  364. package/src/composables/formBuilderRegistry.ts +79 -0
  365. package/src/composables/index.ts +140 -0
  366. package/src/composables/useApi.ts +167 -0
  367. package/src/composables/useAppExperiment.ts +159 -0
  368. package/src/composables/useAsync.ts +323 -0
  369. package/src/composables/useAuth.ts +445 -0
  370. package/src/composables/useAutoGroup.ts +641 -0
  371. package/src/composables/useChemicalFormula.ts +275 -0
  372. package/src/composables/useConcentrationUnits.ts +246 -0
  373. package/src/composables/useDoseCalculator.ts +370 -0
  374. package/src/composables/useExperimentData.ts +86 -0
  375. package/src/composables/useExperimentSave.ts +192 -0
  376. package/src/composables/useExperimentSelector.ts +292 -0
  377. package/src/composables/useForm.ts +416 -0
  378. package/src/composables/useFormBuilder.ts +383 -0
  379. package/src/composables/usePasskey.ts +216 -0
  380. package/src/composables/usePlatformContext.ts +299 -0
  381. package/src/composables/usePluginApi.ts +39 -0
  382. package/src/composables/usePluginConfig.ts +93 -0
  383. package/src/composables/useProtocolTemplates.ts +518 -0
  384. package/src/composables/useRackEditor.ts +222 -0
  385. package/src/composables/useReagentSeries.ts +91 -0
  386. package/src/composables/useScheduleDrag.ts +245 -0
  387. package/src/composables/useSequenceUtils.ts +105 -0
  388. package/src/composables/useTheme.ts +58 -0
  389. package/src/composables/useTimeUtils.ts +131 -0
  390. package/src/composables/useToast.ts +40 -0
  391. package/src/composables/useWellPlateEditor.ts +421 -0
  392. package/src/histoire.setup.ts +17 -0
  393. package/src/index.ts +367 -0
  394. package/src/install.ts +32 -0
  395. package/src/stores/auth.ts +152 -0
  396. package/src/stores/index.ts +2 -0
  397. package/src/stores/settings.ts +218 -0
  398. package/src/styles/components/alert-box.css +150 -0
  399. package/src/styles/components/app-avatar-menu.css +155 -0
  400. package/src/styles/components/app-container.css +33 -0
  401. package/src/styles/components/app-layout.css +98 -0
  402. package/src/styles/components/app-page-selector.css +191 -0
  403. package/src/styles/components/app-pill-nav.css +57 -0
  404. package/src/styles/components/app-plugin-switcher.css +209 -0
  405. package/src/styles/components/app-sidebar.css +145 -0
  406. package/src/styles/components/app-top-bar.css +492 -0
  407. package/src/styles/components/audit-trail.css +143 -0
  408. package/src/styles/components/auto-group-modal.css +644 -0
  409. package/src/styles/components/avatar.css +73 -0
  410. package/src/styles/components/batch-progress-list.css +196 -0
  411. package/src/styles/components/breadcrumb.css +64 -0
  412. package/src/styles/components/button.css +188 -0
  413. package/src/styles/components/calendar.css +192 -0
  414. package/src/styles/components/chart-container.css +69 -0
  415. package/src/styles/components/checkbox.css +123 -0
  416. package/src/styles/components/chemical-formula.css +46 -0
  417. package/src/styles/components/collapsible-card.css +253 -0
  418. package/src/styles/components/color-slider.css +110 -0
  419. package/src/styles/components/concentration-input.css +156 -0
  420. package/src/styles/components/confirm-dialog.css +183 -0
  421. package/src/styles/components/dataframe.css +382 -0
  422. package/src/styles/components/date-picker.css +243 -0
  423. package/src/styles/components/datetime-picker.css +229 -0
  424. package/src/styles/components/divider.css +63 -0
  425. package/src/styles/components/dose-calculator.css +301 -0
  426. package/src/styles/components/dropdown-button.css +280 -0
  427. package/src/styles/components/empty-state.css +151 -0
  428. package/src/styles/components/experiment-code-badge.css +33 -0
  429. package/src/styles/components/experiment-data-viewer.css +138 -0
  430. package/src/styles/components/experiment-popover.css +562 -0
  431. package/src/styles/components/experiment-selector-modal.css +285 -0
  432. package/src/styles/components/experiment-timeline.css +529 -0
  433. package/src/styles/components/file-uploader.css +310 -0
  434. package/src/styles/components/fit-panel.css +67 -0
  435. package/src/styles/components/form-builder.css +69 -0
  436. package/src/styles/components/form-field.css +48 -0
  437. package/src/styles/components/formula-input.css +103 -0
  438. package/src/styles/components/group-assigner.css +200 -0
  439. package/src/styles/components/grouping-modal.css +323 -0
  440. package/src/styles/components/icon-button.css +192 -0
  441. package/src/styles/components/input.css +66 -0
  442. package/src/styles/components/loading-spinner.css +67 -0
  443. package/src/styles/components/modal.css +350 -0
  444. package/src/styles/components/molecule-input.css +186 -0
  445. package/src/styles/components/multi-select.css +131 -0
  446. package/src/styles/components/number-input.css +199 -0
  447. package/src/styles/components/pill.css +188 -0
  448. package/src/styles/components/plate-map-editor.css +464 -0
  449. package/src/styles/components/progress-bar.css +133 -0
  450. package/src/styles/components/protocol-step-editor.css +449 -0
  451. package/src/styles/components/rack-editor.css +265 -0
  452. package/src/styles/components/radio-group.css +240 -0
  453. package/src/styles/components/reagent-editor.css +510 -0
  454. package/src/styles/components/reagent-list.css +407 -0
  455. package/src/styles/components/resource-card.css +360 -0
  456. package/src/styles/components/sample-hierarchy-tree.css +314 -0
  457. package/src/styles/components/sample-legend.css +201 -0
  458. package/src/styles/components/sample-selector.css +751 -0
  459. package/src/styles/components/schedule-calendar.css +478 -0
  460. package/src/styles/components/scientific-number.css +63 -0
  461. package/src/styles/components/segmented-control.css +197 -0
  462. package/src/styles/components/select.css +77 -0
  463. package/src/styles/components/sequence-input.css +184 -0
  464. package/src/styles/components/settings-button.css +94 -0
  465. package/src/styles/components/settings-modal.css +95 -0
  466. package/src/styles/components/skeleton.css +49 -0
  467. package/src/styles/components/slider.css +74 -0
  468. package/src/styles/components/status-indicator.css +66 -0
  469. package/src/styles/components/step-wizard.css +192 -0
  470. package/src/styles/components/tabs.css +95 -0
  471. package/src/styles/components/tags-input.css +195 -0
  472. package/src/styles/components/textarea.css +82 -0
  473. package/src/styles/components/theme-toggle.css +69 -0
  474. package/src/styles/components/time-picker.css +171 -0
  475. package/src/styles/components/time-range-input.css +42 -0
  476. package/src/styles/components/toast.css +91 -0
  477. package/src/styles/components/toggle.css +146 -0
  478. package/src/styles/components/tooltip.css +91 -0
  479. package/src/styles/components/unit-input.css +123 -0
  480. package/src/styles/components/well-edit-popup.css +252 -0
  481. package/src/styles/components/well-plate.css +307 -0
  482. package/src/styles/index.css +87 -0
  483. package/src/styles/variables.css +1117 -0
  484. package/src/tailwind.preset.ts +61 -0
  485. package/src/types/auth.ts +55 -0
  486. package/src/types/auto-group.ts +40 -0
  487. package/src/types/components.ts +710 -0
  488. package/src/types/form-builder.ts +197 -0
  489. package/src/types/index.ts +207 -0
  490. package/src/types/platform.ts +116 -0
  491. package/src/utils/color.ts +96 -0
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Tests for useExperimentData.
3
+ *
4
+ * Covers the derived computeds plugins rely on when rendering plugin
5
+ * data that was saved via the platform's /api/experiments/{id}/data
6
+ * endpoint:
7
+ *
8
+ * - tree_data / treeData snake-or-camel case handling
9
+ * - table_data / tableData
10
+ * - summary_data validation (must have a `metadata` key)
11
+ * - load / refresh / error paths
12
+ */
13
+
14
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
15
+ import { createPinia, setActivePinia } from 'pinia'
16
+ import axios, { type AxiosRequestConfig } from 'axios'
17
+
18
+ import { useExperimentData } from '../../composables/useExperimentData'
19
+
20
+ let fakeResponse: unknown = null
21
+ let fakeError: Error | null = null
22
+ let recordedUrls: string[] = []
23
+
24
+ describe('useExperimentData', () => {
25
+ beforeEach(() => {
26
+ setActivePinia(createPinia())
27
+ fakeResponse = {}
28
+ fakeError = null
29
+ recordedUrls = []
30
+ vi.spyOn(axios.Axios.prototype, 'get').mockImplementation(async function (
31
+ this: unknown,
32
+ url: string,
33
+ _config?: AxiosRequestConfig,
34
+ ) {
35
+ recordedUrls.push(url)
36
+ if (fakeError) throw fakeError
37
+ return { data: fakeResponse }
38
+ })
39
+ })
40
+
41
+ afterEach(() => {
42
+ vi.restoreAllMocks()
43
+ })
44
+
45
+ it('fetch resolves data and flips isLoading', async () => {
46
+ fakeResponse = { tree_data: [{ id: 'a', label: 'A' }] }
47
+ const hook = useExperimentData()
48
+
49
+ expect(hook.isLoading.value).toBe(false)
50
+ const p = hook.fetch(42)
51
+ expect(hook.isLoading.value).toBe(true)
52
+ await p
53
+ expect(hook.isLoading.value).toBe(false)
54
+ expect(hook.data.value).toEqual({ tree_data: [{ id: 'a', label: 'A' }] })
55
+ expect(recordedUrls[0]).toBe('/experiments/42/data')
56
+ })
57
+
58
+ it('exposes tree_data via treeData regardless of snake vs camel', async () => {
59
+ fakeResponse = { tree_data: [{ id: '1' }] }
60
+ const hook1 = useExperimentData()
61
+ await hook1.fetch(1)
62
+ expect(hook1.treeData.value).toEqual([{ id: '1' }])
63
+
64
+ fakeResponse = { treeData: [{ id: '2' }] }
65
+ const hook2 = useExperimentData()
66
+ await hook2.fetch(2)
67
+ expect(hook2.treeData.value).toEqual([{ id: '2' }])
68
+ })
69
+
70
+ it('tableData falls back to empty array when absent', async () => {
71
+ fakeResponse = {}
72
+ const hook = useExperimentData()
73
+ await hook.fetch(1)
74
+ expect(hook.tableData.value).toEqual([])
75
+ })
76
+
77
+ it('summaryData requires a metadata key', async () => {
78
+ fakeResponse = { summary_data: { metadata: { n: 1 }, sections: [] } }
79
+ const hook = useExperimentData()
80
+ await hook.fetch(1)
81
+ expect(hook.summaryData.value).toEqual({
82
+ metadata: { n: 1 },
83
+ sections: [],
84
+ })
85
+
86
+ // Invalid summary shape -> returns null
87
+ fakeResponse = { summary_data: { notmeta: true } }
88
+ const hook2 = useExperimentData()
89
+ await hook2.fetch(2)
90
+ expect(hook2.summaryData.value).toBeNull()
91
+ })
92
+
93
+ it('refresh reuses the last experimentId', async () => {
94
+ fakeResponse = { tree_data: [] }
95
+ const hook = useExperimentData()
96
+ await hook.fetch(99)
97
+ recordedUrls.length = 0
98
+
99
+ await hook.refresh()
100
+ expect(recordedUrls).toEqual(['/experiments/99/data'])
101
+ })
102
+
103
+ it('refresh before any fetch is a noop', async () => {
104
+ const hook = useExperimentData()
105
+ await hook.refresh()
106
+ expect(recordedUrls).toEqual([])
107
+ })
108
+
109
+ it('sets error and clears data on network failure', async () => {
110
+ fakeError = new Error('boom')
111
+ const hook = useExperimentData()
112
+ await hook.fetch(1)
113
+ expect(hook.error.value).toBe('boom')
114
+ expect(hook.data.value).toBeNull()
115
+ expect(hook.isLoading.value).toBe(false)
116
+ })
117
+
118
+ it('apiBaseUrl option is honoured', async () => {
119
+ fakeResponse = {}
120
+ const hook = useExperimentData({ apiBaseUrl: '/api/plugin' })
121
+ await hook.fetch(5)
122
+ // useApi attaches baseURL to the request config; the mocked axios
123
+ // call records it in recordedUrls as the relative URL only, so we
124
+ // can't observe baseURL directly here — assert the URL shape.
125
+ expect(recordedUrls[0]).toBe('/experiments/5/data')
126
+ })
127
+ })
@@ -0,0 +1,347 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Mock useApi — intercepts all HTTP calls
5
+ // ---------------------------------------------------------------------------
6
+
7
+ const mockGet = vi.fn()
8
+ const mockPut = vi.fn()
9
+ const mockDelete = vi.fn()
10
+
11
+ vi.mock('../../composables/useApi', () => ({
12
+ useApi: () => ({
13
+ get: mockGet,
14
+ put: mockPut,
15
+ delete: mockDelete,
16
+ }),
17
+ }))
18
+
19
+ import { useExperimentSave } from '../../composables/useExperimentSave'
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Helpers
23
+ // ---------------------------------------------------------------------------
24
+
25
+ function setup(options?: Parameters<typeof useExperimentSave>[0]) {
26
+ return useExperimentSave(options)
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Tests
31
+ // ---------------------------------------------------------------------------
32
+
33
+ describe('useExperimentSave', () => {
34
+ beforeEach(() => {
35
+ vi.clearAllMocks()
36
+ mockGet.mockResolvedValue({})
37
+ mockPut.mockResolvedValue({})
38
+ mockDelete.mockResolvedValue({})
39
+ })
40
+
41
+ // -------------------------------------------------------------------------
42
+ // saveDesign
43
+ // -------------------------------------------------------------------------
44
+
45
+ describe('saveDesign', () => {
46
+ it('should call PUT /experiments/{id}/data with correct body', async () => {
47
+ const { saveDesign } = setup({ pluginId: 'my-plugin', schemaVersion: '2.0' })
48
+
49
+ await saveDesign(42, { layout: 'grid' })
50
+
51
+ expect(mockPut).toHaveBeenCalledOnce()
52
+ expect(mockPut).toHaveBeenCalledWith('/experiments/42/data', {
53
+ plugin_id: 'my-plugin',
54
+ data: { layout: 'grid' },
55
+ schema_version: '2.0',
56
+ })
57
+ })
58
+
59
+ it('should use default schema_version "1.0" when not provided', async () => {
60
+ const { saveDesign } = setup({ pluginId: 'p1' })
61
+
62
+ await saveDesign(1, { x: 1 })
63
+
64
+ const body = mockPut.mock.calls[0][1]
65
+ expect(body.schema_version).toBe('1.0')
66
+ })
67
+
68
+ it('should return true on success', async () => {
69
+ const { saveDesign } = setup({ pluginId: 'p1' })
70
+ const ok = await saveDesign(1, { x: 1 })
71
+ expect(ok).toBe(true)
72
+ })
73
+
74
+ it('should return false on error', async () => {
75
+ mockPut.mockRejectedValueOnce(new Error('Network error'))
76
+ const { saveDesign } = setup({ pluginId: 'p1' })
77
+ const ok = await saveDesign(1, { x: 1 })
78
+ expect(ok).toBe(false)
79
+ })
80
+
81
+ it('should set isSaving during the call', async () => {
82
+ let resolvePut!: (v: unknown) => void
83
+ mockPut.mockImplementationOnce(() => new Promise((r) => { resolvePut = r }))
84
+
85
+ const { saveDesign, isSaving } = setup({ pluginId: 'p1' })
86
+ expect(isSaving.value).toBe(false)
87
+
88
+ const promise = saveDesign(1, { x: 1 })
89
+ expect(isSaving.value).toBe(true)
90
+
91
+ resolvePut({})
92
+ await promise
93
+ expect(isSaving.value).toBe(false)
94
+ })
95
+
96
+ it('should set error on failure', async () => {
97
+ mockPut.mockRejectedValueOnce(new Error('Bad request'))
98
+ const { saveDesign, error } = setup({ pluginId: 'p1' })
99
+
100
+ await saveDesign(1, { x: 1 })
101
+
102
+ expect(error.value).toBe('Bad request')
103
+ })
104
+
105
+ it('should clear previous error on success', async () => {
106
+ mockPut.mockRejectedValueOnce(new Error('First fail'))
107
+ const { saveDesign, error } = setup({ pluginId: 'p1' })
108
+
109
+ await saveDesign(1, { x: 1 })
110
+ expect(error.value).toBe('First fail')
111
+
112
+ mockPut.mockResolvedValueOnce({})
113
+ await saveDesign(1, { x: 1 })
114
+ expect(error.value).toBeNull()
115
+ })
116
+
117
+ it('should update lastSavedAt on success', async () => {
118
+ const { saveDesign, lastSavedAt } = setup({ pluginId: 'p1' })
119
+ expect(lastSavedAt.value).toBeNull()
120
+
121
+ await saveDesign(1, { x: 1 })
122
+
123
+ expect(lastSavedAt.value).toBeInstanceOf(Date)
124
+ })
125
+
126
+ it('should not update lastSavedAt on failure', async () => {
127
+ mockPut.mockRejectedValueOnce(new Error('fail'))
128
+ const { saveDesign, lastSavedAt } = setup({ pluginId: 'p1' })
129
+
130
+ await saveDesign(1, { x: 1 })
131
+
132
+ expect(lastSavedAt.value).toBeNull()
133
+ })
134
+
135
+ it('should require pluginId', async () => {
136
+ const { saveDesign } = setup()
137
+ const ok = await saveDesign(1, { x: 1 })
138
+ expect(ok).toBe(false)
139
+ })
140
+ })
141
+
142
+ // -------------------------------------------------------------------------
143
+ // saveAnalysis
144
+ // -------------------------------------------------------------------------
145
+
146
+ describe('saveAnalysis', () => {
147
+ it('should call PUT /experiments/{id}/results/{pluginId}', async () => {
148
+ const { saveAnalysis } = setup({ pluginId: 'my-plugin' })
149
+
150
+ await saveAnalysis(42, { score: 0.95 })
151
+
152
+ expect(mockPut).toHaveBeenCalledOnce()
153
+ expect(mockPut).toHaveBeenCalledWith(
154
+ '/experiments/42/results/my-plugin',
155
+ { result: { score: 0.95 } },
156
+ )
157
+ })
158
+
159
+ it('should return true on success', async () => {
160
+ const { saveAnalysis } = setup({ pluginId: 'p1' })
161
+ const ok = await saveAnalysis(1, { score: 1 })
162
+ expect(ok).toBe(true)
163
+ })
164
+
165
+ it('should return false on error', async () => {
166
+ mockPut.mockRejectedValueOnce(new Error('fail'))
167
+ const { saveAnalysis } = setup({ pluginId: 'p1' })
168
+ const ok = await saveAnalysis(1, { score: 1 })
169
+ expect(ok).toBe(false)
170
+ })
171
+
172
+ it('should require pluginId', async () => {
173
+ const { saveAnalysis } = setup()
174
+ const ok = await saveAnalysis(1, { score: 1 })
175
+ expect(ok).toBe(false)
176
+ })
177
+ })
178
+
179
+ // -------------------------------------------------------------------------
180
+ // save (combined)
181
+ // -------------------------------------------------------------------------
182
+
183
+ describe('save', () => {
184
+ it('should call both endpoints when design and analysis provided', async () => {
185
+ const { save } = setup({ pluginId: 'p1', schemaVersion: '2.0' })
186
+
187
+ const ok = await save(1, {
188
+ design: { layout: 'grid' },
189
+ analysis: { score: 0.9 },
190
+ })
191
+
192
+ expect(ok).toBe(true)
193
+ expect(mockPut).toHaveBeenCalledTimes(2)
194
+ })
195
+
196
+ it('should call only design endpoint when analysis is omitted', async () => {
197
+ const { save } = setup({ pluginId: 'p1' })
198
+
199
+ await save(1, { design: { x: 1 } })
200
+
201
+ expect(mockPut).toHaveBeenCalledTimes(1)
202
+ expect(mockPut.mock.calls[0][0]).toBe('/experiments/1/data')
203
+ })
204
+
205
+ it('should call only analysis endpoint when design is omitted', async () => {
206
+ const { save } = setup({ pluginId: 'p1' })
207
+
208
+ await save(1, { analysis: { score: 1 } })
209
+
210
+ expect(mockPut).toHaveBeenCalledTimes(1)
211
+ expect(mockPut.mock.calls[0][0]).toBe('/experiments/1/results/p1')
212
+ })
213
+
214
+ it('should return false if either call fails', async () => {
215
+ mockPut
216
+ .mockResolvedValueOnce({}) // design succeeds
217
+ .mockRejectedValueOnce(new Error()) // analysis fails
218
+
219
+ const { save } = setup({ pluginId: 'p1' })
220
+ const ok = await save(1, {
221
+ design: { x: 1 },
222
+ analysis: { y: 2 },
223
+ })
224
+
225
+ expect(ok).toBe(false)
226
+ })
227
+ })
228
+
229
+ // -------------------------------------------------------------------------
230
+ // loadDesign
231
+ // -------------------------------------------------------------------------
232
+
233
+ describe('loadDesign', () => {
234
+ it('should call GET /experiments/{id}/data', async () => {
235
+ mockGet.mockResolvedValueOnce({ data: { key: 'value' } })
236
+ const { loadDesign } = setup()
237
+
238
+ await loadDesign(42)
239
+
240
+ expect(mockGet).toHaveBeenCalledOnce()
241
+ expect(mockGet).toHaveBeenCalledWith('/experiments/42/data')
242
+ })
243
+
244
+ it('should return the data on success', async () => {
245
+ mockGet.mockResolvedValueOnce({ data: { key: 'value' } })
246
+ const { loadDesign } = setup()
247
+
248
+ const result = await loadDesign(1)
249
+ expect(result).toEqual({ data: { key: 'value' } })
250
+ })
251
+
252
+ it('should return null on error', async () => {
253
+ mockGet.mockRejectedValueOnce(new Error('not found'))
254
+ const { loadDesign } = setup()
255
+
256
+ const result = await loadDesign(999)
257
+ expect(result).toBeNull()
258
+ })
259
+ })
260
+
261
+ // -------------------------------------------------------------------------
262
+ // loadAnalysis
263
+ // -------------------------------------------------------------------------
264
+
265
+ describe('loadAnalysis', () => {
266
+ it('should call GET /experiments/{id}/results/{pluginId}', async () => {
267
+ mockGet.mockResolvedValueOnce({ result: { score: 1 } })
268
+ const { loadAnalysis } = setup({ pluginId: 'p1' })
269
+
270
+ await loadAnalysis(42)
271
+
272
+ expect(mockGet).toHaveBeenCalledOnce()
273
+ expect(mockGet).toHaveBeenCalledWith('/experiments/42/results/p1')
274
+ })
275
+
276
+ it('should return the result on success', async () => {
277
+ mockGet.mockResolvedValueOnce({ result: { score: 0.5 } })
278
+ const { loadAnalysis } = setup({ pluginId: 'p1' })
279
+
280
+ const result = await loadAnalysis(1)
281
+ expect(result).toEqual({ result: { score: 0.5 } })
282
+ })
283
+
284
+ it('should return null on error', async () => {
285
+ mockGet.mockRejectedValueOnce(new Error('not found'))
286
+ const { loadAnalysis } = setup({ pluginId: 'p1' })
287
+
288
+ const result = await loadAnalysis(999)
289
+ expect(result).toBeNull()
290
+ })
291
+
292
+ it('should return null when pluginId not set', async () => {
293
+ const { loadAnalysis } = setup()
294
+ const result = await loadAnalysis(1)
295
+ expect(result).toBeNull()
296
+ })
297
+ })
298
+
299
+ // -------------------------------------------------------------------------
300
+ // deleteDesign
301
+ // -------------------------------------------------------------------------
302
+
303
+ describe('deleteDesign', () => {
304
+ it('should call DELETE /experiments/{id}/data', async () => {
305
+ const { deleteDesign } = setup()
306
+
307
+ await deleteDesign(42)
308
+
309
+ expect(mockDelete).toHaveBeenCalledOnce()
310
+ expect(mockDelete).toHaveBeenCalledWith('/experiments/42/data')
311
+ })
312
+
313
+ it('should return true on success', async () => {
314
+ const { deleteDesign } = setup()
315
+ const ok = await deleteDesign(1)
316
+ expect(ok).toBe(true)
317
+ })
318
+
319
+ it('should return false on error', async () => {
320
+ mockDelete.mockRejectedValueOnce(new Error('fail'))
321
+ const { deleteDesign } = setup()
322
+ const ok = await deleteDesign(1)
323
+ expect(ok).toBe(false)
324
+ })
325
+ })
326
+
327
+ // -------------------------------------------------------------------------
328
+ // deleteAnalysis
329
+ // -------------------------------------------------------------------------
330
+
331
+ describe('deleteAnalysis', () => {
332
+ it('should call DELETE /experiments/{id}/results/{pluginId}', async () => {
333
+ const { deleteAnalysis } = setup({ pluginId: 'p1' })
334
+
335
+ await deleteAnalysis(42)
336
+
337
+ expect(mockDelete).toHaveBeenCalledOnce()
338
+ expect(mockDelete).toHaveBeenCalledWith('/experiments/42/results/p1')
339
+ })
340
+
341
+ it('should return false when pluginId not set', async () => {
342
+ const { deleteAnalysis } = setup()
343
+ const ok = await deleteAnalysis(1)
344
+ expect(ok).toBe(false)
345
+ })
346
+ })
347
+ })
@@ -0,0 +1,205 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+
3
+ // Mock vue lifecycle hooks since we're not in a component
4
+ vi.mock('vue', async () => {
5
+ const actual = await vi.importActual('vue')
6
+ return {
7
+ ...actual,
8
+ onMounted: vi.fn(),
9
+ onUnmounted: vi.fn(),
10
+ }
11
+ })
12
+
13
+ import { useForm } from '../../composables/useForm'
14
+
15
+ describe('useForm', () => {
16
+ describe('initialization', () => {
17
+ it('should initialize with provided values', () => {
18
+ const { data } = useForm({ name: '', email: '' })
19
+ expect(data.name).toBe('')
20
+ expect(data.email).toBe('')
21
+ })
22
+
23
+ it('should start with no errors', () => {
24
+ const { errors } = useForm({ name: '' })
25
+ expect(errors.name).toBeNull()
26
+ })
27
+
28
+ it('should start as not dirty', () => {
29
+ const { isDirty } = useForm({ name: '' })
30
+ expect(isDirty.value).toBe(false)
31
+ })
32
+ })
33
+
34
+ describe('validation - required', () => {
35
+ it('should fail for empty required field', () => {
36
+ const { validateField, errors } = useForm(
37
+ { name: '' },
38
+ { name: { required: true } }
39
+ )
40
+ const valid = validateField('name')
41
+ expect(valid).toBe(false)
42
+ expect(errors.name).toBe('This field is required')
43
+ })
44
+
45
+ it('should pass for filled required field', () => {
46
+ const { data, validateField, errors } = useForm(
47
+ { name: '' },
48
+ { name: { required: true } }
49
+ )
50
+ data.name = 'Alice'
51
+ const valid = validateField('name')
52
+ expect(valid).toBe(true)
53
+ expect(errors.name).toBeNull()
54
+ })
55
+
56
+ it('should use custom required message', () => {
57
+ const { validateField, errors } = useForm(
58
+ { name: '' },
59
+ { name: { required: 'Name is required' } }
60
+ )
61
+ validateField('name')
62
+ expect(errors.name).toBe('Name is required')
63
+ })
64
+ })
65
+
66
+ describe('validation - email', () => {
67
+ it('should reject invalid email', () => {
68
+ const { data, validateField, errors } = useForm(
69
+ { email: '' },
70
+ { email: { email: true } }
71
+ )
72
+ data.email = 'notanemail'
73
+ const valid = validateField('email')
74
+ expect(valid).toBe(false)
75
+ expect(errors.email).toBe('Invalid email address')
76
+ })
77
+
78
+ it('should accept valid email', () => {
79
+ const { data, validateField, errors } = useForm(
80
+ { email: '' },
81
+ { email: { email: true } }
82
+ )
83
+ data.email = 'user@example.com'
84
+ const valid = validateField('email')
85
+ expect(valid).toBe(true)
86
+ expect(errors.email).toBeNull()
87
+ })
88
+ })
89
+
90
+ describe('validation - minLength', () => {
91
+ it('should reject short strings', () => {
92
+ const { data, validateField, errors } = useForm(
93
+ { password: '' },
94
+ { password: { minLength: 8 } }
95
+ )
96
+ data.password = 'abc'
97
+ const valid = validateField('password')
98
+ expect(valid).toBe(false)
99
+ expect(errors.password).toBe('Must be at least 8 characters')
100
+ })
101
+ })
102
+
103
+ describe('validation - pattern', () => {
104
+ it('should validate with regex', () => {
105
+ const { data, validateField, errors } = useForm(
106
+ { code: '' },
107
+ { code: { pattern: /^[A-Z]{3}$/ } }
108
+ )
109
+ data.code = 'ab'
110
+ expect(validateField('code')).toBe(false)
111
+ expect(errors.code).toBe('Invalid format')
112
+
113
+ data.code = 'ABC'
114
+ expect(validateField('code')).toBe(true)
115
+ })
116
+ })
117
+
118
+ describe('validation - custom', () => {
119
+ it('should support custom validator', () => {
120
+ const { data, validateField, errors } = useForm(
121
+ { password: '', confirm: '' },
122
+ {
123
+ confirm: {
124
+ custom: (value, formData) =>
125
+ value !== formData.password ? 'Passwords must match' : undefined,
126
+ },
127
+ }
128
+ )
129
+ data.password = 'secret123'
130
+ data.confirm = 'different'
131
+ expect(validateField('confirm')).toBe(false)
132
+ expect(errors.confirm).toBe('Passwords must match')
133
+
134
+ data.confirm = 'secret123'
135
+ expect(validateField('confirm')).toBe(true)
136
+ })
137
+ })
138
+
139
+ describe('validate (all fields)', () => {
140
+ it('should validate all fields at once', () => {
141
+ const { validate, errors } = useForm(
142
+ { name: '', email: '' },
143
+ { name: { required: true }, email: { required: true } }
144
+ )
145
+ const valid = validate()
146
+ expect(valid).toBe(false)
147
+ expect(errors.name).not.toBeNull()
148
+ expect(errors.email).not.toBeNull()
149
+ })
150
+ })
151
+
152
+ describe('setFieldValue', () => {
153
+ it('should update field value', () => {
154
+ const { data, setFieldValue } = useForm({ name: '' })
155
+ setFieldValue('name', 'Bob')
156
+ expect(data.name).toBe('Bob')
157
+ })
158
+ })
159
+
160
+ describe('reset', () => {
161
+ it('should reset to initial values', () => {
162
+ const { data, errors, touched, dirty, reset, setFieldTouched } = useForm(
163
+ { name: 'initial' },
164
+ { name: { required: true } }
165
+ )
166
+ data.name = 'changed'
167
+ setFieldTouched('name')
168
+ reset()
169
+ expect(data.name).toBe('initial')
170
+ expect(errors.name).toBeNull()
171
+ expect(touched.name).toBe(false)
172
+ expect(dirty.name).toBe(false)
173
+ })
174
+ })
175
+
176
+ describe('dirty tracking', () => {
177
+ it('should track dirty state on setFieldValue', () => {
178
+ const { setFieldValue } = useForm({ name: 'original' })
179
+ setFieldValue('name', 'changed')
180
+ // Note: dirty tracking happens via watch, which may be async in tests
181
+ // setFieldValue directly mutates data, watch triggers next tick
182
+ })
183
+ })
184
+
185
+ describe('handleSubmit', () => {
186
+ it('should call onSubmit when valid', async () => {
187
+ const onSubmit = vi.fn()
188
+ const { data, handleSubmit } = useForm({ name: 'Alice' })
189
+ const handler = handleSubmit(onSubmit)
190
+ await handler()
191
+ expect(onSubmit).toHaveBeenCalledWith(data)
192
+ })
193
+
194
+ it('should not call onSubmit when invalid', async () => {
195
+ const onSubmit = vi.fn()
196
+ const { handleSubmit } = useForm(
197
+ { name: '' },
198
+ { name: { required: true } }
199
+ )
200
+ const handler = handleSubmit(onSubmit)
201
+ await handler()
202
+ expect(onSubmit).not.toHaveBeenCalled()
203
+ })
204
+ })
205
+ })