@morscherlab/mint-sdk 1.4.0-beta.8 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/README.md +14 -3
  2. package/dist/{ActionMenu-CScINahU.js → AlertBox-DwgNTEqf.js} +40 -209
  3. package/dist/AlertBox-DwgNTEqf.js.map +1 -0
  4. package/dist/{AppSidebar-BmiiPfz9.js → AppSidebar-CZZaxni0.js} +1249 -239
  5. package/dist/AppSidebar-CZZaxni0.js.map +1 -0
  6. package/dist/{ConfirmDialog-CJ7hqYUP.js → ConfirmDialog-CjWoxAD1.js} +2 -2
  7. package/dist/{ConfirmDialog-CJ7hqYUP.js.map → ConfirmDialog-CjWoxAD1.js.map} +1 -1
  8. package/dist/ExperimentPopover-DujiuQIO.js +337 -0
  9. package/dist/ExperimentPopover-DujiuQIO.js.map +1 -0
  10. package/dist/{ExperimentSelectorModal-D-9z7cNx.js → ExperimentSelectorModal-B4j4eUOJ.js} +244 -211
  11. package/dist/ExperimentSelectorModal-B4j4eUOJ.js.map +1 -0
  12. package/dist/{SegmentedControl-DERBN-Xd.js → SegmentedControl-DyUPH3I5.js} +2 -2
  13. package/dist/{SegmentedControl-DERBN-Xd.js.map → SegmentedControl-DyUPH3I5.js.map} +1 -1
  14. package/dist/{SettingsModal-CSLE31yg.js → SettingsModal-D2MOOtuO.js} +28 -27
  15. package/dist/SettingsModal-D2MOOtuO.js.map +1 -0
  16. package/dist/__stories__/experiment-helpers.d.ts +10 -0
  17. package/dist/__tests__/components/AppTopBarOnboarding.test.d.ts +1 -0
  18. package/dist/__tests__/components/FloatingPanelsInModal.test.d.ts +1 -0
  19. package/dist/__tests__/composables/useFloatingPanel.test.d.ts +1 -0
  20. package/dist/__tests__/composables/useOnboarding.test.d.ts +1 -0
  21. package/dist/__tests__/styles/paletteTokens.test.d.ts +1 -0
  22. package/dist/__tests__/styles/v2AuditRules.test.d.ts +1 -0
  23. package/dist/components/AppSidebar.vue.d.ts +1 -1
  24. package/dist/components/AppTopBar.vue.d.ts +20 -5
  25. package/dist/components/ArtifactSaveDialog.vue.d.ts +9 -1
  26. package/dist/components/AuditTrail.vue.d.ts +1 -1
  27. package/dist/components/BaseButton.vue.d.ts +4 -2
  28. package/dist/components/BaseSelect.vue.d.ts +2 -2
  29. package/dist/components/ChartContainer.vue.d.ts +2 -0
  30. package/dist/components/DatePicker.vue.d.ts +1 -1
  31. package/dist/components/ExperimentPopover.vue.d.ts +18 -0
  32. package/dist/components/ExperimentSelectorModal.vue.d.ts +15 -5
  33. package/dist/components/FilePicker.vue.d.ts +1 -1
  34. package/dist/components/FileUploader.vue.d.ts +2 -2
  35. package/dist/components/JobsStatusTray.vue.d.ts +15 -4
  36. package/dist/components/PluginIcon.vue.d.ts +2 -2
  37. package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
  38. package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
  39. package/dist/components/SampleSelector.vue.d.ts +6 -6
  40. package/dist/components/SequenceProgressBar.vue.d.ts +3 -0
  41. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +3 -3
  42. package/dist/components/StepWizard.vue.d.ts +3 -0
  43. package/dist/components/internal/AppTopBarOnboardingInternal.vue.d.ts +7 -0
  44. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -1
  45. package/dist/components/internal/AutoGroupModalInternal.vue.d.ts +3 -3
  46. package/dist/components/internal/DatePickerTimeInternal.vue.d.ts +1 -1
  47. package/dist/components/internal/OnboardingLayerInternal.vue.d.ts +7 -0
  48. package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +2 -7
  49. package/dist/components/internal/SelectMultipleInternal.vue.d.ts +4 -0
  50. package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
  51. package/dist/composables/createClient.d.ts +2 -0
  52. package/dist/composables/index.d.ts +1 -0
  53. package/dist/composables/pluginIdentity.d.ts +24 -0
  54. package/dist/composables/pluginWorkspaceModel.d.ts +2 -2
  55. package/dist/composables/useAppExperiment.d.ts +8 -0
  56. package/dist/composables/useCalendarGrid.d.ts +1 -1
  57. package/dist/composables/useExperimentSelector.d.ts +6 -2
  58. package/dist/composables/useFileImport.d.ts +7 -0
  59. package/dist/composables/useFloatingPanel.d.ts +29 -0
  60. package/dist/composables/useJobCenter.d.ts +41 -3
  61. package/dist/composables/useOnboarding.d.ts +119 -0
  62. package/dist/composables/usePlatform.d.ts +1 -0
  63. package/dist/{controlSchemaModel-CP5-_nAj.js → controlSchemaModel-Bsg0t5Ab.js} +2 -2
  64. package/dist/{controlSchemaModel-CP5-_nAj.js.map → controlSchemaModel-Bsg0t5Ab.js.map} +1 -1
  65. package/dist/{data-C2PwI_dn.js → data-Boeh4uPI.js} +180 -125
  66. package/dist/data-Boeh4uPI.js.map +1 -0
  67. package/dist/data.d.ts +1 -1
  68. package/dist/data.js +9 -9
  69. package/dist/{experiment-43OJtH7R.js → experiment-DyO54FQB.js} +25 -61
  70. package/dist/experiment-DyO54FQB.js.map +1 -0
  71. package/dist/{experimentStatus-BZmU4czV.js → experimentStatus-DCn2QKG7.js} +3 -3
  72. package/dist/{experimentStatus-BZmU4czV.js.map → experimentStatus-DCn2QKG7.js.map} +1 -1
  73. package/dist/index.js +20 -20
  74. package/dist/{install-DAID6c2-.js → install-BEHp7Mo-.js} +15 -14
  75. package/dist/{install-DAID6c2-.js.map → install-BEHp7Mo-.js.map} +1 -1
  76. package/dist/install.js +1 -1
  77. package/dist/{lab-DLwRl_Ql.js → lab-D61eaX6f.js} +136 -148
  78. package/dist/lab-D61eaX6f.js.map +1 -0
  79. package/dist/lab.js +4 -3
  80. package/dist/{records-BzTcxekG.js → records-D4aQXyZu.js} +647 -389
  81. package/dist/records-D4aQXyZu.js.map +1 -0
  82. package/dist/{runtime-D6gVat6r.js → runtime-q-R0CNLh.js} +417 -54
  83. package/dist/runtime-q-R0CNLh.js.map +1 -0
  84. package/dist/runtime.d.ts +1 -1
  85. package/dist/runtime.js +7 -7
  86. package/dist/{shell-5ju97nOS.js → shell-6CGFvUUw.js} +10 -5
  87. package/dist/{shell-5ju97nOS.js.map → shell-6CGFvUUw.js.map} +1 -1
  88. package/dist/shell.d.ts +1 -0
  89. package/dist/shell.js +6 -6
  90. package/dist/stores/index.d.ts +1 -1
  91. package/dist/stores/index.js +2 -2
  92. package/dist/stores/settings.d.ts +4 -12
  93. package/dist/styles.css +2753 -2101
  94. package/dist/types/componentWorkflowTypes.d.ts +2 -0
  95. package/dist/types/components.d.ts +2 -0
  96. package/dist/types/generated/platformContext.d.ts +1 -0
  97. package/dist/types/generated-ui.d.ts +4 -0
  98. package/dist/types/index.d.ts +1 -1
  99. package/dist/types/jobs.d.ts +9 -0
  100. package/dist/types/platform.d.ts +1 -1
  101. package/dist/{BaseModal-DfRIuVoj.js → useDropdownState-uFIz6Yv8.js} +75 -5
  102. package/dist/useDropdownState-uFIz6Yv8.js.map +1 -0
  103. package/dist/{useExperimentSamples-CxcnxZ8h.js → useExperimentSamples-QEFoq4tf.js} +48 -6
  104. package/dist/useExperimentSamples-QEFoq4tf.js.map +1 -0
  105. package/dist/{useListReorder-CcbEYxX-.js → useListReorder-DsuzwWGS.js} +219 -437
  106. package/dist/useListReorder-DsuzwWGS.js.map +1 -0
  107. package/dist/{useRackEditor-BD5NIo5s.js → useRackEditor-KpZ4iJ6o.js} +85 -81
  108. package/dist/useRackEditor-KpZ4iJ6o.js.map +1 -0
  109. package/dist/useRealtime-B58Lo-Tm.js +508 -0
  110. package/dist/useRealtime-B58Lo-Tm.js.map +1 -0
  111. package/dist/useRequest-BuDmW8Jc.js +215 -0
  112. package/dist/useRequest-BuDmW8Jc.js.map +1 -0
  113. package/dist/utils/time.d.ts +7 -0
  114. package/package.json +3 -2
  115. package/src/__stories__/experiment-helpers.ts +3 -3
  116. package/src/__tests__/components/ActionMenu.test.ts +2 -2
  117. package/src/__tests__/components/AppTopBar.test.ts +257 -3
  118. package/src/__tests__/components/AppTopBarOnboarding.test.ts +160 -0
  119. package/src/__tests__/components/AppTopBarPageSelector.test.ts +125 -9
  120. package/src/__tests__/components/AppTopBarPillNav.test.ts +35 -0
  121. package/src/__tests__/components/ArtifactSaveDialog.test.ts +78 -0
  122. package/src/__tests__/components/BaseButton.test.ts +19 -0
  123. package/src/__tests__/components/BaseModal.test.ts +13 -0
  124. package/src/__tests__/components/BaseSelect.multiple.test.ts +87 -32
  125. package/src/__tests__/components/BaseSelect.test.ts +2 -1
  126. package/src/__tests__/components/BatchProgressList.test.ts +16 -0
  127. package/src/__tests__/components/CalendarGridPanel.test.ts +2 -2
  128. package/src/__tests__/components/ChartContainer.test.ts +11 -0
  129. package/src/__tests__/components/ChemicalFormula.test.ts +6 -3
  130. package/src/__tests__/components/DatePicker.datetime.test.ts +6 -0
  131. package/src/__tests__/components/DatePicker.test.ts +21 -0
  132. package/src/__tests__/components/DatePicker.time.test.ts +2 -2
  133. package/src/__tests__/components/EmptyState.test.ts +1 -1
  134. package/src/__tests__/components/ExperimentPopover.test.ts +125 -16
  135. package/src/__tests__/components/ExperimentSelectorModal.test.ts +153 -14
  136. package/src/__tests__/components/FloatingPanelsInModal.test.ts +94 -0
  137. package/src/__tests__/components/GeneratedPluginView.test.ts +23 -2
  138. package/src/__tests__/components/InstrumentStateBadge.test.ts +4 -4
  139. package/src/__tests__/components/JobsStatusTray.test.ts +373 -71
  140. package/src/__tests__/components/PluginWorkspaceView.test.ts +63 -1
  141. package/src/__tests__/components/ResourcePickerListInternal.test.ts +0 -20
  142. package/src/__tests__/components/SampleSelector.test.ts +33 -8
  143. package/src/__tests__/components/SequenceProgressBar.test.ts +12 -0
  144. package/src/__tests__/components/SettingsModal.test.ts +9 -2
  145. package/src/__tests__/components/StepWizard.test.ts +11 -0
  146. package/src/__tests__/components/WellPlate.decoration.test.ts +11 -4
  147. package/src/__tests__/components/deprecationTags.test.ts +1 -1
  148. package/src/__tests__/composables/useAppExperiment.test.ts +27 -1
  149. package/src/__tests__/composables/useCalendarGrid.test.ts +4 -2
  150. package/src/__tests__/composables/useExperimentSelector.test.ts +111 -0
  151. package/src/__tests__/composables/useFileImport.test.ts +36 -0
  152. package/src/__tests__/composables/useFloatingPanel.test.ts +68 -0
  153. package/src/__tests__/composables/useJobCenter.test.ts +262 -3
  154. package/src/__tests__/composables/useOnboarding.test.ts +317 -0
  155. package/src/__tests__/docs/frontendDocsCatalog.test.ts +4 -3
  156. package/src/__tests__/fixtures/table-import/cells.xlsx +0 -0
  157. package/src/__tests__/fixtures/table-import/samples.xlsx +0 -0
  158. package/src/__tests__/stores/settings.test.ts +21 -21
  159. package/src/__tests__/styles/baseControlsRules.test.ts +6 -2
  160. package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
  161. package/src/__tests__/styles/cssTokens.test.ts +6 -3
  162. package/src/__tests__/styles/menusShellRefresh.test.ts +22 -12
  163. package/src/__tests__/styles/paletteTokens.test.ts +44 -0
  164. package/src/__tests__/styles/v2AuditRules.test.ts +104 -0
  165. package/src/__tests__/utils/plotlyTemplate.test.ts +4 -4
  166. package/src/__tests__/utils/time.test.ts +18 -1
  167. package/src/components/ActionMenu.vue +6 -37
  168. package/src/components/AdductText.story.vue +7 -0
  169. package/src/components/AdductText.vue +1 -1
  170. package/src/components/AlertBox.story.vue +2 -2
  171. package/src/components/AlertBox.vue +18 -16
  172. package/src/components/AppSidebar.story.vue +0 -28
  173. package/src/components/AppTopBar.onboarding.story.vue +69 -0
  174. package/src/components/AppTopBar.story.vue +105 -15
  175. package/src/components/AppTopBar.vue +248 -122
  176. package/src/components/ArtifactSaveDialog.story.vue +23 -0
  177. package/src/components/ArtifactSaveDialog.vue +102 -12
  178. package/src/components/AuditTrail.story.vue +2 -1
  179. package/src/components/AuditTrail.vue +2 -21
  180. package/src/components/Avatar.vue +10 -14
  181. package/src/components/BaseButton.vue +16 -2
  182. package/src/components/BaseModal.vue +2 -1
  183. package/src/components/BaseSelect.multiple.story.vue +13 -2
  184. package/src/components/BaseSelect.vue +4 -3
  185. package/src/components/BatchProgressList.vue +35 -25
  186. package/src/components/ChartContainer.story.vue +2 -1
  187. package/src/components/ChartContainer.vue +8 -6
  188. package/src/components/ConfirmDialog.story.vue +1 -1
  189. package/src/components/EmptyState.vue +1 -1
  190. package/src/components/ExperimentPopover.story.vue +165 -53
  191. package/src/components/ExperimentPopover.vue +169 -187
  192. package/src/components/ExperimentSelectorModal.story.vue +73 -31
  193. package/src/components/ExperimentSelectorModal.vue +144 -111
  194. package/src/components/FormField.story.vue +8 -48
  195. package/src/components/GeneratedPluginView.vue +11 -10
  196. package/src/components/GeneratedResultRenderer.vue +3 -4
  197. package/src/components/InstrumentStateBadge.vue +1 -1
  198. package/src/components/JobsStatusTray.story.vue +97 -43
  199. package/src/components/JobsStatusTray.vue +287 -290
  200. package/src/components/LayoutResizeHandle.story.vue +65 -4
  201. package/src/components/LoadingSpinner.vue +1 -1
  202. package/src/components/NumberInput.vue +3 -6
  203. package/src/components/PluginIcon.story.vue +2 -10
  204. package/src/components/PluginIcon.vue +2 -2
  205. package/src/components/PluginWorkspaceView.props.ts +3 -0
  206. package/src/components/PluginWorkspaceView.story.vue +1 -1
  207. package/src/components/PluginWorkspaceView.vue +17 -0
  208. package/src/components/RuntimeUpdateNotice.vue +1 -1
  209. package/src/components/SampleSelector.story.vue +22 -7
  210. package/src/components/SampleSelector.vue +34 -37
  211. package/src/components/SampleSelectorGroupNode.vue +1 -1
  212. package/src/components/SectionCard.story.vue +35 -2
  213. package/src/components/SequenceProgressBar.story.vue +9 -0
  214. package/src/components/SequenceProgressBar.vue +6 -1
  215. package/src/components/SettingsModal.schema.ts +1 -1
  216. package/src/components/SettingsModal.vue +9 -14
  217. package/src/components/SmartGroupFieldRecipe.story.vue +1 -1
  218. package/src/components/SmartGroupManual.story.vue +1 -1
  219. package/src/components/StepWizard.story.vue +33 -0
  220. package/src/components/StepWizard.vue +8 -1
  221. package/src/components/Tooltip.vue +9 -32
  222. package/src/components/WellPlate.story.vue +3 -1
  223. package/src/components/WellPlate.vue +7 -0
  224. package/src/components/internal/AppTopBarOnboardingInternal.vue +172 -0
  225. package/src/components/internal/AppTopBarPageSelectorInternal.vue +140 -72
  226. package/src/components/internal/AppTopBarPillNavInternal.vue +33 -2
  227. package/src/components/internal/DatePickerDateInternal.vue +23 -85
  228. package/src/components/internal/DatePickerDateTimeInternal.vue +10 -14
  229. package/src/components/internal/DatePickerTimeInternal.vue +13 -9
  230. package/src/components/internal/NumberUnitInputInternal.vue +4 -18
  231. package/src/components/internal/OnboardingLayerInternal.vue +173 -0
  232. package/src/components/internal/ResourcePickerListInternal.vue +45 -85
  233. package/src/components/internal/SelectMultipleInternal.vue +187 -65
  234. package/src/components/internal/SelectSearchableInternal.vue +8 -4
  235. package/src/components/internal/WellEditPopupInternal.vue +11 -22
  236. package/src/components/internal/WorkspaceShellInternal.vue +12 -0
  237. package/src/composables/createClient.ts +2 -0
  238. package/src/composables/index.ts +1 -0
  239. package/src/composables/pluginIdentity.ts +31 -0
  240. package/src/composables/pluginWorkspaceModel.ts +2 -2
  241. package/src/composables/useAppExperiment.ts +20 -0
  242. package/src/composables/useCalendarGrid.ts +4 -3
  243. package/src/composables/useExperimentSelector.ts +77 -31
  244. package/src/composables/useFileImport.ts +51 -1
  245. package/src/composables/useFloatingPanel.ts +107 -0
  246. package/src/composables/useJobCenter.ts +188 -6
  247. package/src/composables/useOnboarding.ts +465 -0
  248. package/src/composables/usePluginWorkspace.ts +1 -1
  249. package/src/data.ts +1 -0
  250. package/src/runtime.ts +2 -1
  251. package/src/shell.ts +17 -0
  252. package/src/stores/index.ts +0 -2
  253. package/src/stores/settings.ts +20 -50
  254. package/src/styles/components/action-menu.css +1 -1
  255. package/src/styles/components/adduct-text.css +13 -5
  256. package/src/styles/components/alert-box.css +39 -58
  257. package/src/styles/components/app-layout.css +10 -6
  258. package/src/styles/components/app-page-selector.css +121 -44
  259. package/src/styles/components/app-pill-nav.css +11 -12
  260. package/src/styles/components/app-sidebar.css +9 -18
  261. package/src/styles/components/app-top-bar.css +219 -55
  262. package/src/styles/components/artifact-save-dialog.css +153 -2
  263. package/src/styles/components/audit-trail.css +16 -19
  264. package/src/styles/components/avatar.css +6 -4
  265. package/src/styles/components/batch-progress-list.css +38 -36
  266. package/src/styles/components/button.css +36 -59
  267. package/src/styles/components/calendar.css +12 -12
  268. package/src/styles/components/chart-container.css +34 -23
  269. package/src/styles/components/checkbox.css +2 -12
  270. package/src/styles/components/collapsible-card.css +27 -29
  271. package/src/styles/components/confirm-dialog.css +13 -14
  272. package/src/styles/components/dataframe.css +13 -5
  273. package/src/styles/components/date-picker.css +84 -94
  274. package/src/styles/components/datetime-picker.css +35 -47
  275. package/src/styles/components/divider.css +20 -20
  276. package/src/styles/components/dose-calculator.css +3 -3
  277. package/src/styles/components/empty-state.css +20 -17
  278. package/src/styles/components/experiment-code-badge.css +2 -0
  279. package/src/styles/components/experiment-popover.css +174 -329
  280. package/src/styles/components/experiment-selector-modal.css +88 -56
  281. package/src/styles/components/file-picker.css +3 -3
  282. package/src/styles/components/file-uploader.css +38 -49
  283. package/src/styles/components/form-builder.css +3 -3
  284. package/src/styles/components/formula-input.css +1 -1
  285. package/src/styles/components/icon-button.css +18 -24
  286. package/src/styles/components/input.css +5 -4
  287. package/src/styles/components/instrument-monitor.css +13 -15
  288. package/src/styles/components/jobs-status-tray.css +140 -296
  289. package/src/styles/components/layout-resize-handle.css +59 -16
  290. package/src/styles/components/modal.css +5 -7
  291. package/src/styles/components/molecule-input.css +2 -2
  292. package/src/styles/components/multi-select.css +65 -117
  293. package/src/styles/components/number-input.css +26 -19
  294. package/src/styles/components/onboarding.css +368 -0
  295. package/src/styles/components/pill.css +3 -2
  296. package/src/styles/components/plate-editor.css +0 -1
  297. package/src/styles/components/plugin-icon.css +26 -23
  298. package/src/styles/components/popover.css +2 -2
  299. package/src/styles/components/presence-avatars.css +1 -1
  300. package/src/styles/components/progress-bar.css +3 -3
  301. package/src/styles/components/protocol-step-editor.css +1 -3
  302. package/src/styles/components/resource-card.css +2 -2
  303. package/src/styles/components/resource-picker-list.css +6 -57
  304. package/src/styles/components/sample-hierarchy-tree.css +4 -4
  305. package/src/styles/components/sample-selector.css +138 -79
  306. package/src/styles/components/schedule-calendar.css +8 -8
  307. package/src/styles/components/searchable-select.css +15 -19
  308. package/src/styles/components/section-card.css +54 -49
  309. package/src/styles/components/segmented-control.css +18 -15
  310. package/src/styles/components/select.css +3 -3
  311. package/src/styles/components/sequence-input.css +2 -2
  312. package/src/styles/components/sequence-progress-bar.css +17 -8
  313. package/src/styles/components/settings-modal.css +7 -2
  314. package/src/styles/components/smart-group.css +18 -18
  315. package/src/styles/components/step-wizard.css +52 -16
  316. package/src/styles/components/tabs.css +2 -2
  317. package/src/styles/components/textarea.css +3 -3
  318. package/src/styles/components/time-picker.css +37 -25
  319. package/src/styles/components/toast.css +13 -11
  320. package/src/styles/components/toggle.css +1 -11
  321. package/src/styles/components/tooltip.css +12 -11
  322. package/src/styles/components/unit-input.css +9 -8
  323. package/src/styles/components/well-edit-popup.css +2 -2
  324. package/src/styles/components/well-plate.css +9 -2
  325. package/src/styles/index.css +1 -0
  326. package/src/styles/variables.css +247 -78
  327. package/src/types/componentWorkflowTypes.ts +2 -0
  328. package/src/types/components.ts +2 -0
  329. package/src/types/generated/platformContext.ts +1 -0
  330. package/src/types/generated-ui.ts +4 -0
  331. package/src/types/index.ts +1 -0
  332. package/src/types/jobs.ts +10 -0
  333. package/src/types/platform.ts +1 -1
  334. package/src/utils/time.ts +21 -0
  335. package/dist/ActionMenu-CScINahU.js.map +0 -1
  336. package/dist/AppSidebar-BmiiPfz9.js.map +0 -1
  337. package/dist/BaseModal-DfRIuVoj.js.map +0 -1
  338. package/dist/ExperimentPopover-C4sGw-is.js +0 -359
  339. package/dist/ExperimentPopover-C4sGw-is.js.map +0 -1
  340. package/dist/ExperimentSelectorModal-D-9z7cNx.js.map +0 -1
  341. package/dist/SettingsModal-CSLE31yg.js.map +0 -1
  342. package/dist/data-C2PwI_dn.js.map +0 -1
  343. package/dist/experiment-43OJtH7R.js.map +0 -1
  344. package/dist/lab-DLwRl_Ql.js.map +0 -1
  345. package/dist/records-BzTcxekG.js.map +0 -1
  346. package/dist/runtime-D6gVat6r.js.map +0 -1
  347. package/dist/useExperimentSamples-CxcnxZ8h.js.map +0 -1
  348. package/dist/useListReorder-CcbEYxX-.js.map +0 -1
  349. package/dist/useRackEditor-BD5NIo5s.js.map +0 -1
  350. package/dist/useRealtime-PalKue84.js +0 -696
  351. package/dist/useRealtime-PalKue84.js.map +0 -1
  352. package/dist/useRequest-D5JDfsgZ.js +0 -76
  353. package/dist/useRequest-D5JDfsgZ.js.map +0 -1
@@ -57,4 +57,20 @@ describe('BatchProgressList', () => {
57
57
  await wrapper.get('.mint-batch-progress__completed-toggle').trigger('click')
58
58
  expect(wrapper.findAll('.mint-batch-progress__item')).toHaveLength(3)
59
59
  })
60
+
61
+ it.each([
62
+ ['completed', undefined, '100%', '100%'],
63
+ ['processing', 40, '40%', '40%'],
64
+ ['pending', undefined, 'Queued', '0%'],
65
+ ['error', undefined, 'Failed', '0%'],
66
+ ['skipped', undefined, 'Skipped', '0%'],
67
+ ] as const)('should show %s rows with their status text and bar width', async (status, progress, text, width) => {
68
+ const wrapper = mount(BatchProgressList, {
69
+ props: { items: [{ id: 'x', label: 'X', status, progress }], autoScroll: false },
70
+ })
71
+ if (status === 'completed') await wrapper.get('.mint-batch-progress__completed-toggle').trigger('click')
72
+
73
+ expect(wrapper.get('.mint-batch-progress__item-status').text()).toBe(text)
74
+ expect(wrapper.get('.mint-batch-progress__item-progress-bar').attributes('style')).toContain(`width: ${width}`)
75
+ })
60
76
  })
@@ -13,7 +13,7 @@ describe('CalendarGridPanelInternal', () => {
13
13
  it('renders calendar state and emits navigation and selection events', async () => {
14
14
  const wrapper = mount(CalendarGridPanelInternal, {
15
15
  props: {
16
- weekDays: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
16
+ weekDays: ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
17
17
  monthYear: 'April 2026',
18
18
  days,
19
19
  selectedDate: days[1].date,
@@ -38,7 +38,7 @@ describe('CalendarGridPanelInternal', () => {
38
38
  const today = new Date()
39
39
  const wrapper = mount(CalendarGridPanelInternal, {
40
40
  props: {
41
- weekDays: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
41
+ weekDays: ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
42
42
  monthYear: 'Today',
43
43
  days: [{ date: today, isCurrentMonth: true, isDisabled: false }],
44
44
  selectedDate: today,
@@ -21,6 +21,17 @@ describe('ChartContainer', () => {
21
21
  expect(wrapper.text()).toContain('Chart')
22
22
  expect(wrapper.get('.mint-chart__footer').text()).toBe('Footer')
23
23
  })
24
+
25
+ it.each([
26
+ [undefined, 'No data'],
27
+ ['Pick samples to draw the distribution.', 'No dataPick samples to draw the distribution.'],
28
+ ])('should render the empty title and hint %s when empty', (emptyHint, text) => {
29
+ const wrapper = mount(ChartContainer, { props: { empty: true, emptyText: 'No data', emptyHint } })
30
+
31
+ const empty = wrapper.get('.mint-chart__empty')
32
+ expect(empty.text()).toBe(text)
33
+ expect(empty.find('.mint-chart__empty-hint').exists()).toBe(emptyHint !== undefined)
34
+ })
24
35
  })
25
36
 
26
37
  describe('ChartContainer header legend (9a)', () => {
@@ -25,9 +25,12 @@ describe('ChemicalFormula', () => {
25
25
  expect(mount(ChemicalFormula, { props: { emptyText: 'Unknown' } }).text()).toBe('Unknown')
26
26
  })
27
27
 
28
- it('supports standalone sans-serif adduct labels', () => {
29
- expect(mount(AdductText, { props: { value: '[M-H]-', font: 'sans' } }).classes())
30
- .toContain('mint-adduct-text--sans')
28
+ it.each([
29
+ [undefined, 'sans'],
30
+ ['mono', 'mono'],
31
+ ] as const)('should use the %s font prop as the %s adduct font', (font, cls) => {
32
+ expect(mount(AdductText, { props: { value: '[M-H]-', font } }).classes())
33
+ .toContain(`mint-adduct-text--${cls}`)
31
34
  })
32
35
 
33
36
  it('should render a trailing adduct charge as a superscript when the value is bracketed', () => {
@@ -28,6 +28,12 @@ describe('DatePicker type="datetime"', () => {
28
28
  expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual(['2026-04-22T09:00'])
29
29
  })
30
30
 
31
+ it('should show the date as ISO YYYY-MM-DD when a value is set', () => {
32
+ const wrapper = mount(DatePicker, { props: { type: 'datetime', modelValue: '2026-04-22T14:30' } })
33
+
34
+ expect((wrapper.get('input').element as HTMLInputElement).value).toBe('2026-04-22 14:30')
35
+ })
36
+
31
37
  it('uses shared dropdown state for Escape close behavior', async () => {
32
38
  const wrapper = mount(DatePicker, {
33
39
  props: {
@@ -43,6 +43,27 @@ describe('DatePicker', () => {
43
43
  wrapper.unmount()
44
44
  })
45
45
 
46
+ it('should show the bound date as ISO YYYY-MM-DD when a value is set', () => {
47
+ const wrapper = mount(DatePicker, { props: { modelValue: '2026-02-10' } })
48
+
49
+ expect((wrapper.get('input').element as HTMLInputElement).value).toBe('2026-02-10')
50
+ })
51
+
52
+ it('should clear the value from the field button when clearable', async () => {
53
+ const wrapper = mount(DatePicker, { props: { modelValue: '2026-02-10' } })
54
+
55
+ await wrapper.get('[aria-label="Clear date"]').trigger('click')
56
+
57
+ expect(wrapper.emitted('update:modelValue')).toEqual([[undefined]])
58
+ expect(dropdown()).toBeNull()
59
+ })
60
+
61
+ it('should hide the clear button when the field is disabled', () => {
62
+ const wrapper = mount(DatePicker, { props: { modelValue: '2026-02-10', disabled: true } })
63
+
64
+ expect(wrapper.find('[aria-label="Clear date"]').exists()).toBe(false)
65
+ })
66
+
46
67
  it('opens from the input with Enter, Space and ArrowDown', async () => {
47
68
  const wrapper = mount(DatePicker, { attachTo: document.body })
48
69
 
@@ -114,12 +114,12 @@ describe('DatePicker type="time"', () => {
114
114
  expect(wrapper.findAll('.mint-time-picker__slot')[1].classes()).toContain('mint-menu-item--selected')
115
115
  })
116
116
 
117
- it('should reserve the check column on unselected slots when opened', async () => {
117
+ it('should mark the selected slot without a check icon when opened', async () => {
118
118
  const wrapper = mount(DatePicker, { props: { type: 'time', modelValue: '00:15' } })
119
119
 
120
120
  await wrapper.get('input').trigger('click')
121
121
 
122
- expect(wrapper.findAll('.mint-time-picker__slot')[0].find('.mint-menu-item__check').exists()).toBe(true)
122
+ expect(wrapper.findAll('.mint-time-picker__slot')[1].find('.mint-menu-item__check').exists()).toBe(false)
123
123
  })
124
124
 
125
125
  it('should mark a slot unavailable when disabledSlots returns a reason', async () => {
@@ -13,7 +13,7 @@ describe('EmptyState', () => {
13
13
 
14
14
  const button = wrapper.find('.mint-button')
15
15
  expect(button.exists()).toBe(true)
16
- expect(button.classes()).toContain('mint-button--secondary')
16
+ expect(button.classes()).toEqual(expect.arrayContaining(['mint-button--secondary', 'mint-button--sm']))
17
17
  expect(button.text()).toBe('Create experiment')
18
18
 
19
19
  await button.trigger('click')
@@ -1,5 +1,5 @@
1
- import { mount } from '@vue/test-utils'
2
- import { afterEach, describe, expect, it } from 'vitest'
1
+ import { flushPromises, mount } from '@vue/test-utils'
2
+ import { afterEach, describe, expect, it, vi } from 'vitest'
3
3
  import ExperimentPopover from '../../components/ExperimentPopover.vue'
4
4
 
5
5
  function mountPopover(props = {}) {
@@ -16,15 +16,25 @@ function mountPopover(props = {}) {
16
16
  })
17
17
  }
18
18
 
19
+ function findOption(wrapper: ReturnType<typeof mountPopover>, label: string) {
20
+ return wrapper.findAll('.mint-experiment-popover__option').find(option => option.text().includes(label))
21
+ }
22
+
19
23
  describe('ExperimentPopover', () => {
20
24
  afterEach(() => {
21
25
  document.body.innerHTML = ''
26
+ vi.useRealTimers()
27
+ vi.restoreAllMocks()
22
28
  })
23
29
 
24
30
  it('requests experiment selection on the first click when no experiment is selected', async () => {
25
31
  const wrapper = mountPopover()
26
32
 
27
- await wrapper.find('.mint-experiment-popover__trigger').trigger('click')
33
+ const trigger = wrapper.find('.mint-experiment-popover__trigger')
34
+ expect(trigger.classes()).toContain('mint-experiment-popover__trigger--empty')
35
+ expect(trigger.text()).toContain('Attach experiment')
36
+
37
+ await trigger.trigger('click')
28
38
 
29
39
  expect(wrapper.emitted('select')).toHaveLength(1)
30
40
  expect(wrapper.find('.mint-experiment-popover__panel').exists()).toBe(false)
@@ -51,15 +61,18 @@ describe('ExperimentPopover', () => {
51
61
  wrapper.unmount()
52
62
  })
53
63
 
54
- it('prefers the experiment name in the trigger when both name and code are available', () => {
64
+ it('shows the code badge and the truncated name in the trigger, with both in its title', () => {
55
65
  const wrapper = mountPopover({
56
66
  experimentName: 'Dose response run',
57
67
  experimentCode: 'EXP-001',
68
+ maxNameWidth: 120,
58
69
  })
59
70
 
60
71
  const trigger = wrapper.find('.mint-experiment-popover__trigger')
61
- expect(trigger.text()).toContain('Dose response run')
62
- expect(trigger.text()).not.toContain('EXP-001')
72
+ expect(trigger.find('.mint-exp-code').text()).toBe('EXP-001')
73
+ expect(trigger.find('.mint-experiment-popover__trigger-text').text()).toBe('Dose response run')
74
+ expect(trigger.find('.mint-experiment-popover__trigger-text').attributes('style')).toContain('max-width: 120px')
75
+ expect(trigger.attributes('title')).toBe('EXP-001 · Dose response run')
63
76
 
64
77
  wrapper.unmount()
65
78
  })
@@ -92,31 +105,48 @@ describe('ExperimentPopover', () => {
92
105
  wrapper.unmount()
93
106
  })
94
107
 
95
- it('shows a placeholder instead of "No experiment" while the record resolves', () => {
108
+ it.each([
109
+ { panelRight: 900, alignedEnd: false },
110
+ { panelRight: 1100, alignedEnd: true },
111
+ ])('should align the panel to the right edge only when it would overflow the viewport (right $panelRight)', async ({ panelRight, alignedEnd }) => {
112
+ vi.spyOn(document.documentElement, 'clientWidth', 'get').mockReturnValue(1024)
113
+ vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ right: panelRight } as DOMRect)
114
+ const wrapper = mountPopover({ experimentName: 'Dose response run', experimentCode: 'EXP-001' })
115
+
116
+ await wrapper.find('.mint-experiment-popover__trigger').trigger('click')
117
+ await flushPromises()
118
+
119
+ expect(wrapper.get('.mint-experiment-popover__panel').classes('mint-experiment-popover__panel--end')).toBe(alignedEnd)
120
+ wrapper.unmount()
121
+ })
122
+
123
+ it('shows a placeholder instead of the empty label while the record resolves', () => {
96
124
  const wrapper = mountPopover({ isResolving: true, showDetach: true })
97
125
 
98
126
  const trigger = wrapper.find('.mint-experiment-popover__trigger')
99
127
  expect(trigger.find('.mint-experiment-popover__trigger-skeleton').exists()).toBe(true)
100
- expect(trigger.text()).not.toContain('No experiment')
128
+ expect(trigger.text()).not.toContain('Attach experiment')
101
129
  expect(trigger.attributes('aria-busy')).toBe('true')
102
130
 
103
131
  wrapper.unmount()
104
132
  })
105
133
 
106
- it('never renders the bound id as a stand-in name while resolving', () => {
107
- const wrapper = mountPopover({ isResolving: true, showDetach: true })
134
+ it('never renders the bound id as a stand-in name while resolving', async () => {
135
+ const wrapper = mountPopover({ isResolving: true, showDetach: true, experimentId: 12 })
136
+
137
+ await wrapper.find('.mint-experiment-popover__trigger').trigger('click')
108
138
 
109
139
  expect(wrapper.text()).not.toMatch(/\d/)
110
140
 
111
141
  wrapper.unmount()
112
142
  })
113
143
 
114
- it('shows the resolution failure instead of "No experiment"', () => {
144
+ it('shows the resolution failure instead of the empty label', () => {
115
145
  const wrapper = mountPopover({ error: 'Experiment not found', showDetach: true })
116
146
 
117
147
  const trigger = wrapper.find('.mint-experiment-popover__trigger')
118
148
  expect(trigger.text()).toContain('Experiment unavailable')
119
- expect(trigger.text()).not.toContain('No experiment')
149
+ expect(trigger.text()).not.toContain('Attach experiment')
120
150
  expect(trigger.classes()).toContain('mint-experiment-popover__trigger--error')
121
151
 
122
152
  wrapper.unmount()
@@ -130,7 +160,7 @@ describe('ExperimentPopover', () => {
130
160
  const panel = wrapper.find('.mint-experiment-popover__panel')
131
161
  expect(panel.text()).toContain('Experiment not found')
132
162
 
133
- await wrapper.find('.mint-experiment-popover__detach-btn').trigger('click')
163
+ await findOption(wrapper, 'Detach')!.trigger('click')
134
164
  expect(wrapper.emitted('detach')).toHaveLength(1)
135
165
 
136
166
  wrapper.unmount()
@@ -143,7 +173,7 @@ describe('ExperimentPopover', () => {
143
173
 
144
174
  const panel = wrapper.find('.mint-experiment-popover__panel')
145
175
  expect(panel.findAll('.mint-experiment-popover__skeleton-line').length).toBeGreaterThan(0)
146
- expect(panel.find('.mint-experiment-popover__select-btn').exists()).toBe(false)
176
+ expect(panel.find('.mint-experiment-popover__meta').exists()).toBe(false)
147
177
 
148
178
  wrapper.unmount()
149
179
  })
@@ -151,7 +181,8 @@ describe('ExperimentPopover', () => {
151
181
  it('hides the inline save action while no resolved experiment exists', () => {
152
182
  const wrapper = mountPopover({ isResolving: true, showSave: true })
153
183
 
154
- expect(wrapper.find('.mint-experiment-popover__save-trigger').exists()).toBe(false)
184
+ expect(wrapper.find('.mint-experiment-popover__save').exists()).toBe(false)
185
+ expect(wrapper.find('.mint-experiment-popover--save').exists()).toBe(false)
155
186
 
156
187
  wrapper.unmount()
157
188
  })
@@ -163,11 +194,89 @@ describe('ExperimentPopover', () => {
163
194
  })
164
195
 
165
196
  await wrapper.find('.mint-experiment-popover__trigger').trigger('click')
166
- await wrapper.find('.mint-experiment-popover__change-btn').trigger('click')
197
+ await findOption(wrapper, 'Change experiment')!.trigger('click')
167
198
 
168
199
  expect(wrapper.emitted('select')).toHaveLength(1)
169
200
  expect(wrapper.find('.mint-experiment-popover__panel').exists()).toBe(false)
170
201
 
171
202
  wrapper.unmount()
172
203
  })
204
+
205
+ it('lists project, status, owner and relative update time in the panel', async () => {
206
+ vi.useFakeTimers({ toFake: ['Date'] })
207
+ vi.setSystemTime(new Date('2026-10-28T12:00:00Z'))
208
+ const wrapper = mountPopover({
209
+ experimentName: 'Dose response run',
210
+ experimentCode: 'EXP-001',
211
+ experimentStatus: 'ongoing',
212
+ experimentProject: 'Hypoxia atlas',
213
+ experimentOwner: 'J. Doe',
214
+ experimentUpdatedAt: '2026-10-28T10:00:00Z',
215
+ })
216
+
217
+ await wrapper.find('.mint-experiment-popover__trigger').trigger('click')
218
+
219
+ const rows = wrapper.findAll('.mint-experiment-popover__meta dt').map(dt => [dt.text(), dt.element.nextElementSibling?.textContent])
220
+ expect(rows).toEqual([
221
+ ['Project', 'Hypoxia atlas'],
222
+ ['Status', 'Ongoing'],
223
+ ['Owner', 'J. Doe'],
224
+ ['Updated', '2 hours ago'],
225
+ ])
226
+ expect(wrapper.find('.mint-experiment-popover__panel button:not(.mint-experiment-popover__option)').exists()).toBe(false)
227
+ expect(findOption(wrapper, 'Detach')).toBeUndefined()
228
+
229
+ wrapper.unmount()
230
+ })
231
+
232
+ it.each([
233
+ { experimentId: 12, href: '/experiments/12' },
234
+ { experimentId: null, href: undefined },
235
+ ])('links "Open in MINT" to the experiment in a new tab for id $experimentId', async ({ experimentId, href }) => {
236
+ const wrapper = mountPopover({ experimentName: 'Dose response run', experimentId })
237
+
238
+ await wrapper.find('.mint-experiment-popover__trigger').trigger('click')
239
+
240
+ const link = findOption(wrapper, 'Open in MINT')
241
+ expect(link?.attributes('href')).toBe(href)
242
+ if (link) {
243
+ expect(link.attributes('target')).toBe('_blank')
244
+ expect(link.attributes('rel')).toBe('noopener')
245
+ }
246
+
247
+ wrapper.unmount()
248
+ })
249
+
250
+ it('saves at once from the joined Save button when confirmation is off', async () => {
251
+ const wrapper = mountPopover({
252
+ experimentName: 'Dose response run',
253
+ showSave: true,
254
+ confirmSave: false,
255
+ saveLabel: 'Push',
256
+ })
257
+
258
+ expect(wrapper.find('.mint-experiment-popover--save').exists()).toBe(true)
259
+ const save = wrapper.find('.mint-experiment-popover__save')
260
+ expect(save.text()).toBe('Push')
261
+
262
+ await save.trigger('click')
263
+
264
+ expect(wrapper.emitted('save')).toHaveLength(1)
265
+
266
+ wrapper.unmount()
267
+ })
268
+
269
+ it.each([
270
+ { saveLoading: false, dirty: true, label: 'Save', disabled: false, dot: true },
271
+ { saveLoading: true, dirty: true, label: 'Saving…', disabled: true, dot: false },
272
+ ])('renders Save as "$label" with the dirty dot $dot', ({ saveLoading, dirty, label, disabled, dot }) => {
273
+ const wrapper = mountPopover({ experimentName: 'Dose response run', showSave: true, saveLoading, dirty })
274
+
275
+ const save = wrapper.find('.mint-experiment-popover__save')
276
+ expect(save.text()).toBe(label)
277
+ expect(save.attributes('disabled') !== undefined).toBe(disabled)
278
+ expect(save.find('.mint-experiment-popover__dirty').exists()).toBe(dot)
279
+
280
+ wrapper.unmount()
281
+ })
173
282
  })
@@ -1,35 +1,45 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest'
2
2
  import { mount } from '@vue/test-utils'
3
3
  import { createPinia, setActivePinia } from 'pinia'
4
- import { computed, reactive, ref } from 'vue'
4
+ import { reactive, ref } from 'vue'
5
5
 
6
6
  import ExperimentSelectorModal from '../../components/ExperimentSelectorModal.vue'
7
7
  import { useExperimentSelector } from '../../composables/useExperimentSelector'
8
8
  import { useExperimentStore } from '../../stores/experiment'
9
- import type { ExperimentSummary } from '../../types'
9
+ import type { ExperimentFilters, ExperimentSummary } from '../../types'
10
10
 
11
11
  const fetchExperiments = vi.fn(async () => undefined)
12
12
  const fetchFilterOptions = vi.fn(async () => undefined)
13
+ const fetchStatusCounts = vi.fn(async () => undefined)
13
14
  const experiments = ref<ExperimentSummary[]>([])
15
+ const experimentTypes = ref<{ value: string; label: string }[]>([])
16
+ const projects = ref<{ value: string; label: string }[]>([])
17
+ const statusCounts = ref<Record<string, number>>({})
18
+ let filters: ExperimentFilters = newFilters()
19
+
20
+ function newFilters(): ExperimentFilters {
21
+ return reactive({
22
+ search: undefined,
23
+ status: undefined,
24
+ project: undefined,
25
+ experimentType: undefined,
26
+ datePreset: undefined,
27
+ mine: undefined,
28
+ })
29
+ }
14
30
 
15
31
  vi.mock('../../composables/useExperimentSelector', () => ({
16
32
  useExperimentSelector: vi.fn(() => ({
17
33
  experiments,
18
- filters: reactive({
19
- search: undefined,
20
- status: undefined,
21
- project: undefined,
22
- experimentType: undefined,
23
- datePreset: undefined,
24
- }),
34
+ filters,
25
35
  isLoading: ref(false),
26
36
  error: ref(null),
27
- sortKey: ref('created_at:desc'),
28
- experimentTypes: ref([]),
29
- projects: ref([]),
30
- groupedByProject: computed(() => []),
37
+ experimentTypes,
38
+ projects,
39
+ statusCounts,
31
40
  fetch: fetchExperiments,
32
41
  fetchFilterOptions,
42
+ fetchStatusCounts,
33
43
  })),
34
44
  }))
35
45
 
@@ -49,19 +59,30 @@ function experiment(overrides: Partial<ExperimentSummary> = {}): ExperimentSumma
49
59
 
50
60
  interface ModalProps {
51
61
  open: boolean
62
+ experimentType?: string
52
63
  allowedExperimentTypes?: string[] | null
53
64
  currentExperimentId?: number | null
54
65
  }
55
66
 
56
- function mountModal(props: ModalProps) {
67
+ function mountModal(props: ModalProps, slots: Record<string, string> = {}) {
57
68
  return mount(ExperimentSelectorModal, {
58
69
  props,
70
+ slots,
59
71
  global: {
60
72
  stubs: {
61
73
  BaseModal: {
62
74
  props: ['open', 'title', 'size'],
63
75
  template: '<div><slot /></div>',
64
76
  },
77
+ // Renders the trigger and every item inline, without the teleported panel.
78
+ ActionMenu: {
79
+ props: ['label', 'items'],
80
+ emits: ['select'],
81
+ template: `<div class="menu-stub" :data-label="label">
82
+ <slot name="trigger" :open="false" :toggle="() => {}" :keydown="() => {}" />
83
+ <button v-for="item in items" :key="item.id" class="menu-stub__item" :data-checked="item.checked" @click="$emit('select', item)">{{ item.label }}</button>
84
+ </div>`,
85
+ },
65
86
  },
66
87
  },
67
88
  })
@@ -71,8 +92,13 @@ describe('ExperimentSelectorModal', () => {
71
92
  beforeEach(() => {
72
93
  setActivePinia(createPinia())
73
94
  experiments.value = []
95
+ experimentTypes.value = []
96
+ projects.value = []
97
+ statusCounts.value = {}
98
+ filters = newFilters()
74
99
  fetchExperiments.mockClear()
75
100
  fetchFilterOptions.mockClear()
101
+ fetchStatusCounts.mockClear()
76
102
  vi.mocked(useExperimentSelector).mockClear()
77
103
  })
78
104
 
@@ -81,6 +107,7 @@ describe('ExperimentSelectorModal', () => {
81
107
 
82
108
  expect(fetchFilterOptions).toHaveBeenCalledTimes(1)
83
109
  expect(fetchExperiments).toHaveBeenCalledTimes(1)
110
+ expect(fetchStatusCounts).toHaveBeenCalledTimes(1)
84
111
  })
85
112
 
86
113
  it('does not fetch while mounted closed', () => {
@@ -96,6 +123,7 @@ describe('ExperimentSelectorModal', () => {
96
123
  expect(useExperimentSelector).toHaveBeenCalledWith({
97
124
  experimentType: undefined,
98
125
  allowedExperimentTypes: ['mass_spectrometry'],
126
+ withStatusCounts: true,
99
127
  })
100
128
  })
101
129
 
@@ -129,6 +157,12 @@ describe('ExperimentSelectorModal', () => {
129
157
  expect(wrapper.emitted('update:open')).toEqual([[false]])
130
158
  })
131
159
 
160
+ it('should mark the search field autofocus when the modal opens', () => {
161
+ const wrapper = mountModal({ open: true })
162
+
163
+ expect(wrapper.get('input[type="search"]').attributes()).toHaveProperty('autofocus')
164
+ })
165
+
132
166
  it('should show the experiment code as a pill on the second line', () => {
133
167
  experiments.value = [experiment()]
134
168
  const wrapper = mountModal({ open: true })
@@ -168,4 +202,109 @@ describe('ExperimentSelectorModal', () => {
168
202
  expect(store.currentId).toBeNull()
169
203
  expect(wrapper.emitted('update:open')).toEqual([[false]])
170
204
  })
205
+
206
+ it('should show the description slot above the experiment list', () => {
207
+ const wrapper = mountModal({ open: true }, { description: '<p class="why">Keep the result with its samples.</p>' })
208
+
209
+ const description = wrapper.get('.mint-experiment-selector__description')
210
+ expect(description.find('.why').text()).toBe('Keep the result with its samples.')
211
+ expect(description.element.compareDocumentPosition(wrapper.get('.mint-experiment-selector').element))
212
+ .toBe(Node.DOCUMENT_POSITION_FOLLOWING)
213
+ })
214
+
215
+ it.each([
216
+ [null, false],
217
+ [7, true],
218
+ ])('should render the footer slot when the current experiment is %s, with the clear button %s', (currentExperimentId, hasClear) => {
219
+ const wrapper = mountModal({ open: true, currentExperimentId }, { footer: '<span class="extra">Skip</span>' })
220
+
221
+ const footer = wrapper.get('.mint-experiment-selector__footer')
222
+ expect(footer.find('.extra').exists()).toBe(true)
223
+ expect(footer.find('.mint-experiment-selector__clear-btn').exists()).toBe(hasClear)
224
+ })
225
+
226
+ it('should close without touching the store from the footer slot close()', async () => {
227
+ const store = useExperimentStore()
228
+ const wrapper = mountModal({ open: true }, {
229
+ footer: '<template #footer="{ close }"><button class="skip" @click="close">Skip</button></template>',
230
+ })
231
+
232
+ await wrapper.get('.skip').trigger('click')
233
+
234
+ expect(wrapper.emitted('update:open')).toEqual([[false]])
235
+ expect(store.current).toBeNull()
236
+ })
237
+
238
+ it('should list each status with its count and check the current one', async () => {
239
+ statusCounts.value = { ongoing: 3, planned: 1, completed: 0, cancelled: 2 }
240
+ filters.status = 'ongoing'
241
+ const wrapper = mountModal({ open: true })
242
+
243
+ const items = wrapper.get('[data-label="Status"]').findAll('.menu-stub__item')
244
+ expect(items.map(item => item.text())).toEqual([
245
+ 'Any status', 'Ongoing · 3', 'Planned · 1', 'Completed · 0', 'Cancelled · 2',
246
+ ])
247
+ expect(items.map(item => item.attributes('data-checked'))).toEqual(['false', 'true', 'false', 'false', 'false'])
248
+ })
249
+
250
+ it('should show the active status with its dot on the trigger and reset it from Any status', async () => {
251
+ filters.status = 'completed'
252
+ const wrapper = mountModal({ open: true })
253
+ const menu = wrapper.get('[data-label="Status"]')
254
+
255
+ const trigger = menu.get('.mint-experiment-selector__toggle')
256
+ expect(trigger.classes()).toContain('mint-experiment-selector__toggle--active')
257
+ expect(trigger.text()).toBe('Completed')
258
+ expect(trigger.find('.mint-experiment-selector__dot--success').exists()).toBe(true)
259
+
260
+ await menu.findAll('.menu-stub__item')[0].trigger('click')
261
+
262
+ expect(filters.status).toBeUndefined()
263
+ })
264
+
265
+ it('should filter by the chosen project', async () => {
266
+ projects.value = [{ value: 'Screen', label: 'Screen' }]
267
+ const wrapper = mountModal({ open: true })
268
+ const menu = wrapper.get('[data-label="Project"]')
269
+
270
+ await menu.findAll('.menu-stub__item')[1].trigger('click')
271
+
272
+ expect(filters.project).toBe('Screen')
273
+ expect(menu.get('.mint-experiment-selector__toggle').text()).toBe('Screen')
274
+ })
275
+
276
+ it.each([
277
+ { experimentType: undefined, shown: true },
278
+ { experimentType: 'screen', shown: false },
279
+ ])('should show the type menu only when the type is choosable (experimentType=$experimentType)', ({ experimentType, shown }) => {
280
+ experimentTypes.value = [{ value: 'screen', label: 'Screen' }, { value: 'lcms', label: 'LC-MS' }]
281
+ const wrapper = mountModal({ open: true, experimentType })
282
+
283
+ expect(wrapper.find('[data-label="Type"]').exists()).toBe(shown)
284
+ })
285
+
286
+ it('should toggle the mine filter from the Mine button', async () => {
287
+ const wrapper = mountModal({ open: true })
288
+ const mine = wrapper.get('[aria-pressed]')
289
+
290
+ await mine.trigger('click')
291
+
292
+ expect(filters.mine).toBe(true)
293
+ expect(mine.attributes('aria-pressed')).toBe('true')
294
+ })
295
+
296
+ it('should clear status, project and mine but keep the search when Clear N is clicked', async () => {
297
+ const wrapper = mountModal({ open: true })
298
+ expect(wrapper.find('.mint-experiment-selector__clear-filters').exists()).toBe(false)
299
+
300
+ Object.assign(filters, { search: 'glu', status: 'planned', project: 'Screen', mine: true })
301
+ await wrapper.vm.$nextTick()
302
+ const clear = wrapper.get('.mint-experiment-selector__clear-filters')
303
+ expect(clear.text()).toBe('Clear 3')
304
+
305
+ await clear.trigger('click')
306
+
307
+ expect(filters).toMatchObject({ search: 'glu', status: undefined, project: undefined, mine: undefined })
308
+ expect(wrapper.find('.mint-experiment-selector__clear-filters').exists()).toBe(false)
309
+ })
171
310
  })