@morscherlab/mint-sdk 1.2.0-beta.8 → 1.2.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.
- package/README.md +60 -0
- package/dist/{ExperimentPopover-CfOdh5Rm.js → ExperimentPopover-CJdNlCZb.js} +24 -15
- package/dist/ExperimentPopover-CJdNlCZb.js.map +1 -0
- package/dist/{ExperimentSelectorModal-DvIagrxY.js → ExperimentSelectorModal-DCXmwKNS.js} +5 -5
- package/dist/ExperimentSelectorModal-DCXmwKNS.js.map +1 -0
- package/dist/{SettingsModal-W3TYfnP9.js → SettingsModal-BYR20I4c.js} +5 -5
- package/dist/SettingsModal-BYR20I4c.js.map +1 -0
- package/dist/{EmptyState-NBqf2_8z.js → Skeleton-BAF3CKY7.js} +111 -93
- package/dist/Skeleton-BAF3CKY7.js.map +1 -0
- package/dist/__tests__/components/DataStates.test.d.ts +1 -0
- package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
- package/dist/__tests__/components/FileBrowserModal.test.d.ts +1 -0
- package/dist/__tests__/components/FilePicker.state.test.d.ts +1 -0
- package/dist/__tests__/components/FilePicker.test.d.ts +1 -0
- package/dist/__tests__/components/PlotlyChart.test.d.ts +1 -0
- package/dist/__tests__/components/iconButtonIconSizing.test.d.ts +1 -0
- package/dist/__tests__/composables/useFileBrowser.test.d.ts +1 -0
- package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
- package/dist/__tests__/composables/usePlatformFilePickerAdapter.test.d.ts +1 -0
- package/dist/__tests__/styles/cssTokens.test.d.ts +1 -0
- package/dist/apiErrors.d.ts +26 -0
- package/dist/components/ActionMenu.vue.d.ts +1 -1
- package/dist/components/AppPluginSwitcher.vue.d.ts +2 -0
- package/dist/components/AuditTrail.vue.d.ts +13 -0
- package/dist/components/BaseInput.vue.d.ts +1 -1
- package/dist/components/BaseSelect.vue.d.ts +1 -1
- package/dist/components/BaseTextarea.vue.d.ts +1 -1
- package/dist/components/BatchProgressList.vue.d.ts +17 -0
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +2 -2
- package/dist/components/ConcentrationInput.vue.d.ts +1 -1
- package/dist/components/DataFrame.vue.d.ts +14 -2
- package/dist/components/DatePicker.vue.d.ts +2 -2
- package/dist/components/DateTimePicker.vue.d.ts +3 -1
- package/dist/components/ExperimentDataViewer.vue.d.ts +26 -1
- package/dist/components/ExperimentPopover.vue.d.ts +1 -0
- package/dist/components/ExperimentTimeline.vue.d.ts +29 -1
- package/dist/components/FileBrowserModal.vue.d.ts +99 -0
- package/dist/components/FilePicker.vue.d.ts +32 -0
- package/dist/components/FileUploader.vue.d.ts +5 -2
- package/dist/components/FormulaInput.vue.d.ts +1 -1
- package/dist/components/GeneratedPlotlyResult.vue.d.ts +1 -3
- package/dist/components/LcmsSequenceTable.vue.d.ts +29 -1
- package/dist/components/MobileSupportGate.vue.d.ts +1 -1
- package/dist/components/MoleculeInput.vue.d.ts +1 -1
- package/dist/components/NumberInput.vue.d.ts +1 -1
- package/dist/components/PlotlyChart.vue.d.ts +59 -0
- package/dist/components/ProgressBar.vue.d.ts +1 -1
- package/dist/components/ProtocolStepEditor.vue.d.ts +1 -0
- package/dist/components/ReagentList.vue.d.ts +29 -1
- package/dist/components/ResourceCard.vue.d.ts +1 -1
- package/dist/components/SampleHierarchyTree.vue.d.ts +29 -1
- package/dist/components/SampleSelector.drag.d.ts +13 -0
- package/dist/components/SampleSelector.tree.d.ts +2 -0
- package/dist/components/SampleSelector.vue.d.ts +34 -1
- package/dist/components/ScheduleCalendar.vue.d.ts +20 -0
- package/dist/components/SecretInput.vue.d.ts +1 -1
- package/dist/components/SequenceInput.vue.d.ts +1 -1
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -0
- package/dist/components/StatusIndicator.vue.d.ts +1 -1
- package/dist/components/TagsInput.vue.d.ts +1 -1
- package/dist/components/TimePicker.vue.d.ts +2 -1
- package/dist/components/TimeRangeInput.vue.d.ts +1 -1
- package/dist/components/UnitInput.vue.d.ts +1 -1
- package/dist/components/WellPlate.geometry.d.ts +15 -0
- package/dist/components/WellPlate.interaction.d.ts +13 -0
- package/dist/components/WellPlate.vue.d.ts +30 -1
- package/dist/components/fileBrowserPresentation.d.ts +10 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +8 -8
- package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -0
- package/dist/components/internal/DataStateInternal.vue.d.ts +43 -0
- package/dist/components/internal/FileBrowserDialogInternal.vue.d.ts +34 -0
- package/dist/components/internal/FileBrowserListingInternal.vue.d.ts +25 -0
- package/dist/components/internal/FileBrowserToolbarInternal.vue.d.ts +26 -0
- package/dist/components/internal/FilePickerPreviewInternal.vue.d.ts +18 -0
- package/dist/components/internal/FilePickerTreeInternal.vue.d.ts +31 -0
- package/dist/{components-B80gV0P6.js → components-DIum5hkx.js} +5415 -2753
- package/dist/components-DIum5hkx.js.map +1 -0
- package/dist/composables/controlSchemaTypes.d.ts +34 -0
- package/dist/composables/filePicker/localFiles.d.ts +3 -0
- package/dist/composables/filePicker/usePickerNavigation.d.ts +96 -0
- package/dist/composables/filePicker/usePickerSelection.d.ts +31 -0
- package/dist/composables/filePicker/validation.d.ts +5 -0
- package/dist/composables/index.d.ts +3 -0
- package/dist/composables/index.js +10 -10
- package/dist/composables/useApi.d.ts +14 -7
- package/dist/composables/useDropdownState.d.ts +18 -2
- package/dist/composables/useEventStream.d.ts +9 -0
- package/dist/composables/useFileBrowser.d.ts +55 -0
- package/dist/composables/useFilePicker.d.ts +189 -0
- package/dist/composables/useListReorder.d.ts +90 -0
- package/dist/composables/useMenuKeyboard.d.ts +2 -2
- package/dist/composables/usePlatformFilePickerAdapter.d.ts +11 -0
- package/dist/composables/usePluginConfig.d.ts +3 -1
- package/dist/composables/useRequestSyncState.d.ts +42 -2
- package/dist/composables/useScheduleDrag.d.ts +1 -0
- package/dist/{composables-C8mmD5ta.js → composables-mFXsTA4g.js} +307 -74
- package/dist/composables-mFXsTA4g.js.map +1 -0
- package/dist/{useApi-CDBq6z23.js → errors-BLuqMee5.js} +178 -16
- package/dist/errors-BLuqMee5.js.map +1 -0
- package/dist/{experiment-Y9Iz7KYi.js → experiment-CVDRAnqp.js} +3 -8
- package/dist/experiment-CVDRAnqp.js.map +1 -0
- package/dist/{experiment-utils-DV3DrvE_.js → experiment-utils-CV2Wza3z.js} +1 -1
- package/dist/experiment-utils-CV2Wza3z.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -15
- package/dist/install.js +2 -7
- package/dist/install.js.map +1 -1
- package/dist/permissions.d.ts +42 -1
- package/dist/permissions.js +20 -10
- package/dist/permissions.js.map +1 -1
- package/dist/stores/index.d.ts +1 -1
- package/dist/stores/index.js +3 -3
- package/dist/stores/settings.d.ts +31 -1
- package/dist/styles.css +15162 -32475
- package/dist/templates/index.js +2 -4
- package/dist/{templates-CK72aKO1.js → templates-CmGfmBRA.js} +291 -85
- package/dist/templates-CmGfmBRA.js.map +1 -0
- package/dist/types/fileBrowserTypes.d.ts +77 -0
- package/dist/types/filePicker.d.ts +77 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/{useControlSchema-_OVhavKq.js → useControlSchema-DldpxNOP.js} +234 -1121
- package/dist/useControlSchema-DldpxNOP.js.map +1 -0
- package/dist/{useDropdownState-D5G4UvWd.js → useDropdownState-C7hRiVQf.js} +22 -6
- package/dist/useDropdownState-C7hRiVQf.js.map +1 -0
- package/dist/{useExperimentSelector-DSEckf_g.js → useExperimentSelector-CEcIaueJ.js} +4 -4
- package/dist/{useExperimentSelector-DSEckf_g.js.map → useExperimentSelector-CEcIaueJ.js.map} +1 -1
- package/dist/{useFormBuilder-Bd714C7y.js → useFormBuilder-z5L_dUIy.js} +2 -2
- package/dist/{useFormBuilder-Bd714C7y.js.map → useFormBuilder-z5L_dUIy.js.map} +1 -1
- package/dist/utils/color.d.ts +13 -0
- package/dist/utils/errors.d.ts +9 -0
- package/dist/{useProtocolTemplates-Dq5Pzwyd.js → validation-uwoJGEb6.js} +231 -158
- package/dist/validation-uwoJGEb6.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/apiErrors.test.ts +81 -0
- package/src/__tests__/components/ActionMenu.test.ts +94 -0
- package/src/__tests__/components/DataFrame.test.ts +183 -11
- package/src/__tests__/components/DataStates.test.ts +176 -0
- package/src/__tests__/components/DatePicker.test.ts +76 -0
- package/src/__tests__/components/DateTimePicker.test.ts +36 -0
- package/src/__tests__/components/ExperimentPopover.test.ts +30 -0
- package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
- package/src/__tests__/components/FileBrowserModal.test.ts +460 -0
- package/src/__tests__/components/FilePicker.state.test.ts +287 -0
- package/src/__tests__/components/FilePicker.test.ts +487 -0
- package/src/__tests__/components/GeneratedPlotlyResult.test.ts +52 -5
- package/src/__tests__/components/GroupAssigner.test.ts +53 -0
- package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
- package/src/__tests__/components/PlotlyChart.test.ts +305 -0
- package/src/__tests__/components/RackEditor.test.ts +35 -0
- package/src/__tests__/components/ReagentList.test.ts +64 -2
- package/src/__tests__/components/RetiredStyles.test.ts +31 -0
- package/src/__tests__/components/SampleSelector.drag.test.ts +51 -1
- package/src/__tests__/components/ScheduleCalendar.test.ts +63 -0
- package/src/__tests__/components/SegmentedControl.test.ts +107 -0
- package/src/__tests__/components/TimePicker.test.ts +61 -0
- package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
- package/src/__tests__/components/iconButtonIconSizing.test.ts +18 -0
- package/src/__tests__/composables/useApi.test.ts +92 -9
- package/src/__tests__/composables/useDropdownState.test.ts +58 -1
- package/src/__tests__/composables/useFileBrowser.test.ts +305 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +7 -2
- package/src/__tests__/composables/useListReorder.test.ts +145 -0
- package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +67 -0
- package/src/__tests__/composables/usePluginClient.test.ts +3 -0
- package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
- package/src/__tests__/stores/settings.test.ts +71 -1
- package/src/__tests__/styles/cssTokens.test.ts +131 -0
- package/src/__tests__/utils/permissions.test.ts +68 -0
- package/src/apiErrors.ts +85 -0
- package/src/components/ActionMenu.vue +45 -88
- package/src/components/AlertBox.vue +0 -4
- package/src/components/AppAvatarMenu.vue +2 -14
- package/src/components/AppLayout.vue +0 -4
- package/src/components/AppPluginSwitcher.story.vue +87 -0
- package/src/components/AppPluginSwitcher.vue +2 -5
- package/src/components/AppSidebar.vue +0 -4
- package/src/components/AppToastContainer.vue +6 -8
- package/src/components/AppTopBar.vue +0 -4
- package/src/components/ArtifactSaveDialog.vue +0 -4
- package/src/components/ArtifactSelectorModal.vue +0 -4
- package/src/components/AuditTrail.story.vue +18 -0
- package/src/components/AuditTrail.vue +27 -7
- package/src/components/AutoGroupModal.vue +0 -4
- package/src/components/Avatar.vue +0 -4
- package/src/components/BaseButton.vue +0 -4
- package/src/components/BaseCheckbox.vue +0 -4
- package/src/components/BaseInput.vue +0 -4
- package/src/components/BaseModal.vue +0 -4
- package/src/components/BasePill.vue +0 -4
- package/src/components/BaseRadioGroup.vue +0 -4
- package/src/components/BaseSelect.vue +0 -4
- package/src/components/BaseSlider.vue +0 -4
- package/src/components/BaseTabs.vue +0 -4
- package/src/components/BaseTextarea.vue +0 -4
- package/src/components/BaseToggle.vue +0 -4
- package/src/components/BatchProgressList.story.vue +18 -0
- package/src/components/BatchProgressList.vue +32 -4
- package/src/components/Breadcrumb.vue +0 -4
- package/src/components/Calendar.vue +0 -4
- package/src/components/ChartContainer.story.vue +1 -1
- package/src/components/ChartContainer.vue +0 -4
- package/src/components/ChemicalFormula.vue +0 -4
- package/src/components/CollapsibleCard.vue +0 -4
- package/src/components/ColorSlider.vue +0 -4
- package/src/components/ConcentrationInput.vue +0 -4
- package/src/components/ConfirmDialog.vue +0 -4
- package/src/components/DataFrame.story.vue +14 -0
- package/src/components/DataFrame.vue +97 -23
- package/src/components/DatePicker.vue +73 -27
- package/src/components/DateTimePicker.vue +45 -17
- package/src/components/Divider.vue +0 -4
- package/src/components/DoseCalculator.vue +0 -4
- package/src/components/DropdownButton.vue +2 -14
- package/src/components/EmptyState.vue +0 -4
- package/src/components/ExperimentCodeBadge.vue +0 -4
- package/src/components/ExperimentDataViewer.story.vue +3 -0
- package/src/components/ExperimentDataViewer.vue +33 -13
- package/src/components/ExperimentPopover.vue +12 -7
- package/src/components/ExperimentSelectorModal.vue +0 -4
- package/src/components/ExperimentTimeline.story.vue +18 -0
- package/src/components/ExperimentTimeline.vue +81 -75
- package/src/components/FileBrowserModal.story.vue +203 -0
- package/src/components/FileBrowserModal.vue +384 -0
- package/src/components/FilePicker.story.vue +44 -0
- package/src/components/FilePicker.vue +284 -0
- package/src/components/FileUploader.vue +32 -18
- package/src/components/FitPanel.vue +0 -4
- package/src/components/FormActions.story.vue +96 -0
- package/src/components/FormActions.vue +0 -4
- package/src/components/FormBuilder.vue +0 -4
- package/src/components/FormField.vue +0 -4
- package/src/components/FormulaInput.vue +0 -4
- package/src/components/GeneratedPathInput.story.vue +68 -0
- package/src/components/GeneratedPlotlyResult.story.vue +96 -0
- package/src/components/GeneratedPlotlyResult.vue +34 -69
- package/src/components/GeneratedPluginView.story.vue +145 -0
- package/src/components/GeneratedPluginView.vue +10 -10
- package/src/components/GeneratedResultRenderer.story.vue +117 -0
- package/src/components/GeneratedResultRenderer.vue +4 -9
- package/src/components/GroupAssigner.vue +67 -5
- package/src/components/IconButton.vue +0 -4
- package/src/components/InstrumentAlertLog.vue +0 -4
- package/src/components/InstrumentStateBadge.vue +0 -4
- package/src/components/InstrumentStatusCard.vue +0 -4
- package/src/components/JobsStatusTray.vue +0 -4
- package/src/components/LcmsSequenceTable.vue +81 -50
- package/src/components/LoadingSpinner.vue +0 -4
- package/src/components/MobileSupportGate.vue +0 -4
- package/src/components/MoleculeInput.vue +0 -4
- package/src/components/MultiSelect.vue +0 -4
- package/src/components/NumberInput.vue +0 -4
- package/src/components/PlateMapEditor.vue +3 -14
- package/src/components/PlotlyChart.story.vue +61 -0
- package/src/components/PlotlyChart.vue +233 -0
- package/src/components/PluginIcon.vue +0 -4
- package/src/components/ProgressBar.vue +0 -4
- package/src/components/ProtocolStepEditor.vue +2 -4
- package/src/components/RackEditor.vue +51 -70
- package/src/components/ReagentEditor.vue +0 -4
- package/src/components/ReagentList.story.vue +18 -0
- package/src/components/ReagentList.vue +127 -82
- package/src/components/ResourceCard.vue +0 -4
- package/src/components/RuntimeUpdateNotice.story.vue +49 -0
- package/src/components/RuntimeUpdateNotice.vue +4 -4
- package/src/components/SampleHierarchyTree.story.vue +18 -0
- package/src/components/SampleHierarchyTree.vue +32 -9
- package/src/components/SampleLegend.vue +2 -8
- package/src/components/SampleSelector.drag.ts +51 -0
- package/src/components/SampleSelector.story.vue +6 -0
- package/src/components/SampleSelector.tree.ts +2 -0
- package/src/components/SampleSelector.vue +49 -10
- package/src/components/SampleSelectorGroupNode.vue +19 -0
- package/src/components/ScheduleCalendar.story.vue +6 -0
- package/src/components/ScheduleCalendar.vue +82 -7
- package/src/components/ScientificNumber.vue +0 -4
- package/src/components/SectionCard.vue +0 -4
- package/src/components/SegmentedControl.vue +50 -7
- package/src/components/SequenceInput.vue +0 -4
- package/src/components/SequenceProgressBar.story.vue +110 -0
- package/src/components/SequenceProgressBar.vue +0 -4
- package/src/components/SettingsModal.vue +0 -4
- package/src/components/Skeleton.vue +0 -4
- package/src/components/SmartGroupFieldRecipe.vue +8 -6
- package/src/components/SmartGroupManual.vue +0 -4
- package/src/components/StatusIndicator.vue +0 -4
- package/src/components/StepWizard.vue +0 -4
- package/src/components/TagsInput.vue +4 -6
- package/src/components/ThemeToggle.vue +0 -4
- package/src/components/TimePicker.vue +36 -7
- package/src/components/TimeRangeInput.vue +0 -4
- package/src/components/Tooltip.story.vue +3 -3
- package/src/components/Tooltip.vue +0 -4
- package/src/components/UnitInput.vue +0 -4
- package/src/components/WellPlate.geometry.ts +60 -1
- package/src/components/WellPlate.interaction.ts +44 -0
- package/src/components/WellPlate.story.vue +8 -0
- package/src/components/WellPlate.vue +53 -7
- package/src/components/fileBrowserPresentation.ts +39 -0
- package/src/components/index.ts +5 -0
- package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -5
- package/src/components/internal/AppTopBarPillNavInternal.vue +0 -4
- package/src/components/internal/DataStateInternal.vue +74 -0
- package/src/components/internal/FileBrowserDialogInternal.vue +75 -0
- package/src/components/internal/FileBrowserListingInternal.vue +197 -0
- package/src/components/internal/FileBrowserToolbarInternal.vue +136 -0
- package/src/components/internal/FilePickerPreviewInternal.vue +100 -0
- package/src/components/internal/FilePickerTreeInternal.vue +238 -0
- package/src/components/internal/FormSectionRenderer.vue +0 -4
- package/src/components/internal/WellEditPopupInternal.vue +0 -4
- package/src/composables/controlSchemaTypes.ts +34 -0
- package/src/composables/filePicker/localFiles.ts +58 -0
- package/src/composables/filePicker/usePickerNavigation.ts +220 -0
- package/src/composables/filePicker/usePickerSelection.ts +259 -0
- package/src/composables/filePicker/validation.ts +41 -0
- package/src/composables/index.ts +17 -0
- package/src/composables/useApi.ts +57 -13
- package/src/composables/useDropdownState.ts +35 -5
- package/src/composables/useEventStream.ts +14 -2
- package/src/composables/useFileBrowser.ts +314 -0
- package/src/composables/useFileImport.ts +2 -7
- package/src/composables/useFilePicker.ts +366 -0
- package/src/composables/useGeneratedAnalysis.ts +18 -26
- package/src/composables/useListReorder.story.vue +143 -0
- package/src/composables/useListReorder.ts +242 -0
- package/src/composables/useMenuKeyboard.ts +2 -2
- package/src/composables/useOptimisticMutation.ts +1 -15
- package/src/composables/usePlatformFilePickerAdapter.ts +104 -0
- package/src/composables/usePluginClient.ts +3 -0
- package/src/composables/usePluginConfig.ts +3 -1
- package/src/composables/useRackEditor.ts +2 -11
- package/src/composables/useRequestSyncState.ts +98 -28
- package/src/composables/useResourceCrud.ts +1 -15
- package/src/composables/useScheduleDrag.ts +37 -0
- package/src/composables/useWellPainting.ts +5 -29
- package/src/composables/useWellPlateEditor.ts +1 -11
- package/src/index.ts +3 -0
- package/src/permissions.ts +48 -9
- package/src/stores/experiment.ts +2 -7
- package/src/stores/index.ts +9 -1
- package/src/stores/settings.ts +93 -1
- package/src/styles/components/audit-trail.css +5 -5
- package/src/styles/components/batch-progress-list.css +7 -0
- package/src/styles/components/color-slider.css +2 -2
- package/src/styles/components/data-state.css +30 -0
- package/src/styles/components/dataframe.css +29 -35
- package/src/styles/components/experiment-data-viewer.css +4 -4
- package/src/styles/components/experiment-timeline.css +3 -30
- package/src/styles/components/file-browser-modal.css +489 -0
- package/src/styles/components/file-picker.css +502 -0
- package/src/styles/components/form-builder.css +5 -5
- package/src/styles/components/icon-button.css +9 -0
- package/src/styles/components/lcms-sequence-table.css +16 -1
- package/src/styles/components/multi-select.css +1 -1
- package/src/styles/components/reagent-list.css +38 -31
- package/src/styles/components/sample-hierarchy-tree.css +5 -19
- package/src/styles/components/sample-selector.css +9 -2
- package/src/styles/components/schedule-calendar.css +8 -0
- package/src/styles/components/scientific-number.css +1 -1
- package/src/styles/components/well-plate.css +8 -0
- package/src/styles/index.css +11 -0
- package/src/styles/variables.css +29 -0
- package/src/templates/builderPresetControls.ts +1 -1
- package/src/templates/componentPlateHelpers.ts +3 -1
- package/src/templates/doseResponseAdapters.ts +2 -13
- package/src/templates/elisaAssayCollectionBuilder.ts +2 -2
- package/src/templates/plateMapBuilder.ts +2 -2
- package/src/templates/qpcrPlateBuilder.ts +1 -1
- package/src/templates/templateValidators.ts +1 -1
- package/src/templates/timeCourseBuilder.ts +2 -2
- package/src/templates/wellPlateScreenCollectionBuilder.ts +2 -2
- package/src/types/fileBrowserTypes.ts +85 -0
- package/src/types/filePicker.ts +72 -0
- package/src/types/index.ts +14 -0
- package/src/utils/color.ts +51 -0
- package/src/utils/errors.ts +29 -0
- package/dist/EmptyState-NBqf2_8z.js.map +0 -1
- package/dist/ExperimentPopover-CfOdh5Rm.js.map +0 -1
- package/dist/ExperimentSelectorModal-DvIagrxY.js.map +0 -1
- package/dist/SettingsModal-W3TYfnP9.js.map +0 -1
- package/dist/components-B80gV0P6.js.map +0 -1
- package/dist/composables/autoGroupConstants.d.ts +0 -1
- package/dist/composables/autoGroupGrouping.d.ts +0 -3
- package/dist/composables/useAutoGroupInputSources.d.ts +0 -32
- package/dist/composables-C8mmD5ta.js.map +0 -1
- package/dist/experiment-Y9Iz7KYi.js.map +0 -1
- package/dist/experiment-utils-DV3DrvE_.js.map +0 -1
- package/dist/templates/builderDefaults.d.ts +0 -1
- package/dist/templates/plateGeometry.d.ts +0 -4
- package/dist/templates-CK72aKO1.js.map +0 -1
- package/dist/useApi-CDBq6z23.js.map +0 -1
- package/dist/useControlSchema-_OVhavKq.js.map +0 -1
- package/dist/useDropdownState-D5G4UvWd.js.map +0 -1
- package/dist/useProtocolTemplates-Dq5Pzwyd.js.map +0 -1
- package/src/__tests__/composables/useAutoGroupInputSources.test.ts +0 -127
- package/src/composables/autoGroupConstants.ts +0 -4
- package/src/composables/autoGroupGrouping.ts +0 -148
- package/src/composables/useAutoGroupInputSources.ts +0 -147
- package/src/templates/builderDefaults.ts +0 -11
- package/src/templates/plateGeometry.ts +0 -62
- /package/dist/__tests__/{composables/useAutoGroupInputSources.test.d.ts → apiErrors.test.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morscherlab/mint-sdk",
|
|
3
|
-
"version": "1.2.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec INtegrated Toolkit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { MintApiError, mintApiErrorFromResponse } from '../apiErrors'
|
|
4
|
+
|
|
5
|
+
describe('MintApiError', () => {
|
|
6
|
+
it('should expose a typed server envelope', () => {
|
|
7
|
+
const error = mintApiErrorFromResponse(404, {
|
|
8
|
+
code: 'leaf.ARTIFACT_NOT_FOUND',
|
|
9
|
+
message: 'Artifact is not available',
|
|
10
|
+
status: 404,
|
|
11
|
+
request_id: 'req-typed',
|
|
12
|
+
details: { artifact_id: 67 },
|
|
13
|
+
detail: 'Artifact is not available',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
expect({
|
|
17
|
+
type: error.constructor,
|
|
18
|
+
code: error.code,
|
|
19
|
+
status: error.status,
|
|
20
|
+
requestId: error.requestId,
|
|
21
|
+
details: error.details,
|
|
22
|
+
}).toEqual({
|
|
23
|
+
type: MintApiError,
|
|
24
|
+
code: 'leaf.ARTIFACT_NOT_FOUND',
|
|
25
|
+
status: 404,
|
|
26
|
+
requestId: 'req-typed',
|
|
27
|
+
details: { artifact_id: 67 },
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should retain a legacy FastAPI validation detail list', () => {
|
|
32
|
+
const detail = [{ type: 'int_parsing', loc: ['body', 'count'] }]
|
|
33
|
+
|
|
34
|
+
const error = mintApiErrorFromResponse(422, { detail })
|
|
35
|
+
|
|
36
|
+
expect(error.detail).toBe(detail)
|
|
37
|
+
expect({ message: error.message, code: error.code, details: error.details }).toEqual({
|
|
38
|
+
message: 'Validation error',
|
|
39
|
+
code: 'unknown',
|
|
40
|
+
details: null,
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('preserves nested SDK errors and outer platform metadata', () => {
|
|
45
|
+
const detail = { error: 'NOT_FOUND', message: 'Missing artifact', details: { id: 67 }, request_id: 'inner' }
|
|
46
|
+
const body = { code: 'resource.not_found', message: 'Resource not found', request_id: 'outer', detail }
|
|
47
|
+
const error = mintApiErrorFromResponse(404, body)
|
|
48
|
+
|
|
49
|
+
expect(error).toMatchObject({
|
|
50
|
+
code: 'NOT_FOUND', message: 'Missing artifact', status: 404,
|
|
51
|
+
requestId: 'outer', details: { id: 67 }, detail, body,
|
|
52
|
+
})
|
|
53
|
+
expect(mintApiErrorFromResponse(404, { ...body, details: [] }).details).toEqual([])
|
|
54
|
+
expect(mintApiErrorFromResponse(404, detail).code).toBe('NOT_FOUND')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('prefers canonical envelopes over legacy nested SDK error metadata', () => {
|
|
58
|
+
const body = {
|
|
59
|
+
status: 426, code: 'plugin.api_version_mismatch', message: 'Plugin API version mismatch',
|
|
60
|
+
detail: { error: 'PLATFORM_API_VERSION_MISMATCH', message: 'Legacy version mismatch' },
|
|
61
|
+
}
|
|
62
|
+
expect(mintApiErrorFromResponse(426, body)).toMatchObject({
|
|
63
|
+
status: 426, code: body.code, message: body.message, detail: body.detail,
|
|
64
|
+
})
|
|
65
|
+
expect(mintApiErrorFromResponse(502, body).status).toBe(502)
|
|
66
|
+
expect(mintApiErrorFromResponse(426, { ...body, status: '426' }).code)
|
|
67
|
+
.toBe('PLATFORM_API_VERSION_MISMATCH')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('retains platform codes and falls back to legacy detail and header request IDs', () => {
|
|
71
|
+
expect(mintApiErrorFromResponse(403, {
|
|
72
|
+
code: 'auth.forbidden', message: '', detail: 'Restricted', request_id: '',
|
|
73
|
+
}, { requestId: 'header-id' })).toMatchObject({
|
|
74
|
+
code: 'auth.forbidden', message: 'Restricted', requestId: 'header-id',
|
|
75
|
+
})
|
|
76
|
+
expect(mintApiErrorFromResponse(502, '<html>Bad gateway</html>')).toMatchObject({
|
|
77
|
+
code: 'unknown', message: 'Bad gateway', body: '<html>Bad gateway</html>',
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
})
|
|
@@ -102,6 +102,100 @@ describe('ActionMenu', () => {
|
|
|
102
102
|
expect(document.body.querySelector('.mint-action-menu__panel')).toBeNull()
|
|
103
103
|
})
|
|
104
104
|
|
|
105
|
+
it('should open from the trigger keyboard shortcuts and focus the first item', async () => {
|
|
106
|
+
const wrapper = mount(ActionMenu, {
|
|
107
|
+
attachTo: document.body,
|
|
108
|
+
props: { label: 'More user actions', items },
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
await wrapper.get('button').trigger('keydown', { key: 'ArrowDown' })
|
|
112
|
+
await wrapper.vm.$nextTick()
|
|
113
|
+
|
|
114
|
+
const menuItems = document.body.querySelectorAll<HTMLElement>('.mint-action-menu__item')
|
|
115
|
+
expect(menuItems).toHaveLength(2)
|
|
116
|
+
expect(document.activeElement).toBe(menuItems[0])
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should open focused on the last item with ArrowUp', async () => {
|
|
120
|
+
const wrapper = mount(ActionMenu, {
|
|
121
|
+
attachTo: document.body,
|
|
122
|
+
props: { label: 'More user actions', items },
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
await wrapper.get('button').trigger('keydown', { key: 'ArrowUp' })
|
|
126
|
+
await wrapper.vm.$nextTick()
|
|
127
|
+
|
|
128
|
+
const menuItems = document.body.querySelectorAll<HTMLElement>('.mint-action-menu__item')
|
|
129
|
+
expect(document.activeElement).toBe(menuItems[1])
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('should cycle items with the arrow keys and jump with Home/End', async () => {
|
|
133
|
+
const wrapper = mount(ActionMenu, {
|
|
134
|
+
attachTo: document.body,
|
|
135
|
+
props: { label: 'More user actions', items },
|
|
136
|
+
})
|
|
137
|
+
await wrapper.get('button').trigger('click')
|
|
138
|
+
await wrapper.vm.$nextTick()
|
|
139
|
+
|
|
140
|
+
const panel = document.body.querySelector<HTMLElement>('.mint-action-menu__panel')
|
|
141
|
+
const menuItems = document.body.querySelectorAll<HTMLElement>('.mint-action-menu__item')
|
|
142
|
+
|
|
143
|
+
function press(key: string) {
|
|
144
|
+
panel?.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true }))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
press('ArrowDown')
|
|
148
|
+
expect(document.activeElement).toBe(menuItems[1])
|
|
149
|
+
press('ArrowDown')
|
|
150
|
+
expect(document.activeElement).toBe(menuItems[0])
|
|
151
|
+
press('End')
|
|
152
|
+
expect(document.activeElement).toBe(menuItems[1])
|
|
153
|
+
press('Home')
|
|
154
|
+
expect(document.activeElement).toBe(menuItems[0])
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should close on Tab without forcing focus back to the trigger', async () => {
|
|
158
|
+
const wrapper = mount(ActionMenu, {
|
|
159
|
+
attachTo: document.body,
|
|
160
|
+
props: { label: 'More user actions', items },
|
|
161
|
+
})
|
|
162
|
+
await wrapper.get('button').trigger('click')
|
|
163
|
+
|
|
164
|
+
document.body.querySelector<HTMLElement>('.mint-action-menu__panel')?.dispatchEvent(
|
|
165
|
+
new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }),
|
|
166
|
+
)
|
|
167
|
+
await wrapper.vm.$nextTick()
|
|
168
|
+
|
|
169
|
+
expect(document.body.querySelector('.mint-action-menu__panel')).toBeNull()
|
|
170
|
+
expect(document.activeElement).not.toBe(wrapper.get('button').element)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('should not open from the keyboard while disabled', async () => {
|
|
174
|
+
const wrapper = mount(ActionMenu, {
|
|
175
|
+
attachTo: document.body,
|
|
176
|
+
props: { label: 'More user actions', items, disabled: true },
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
await wrapper.get('button').trigger('keydown', { key: 'ArrowDown' })
|
|
180
|
+
await wrapper.vm.$nextTick()
|
|
181
|
+
|
|
182
|
+
expect(document.body.querySelector('.mint-action-menu__panel')).toBeNull()
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('should close when a click lands outside the trigger and panel', async () => {
|
|
186
|
+
const wrapper = mount(ActionMenu, {
|
|
187
|
+
attachTo: document.body,
|
|
188
|
+
props: { label: 'More user actions', items },
|
|
189
|
+
})
|
|
190
|
+
await wrapper.get('button').trigger('click')
|
|
191
|
+
expect(document.body.querySelector('.mint-action-menu__panel')).not.toBeNull()
|
|
192
|
+
|
|
193
|
+
document.body.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
194
|
+
await wrapper.vm.$nextTick()
|
|
195
|
+
|
|
196
|
+
expect(document.body.querySelector('.mint-action-menu__panel')).toBeNull()
|
|
197
|
+
})
|
|
198
|
+
|
|
105
199
|
it('should return focus to the trigger when Escape closes the menu', async () => {
|
|
106
200
|
const wrapper = mount(ActionMenu, {
|
|
107
201
|
attachTo: document.body,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
1
|
+
import { afterEach, describe, it, expect } from 'vitest'
|
|
2
2
|
import { mount } from '@vue/test-utils'
|
|
3
|
+
import { createPinia, setActivePinia } from 'pinia'
|
|
3
4
|
import DataFrame from '../../components/DataFrame.vue'
|
|
5
|
+
import { useSettingsStore } from '../../stores/settings'
|
|
4
6
|
import type { DataFrameColumn, SortState, PaginationState } from '../../types'
|
|
5
7
|
|
|
6
8
|
describe('DataFrame', () => {
|
|
@@ -167,7 +169,7 @@ describe('DataFrame', () => {
|
|
|
167
169
|
props: { data: mockData, columns: sortableColumns },
|
|
168
170
|
})
|
|
169
171
|
const nameHeader = wrapper.findAll('.mint-dataframe__th')[0]
|
|
170
|
-
await nameHeader.trigger('click')
|
|
172
|
+
await nameHeader.get('.mint-dataframe__th-button').trigger('click')
|
|
171
173
|
|
|
172
174
|
expect(wrapper.emitted('update:sort')).toHaveLength(1)
|
|
173
175
|
const sortState = wrapper.emitted('update:sort')?.[0]?.[0] as SortState
|
|
@@ -184,7 +186,7 @@ describe('DataFrame', () => {
|
|
|
184
186
|
},
|
|
185
187
|
})
|
|
186
188
|
const nameHeader = wrapper.findAll('.mint-dataframe__th')[0]
|
|
187
|
-
await nameHeader.trigger('click')
|
|
189
|
+
await nameHeader.get('.mint-dataframe__th-button').trigger('click')
|
|
188
190
|
|
|
189
191
|
const sortState = wrapper.emitted('update:sort')?.[0]?.[0] as SortState
|
|
190
192
|
expect(sortState.direction).toBe('desc')
|
|
@@ -199,7 +201,7 @@ describe('DataFrame', () => {
|
|
|
199
201
|
},
|
|
200
202
|
})
|
|
201
203
|
const nameHeader = wrapper.findAll('.mint-dataframe__th')[0]
|
|
202
|
-
await nameHeader.trigger('click')
|
|
204
|
+
await nameHeader.get('.mint-dataframe__th-button').trigger('click')
|
|
203
205
|
|
|
204
206
|
const sortState = wrapper.emitted('update:sort')?.[0]?.[0]
|
|
205
207
|
expect(sortState).toBeNull()
|
|
@@ -210,7 +212,7 @@ describe('DataFrame', () => {
|
|
|
210
212
|
props: { data: mockData, columns: sortableColumns },
|
|
211
213
|
})
|
|
212
214
|
const nameHeader = wrapper.findAll('.mint-dataframe__th')[0]
|
|
213
|
-
await nameHeader.trigger('click')
|
|
215
|
+
await nameHeader.get('.mint-dataframe__th-button').trigger('click')
|
|
214
216
|
|
|
215
217
|
// After sorting, first row should have name starting with 'A'
|
|
216
218
|
const firstRow = wrapper.findAll('.mint-dataframe__row')[0]
|
|
@@ -223,7 +225,7 @@ describe('DataFrame', () => {
|
|
|
223
225
|
props: { data: mockData, columns: sortableColumns },
|
|
224
226
|
})
|
|
225
227
|
const ageHeader = wrapper.findAll('.mint-dataframe__th')[1]
|
|
226
|
-
await ageHeader.trigger('click')
|
|
228
|
+
await ageHeader.get('.mint-dataframe__th-button').trigger('click')
|
|
227
229
|
|
|
228
230
|
// After sorting ascending, first row should have age 25
|
|
229
231
|
const firstRow = wrapper.findAll('.mint-dataframe__row')[0]
|
|
@@ -553,28 +555,38 @@ describe('DataFrame', () => {
|
|
|
553
555
|
const wrapper = mount(DataFrame, {
|
|
554
556
|
props: { data: [], columns: mockColumns },
|
|
555
557
|
})
|
|
556
|
-
expect(wrapper.find('.mint-
|
|
558
|
+
expect(wrapper.find('.mint-dataframe__state').exists()).toBe(true)
|
|
559
|
+
expect(wrapper.find('[data-state="empty"]').exists()).toBe(true)
|
|
557
560
|
})
|
|
558
561
|
|
|
559
562
|
it('should display empty text', () => {
|
|
560
563
|
const wrapper = mount(DataFrame, {
|
|
561
564
|
props: { data: [], columns: mockColumns },
|
|
562
565
|
})
|
|
563
|
-
expect(wrapper.find('.mint-
|
|
566
|
+
expect(wrapper.find('.mint-empty-state__title').text()).toBe('No data')
|
|
564
567
|
})
|
|
565
568
|
|
|
566
569
|
it('should use custom empty text', () => {
|
|
567
570
|
const wrapper = mount(DataFrame, {
|
|
568
571
|
props: { data: [], columns: mockColumns, emptyText: 'No records found' },
|
|
569
572
|
})
|
|
570
|
-
expect(wrapper.find('.mint-
|
|
573
|
+
expect(wrapper.find('.mint-empty-state__title').text()).toBe('No records found')
|
|
571
574
|
})
|
|
572
575
|
|
|
573
576
|
it('should not show empty state when loading', () => {
|
|
574
577
|
const wrapper = mount(DataFrame, {
|
|
575
578
|
props: { data: [], columns: mockColumns, loading: true },
|
|
576
579
|
})
|
|
577
|
-
expect(wrapper.find('
|
|
580
|
+
expect(wrapper.find('[data-state="empty"]').exists()).toBe(false)
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
it('should render the empty slot instead of the default placeholder', () => {
|
|
584
|
+
const wrapper = mount(DataFrame, {
|
|
585
|
+
props: { data: [], columns: mockColumns },
|
|
586
|
+
slots: { empty: '<p class="custom-empty">Nothing here</p>' },
|
|
587
|
+
})
|
|
588
|
+
expect(wrapper.find('.custom-empty').exists()).toBe(true)
|
|
589
|
+
expect(wrapper.find('[data-state="empty"]').exists()).toBe(false)
|
|
578
590
|
})
|
|
579
591
|
})
|
|
580
592
|
|
|
@@ -590,7 +602,40 @@ describe('DataFrame', () => {
|
|
|
590
602
|
const wrapper = mount(DataFrame, {
|
|
591
603
|
props: { data: mockData, columns: mockColumns, loading: true },
|
|
592
604
|
})
|
|
593
|
-
expect(wrapper.find('.mint-
|
|
605
|
+
expect(wrapper.find('.mint-spinner').exists()).toBe(true)
|
|
606
|
+
})
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
describe('error state', () => {
|
|
610
|
+
it('should render nothing extra when error is not passed', () => {
|
|
611
|
+
const wrapper = mount(DataFrame, {
|
|
612
|
+
props: { data: mockData, columns: mockColumns },
|
|
613
|
+
})
|
|
614
|
+
expect(wrapper.find('[data-state="error"]').exists()).toBe(false)
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
it('should replace the empty state with an error state', () => {
|
|
618
|
+
const wrapper = mount(DataFrame, {
|
|
619
|
+
props: { data: [], columns: mockColumns, error: 'Request failed' },
|
|
620
|
+
})
|
|
621
|
+
expect(wrapper.find('[data-state="error"]').exists()).toBe(true)
|
|
622
|
+
expect(wrapper.find('[data-state="empty"]').exists()).toBe(false)
|
|
623
|
+
expect(wrapper.find('.mint-empty-state__title').text()).toBe('Request failed')
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
it('should take precedence over the empty state even with rows present', () => {
|
|
627
|
+
const wrapper = mount(DataFrame, {
|
|
628
|
+
props: { data: mockData, columns: mockColumns, error: 'Boom' },
|
|
629
|
+
})
|
|
630
|
+
expect(wrapper.find('[data-state="error"]').exists()).toBe(true)
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
it('should render the error slot with the message', () => {
|
|
634
|
+
const wrapper = mount(DataFrame, {
|
|
635
|
+
props: { data: [], columns: mockColumns, error: 'Boom' },
|
|
636
|
+
slots: { error: '<p class="custom-error">custom error</p>' },
|
|
637
|
+
})
|
|
638
|
+
expect(wrapper.find('.custom-error').exists()).toBe(true)
|
|
594
639
|
})
|
|
595
640
|
})
|
|
596
641
|
|
|
@@ -911,5 +956,132 @@ describe('DataFrame', () => {
|
|
|
911
956
|
await wrapper.find('.mint-dataframe__resize-handle').trigger('click')
|
|
912
957
|
expect(wrapper.emitted('update:sort')).toBeUndefined()
|
|
913
958
|
})
|
|
959
|
+
|
|
960
|
+
it('should expose the resize handle as a slider with its current width', () => {
|
|
961
|
+
const wrapper = mount(DataFrame, {
|
|
962
|
+
props: {
|
|
963
|
+
data: mockData,
|
|
964
|
+
columns: [{ key: 'name', label: 'Name', minWidth: 80 }],
|
|
965
|
+
resizable: true,
|
|
966
|
+
columnWidths: { name: 240 },
|
|
967
|
+
},
|
|
968
|
+
})
|
|
969
|
+
|
|
970
|
+
const handle = wrapper.get('.mint-dataframe__resize-handle')
|
|
971
|
+
expect(handle.attributes('role')).toBe('slider')
|
|
972
|
+
expect(handle.attributes('tabindex')).toBe('0')
|
|
973
|
+
expect(handle.attributes('aria-label')).toBe('Resize Name column')
|
|
974
|
+
expect(handle.attributes('aria-valuenow')).toBe('240')
|
|
975
|
+
expect(handle.attributes('aria-valuemin')).toBe('80')
|
|
976
|
+
})
|
|
977
|
+
|
|
978
|
+
it('should resize a column with the arrow keys, clamped to the minimum width', async () => {
|
|
979
|
+
const wrapper = mount(DataFrame, {
|
|
980
|
+
props: {
|
|
981
|
+
data: mockData,
|
|
982
|
+
columns: [{ key: 'name', label: 'Name' }],
|
|
983
|
+
resizable: true,
|
|
984
|
+
columnWidths: { name: 120 },
|
|
985
|
+
},
|
|
986
|
+
})
|
|
987
|
+
|
|
988
|
+
const handle = wrapper.get('.mint-dataframe__resize-handle')
|
|
989
|
+
await handle.trigger('keydown', { key: 'ArrowRight' })
|
|
990
|
+
expect(wrapper.emitted('update:columnWidths')?.[0]).toEqual([{ name: 136 }])
|
|
991
|
+
|
|
992
|
+
await handle.trigger('keydown', { key: 'ArrowLeft' })
|
|
993
|
+
expect(wrapper.emitted('update:columnWidths')?.[1]).toEqual([{ name: 104 }])
|
|
994
|
+
|
|
995
|
+
await wrapper.setProps({ columnWidths: { name: 50 } })
|
|
996
|
+
await handle.trigger('keydown', { key: 'ArrowLeft' })
|
|
997
|
+
expect(wrapper.emitted('update:columnWidths')?.[2]).toEqual([{ name: 48 }])
|
|
998
|
+
})
|
|
999
|
+
})
|
|
1000
|
+
|
|
1001
|
+
describe('sortable header accessibility', () => {
|
|
1002
|
+
const sortableColumn = [{ key: 'name', label: 'Name', sortable: true }]
|
|
1003
|
+
|
|
1004
|
+
it('should render sortable headers as buttons and plain headers without one', () => {
|
|
1005
|
+
const wrapper = mount(DataFrame, {
|
|
1006
|
+
props: {
|
|
1007
|
+
data: mockData,
|
|
1008
|
+
columns: [...sortableColumn, { key: 'age', label: 'Age' }],
|
|
1009
|
+
},
|
|
1010
|
+
})
|
|
1011
|
+
|
|
1012
|
+
const headers = wrapper.findAll('.mint-dataframe__th')
|
|
1013
|
+
expect(headers[0].get('.mint-dataframe__th-button').element.tagName).toBe('BUTTON')
|
|
1014
|
+
expect(headers[1].get('.mint-dataframe__th-button').element.tagName).toBe('DIV')
|
|
1015
|
+
})
|
|
1016
|
+
|
|
1017
|
+
it('should reflect the sort direction with aria-sort', async () => {
|
|
1018
|
+
const wrapper = mount(DataFrame, {
|
|
1019
|
+
props: {
|
|
1020
|
+
data: mockData,
|
|
1021
|
+
columns: [...sortableColumn, { key: 'age', label: 'Age' }],
|
|
1022
|
+
},
|
|
1023
|
+
})
|
|
1024
|
+
|
|
1025
|
+
const nameHeader = () => wrapper.findAll('.mint-dataframe__th')[0]
|
|
1026
|
+
expect(nameHeader().attributes('aria-sort')).toBe('none')
|
|
1027
|
+
expect(wrapper.findAll('.mint-dataframe__th')[1].attributes('aria-sort')).toBeUndefined()
|
|
1028
|
+
|
|
1029
|
+
await nameHeader().get('.mint-dataframe__th-button').trigger('click')
|
|
1030
|
+
expect(nameHeader().attributes('aria-sort')).toBe('ascending')
|
|
1031
|
+
|
|
1032
|
+
await nameHeader().get('.mint-dataframe__th-button').trigger('click')
|
|
1033
|
+
expect(nameHeader().attributes('aria-sort')).toBe('descending')
|
|
1034
|
+
|
|
1035
|
+
await nameHeader().get('.mint-dataframe__th-button').trigger('click')
|
|
1036
|
+
expect(nameHeader().attributes('aria-sort')).toBe('none')
|
|
1037
|
+
})
|
|
1038
|
+
|
|
1039
|
+
it('should sort from the keyboard with Enter and Space', async () => {
|
|
1040
|
+
const wrapper = mount(DataFrame, {
|
|
1041
|
+
props: { data: mockData, columns: sortableColumn },
|
|
1042
|
+
})
|
|
1043
|
+
|
|
1044
|
+
const button = wrapper.get('.mint-dataframe__th-button')
|
|
1045
|
+
await button.trigger('keydown', { key: 'Enter' })
|
|
1046
|
+
expect(wrapper.emitted('update:sort')?.[0]?.[0]).toEqual({ key: 'name', direction: 'asc' })
|
|
1047
|
+
|
|
1048
|
+
await button.trigger('keydown', { key: ' ' })
|
|
1049
|
+
expect(wrapper.emitted('update:sort')?.[1]?.[0]).toEqual({ key: 'name', direction: 'desc' })
|
|
1050
|
+
})
|
|
1051
|
+
})
|
|
1052
|
+
|
|
1053
|
+
describe('table density default', () => {
|
|
1054
|
+
afterEach(() => {
|
|
1055
|
+
setActivePinia(undefined)
|
|
1056
|
+
})
|
|
1057
|
+
|
|
1058
|
+
it('falls back to md when no settings store is installed', () => {
|
|
1059
|
+
setActivePinia(undefined)
|
|
1060
|
+
const wrapper = mount(DataFrame, {
|
|
1061
|
+
props: { data: mockData, columns: mockColumns },
|
|
1062
|
+
})
|
|
1063
|
+
expect(wrapper.find('.mint-dataframe__td--md').exists()).toBe(true)
|
|
1064
|
+
})
|
|
1065
|
+
|
|
1066
|
+
it('follows the tableDensity setting when size is not passed', () => {
|
|
1067
|
+
setActivePinia(createPinia())
|
|
1068
|
+
useSettingsStore().tableDensity = 'compact'
|
|
1069
|
+
|
|
1070
|
+
const wrapper = mount(DataFrame, {
|
|
1071
|
+
props: { data: mockData, columns: mockColumns },
|
|
1072
|
+
})
|
|
1073
|
+
expect(wrapper.find('.mint-dataframe__td--sm').exists()).toBe(true)
|
|
1074
|
+
expect(wrapper.find('.mint-dataframe__td--md').exists()).toBe(false)
|
|
1075
|
+
})
|
|
1076
|
+
|
|
1077
|
+
it('lets an explicit size prop win over the density setting', () => {
|
|
1078
|
+
setActivePinia(createPinia())
|
|
1079
|
+
useSettingsStore().tableDensity = 'compact'
|
|
1080
|
+
|
|
1081
|
+
const wrapper = mount(DataFrame, {
|
|
1082
|
+
props: { data: mockData, columns: mockColumns, size: 'lg' },
|
|
1083
|
+
})
|
|
1084
|
+
expect(wrapper.find('.mint-dataframe__td--lg').exists()).toBe(true)
|
|
1085
|
+
})
|
|
914
1086
|
})
|
|
915
1087
|
})
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import AuditTrail from '../../components/AuditTrail.vue'
|
|
4
|
+
import BatchProgressList from '../../components/BatchProgressList.vue'
|
|
5
|
+
import ExperimentTimeline from '../../components/ExperimentTimeline.vue'
|
|
6
|
+
import LcmsSequenceTable from '../../components/LcmsSequenceTable.vue'
|
|
7
|
+
import ReagentList from '../../components/ReagentList.vue'
|
|
8
|
+
import SampleHierarchyTree from '../../components/SampleHierarchyTree.vue'
|
|
9
|
+
import ScheduleCalendar from '../../components/ScheduleCalendar.vue'
|
|
10
|
+
import WellPlate from '../../components/WellPlate.vue'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The `loading` / `error` props and the `#empty` / `#error` / `#loading` slots
|
|
14
|
+
* are one contract shared by the data components, so they are asserted in one
|
|
15
|
+
* place. Each entry pairs a component with the props that leave it empty and a
|
|
16
|
+
* selector that only its populated rendering produces.
|
|
17
|
+
*/
|
|
18
|
+
const cases = [
|
|
19
|
+
{
|
|
20
|
+
name: 'AuditTrail',
|
|
21
|
+
component: AuditTrail,
|
|
22
|
+
emptyProps: { entries: [] },
|
|
23
|
+
filledProps: {
|
|
24
|
+
entries: [{ id: 'a', type: 'created', action: 'Created', timestamp: '2026-01-01T00:00:00Z' }],
|
|
25
|
+
},
|
|
26
|
+
contentSelector: '.mint-audit-trail__list',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'BatchProgressList',
|
|
30
|
+
component: BatchProgressList,
|
|
31
|
+
emptyProps: { items: [] },
|
|
32
|
+
filledProps: { items: [{ id: '1', label: 'One', status: 'pending' }] },
|
|
33
|
+
contentSelector: '.mint-batch-progress__list',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'ExperimentTimeline',
|
|
37
|
+
component: ExperimentTimeline,
|
|
38
|
+
emptyProps: { modelValue: [] },
|
|
39
|
+
filledProps: { modelValue: [{ id: 's1', name: 'Step 1', order: 1, type: 'incubation' }] },
|
|
40
|
+
contentSelector: '.mint-timeline__step',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'LcmsSequenceTable',
|
|
44
|
+
component: LcmsSequenceTable,
|
|
45
|
+
emptyProps: { items: [] },
|
|
46
|
+
filledProps: {
|
|
47
|
+
items: [{
|
|
48
|
+
file_name: 'a.raw',
|
|
49
|
+
position: 'A1',
|
|
50
|
+
sample_type: 'Unknown',
|
|
51
|
+
instrument_method: 'm',
|
|
52
|
+
injection_volume: 1,
|
|
53
|
+
}],
|
|
54
|
+
},
|
|
55
|
+
contentSelector: '.mint-lcms-sequence-table__scroller',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'ReagentList',
|
|
59
|
+
component: ReagentList,
|
|
60
|
+
emptyProps: { modelValue: [] },
|
|
61
|
+
filledProps: { modelValue: [{ id: 'r1', name: 'Water' }] },
|
|
62
|
+
contentSelector: '.mint-reagent-list__table',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'SampleHierarchyTree',
|
|
66
|
+
component: SampleHierarchyTree,
|
|
67
|
+
emptyProps: { nodes: [] },
|
|
68
|
+
filledProps: { nodes: [{ id: 'n1', label: 'Study', type: 'study' }] },
|
|
69
|
+
contentSelector: '.mint-sample-tree__node',
|
|
70
|
+
},
|
|
71
|
+
] as const
|
|
72
|
+
|
|
73
|
+
describe('shared data component states', () => {
|
|
74
|
+
describe.each(cases)('$name', ({ component, emptyProps, filledProps, contentSelector }) => {
|
|
75
|
+
it('renders its content when neither loading nor error is passed', () => {
|
|
76
|
+
const wrapper = mount(component, { props: { ...filledProps } as never })
|
|
77
|
+
|
|
78
|
+
expect(wrapper.find(contentSelector).exists()).toBe(true)
|
|
79
|
+
expect(wrapper.find('[data-state]').exists()).toBe(false)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('renders an empty state instead of content when there is nothing to show', () => {
|
|
83
|
+
const wrapper = mount(component, { props: { ...emptyProps } as never })
|
|
84
|
+
|
|
85
|
+
expect(wrapper.find('[data-state="empty"]').exists()).toBe(true)
|
|
86
|
+
expect(wrapper.find(contentSelector).exists()).toBe(false)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('renders a loading state that replaces the content', () => {
|
|
90
|
+
const wrapper = mount(component, { props: { ...filledProps, loading: true } as never })
|
|
91
|
+
|
|
92
|
+
expect(wrapper.find('[data-state="loading"]').exists()).toBe(true)
|
|
93
|
+
expect(wrapper.find('.mint-spinner').exists()).toBe(true)
|
|
94
|
+
expect(wrapper.find(contentSelector).exists()).toBe(false)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('renders an error state that wins over content and loading', () => {
|
|
98
|
+
const wrapper = mount(component, {
|
|
99
|
+
props: { ...filledProps, loading: true, error: 'Request failed' } as never,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
expect(wrapper.find('[data-state="error"]').exists()).toBe(true)
|
|
103
|
+
expect(wrapper.find('[data-state="loading"]').exists()).toBe(false)
|
|
104
|
+
expect(wrapper.find('.mint-empty-state__title').text()).toBe('Request failed')
|
|
105
|
+
expect(wrapper.find(contentSelector).exists()).toBe(false)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('lets the empty slot replace the default placeholder', () => {
|
|
109
|
+
const wrapper = mount(component, {
|
|
110
|
+
props: { ...emptyProps } as never,
|
|
111
|
+
slots: { empty: '<p class="slot-empty">custom</p>' },
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
expect(wrapper.find('.slot-empty').exists()).toBe(true)
|
|
115
|
+
expect(wrapper.find('[data-state="empty"]').exists()).toBe(false)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('lets the error slot replace the default placeholder', () => {
|
|
119
|
+
const wrapper = mount(component, {
|
|
120
|
+
props: { ...filledProps, error: 'Boom' } as never,
|
|
121
|
+
slots: { error: '<p class="slot-error">custom</p>' },
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
expect(wrapper.find('.slot-error').exists()).toBe(true)
|
|
125
|
+
expect(wrapper.find('[data-state="error"]').exists()).toBe(false)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
describe('opt-in empty states', () => {
|
|
130
|
+
it('keeps the WellPlate grid when no emptyMessage is given', () => {
|
|
131
|
+
const wrapper = mount(WellPlate, { props: { wells: {} } })
|
|
132
|
+
|
|
133
|
+
expect(wrapper.find('.mint-well-plate__scroll').exists()).toBe(true)
|
|
134
|
+
expect(wrapper.find('[data-state]').exists()).toBe(false)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('replaces the WellPlate grid once an emptyMessage is given', () => {
|
|
138
|
+
const wrapper = mount(WellPlate, {
|
|
139
|
+
props: { wells: {}, emptyMessage: 'No plate loaded' },
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
expect(wrapper.find('[data-state="empty"]').exists()).toBe(true)
|
|
143
|
+
expect(wrapper.find('.mint-well-plate__scroll').exists()).toBe(false)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('replaces the WellPlate grid on error', () => {
|
|
147
|
+
const wrapper = mount(WellPlate, { props: { error: 'Boom' } })
|
|
148
|
+
|
|
149
|
+
expect(wrapper.find('[data-state="error"]').exists()).toBe(true)
|
|
150
|
+
expect(wrapper.find('.mint-well-plate__scroll').exists()).toBe(false)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('keeps the ScheduleCalendar grid when no emptyMessage is given', () => {
|
|
154
|
+
const wrapper = mount(ScheduleCalendar, { props: { events: [] } })
|
|
155
|
+
|
|
156
|
+
expect(wrapper.find('.mint-schedule__body').exists()).toBe(true)
|
|
157
|
+
expect(wrapper.find('[data-state]').exists()).toBe(false)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('replaces the ScheduleCalendar grid once an emptyMessage is given', () => {
|
|
161
|
+
const wrapper = mount(ScheduleCalendar, {
|
|
162
|
+
props: { events: [], emptyMessage: 'Nothing scheduled' },
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
expect(wrapper.find('[data-state="empty"]').exists()).toBe(true)
|
|
166
|
+
expect(wrapper.find('.mint-schedule__body').exists()).toBe(false)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('replaces the ScheduleCalendar grid while loading', () => {
|
|
170
|
+
const wrapper = mount(ScheduleCalendar, { props: { loading: true } })
|
|
171
|
+
|
|
172
|
+
expect(wrapper.find('[data-state="loading"]').exists()).toBe(true)
|
|
173
|
+
expect(wrapper.find('.mint-schedule__body').exists()).toBe(false)
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
})
|