@morscherlab/mint-sdk 1.0.0-beta.2 → 1.0.0-beta.3

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 (399) hide show
  1. package/README.md +218 -6
  2. package/dist/__tests__/components/ActionItem.test.d.ts +1 -0
  3. package/dist/__tests__/components/AppAvatarMenu.test.d.ts +1 -0
  4. package/dist/__tests__/components/AppPageSelector.test.d.ts +1 -0
  5. package/dist/__tests__/components/AppPillNav.test.d.ts +1 -0
  6. package/dist/__tests__/components/AppPluginSwitcher.test.d.ts +1 -0
  7. package/dist/__tests__/components/AppToastContainer.test.d.ts +1 -0
  8. package/dist/__tests__/components/BaseRadioGroup.test.d.ts +1 -0
  9. package/dist/__tests__/components/BaseSelect.test.d.ts +1 -0
  10. package/dist/__tests__/components/BaseTabs.test.d.ts +1 -0
  11. package/dist/__tests__/components/BatchProgressList.test.d.ts +1 -0
  12. package/dist/__tests__/components/BioTemplateExperimentWorkspaceView.test.d.ts +1 -0
  13. package/dist/__tests__/components/BioTemplatePackWorkspaceView.test.d.ts +1 -0
  14. package/dist/__tests__/components/BioTemplatePresetWorkspaceView.test.d.ts +1 -0
  15. package/dist/__tests__/components/BioTemplateRenderer.test.d.ts +1 -0
  16. package/dist/__tests__/components/Breadcrumb.test.d.ts +1 -0
  17. package/dist/__tests__/components/CalendarGridPanel.test.d.ts +1 -0
  18. package/dist/__tests__/components/ConcentrationInput.test.d.ts +1 -0
  19. package/dist/__tests__/components/ControlWorkspaceView.test.d.ts +1 -0
  20. package/dist/__tests__/components/DatePicker.test.d.ts +1 -0
  21. package/dist/__tests__/components/DateTimePicker.test.d.ts +1 -0
  22. package/dist/__tests__/components/EmptyState.test.d.ts +1 -0
  23. package/dist/__tests__/components/ExperimentPopover.test.d.ts +1 -0
  24. package/dist/__tests__/components/FormBuilder.test.d.ts +1 -0
  25. package/dist/__tests__/components/FormCompatibility.test.d.ts +1 -0
  26. package/dist/__tests__/components/GroupAssigner.test.d.ts +1 -0
  27. package/dist/__tests__/components/GroupingModal.test.d.ts +1 -0
  28. package/dist/__tests__/components/MultiSelect.test.d.ts +1 -0
  29. package/dist/__tests__/components/ProtocolStepEditor.test.d.ts +1 -0
  30. package/dist/__tests__/components/ReagentList.test.d.ts +1 -0
  31. package/dist/__tests__/components/SampleHierarchyTree.test.d.ts +1 -0
  32. package/dist/__tests__/components/SampleSelector.test.d.ts +1 -0
  33. package/dist/__tests__/components/SegmentedControl.test.d.ts +1 -0
  34. package/dist/__tests__/components/SettingsButton.test.d.ts +1 -0
  35. package/dist/__tests__/components/SettingsModal.test.d.ts +1 -0
  36. package/dist/__tests__/components/TagsInput.test.d.ts +1 -0
  37. package/dist/__tests__/components/ThemeToggle.test.d.ts +1 -0
  38. package/dist/__tests__/components/TimePicker.test.d.ts +1 -0
  39. package/dist/__tests__/composables/useBioTemplatePackWorkspace.test.d.ts +1 -0
  40. package/dist/__tests__/composables/useBioTemplatePresetWorkspace.test.d.ts +1 -0
  41. package/dist/__tests__/composables/useBioTemplateWorkspace.test.d.ts +1 -0
  42. package/dist/__tests__/composables/useCalendarGrid.test.d.ts +1 -0
  43. package/dist/__tests__/composables/useControlSchema.test.d.ts +1 -0
  44. package/dist/__tests__/composables/useDebouncedWatch.test.d.ts +1 -0
  45. package/dist/__tests__/composables/useDropdownState.test.d.ts +1 -0
  46. package/dist/__tests__/composables/useEventListener.test.d.ts +1 -0
  47. package/dist/__tests__/composables/useExpansionSet.test.d.ts +1 -0
  48. package/dist/__tests__/composables/useExperimentData.test.d.ts +1 -0
  49. package/dist/__tests__/composables/useExperimentSelector.test.d.ts +1 -0
  50. package/dist/__tests__/composables/useGroupAssignment.test.d.ts +1 -0
  51. package/dist/__tests__/composables/useListSelection.test.d.ts +1 -0
  52. package/dist/__tests__/composables/usePluginClient.test.d.ts +1 -0
  53. package/dist/__tests__/composables/usePluginConfig.test.d.ts +1 -0
  54. package/dist/__tests__/composables/useRequestSyncState.test.d.ts +1 -0
  55. package/dist/__tests__/composables/useSampleGroups.test.d.ts +1 -0
  56. package/dist/__tests__/composables/useSelectionLimit.test.d.ts +1 -0
  57. package/dist/__tests__/composables/useSortedItems.test.d.ts +1 -0
  58. package/dist/__tests__/composables/useTemplateCollection.test.d.ts +1 -0
  59. package/dist/__tests__/composables/useTextSearch.test.d.ts +1 -0
  60. package/dist/__tests__/composables/useTheme.test.d.ts +1 -0
  61. package/dist/__tests__/composables/useTimeUtils.test.d.ts +1 -0
  62. package/dist/__tests__/docs/frontendDocsCatalog.test.d.ts +1 -0
  63. package/dist/__tests__/templates/templates.test.d.ts +1 -0
  64. package/dist/{auth-DsI0rQ7_.js → auth-QQj2kkze.js} +12 -5
  65. package/dist/auth-QQj2kkze.js.map +1 -0
  66. package/dist/components/ActionItem.vue.d.ts +32 -0
  67. package/dist/components/AppAvatarMenu.vue.d.ts +2 -7
  68. package/dist/components/AppPageSelector.vue.d.ts +3 -6
  69. package/dist/components/AppPillNav.vue.d.ts +2 -2
  70. package/dist/components/AppSidebar.vue.d.ts +56 -3
  71. package/dist/components/AppToastContainer.vue.d.ts +2 -0
  72. package/dist/components/AppTopBar.vue.d.ts +41 -10
  73. package/dist/components/BaseButton.vue.d.ts +1 -1
  74. package/dist/components/BaseCheckbox.vue.d.ts +1 -1
  75. package/dist/components/BaseInput.vue.d.ts +2 -2
  76. package/dist/components/BasePill.vue.d.ts +2 -2
  77. package/dist/components/BaseRadioGroup.vue.d.ts +3 -3
  78. package/dist/components/BaseSelect.vue.d.ts +3 -3
  79. package/dist/components/BaseTabs.vue.d.ts +2 -2
  80. package/dist/components/BaseTextarea.vue.d.ts +1 -1
  81. package/dist/components/BaseToggle.vue.d.ts +1 -1
  82. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +117 -0
  83. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +92 -0
  84. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +82 -0
  85. package/dist/components/BioTemplateRenderer.vue.d.ts +29 -0
  86. package/dist/components/Breadcrumb.vue.d.ts +2 -2
  87. package/dist/components/Calendar.vue.d.ts +1 -1
  88. package/dist/components/CalendarGridPanel.vue.d.ts +25 -0
  89. package/dist/components/CollapsibleCard.vue.d.ts +1 -1
  90. package/dist/components/ConcentrationInput.vue.d.ts +2 -2
  91. package/dist/components/ConfirmDialog.vue.d.ts +2 -2
  92. package/dist/components/ControlWorkspaceView.vue.d.ts +130 -0
  93. package/dist/components/DatePicker.vue.d.ts +1 -1
  94. package/dist/components/DateTimePicker.vue.d.ts +3 -3
  95. package/dist/components/Divider.vue.d.ts +1 -1
  96. package/dist/components/DropdownButton.vue.d.ts +3 -3
  97. package/dist/components/EmptyState.vue.d.ts +1 -2
  98. package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
  99. package/dist/components/ExperimentTimeline.vue.d.ts +2 -2
  100. package/dist/components/FileUploader.vue.d.ts +1 -1
  101. package/dist/components/FitPanel.vue.d.ts +1 -1
  102. package/dist/components/FormActions.vue.d.ts +4 -4
  103. package/dist/components/FormBuilder.vue.d.ts +22 -8
  104. package/dist/components/FormFieldRenderer.vue.d.ts +7 -10
  105. package/dist/components/FormSection.vue.d.ts +11 -24
  106. package/dist/components/FormulaInput.vue.d.ts +2 -2
  107. package/dist/components/MoleculeInput.vue.d.ts +2 -2
  108. package/dist/components/MultiSelect.vue.d.ts +3 -3
  109. package/dist/components/NumberInput.vue.d.ts +1 -1
  110. package/dist/components/ProgressBar.vue.d.ts +1 -1
  111. package/dist/components/ProtocolStepEditor.vue.d.ts +3 -1
  112. package/dist/components/RackEditor.vue.d.ts +2 -2
  113. package/dist/components/SampleLegend.vue.d.ts +2 -2
  114. package/dist/components/ScheduleCalendar.vue.d.ts +2 -2
  115. package/dist/components/SegmentedControl.vue.d.ts +2 -2
  116. package/dist/components/SequenceInput.vue.d.ts +3 -3
  117. package/dist/components/SettingsButton.vue.d.ts +2 -2
  118. package/dist/components/SettingsModal.vue.d.ts +13 -5
  119. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  120. package/dist/components/TagsInput.vue.d.ts +3 -2
  121. package/dist/components/TimePicker.vue.d.ts +3 -3
  122. package/dist/components/TimeRangeInput.vue.d.ts +1 -1
  123. package/dist/components/UnitInput.vue.d.ts +2 -2
  124. package/dist/components/WellPlate.vue.d.ts +8 -8
  125. package/dist/components/index.d.ts +11 -1
  126. package/dist/components/index.js +3 -3
  127. package/dist/components/internal/FormFieldRendererInternal.vue.d.ts +31 -0
  128. package/dist/components/internal/FormSectionRenderer.vue.d.ts +43 -0
  129. package/dist/{components-_XqPEhP9.js → components-D_Sr0adg.js} +7290 -6518
  130. package/dist/components-D_Sr0adg.js.map +1 -0
  131. package/dist/composables/index.d.ts +21 -2
  132. package/dist/composables/index.js +4 -3
  133. package/dist/composables/platformContextHelpers.d.ts +14 -0
  134. package/dist/composables/useBioTemplateComponents.d.ts +20 -0
  135. package/dist/composables/useBioTemplateControls.d.ts +6 -0
  136. package/dist/composables/useBioTemplatePackWorkspace.d.ts +45 -0
  137. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +74 -0
  138. package/dist/composables/useBioTemplateWorkspace.d.ts +50 -0
  139. package/dist/composables/useCalendarGrid.d.ts +26 -0
  140. package/dist/composables/useControlSchema.d.ts +321 -0
  141. package/dist/composables/useDebouncedWatch.d.ts +20 -0
  142. package/dist/composables/useDropdownState.d.ts +19 -0
  143. package/dist/composables/useEventListener.d.ts +13 -0
  144. package/dist/composables/useExpansionSet.d.ts +21 -0
  145. package/dist/composables/useExperimentData.d.ts +10 -0
  146. package/dist/composables/useExperimentSave.d.ts +31 -2
  147. package/dist/composables/useExperimentSelector.d.ts +20 -0
  148. package/dist/composables/useForm.d.ts +2 -0
  149. package/dist/composables/useGroupAssignment.d.ts +31 -0
  150. package/dist/composables/useListSelection.d.ts +35 -0
  151. package/dist/composables/usePlatformContext.d.ts +21 -3
  152. package/dist/composables/usePluginApi.d.ts +7 -14
  153. package/dist/composables/usePluginClient.d.ts +109 -0
  154. package/dist/composables/usePluginConfig.d.ts +12 -0
  155. package/dist/composables/useRequestSyncState.d.ts +34 -0
  156. package/dist/composables/useSampleGroups.d.ts +32 -0
  157. package/dist/composables/useSelectionLimit.d.ts +17 -0
  158. package/dist/composables/useSortedItems.d.ts +32 -0
  159. package/dist/composables/useTemplateCollection.d.ts +58 -0
  160. package/dist/composables/useTextSearch.d.ts +18 -0
  161. package/dist/composables/useTimeUtils.d.ts +8 -0
  162. package/dist/{composables-tiZqLu1M.js → composables-C3dpXQN5.js} +228 -146
  163. package/dist/composables-C3dpXQN5.js.map +1 -0
  164. package/dist/index.d.ts +12 -3
  165. package/dist/index.js +6 -5
  166. package/dist/install.d.ts +7 -2
  167. package/dist/install.js +2 -2
  168. package/dist/install.js.map +1 -1
  169. package/dist/stores/index.js +1 -1
  170. package/dist/stores/settings.d.ts +4 -1
  171. package/dist/styles.css +5235 -5977
  172. package/dist/templates/adapters.d.ts +43 -0
  173. package/dist/templates/builders.d.ts +63 -0
  174. package/dist/templates/catalog.d.ts +188 -0
  175. package/dist/templates/componentBindings.d.ts +58 -0
  176. package/dist/templates/controlSchemas.d.ts +25 -0
  177. package/dist/templates/index.d.ts +15 -0
  178. package/dist/templates/index.js +2 -0
  179. package/dist/templates/lookup.d.ts +4 -0
  180. package/dist/templates/packs.d.ts +18 -0
  181. package/dist/templates/presets.d.ts +90 -0
  182. package/dist/templates/types.d.ts +531 -0
  183. package/dist/templates-50NPjaxL.js +9333 -0
  184. package/dist/templates-50NPjaxL.js.map +1 -0
  185. package/dist/types/components.d.ts +26 -4
  186. package/dist/types/form-builder.d.ts +6 -8
  187. package/dist/types/index.d.ts +2 -2
  188. package/dist/types/platform.d.ts +7 -1
  189. package/dist/useScheduleDrag-D4oWdh41.js +4371 -0
  190. package/dist/useScheduleDrag-D4oWdh41.js.map +1 -0
  191. package/dist/utils/formModelSync.d.ts +5 -0
  192. package/dist/utils/items.d.ts +8 -0
  193. package/dist/utils/options.d.ts +6 -0
  194. package/dist/utils/pluginIcon.d.ts +9 -0
  195. package/package.json +7 -2
  196. package/src/__tests__/components/ActionItem.test.ts +99 -0
  197. package/src/__tests__/components/AppAvatarMenu.test.ts +27 -0
  198. package/src/__tests__/components/AppPageSelector.test.ts +134 -0
  199. package/src/__tests__/components/AppPillNav.test.ts +78 -0
  200. package/src/__tests__/components/AppPluginSwitcher.test.ts +44 -0
  201. package/src/__tests__/components/AppSidebar.test.ts +370 -0
  202. package/src/__tests__/components/AppToastContainer.test.ts +48 -0
  203. package/src/__tests__/components/AppTopBar.test.ts +383 -0
  204. package/src/__tests__/components/BaseRadioGroup.test.ts +25 -0
  205. package/src/__tests__/components/BaseSelect.test.ts +21 -0
  206. package/src/__tests__/components/BaseTabs.test.ts +25 -0
  207. package/src/__tests__/components/BatchProgressList.test.ts +52 -0
  208. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +153 -0
  209. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +161 -0
  210. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +281 -0
  211. package/src/__tests__/components/BioTemplateRenderer.test.ts +71 -0
  212. package/src/__tests__/components/Breadcrumb.test.ts +23 -0
  213. package/src/__tests__/components/CalendarGridPanel.test.ts +36 -0
  214. package/src/__tests__/components/ConcentrationInput.test.ts +45 -0
  215. package/src/__tests__/components/ControlWorkspaceView.test.ts +1031 -0
  216. package/src/__tests__/components/DataFrame.test.ts +11 -0
  217. package/src/__tests__/components/DatePicker.test.ts +45 -0
  218. package/src/__tests__/components/DateTimePicker.test.ts +48 -0
  219. package/src/__tests__/components/DropdownButton.test.ts +23 -0
  220. package/src/__tests__/components/EmptyState.test.ts +23 -0
  221. package/src/__tests__/components/ExperimentPopover.test.ts +56 -0
  222. package/src/__tests__/components/FormBuilder.test.ts +296 -0
  223. package/src/__tests__/components/FormCompatibility.test.ts +94 -0
  224. package/src/__tests__/components/GroupAssigner.test.ts +30 -0
  225. package/src/__tests__/components/GroupingModal.test.ts +73 -0
  226. package/src/__tests__/components/MultiSelect.test.ts +48 -0
  227. package/src/__tests__/components/ProtocolStepEditor.test.ts +33 -0
  228. package/src/__tests__/components/ReagentList.test.ts +82 -0
  229. package/src/__tests__/components/SampleHierarchyTree.test.ts +53 -0
  230. package/src/__tests__/components/SampleSelector.test.ts +60 -0
  231. package/src/__tests__/components/SegmentedControl.test.ts +24 -0
  232. package/src/__tests__/components/SettingsButton.test.ts +44 -0
  233. package/src/__tests__/components/SettingsModal.test.ts +296 -0
  234. package/src/__tests__/components/TagsInput.test.ts +75 -0
  235. package/src/__tests__/components/ThemeToggle.test.ts +47 -0
  236. package/src/__tests__/components/TimePicker.test.ts +38 -0
  237. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +122 -0
  238. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +199 -0
  239. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +99 -0
  240. package/src/__tests__/composables/useCalendarGrid.test.ts +38 -0
  241. package/src/__tests__/composables/useControlSchema.test.ts +919 -0
  242. package/src/__tests__/composables/useDebouncedWatch.test.ts +93 -0
  243. package/src/__tests__/composables/useDropdownState.test.ts +95 -0
  244. package/src/__tests__/composables/useEventListener.test.ts +116 -0
  245. package/src/__tests__/composables/useExpansionSet.test.ts +62 -0
  246. package/src/__tests__/composables/useExperimentData.test.ts +4 -0
  247. package/src/__tests__/composables/useExperimentSave.test.ts +203 -8
  248. package/src/__tests__/composables/useExperimentSelector.test.ts +164 -0
  249. package/src/__tests__/composables/useForm.test.ts +58 -0
  250. package/src/__tests__/composables/useFormBuilder.test.ts +77 -0
  251. package/src/__tests__/composables/useGroupAssignment.test.ts +73 -0
  252. package/src/__tests__/composables/useListSelection.test.ts +66 -0
  253. package/src/__tests__/composables/usePluginClient.test.ts +444 -0
  254. package/src/__tests__/composables/usePluginConfig.test.ts +5 -0
  255. package/src/__tests__/composables/useRequestSyncState.test.ts +92 -0
  256. package/src/__tests__/composables/useSampleGroups.test.ts +66 -0
  257. package/src/__tests__/composables/useSelectionLimit.test.ts +41 -0
  258. package/src/__tests__/composables/useSortedItems.test.ts +87 -0
  259. package/src/__tests__/composables/useTemplateCollection.test.ts +147 -0
  260. package/src/__tests__/composables/useTextSearch.test.ts +55 -0
  261. package/src/__tests__/composables/useTheme.test.ts +91 -0
  262. package/src/__tests__/composables/useTimeUtils.test.ts +35 -0
  263. package/src/__tests__/docs/frontendDocsCatalog.test.ts +229 -0
  264. package/src/__tests__/fixtures/templates/dose-response.json +81 -0
  265. package/src/__tests__/fixtures/templates/plate-map.json +54 -0
  266. package/src/__tests__/fixtures/templates/qpcr-plate.json +96 -0
  267. package/src/__tests__/fixtures/templates/sample-sheet.json +71 -0
  268. package/src/__tests__/templates/templates.test.ts +1043 -0
  269. package/src/components/ActionItem.vue +82 -0
  270. package/src/components/AppAvatarMenu.vue +15 -69
  271. package/src/components/AppLayout.story.vue +25 -25
  272. package/src/components/AppPageSelector.vue +63 -94
  273. package/src/components/AppPillNav.vue +44 -39
  274. package/src/components/AppPluginSwitcher.vue +41 -145
  275. package/src/components/AppSidebar.story.vue +94 -0
  276. package/src/components/AppSidebar.vue +187 -12
  277. package/src/components/{ToastNotification.story.vue → AppToastContainer.story.vue} +6 -6
  278. package/src/components/AppToastContainer.vue +62 -0
  279. package/src/components/AppTopBar.story.vue +7 -30
  280. package/src/components/AppTopBar.vue +251 -57
  281. package/src/components/BaseModal.vue +3 -5
  282. package/src/components/BaseRadioGroup.vue +7 -3
  283. package/src/components/BaseSelect.vue +11 -7
  284. package/src/components/BaseTabs.vue +6 -4
  285. package/src/components/BatchProgressList.vue +5 -8
  286. package/src/components/BioTemplateExperimentWorkspaceView.story.vue +123 -0
  287. package/src/components/BioTemplateExperimentWorkspaceView.vue +337 -0
  288. package/src/components/BioTemplatePackWorkspaceView.story.vue +107 -0
  289. package/src/components/BioTemplatePackWorkspaceView.vue +176 -0
  290. package/src/components/BioTemplatePresetWorkspaceView.story.vue +151 -0
  291. package/src/components/BioTemplatePresetWorkspaceView.vue +392 -0
  292. package/src/components/BioTemplateRenderer.story.vue +57 -0
  293. package/src/components/BioTemplateRenderer.vue +269 -0
  294. package/src/components/Breadcrumb.vue +14 -8
  295. package/src/components/CalendarGridPanel.vue +120 -0
  296. package/src/components/ConcentrationInput.vue +27 -64
  297. package/src/components/ControlWorkspaceView.story.vue +336 -0
  298. package/src/components/ControlWorkspaceView.vue +347 -0
  299. package/src/components/DataFrame.vue +34 -50
  300. package/src/components/DatePicker.vue +59 -192
  301. package/src/components/DateTimePicker.vue +50 -171
  302. package/src/components/DropdownButton.vue +14 -32
  303. package/src/components/EmptyState.vue +4 -2
  304. package/src/components/ExperimentPopover.vue +5 -22
  305. package/src/components/FormBuilder.vue +124 -27
  306. package/src/components/FormFieldRenderer.vue +15 -38
  307. package/src/components/FormSection.vue +20 -73
  308. package/src/components/GroupAssigner.vue +24 -56
  309. package/src/components/GroupingModal.story.vue +3 -3
  310. package/src/components/GroupingModal.vue +30 -391
  311. package/src/components/MultiSelect.vue +17 -12
  312. package/src/components/PlateMapEditor.vue +3 -8
  313. package/src/components/PluginIcon.vue +2 -22
  314. package/src/components/ProtocolStepEditor.vue +13 -22
  315. package/src/components/ReagentList.vue +25 -33
  316. package/src/components/SampleHierarchyTree.vue +12 -23
  317. package/src/components/SampleSelector.vue +42 -122
  318. package/src/components/SegmentedControl.vue +7 -3
  319. package/src/components/SettingsButton.story.vue +1 -1
  320. package/src/components/SettingsButton.vue +15 -27
  321. package/src/components/SettingsModal.story.vue +1 -1
  322. package/src/components/SettingsModal.vue +120 -29
  323. package/src/components/TagsInput.vue +29 -14
  324. package/src/components/ThemeToggle.vue +9 -7
  325. package/src/components/TimePicker.vue +19 -41
  326. package/src/components/ToastNotification.vue +4 -57
  327. package/src/components/Tooltip.vue +7 -12
  328. package/src/components/WellEditPopup.vue +3 -8
  329. package/src/components/WellPlate.vue +4 -10
  330. package/src/components/index.ts +11 -1
  331. package/src/components/internal/FormFieldRendererInternal.vue +50 -0
  332. package/src/components/internal/FormSectionRenderer.vue +78 -0
  333. package/src/composables/index.ts +212 -0
  334. package/src/composables/platformContextHelpers.ts +74 -0
  335. package/src/composables/useBioTemplateComponents.ts +93 -0
  336. package/src/composables/useBioTemplateControls.ts +41 -0
  337. package/src/composables/useBioTemplatePackWorkspace.ts +181 -0
  338. package/src/composables/useBioTemplatePresetWorkspace.ts +337 -0
  339. package/src/composables/useBioTemplateWorkspace.ts +139 -0
  340. package/src/composables/useCalendarGrid.ts +140 -0
  341. package/src/composables/useControlSchema.ts +1274 -0
  342. package/src/composables/useDebouncedWatch.ts +119 -0
  343. package/src/composables/useDropdownState.ts +83 -0
  344. package/src/composables/useEventListener.ts +111 -0
  345. package/src/composables/useExpansionSet.ts +117 -0
  346. package/src/composables/useExperimentData.ts +20 -11
  347. package/src/composables/useExperimentSave.ts +202 -50
  348. package/src/composables/useExperimentSelector.ts +86 -72
  349. package/src/composables/useForm.ts +49 -4
  350. package/src/composables/useFormBuilder.ts +93 -42
  351. package/src/composables/useGroupAssignment.ts +148 -0
  352. package/src/composables/useListSelection.ts +158 -0
  353. package/src/composables/usePluginApi.ts +7 -14
  354. package/src/composables/usePluginClient.ts +425 -0
  355. package/src/composables/usePluginConfig.ts +34 -13
  356. package/src/composables/useRequestSyncState.ts +126 -0
  357. package/src/composables/useSampleGroups.ts +126 -0
  358. package/src/composables/useSelectionLimit.ts +57 -0
  359. package/src/composables/useSortedItems.ts +118 -0
  360. package/src/composables/useTemplateCollection.ts +229 -0
  361. package/src/composables/useTextSearch.ts +60 -0
  362. package/src/composables/useTheme.ts +2 -28
  363. package/src/composables/useTimeUtils.ts +26 -2
  364. package/src/composables/useWellPlateEditor.ts +13 -9
  365. package/src/index.ts +224 -4
  366. package/src/install.ts +11 -4
  367. package/src/stores/settings.ts +13 -9
  368. package/src/styles/components/app-page-selector.css +23 -0
  369. package/src/styles/components/app-pill-nav.css +7 -0
  370. package/src/styles/components/app-top-bar.css +34 -0
  371. package/src/styles/components/concentration-input.css +3 -142
  372. package/src/styles/components/empty-state.css +0 -16
  373. package/src/styles/components/settings-button.css +3 -66
  374. package/src/styles/components/theme-toggle.css +3 -66
  375. package/src/styles/index.css +0 -1
  376. package/src/templates/adapters.ts +785 -0
  377. package/src/templates/builders.ts +2149 -0
  378. package/src/templates/catalog.ts +245 -0
  379. package/src/templates/componentBindings.ts +615 -0
  380. package/src/templates/controlSchemas.ts +718 -0
  381. package/src/templates/index.ts +314 -0
  382. package/src/templates/lookup.ts +18 -0
  383. package/src/templates/packs.ts +156 -0
  384. package/src/templates/presets.ts +146 -0
  385. package/src/templates/types.ts +668 -0
  386. package/src/types/components.ts +41 -4
  387. package/src/types/form-builder.ts +7 -2
  388. package/src/types/index.ts +14 -0
  389. package/src/types/platform.ts +7 -1
  390. package/src/utils/formModelSync.ts +52 -0
  391. package/src/utils/items.ts +28 -0
  392. package/src/utils/options.ts +23 -0
  393. package/src/utils/pluginIcon.ts +30 -0
  394. package/dist/auth-DsI0rQ7_.js.map +0 -1
  395. package/dist/components-_XqPEhP9.js.map +0 -1
  396. package/dist/composables-tiZqLu1M.js.map +0 -1
  397. package/dist/useScheduleDrag-CA9sGNJG.js +0 -7181
  398. package/dist/useScheduleDrag-CA9sGNJG.js.map +0 -1
  399. package/src/styles/components/grouping-modal.css +0 -323
@@ -0,0 +1,93 @@
1
+ import { effectScope, nextTick, ref } from 'vue'
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
3
+ import { useDebouncedWatch } from '../../composables/useDebouncedWatch'
4
+
5
+ describe('useDebouncedWatch', () => {
6
+ beforeEach(() => {
7
+ vi.useFakeTimers()
8
+ })
9
+
10
+ afterEach(() => {
11
+ vi.useRealTimers()
12
+ })
13
+
14
+ it('debounces source changes and runs the latest callback value', async () => {
15
+ const query = ref('')
16
+ const calls: string[] = []
17
+
18
+ const watcher = useDebouncedWatch(query, value => {
19
+ calls.push(value)
20
+ }, { delay: 200 })
21
+
22
+ query.value = 'glu'
23
+ await nextTick()
24
+ query.value = 'glucose'
25
+ await nextTick()
26
+
27
+ expect(watcher.isPending.value).toBe(true)
28
+ vi.advanceTimersByTime(199)
29
+ expect(calls).toEqual([])
30
+
31
+ vi.advanceTimersByTime(1)
32
+ expect(calls).toEqual(['glucose'])
33
+ expect(watcher.isPending.value).toBe(false)
34
+ })
35
+
36
+ it('can cancel and flush pending callbacks without stopping the watcher', async () => {
37
+ const query = ref('')
38
+ const calls: string[] = []
39
+ const watcher = useDebouncedWatch(query, value => {
40
+ calls.push(value)
41
+ })
42
+
43
+ query.value = 'first'
44
+ await nextTick()
45
+ watcher.cancel()
46
+ vi.runAllTimers()
47
+ expect(calls).toEqual([])
48
+
49
+ query.value = 'second'
50
+ await nextTick()
51
+ watcher.flush()
52
+ expect(calls).toEqual(['second'])
53
+ })
54
+
55
+ it('runs callback cleanup when stopped or invalidated', async () => {
56
+ const query = ref('')
57
+ const cleanup = vi.fn()
58
+ const watcher = useDebouncedWatch(query, (_value, _oldValue, onCleanup) => {
59
+ onCleanup(cleanup)
60
+ }, { delay: 100 })
61
+
62
+ query.value = 'first'
63
+ await nextTick()
64
+ vi.advanceTimersByTime(100)
65
+
66
+ query.value = 'second'
67
+ await nextTick()
68
+ expect(cleanup).toHaveBeenCalledTimes(1)
69
+
70
+ vi.advanceTimersByTime(100)
71
+ watcher.stop()
72
+ expect(cleanup).toHaveBeenCalledTimes(2)
73
+ })
74
+
75
+ it('clears pending callbacks when the current effect scope disposes', async () => {
76
+ const query = ref('')
77
+ const calls: string[] = []
78
+ const scope = effectScope()
79
+
80
+ scope.run(() => {
81
+ useDebouncedWatch(query, value => {
82
+ calls.push(value)
83
+ })
84
+ })
85
+
86
+ query.value = 'pending'
87
+ await nextTick()
88
+ scope.stop()
89
+ vi.runAllTimers()
90
+
91
+ expect(calls).toEqual([])
92
+ })
93
+ })
@@ -0,0 +1,95 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { defineComponent, nextTick } from 'vue'
3
+ import { afterEach, describe, expect, it, vi } from 'vitest'
4
+ import {
5
+ useDropdownState,
6
+ type UseDropdownStateOptions,
7
+ type UseDropdownStateReturn,
8
+ } from '../../composables/useDropdownState'
9
+
10
+ function mountHarness(options: UseDropdownStateOptions = {}) {
11
+ let state: UseDropdownStateReturn | undefined
12
+ const wrapper = mount(defineComponent({
13
+ setup() {
14
+ state = useDropdownState(options)
15
+ return state
16
+ },
17
+ template: '<div ref="rootRef" class="dropdown-root"><button class="inside">Inside</button></div>',
18
+ }), { attachTo: document.body })
19
+
20
+ if (!state) {
21
+ throw new Error('Dropdown harness did not initialize.')
22
+ }
23
+
24
+ return { wrapper, state }
25
+ }
26
+
27
+ describe('useDropdownState', () => {
28
+ afterEach(() => {
29
+ document.body.innerHTML = ''
30
+ })
31
+
32
+ it('opens, closes, and toggles with callbacks only on state changes', () => {
33
+ const onOpen = vi.fn()
34
+ const onClose = vi.fn()
35
+ const { wrapper, state } = mountHarness({ onOpen, onClose })
36
+
37
+ state.open()
38
+ state.open()
39
+ expect(state.isOpen.value).toBe(true)
40
+ expect(onOpen).toHaveBeenCalledTimes(1)
41
+
42
+ state.toggle()
43
+ state.close()
44
+ expect(state.isOpen.value).toBe(false)
45
+ expect(onClose).toHaveBeenCalledTimes(1)
46
+
47
+ state.toggle()
48
+ expect(state.isOpen.value).toBe(true)
49
+ expect(onOpen).toHaveBeenCalledTimes(2)
50
+
51
+ wrapper.unmount()
52
+ })
53
+
54
+ it('closes on outside click but keeps inside clicks open', async () => {
55
+ const { wrapper, state } = mountHarness()
56
+
57
+ state.open()
58
+ await nextTick()
59
+ await wrapper.find('.inside').trigger('click')
60
+ await nextTick()
61
+ expect(state.isOpen.value).toBe(true)
62
+
63
+ document.body.click()
64
+ await nextTick()
65
+ expect(state.isOpen.value).toBe(false)
66
+
67
+ wrapper.unmount()
68
+ })
69
+
70
+ it('closes on Escape by default', async () => {
71
+ const { wrapper, state } = mountHarness()
72
+
73
+ state.open()
74
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
75
+ await nextTick()
76
+
77
+ expect(state.isOpen.value).toBe(false)
78
+ wrapper.unmount()
79
+ })
80
+
81
+ it('can opt out of outside-click and Escape closing', async () => {
82
+ const { wrapper, state } = mountHarness({
83
+ closeOnOutsideClick: false,
84
+ closeOnEscape: false,
85
+ })
86
+
87
+ state.open()
88
+ document.body.click()
89
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
90
+ await nextTick()
91
+
92
+ expect(state.isOpen.value).toBe(true)
93
+ wrapper.unmount()
94
+ })
95
+ })
@@ -0,0 +1,116 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { defineComponent, nextTick, ref, type Ref } from 'vue'
3
+ import { afterEach, describe, expect, it, vi } from 'vitest'
4
+ import {
5
+ useEventListener,
6
+ type EventListenerStop,
7
+ } from '../../composables/useEventListener'
8
+
9
+ describe('useEventListener', () => {
10
+ afterEach(() => {
11
+ document.body.innerHTML = ''
12
+ })
13
+
14
+ it('binds on mount and removes the listener on unmount', async () => {
15
+ const listener = vi.fn()
16
+ const wrapper = mount(defineComponent({
17
+ setup() {
18
+ useEventListener(() => document, 'click', listener)
19
+ },
20
+ template: '<div />',
21
+ }))
22
+
23
+ document.body.click()
24
+ await nextTick()
25
+ expect(listener).toHaveBeenCalledTimes(1)
26
+
27
+ wrapper.unmount()
28
+ document.body.click()
29
+ await nextTick()
30
+ expect(listener).toHaveBeenCalledTimes(1)
31
+ })
32
+
33
+ it('returns a stop function for manual cleanup', async () => {
34
+ const listener = vi.fn()
35
+ let stop: EventListenerStop | undefined
36
+ const wrapper = mount(defineComponent({
37
+ setup() {
38
+ stop = useEventListener(() => document, 'keydown', listener)
39
+ },
40
+ template: '<div />',
41
+ }))
42
+
43
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'A' }))
44
+ await nextTick()
45
+ expect(listener).toHaveBeenCalledTimes(1)
46
+
47
+ stop?.()
48
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'B' }))
49
+ await nextTick()
50
+ expect(listener).toHaveBeenCalledTimes(1)
51
+
52
+ wrapper.unmount()
53
+ })
54
+
55
+ it('moves listeners when a ref target changes', async () => {
56
+ const listener = vi.fn()
57
+ const first = document.createElement('button')
58
+ const second = document.createElement('button')
59
+ let target: Ref<HTMLElement | null> | undefined
60
+
61
+ const wrapper = mount(defineComponent({
62
+ setup() {
63
+ target = ref(first)
64
+ useEventListener(target, 'click', listener)
65
+ },
66
+ template: '<div />',
67
+ }))
68
+
69
+ first.click()
70
+ await nextTick()
71
+ expect(listener).toHaveBeenCalledTimes(1)
72
+
73
+ if (!target) throw new Error('Target ref did not initialize.')
74
+ target.value = second
75
+ await nextTick()
76
+
77
+ first.click()
78
+ second.click()
79
+ await nextTick()
80
+ expect(listener).toHaveBeenCalledTimes(2)
81
+
82
+ wrapper.unmount()
83
+ second.click()
84
+ await nextTick()
85
+ expect(listener).toHaveBeenCalledTimes(2)
86
+ })
87
+
88
+ it('binds and removes listeners from a reactive enabled option', async () => {
89
+ const enabled = ref(false)
90
+ const listener = vi.fn()
91
+ const wrapper = mount(defineComponent({
92
+ setup() {
93
+ useEventListener(() => document, 'click', listener, { enabled })
94
+ },
95
+ template: '<div />',
96
+ }))
97
+
98
+ document.body.click()
99
+ await nextTick()
100
+ expect(listener).not.toHaveBeenCalled()
101
+
102
+ enabled.value = true
103
+ await nextTick()
104
+ document.body.click()
105
+ await nextTick()
106
+ expect(listener).toHaveBeenCalledTimes(1)
107
+
108
+ enabled.value = false
109
+ await nextTick()
110
+ document.body.click()
111
+ await nextTick()
112
+ expect(listener).toHaveBeenCalledTimes(1)
113
+
114
+ wrapper.unmount()
115
+ })
116
+ })
@@ -0,0 +1,62 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { nextTick, ref } from 'vue'
3
+ import { useExpansionSet } from '../../composables/useExpansionSet'
4
+
5
+ describe('useExpansionSet', () => {
6
+ it('tracks expanded ids without mutating defaults', () => {
7
+ const defaults = ['study', 'plate', 'plate']
8
+ const expansion = useExpansionSet({ defaultIds: defaults })
9
+
10
+ expect(expansion.expandedList.value).toEqual(['study', 'plate'])
11
+ expect(expansion.isExpanded('study')).toBe(true)
12
+ expect(expansion.toggle('study')).toBe(false)
13
+ expect(expansion.isExpanded('study')).toBe(false)
14
+ expect(expansion.toggle('sample')).toBe(true)
15
+ expect(expansion.expandedList.value).toEqual(['plate', 'sample'])
16
+
17
+ expansion.reset()
18
+ expect(expansion.expandedList.value).toEqual(['study', 'plate'])
19
+ expect(defaults).toEqual(['study', 'plate', 'plate'])
20
+ })
21
+
22
+ it('supports bulk expansion and collapse helpers', () => {
23
+ const expansion = useExpansionSet()
24
+
25
+ expansion.expand('study')
26
+ expansion.expandMany(['plate', 'sample', 'plate'])
27
+ expect(expansion.expandedList.value).toEqual(['study', 'plate', 'sample'])
28
+
29
+ expansion.collapse('plate')
30
+ expect(expansion.expandedList.value).toEqual(['study', 'sample'])
31
+
32
+ expansion.setExpanded(['treatment'])
33
+ expect(expansion.expandedList.value).toEqual(['treatment'])
34
+
35
+ expansion.collapseAll()
36
+ expect(expansion.expandedList.value).toEqual([])
37
+ })
38
+
39
+ it('reacts to expand-all and all-id sources', async () => {
40
+ const expandAll = ref(false)
41
+ const allIds = ref(['study', 'plate'])
42
+ const expansion = useExpansionSet({
43
+ defaultIds: ['study'],
44
+ allIds,
45
+ expandAll,
46
+ })
47
+
48
+ expect(expansion.expandedList.value).toEqual(['study'])
49
+
50
+ expandAll.value = true
51
+ await nextTick()
52
+ expect(expansion.expandedList.value).toEqual(['study', 'plate'])
53
+
54
+ allIds.value = ['study', 'plate', 'sample']
55
+ await nextTick()
56
+ expect(expansion.expandedList.value).toEqual(['study', 'plate', 'sample'])
57
+
58
+ expandAll.value = false
59
+ await nextTick()
60
+ expect(expansion.expandedList.value).toEqual(['study'])
61
+ })
62
+ })
@@ -9,6 +9,7 @@
9
9
  * - table_data / tableData
10
10
  * - summary_data validation (must have a `metadata` key)
11
11
  * - load / refresh / error paths
12
+ * - lastLoadedAt tracks successful syncs
12
13
  */
13
14
 
14
15
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -47,11 +48,13 @@ describe('useExperimentData', () => {
47
48
  const hook = useExperimentData()
48
49
 
49
50
  expect(hook.isLoading.value).toBe(false)
51
+ expect(hook.lastLoadedAt.value).toBeNull()
50
52
  const p = hook.fetch(42)
51
53
  expect(hook.isLoading.value).toBe(true)
52
54
  await p
53
55
  expect(hook.isLoading.value).toBe(false)
54
56
  expect(hook.data.value).toEqual({ tree_data: [{ id: 'a', label: 'A' }] })
57
+ expect(hook.lastLoadedAt.value).toBeInstanceOf(Date)
55
58
  expect(recordedUrls[0]).toBe('/experiments/42/data')
56
59
  })
57
60
 
@@ -113,6 +116,7 @@ describe('useExperimentData', () => {
113
116
  expect(hook.error.value).toBe('boom')
114
117
  expect(hook.data.value).toBeNull()
115
118
  expect(hook.isLoading.value).toBe(false)
119
+ expect(hook.lastLoadedAt.value).toBeNull()
116
120
  })
117
121
 
118
122
  it('apiBaseUrl option is honoured', async () => {
@@ -7,13 +7,14 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'
7
7
  const mockGet = vi.fn()
8
8
  const mockPut = vi.fn()
9
9
  const mockDelete = vi.fn()
10
+ const mockUseApi = vi.fn((_options?: unknown) => ({
11
+ get: mockGet,
12
+ put: mockPut,
13
+ delete: mockDelete,
14
+ }))
10
15
 
11
16
  vi.mock('../../composables/useApi', () => ({
12
- useApi: () => ({
13
- get: mockGet,
14
- put: mockPut,
15
- delete: mockDelete,
16
- }),
17
+ useApi: (options: unknown) => mockUseApi(options),
17
18
  }))
18
19
 
19
20
  import { useExperimentSave } from '../../composables/useExperimentSave'
@@ -36,6 +37,8 @@ describe('useExperimentSave', () => {
36
37
  mockGet.mockResolvedValue({})
37
38
  mockPut.mockResolvedValue({})
38
39
  mockDelete.mockResolvedValue({})
40
+ delete (window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__
41
+ window.history.replaceState({}, '', '/')
39
42
  })
40
43
 
41
44
  // -------------------------------------------------------------------------
@@ -137,6 +140,179 @@ describe('useExperimentSave', () => {
137
140
  const ok = await saveDesign(1, { x: 1 })
138
141
  expect(ok).toBe(false)
139
142
  })
143
+
144
+ it('should default pluginId and apiBaseUrl from injected platform context', async () => {
145
+ ;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
146
+ isIntegrated: true,
147
+ theme: 'light',
148
+ platformApiUrl: '/api',
149
+ plugin: {
150
+ id: 'platform-plugin',
151
+ name: 'Platform Plugin',
152
+ version: '1.0.0',
153
+ route_prefix: '/platform-plugin',
154
+ api_prefix: '/api/platform-plugin',
155
+ },
156
+ }
157
+
158
+ const { saveDesign } = setup()
159
+ await saveDesign(1, { x: 1 })
160
+
161
+ expect(mockUseApi).toHaveBeenCalledWith({ baseUrl: '/api' })
162
+ expect(mockPut).toHaveBeenCalledWith('/experiments/1/data', {
163
+ plugin_id: 'platform-plugin',
164
+ data: { x: 1 },
165
+ schema_version: '1.0',
166
+ })
167
+ })
168
+ })
169
+
170
+ // -------------------------------------------------------------------------
171
+ // saveTemplatePreset
172
+ // -------------------------------------------------------------------------
173
+
174
+ describe('saveTemplatePreset', () => {
175
+ it('should create a preset collection and save it as design data', async () => {
176
+ const { saveTemplatePreset } = setup({ pluginId: 'p1' })
177
+
178
+ const ok = await saveTemplatePreset(42, 'wellplate', {
179
+ samples: ['Control', 'Treatment'],
180
+ compounds: { 'Drug A': [10, 1] },
181
+ })
182
+
183
+ expect(ok).toBe(true)
184
+ expect(mockPut).toHaveBeenCalledOnce()
185
+ const body = mockPut.mock.calls[0][1]
186
+ expect(mockPut.mock.calls[0][0]).toBe('/experiments/42/data')
187
+ expect(body.plugin_id).toBe('p1')
188
+ expect(body.data.metadata.preset).toBe('wellplate-screen')
189
+ expect(Object.keys(body.data.templates)).toEqual(['plate-map', 'sample-sheet', 'dose-response'])
190
+ expect(body.data.templates['plate-map'].data.samples[1].id).toBe('treatment')
191
+ })
192
+
193
+ it('should resolve preset aliases', async () => {
194
+ const { saveTemplatePreset } = setup({ pluginId: 'p1' })
195
+
196
+ await saveTemplatePreset(42, 'gene expression', {
197
+ samples: ['Control'],
198
+ targets: ['ACTB'],
199
+ })
200
+
201
+ const body = mockPut.mock.calls[0][1]
202
+ expect(body.data.metadata.preset).toBe('qpcr-expression')
203
+ expect(Object.keys(body.data.templates)).toEqual(['sample-sheet', 'qpcr-plate'])
204
+ })
205
+
206
+ it('should return false when pluginId is missing', async () => {
207
+ const { saveTemplatePreset, error } = setup()
208
+
209
+ const ok = await saveTemplatePreset(42, 'lcms', { samples: ['S001'] })
210
+
211
+ expect(ok).toBe(false)
212
+ expect(error.value).toBe('pluginId is required for saveDesign')
213
+ expect(mockPut).not.toHaveBeenCalled()
214
+ })
215
+ })
216
+
217
+ // -------------------------------------------------------------------------
218
+ // current experiment shortcuts
219
+ // -------------------------------------------------------------------------
220
+
221
+ describe('current experiment shortcuts', () => {
222
+ it('should expose current experiment id from platform injection', async () => {
223
+ ;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
224
+ isIntegrated: true,
225
+ theme: 'light',
226
+ plugin: {
227
+ id: 'p1',
228
+ name: 'Plugin',
229
+ version: '1.0.0',
230
+ route_prefix: '/plugin',
231
+ api_prefix: '/api/plugin',
232
+ },
233
+ currentExperiment: { id: 77 },
234
+ }
235
+
236
+ const { currentExperimentId, hasCurrentExperiment, requireCurrentExperimentId } = setup()
237
+
238
+ expect(currentExperimentId.value).toBe(77)
239
+ expect(hasCurrentExperiment.value).toBe(true)
240
+ expect(requireCurrentExperimentId()).toBe(77)
241
+ })
242
+
243
+ it('should expose current experiment id from URL query and path', () => {
244
+ window.history.replaceState({}, '', '/analysis?experimentId=42')
245
+ expect(setup().currentExperimentId.value).toBe(42)
246
+
247
+ window.history.replaceState({}, '', '/experiments/43/analysis')
248
+ expect(setup().currentExperimentId.value).toBe(43)
249
+ })
250
+
251
+ it('should save design for the current experiment', async () => {
252
+ window.history.replaceState({}, '', '/analysis?experimentId=42')
253
+ const { saveCurrentDesign } = setup({ pluginId: 'p1' })
254
+
255
+ const ok = await saveCurrentDesign({ layout: 'grid' })
256
+
257
+ expect(ok).toBe(true)
258
+ expect(mockPut).toHaveBeenCalledWith('/experiments/42/data', {
259
+ plugin_id: 'p1',
260
+ data: { layout: 'grid' },
261
+ schema_version: '1.0',
262
+ })
263
+ })
264
+
265
+ it('should save a template preset for the current experiment', async () => {
266
+ window.history.replaceState({}, '', '/experiments/42/template')
267
+ const { saveCurrentTemplatePreset } = setup({ pluginId: 'p1' })
268
+
269
+ const ok = await saveCurrentTemplatePreset('wellplate', {
270
+ samples: ['Control', 'Treatment'],
271
+ compounds: { 'Drug A': [10, 1] },
272
+ })
273
+
274
+ expect(ok).toBe(true)
275
+ expect(mockPut.mock.calls[0][0]).toBe('/experiments/42/data')
276
+ expect(mockPut.mock.calls[0][1].data.metadata.preset).toBe('wellplate-screen')
277
+ })
278
+
279
+ it('should save analysis for the current experiment', async () => {
280
+ window.history.replaceState({}, '', '/analysis?experiment_id=44')
281
+ const { saveCurrentAnalysis } = setup({ pluginId: 'p1' })
282
+
283
+ const ok = await saveCurrentAnalysis({ score: 0.95 })
284
+
285
+ expect(ok).toBe(true)
286
+ expect(mockPut).toHaveBeenCalledWith('/experiments/44/results/p1', {
287
+ result: { score: 0.95 },
288
+ })
289
+ })
290
+
291
+ it('should load and delete current experiment data', async () => {
292
+ window.history.replaceState({}, '', '/experiments/45/template')
293
+ mockGet.mockResolvedValueOnce({ design: { plate: 'A' } })
294
+ const {
295
+ loadCurrentDesign,
296
+ deleteCurrentAnalysis,
297
+ } = setup({ pluginId: 'p1' })
298
+
299
+ await expect(loadCurrentDesign()).resolves.toEqual({ design: { plate: 'A' } })
300
+ await expect(deleteCurrentAnalysis()).resolves.toBe(true)
301
+
302
+ expect(mockGet).toHaveBeenCalledWith('/experiments/45/data')
303
+ expect(mockDelete).toHaveBeenCalledWith('/experiments/45/results/p1')
304
+ })
305
+
306
+ it('should return false when current experiment is missing', async () => {
307
+ const { saveCurrentTemplatePreset, error, requireCurrentExperimentId } = setup({ pluginId: 'p1' })
308
+
309
+ expect(() => requireCurrentExperimentId()).toThrow('[MINT SDK] No current experiment is selected.')
310
+ const ok = await saveCurrentTemplatePreset('lcms', { samples: ['S001'] })
311
+
312
+ expect(ok).toBe(false)
313
+ expect(error.value).toBe('No current experiment is selected for saveTemplatePreset')
314
+ expect(mockPut).not.toHaveBeenCalled()
315
+ })
140
316
  })
141
317
 
142
318
  // -------------------------------------------------------------------------
@@ -251,10 +427,27 @@ describe('useExperimentSave', () => {
251
427
 
252
428
  it('should return null on error', async () => {
253
429
  mockGet.mockRejectedValueOnce(new Error('not found'))
254
- const { loadDesign } = setup()
430
+ const { loadDesign, error } = setup()
255
431
 
256
432
  const result = await loadDesign(999)
257
433
  expect(result).toBeNull()
434
+ expect(error.value).toBe('not found')
435
+ })
436
+
437
+ it('should set isLoading and lastLoadedAt during the call', async () => {
438
+ let resolveGet!: (v: unknown) => void
439
+ mockGet.mockImplementationOnce(() => new Promise((r) => { resolveGet = r }))
440
+
441
+ const { loadDesign, isLoading, lastLoadedAt } = setup()
442
+ expect(isLoading.value).toBe(false)
443
+
444
+ const promise = loadDesign(42)
445
+ expect(isLoading.value).toBe(true)
446
+
447
+ resolveGet({ data: { key: 'value' } })
448
+ await promise
449
+ expect(isLoading.value).toBe(false)
450
+ expect(lastLoadedAt.value).toBeInstanceOf(Date)
258
451
  })
259
452
  })
260
453
 
@@ -290,9 +483,10 @@ describe('useExperimentSave', () => {
290
483
  })
291
484
 
292
485
  it('should return null when pluginId not set', async () => {
293
- const { loadAnalysis } = setup()
486
+ const { loadAnalysis, error } = setup()
294
487
  const result = await loadAnalysis(1)
295
488
  expect(result).toBeNull()
489
+ expect(error.value).toBe('pluginId is required for loadAnalysis')
296
490
  })
297
491
  })
298
492
 
@@ -339,9 +533,10 @@ describe('useExperimentSave', () => {
339
533
  })
340
534
 
341
535
  it('should return false when pluginId not set', async () => {
342
- const { deleteAnalysis } = setup()
536
+ const { deleteAnalysis, error } = setup()
343
537
  const ok = await deleteAnalysis(1)
344
538
  expect(ok).toBe(false)
539
+ expect(error.value).toBe('pluginId is required for deleteAnalysis')
345
540
  })
346
541
  })
347
542
  })