@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
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
const mockGet = vi.fn()
|
|
4
|
+
vi.mock('../../composables/useApi', () => ({
|
|
5
|
+
useApi: () => ({ get: mockGet }),
|
|
6
|
+
}))
|
|
7
|
+
|
|
8
|
+
import { effectScope, nextTick, ref } from 'vue'
|
|
9
|
+
|
|
10
|
+
import { useFileBrowser } from '../../composables/useFileBrowser'
|
|
11
|
+
|
|
12
|
+
const MOUNTS = {
|
|
13
|
+
mounts: [
|
|
14
|
+
{ id: 'seq', label: '/mnt/sequencer-01', available: false },
|
|
15
|
+
{ id: 'qe', label: '/mnt/instrument-qe', available: true },
|
|
16
|
+
],
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const LISTING = {
|
|
20
|
+
mount_id: 'qe',
|
|
21
|
+
path: 'raw',
|
|
22
|
+
parent: '',
|
|
23
|
+
breadcrumbs: [{ label: '/mnt/instrument-qe', path: '' }, { label: 'raw', path: 'raw' }],
|
|
24
|
+
entries: [
|
|
25
|
+
{ name: 'blanks', path: 'raw/blanks', kind: 'directory', child_count: 6 },
|
|
26
|
+
{ name: 'a.raw', path: 'raw/a.raw', kind: 'file', size_bytes: 10, extension: '.raw' },
|
|
27
|
+
],
|
|
28
|
+
total_count: 2,
|
|
29
|
+
match_count: 1,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
mockGet.mockReset()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('useFileBrowser', () => {
|
|
37
|
+
it('should load the configured mounts', async () => {
|
|
38
|
+
mockGet.mockResolvedValueOnce(MOUNTS).mockResolvedValueOnce(LISTING)
|
|
39
|
+
const browser = useFileBrowser()
|
|
40
|
+
await browser.init()
|
|
41
|
+
|
|
42
|
+
expect(browser.mounts.value.map(m => m.id)).toEqual(['seq', 'qe'])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// An offline mount must not be the one we open on.
|
|
46
|
+
it('should open the first available mount', async () => {
|
|
47
|
+
mockGet.mockResolvedValueOnce(MOUNTS).mockResolvedValueOnce(LISTING)
|
|
48
|
+
const browser = useFileBrowser()
|
|
49
|
+
await browser.init()
|
|
50
|
+
|
|
51
|
+
expect(mockGet.mock.calls[1][0]).toContain('mount_id=qe')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// 'nas' is listed after 'qe', so choosing it proves the preference is honoured
|
|
55
|
+
// rather than the first-available fallback happening to win.
|
|
56
|
+
it('should honour an explicit initial mount when it is reachable', async () => {
|
|
57
|
+
mockGet
|
|
58
|
+
.mockResolvedValueOnce({ mounts: [...MOUNTS.mounts, { id: 'nas', label: '/mnt/nas', available: true }] })
|
|
59
|
+
.mockResolvedValueOnce(LISTING)
|
|
60
|
+
const browser = useFileBrowser({ initialMountId: 'nas' })
|
|
61
|
+
await browser.init()
|
|
62
|
+
|
|
63
|
+
expect(mockGet.mock.calls[1][0]).toContain('mount_id=nas')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should expose the listing entries', async () => {
|
|
67
|
+
mockGet.mockResolvedValue(LISTING)
|
|
68
|
+
const browser = useFileBrowser()
|
|
69
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
70
|
+
|
|
71
|
+
expect(browser.entries.value.map(e => e.name)).toEqual(['blanks', 'a.raw'])
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('should expose the breadcrumb trail', async () => {
|
|
75
|
+
mockGet.mockResolvedValue(LISTING)
|
|
76
|
+
const browser = useFileBrowser()
|
|
77
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
78
|
+
|
|
79
|
+
expect(browser.breadcrumbs.value.map(c => c.label)).toEqual(['/mnt/instrument-qe', 'raw'])
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should send the configured type rules', async () => {
|
|
83
|
+
mockGet.mockResolvedValue(LISTING)
|
|
84
|
+
const browser = useFileBrowser({ typeRules: ['.raw', '.mzML'] })
|
|
85
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
86
|
+
|
|
87
|
+
expect(mockGet.mock.calls[0][0]).toContain('type_rules=.raw&type_rules=.mzML')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('should surface a listing failure', async () => {
|
|
91
|
+
mockGet.mockRejectedValue(new Error('Mount is offline'))
|
|
92
|
+
const browser = useFileBrowser()
|
|
93
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
94
|
+
|
|
95
|
+
expect(browser.error.value).toContain('Mount is offline')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should empty the listing when a request fails', async () => {
|
|
99
|
+
mockGet.mockRejectedValue(new Error('nope'))
|
|
100
|
+
const browser = useFileBrowser()
|
|
101
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
102
|
+
|
|
103
|
+
expect(browser.entries.value).toEqual([])
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// A slow response for the folder you left must not overwrite the one you are in.
|
|
107
|
+
it('should ignore a superseded listing response', async () => {
|
|
108
|
+
let resolveSlow: (value: unknown) => void = () => {}
|
|
109
|
+
const slow = new Promise(resolve => { resolveSlow = resolve })
|
|
110
|
+
mockGet.mockReturnValueOnce(slow).mockResolvedValueOnce(LISTING)
|
|
111
|
+
|
|
112
|
+
const browser = useFileBrowser()
|
|
113
|
+
const first = browser.navigate({ mount_id: 'qe', path: 'stale' })
|
|
114
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
115
|
+
resolveSlow({ ...LISTING, path: 'stale', entries: [] })
|
|
116
|
+
await first
|
|
117
|
+
|
|
118
|
+
expect(browser.path.value).toBe('raw')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should take the current folder into the selection', async () => {
|
|
122
|
+
mockGet.mockResolvedValue(LISTING)
|
|
123
|
+
const browser = useFileBrowser()
|
|
124
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
125
|
+
browser.selectCurrentFolder()
|
|
126
|
+
|
|
127
|
+
expect(browser.selected.value).toMatchObject([{ mount_id: 'qe', path: 'raw', kind: 'directory' }])
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should not take the same folder twice', async () => {
|
|
131
|
+
mockGet.mockResolvedValue(LISTING)
|
|
132
|
+
const browser = useFileBrowser()
|
|
133
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
134
|
+
browser.selectCurrentFolder()
|
|
135
|
+
browser.selectCurrentFolder()
|
|
136
|
+
|
|
137
|
+
expect(browser.selected.value).toHaveLength(1)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('should clear the selection', async () => {
|
|
141
|
+
mockGet.mockResolvedValue(LISTING)
|
|
142
|
+
const browser = useFileBrowser()
|
|
143
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
144
|
+
browser.selectCurrentFolder()
|
|
145
|
+
browser.clear()
|
|
146
|
+
|
|
147
|
+
expect(browser.selected.value).toEqual([])
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('should report the unfiltered total count', async () => {
|
|
151
|
+
mockGet.mockResolvedValue(LISTING)
|
|
152
|
+
const browser = useFileBrowser()
|
|
153
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
154
|
+
|
|
155
|
+
expect(browser.totalCount.value).toBe(2)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('should not be loading once a request settles', async () => {
|
|
159
|
+
mockGet.mockResolvedValue(LISTING)
|
|
160
|
+
const browser = useFileBrowser()
|
|
161
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
162
|
+
|
|
163
|
+
expect(browser.isLoading.value).toBe(false)
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
describe('useFileBrowser review fixes', () => {
|
|
168
|
+
it('should skip an offline mount even when named as the initial one', async () => {
|
|
169
|
+
mockGet.mockResolvedValueOnce(MOUNTS).mockResolvedValueOnce(LISTING)
|
|
170
|
+
const browser = useFileBrowser({ initialMountId: 'seq' })
|
|
171
|
+
await browser.init()
|
|
172
|
+
|
|
173
|
+
expect(mockGet.mock.calls[1][0]).toContain('mount_id=qe')
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// No configured mounts means the routes are absent, not that browsing failed.
|
|
177
|
+
it('should treat a 404 mount list as no mounts configured', async () => {
|
|
178
|
+
mockGet.mockRejectedValue({ response: { status: 404 } })
|
|
179
|
+
const browser = useFileBrowser()
|
|
180
|
+
await browser.init()
|
|
181
|
+
|
|
182
|
+
expect(browser.error.value).toBeNull()
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('should leave the mount list empty when none are configured', async () => {
|
|
186
|
+
mockGet.mockRejectedValue({ response: { status: 404 } })
|
|
187
|
+
const browser = useFileBrowser()
|
|
188
|
+
await browser.init()
|
|
189
|
+
|
|
190
|
+
expect(browser.mounts.value).toEqual([])
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('should surface the API error detail rather than the status code', async () => {
|
|
194
|
+
mockGet.mockRejectedValue({
|
|
195
|
+
response: { status: 404, data: { detail: "Mount 'nas' is not available" } },
|
|
196
|
+
message: 'Request failed with status code 404',
|
|
197
|
+
})
|
|
198
|
+
const browser = useFileBrowser()
|
|
199
|
+
await browser.navigate({ mount_id: 'nas', path: '' })
|
|
200
|
+
|
|
201
|
+
expect(browser.error.value).toBe("Mount 'nas' is not available")
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('should refetch when the sort changes', async () => {
|
|
205
|
+
mockGet.mockResolvedValue(LISTING)
|
|
206
|
+
const browser = useFileBrowser()
|
|
207
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
208
|
+
const before = mockGet.mock.calls.length
|
|
209
|
+
browser.sort.value = 'size'
|
|
210
|
+
await new Promise(resolve => setTimeout(resolve, 0))
|
|
211
|
+
|
|
212
|
+
expect(mockGet.mock.calls.length).toBeGreaterThan(before)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('should refetch when the search changes', async () => {
|
|
216
|
+
mockGet.mockResolvedValue(LISTING)
|
|
217
|
+
const browser = useFileBrowser()
|
|
218
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
219
|
+
const before = mockGet.mock.calls.length
|
|
220
|
+
browser.search.value = 'hela'
|
|
221
|
+
await new Promise(resolve => setTimeout(resolve, 400))
|
|
222
|
+
|
|
223
|
+
expect(mockGet.mock.calls.length).toBeGreaterThan(before)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('should expose how many rows the listing carries', async () => {
|
|
227
|
+
mockGet.mockResolvedValue({ ...LISTING, shown_count: 2 })
|
|
228
|
+
const browser = useFileBrowser()
|
|
229
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
230
|
+
|
|
231
|
+
expect(browser.shownCount.value).toBe(2)
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('should expose a truncated listing', async () => {
|
|
235
|
+
mockGet.mockResolvedValue({ ...LISTING, truncated: true })
|
|
236
|
+
const browser = useFileBrowser()
|
|
237
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
238
|
+
|
|
239
|
+
expect(browser.truncated.value).toBe(true)
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
describe('useFileBrowser selection and request lifecycle', () => {
|
|
245
|
+
it('never selects an uninitialized or failed directory', async () => {
|
|
246
|
+
const browser = useFileBrowser()
|
|
247
|
+
browser.selectCurrentFolder()
|
|
248
|
+
expect(browser.selected.value).toEqual([])
|
|
249
|
+
mockGet.mockRejectedValueOnce(new Error('offline'))
|
|
250
|
+
await browser.navigate({ mount_id: 'nas', path: 'runs' })
|
|
251
|
+
browser.selectCurrentFolder()
|
|
252
|
+
expect(browser.selected.value).toEqual([])
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('retries the failed destination rather than returning to the previous folder', async () => {
|
|
256
|
+
mockGet.mockResolvedValueOnce(LISTING).mockRejectedValueOnce(new Error('offline')).mockResolvedValueOnce({ ...LISTING, path: 'new' })
|
|
257
|
+
const browser = useFileBrowser()
|
|
258
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
259
|
+
await browser.navigate({ mount_id: 'qe', path: 'new' })
|
|
260
|
+
await browser.refresh()
|
|
261
|
+
expect(mockGet.mock.calls.at(-1)?.[0]).toContain('path=new')
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('does not let obsolete requests keep the current directory loading', async () => {
|
|
265
|
+
let finish!: (value: unknown) => void
|
|
266
|
+
mockGet.mockReturnValueOnce(new Promise(resolve => { finish = resolve })).mockResolvedValueOnce(LISTING)
|
|
267
|
+
const browser = useFileBrowser()
|
|
268
|
+
const old = browser.navigate({ mount_id: 'qe', path: 'old' })
|
|
269
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
270
|
+
expect(browser.isLoading.value).toBe(false)
|
|
271
|
+
finish(LISTING)
|
|
272
|
+
await old
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it('refreshes when reactive type rules change', async () => {
|
|
276
|
+
mockGet.mockResolvedValue(LISTING)
|
|
277
|
+
const typeRules = ref(['.raw'])
|
|
278
|
+
const scope = effectScope()
|
|
279
|
+
const browser = scope.run(() => useFileBrowser({ typeRules }))!
|
|
280
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
281
|
+
typeRules.value = ['.mzML']
|
|
282
|
+
await nextTick()
|
|
283
|
+
expect(mockGet.mock.calls.at(-1)?.[0]).toContain('type_rules=.mzML')
|
|
284
|
+
scope.stop()
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('disposes a pending search without issuing another request', async () => {
|
|
288
|
+
vi.useFakeTimers()
|
|
289
|
+
const scope = effectScope()
|
|
290
|
+
try {
|
|
291
|
+
mockGet.mockResolvedValue(LISTING)
|
|
292
|
+
const browser = scope.run(() => useFileBrowser())!
|
|
293
|
+
await browser.navigate({ mount_id: 'qe', path: 'raw' })
|
|
294
|
+
browser.search.value = 'abc'
|
|
295
|
+
await nextTick()
|
|
296
|
+
const calls = mockGet.mock.calls.length
|
|
297
|
+
scope.stop()
|
|
298
|
+
await vi.advanceTimersByTimeAsync(300)
|
|
299
|
+
expect(mockGet).toHaveBeenCalledTimes(calls)
|
|
300
|
+
} finally {
|
|
301
|
+
scope.stop()
|
|
302
|
+
vi.useRealTimers()
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
})
|
|
@@ -2,6 +2,7 @@ import { effectScope } from 'vue'
|
|
|
2
2
|
import { describe, expect, it, vi } from 'vitest'
|
|
3
3
|
|
|
4
4
|
import { useGeneratedAnalysis } from '../../composables/useGeneratedAnalysis'
|
|
5
|
+
import { MintApiError } from '../../apiErrors'
|
|
5
6
|
import {
|
|
6
7
|
createGeneratedAnalysisTransport,
|
|
7
8
|
type GeneratedAnalysisTransport,
|
|
@@ -701,7 +702,10 @@ describe('useGeneratedAnalysis', () => {
|
|
|
701
702
|
])
|
|
702
703
|
})
|
|
703
704
|
|
|
704
|
-
it(
|
|
705
|
+
it.each([
|
|
706
|
+
{ detail: 'Session not found' },
|
|
707
|
+
{ code: 'resource.not_found', message: 'Session not found', request_id: 'session-request' },
|
|
708
|
+
])('should recreate an expired session and retry one job with the same request id (%j)', async (errorBody) => {
|
|
705
709
|
const storage = memoryStorage()
|
|
706
710
|
const createId = vi.fn()
|
|
707
711
|
.mockReturnValueOnce('tab-1')
|
|
@@ -713,7 +717,7 @@ describe('useGeneratedAnalysis', () => {
|
|
|
713
717
|
id: 'session-1', tab_id: 'tab-1', created_at: '', last_seen_at: '', active_job_ids: [],
|
|
714
718
|
}))
|
|
715
719
|
.mockResolvedValueOnce(response({ jobs: [] }))
|
|
716
|
-
.mockResolvedValueOnce(response(
|
|
720
|
+
.mockResolvedValueOnce(response(errorBody, 404))
|
|
717
721
|
.mockResolvedValueOnce(response({ sessions: [] }))
|
|
718
722
|
.mockResolvedValueOnce(response({
|
|
719
723
|
id: 'session-2', tab_id: 'tab-1', created_at: '', last_seen_at: '', active_job_ids: [],
|
|
@@ -796,6 +800,7 @@ describe('useGeneratedAnalysis', () => {
|
|
|
796
800
|
scope.stop()
|
|
797
801
|
}
|
|
798
802
|
|
|
803
|
+
expect(caught).toBeInstanceOf(MintApiError)
|
|
799
804
|
expect({
|
|
800
805
|
status: (caught as { status?: number } | undefined)?.status,
|
|
801
806
|
message: caught instanceof Error ? caught.message : null,
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils'
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { defineComponent, nextTick, ref } from 'vue'
|
|
4
|
+
import {
|
|
5
|
+
reorderList,
|
|
6
|
+
useListReorder,
|
|
7
|
+
useReorderAnnouncer,
|
|
8
|
+
type ListReorderMove,
|
|
9
|
+
} from '../../composables/useListReorder'
|
|
10
|
+
|
|
11
|
+
interface Row { id: string; name: string }
|
|
12
|
+
|
|
13
|
+
function mountHarness(onReorder: (move: ListReorderMove<Row>) => void, disabled = false) {
|
|
14
|
+
const items = ref<Row[]>([
|
|
15
|
+
{ id: 'a', name: 'Alpha' },
|
|
16
|
+
{ id: 'b', name: 'Beta' },
|
|
17
|
+
{ id: 'c', name: 'Gamma' },
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
const wrapper = mount(defineComponent({
|
|
21
|
+
setup() {
|
|
22
|
+
const reorder = useListReorder<Row>({
|
|
23
|
+
items: () => items.value,
|
|
24
|
+
getId: (row) => row.id,
|
|
25
|
+
getLabel: (row) => row.name,
|
|
26
|
+
disabled: () => disabled,
|
|
27
|
+
itemNoun: 'Row',
|
|
28
|
+
onReorder,
|
|
29
|
+
})
|
|
30
|
+
return { items, ...reorder }
|
|
31
|
+
},
|
|
32
|
+
template: `
|
|
33
|
+
<ul>
|
|
34
|
+
<li v-for="(row, index) in items" :key="row.id" :data-id="row.id"
|
|
35
|
+
@keydown="onKeydown($event, row, index)"
|
|
36
|
+
@dragstart="onDragStart($event, row, index)"
|
|
37
|
+
@dragover="onDragOver($event, row, index)"
|
|
38
|
+
@drop="onDrop($event, row, index)">{{ row.name }}</li>
|
|
39
|
+
</ul>
|
|
40
|
+
<p class="announcement">{{ announcement }}</p>
|
|
41
|
+
`,
|
|
42
|
+
}), { attachTo: document.body })
|
|
43
|
+
|
|
44
|
+
return { wrapper, items }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('reorderList', () => {
|
|
48
|
+
it('returns a copy with the item moved, leaving the source untouched', () => {
|
|
49
|
+
const source = ['a', 'b', 'c']
|
|
50
|
+
|
|
51
|
+
expect(reorderList(source, 0, 2)).toEqual(['b', 'c', 'a'])
|
|
52
|
+
expect(source).toEqual(['a', 'b', 'c'])
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('useReorderAnnouncer', () => {
|
|
57
|
+
it('publishes messages and re-announces an identical message', async () => {
|
|
58
|
+
const { announcement, announce } = useReorderAnnouncer()
|
|
59
|
+
|
|
60
|
+
announce('Alpha moved.')
|
|
61
|
+
await nextTick()
|
|
62
|
+
expect(announcement.value).toBe('Alpha moved.')
|
|
63
|
+
|
|
64
|
+
announce('Alpha moved.')
|
|
65
|
+
expect(announcement.value).toBe('')
|
|
66
|
+
await nextTick()
|
|
67
|
+
expect(announcement.value).toBe('Alpha moved.')
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('useListReorder', () => {
|
|
72
|
+
it('moves an item one position later on Alt+ArrowDown and announces it', async () => {
|
|
73
|
+
const onReorder = vi.fn()
|
|
74
|
+
const { wrapper } = mountHarness(onReorder)
|
|
75
|
+
|
|
76
|
+
await wrapper.get('[data-id="a"]').trigger('keydown', { key: 'ArrowDown', altKey: true })
|
|
77
|
+
|
|
78
|
+
expect(onReorder).toHaveBeenCalledTimes(1)
|
|
79
|
+
const move = onReorder.mock.calls[0][0] as ListReorderMove<Row>
|
|
80
|
+
expect(move.fromIndex).toBe(0)
|
|
81
|
+
expect(move.toIndex).toBe(1)
|
|
82
|
+
expect(move.source).toBe('keyboard')
|
|
83
|
+
expect(move.items.map(row => row.id)).toEqual(['b', 'a', 'c'])
|
|
84
|
+
|
|
85
|
+
await nextTick()
|
|
86
|
+
expect(wrapper.get('.announcement').text()).toBe('Alpha moved to position 2 of 3.')
|
|
87
|
+
|
|
88
|
+
wrapper.unmount()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('moves an item one position earlier on Alt+ArrowUp and Alt+ArrowLeft', async () => {
|
|
92
|
+
const onReorder = vi.fn()
|
|
93
|
+
const { wrapper } = mountHarness(onReorder)
|
|
94
|
+
|
|
95
|
+
await wrapper.get('[data-id="c"]').trigger('keydown', { key: 'ArrowUp', altKey: true })
|
|
96
|
+
await wrapper.get('[data-id="c"]').trigger('keydown', { key: 'ArrowLeft', altKey: true })
|
|
97
|
+
|
|
98
|
+
expect(onReorder.mock.calls.map(call => (call[0] as ListReorderMove<Row>).toIndex)).toEqual([1, 1])
|
|
99
|
+
|
|
100
|
+
wrapper.unmount()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('ignores arrow keys without Alt and moves past either end', async () => {
|
|
104
|
+
const onReorder = vi.fn()
|
|
105
|
+
const { wrapper } = mountHarness(onReorder)
|
|
106
|
+
|
|
107
|
+
await wrapper.get('[data-id="a"]').trigger('keydown', { key: 'ArrowDown' })
|
|
108
|
+
await wrapper.get('[data-id="a"]').trigger('keydown', { key: 'ArrowUp', altKey: true })
|
|
109
|
+
await wrapper.get('[data-id="c"]').trigger('keydown', { key: 'ArrowDown', altKey: true })
|
|
110
|
+
|
|
111
|
+
expect(onReorder).not.toHaveBeenCalled()
|
|
112
|
+
|
|
113
|
+
wrapper.unmount()
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('reorders through a drag and clears its drag state', async () => {
|
|
117
|
+
const onReorder = vi.fn()
|
|
118
|
+
const { wrapper } = mountHarness(onReorder)
|
|
119
|
+
|
|
120
|
+
const dataTransfer = { effectAllowed: '', dropEffect: '', setData: vi.fn() }
|
|
121
|
+
await wrapper.get('[data-id="a"]').trigger('dragstart', { dataTransfer })
|
|
122
|
+
await wrapper.get('[data-id="c"]').trigger('dragover', { dataTransfer })
|
|
123
|
+
await wrapper.get('[data-id="c"]').trigger('drop', { dataTransfer })
|
|
124
|
+
|
|
125
|
+
const move = onReorder.mock.calls[0][0] as ListReorderMove<Row>
|
|
126
|
+
expect(move).toMatchObject({ fromIndex: 0, toIndex: 2, source: 'drag' })
|
|
127
|
+
expect(move.items.map(row => row.id)).toEqual(['b', 'c', 'a'])
|
|
128
|
+
expect(dataTransfer.setData).toHaveBeenCalledWith('text/plain', 'a')
|
|
129
|
+
|
|
130
|
+
wrapper.unmount()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('blocks every reorder while disabled', async () => {
|
|
134
|
+
const onReorder = vi.fn()
|
|
135
|
+
const { wrapper } = mountHarness(onReorder, true)
|
|
136
|
+
|
|
137
|
+
await wrapper.get('[data-id="a"]').trigger('keydown', { key: 'ArrowDown', altKey: true })
|
|
138
|
+
await wrapper.get('[data-id="a"]').trigger('dragstart', { dataTransfer: { setData: vi.fn() } })
|
|
139
|
+
await wrapper.get('[data-id="b"]').trigger('drop', { dataTransfer: { setData: vi.fn() } })
|
|
140
|
+
|
|
141
|
+
expect(onReorder).not.toHaveBeenCalled()
|
|
142
|
+
|
|
143
|
+
wrapper.unmount()
|
|
144
|
+
})
|
|
145
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { beforeEach, expect, it, vi } from 'vitest'
|
|
2
|
+
const { get } = vi.hoisted(() => ({ get: vi.fn() }))
|
|
3
|
+
vi.mock('../../composables/useApi', () => ({ useApi: () => ({ get }) }))
|
|
4
|
+
import {
|
|
5
|
+
decodePlatformPickerPath,
|
|
6
|
+
encodePlatformPickerPath,
|
|
7
|
+
usePlatformFilePickerAdapter,
|
|
8
|
+
} from '../../composables/usePlatformFilePickerAdapter'
|
|
9
|
+
|
|
10
|
+
beforeEach(() => get.mockReset())
|
|
11
|
+
it('preserves mount and relative path identities with punctuation and Unicode', () => {
|
|
12
|
+
const path = '测试/one, [2] # ?.raw'
|
|
13
|
+
expect(decodePlatformPickerPath(encodePlatformPickerPath('instrument', path))).toEqual({
|
|
14
|
+
mountId: 'instrument',
|
|
15
|
+
path,
|
|
16
|
+
})
|
|
17
|
+
expect(() => decodePlatformPickerPath('["mount"]')).toThrow()
|
|
18
|
+
expect(() => decodePlatformPickerPath('["", "file"]')).toThrow()
|
|
19
|
+
})
|
|
20
|
+
it('passes cancellation, marks offline mounts, and maps compound extensions', async () => {
|
|
21
|
+
get
|
|
22
|
+
.mockResolvedValueOnce({
|
|
23
|
+
mounts: [
|
|
24
|
+
{ id: 'online', label: 'Instrument', available: true },
|
|
25
|
+
{ id: 'offline', label: 'NAS', available: false },
|
|
26
|
+
],
|
|
27
|
+
})
|
|
28
|
+
.mockResolvedValueOnce({
|
|
29
|
+
entries: [{ kind: 'file', path: 'run.mzML.gz', name: 'run.mzML.gz', size_bytes: 7 }],
|
|
30
|
+
})
|
|
31
|
+
const adapter = usePlatformFilePickerAdapter({ apiBaseUrl: '/api' })
|
|
32
|
+
const signal = new AbortController().signal
|
|
33
|
+
const roots = await adapter.listRoot({ signal })
|
|
34
|
+
expect(roots[1]?.unavailableReason).toContain('offline')
|
|
35
|
+
const files = await adapter.listChildren(roots[0]!.path, { signal })
|
|
36
|
+
expect(files[0]).toMatchObject({
|
|
37
|
+
kind: 'file',
|
|
38
|
+
format: 'mzML.gz',
|
|
39
|
+
bytes: 7,
|
|
40
|
+
parentPath: roots[0]!.path,
|
|
41
|
+
})
|
|
42
|
+
expect(get.mock.calls.every((call) => call[1].signal === signal)).toBe(true)
|
|
43
|
+
})
|
|
44
|
+
it('refuses truncated listings rather than returning a partial folder selection', async () => {
|
|
45
|
+
get.mockResolvedValue({ truncated: true, entries: [] })
|
|
46
|
+
await expect(
|
|
47
|
+
usePlatformFilePickerAdapter({ apiBaseUrl: '/api' }).listChildren(
|
|
48
|
+
encodePlatformPickerPath('a'),
|
|
49
|
+
),
|
|
50
|
+
).rejects.toThrow('listing limit')
|
|
51
|
+
})
|
|
52
|
+
it('scopes shallow search to one level and recursive search skips offline mounts', async () => {
|
|
53
|
+
get.mockImplementation(async (url: string) =>
|
|
54
|
+
url === '/filesystem/mounts'
|
|
55
|
+
? {
|
|
56
|
+
mounts: [
|
|
57
|
+
{ id: 'a', label: 'Instrument', available: true },
|
|
58
|
+
{ id: 'b', label: 'NAS', available: false },
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
: { entries: [{ kind: 'file', path: 'run.raw', name: 'run.raw', size_bytes: 1 }] },
|
|
62
|
+
)
|
|
63
|
+
const adapter = usePlatformFilePickerAdapter({ apiBaseUrl: '/api' })
|
|
64
|
+
expect(await adapter.search('run', { path: '', recursive: false })).toEqual([])
|
|
65
|
+
expect(await adapter.search('run', { path: '', recursive: true })).toHaveLength(1)
|
|
66
|
+
expect(get.mock.calls.filter((call) => call[0].includes('/browse'))).toHaveLength(1)
|
|
67
|
+
})
|
|
@@ -184,6 +184,7 @@ describe('usePluginClient', () => {
|
|
|
184
184
|
|
|
185
185
|
expect(requestConfigs[0]!.baseURL).toBe('/api/drp')
|
|
186
186
|
expect(requestConfigs[0]!.url).toBe('/health')
|
|
187
|
+
expect(requestConfigs[0]).toMatchObject({ _mintTypedErrors: true })
|
|
187
188
|
})
|
|
188
189
|
|
|
189
190
|
it('omits MINT auth transport for a public generated endpoint', async () => {
|
|
@@ -396,6 +397,7 @@ describe('usePluginClient', () => {
|
|
|
396
397
|
expect(requestConfigs[0]!.url).toBe('/schedule/import')
|
|
397
398
|
expect(requestConfigs[0]!.params).toEqual({ instrument_id: 'orbitrap', mode: 'replace' })
|
|
398
399
|
expect(requestConfigs[0]!.headers).toEqual({ 'Content-Type': undefined })
|
|
400
|
+
expect(requestConfigs[0]).toMatchObject({ _mintTypedErrors: true })
|
|
399
401
|
expect(requestBodies[0]).toBeInstanceOf(FormData)
|
|
400
402
|
const form = requestBodies[0] as FormData
|
|
401
403
|
expect(form.get('file')).toBeInstanceOf(Blob)
|
|
@@ -462,6 +464,7 @@ describe('usePluginClient', () => {
|
|
|
462
464
|
expect(requestConfigs[0]!.url).toBe('/schedule/export')
|
|
463
465
|
expect(requestConfigs[0]!.params).toEqual({ instrument_id: 'orbitrap', start: '2026-05-27' })
|
|
464
466
|
expect(requestConfigs[0]!.responseType).toBe('blob')
|
|
467
|
+
expect(requestConfigs[0]).toMatchObject({ _mintTypedErrors: true })
|
|
465
468
|
})
|
|
466
469
|
|
|
467
470
|
it('downloads generated POST endpoint blobs with the contract body and request options', async () => {
|