@morscherlab/mint-sdk 1.4.0-beta.7 → 1.4.0-beta.9
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.
- package/dist/{ActionMenu-CScINahU.js → AlertBox-B2Bgr3ei.js} +40 -209
- package/dist/AlertBox-B2Bgr3ei.js.map +1 -0
- package/dist/{AppSidebar-BmiiPfz9.js → AppSidebar-Dq9W1jE2.js} +160 -109
- package/dist/AppSidebar-Dq9W1jE2.js.map +1 -0
- package/dist/{ConfirmDialog-CJ7hqYUP.js → ConfirmDialog-nbJj51is.js} +2 -2
- package/dist/{ConfirmDialog-CJ7hqYUP.js.map → ConfirmDialog-nbJj51is.js.map} +1 -1
- package/dist/ExperimentPopover-5T1fAq2L.js +337 -0
- package/dist/ExperimentPopover-5T1fAq2L.js.map +1 -0
- package/dist/{ExperimentSelectorModal-D-9z7cNx.js → ExperimentSelectorModal-65peG4Up.js} +198 -181
- package/dist/ExperimentSelectorModal-65peG4Up.js.map +1 -0
- package/dist/{SegmentedControl-DERBN-Xd.js → SegmentedControl-BtnnjWzs.js} +2 -2
- package/dist/{SegmentedControl-DERBN-Xd.js.map → SegmentedControl-BtnnjWzs.js.map} +1 -1
- package/dist/{SettingsModal-CSLE31yg.js → SettingsModal-483nyI6b.js} +9 -9
- package/dist/SettingsModal-483nyI6b.js.map +1 -0
- package/dist/components/AppSidebar.vue.d.ts +1 -1
- package/dist/components/ArtifactSaveDialog.vue.d.ts +9 -1
- package/dist/components/AuditTrail.vue.d.ts +1 -1
- package/dist/components/BaseSelect.vue.d.ts +2 -2
- package/dist/components/ChartContainer.vue.d.ts +2 -0
- package/dist/components/DatePicker.vue.d.ts +1 -1
- package/dist/components/ExperimentPopover.vue.d.ts +18 -0
- package/dist/components/ExperimentSelectorModal.vue.d.ts +0 -4
- package/dist/components/FilePicker.vue.d.ts +1 -1
- package/dist/components/FileUploader.vue.d.ts +2 -2
- package/dist/components/PluginIcon.vue.d.ts +2 -2
- package/dist/components/SampleSelector.vue.d.ts +6 -6
- package/dist/components/SequenceProgressBar.vue.d.ts +3 -0
- package/dist/components/StepWizard.vue.d.ts +3 -0
- package/dist/components/internal/AutoGroupModalInternal.vue.d.ts +1 -1
- package/dist/components/internal/DatePickerTimeInternal.vue.d.ts +1 -1
- package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +2 -7
- package/dist/components/internal/SelectMultipleInternal.vue.d.ts +4 -0
- package/dist/composables/createClient.d.ts +2 -0
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/pluginIdentity.d.ts +24 -0
- package/dist/composables/useAppExperiment.d.ts +8 -0
- package/dist/composables/useCalendarGrid.d.ts +1 -1
- package/dist/composables/useExperimentSelector.d.ts +6 -2
- package/dist/composables/usePlatform.d.ts +1 -0
- package/dist/{controlSchemaModel-CP5-_nAj.js → controlSchemaModel-B8CTXagP.js} +2 -2
- package/dist/{controlSchemaModel-CP5-_nAj.js.map → controlSchemaModel-B8CTXagP.js.map} +1 -1
- package/dist/{data-C2PwI_dn.js → data-CgILxDLW.js} +178 -124
- package/dist/data-CgILxDLW.js.map +1 -0
- package/dist/data.js +8 -8
- package/dist/{experimentStatus-BZmU4czV.js → experimentStatus-Cqp6qNrR.js} +3 -3
- package/dist/{experimentStatus-BZmU4czV.js.map → experimentStatus-Cqp6qNrR.js.map} +1 -1
- package/dist/index.js +19 -19
- package/dist/{install-DAID6c2-.js → install-BtLWTk6H.js} +15 -14
- package/dist/{install-DAID6c2-.js.map → install-BtLWTk6H.js.map} +1 -1
- package/dist/install.js +1 -1
- package/dist/{lab-DLwRl_Ql.js → lab-MHzp-_J8.js} +128 -147
- package/dist/lab-MHzp-_J8.js.map +1 -0
- package/dist/lab.js +4 -3
- package/dist/{records-BzTcxekG.js → records-B6gpM_3e.js} +472 -274
- package/dist/records-B6gpM_3e.js.map +1 -0
- package/dist/{runtime-D6gVat6r.js → runtime-7vfl_bI3.js} +27 -31
- package/dist/runtime-7vfl_bI3.js.map +1 -0
- package/dist/runtime.js +6 -6
- package/dist/{shell-5ju97nOS.js → shell-Cs9OJMnm.js} +5 -5
- package/dist/{shell-5ju97nOS.js.map → shell-Cs9OJMnm.js.map} +1 -1
- package/dist/shell.js +5 -5
- package/dist/styles.css +1606 -1666
- package/dist/types/componentWorkflowTypes.d.ts +2 -0
- package/dist/types/generated/platformContext.d.ts +1 -0
- package/dist/types/generated-ui.d.ts +4 -0
- package/dist/{BaseModal-DfRIuVoj.js → useDropdownState-8yWMu-7_.js} +60 -2
- package/dist/useDropdownState-8yWMu-7_.js.map +1 -0
- package/dist/{useExperimentSamples-CxcnxZ8h.js → useExperimentSamples-DHnxKFAF.js} +3 -3
- package/dist/{useExperimentSamples-CxcnxZ8h.js.map → useExperimentSamples-DHnxKFAF.js.map} +1 -1
- package/dist/{useListReorder-CcbEYxX-.js → useListReorder-BitdUhxn.js} +109 -277
- package/dist/useListReorder-BitdUhxn.js.map +1 -0
- package/dist/{useRackEditor-BD5NIo5s.js → useRackEditor-DiwCt77M.js} +71 -57
- package/dist/useRackEditor-DiwCt77M.js.map +1 -0
- package/dist/{useRealtime-PalKue84.js → useRealtime-C3iy9QkU.js} +7 -4
- package/dist/useRealtime-C3iy9QkU.js.map +1 -0
- package/dist/useRequest-D506eHi8.js +244 -0
- package/dist/useRequest-D506eHi8.js.map +1 -0
- package/dist/utils/time.d.ts +7 -0
- package/package.json +1 -1
- package/src/__tests__/components/AppTopBar.test.ts +51 -2
- package/src/__tests__/components/AppTopBarPillNav.test.ts +35 -0
- package/src/__tests__/components/ArtifactSaveDialog.test.ts +78 -0
- package/src/__tests__/components/BaseSelect.multiple.test.ts +87 -32
- package/src/__tests__/components/BaseSelect.test.ts +2 -1
- package/src/__tests__/components/BatchProgressList.test.ts +16 -0
- package/src/__tests__/components/CalendarGridPanel.test.ts +2 -2
- package/src/__tests__/components/ChartContainer.test.ts +11 -0
- package/src/__tests__/components/ChemicalFormula.test.ts +6 -3
- package/src/__tests__/components/DatePicker.datetime.test.ts +6 -0
- package/src/__tests__/components/DatePicker.test.ts +21 -0
- package/src/__tests__/components/DatePicker.time.test.ts +2 -2
- package/src/__tests__/components/EmptyState.test.ts +1 -1
- package/src/__tests__/components/ExperimentPopover.test.ts +125 -16
- package/src/__tests__/components/ExperimentSelectorModal.test.ts +119 -13
- package/src/__tests__/components/GeneratedPluginView.test.ts +22 -1
- package/src/__tests__/components/InstrumentStateBadge.test.ts +4 -4
- package/src/__tests__/components/ResourcePickerListInternal.test.ts +0 -20
- package/src/__tests__/components/SampleSelector.test.ts +13 -8
- package/src/__tests__/components/SequenceProgressBar.test.ts +12 -0
- package/src/__tests__/components/StepWizard.test.ts +11 -0
- package/src/__tests__/components/WellPlate.decoration.test.ts +11 -4
- package/src/__tests__/components/deprecationTags.test.ts +1 -1
- package/src/__tests__/composables/useAppExperiment.test.ts +27 -1
- package/src/__tests__/composables/useCalendarGrid.test.ts +4 -2
- package/src/__tests__/composables/useExperimentSelector.test.ts +111 -0
- package/src/__tests__/styles/baseControlsRules.test.ts +6 -2
- package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
- package/src/__tests__/styles/cssTokens.test.ts +2 -2
- package/src/__tests__/styles/menusShellRefresh.test.ts +14 -10
- package/src/__tests__/utils/plotlyTemplate.test.ts +4 -4
- package/src/__tests__/utils/time.test.ts +18 -1
- package/src/components/AdductText.story.vue +7 -0
- package/src/components/AdductText.vue +1 -1
- package/src/components/AlertBox.story.vue +2 -2
- package/src/components/AlertBox.vue +18 -16
- package/src/components/AppSidebar.story.vue +0 -28
- package/src/components/AppTopBar.story.vue +47 -12
- package/src/components/AppTopBar.vue +118 -114
- package/src/components/ArtifactSaveDialog.story.vue +23 -0
- package/src/components/ArtifactSaveDialog.vue +102 -12
- package/src/components/AuditTrail.story.vue +2 -1
- package/src/components/AuditTrail.vue +2 -21
- package/src/components/Avatar.vue +10 -14
- package/src/components/BaseSelect.multiple.story.vue +13 -2
- package/src/components/BaseSelect.vue +4 -3
- package/src/components/BatchProgressList.vue +35 -25
- package/src/components/ChartContainer.story.vue +2 -1
- package/src/components/ChartContainer.vue +8 -6
- package/src/components/ConfirmDialog.story.vue +1 -1
- package/src/components/EmptyState.vue +1 -1
- package/src/components/ExperimentPopover.story.vue +165 -53
- package/src/components/ExperimentPopover.vue +169 -187
- package/src/components/ExperimentSelectorModal.story.vue +40 -36
- package/src/components/ExperimentSelectorModal.vue +124 -110
- package/src/components/FormField.story.vue +8 -48
- package/src/components/GeneratedPluginView.vue +7 -1
- package/src/components/InstrumentStateBadge.vue +1 -1
- package/src/components/JobsStatusTray.vue +1 -1
- package/src/components/LoadingSpinner.vue +1 -1
- package/src/components/NumberInput.vue +3 -6
- package/src/components/PluginIcon.story.vue +2 -10
- package/src/components/PluginIcon.vue +2 -2
- package/src/components/PluginWorkspaceView.story.vue +1 -1
- package/src/components/RuntimeUpdateNotice.vue +1 -1
- package/src/components/SampleSelector.story.vue +1 -1
- package/src/components/SampleSelector.vue +27 -37
- package/src/components/SampleSelectorGroupNode.vue +1 -1
- package/src/components/SectionCard.story.vue +2 -2
- package/src/components/SequenceProgressBar.story.vue +9 -0
- package/src/components/SequenceProgressBar.vue +6 -1
- package/src/components/SettingsModal.schema.ts +1 -1
- package/src/components/SettingsModal.vue +2 -2
- package/src/components/SmartGroupFieldRecipe.story.vue +1 -1
- package/src/components/SmartGroupManual.story.vue +1 -1
- package/src/components/StepWizard.story.vue +33 -0
- package/src/components/StepWizard.vue +8 -1
- package/src/components/WellPlate.story.vue +3 -1
- package/src/components/WellPlate.vue +7 -0
- package/src/components/internal/AppTopBarPillNavInternal.vue +33 -2
- package/src/components/internal/DatePickerDateInternal.vue +16 -40
- package/src/components/internal/DatePickerDateTimeInternal.vue +3 -13
- package/src/components/internal/DatePickerTimeInternal.vue +5 -8
- package/src/components/internal/ResourcePickerListInternal.vue +45 -85
- package/src/components/internal/SelectMultipleInternal.vue +182 -65
- package/src/components/internal/SelectSearchableInternal.vue +2 -3
- package/src/composables/createClient.ts +2 -0
- package/src/composables/index.ts +1 -0
- package/src/composables/pluginIdentity.ts +31 -0
- package/src/composables/useAppExperiment.ts +20 -0
- package/src/composables/useCalendarGrid.ts +4 -3
- package/src/composables/useExperimentSelector.ts +77 -31
- package/src/styles/components/adduct-text.css +13 -5
- package/src/styles/components/alert-box.css +39 -58
- package/src/styles/components/app-layout.css +6 -5
- package/src/styles/components/app-page-selector.css +3 -1
- package/src/styles/components/app-pill-nav.css +11 -12
- package/src/styles/components/app-sidebar.css +9 -18
- package/src/styles/components/app-top-bar.css +57 -55
- package/src/styles/components/artifact-save-dialog.css +153 -2
- package/src/styles/components/audit-trail.css +16 -19
- package/src/styles/components/avatar.css +6 -4
- package/src/styles/components/batch-progress-list.css +38 -36
- package/src/styles/components/button.css +28 -59
- package/src/styles/components/calendar.css +12 -12
- package/src/styles/components/chart-container.css +34 -23
- package/src/styles/components/checkbox.css +2 -12
- package/src/styles/components/collapsible-card.css +27 -29
- package/src/styles/components/confirm-dialog.css +13 -14
- package/src/styles/components/dataframe.css +12 -4
- package/src/styles/components/date-picker.css +80 -92
- package/src/styles/components/datetime-picker.css +31 -43
- package/src/styles/components/divider.css +20 -20
- package/src/styles/components/dose-calculator.css +3 -3
- package/src/styles/components/empty-state.css +20 -17
- package/src/styles/components/experiment-code-badge.css +2 -0
- package/src/styles/components/experiment-popover.css +174 -329
- package/src/styles/components/experiment-selector-modal.css +75 -56
- package/src/styles/components/file-picker.css +3 -3
- package/src/styles/components/file-uploader.css +38 -49
- package/src/styles/components/form-builder.css +3 -3
- package/src/styles/components/formula-input.css +1 -1
- package/src/styles/components/icon-button.css +18 -24
- package/src/styles/components/input.css +5 -4
- package/src/styles/components/instrument-monitor.css +13 -15
- package/src/styles/components/jobs-status-tray.css +31 -25
- package/src/styles/components/modal.css +2 -4
- package/src/styles/components/molecule-input.css +2 -2
- package/src/styles/components/multi-select.css +65 -117
- package/src/styles/components/number-input.css +26 -19
- package/src/styles/components/pill.css +3 -2
- package/src/styles/components/plate-editor.css +0 -1
- package/src/styles/components/plugin-icon.css +26 -23
- package/src/styles/components/popover.css +2 -2
- package/src/styles/components/presence-avatars.css +1 -1
- package/src/styles/components/protocol-step-editor.css +1 -3
- package/src/styles/components/resource-card.css +2 -2
- package/src/styles/components/resource-picker-list.css +6 -57
- package/src/styles/components/sample-hierarchy-tree.css +4 -4
- package/src/styles/components/sample-selector.css +95 -79
- package/src/styles/components/schedule-calendar.css +8 -8
- package/src/styles/components/searchable-select.css +12 -12
- package/src/styles/components/section-card.css +28 -47
- package/src/styles/components/segmented-control.css +18 -15
- package/src/styles/components/select.css +3 -3
- package/src/styles/components/sequence-input.css +2 -2
- package/src/styles/components/sequence-progress-bar.css +17 -8
- package/src/styles/components/smart-group.css +18 -18
- package/src/styles/components/step-wizard.css +52 -16
- package/src/styles/components/tabs.css +2 -2
- package/src/styles/components/textarea.css +3 -3
- package/src/styles/components/time-picker.css +36 -21
- package/src/styles/components/toast.css +12 -10
- package/src/styles/components/toggle.css +1 -11
- package/src/styles/components/tooltip.css +10 -9
- package/src/styles/components/unit-input.css +8 -7
- package/src/styles/components/well-plate.css +9 -2
- package/src/styles/variables.css +106 -75
- package/src/types/componentWorkflowTypes.ts +2 -0
- package/src/types/generated/platformContext.ts +1 -0
- package/src/types/generated-ui.ts +4 -0
- package/src/utils/time.ts +21 -0
- package/dist/ActionMenu-CScINahU.js.map +0 -1
- package/dist/AppSidebar-BmiiPfz9.js.map +0 -1
- package/dist/BaseModal-DfRIuVoj.js.map +0 -1
- package/dist/ExperimentPopover-C4sGw-is.js +0 -359
- package/dist/ExperimentPopover-C4sGw-is.js.map +0 -1
- package/dist/ExperimentSelectorModal-D-9z7cNx.js.map +0 -1
- package/dist/SettingsModal-CSLE31yg.js.map +0 -1
- package/dist/data-C2PwI_dn.js.map +0 -1
- package/dist/lab-DLwRl_Ql.js.map +0 -1
- package/dist/records-BzTcxekG.js.map +0 -1
- package/dist/runtime-D6gVat6r.js.map +0 -1
- package/dist/useListReorder-CcbEYxX-.js.map +0 -1
- package/dist/useRackEditor-BD5NIo5s.js.map +0 -1
- package/dist/useRealtime-PalKue84.js.map +0 -1
- package/dist/useRequest-D5JDfsgZ.js +0 -76
- package/dist/useRequest-D5JDfsgZ.js.map +0 -1
|
@@ -196,4 +196,39 @@ describe('AppTopBarPillNavInternal', () => {
|
|
|
196
196
|
|
|
197
197
|
expect(wrapper.find('.mint-pill-nav__item').text()).toBe('Records')
|
|
198
198
|
})
|
|
199
|
+
const SIX = ['One', 'Two', 'Three', 'Four', 'Five', { id: 'grp', label: 'Group', children: ['Six'] }]
|
|
200
|
+
|
|
201
|
+
it('should fold the items after the fourth into a More dropdown', async () => {
|
|
202
|
+
const wrapper = mount(AppTopBarPillNavInternal, { props: { currentItemId: 'One', items: SIX } })
|
|
203
|
+
|
|
204
|
+
expect(wrapper.findAll('.mint-pill-nav__item').map(item => item.text())).toEqual([
|
|
205
|
+
'One', 'Two', 'Three', 'Four', 'More',
|
|
206
|
+
])
|
|
207
|
+
await wrapper.findAll('.mint-pill-nav__item')[4].trigger('click')
|
|
208
|
+
expect(wrapper.findAll('.mint-pill-nav__dropdown-label').map(option => option.text())).toEqual(['Five', 'Six'])
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('should emit the original item or group when a folded entry is chosen', async () => {
|
|
212
|
+
const wrapper = mount(AppTopBarPillNavInternal, { props: { currentItemId: 'One', items: SIX } })
|
|
213
|
+
|
|
214
|
+
await wrapper.findAll('.mint-pill-nav__item')[4].trigger('click')
|
|
215
|
+
await wrapper.findAll('.mint-pill-nav__dropdown-item')[0].trigger('click')
|
|
216
|
+
await wrapper.findAll('.mint-pill-nav__item')[4].trigger('click')
|
|
217
|
+
await wrapper.findAll('.mint-pill-nav__dropdown-item')[1].trigger('click')
|
|
218
|
+
|
|
219
|
+
expect(wrapper.emitted('select')).toEqual([[{ id: 'Five', label: 'Five' }]])
|
|
220
|
+
expect(wrapper.emitted('option-select')?.[0]).toEqual([
|
|
221
|
+
{ id: 'Six', label: 'Six' },
|
|
222
|
+
{ id: 'grp', label: 'Group', children: [{ id: 'Six', label: 'Six' }] },
|
|
223
|
+
])
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('should label the More pill with a folded item when that item is current', () => {
|
|
227
|
+
const wrapper = mount(AppTopBarPillNavInternal, { props: { currentItemId: 'Five', items: SIX } })
|
|
228
|
+
|
|
229
|
+
const more = wrapper.findAll('.mint-pill-nav__item')[4]
|
|
230
|
+
expect(more.text()).toBe('Five')
|
|
231
|
+
expect(more.find('.mint-pill-nav__chevron').exists()).toBe(true)
|
|
232
|
+
expect(more.classes()).toContain('mint-pill-nav__item--active')
|
|
233
|
+
})
|
|
199
234
|
})
|
|
@@ -565,4 +565,82 @@ describe('ArtifactSaveDialog', () => {
|
|
|
565
565
|
expect(wrapper.find('.mint-artifact-save-dialog__error').exists()).toBe(true)
|
|
566
566
|
})
|
|
567
567
|
})
|
|
568
|
+
|
|
569
|
+
// -------------------------------------------------------------------------
|
|
570
|
+
// Save mode (replace vs new) and experiment strip
|
|
571
|
+
// -------------------------------------------------------------------------
|
|
572
|
+
|
|
573
|
+
describe('save mode', () => {
|
|
574
|
+
it.each([
|
|
575
|
+
[null, false],
|
|
576
|
+
[makeDetail(), true],
|
|
577
|
+
])('should offer the replace/new cards only when an artifact can be replaced (%#)', (existingArtifact, shown) => {
|
|
578
|
+
const { wrapper } = mountDialog({ result: { x: 1 }, existingArtifact })
|
|
579
|
+
|
|
580
|
+
expect(wrapper.find('.mint-artifact-save-dialog__modes').exists()).toBe(shown)
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
it('should send a create payload when save as new is picked over an existing artifact', async () => {
|
|
584
|
+
const { wrapper, saveHandler } = mountDialog({ mode: 'json', result: { x: 1 }, existingArtifact: makeDetail() })
|
|
585
|
+
expect(wrapper.get('.mint-artifact-save-dialog__submit').text()).toBe('Replace result')
|
|
586
|
+
|
|
587
|
+
await wrapper.findAll('.mint-artifact-save-dialog__mode')[1].trigger('click')
|
|
588
|
+
expect(wrapper.find('.mint-artifact-save-dialog__name').exists()).toBe(true)
|
|
589
|
+
expect(wrapper.get('.mint-artifact-save-dialog__submit').text()).toBe('Save result')
|
|
590
|
+
await submit(wrapper)
|
|
591
|
+
|
|
592
|
+
expect(saveHandler.mock.calls[0][0]).toMatchObject({ kind: 'json', mode: 'create', artifactKey: 'default' })
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
// An explicit "new result" must never upsert over an existing JSON artifact.
|
|
596
|
+
it.each(['fit', 'other'])('should block a json save as new when the key %s exists', async (key) => {
|
|
597
|
+
const { wrapper, saveHandler } = mountDialog({
|
|
598
|
+
mode: 'json',
|
|
599
|
+
result: { x: 1 },
|
|
600
|
+
existingArtifact: makeDetail(),
|
|
601
|
+
existingArtifactKeys: ['other'],
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
await wrapper.findAll('.mint-artifact-save-dialog__mode')[1].trigger('click')
|
|
605
|
+
await wrapper.get('.mint-artifact-save-dialog__key input').setValue(key)
|
|
606
|
+
await submit(wrapper)
|
|
607
|
+
|
|
608
|
+
expect(saveHandler).not.toHaveBeenCalled()
|
|
609
|
+
expect(wrapper.get('.mint-artifact-save-dialog__submit').attributes('disabled')).toBeDefined()
|
|
610
|
+
expect(wrapper.get('.mint-artifact-save-dialog__key').text()).toContain('already used')
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
it('should show the experiment strip with code and name when the experiment is known', () => {
|
|
614
|
+
const { wrapper } = mountDialog({
|
|
615
|
+
experiment: { id: 42, experiment_code: 'EXP-042', name: 'Dose response' },
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
expect(wrapper.get('.mint-artifact-save-dialog__experiment').text()).toContain('EXP-042')
|
|
619
|
+
expect(wrapper.get('.mint-artifact-save-dialog__experiment-name').text()).toBe('Dose response')
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
it.each([
|
|
623
|
+
[false, false],
|
|
624
|
+
[true, true],
|
|
625
|
+
])('should show the Change link only when a change-experiment listener is bound (%s)', (bound, shown) => {
|
|
626
|
+
const listener = vi.fn()
|
|
627
|
+
const { wrapper } = mountDialog({
|
|
628
|
+
experiment: { id: 42, name: 'Dose response' },
|
|
629
|
+
...(bound ? { onChangeExperiment: listener } : {}),
|
|
630
|
+
})
|
|
631
|
+
|
|
632
|
+
expect(wrapper.find('.mint-artifact-save-dialog__change').exists()).toBe(shown)
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
it('should emit change-experiment when Change is clicked', async () => {
|
|
636
|
+
const { wrapper } = mountDialog({
|
|
637
|
+
experiment: { id: 42, name: 'Dose response' },
|
|
638
|
+
onChangeExperiment: vi.fn(),
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
await wrapper.get('.mint-artifact-save-dialog__change').trigger('click')
|
|
642
|
+
|
|
643
|
+
expect(wrapper.emitted('change-experiment')).toHaveLength(1)
|
|
644
|
+
})
|
|
645
|
+
})
|
|
568
646
|
})
|
|
@@ -8,56 +8,111 @@ const options = [
|
|
|
8
8
|
{ value: 'c', label: 'C' },
|
|
9
9
|
]
|
|
10
10
|
|
|
11
|
+
function mountMultiple(props: Record<string, unknown> = {}) {
|
|
12
|
+
return mount(BaseSelect, { props: { multiple: true, modelValue: ['a'], options, ...props } })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function openPanel(wrapper: ReturnType<typeof mountMultiple>) {
|
|
16
|
+
await wrapper.find('.mint-multi-select__field').trigger('click')
|
|
17
|
+
return wrapper
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
describe('BaseSelect multiple', () => {
|
|
12
|
-
it('should render the chip
|
|
21
|
+
it('should render the chip field when multiple is a bare template attribute', () => {
|
|
13
22
|
// A compiled `<BaseSelect multiple>` passes '' because `multiple` has no runtime Boolean type.
|
|
14
23
|
const wrapper = mount(BaseSelect, { props: { multiple: '' as unknown as true, modelValue: [], options } })
|
|
15
24
|
|
|
16
|
-
expect(wrapper.find('.mint-multi-
|
|
25
|
+
expect(wrapper.find('.mint-multi-select__field').exists()).toBe(true)
|
|
17
26
|
})
|
|
18
27
|
|
|
19
28
|
it('accepts string and number shorthand options', async () => {
|
|
20
|
-
const wrapper =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
options: ['glucose', 2, { value: 'dox', label: 'Doxorubicin' }],
|
|
25
|
-
},
|
|
26
|
-
})
|
|
29
|
+
const wrapper = await openPanel(mountMultiple({
|
|
30
|
+
modelValue: ['glucose', 2],
|
|
31
|
+
options: ['glucose', 2, { value: 'dox', label: 'Doxorubicin' }],
|
|
32
|
+
}))
|
|
27
33
|
|
|
28
|
-
expect(wrapper.findAll('
|
|
29
|
-
|
|
30
|
-
'2',
|
|
31
|
-
'Doxorubicin',
|
|
32
|
-
])
|
|
33
|
-
expect(wrapper.find('.mint-multi-select__summary').text()).toBe('glucose, 2')
|
|
34
|
+
expect(wrapper.findAll('.mint-multi-select__chip').map(chip => chip.text())).toEqual(['glucose', '2'])
|
|
35
|
+
expect(wrapper.findAll('[role="option"]').map(row => row.text())).toEqual(['glucose', '2', 'Doxorubicin'])
|
|
34
36
|
|
|
35
|
-
await wrapper.findAll('
|
|
37
|
+
await wrapper.findAll('[role="option"]')[2].trigger('click')
|
|
36
38
|
|
|
37
|
-
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([
|
|
38
|
-
['glucose', 2, 'dox'],
|
|
39
|
-
])
|
|
39
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([['glucose', 2, 'dox']])
|
|
40
40
|
})
|
|
41
41
|
|
|
42
42
|
it('uses the shared selection limit for max', async () => {
|
|
43
|
-
const wrapper =
|
|
44
|
-
props: {
|
|
45
|
-
multiple: true,
|
|
46
|
-
modelValue: ['a'],
|
|
47
|
-
options,
|
|
48
|
-
max: 1,
|
|
49
|
-
},
|
|
50
|
-
})
|
|
43
|
+
const wrapper = await openPanel(mountMultiple({ max: 1 }))
|
|
51
44
|
|
|
52
|
-
await wrapper.findAll('
|
|
45
|
+
await wrapper.findAll('[role="option"]')[1].trigger('click')
|
|
53
46
|
|
|
54
47
|
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
|
|
55
|
-
expect(wrapper.findAll('
|
|
48
|
+
expect(wrapper.findAll('[role="option"]')[1].attributes('aria-disabled')).toBe('true')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should reserve the check box on every row when only one is selected', async () => {
|
|
52
|
+
const wrapper = await openPanel(mountMultiple())
|
|
53
|
+
|
|
54
|
+
expect(wrapper.findAll('.mint-multi-select__check')).toHaveLength(3)
|
|
55
|
+
expect(wrapper.findAll('.mint-multi-select__check--on')).toHaveLength(1)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it.each(['Enter', ' ', 'ArrowDown'])('should open the listbox when %j is pressed on the field', async (key) => {
|
|
59
|
+
const wrapper = mountMultiple()
|
|
60
|
+
|
|
61
|
+
await wrapper.find('.mint-multi-select__field').trigger('keydown', { key })
|
|
62
|
+
|
|
63
|
+
expect(wrapper.find('[role="listbox"]').exists()).toBe(true)
|
|
64
|
+
expect(wrapper.find('.mint-multi-select__field').attributes('aria-expanded')).toBe('true')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should close the listbox when the field is clicked while it is open', async () => {
|
|
68
|
+
const wrapper = await openPanel(mountMultiple())
|
|
69
|
+
|
|
70
|
+
await wrapper.find('.mint-multi-select__field').trigger('click')
|
|
71
|
+
|
|
72
|
+
expect(wrapper.find('[role="listbox"]').exists()).toBe(false)
|
|
73
|
+
expect(wrapper.find('.mint-multi-select__field').attributes('aria-expanded')).toBe('false')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should close the listbox when Escape is pressed in the filter', async () => {
|
|
77
|
+
const wrapper = await openPanel(mountMultiple())
|
|
78
|
+
|
|
79
|
+
await wrapper.find('input[type="search"]').trigger('keydown', { key: 'Escape' })
|
|
80
|
+
|
|
81
|
+
expect(wrapper.find('[role="listbox"]').exists()).toBe(false)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should remove the last chip when Backspace is pressed in an empty filter', async () => {
|
|
85
|
+
const wrapper = await openPanel(mountMultiple({ modelValue: ['a', 'b'] }))
|
|
86
|
+
|
|
87
|
+
await wrapper.find('input[type="search"]').trigger('keydown', { key: 'Backspace' })
|
|
88
|
+
|
|
89
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([['a']])
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should remove a chip without opening the listbox when its remove button is clicked', async () => {
|
|
93
|
+
const wrapper = mountMultiple({ modelValue: ['a', 'b'] })
|
|
94
|
+
|
|
95
|
+
await wrapper.find('button[aria-label="Remove A"]').trigger('click')
|
|
96
|
+
|
|
97
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([['b']])
|
|
98
|
+
expect(wrapper.find('[role="listbox"]').exists()).toBe(false)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should narrow the rows and toggle the active row with Enter when the filter has text', async () => {
|
|
102
|
+
const wrapper = await openPanel(mountMultiple())
|
|
103
|
+
const filter = wrapper.find('input[type="search"]')
|
|
104
|
+
|
|
105
|
+
await filter.setValue('c')
|
|
106
|
+
await filter.trigger('keydown', { key: 'Enter' })
|
|
107
|
+
|
|
108
|
+
expect(wrapper.findAll('[role="option"]').map(row => row.text())).toEqual(['C'])
|
|
109
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([['a', 'c']])
|
|
56
110
|
})
|
|
57
111
|
|
|
58
|
-
it('should
|
|
59
|
-
const wrapper =
|
|
112
|
+
it('should neither open nor offer chip removal when disabled', async () => {
|
|
113
|
+
const wrapper = await openPanel(mountMultiple({ disabled: true }))
|
|
60
114
|
|
|
61
|
-
expect(wrapper.
|
|
115
|
+
expect(wrapper.find('[role="listbox"]').exists()).toBe(false)
|
|
116
|
+
expect(wrapper.find('.mint-multi-select__chip-remove').exists()).toBe(false)
|
|
62
117
|
})
|
|
63
118
|
})
|
|
@@ -58,7 +58,8 @@ describe('BaseSelect', () => {
|
|
|
58
58
|
it.each([
|
|
59
59
|
['aria-labelledby', 'native', {}, 'select'],
|
|
60
60
|
['aria-describedby', 'searchable', { searchable: true }, '.mint-searchable-select__trigger'],
|
|
61
|
-
['aria-describedby', 'multiple', { multiple: true, modelValue: [] as string[] },
|
|
61
|
+
['aria-describedby', 'multiple', { multiple: true, modelValue: [] as string[] }, '.mint-multi-select__field'],
|
|
62
|
+
['aria-labelledby', 'multiple', { multiple: true, modelValue: [] as string[] }, '.mint-multi-select__field'],
|
|
62
63
|
] as const)('should forward %s when the select is %s', (attr, _, extra, selector) => {
|
|
63
64
|
const wrapper = mount(BaseSelect, { props: { options: ['linear'], ...extra }, attrs: { [attr]: 'hint' } })
|
|
64
65
|
const target = selector ? wrapper.get(selector) : wrapper
|
|
@@ -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: ['
|
|
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: ['
|
|
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(
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
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')[
|
|
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()).
|
|
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
|
-
|
|
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('
|
|
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()).
|
|
62
|
-
expect(trigger.text()).
|
|
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(
|
|
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('
|
|
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
|
|
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('
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
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
|
})
|