@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
|
@@ -3,6 +3,25 @@ import { describe, expect, it } from 'vitest'
|
|
|
3
3
|
import SegmentedControl from '../../components/SegmentedControl.vue'
|
|
4
4
|
|
|
5
5
|
describe('SegmentedControl', () => {
|
|
6
|
+
it('defaults to card while allowing an explicit simple variant', async () => {
|
|
7
|
+
const wrapper = mount(SegmentedControl, {
|
|
8
|
+
props: {
|
|
9
|
+
modelValue: 'table',
|
|
10
|
+
options: [{ value: 'table', label: 'Table', description: 'View rows' }],
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
expect(wrapper.classes()).toContain('mint-segmented-control--card')
|
|
15
|
+
expect(wrapper.get('button').classes()).toContain('mint-segmented-control__option--card')
|
|
16
|
+
expect(wrapper.get('.mint-segmented-control__description').text()).toBe('View rows')
|
|
17
|
+
|
|
18
|
+
await wrapper.setProps({ variant: 'simple' })
|
|
19
|
+
|
|
20
|
+
expect(wrapper.classes()).toContain('mint-segmented-control--simple')
|
|
21
|
+
expect(wrapper.get('button').classes()).toContain('mint-segmented-control__option--simple')
|
|
22
|
+
expect(wrapper.find('.mint-segmented-control__description').exists()).toBe(false)
|
|
23
|
+
})
|
|
24
|
+
|
|
6
25
|
it('accepts string and number shorthand options', async () => {
|
|
7
26
|
const wrapper = mount(SegmentedControl, {
|
|
8
27
|
props: {
|
|
@@ -66,4 +85,92 @@ describe('SegmentedControl', () => {
|
|
|
66
85
|
|
|
67
86
|
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
|
|
68
87
|
})
|
|
88
|
+
|
|
89
|
+
describe('roving tabindex', () => {
|
|
90
|
+
const options = [
|
|
91
|
+
{ value: 'table', label: 'Table' },
|
|
92
|
+
{ value: 'chart', label: 'Chart' },
|
|
93
|
+
{ value: 'raw', label: 'Raw' },
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
it('makes only the checked option tabbable', () => {
|
|
97
|
+
const wrapper = mount(SegmentedControl, {
|
|
98
|
+
props: { modelValue: 'chart', options },
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
expect(wrapper.findAll('button').map(button => button.attributes('tabindex')))
|
|
102
|
+
.toEqual(['-1', '0', '-1'])
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('falls back to the first selectable option when nothing matches', () => {
|
|
106
|
+
const wrapper = mount(SegmentedControl, {
|
|
107
|
+
props: { modelValue: 'missing', options },
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
expect(wrapper.findAll('button').map(button => button.attributes('tabindex')))
|
|
111
|
+
.toEqual(['0', '-1', '-1'])
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('activates the adjacent option with the arrow keys, wrapping at the ends', async () => {
|
|
115
|
+
const wrapper = mount(SegmentedControl, {
|
|
116
|
+
props: { modelValue: 'chart', options },
|
|
117
|
+
attachTo: document.body,
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
const buttons = wrapper.findAll('button')
|
|
121
|
+
await buttons[1].trigger('keydown', { key: 'ArrowRight' })
|
|
122
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['raw'])
|
|
123
|
+
|
|
124
|
+
await buttons[1].trigger('keydown', { key: 'ArrowLeft' })
|
|
125
|
+
expect(wrapper.emitted('update:modelValue')?.[1]).toEqual(['table'])
|
|
126
|
+
|
|
127
|
+
await wrapper.setProps({ modelValue: 'raw' })
|
|
128
|
+
await wrapper.findAll('button')[2].trigger('keydown', { key: 'ArrowRight' })
|
|
129
|
+
expect(wrapper.emitted('update:modelValue')?.[2]).toEqual(['table'])
|
|
130
|
+
|
|
131
|
+
wrapper.unmount()
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('jumps to the first and last option with Home and End', async () => {
|
|
135
|
+
const wrapper = mount(SegmentedControl, {
|
|
136
|
+
props: { modelValue: 'chart', options },
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const buttons = wrapper.findAll('button')
|
|
140
|
+
await buttons[1].trigger('keydown', { key: 'Home' })
|
|
141
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['table'])
|
|
142
|
+
|
|
143
|
+
await buttons[1].trigger('keydown', { key: 'End' })
|
|
144
|
+
expect(wrapper.emitted('update:modelValue')?.[1]).toEqual(['raw'])
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('skips disabled options during arrow navigation', async () => {
|
|
148
|
+
const wrapper = mount(SegmentedControl, {
|
|
149
|
+
props: {
|
|
150
|
+
modelValue: 'table',
|
|
151
|
+
options,
|
|
152
|
+
disabledValues: ['chart'],
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
await wrapper.findAll('button')[0].trigger('keydown', { key: 'ArrowRight' })
|
|
157
|
+
|
|
158
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['raw'])
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('moves focus onto the newly activated option', async () => {
|
|
162
|
+
const wrapper = mount(SegmentedControl, {
|
|
163
|
+
props: { modelValue: 'table', options },
|
|
164
|
+
attachTo: document.body,
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
await wrapper.findAll('button')[0].trigger('keydown', { key: 'ArrowRight' })
|
|
168
|
+
await wrapper.setProps({ modelValue: 'chart' })
|
|
169
|
+
await wrapper.vm.$nextTick()
|
|
170
|
+
|
|
171
|
+
expect(document.activeElement).toBe(wrapper.findAll('button')[1].element)
|
|
172
|
+
|
|
173
|
+
wrapper.unmount()
|
|
174
|
+
})
|
|
175
|
+
})
|
|
69
176
|
})
|
|
@@ -35,4 +35,65 @@ describe('TimePicker', () => {
|
|
|
35
35
|
|
|
36
36
|
wrapper.unmount()
|
|
37
37
|
})
|
|
38
|
+
|
|
39
|
+
it('publishes the highlighted slot through aria-activedescendant', async () => {
|
|
40
|
+
const wrapper = mount(TimePicker, {
|
|
41
|
+
props: { modelValue: '14:30' },
|
|
42
|
+
attachTo: document.body,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const input = wrapper.get('input')
|
|
46
|
+
expect(input.attributes('aria-activedescendant')).toBeUndefined()
|
|
47
|
+
|
|
48
|
+
await wrapper.get('.mint-time-picker').trigger('keydown', { key: 'ArrowDown' })
|
|
49
|
+
await wrapper.vm.$nextTick()
|
|
50
|
+
|
|
51
|
+
const listbox = wrapper.get('.mint-time-picker__list')
|
|
52
|
+
expect(input.attributes('aria-controls')).toBe(listbox.attributes('id'))
|
|
53
|
+
|
|
54
|
+
const activeId = input.attributes('aria-activedescendant')
|
|
55
|
+
expect(activeId).toBeDefined()
|
|
56
|
+
expect(wrapper.find(`#${activeId}`).exists()).toBe(true)
|
|
57
|
+
|
|
58
|
+
wrapper.unmount()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('jumps to the first and last selectable slot with Home and End', async () => {
|
|
62
|
+
const wrapper = mount(TimePicker, {
|
|
63
|
+
props: { min: '09:00', max: '10:00', step: 30 },
|
|
64
|
+
attachTo: document.body,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const root = wrapper.get('.mint-time-picker')
|
|
68
|
+
await root.trigger('keydown', { key: 'ArrowDown' })
|
|
69
|
+
await root.trigger('keydown', { key: 'End' })
|
|
70
|
+
await wrapper.vm.$nextTick()
|
|
71
|
+
|
|
72
|
+
const slots = wrapper.findAll('.mint-time-picker__slot')
|
|
73
|
+
expect(slots.at(-1)?.classes()).toContain('mint-time-picker__slot--highlighted')
|
|
74
|
+
|
|
75
|
+
await root.trigger('keydown', { key: 'Home' })
|
|
76
|
+
await wrapper.vm.$nextTick()
|
|
77
|
+
expect(wrapper.findAll('.mint-time-picker__slot')[0].classes())
|
|
78
|
+
.toContain('mint-time-picker__slot--highlighted')
|
|
79
|
+
|
|
80
|
+
wrapper.unmount()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('returns focus to the input when Escape closes the list', async () => {
|
|
84
|
+
const wrapper = mount(TimePicker, { attachTo: document.body })
|
|
85
|
+
const input = wrapper.get<HTMLInputElement>('input').element
|
|
86
|
+
|
|
87
|
+
await wrapper.get('.mint-time-picker').trigger('keydown', { key: 'ArrowDown' })
|
|
88
|
+
expect(wrapper.find('.mint-time-picker__dropdown').exists()).toBe(true)
|
|
89
|
+
|
|
90
|
+
await wrapper.get('.mint-time-picker').trigger('keydown', { key: 'Escape' })
|
|
91
|
+
await wrapper.vm.$nextTick()
|
|
92
|
+
await wrapper.vm.$nextTick()
|
|
93
|
+
|
|
94
|
+
expect(wrapper.find('.mint-time-picker__dropdown').exists()).toBe(false)
|
|
95
|
+
expect(document.activeElement).toBe(input)
|
|
96
|
+
|
|
97
|
+
wrapper.unmount()
|
|
98
|
+
})
|
|
38
99
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { nextTick } from 'vue'
|
|
2
3
|
import {
|
|
3
4
|
nextClickedWellSelection,
|
|
4
5
|
useWellPlateInteraction,
|
|
@@ -41,6 +42,14 @@ function dragEvent(data: Record<string, string> = {}): DragEvent {
|
|
|
41
42
|
} as unknown as DragEvent
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
function keyEvent(key: string, altKey = true): KeyboardEvent {
|
|
46
|
+
return {
|
|
47
|
+
key,
|
|
48
|
+
altKey,
|
|
49
|
+
preventDefault: vi.fn(),
|
|
50
|
+
} as unknown as KeyboardEvent
|
|
51
|
+
}
|
|
52
|
+
|
|
44
53
|
function createInteraction(overrides: Partial<Parameters<typeof useWellPlateInteraction>[0]> = {}) {
|
|
45
54
|
const emitted: Record<string, unknown[]> = {}
|
|
46
55
|
const selected = { value: [] as string[] }
|
|
@@ -168,4 +177,54 @@ describe('WellPlate interaction helpers', () => {
|
|
|
168
177
|
['A1', 'A2', 'B1', 'B2'],
|
|
169
178
|
])
|
|
170
179
|
})
|
|
180
|
+
|
|
181
|
+
it('moves a well with the keyboard pick-up/drop chord in drag mode', async () => {
|
|
182
|
+
const { emitted, interaction, selectionMode } = createInteraction()
|
|
183
|
+
selectionMode.value = 'drag'
|
|
184
|
+
const source = well({ id: 'A1', row: 0, col: 0, sampleType: 'sample' })
|
|
185
|
+
const target = well({ id: 'B2', row: 1, col: 1 })
|
|
186
|
+
|
|
187
|
+
interaction.handleWellMoveKeydown(source, keyEvent('Enter'))
|
|
188
|
+
expect(interaction.dragSourceWell.value).toBe('A1')
|
|
189
|
+
|
|
190
|
+
interaction.handleWellMoveKeydown(target, keyEvent('Enter'))
|
|
191
|
+
|
|
192
|
+
expect(emitted.move).toEqual([['A1', 'B2']])
|
|
193
|
+
expect(interaction.dragSourceWell.value).toBeNull()
|
|
194
|
+
|
|
195
|
+
await nextTick()
|
|
196
|
+
expect(interaction.moveAnnouncement.value).toBe('Well A1 moved to B2.')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('cancels a keyboard pick-up with Escape and refuses empty wells', () => {
|
|
200
|
+
const { emitted, interaction, selectionMode } = createInteraction()
|
|
201
|
+
selectionMode.value = 'drag'
|
|
202
|
+
|
|
203
|
+
interaction.handleWellMoveKeydown(well({ id: 'A2', row: 0, col: 1 }), keyEvent('Enter'))
|
|
204
|
+
expect(interaction.dragSourceWell.value).toBeNull()
|
|
205
|
+
|
|
206
|
+
interaction.handleWellMoveKeydown(
|
|
207
|
+
well({ id: 'A1', row: 0, col: 0, sampleType: 'sample' }),
|
|
208
|
+
keyEvent('Enter'),
|
|
209
|
+
)
|
|
210
|
+
expect(interaction.dragSourceWell.value).toBe('A1')
|
|
211
|
+
|
|
212
|
+
interaction.handleWellMoveKeydown(
|
|
213
|
+
well({ id: 'A1', row: 0, col: 0, sampleType: 'sample' }),
|
|
214
|
+
keyEvent('Escape', false),
|
|
215
|
+
)
|
|
216
|
+
expect(interaction.dragSourceWell.value).toBeNull()
|
|
217
|
+
expect(emitted.move).toBeUndefined()
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('ignores the move chord outside drag selection mode', () => {
|
|
221
|
+
const { interaction } = createInteraction()
|
|
222
|
+
|
|
223
|
+
interaction.handleWellMoveKeydown(
|
|
224
|
+
well({ id: 'A1', row: 0, col: 0, sampleType: 'sample' }),
|
|
225
|
+
keyEvent('Enter'),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
expect(interaction.dragSourceWell.value).toBeNull()
|
|
229
|
+
})
|
|
171
230
|
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
|
|
5
|
+
// jsdom has no layout, so this pins the stylesheet contract instead: a bare
|
|
6
|
+
// <svg> passed to IconButton's slot must be sized by the icon box, otherwise it
|
|
7
|
+
// collapses to zero width as a flex item (FilePicker source toggles, beta.14).
|
|
8
|
+
describe('IconButton icon sizing', () => {
|
|
9
|
+
it('should size a bare slot svg to the icon box when the caller passes no dimensions', () => {
|
|
10
|
+
const css = readFileSync(
|
|
11
|
+
resolve(__dirname, '../../styles/components/icon-button.css'),
|
|
12
|
+
'utf8',
|
|
13
|
+
)
|
|
14
|
+
expect(css).toMatch(
|
|
15
|
+
/\.mint-icon-button__icon--sm > svg,\s*\.mint-icon-button__icon--md > svg,\s*\.mint-icon-button__icon--lg > svg \{\s*width: 100%;\s*height: 100%;\s*\}/,
|
|
16
|
+
)
|
|
17
|
+
})
|
|
18
|
+
})
|
|
@@ -3,6 +3,7 @@ import { createPinia, setActivePinia } from 'pinia'
|
|
|
3
3
|
import { AxiosError, type AxiosRequestConfig, type InternalAxiosRequestConfig } from 'axios'
|
|
4
4
|
|
|
5
5
|
import { AuthenticationRequiredError, useApi } from '../../composables/useApi'
|
|
6
|
+
import { MintApiError } from '../../apiErrors'
|
|
6
7
|
import { useAuthStore } from '../../stores/auth'
|
|
7
8
|
|
|
8
9
|
function readAuthorizationHeader(headers: InternalAxiosRequestConfig['headers']): unknown {
|
|
@@ -32,6 +33,7 @@ function unauthorizedAdapter(config: AxiosRequestConfig): Promise<never> {
|
|
|
32
33
|
describe('useApi URL helpers', () => {
|
|
33
34
|
beforeEach(() => {
|
|
34
35
|
setActivePinia(createPinia())
|
|
36
|
+
useAuthStore().clearToken()
|
|
35
37
|
})
|
|
36
38
|
|
|
37
39
|
afterEach(() => {
|
|
@@ -98,25 +100,35 @@ describe('useApi URL helpers', () => {
|
|
|
98
100
|
expect(authorization).toBeUndefined()
|
|
99
101
|
})
|
|
100
102
|
|
|
101
|
-
it('normalizes authenticated 401 responses and clears the rejected token', async () => {
|
|
103
|
+
it.each([false, true])('normalizes authenticated 401 responses and clears the rejected token (typedErrors=%s)', async (typedErrors) => {
|
|
102
104
|
const authStore = useAuthStore()
|
|
103
105
|
authStore.setToken('expired-token', 3600)
|
|
104
|
-
const api = useApi({ baseUrl: '/api' })
|
|
106
|
+
const api = useApi({ baseUrl: '/api', typedErrors })
|
|
105
107
|
|
|
106
108
|
const adapter = vi.fn(unauthorizedAdapter)
|
|
107
109
|
const error = await api.get('/protected', { adapter }).catch((reason: unknown) => reason)
|
|
108
110
|
|
|
109
|
-
expect({
|
|
111
|
+
expect({
|
|
112
|
+
attempts: adapter.mock.calls.length,
|
|
113
|
+
errorType: error?.constructor,
|
|
114
|
+
typed: error instanceof MintApiError,
|
|
115
|
+
status: (error as MintApiError).status,
|
|
116
|
+
code: (error as MintApiError).code,
|
|
117
|
+
token: authStore.token,
|
|
118
|
+
}).toEqual({
|
|
110
119
|
attempts: 1,
|
|
111
120
|
errorType: AuthenticationRequiredError,
|
|
121
|
+
typed: true,
|
|
122
|
+
status: 401,
|
|
123
|
+
code: 'AUTHENTICATION_REQUIRED',
|
|
112
124
|
token: null,
|
|
113
125
|
})
|
|
114
126
|
})
|
|
115
127
|
|
|
116
|
-
it('does not clear auth state for public requests that return 401', async () => {
|
|
128
|
+
it.each([false, true])('does not clear auth state for public requests that return 401 (typedErrors=%s)', async (typedErrors) => {
|
|
117
129
|
const authStore = useAuthStore()
|
|
118
130
|
authStore.setToken('still-valid-token', 3600)
|
|
119
|
-
const api = useApi({ baseUrl: '/api', withAuth: false })
|
|
131
|
+
const api = useApi({ baseUrl: '/api', typedErrors, withAuth: false })
|
|
120
132
|
|
|
121
133
|
const error = await api.get('/public', { adapter: unauthorizedAdapter }).catch((reason: unknown) => reason)
|
|
122
134
|
|
|
@@ -126,10 +138,10 @@ describe('useApi URL helpers', () => {
|
|
|
126
138
|
})
|
|
127
139
|
})
|
|
128
140
|
|
|
129
|
-
it('does not clear a replacement token after an older request returns 401', async () => {
|
|
141
|
+
it.each([false, true])('does not clear a replacement token after an older request returns 401 (typedErrors=%s)', async (typedErrors) => {
|
|
130
142
|
const authStore = useAuthStore()
|
|
131
143
|
authStore.setToken('rejected-token', 3600)
|
|
132
|
-
const api = useApi({ baseUrl: '/api' })
|
|
144
|
+
const api = useApi({ baseUrl: '/api', typedErrors })
|
|
133
145
|
|
|
134
146
|
await api.get('/protected', {
|
|
135
147
|
adapter: async (config) => {
|
|
@@ -141,9 +153,10 @@ describe('useApi URL helpers', () => {
|
|
|
141
153
|
expect(authStore.token).toBe('replacement-token')
|
|
142
154
|
})
|
|
143
155
|
|
|
144
|
-
it('does not clear a token created while an unauthenticated request is in flight', async () => {
|
|
156
|
+
it.each([false, true])('does not clear a token created while an unauthenticated request is in flight (typedErrors=%s)', async (typedErrors) => {
|
|
145
157
|
const authStore = useAuthStore()
|
|
146
|
-
const api = useApi({ baseUrl: '/api' })
|
|
158
|
+
const api = useApi({ baseUrl: '/api', typedErrors })
|
|
159
|
+
expect(authStore.token).toBeNull()
|
|
147
160
|
|
|
148
161
|
await api.get('/protected', {
|
|
149
162
|
adapter: async (config) => {
|
|
@@ -154,4 +167,74 @@ describe('useApi URL helpers', () => {
|
|
|
154
167
|
|
|
155
168
|
expect(authStore.token).toBe('new-login-token')
|
|
156
169
|
})
|
|
170
|
+
|
|
171
|
+
it('normalizes a typed response when typed errors are enabled', async () => {
|
|
172
|
+
const api = useApi({ baseUrl: '/api', typedErrors: true })
|
|
173
|
+
const adapter = vi.fn(async (config: AxiosRequestConfig) => Promise.reject(new AxiosError(
|
|
174
|
+
'Not Found',
|
|
175
|
+
AxiosError.ERR_BAD_REQUEST,
|
|
176
|
+
config as InternalAxiosRequestConfig,
|
|
177
|
+
undefined,
|
|
178
|
+
{
|
|
179
|
+
data: {
|
|
180
|
+
code: 'leaf.ARTIFACT_NOT_FOUND',
|
|
181
|
+
message: 'Artifact is not available',
|
|
182
|
+
status: 404,
|
|
183
|
+
request_id: 'req-generated',
|
|
184
|
+
details: { artifact_id: 67 },
|
|
185
|
+
detail: 'Artifact is not available',
|
|
186
|
+
},
|
|
187
|
+
status: 404,
|
|
188
|
+
statusText: 'Not Found',
|
|
189
|
+
headers: { 'x-request-id': 'req-generated' },
|
|
190
|
+
config: config as InternalAxiosRequestConfig,
|
|
191
|
+
},
|
|
192
|
+
)))
|
|
193
|
+
|
|
194
|
+
const error = await api.get('/artifacts/67', { adapter }).catch((reason: unknown) => reason)
|
|
195
|
+
|
|
196
|
+
const typedError = error as MintApiError
|
|
197
|
+
expect({ attempts: adapter.mock.calls.length, type: typedError.constructor, code: typedError.code }).toEqual({
|
|
198
|
+
attempts: 1,
|
|
199
|
+
type: MintApiError,
|
|
200
|
+
code: 'leaf.ARTIFACT_NOT_FOUND',
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it.each([true, false])('normalizes Blob download errors (JSON=%s)', async (json) => {
|
|
205
|
+
const api = useApi({ typedErrors: true })
|
|
206
|
+
const body = { code: 'artifact.unavailable', message: 'No artifact', details: { id: 67 } }
|
|
207
|
+
const blob = new Blob([json ? JSON.stringify(body) : 'Bad gateway'], {
|
|
208
|
+
type: json ? 'application/json' : 'text/plain',
|
|
209
|
+
})
|
|
210
|
+
let cause: AxiosError | undefined
|
|
211
|
+
const error = await api.get('/download', {
|
|
212
|
+
responseType: 'blob',
|
|
213
|
+
adapter: async (config) => {
|
|
214
|
+
cause = new AxiosError('Bad gateway', AxiosError.ERR_BAD_RESPONSE, config, undefined, {
|
|
215
|
+
data: blob, status: 502, statusText: 'Bad gateway',
|
|
216
|
+
headers: { 'x-request-id': 'blob-request' }, config,
|
|
217
|
+
})
|
|
218
|
+
throw cause
|
|
219
|
+
},
|
|
220
|
+
}).catch((reason: unknown) => reason)
|
|
221
|
+
|
|
222
|
+
expect(error).toBeInstanceOf(MintApiError)
|
|
223
|
+
expect(error).toMatchObject({
|
|
224
|
+
code: json ? body.code : 'unknown',
|
|
225
|
+
message: json ? body.message : 'Bad gateway',
|
|
226
|
+
status: 502, requestId: 'blob-request', cause,
|
|
227
|
+
})
|
|
228
|
+
expect((error as MintApiError).body).toEqual(json ? body : blob)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('leaves cancellations and network failures unchanged in typed mode', async () => {
|
|
232
|
+
const api = useApi({ typedErrors: true })
|
|
233
|
+
const cause = new AxiosError('Offline', AxiosError.ERR_NETWORK)
|
|
234
|
+
await expect(api.get('/offline', { adapter: async () => { throw cause } })).rejects.toBe(cause)
|
|
235
|
+
const controller = new AbortController()
|
|
236
|
+
controller.abort()
|
|
237
|
+
await expect(api.get('/cancelled', { signal: controller.signal })).rejects.toMatchObject({ code: 'ERR_CANCELED' })
|
|
238
|
+
})
|
|
239
|
+
|
|
157
240
|
})
|
|
@@ -14,7 +14,7 @@ function mountHarness(options: UseDropdownStateOptions = {}) {
|
|
|
14
14
|
state = useDropdownState(options)
|
|
15
15
|
return state
|
|
16
16
|
},
|
|
17
|
-
template: '<div ref="rootRef" class="dropdown-root"><button class="inside">Inside</button></div>',
|
|
17
|
+
template: '<div ref="rootRef" class="dropdown-root"><button ref="triggerRef" class="trigger">Trigger</button><button class="inside">Inside</button></div>',
|
|
18
18
|
}), { attachTo: document.body })
|
|
19
19
|
|
|
20
20
|
if (!state) {
|
|
@@ -78,6 +78,63 @@ describe('useDropdownState', () => {
|
|
|
78
78
|
wrapper.unmount()
|
|
79
79
|
})
|
|
80
80
|
|
|
81
|
+
it('returns focus to the trigger only when asked', async () => {
|
|
82
|
+
const { wrapper, state } = mountHarness()
|
|
83
|
+
const trigger = wrapper.get<HTMLButtonElement>('.trigger').element
|
|
84
|
+
const inside = wrapper.get<HTMLButtonElement>('.inside').element
|
|
85
|
+
|
|
86
|
+
state.open()
|
|
87
|
+
inside.focus()
|
|
88
|
+
state.close()
|
|
89
|
+
await nextTick()
|
|
90
|
+
expect(document.activeElement).toBe(inside)
|
|
91
|
+
|
|
92
|
+
state.open()
|
|
93
|
+
inside.focus()
|
|
94
|
+
state.close(true)
|
|
95
|
+
await nextTick()
|
|
96
|
+
expect(document.activeElement).toBe(trigger)
|
|
97
|
+
|
|
98
|
+
wrapper.unmount()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('returns focus to the trigger when Escape closes the dropdown', async () => {
|
|
102
|
+
const { wrapper, state } = mountHarness()
|
|
103
|
+
const trigger = wrapper.get<HTMLButtonElement>('.trigger').element
|
|
104
|
+
|
|
105
|
+
state.open()
|
|
106
|
+
wrapper.get<HTMLButtonElement>('.inside').element.focus()
|
|
107
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
|
|
108
|
+
await nextTick()
|
|
109
|
+
await nextTick()
|
|
110
|
+
|
|
111
|
+
expect(state.isOpen.value).toBe(false)
|
|
112
|
+
expect(document.activeElement).toBe(trigger)
|
|
113
|
+
|
|
114
|
+
wrapper.unmount()
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('closes on scroll and resize only when closeOnScroll is set', async () => {
|
|
118
|
+
const plain = mountHarness()
|
|
119
|
+
plain.state.open()
|
|
120
|
+
window.dispatchEvent(new Event('scroll'))
|
|
121
|
+
await nextTick()
|
|
122
|
+
expect(plain.state.isOpen.value).toBe(true)
|
|
123
|
+
plain.wrapper.unmount()
|
|
124
|
+
|
|
125
|
+
const scrolling = mountHarness({ closeOnScroll: true })
|
|
126
|
+
scrolling.state.open()
|
|
127
|
+
window.dispatchEvent(new Event('scroll'))
|
|
128
|
+
await nextTick()
|
|
129
|
+
expect(scrolling.state.isOpen.value).toBe(false)
|
|
130
|
+
|
|
131
|
+
scrolling.state.open()
|
|
132
|
+
window.dispatchEvent(new Event('resize'))
|
|
133
|
+
await nextTick()
|
|
134
|
+
expect(scrolling.state.isOpen.value).toBe(false)
|
|
135
|
+
scrolling.wrapper.unmount()
|
|
136
|
+
})
|
|
137
|
+
|
|
81
138
|
it('can opt out of outside-click and Escape closing', async () => {
|
|
82
139
|
const { wrapper, state } = mountHarness({
|
|
83
140
|
closeOnOutsideClick: false,
|