@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
|
@@ -42,4 +42,80 @@ describe('DatePicker', () => {
|
|
|
42
42
|
|
|
43
43
|
wrapper.unmount()
|
|
44
44
|
})
|
|
45
|
+
|
|
46
|
+
it('opens from the input with Enter, Space and ArrowDown', async () => {
|
|
47
|
+
const wrapper = mount(DatePicker, { attachTo: document.body })
|
|
48
|
+
|
|
49
|
+
for (const key of ['Enter', ' ', 'ArrowDown']) {
|
|
50
|
+
await wrapper.get('input').trigger('keydown', { key })
|
|
51
|
+
expect(dropdown()).not.toBeNull()
|
|
52
|
+
|
|
53
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
|
|
54
|
+
await nextTick()
|
|
55
|
+
expect(dropdown()).toBeNull()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
wrapper.unmount()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('moves focus into the calendar grid and back to the input on close', async () => {
|
|
62
|
+
const wrapper = mount(DatePicker, {
|
|
63
|
+
props: { modelValue: '2026-04-22' },
|
|
64
|
+
attachTo: document.body,
|
|
65
|
+
})
|
|
66
|
+
const input = wrapper.get<HTMLInputElement>('input').element
|
|
67
|
+
|
|
68
|
+
await wrapper.get('input').trigger('click')
|
|
69
|
+
await nextTick()
|
|
70
|
+
await nextTick()
|
|
71
|
+
await nextTick()
|
|
72
|
+
|
|
73
|
+
expect(document.activeElement).toBe(
|
|
74
|
+
document.body.querySelector('.mint-date-picker__day--selected'),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
|
|
78
|
+
await nextTick()
|
|
79
|
+
await nextTick()
|
|
80
|
+
|
|
81
|
+
expect(document.activeElement).toBe(input)
|
|
82
|
+
|
|
83
|
+
wrapper.unmount()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('marks the panel as a non-modal dialog', async () => {
|
|
87
|
+
const wrapper = mount(DatePicker, { attachTo: document.body })
|
|
88
|
+
|
|
89
|
+
await wrapper.get('input').trigger('click')
|
|
90
|
+
|
|
91
|
+
const panel = dropdown()
|
|
92
|
+
expect(panel?.getAttribute('role')).toBe('dialog')
|
|
93
|
+
expect(panel?.hasAttribute('aria-modal')).toBe(false)
|
|
94
|
+
expect(wrapper.get('input').attributes('aria-haspopup')).toBe('dialog')
|
|
95
|
+
expect(wrapper.get('input').attributes('aria-expanded')).toBe('true')
|
|
96
|
+
|
|
97
|
+
wrapper.unmount()
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('keeps the panel open while the page scrolls, repositioning instead', async () => {
|
|
101
|
+
const wrapper = mount(DatePicker, { attachTo: document.body })
|
|
102
|
+
|
|
103
|
+
await wrapper.get('input').trigger('click')
|
|
104
|
+
window.dispatchEvent(new Event('scroll'))
|
|
105
|
+
await nextTick()
|
|
106
|
+
|
|
107
|
+
expect(dropdown()).not.toBeNull()
|
|
108
|
+
|
|
109
|
+
wrapper.unmount()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('does not open from the keyboard while disabled', async () => {
|
|
113
|
+
const wrapper = mount(DatePicker, { props: { disabled: true }, attachTo: document.body })
|
|
114
|
+
|
|
115
|
+
await wrapper.get('input').trigger('keydown', { key: 'Enter' })
|
|
116
|
+
|
|
117
|
+
expect(dropdown()).toBeNull()
|
|
118
|
+
|
|
119
|
+
wrapper.unmount()
|
|
120
|
+
})
|
|
45
121
|
})
|
|
@@ -45,4 +45,40 @@ describe('DateTimePicker', () => {
|
|
|
45
45
|
|
|
46
46
|
wrapper.unmount()
|
|
47
47
|
})
|
|
48
|
+
|
|
49
|
+
it('opens from the keyboard, focuses the calendar, and restores focus on close', async () => {
|
|
50
|
+
const wrapper = mount(DateTimePicker, {
|
|
51
|
+
props: { modelValue: '2026-04-22T14:30' },
|
|
52
|
+
attachTo: document.body,
|
|
53
|
+
})
|
|
54
|
+
const input = wrapper.get<HTMLInputElement>('input').element
|
|
55
|
+
|
|
56
|
+
await wrapper.get('input').trigger('keydown', { key: 'ArrowDown' })
|
|
57
|
+
await nextTick()
|
|
58
|
+
await nextTick()
|
|
59
|
+
await nextTick()
|
|
60
|
+
|
|
61
|
+
expect(wrapper.find('.mint-datetime-picker__dropdown').exists()).toBe(true)
|
|
62
|
+
expect(document.activeElement).toBe(wrapper.get('.mint-date-picker__day--selected').element)
|
|
63
|
+
|
|
64
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
|
|
65
|
+
await nextTick()
|
|
66
|
+
await nextTick()
|
|
67
|
+
|
|
68
|
+
expect(document.activeElement).toBe(input)
|
|
69
|
+
|
|
70
|
+
wrapper.unmount()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('marks the panel as a non-modal dialog', async () => {
|
|
74
|
+
const wrapper = mount(DateTimePicker, { attachTo: document.body })
|
|
75
|
+
|
|
76
|
+
await wrapper.get('input').trigger('click')
|
|
77
|
+
|
|
78
|
+
const panel = wrapper.get('.mint-datetime-picker__dropdown')
|
|
79
|
+
expect(panel.attributes('role')).toBe('dialog')
|
|
80
|
+
expect(panel.attributes('aria-modal')).toBeUndefined()
|
|
81
|
+
|
|
82
|
+
wrapper.unmount()
|
|
83
|
+
})
|
|
48
84
|
})
|
|
@@ -21,6 +21,36 @@ describe('ExperimentPopover', () => {
|
|
|
21
21
|
document.body.innerHTML = ''
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
+
it('requests experiment selection on the first click when no experiment is selected', async () => {
|
|
25
|
+
const wrapper = mountPopover()
|
|
26
|
+
|
|
27
|
+
await wrapper.find('.mint-experiment-popover__trigger').trigger('click')
|
|
28
|
+
|
|
29
|
+
expect(wrapper.emitted('select')).toHaveLength(1)
|
|
30
|
+
expect(wrapper.find('.mint-experiment-popover__panel').exists()).toBe(false)
|
|
31
|
+
|
|
32
|
+
wrapper.unmount()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it.each([
|
|
36
|
+
{ experimentName: 'Dose response run' },
|
|
37
|
+
{ experimentCode: 'EXP-001' },
|
|
38
|
+
{ isResolving: true },
|
|
39
|
+
{ error: 'Experiment not found' },
|
|
40
|
+
])('keeps the popover interaction for bound experiment state %o', async (props) => {
|
|
41
|
+
const wrapper = mountPopover(props)
|
|
42
|
+
|
|
43
|
+
await wrapper.find('.mint-experiment-popover__trigger').trigger('click')
|
|
44
|
+
|
|
45
|
+
expect(wrapper.emitted('select')).toBeUndefined()
|
|
46
|
+
expect(wrapper.find('.mint-experiment-popover__panel').exists()).toBe(true)
|
|
47
|
+
|
|
48
|
+
await wrapper.find('.mint-experiment-popover__trigger').trigger('click')
|
|
49
|
+
expect(wrapper.find('.mint-experiment-popover__panel').exists()).toBe(false)
|
|
50
|
+
|
|
51
|
+
wrapper.unmount()
|
|
52
|
+
})
|
|
53
|
+
|
|
24
54
|
it('prefers the experiment name in the trigger when both name and code are available', () => {
|
|
25
55
|
const wrapper = mountPopover({
|
|
26
56
|
experimentName: 'Dose response run',
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import ExperimentTimeline from '../../components/ExperimentTimeline.vue'
|
|
4
|
+
import type { ProtocolStep } from '../../types'
|
|
5
|
+
|
|
6
|
+
function makeSteps(): ProtocolStep[] {
|
|
7
|
+
return [
|
|
8
|
+
{ id: 's1', name: 'Thaw', type: 'incubation', status: 'pending', order: 0 },
|
|
9
|
+
{ id: 's2', name: 'Quench', type: 'incubation', status: 'pending', order: 1 },
|
|
10
|
+
{ id: 's3', name: 'Extract', type: 'incubation', status: 'pending', order: 2 },
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('ExperimentTimeline', () => {
|
|
15
|
+
it('renders steps in order', () => {
|
|
16
|
+
const wrapper = mount(ExperimentTimeline, { props: { modelValue: makeSteps() } })
|
|
17
|
+
|
|
18
|
+
expect(wrapper.findAll('.mint-timeline__title').map(title => title.text()))
|
|
19
|
+
.toEqual(['Thaw', 'Quench', 'Extract'])
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('reorders a step with Alt and the arrow keys while editable', async () => {
|
|
23
|
+
const wrapper = mount(ExperimentTimeline, {
|
|
24
|
+
props: { modelValue: makeSteps(), editable: true },
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const first = wrapper.findAll('.mint-timeline__step')[0]
|
|
28
|
+
expect(first.attributes('tabindex')).toBe('0')
|
|
29
|
+
expect(first.attributes('aria-label')).toContain('Press Alt with the arrow keys')
|
|
30
|
+
|
|
31
|
+
await first.trigger('keydown', { key: 'ArrowDown', altKey: true })
|
|
32
|
+
|
|
33
|
+
expect(wrapper.emitted('step-reorder')?.[0]).toEqual(['s1', 1])
|
|
34
|
+
const steps = wrapper.emitted('update:modelValue')?.[0]?.[0] as ProtocolStep[]
|
|
35
|
+
expect(steps.map(step => step.id)).toEqual(['s2', 's1', 's3'])
|
|
36
|
+
expect(steps.map(step => step.order)).toEqual([0, 1, 2])
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('announces the move politely', async () => {
|
|
40
|
+
const wrapper = mount(ExperimentTimeline, {
|
|
41
|
+
props: { modelValue: makeSteps(), editable: true },
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
await wrapper.findAll('.mint-timeline__step')[2]
|
|
45
|
+
.trigger('keydown', { key: 'ArrowUp', altKey: true })
|
|
46
|
+
await wrapper.vm.$nextTick()
|
|
47
|
+
|
|
48
|
+
const liveRegion = wrapper.get('[role="status"]')
|
|
49
|
+
expect(liveRegion.attributes('aria-live')).toBe('polite')
|
|
50
|
+
expect(liveRegion.text()).toBe('Extract moved to position 2 of 3.')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('is not keyboard reorderable when not editable', async () => {
|
|
54
|
+
const wrapper = mount(ExperimentTimeline, { props: { modelValue: makeSteps() } })
|
|
55
|
+
|
|
56
|
+
const first = wrapper.findAll('.mint-timeline__step')[0]
|
|
57
|
+
expect(first.attributes('tabindex')).toBeUndefined()
|
|
58
|
+
|
|
59
|
+
await first.trigger('keydown', { key: 'ArrowDown', altKey: true })
|
|
60
|
+
|
|
61
|
+
expect(wrapper.emitted('step-reorder')).toBeUndefined()
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import { enableAutoUnmount, mount } from '@vue/test-utils'
|
|
2
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
3
|
+
import { nextTick } from 'vue'
|
|
4
|
+
|
|
5
|
+
enableAutoUnmount(afterEach)
|
|
6
|
+
|
|
7
|
+
import FileBrowserModal from '../../components/FileBrowserModal.vue'
|
|
8
|
+
import type { FileEntry, ServerMount } from '../../types/fileBrowserTypes'
|
|
9
|
+
|
|
10
|
+
const MOUNTS: ServerMount[] = [
|
|
11
|
+
{
|
|
12
|
+
id: 'qe',
|
|
13
|
+
label: '/mnt/instrument-qe',
|
|
14
|
+
count: 4,
|
|
15
|
+
children: [{ id: 'qe:2026-07', label: '2026-07', count: 12 }],
|
|
16
|
+
},
|
|
17
|
+
{ id: 'nas', label: '/mnt/nas-shared', count: 18 },
|
|
18
|
+
{ id: 'seq', label: '/mnt/sequencer-01', available: false },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const ENTRIES: FileEntry[] = [
|
|
22
|
+
{ name: 'calibration', path: 'raw/calibration', kind: 'directory', child_count: 2 },
|
|
23
|
+
{ name: 'blanks', path: 'raw/blanks', kind: 'directory', child_count: 6 },
|
|
24
|
+
{
|
|
25
|
+
name: 'HeLa_norm_0h_r1.raw',
|
|
26
|
+
path: 'raw/HeLa_norm_0h_r1.raw',
|
|
27
|
+
kind: 'file',
|
|
28
|
+
size_bytes: 1_900_000_000,
|
|
29
|
+
extension: '.raw',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'acquisition_log.txt',
|
|
33
|
+
path: 'raw/acquisition_log.txt',
|
|
34
|
+
kind: 'file',
|
|
35
|
+
size_bytes: 48_000,
|
|
36
|
+
extension: '.txt',
|
|
37
|
+
matches_type_rules: false,
|
|
38
|
+
},
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
function mountBrowser(props: Record<string, unknown> = {}) {
|
|
42
|
+
return mount(FileBrowserModal, {
|
|
43
|
+
props: {
|
|
44
|
+
modelValue: true,
|
|
45
|
+
mounts: MOUNTS,
|
|
46
|
+
mountId: 'qe',
|
|
47
|
+
path: 'raw',
|
|
48
|
+
entries: ENTRIES,
|
|
49
|
+
breadcrumbs: [
|
|
50
|
+
{ label: '/mnt/instrument-qe', path: '' },
|
|
51
|
+
{ label: 'raw', path: 'raw' },
|
|
52
|
+
],
|
|
53
|
+
...props,
|
|
54
|
+
},
|
|
55
|
+
global: { stubs: { teleport: true } },
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function rowAt(wrapper: ReturnType<typeof mountBrowser>, index: number) {
|
|
60
|
+
return wrapper.findAll('.mint-file-browser__row')[index]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe('FileBrowserModal', () => {
|
|
64
|
+
it('should render one row per entry', () => {
|
|
65
|
+
expect(mountBrowser().findAll('.mint-file-browser__row')).toHaveLength(ENTRIES.length)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('should render a row per configured mount', () => {
|
|
69
|
+
expect(mountBrowser().findAll('.mint-file-browser__mount')).toHaveLength(4)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should mark an unavailable mount as offline', () => {
|
|
73
|
+
expect(mountBrowser().text()).toContain('offline')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// The listing never lies about what is there: non-matching entries dim.
|
|
77
|
+
it('should keep a non-matching entry in the listing', () => {
|
|
78
|
+
expect(mountBrowser().text()).toContain('acquisition_log.txt')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should dim a non-matching entry when dimNonMatching is set', () => {
|
|
82
|
+
expect(rowAt(mountBrowser(), 3).classes()).toContain('mint-file-browser__row--dimmed')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('should not dim a non-matching entry when dimNonMatching is false', () => {
|
|
86
|
+
const wrapper = mountBrowser({ dimNonMatching: false })
|
|
87
|
+
|
|
88
|
+
expect(rowAt(wrapper, 3).classes()).not.toContain('mint-file-browser__row--dimmed')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should not dim a matching entry', () => {
|
|
92
|
+
expect(rowAt(mountBrowser(), 2).classes()).not.toContain('mint-file-browser__row--dimmed')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// Ticking a folder takes the whole directory; clicking the row opens it.
|
|
96
|
+
it('should emit navigate when a directory row is clicked', async () => {
|
|
97
|
+
const wrapper = mountBrowser()
|
|
98
|
+
await rowAt(wrapper, 0).trigger('click')
|
|
99
|
+
|
|
100
|
+
expect(wrapper.emitted('navigate')?.[0]?.[0]).toMatchObject({ path: 'raw/calibration' })
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('should not emit navigate when a file row is clicked', async () => {
|
|
104
|
+
const wrapper = mountBrowser()
|
|
105
|
+
await rowAt(wrapper, 2).trigger('click')
|
|
106
|
+
|
|
107
|
+
expect(wrapper.emitted('navigate')).toBeUndefined()
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should select the whole folder when a directory checkbox is ticked', async () => {
|
|
111
|
+
const wrapper = mountBrowser()
|
|
112
|
+
await rowAt(wrapper, 0).find('.mint-file-browser__checkbox').setValue(true)
|
|
113
|
+
|
|
114
|
+
expect(wrapper.emitted('update:selected')?.[0]?.[0]).toMatchObject([
|
|
115
|
+
{ path: 'raw/calibration', kind: 'directory' },
|
|
116
|
+
])
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// setValue only dispatches `change`; a real user also clicks, and it is the
|
|
120
|
+
// @click.stop that keeps the tick from bubbling into a navigation.
|
|
121
|
+
it('should not navigate when a directory checkbox is clicked', async () => {
|
|
122
|
+
const wrapper = mountBrowser()
|
|
123
|
+
const checkbox = rowAt(wrapper, 0).find('.mint-file-browser__checkbox')
|
|
124
|
+
await checkbox.trigger('click')
|
|
125
|
+
await checkbox.setValue(true)
|
|
126
|
+
|
|
127
|
+
expect(wrapper.emitted('navigate')).toBeUndefined()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// The row is a div, so this button is the only thing Tab can reach.
|
|
131
|
+
it('should expose a focusable open button on a directory row', () => {
|
|
132
|
+
expect(rowAt(mountBrowser(), 0).find('button.mint-file-browser__open').exists()).toBe(true)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should not expose an open button on a file row', () => {
|
|
136
|
+
expect(rowAt(mountBrowser(), 2).find('button.mint-file-browser__open').exists()).toBe(false)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('should navigate when the open button is activated', async () => {
|
|
140
|
+
const wrapper = mountBrowser()
|
|
141
|
+
await rowAt(wrapper, 0).find('button.mint-file-browser__open').trigger('click')
|
|
142
|
+
|
|
143
|
+
expect(wrapper.emitted('navigate')?.[0]?.[0]).toMatchObject({ path: 'raw/calibration' })
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('should not select when the open button is activated', async () => {
|
|
147
|
+
const wrapper = mountBrowser()
|
|
148
|
+
await rowAt(wrapper, 0).find('button.mint-file-browser__open').trigger('click')
|
|
149
|
+
|
|
150
|
+
expect(wrapper.emitted('update:selected')).toBeUndefined()
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should label the open button with the folder name', () => {
|
|
154
|
+
const button = rowAt(mountBrowser(), 0).find('button.mint-file-browser__open')
|
|
155
|
+
|
|
156
|
+
expect(button.attributes('aria-label')).toBe('Open calibration')
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('should describe a ticked folder by its child count', async () => {
|
|
160
|
+
const wrapper = mountBrowser({
|
|
161
|
+
selected: [
|
|
162
|
+
{ mount_id: 'qe', path: 'raw/blanks', name: 'blanks', kind: 'directory', child_count: 6 },
|
|
163
|
+
],
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
// "items", not "files": the server counts every visible entry, subdirectories included.
|
|
167
|
+
expect(wrapper.text()).toContain('whole folder, 6 items')
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('should emit selectFolder for the directory being viewed', async () => {
|
|
171
|
+
const wrapper = mountBrowser()
|
|
172
|
+
await wrapper.find('.mint-file-browser__select-folder').trigger('click')
|
|
173
|
+
|
|
174
|
+
expect(wrapper.emitted('selectFolder')?.[0]?.[0]).toMatchObject({ mount_id: 'qe', path: 'raw' })
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('should show the local picker button by default', () => {
|
|
178
|
+
expect(mountBrowser().find('.mint-file-browser__local').exists()).toBe(true)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should hide the local picker when showLocalPicker is false', () => {
|
|
182
|
+
expect(
|
|
183
|
+
mountBrowser({ showLocalPicker: false }).find('.mint-file-browser__local').exists(),
|
|
184
|
+
).toBe(false)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('should emit chooseLocal when the local picker is clicked', async () => {
|
|
188
|
+
const wrapper = mountBrowser()
|
|
189
|
+
await wrapper.find('.mint-file-browser__local').trigger('click')
|
|
190
|
+
|
|
191
|
+
expect(wrapper.emitted('chooseLocal')).toHaveLength(1)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should render the type rules', () => {
|
|
195
|
+
expect(mountBrowser({ typeRules: ['.raw', '.mzML'] }).text()).toContain('.raw .mzML')
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('should report how many entries match the type rules', () => {
|
|
199
|
+
expect(mountBrowser({ totalCount: 1284, matchCount: 9 }).text()).toContain(
|
|
200
|
+
'9 match the type rules',
|
|
201
|
+
)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('should summarise the selection count', () => {
|
|
205
|
+
const wrapper = mountBrowser({
|
|
206
|
+
selected: [
|
|
207
|
+
{ mount_id: 'qe', path: 'raw/blanks', name: 'blanks', kind: 'directory' },
|
|
208
|
+
{ mount_id: 'qe', path: 'raw/a.raw', name: 'a.raw', kind: 'file' },
|
|
209
|
+
],
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
expect(wrapper.text()).toContain('2 selected')
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('should render a chip per selected item', () => {
|
|
216
|
+
const wrapper = mountBrowser({
|
|
217
|
+
selected: [
|
|
218
|
+
{ mount_id: 'qe', path: 'raw/blanks', name: 'blanks', kind: 'directory' },
|
|
219
|
+
{ mount_id: 'qe', path: 'raw/a.raw', name: 'a.raw', kind: 'file' },
|
|
220
|
+
],
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
expect(wrapper.findAll('.mint-file-browser__chip')).toHaveLength(2)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('should clear the selection', async () => {
|
|
227
|
+
const wrapper = mountBrowser({
|
|
228
|
+
selected: [{ mount_id: 'qe', path: 'raw/a.raw', name: 'a.raw', kind: 'file' }],
|
|
229
|
+
})
|
|
230
|
+
await wrapper.find('.mint-file-browser__clear').trigger('click')
|
|
231
|
+
|
|
232
|
+
expect(wrapper.emitted('update:selected')?.[0]?.[0]).toEqual([])
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('should remove one item from the selection via its chip', async () => {
|
|
236
|
+
const wrapper = mountBrowser({
|
|
237
|
+
selected: [
|
|
238
|
+
{ mount_id: 'qe', path: 'raw/blanks', name: 'blanks', kind: 'directory' },
|
|
239
|
+
{ mount_id: 'qe', path: 'raw/a.raw', name: 'a.raw', kind: 'file' },
|
|
240
|
+
],
|
|
241
|
+
})
|
|
242
|
+
await wrapper.findAll('.mint-file-browser__chip-remove')[0].trigger('click')
|
|
243
|
+
|
|
244
|
+
expect(wrapper.emitted('update:selected')?.[0]?.[0]).toMatchObject([{ path: 'raw/a.raw' }])
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('should confirm with the current selection', async () => {
|
|
248
|
+
const wrapper = mountBrowser({
|
|
249
|
+
selected: [{ mount_id: 'qe', path: 'raw/a.raw', name: 'a.raw', kind: 'file' }],
|
|
250
|
+
})
|
|
251
|
+
await wrapper.find('.mint-file-browser__confirm').trigger('click')
|
|
252
|
+
|
|
253
|
+
expect(wrapper.emitted('confirm')?.[0]?.[0]).toMatchObject([{ path: 'raw/a.raw' }])
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('should disable confirming an empty selection', () => {
|
|
257
|
+
expect(mountBrowser().find('.mint-file-browser__confirm').attributes('disabled')).toBeDefined()
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('should emit navigate for the parent when the back button is used', async () => {
|
|
261
|
+
const wrapper = mountBrowser({ parent: '' })
|
|
262
|
+
await wrapper.find('.mint-file-browser__back').trigger('click')
|
|
263
|
+
|
|
264
|
+
expect(wrapper.emitted('navigate')?.[0]?.[0]).toMatchObject({ mount_id: 'qe', path: '' })
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('should disable the back button at a mount root', () => {
|
|
268
|
+
const wrapper = mountBrowser({ path: '', parent: null })
|
|
269
|
+
|
|
270
|
+
expect(wrapper.find('.mint-file-browser__back').attributes('disabled')).toBeDefined()
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('should emit navigate when a mount is chosen', async () => {
|
|
274
|
+
const wrapper = mountBrowser()
|
|
275
|
+
await wrapper.findAll('.mint-file-browser__mount')[1].trigger('click')
|
|
276
|
+
|
|
277
|
+
expect(wrapper.emitted('navigate')?.[0]?.[0]).toMatchObject({
|
|
278
|
+
mount_id: 'qe:2026-07',
|
|
279
|
+
path: '',
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it('should not navigate to an unavailable mount', async () => {
|
|
284
|
+
const wrapper = mountBrowser()
|
|
285
|
+
await wrapper.findAll('.mint-file-browser__mount')[3].trigger('click')
|
|
286
|
+
|
|
287
|
+
expect(wrapper.emitted('navigate')).toBeUndefined()
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it('should emit search updates', async () => {
|
|
291
|
+
const wrapper = mountBrowser()
|
|
292
|
+
await wrapper.find('input[aria-label="Search this folder"]').setValue('hela')
|
|
293
|
+
|
|
294
|
+
expect(wrapper.emitted('update:search')?.[0]?.[0]).toBe('hela')
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('should show an empty state when the directory has no entries', () => {
|
|
298
|
+
expect(mountBrowser({ entries: [] }).text()).toContain('This folder is empty')
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('should show a loading skeleton while listing', () => {
|
|
302
|
+
expect(mountBrowser({ loading: true }).find('.mint-file-browser__skeleton').exists()).toBe(true)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('should surface a listing error', () => {
|
|
306
|
+
expect(mountBrowser({ error: 'Mount is offline' }).text()).toContain('Mount is offline')
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('should state that mounts are read-only', () => {
|
|
310
|
+
expect(mountBrowser().text()).toContain('Read-only · Select paths, not copies')
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
describe('FileBrowserModal review fixes', () => {
|
|
315
|
+
// A search filters server-side while totalCount stays the whole directory.
|
|
316
|
+
it('should say how many rows are shown when a search narrows the listing', () => {
|
|
317
|
+
const wrapper = mount(FileBrowserModal, {
|
|
318
|
+
props: {
|
|
319
|
+
modelValue: true,
|
|
320
|
+
mounts: MOUNTS,
|
|
321
|
+
mountId: 'qe',
|
|
322
|
+
path: 'raw',
|
|
323
|
+
entries: ENTRIES,
|
|
324
|
+
totalCount: 1284,
|
|
325
|
+
},
|
|
326
|
+
global: { stubs: { teleport: true } },
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
expect(wrapper.text()).toContain('4 shown')
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
it('should not claim rows are hidden when nothing is filtered', () => {
|
|
333
|
+
const wrapper = mount(FileBrowserModal, {
|
|
334
|
+
props: {
|
|
335
|
+
modelValue: true,
|
|
336
|
+
mounts: MOUNTS,
|
|
337
|
+
mountId: 'qe',
|
|
338
|
+
path: 'raw',
|
|
339
|
+
entries: ENTRIES,
|
|
340
|
+
totalCount: 4,
|
|
341
|
+
},
|
|
342
|
+
global: { stubs: { teleport: true } },
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
expect(wrapper.text()).not.toContain('shown')
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
it('should report a capped listing', () => {
|
|
349
|
+
const wrapper = mount(FileBrowserModal, {
|
|
350
|
+
props: {
|
|
351
|
+
modelValue: true,
|
|
352
|
+
mounts: MOUNTS,
|
|
353
|
+
mountId: 'qe',
|
|
354
|
+
path: 'raw',
|
|
355
|
+
entries: ENTRIES,
|
|
356
|
+
totalCount: 4,
|
|
357
|
+
truncated: true,
|
|
358
|
+
},
|
|
359
|
+
global: { stubs: { teleport: true } },
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
expect(wrapper.text()).toContain('listing capped')
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('should label a folder count as items rather than files', () => {
|
|
366
|
+
const wrapper = mount(FileBrowserModal, {
|
|
367
|
+
props: { modelValue: true, mounts: MOUNTS, mountId: 'qe', path: 'raw', entries: ENTRIES },
|
|
368
|
+
global: { stubs: { teleport: true } },
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
expect(wrapper.text()).toContain('2 items')
|
|
372
|
+
})
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
describe('FileBrowserModal polish', () => {
|
|
376
|
+
it.each([{ loading: true }, { error: 'Offline' }, { mountId: '' }])(
|
|
377
|
+
'disables current-folder selection without a ready listing: %o',
|
|
378
|
+
(props) => {
|
|
379
|
+
const wrapper = mountBrowser(props)
|
|
380
|
+
const button = wrapper.get('.mint-file-browser__select-folder')
|
|
381
|
+
expect(button.attributes('disabled')).toBeDefined()
|
|
382
|
+
},
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
it('distinguishes no configured mounts from an empty folder', () => {
|
|
386
|
+
const wrapper = mountBrowser({ mounts: [], mountId: '', entries: [] })
|
|
387
|
+
expect(wrapper.text()).toContain('No server mounts configured')
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it('offers a retry after a browse failure', async () => {
|
|
391
|
+
const wrapper = mountBrowser({ error: 'NAS offline' })
|
|
392
|
+
await wrapper.get('button[aria-label="Retry folder listing"]').trigger('click')
|
|
393
|
+
expect(wrapper.emitted('refresh')).toHaveLength(1)
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
it('keeps selections with separator characters in their mount IDs distinct', async () => {
|
|
397
|
+
const wrapper = mountBrowser({
|
|
398
|
+
mountId: 'a',
|
|
399
|
+
entries: [{ name: 'x', path: 'b::c', kind: 'file' }],
|
|
400
|
+
selected: [{ mount_id: 'a::b', path: 'c', name: 'c', kind: 'file' }],
|
|
401
|
+
})
|
|
402
|
+
await wrapper.get('input[type="checkbox"]').setValue(true)
|
|
403
|
+
expect(wrapper.emitted('update:selected')?.[0]?.[0]).toHaveLength(2)
|
|
404
|
+
})
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
describe('FileBrowserModal dialog workspace', () => {
|
|
408
|
+
it('uses SDK actions and an accessible independently labelled dialog', () => {
|
|
409
|
+
const wrapper = mountBrowser({ title: 'Choose instrument files' })
|
|
410
|
+
expect(wrapper.get('[role="dialog"]').attributes('aria-label')).toBe('Choose instrument files')
|
|
411
|
+
expect(wrapper.get('.mint-file-browser__confirm').classes()).toContain('mint-button--primary')
|
|
412
|
+
expect(wrapper.get('.mint-file-browser__cancel').classes()).toContain('mint-button--secondary')
|
|
413
|
+
expect(wrapper.get('[aria-label="Go to parent folder"]').classes()).toContain(
|
|
414
|
+
'mint-icon-button',
|
|
415
|
+
)
|
|
416
|
+
expect(wrapper.get('[aria-label="Search this folder"]').classes()).toContain('mint-input')
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
it('focuses an initially open dialog, traps Tab and restores focus and scroll on close', async () => {
|
|
420
|
+
const launcher = document.createElement('button')
|
|
421
|
+
document.body.append(launcher)
|
|
422
|
+
launcher.focus()
|
|
423
|
+
document.body.style.overflow = 'scroll'
|
|
424
|
+
const wrapper = mount(FileBrowserModal, {
|
|
425
|
+
attachTo: document.body,
|
|
426
|
+
props: { modelValue: true, showLocalPicker: false },
|
|
427
|
+
global: { stubs: { teleport: true } },
|
|
428
|
+
})
|
|
429
|
+
try {
|
|
430
|
+
await nextTick()
|
|
431
|
+
const close = wrapper.get('[aria-label="Close modal"]')
|
|
432
|
+
expect(document.activeElement).toBe(close.element)
|
|
433
|
+
expect(document.body.style.overflow).toBe('hidden')
|
|
434
|
+
await close.trigger('keydown', { key: 'Tab', shiftKey: true })
|
|
435
|
+
expect(document.activeElement).toBe(wrapper.get('.mint-file-browser__cancel').element)
|
|
436
|
+
await wrapper.get('.mint-file-browser__cancel').trigger('keydown', { key: 'Tab' })
|
|
437
|
+
expect(document.activeElement).toBe(close.element)
|
|
438
|
+
await close.trigger('keydown', { key: 'Escape' })
|
|
439
|
+
expect(wrapper.emitted('cancel')).toHaveLength(1)
|
|
440
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([false])
|
|
441
|
+
await wrapper.setProps({ modelValue: false })
|
|
442
|
+
expect(document.activeElement).toBe(launcher)
|
|
443
|
+
expect(document.body.style.overflow).toBe('scroll')
|
|
444
|
+
} finally {
|
|
445
|
+
wrapper.unmount()
|
|
446
|
+
launcher.remove()
|
|
447
|
+
document.body.style.overflow = ''
|
|
448
|
+
}
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
it('restores the existing scroll setting when unmounted while open', async () => {
|
|
452
|
+
document.body.style.overflow = 'auto'
|
|
453
|
+
const wrapper = mountBrowser()
|
|
454
|
+
await nextTick()
|
|
455
|
+
expect(document.body.style.overflow).toBe('hidden')
|
|
456
|
+
wrapper.unmount()
|
|
457
|
+
expect(document.body.style.overflow).toBe('auto')
|
|
458
|
+
document.body.style.overflow = ''
|
|
459
|
+
})
|
|
460
|
+
})
|