@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
@@ -97,7 +97,7 @@ describe('SampleSelector', () => {
97
97
  },
98
98
  })
99
99
 
100
- expect(wrapper.find('.mint-sample-selector__select-all-count').text()).toBe('0 / 2')
100
+ expect(wrapper.find('.mint-sample-selector__select-all-count').text()).toBe('0/2')
101
101
  expect(wrapper.findAll('.mint-sample-selector__flat-name').map(item => item.text())).toEqual(['S1', 'S2'])
102
102
  })
103
103
 
@@ -115,9 +115,9 @@ describe('SampleSelector', () => {
115
115
  },
116
116
  })
117
117
 
118
- const labels = wrapper.findAll('.mint-sample-selector__actions-row .mint-sample-selector__action-label')
119
- expect(labels.map(label => label.text())).toEqual(['Grouping'])
120
- expect(wrapper.findAll('.mint-sample-selector__actions-row > .mint-sample-selector__action-btn')).toHaveLength(2)
118
+ const buttons = wrapper.findAll('.mint-sample-selector__header-actions > .mint-sample-selector__action-btn')
119
+ expect(buttons.map(button => button.text())).toEqual(['Smart group'])
120
+ expect(wrapper.find('.mint-sample-selector__header').text()).toContain('Samples0 selected')
121
121
 
122
122
  await wrapper.findAllComponents({ name: 'BaseButton' })[0].trigger('click')
123
123
 
@@ -125,6 +125,26 @@ describe('SampleSelector', () => {
125
125
  expect(modal.props('initialMode')).toBe('auto')
126
126
  })
127
127
 
128
+ it.each([
129
+ [true, 'Smart group'],
130
+ [false, 'Group'],
131
+ ])('should keep the grouping label as accessible name and tooltip when smart group is %s', (enableSmartGroup, label) => {
132
+ const wrapper = mount(SampleSelector, {
133
+ props: { samples: ['S1', 'S2'], modelValue: ['S1'], groups: [], enableSmartGroup },
134
+ global: { stubs: { AutoGroupModalInternal: true } },
135
+ })
136
+ const button = wrapper.get('.mint-sample-selector__header-actions > .mint-sample-selector__action-btn')
137
+ const count = wrapper.get('.mint-sample-selector__selected')
138
+
139
+ expect({
140
+ name: button.text(),
141
+ title: button.attributes('title'),
142
+ full: count.get('.mint-sample-selector__selected-full').text(),
143
+ short: count.get('.mint-sample-selector__selected-short').text(),
144
+ shortHidden: count.get('.mint-sample-selector__selected-short').attributes('aria-hidden'),
145
+ }).toEqual({ name: label, title: label, full: '1 selected', short: '1 sel.', shortHidden: 'true' })
146
+ })
147
+
128
148
  it('opens the grouping modal in manual mode', async () => {
129
149
  const wrapper = mount(SampleSelector, {
130
150
  props: {
@@ -140,8 +160,8 @@ describe('SampleSelector', () => {
140
160
  },
141
161
  })
142
162
 
143
- const labels = wrapper.findAll('.mint-sample-selector__actions-row .mint-sample-selector__action-label')
144
- expect(labels.map(label => label.text())).toEqual(['Grouping'])
163
+ const buttons = wrapper.findAll('.mint-sample-selector__header-actions > .mint-sample-selector__action-btn')
164
+ expect(buttons.map(button => button.text())).toEqual(['Group'])
145
165
 
146
166
  await wrapper.findAllComponents({ name: 'BaseButton' })[0].trigger('click')
147
167
 
@@ -472,9 +492,14 @@ describe('SampleSelector checkboxes (7c)', () => {
472
492
  }
473
493
  const stubs = { global: { stubs: { AutoGroupModalInternal: true } } }
474
494
 
475
- it('should show the mono selected / total count when some samples are selected', () => {
495
+ it('should show the mono selected/total count when some samples are selected', () => {
476
496
  const wrapper = mount(SampleSelector, { props: grouped, ...stubs })
477
- expect(wrapper.find('.mint-sample-selector__select-all-count').text()).toBe('1 / 3')
497
+ expect(wrapper.find('.mint-sample-selector__select-all-count').text()).toBe('1/3')
498
+ })
499
+
500
+ it('should offer clear-all-groups in the header when groups exist', () => {
501
+ const wrapper = mount(SampleSelector, { props: { ...grouped, enableGrouping: true }, ...stubs })
502
+ expect(wrapper.find('.mint-sample-selector__header-actions [aria-label="Clear all groups"]').exists()).toBe(true)
478
503
  })
479
504
 
480
505
  it('should mark the select-all checkbox mixed when some samples are selected', () => {
@@ -36,4 +36,16 @@ describe('SequenceProgressBar', () => {
36
36
  expect(wrapper.get('.mint-sequence-progress__label').text()).toBe('2 / 4 injections')
37
37
  expect(wrapper.find('.mint-sequence-progress__footer').exists()).toBe(false)
38
38
  })
39
+
40
+ it.each([
41
+ [0, null],
42
+ [2, '2 failed'],
43
+ ])('should show the failed count when %i injections failed', (failed, expected) => {
44
+ const wrapper = mount(SequenceProgressBar, {
45
+ props: { failed, progress: { current_sample: 3, total_samples: 6 } },
46
+ })
47
+
48
+ const node = wrapper.find('.mint-sequence-progress__failed')
49
+ expect(node.exists() ? node.text() : null).toBe(expected)
50
+ })
39
51
  })
@@ -728,10 +728,17 @@ describe('SettingsModal', () => {
728
728
  expect(useSettingsStore(pinia).theme).toBe('dark')
729
729
  })
730
730
 
731
- it('should render a swatch strip for each palette when appearance is shown', () => {
731
+ it('should preview each palette with its own tokens when appearance is shown', () => {
732
732
  const wrapper = mountSettingsModal({ showAppearance: true })
733
733
 
734
- expect(wrapper.findAll('.mint-settings-modal__palette')).toHaveLength(Object.keys(colorPalettes).length)
734
+ const strips = wrapper.findAll('.mint-settings-modal__swatches')
735
+ expect(strips.map((strip) => strip.attributes('data-palette'))).toEqual(Object.keys(colorPalettes))
736
+ expect(strips[0].findAll('.mint-settings-modal__swatch').map((swatch) => swatch.attributes('style'))).toEqual([
737
+ 'background-color: var(--color-primary);',
738
+ 'background-color: var(--color-primary-hover);',
739
+ 'background-color: var(--color-primary-soft);',
740
+ 'background-color: var(--color-primary-border);',
741
+ ])
735
742
  })
736
743
 
737
744
  it('should resize the density preview when the density changes', async () => {
@@ -73,3 +73,14 @@ describe('StepWizard navigation (2c)', () => {
73
73
  expect(wrapper.find('.mint-wizard__counter').text()).toBe('2 / 2')
74
74
  })
75
75
  })
76
+
77
+ describe('StepWizard orientation', () => {
78
+ it.each([
79
+ ['horizontal', false],
80
+ ['vertical', true],
81
+ ] as const)('should toggle the vertical modifier when orientation is %s', (orientation, vertical) => {
82
+ const wrapper = mount(StepWizard, { props: { steps, orientation } })
83
+
84
+ expect(wrapper.classes('mint-wizard--vertical')).toBe(vertical)
85
+ })
86
+ })
@@ -28,15 +28,22 @@ describe('WellPlate per-well decoration', () => {
28
28
  .toEqual(['2px', 'solid', 'var(--mint-warning)'])
29
29
  })
30
30
 
31
- it('should render no glyph when a QC well has no label or id', () => {
31
+ it.each([
32
+ ['qc', {}, 'Q'],
33
+ ['iqc', {}, 'i'],
34
+ ['sample', {}, ''],
35
+ ['qc', { showWellIds: true }, 'A1'],
36
+ ] as const)('should render the %s glyph when the well has no label (spec %o)', (sampleType, spec, text) => {
32
37
  const wrapper = mount(WellPlate, {
33
38
  props: {
34
- spec: { format: 6 },
35
- wells: { A1: { id: 'A1', row: 0, col: 0, state: 'filled', sampleType: 'qc' } },
39
+ spec: { format: 6, ...spec },
40
+ wells: { A1: { id: 'A1', row: 0, col: 0, state: 'filled', sampleType } },
36
41
  },
37
42
  })
38
43
 
39
- expect(wrapper.findAll('.mint-well-plate__well')[0].text()).toBe('')
44
+ const well = wrapper.findAll('.mint-well-plate__well')[0]
45
+ expect(well.text()).toBe(text)
46
+ expect(well.find('.mint-well-plate__indicator').exists()).toBe(text === 'Q' || text === 'i')
40
47
  })
41
48
 
42
49
  it('should draw plain legend swatches when the legend is shown', () => {
@@ -68,7 +68,7 @@ describe('root exports outside L1 and L2', () => {
68
68
 
69
69
  expect(values.sort()).toEqual([
70
70
  'defineControlModel', 'defineDataFrame', 'definePluginWorkspaceView', 'downloadBlob',
71
- 'resolvePluginFrontendBase', 'useExperiment', 'usePlatform', 'useRequest', 'useTheme', 'useToast',
71
+ 'providePluginIdentity', 'resolvePluginFrontendBase', 'useExperiment', 'usePlatform', 'useRequest', 'useTheme', 'useToast',
72
72
  ])
73
73
  })
74
74
 
@@ -490,6 +490,20 @@ describe('useAppExperiment', () => {
490
490
  expect(state.saveLoading.value).toBe(false)
491
491
  })
492
492
 
493
+ it('should show the refetched update time after a successful save', async () => {
494
+ const store = useExperimentStore()
495
+ useAppExperiment({ onSave: vi.fn().mockResolvedValue('Saved') })
496
+ const state = getState()
497
+ store.select(makeExperiment({ updated_at: '2025-01-01T00:00:00Z' }))
498
+
499
+ const fetch = stubExperimentFetch()
500
+ const saving = state.handleSave()
501
+ fetch.resolveWith(makeExperiment({ updated_at: '2026-10-28T09:30:00Z' }))
502
+ await saving
503
+
504
+ expect(state.popover.value.experimentUpdatedAt).toBe('2026-10-28T09:30:00Z')
505
+ })
506
+
493
507
  it('should not set saveSuccessMessage when onSave returns null', async () => {
494
508
  const onSave = vi.fn().mockResolvedValue(null)
495
509
  useAppExperiment({ onSave })
@@ -716,8 +730,9 @@ describe('useAppExperiment', () => {
716
730
  it('exposes live ExperimentPopover props from the provided state', async () => {
717
731
  const saveDisabled = ref(false)
718
732
  const saveDisabledMessage = ref<string | undefined>('Ready to save')
733
+ const dirty = ref(false)
719
734
  const onSave = vi.fn().mockResolvedValue('Saved')
720
- useAppExperiment({ onSave, saveDisabled, saveDisabledMessage })
735
+ useAppExperiment({ onSave, saveDisabled, saveDisabledMessage, dirty })
721
736
  const state = getState()
722
737
 
723
738
  useExperimentStore().select(makeExperiment({
@@ -725,26 +740,37 @@ describe('useAppExperiment', () => {
725
740
  name: 'Dose response',
726
741
  status: 'ongoing',
727
742
  experiment_code: 'EXP-012',
743
+ project: 'legacy-project',
744
+ project_name: 'Hypoxia atlas',
745
+ created_by_user: { id: 3, username: 'jdoe', first_name: 'Jane', last_name: 'Doe' },
746
+ updated_at: '2026-10-28T10:00:00Z',
728
747
  }))
729
748
 
730
749
  expect(state.popover.value).toMatchObject({
731
750
  experimentName: 'Dose response',
732
751
  experimentCode: 'EXP-012',
733
752
  experimentStatus: 'ongoing',
753
+ experimentId: 12,
754
+ experimentProject: 'Hypoxia atlas',
755
+ experimentOwner: 'Jane Doe',
756
+ experimentUpdatedAt: '2026-10-28T10:00:00Z',
734
757
  isResolving: false,
735
758
  showSave: true,
736
759
  showDetach: true,
737
760
  saveDisabled: false,
738
761
  saveDisabledMessage: 'Ready to save',
739
762
  saveLoading: false,
763
+ dirty: false,
740
764
  })
741
765
  expect(state.popover.value.error).toBeUndefined()
742
766
 
743
767
  saveDisabled.value = true
744
768
  saveDisabledMessage.value = 'Locked'
769
+ dirty.value = true
745
770
 
746
771
  expect(state.popover.value.saveDisabled).toBe(true)
747
772
  expect(state.popover.value.saveDisabledMessage).toBe('Locked')
773
+ expect(state.popover.value.dirty).toBe(true)
748
774
 
749
775
  await state.handleSave()
750
776
 
@@ -2,14 +2,16 @@ import { describe, expect, it } from 'vitest'
2
2
  import { useCalendarGrid } from '../../composables/useCalendarGrid'
3
3
 
4
4
  describe('useCalendarGrid', () => {
5
- it('builds a stable 42-day month grid with previous and next month padding', () => {
5
+ it('builds a stable Monday-first 42-day month grid with previous and next month padding', () => {
6
6
  const calendar = useCalendarGrid()
7
7
  calendar.setCurrentMonthFromDate(new Date('2026-05-07T12:00:00'))
8
8
 
9
- expect(calendar.weekDays).toEqual(['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'])
9
+ expect(calendar.weekDays).toEqual(['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'])
10
10
  expect(calendar.monthYear.value).toBe('May 2026')
11
11
  expect(calendar.calendarDays.value).toHaveLength(42)
12
12
  expect(calendar.calendarDays.value[0].date.getMonth()).toBe(3)
13
+ expect(calendar.calendarDays.value[0].date.getDate()).toBe(27)
14
+ expect(calendar.calendarDays.value[0].date.getDay()).toBe(1)
13
15
  expect(calendar.calendarDays.value.some(day => day.isCurrentMonth && day.date.getDate() === 7)).toBe(true)
14
16
  })
15
17
 
@@ -194,6 +194,117 @@ describe('useExperimentSelector', () => {
194
194
  ])
195
195
  })
196
196
 
197
+ it('should send the scope filters but not status to the stats route when counting', async () => {
198
+ fakeResponse = { total: 3, by_status: { planned: 1, ongoing: 2, completed: 0, cancelled: 0 } } as never
199
+ const hook = useExperimentSelector()
200
+ Object.assign(hook.filters, { search: 'glu', project: 'Screen', mine: true, status: 'planned' })
201
+
202
+ await hook.fetchStatusCounts()
203
+
204
+ const url = new URL(recordedUrls[0], 'http://mint.test')
205
+ expect(url.pathname).toBe('/experiments/stats')
206
+ expect(Object.fromEntries(url.searchParams)).toEqual({ search: 'glu', project: 'Screen', mine: 'true' })
207
+ expect(hook.statusCounts.value).toEqual({ planned: 1, ongoing: 2, completed: 0, cancelled: 0 })
208
+ })
209
+
210
+ it('should send mine=true on the list request when the mine filter is set', async () => {
211
+ const hook = useExperimentSelector()
212
+ hook.filters.mine = true
213
+
214
+ await hook.fetch()
215
+
216
+ expect(recordedUrls[0]).toContain('mine=true')
217
+ })
218
+
219
+ it('should sum the per-type stats when several experiment types are allowed', async () => {
220
+ vi.spyOn(useApi().client, 'get').mockImplementation(async (url: string) => {
221
+ recordedUrls.push(url)
222
+ const type = new URL(url, 'http://mint.test').searchParams.get('experiment_type')
223
+ return { data: { total: 2, by_status: { planned: type === 'drug-response' ? 2 : 0, ongoing: type === 'metabolomics' ? 2 : 0 } } }
224
+ })
225
+ const hook = useExperimentSelector({ allowedExperimentTypes: ['drug-response', 'metabolomics'] })
226
+
227
+ await hook.fetchStatusCounts()
228
+
229
+ expect(recordedUrls).toHaveLength(2)
230
+ expect(hook.statusCounts.value).toEqual({ planned: 2, ongoing: 2 })
231
+ })
232
+
233
+ it('should not query the stats route when the selected type is not allowed', async () => {
234
+ const hook = useExperimentSelector({ experimentType: 'proteomics', allowedExperimentTypes: ['drug-response'] })
235
+
236
+ await hook.fetchStatusCounts()
237
+
238
+ expect(recordedUrls).toEqual([])
239
+ expect(hook.statusCounts.value).toEqual({})
240
+ })
241
+
242
+ it('should refetch status counts with the list when a filter or the search changes and counts are on', async () => {
243
+ vi.useFakeTimers()
244
+ const hook = useExperimentSelector({ withStatusCounts: true })
245
+ const statsUrls = () => recordedUrls.filter(url => url.includes('/experiments/stats'))
246
+
247
+ hook.filters.project = 'Screen'
248
+ await nextTick()
249
+
250
+ expect(statsUrls()).toHaveLength(1)
251
+ expect(recordedUrls.filter(url => url.includes('/experiments?'))).toHaveLength(1)
252
+
253
+ hook.filters.search = 'glu'
254
+ await vi.advanceTimersByTimeAsync(300)
255
+
256
+ expect(statsUrls()).toHaveLength(2)
257
+ expect(statsUrls()[1]).toContain('search=glu')
258
+ })
259
+
260
+ it.each([
261
+ ['status', (hook: ReturnType<typeof useExperimentSelector>) => { hook.filters.status = 'planned' }, 1],
262
+ ['sort', (hook: ReturnType<typeof useExperimentSelector>) => { hook.sortKey.value = 'name:asc' }, 1],
263
+ ['project', (hook: ReturnType<typeof useExperimentSelector>) => { hook.filters.project = 'Screen' }, 2],
264
+ ] as const)('should refetch status counts on a %s change only when it changes the counts', async (_name, change, statsCalls) => {
265
+ // One body that serves both routes, so the first stats request succeeds.
266
+ fakeResponse = { ...fakeResponse, by_status: { planned: 1 } } as never
267
+ const hook = useExperimentSelector({ withStatusCounts: true })
268
+ await hook.fetchStatusCounts()
269
+
270
+ change(hook)
271
+ await nextTick()
272
+
273
+ expect(recordedUrls.filter(url => url.includes('/experiments/stats'))).toHaveLength(statsCalls)
274
+ })
275
+
276
+ it('should refetch status counts on a status change when the previous stats request failed', async () => {
277
+ const stats = vi.fn()
278
+ .mockRejectedValueOnce(new Error('offline'))
279
+ .mockResolvedValue({ data: { total: 1, by_status: { planned: 1 } } })
280
+ vi.spyOn(useApi().client, 'get').mockImplementation(async (url: string) =>
281
+ url.includes('/experiments/stats') ? stats() : { data: fakeResponse })
282
+ const hook = useExperimentSelector({ withStatusCounts: true })
283
+ await hook.fetchStatusCounts()
284
+ expect(hook.statusCounts.value).toEqual({})
285
+
286
+ hook.filters.status = 'planned'
287
+ await nextTick()
288
+
289
+ expect(stats).toHaveBeenCalledTimes(2)
290
+ await vi.waitFor(() => expect(hook.statusCounts.value).toEqual({ planned: 1 }))
291
+ })
292
+
293
+ it('should refetch status counts when a status change flushes a pending search', async () => {
294
+ vi.useFakeTimers()
295
+ const hook = useExperimentSelector({ withStatusCounts: true })
296
+ await hook.fetchStatusCounts()
297
+ const statsUrls = () => recordedUrls.filter(url => url.includes('/experiments/stats'))
298
+
299
+ hook.filters.search = 'glu'
300
+ await nextTick()
301
+ hook.filters.status = 'planned'
302
+ await nextTick()
303
+
304
+ expect(statsUrls()).toHaveLength(2)
305
+ expect(statsUrls()[1]).toContain('search=glu')
306
+ })
307
+
197
308
  it('loadMore appends the next page', async () => {
198
309
  const hook = useExperimentSelector({ limit: 1 })
199
310
 
@@ -1,12 +1,21 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
1
3
  import { describe, expect, it } from 'vitest'
2
4
 
3
5
  import {
4
6
  detectDelimiter,
5
7
  parseDelimitedText,
8
+ readTableFileAsCsv,
6
9
  useFileImport,
7
10
  validateImportFile,
8
11
  } from '../../composables/useFileImport'
9
12
 
13
+ const SAMPLES_CSV = [
14
+ 'Sample Name,Treatment,Inj Vol,Date',
15
+ 'S1,Vehicle,5,2026-10-01',
16
+ 'S2,"Drug ""A"", 10 uM",2.5,2026-10-02',
17
+ ].join('\n')
18
+
10
19
  describe('file import helpers', () => {
11
20
  it('parses quoted CSV fields', () => {
12
21
  const parsed = parseDelimitedText('name,notes\nSample A,"hello, world"\n')
@@ -41,4 +50,31 @@ describe('file import helpers', () => {
41
50
  expect(result.rows).toEqual([{ name: 'A' }])
42
51
  expect(importer.loading.value).toBe(false)
43
52
  })
53
+
54
+ it.each([
55
+ ['samples.csv', SAMPLES_CSV],
56
+ ['samples.tsv', 'Sample Name\tTreatment\tInj Vol\tDate\nS1\tVehicle\t5\t2026-10-01\nS2\t"Drug ""A"", 10 uM"\t2.5\t2026-10-02'],
57
+ ['samples.xlsx', readFileSync(resolve(__dirname, '../fixtures/table-import/samples.xlsx'))],
58
+ ])('should read the first table as CSV text when the upload is %s', async (name, content) => {
59
+ const file = new File([content], name)
60
+
61
+ expect(await readTableFileAsCsv(file)).toBe(SAMPLES_CSV)
62
+ })
63
+
64
+ it('should keep a quote inside a field as text when the quote does not open the field', () => {
65
+ expect(parseDelimitedText('S1\t5" vial\nS2\tok', { hasHeaderRow: false }).rawRows)
66
+ .toEqual([['S1', '5" vial'], ['S2', 'ok']])
67
+ })
68
+
69
+ it('should keep the stored cell text when the upload is .xlsx', async () => {
70
+ const file = new File([readFileSync(resolve(__dirname, '../fixtures/table-import/cells.xlsx'))], 'cells.xlsx')
71
+
72
+ expect(await readTableFileAsCsv(file)).toBe('2026-10-01T13:45:00,08:30:00,TRUE\n S1 ,9007199254740993,0.1')
73
+ })
74
+
75
+ it('should reject a file that is not a workbook when the name ends in .xlsx', async () => {
76
+ const file = new File(['Sample Name\nS1\n'], 'renamed.xlsx')
77
+
78
+ await expect(readTableFileAsCsv(file)).rejects.toThrow('not a valid .xlsx workbook')
79
+ })
44
80
  })
@@ -0,0 +1,68 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { defineComponent, nextTick, ref } from 'vue'
3
+ import { afterEach, describe, expect, it, vi } from 'vitest'
4
+ import { computePosition } from '@floating-ui/dom'
5
+ import { useFloatingPanel } from '../../composables/useFloatingPanel'
6
+
7
+ const cleanup = vi.fn()
8
+ vi.mock('@floating-ui/dom', async (importOriginal) => ({
9
+ ...(await importOriginal<typeof import('@floating-ui/dom')>()),
10
+ autoUpdate: vi.fn((_reference: unknown, _floating: unknown, update: () => void) => {
11
+ update()
12
+ return cleanup
13
+ }),
14
+ computePosition: vi.fn(async () => ({ x: 10, y: 20, placement: 'bottom-start', strategy: 'fixed', middlewareData: {} })),
15
+ }))
16
+
17
+ /** A trigger and the panel placed next to it. */
18
+ const Panel = defineComponent({
19
+ setup() {
20
+ const trigger = ref<HTMLElement | null>(null)
21
+ const panel = ref<HTMLElement | null>(null)
22
+ const isOpen = ref(true)
23
+ const { style } = useFloatingPanel(trigger, panel, isOpen)
24
+ return { trigger, panel, isOpen, style }
25
+ },
26
+ template: '<div><button ref="trigger">Open</button><div v-if="isOpen" ref="panel" class="panel" :style="style" /></div>',
27
+ })
28
+
29
+ async function settle() {
30
+ for (let i = 0; i < 3; i++) await nextTick()
31
+ }
32
+
33
+ describe('useFloatingPanel', () => {
34
+ afterEach(() => {
35
+ cleanup.mockClear()
36
+ document.body.innerHTML = ''
37
+ })
38
+
39
+ it.each([
40
+ ['closes', (wrapper: ReturnType<typeof mount>) => { (wrapper.vm as unknown as { isOpen: boolean }).isOpen = false }],
41
+ ['unmounts', (wrapper: ReturnType<typeof mount>) => wrapper.unmount()],
42
+ ])('should stop autoUpdate when the panel %s', async (_, end) => {
43
+ const wrapper = mount(Panel, { attachTo: document.body })
44
+ await settle()
45
+ expect(cleanup).not.toHaveBeenCalled()
46
+
47
+ end(wrapper)
48
+ await settle()
49
+
50
+ expect(cleanup).toHaveBeenCalledTimes(1)
51
+ })
52
+
53
+ it.each([
54
+ [false, ''],
55
+ [true, 'hidden'],
56
+ ])('should set the panel visibility to %j when referenceHidden is %s', async (referenceHidden, visibility) => {
57
+ vi.mocked(computePosition).mockResolvedValueOnce({
58
+ x: 10, y: 20, placement: 'bottom-start', strategy: 'fixed', middlewareData: { hide: { referenceHidden } },
59
+ })
60
+ mount(Panel, { attachTo: document.body })
61
+ await settle()
62
+
63
+ const panel = document.querySelector<HTMLElement>('.panel')!
64
+ expect(panel.style.position).toBe('fixed')
65
+ expect(panel.style.top).toBe('20px')
66
+ expect(panel.style.visibility).toBe(visibility)
67
+ })
68
+ })