@morscherlab/mint-sdk 1.0.0-alpha.2
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 +326 -0
- package/dist/__stories__/experiment-helpers.d.ts +25 -0
- package/dist/__tests__/components/AppLayout.test.d.ts +1 -0
- package/dist/__tests__/components/AppSidebar.test.d.ts +1 -0
- package/dist/__tests__/components/AppTopBar.test.d.ts +1 -0
- package/dist/__tests__/components/BaseInput.test.d.ts +1 -0
- package/dist/__tests__/components/BasePill.test.d.ts +1 -0
- package/dist/__tests__/components/Calendar.test.d.ts +1 -0
- package/dist/__tests__/components/CollapsibleCard.test.d.ts +1 -0
- package/dist/__tests__/components/DataFrame.test.d.ts +1 -0
- package/dist/__tests__/components/DropdownButton.test.d.ts +1 -0
- package/dist/__tests__/composables/formBuilderRegistry.test.d.ts +1 -0
- package/dist/__tests__/composables/useAppExperiment.test.d.ts +1 -0
- package/dist/__tests__/composables/useAuth.test.d.ts +1 -0
- package/dist/__tests__/composables/useAutoGroup.test.d.ts +1 -0
- package/dist/__tests__/composables/useExperimentData.test.d.ts +13 -0
- package/dist/__tests__/composables/useExperimentSave.test.d.ts +1 -0
- package/dist/__tests__/composables/useForm.test.d.ts +1 -0
- package/dist/__tests__/composables/useFormBuilder.test.d.ts +1 -0
- package/dist/__tests__/composables/usePlatformContext.test.d.ts +1 -0
- package/dist/__tests__/composables/usePluginApi.test.d.ts +13 -0
- package/dist/__tests__/composables/usePluginConfig.test.d.ts +14 -0
- package/dist/__tests__/utils/color.test.d.ts +1 -0
- package/dist/auth-BYmxZdJl.js +297 -0
- package/dist/auth-BYmxZdJl.js.map +1 -0
- package/dist/components/AlertBox.vue.d.ts +34 -0
- package/dist/components/AppAvatarMenu.vue.d.ts +58 -0
- package/dist/components/AppContainer.vue.d.ts +28 -0
- package/dist/components/AppLayout.vue.d.ts +31 -0
- package/dist/components/AppPageSelector.vue.d.ts +43 -0
- package/dist/components/AppPillNav.vue.d.ts +11 -0
- package/dist/components/AppPluginSwitcher.vue.d.ts +38 -0
- package/dist/components/AppSidebar.vue.d.ts +47 -0
- package/dist/components/AppTopBar.vue.d.ts +111 -0
- package/dist/components/AuditTrail.vue.d.ts +38 -0
- package/dist/components/AutoGroupModal.vue.d.ts +124 -0
- package/dist/components/Avatar.vue.d.ts +14 -0
- package/dist/components/BaseButton.vue.d.ts +37 -0
- package/dist/components/BaseCheckbox.vue.d.ts +17 -0
- package/dist/components/BaseInput.vue.d.ts +34 -0
- package/dist/components/BaseModal.vue.d.ts +46 -0
- package/dist/components/BasePill.vue.d.ts +57 -0
- package/dist/components/BaseRadioGroup.vue.d.ts +21 -0
- package/dist/components/BaseSelect.vue.d.ts +20 -0
- package/dist/components/BaseSlider.vue.d.ts +22 -0
- package/dist/components/BaseTabs.vue.d.ts +14 -0
- package/dist/components/BaseTextarea.vue.d.ts +30 -0
- package/dist/components/BaseToggle.vue.d.ts +19 -0
- package/dist/components/BatchProgressList.vue.d.ts +43 -0
- package/dist/components/Breadcrumb.vue.d.ts +33 -0
- package/dist/components/Calendar.vue.d.ts +107 -0
- package/dist/components/ChartContainer.vue.d.ts +31 -0
- package/dist/components/ChemicalFormula.vue.d.ts +8 -0
- package/dist/components/CollapsibleCard.vue.d.ts +41 -0
- package/dist/components/ColorSlider.vue.d.ts +34 -0
- package/dist/components/ConcentrationInput.vue.d.ts +25 -0
- package/dist/components/ConfirmDialog.vue.d.ts +42 -0
- package/dist/components/DataFrame.vue.d.ts +107 -0
- package/dist/components/DatePicker.vue.d.ts +25 -0
- package/dist/components/DateTimePicker.vue.d.ts +30 -0
- package/dist/components/Divider.vue.d.ts +14 -0
- package/dist/components/DoseCalculator.vue.d.ts +19 -0
- package/dist/components/DropdownButton.vue.d.ts +47 -0
- package/dist/components/EmptyState.vue.d.ts +36 -0
- package/dist/components/ExperimentCodeBadge.vue.d.ts +14 -0
- package/dist/components/ExperimentDataViewer.vue.d.ts +29 -0
- package/dist/components/ExperimentPopover.vue.d.ts +32 -0
- package/dist/components/ExperimentSelectorModal.vue.d.ts +28 -0
- package/dist/components/ExperimentTimeline.vue.d.ts +44 -0
- package/dist/components/FileUploader.vue.d.ts +40 -0
- package/dist/components/FitPanel.vue.d.ts +46 -0
- package/dist/components/FormActions.vue.d.ts +33 -0
- package/dist/components/FormBuilder.vue.d.ts +287 -0
- package/dist/components/FormField.vue.d.ts +28 -0
- package/dist/components/FormFieldRenderer.vue.d.ts +31 -0
- package/dist/components/FormSection.vue.d.ts +43 -0
- package/dist/components/FormulaInput.vue.d.ts +25 -0
- package/dist/components/GroupAssigner.vue.d.ts +25 -0
- package/dist/components/GroupingModal.vue.d.ts +12 -0
- package/dist/components/IconButton.vue.d.ts +34 -0
- package/dist/components/LoadingSpinner.vue.d.ts +12 -0
- package/dist/components/MoleculeInput.vue.d.ts +27 -0
- package/dist/components/MultiSelect.vue.d.ts +19 -0
- package/dist/components/NumberInput.vue.d.ts +22 -0
- package/dist/components/PlateMapEditor.vue.d.ts +50 -0
- package/dist/components/ProgressBar.vue.d.ts +23 -0
- package/dist/components/ProtocolStepEditor.vue.d.ts +24 -0
- package/dist/components/RackEditor.vue.d.ts +40 -0
- package/dist/components/ReagentEditor.vue.d.ts +30 -0
- package/dist/components/ReagentList.vue.d.ts +32 -0
- package/dist/components/ResourceCard.vue.d.ts +50 -0
- package/dist/components/SampleHierarchyTree.vue.d.ts +26 -0
- package/dist/components/SampleLegend.vue.d.ts +32 -0
- package/dist/components/SampleSelector.vue.d.ts +29 -0
- package/dist/components/ScheduleCalendar.vue.d.ts +110 -0
- package/dist/components/ScientificNumber.vue.d.ts +14 -0
- package/dist/components/SegmentedControl.vue.d.ts +20 -0
- package/dist/components/SequenceInput.vue.d.ts +54 -0
- package/dist/components/SettingsButton.vue.d.ts +30 -0
- package/dist/components/SettingsModal.vue.d.ts +36 -0
- package/dist/components/Skeleton.vue.d.ts +11 -0
- package/dist/components/StatusIndicator.vue.d.ts +13 -0
- package/dist/components/StepWizard.vue.d.ts +65 -0
- package/dist/components/TagsInput.vue.d.ts +39 -0
- package/dist/components/ThemeToggle.vue.d.ts +7 -0
- package/dist/components/TimePicker.vue.d.ts +29 -0
- package/dist/components/TimeRangeInput.vue.d.ts +27 -0
- package/dist/components/ToastNotification.vue.d.ts +2 -0
- package/dist/components/Tooltip.vue.d.ts +35 -0
- package/dist/components/UnitInput.vue.d.ts +39 -0
- package/dist/components/WellEditPopup.vue.d.ts +25 -0
- package/dist/components/WellPlate.vue.d.ts +73 -0
- package/dist/components/index.d.ts +87 -0
- package/dist/components/index.js +3 -0
- package/dist/components-CKf-UpGi.js +15089 -0
- package/dist/components-CKf-UpGi.js.map +1 -0
- package/dist/composables/experiment-utils.d.ts +8 -0
- package/dist/composables/formBuilderRegistry.d.ts +13 -0
- package/dist/composables/index.d.ts +28 -0
- package/dist/composables/index.js +3 -0
- package/dist/composables/useApi.d.ts +20 -0
- package/dist/composables/useAppExperiment.d.ts +37 -0
- package/dist/composables/useAsync.d.ts +128 -0
- package/dist/composables/useAuth.d.ts +47 -0
- package/dist/composables/useAutoGroup.d.ts +106 -0
- package/dist/composables/useChemicalFormula.d.ts +21 -0
- package/dist/composables/useConcentrationUnits.d.ts +29 -0
- package/dist/composables/useDoseCalculator.d.ts +58 -0
- package/dist/composables/useExperimentData.d.ts +18 -0
- package/dist/composables/useExperimentSave.d.ts +36 -0
- package/dist/composables/useExperimentSelector.d.ts +30 -0
- package/dist/composables/useForm.d.ts +92 -0
- package/dist/composables/useFormBuilder.d.ts +24 -0
- package/dist/composables/usePasskey.d.ts +10 -0
- package/dist/composables/usePlatformContext.d.ts +131 -0
- package/dist/composables/usePluginApi.d.ts +29 -0
- package/dist/composables/usePluginConfig.d.ts +13 -0
- package/dist/composables/useProtocolTemplates.d.ts +44 -0
- package/dist/composables/useRackEditor.d.ts +31 -0
- package/dist/composables/useReagentSeries.d.ts +23 -0
- package/dist/composables/useScheduleDrag.d.ts +78 -0
- package/dist/composables/useSequenceUtils.d.ts +14 -0
- package/dist/composables/useTheme.d.ts +8 -0
- package/dist/composables/useTimeUtils.d.ts +29 -0
- package/dist/composables/useToast.d.ts +22 -0
- package/dist/composables/useWellPlateEditor.d.ts +33 -0
- package/dist/composables-D0QfFzq1.js +805 -0
- package/dist/composables-D0QfFzq1.js.map +1 -0
- package/dist/histoire.setup.d.ts +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/install.d.ts +16 -0
- package/dist/install.js +23 -0
- package/dist/install.js.map +1 -0
- package/dist/stores/auth.d.ts +146 -0
- package/dist/stores/index.d.ts +2 -0
- package/dist/stores/index.js +2 -0
- package/dist/stores/settings.d.ts +75 -0
- package/dist/styles.css +29728 -0
- package/dist/tailwind.preset.d.ts +58 -0
- package/dist/tailwind.preset.js +66 -0
- package/dist/tailwind.preset.js.map +1 -0
- package/dist/types/auth.d.ts +42 -0
- package/dist/types/auto-group.d.ts +34 -0
- package/dist/types/components.d.ts +528 -0
- package/dist/types/form-builder.d.ts +167 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +0 -0
- package/dist/types/platform.d.ts +75 -0
- package/dist/useScheduleDrag-DAJueTbK.js +7181 -0
- package/dist/useScheduleDrag-DAJueTbK.js.map +1 -0
- package/dist/utils/color.d.ts +24 -0
- package/package.json +114 -0
- package/src/__stories__/experiment-helpers.ts +83 -0
- package/src/__tests__/components/AppLayout.test.ts +163 -0
- package/src/__tests__/components/AppSidebar.test.ts +292 -0
- package/src/__tests__/components/AppTopBar.test.ts +683 -0
- package/src/__tests__/components/BaseInput.test.ts +99 -0
- package/src/__tests__/components/BasePill.test.ts +291 -0
- package/src/__tests__/components/Calendar.test.ts +566 -0
- package/src/__tests__/components/CollapsibleCard.test.ts +524 -0
- package/src/__tests__/components/DataFrame.test.ts +767 -0
- package/src/__tests__/components/DropdownButton.test.ts +471 -0
- package/src/__tests__/composables/formBuilderRegistry.test.ts +187 -0
- package/src/__tests__/composables/useAppExperiment.test.ts +560 -0
- package/src/__tests__/composables/useAuth.test.ts +188 -0
- package/src/__tests__/composables/useAutoGroup.test.ts +860 -0
- package/src/__tests__/composables/useExperimentData.test.ts +127 -0
- package/src/__tests__/composables/useExperimentSave.test.ts +347 -0
- package/src/__tests__/composables/useForm.test.ts +205 -0
- package/src/__tests__/composables/useFormBuilder.test.ts +917 -0
- package/src/__tests__/composables/usePlatformContext.test.ts +116 -0
- package/src/__tests__/composables/usePluginApi.test.ts +81 -0
- package/src/__tests__/composables/usePluginConfig.test.ts +176 -0
- package/src/__tests__/utils/color.test.ts +96 -0
- package/src/components/AlertBox.story.vue +204 -0
- package/src/components/AlertBox.vue +88 -0
- package/src/components/AppAvatarMenu.story.vue +155 -0
- package/src/components/AppAvatarMenu.vue +184 -0
- package/src/components/AppContainer.story.vue +104 -0
- package/src/components/AppContainer.vue +34 -0
- package/src/components/AppLayout.story.vue +292 -0
- package/src/components/AppLayout.vue +75 -0
- package/src/components/AppPageSelector.vue +159 -0
- package/src/components/AppPillNav.vue +66 -0
- package/src/components/AppPluginSwitcher.vue +241 -0
- package/src/components/AppSidebar.story.vue +309 -0
- package/src/components/AppSidebar.vue +119 -0
- package/src/components/AppTopBar.story.vue +304 -0
- package/src/components/AppTopBar.vue +661 -0
- package/src/components/AuditTrail.story.vue +163 -0
- package/src/components/AuditTrail.vue +151 -0
- package/src/components/AutoGroupModal.story.vue +273 -0
- package/src/components/AutoGroupModal.vue +566 -0
- package/src/components/Avatar.story.vue +115 -0
- package/src/components/Avatar.vue +79 -0
- package/src/components/BaseButton.story.vue +96 -0
- package/src/components/BaseButton.vue +73 -0
- package/src/components/BaseCheckbox.story.vue +73 -0
- package/src/components/BaseCheckbox.vue +69 -0
- package/src/components/BaseInput.story.vue +98 -0
- package/src/components/BaseInput.vue +74 -0
- package/src/components/BaseModal.story.vue +237 -0
- package/src/components/BaseModal.vue +182 -0
- package/src/components/BasePill.story.vue +142 -0
- package/src/components/BasePill.vue +89 -0
- package/src/components/BaseRadioGroup.story.vue +145 -0
- package/src/components/BaseRadioGroup.vue +124 -0
- package/src/components/BaseSelect.story.vue +120 -0
- package/src/components/BaseSelect.vue +71 -0
- package/src/components/BaseSlider.story.vue +122 -0
- package/src/components/BaseSlider.vue +126 -0
- package/src/components/BaseTabs.story.vue +127 -0
- package/src/components/BaseTabs.vue +59 -0
- package/src/components/BaseTextarea.story.vue +91 -0
- package/src/components/BaseTextarea.vue +62 -0
- package/src/components/BaseToggle.story.vue +81 -0
- package/src/components/BaseToggle.vue +76 -0
- package/src/components/BatchProgressList.story.vue +92 -0
- package/src/components/BatchProgressList.vue +184 -0
- package/src/components/Breadcrumb.story.vue +106 -0
- package/src/components/Breadcrumb.vue +75 -0
- package/src/components/Calendar.story.vue +106 -0
- package/src/components/Calendar.vue +363 -0
- package/src/components/ChartContainer.story.vue +113 -0
- package/src/components/ChartContainer.vue +64 -0
- package/src/components/ChemicalFormula.story.vue +102 -0
- package/src/components/ChemicalFormula.vue +39 -0
- package/src/components/CollapsibleCard.story.vue +135 -0
- package/src/components/CollapsibleCard.vue +167 -0
- package/src/components/ColorSlider.story.vue +120 -0
- package/src/components/ColorSlider.vue +164 -0
- package/src/components/ConcentrationInput.story.vue +77 -0
- package/src/components/ConcentrationInput.vue +185 -0
- package/src/components/ConfirmDialog.story.vue +248 -0
- package/src/components/ConfirmDialog.vue +93 -0
- package/src/components/DataFrame.story.vue +148 -0
- package/src/components/DataFrame.vue +419 -0
- package/src/components/DatePicker.story.vue +119 -0
- package/src/components/DatePicker.vue +330 -0
- package/src/components/DateTimePicker.story.vue +112 -0
- package/src/components/DateTimePicker.vue +392 -0
- package/src/components/Divider.story.vue +80 -0
- package/src/components/Divider.vue +49 -0
- package/src/components/DoseCalculator.story.vue +68 -0
- package/src/components/DoseCalculator.vue +476 -0
- package/src/components/DropdownButton.story.vue +102 -0
- package/src/components/DropdownButton.vue +181 -0
- package/src/components/EmptyState.story.vue +135 -0
- package/src/components/EmptyState.vue +69 -0
- package/src/components/ExperimentCodeBadge.story.vue +77 -0
- package/src/components/ExperimentCodeBadge.vue +64 -0
- package/src/components/ExperimentDataViewer.story.vue +174 -0
- package/src/components/ExperimentDataViewer.vue +288 -0
- package/src/components/ExperimentPopover.story.vue +384 -0
- package/src/components/ExperimentPopover.vue +241 -0
- package/src/components/ExperimentSelectorModal.story.vue +391 -0
- package/src/components/ExperimentSelectorModal.vue +387 -0
- package/src/components/ExperimentTimeline.story.vue +161 -0
- package/src/components/ExperimentTimeline.vue +382 -0
- package/src/components/FileUploader.story.vue +107 -0
- package/src/components/FileUploader.vue +386 -0
- package/src/components/FitPanel.story.vue +125 -0
- package/src/components/FitPanel.vue +120 -0
- package/src/components/FormActions.vue +92 -0
- package/src/components/FormBuilder.vue +214 -0
- package/src/components/FormField.story.vue +132 -0
- package/src/components/FormField.vue +59 -0
- package/src/components/FormFieldRenderer.vue +58 -0
- package/src/components/FormSection.vue +90 -0
- package/src/components/FormulaInput.story.vue +96 -0
- package/src/components/FormulaInput.vue +125 -0
- package/src/components/GroupAssigner.story.vue +83 -0
- package/src/components/GroupAssigner.vue +284 -0
- package/src/components/GroupingModal.story.vue +52 -0
- package/src/components/GroupingModal.vue +422 -0
- package/src/components/IconButton.story.vue +135 -0
- package/src/components/IconButton.vue +73 -0
- package/src/components/LoadingSpinner.story.vue +70 -0
- package/src/components/LoadingSpinner.vue +50 -0
- package/src/components/MoleculeInput.story.vue +66 -0
- package/src/components/MoleculeInput.vue +426 -0
- package/src/components/MultiSelect.story.vue +132 -0
- package/src/components/MultiSelect.vue +118 -0
- package/src/components/NumberInput.story.vue +122 -0
- package/src/components/NumberInput.vue +160 -0
- package/src/components/PlateMapEditor.story.vue +92 -0
- package/src/components/PlateMapEditor.vue +513 -0
- package/src/components/ProgressBar.story.vue +148 -0
- package/src/components/ProgressBar.vue +114 -0
- package/src/components/ProtocolStepEditor.story.vue +69 -0
- package/src/components/ProtocolStepEditor.vue +522 -0
- package/src/components/RackEditor.story.vue +100 -0
- package/src/components/RackEditor.vue +371 -0
- package/src/components/ReagentEditor.story.vue +153 -0
- package/src/components/ReagentEditor.vue +418 -0
- package/src/components/ReagentList.story.vue +137 -0
- package/src/components/ReagentList.vue +463 -0
- package/src/components/ResourceCard.story.vue +150 -0
- package/src/components/ResourceCard.vue +161 -0
- package/src/components/SampleHierarchyTree.story.vue +161 -0
- package/src/components/SampleHierarchyTree.vue +256 -0
- package/src/components/SampleLegend.story.vue +91 -0
- package/src/components/SampleLegend.vue +119 -0
- package/src/components/SampleSelector.story.vue +111 -0
- package/src/components/SampleSelector.vue +1033 -0
- package/src/components/ScheduleCalendar.story.vue +195 -0
- package/src/components/ScheduleCalendar.vue +569 -0
- package/src/components/ScientificNumber.story.vue +127 -0
- package/src/components/ScientificNumber.vue +197 -0
- package/src/components/SegmentedControl.story.vue +132 -0
- package/src/components/SegmentedControl.vue +79 -0
- package/src/components/SequenceInput.story.vue +119 -0
- package/src/components/SequenceInput.vue +209 -0
- package/src/components/SettingsButton.story.vue +58 -0
- package/src/components/SettingsButton.vue +76 -0
- package/src/components/SettingsModal.story.vue +145 -0
- package/src/components/SettingsModal.vue +146 -0
- package/src/components/Skeleton.story.vue +141 -0
- package/src/components/Skeleton.vue +74 -0
- package/src/components/StatusIndicator.story.vue +99 -0
- package/src/components/StatusIndicator.vue +40 -0
- package/src/components/StepWizard.story.vue +155 -0
- package/src/components/StepWizard.vue +223 -0
- package/src/components/TagsInput.story.vue +155 -0
- package/src/components/TagsInput.vue +265 -0
- package/src/components/ThemeToggle.story.vue +36 -0
- package/src/components/ThemeToggle.vue +54 -0
- package/src/components/TimePicker.story.vue +96 -0
- package/src/components/TimePicker.vue +273 -0
- package/src/components/TimeRangeInput.story.vue +104 -0
- package/src/components/TimeRangeInput.vue +122 -0
- package/src/components/ToastNotification.story.vue +157 -0
- package/src/components/ToastNotification.vue +62 -0
- package/src/components/Tooltip.story.vue +138 -0
- package/src/components/Tooltip.vue +119 -0
- package/src/components/UnitInput.story.vue +194 -0
- package/src/components/UnitInput.vue +213 -0
- package/src/components/WellEditPopup.vue +234 -0
- package/src/components/WellPlate.story.vue +282 -0
- package/src/components/WellPlate.vue +830 -0
- package/src/components/index.ts +118 -0
- package/src/composables/experiment-utils.ts +57 -0
- package/src/composables/formBuilderRegistry.ts +79 -0
- package/src/composables/index.ts +140 -0
- package/src/composables/useApi.ts +167 -0
- package/src/composables/useAppExperiment.ts +159 -0
- package/src/composables/useAsync.ts +323 -0
- package/src/composables/useAuth.ts +445 -0
- package/src/composables/useAutoGroup.ts +641 -0
- package/src/composables/useChemicalFormula.ts +275 -0
- package/src/composables/useConcentrationUnits.ts +246 -0
- package/src/composables/useDoseCalculator.ts +370 -0
- package/src/composables/useExperimentData.ts +86 -0
- package/src/composables/useExperimentSave.ts +192 -0
- package/src/composables/useExperimentSelector.ts +292 -0
- package/src/composables/useForm.ts +416 -0
- package/src/composables/useFormBuilder.ts +383 -0
- package/src/composables/usePasskey.ts +216 -0
- package/src/composables/usePlatformContext.ts +299 -0
- package/src/composables/usePluginApi.ts +39 -0
- package/src/composables/usePluginConfig.ts +93 -0
- package/src/composables/useProtocolTemplates.ts +518 -0
- package/src/composables/useRackEditor.ts +222 -0
- package/src/composables/useReagentSeries.ts +91 -0
- package/src/composables/useScheduleDrag.ts +245 -0
- package/src/composables/useSequenceUtils.ts +105 -0
- package/src/composables/useTheme.ts +58 -0
- package/src/composables/useTimeUtils.ts +131 -0
- package/src/composables/useToast.ts +40 -0
- package/src/composables/useWellPlateEditor.ts +421 -0
- package/src/histoire.setup.ts +17 -0
- package/src/index.ts +367 -0
- package/src/install.ts +32 -0
- package/src/stores/auth.ts +152 -0
- package/src/stores/index.ts +2 -0
- package/src/stores/settings.ts +218 -0
- package/src/styles/components/alert-box.css +150 -0
- package/src/styles/components/app-avatar-menu.css +155 -0
- package/src/styles/components/app-container.css +33 -0
- package/src/styles/components/app-layout.css +98 -0
- package/src/styles/components/app-page-selector.css +191 -0
- package/src/styles/components/app-pill-nav.css +57 -0
- package/src/styles/components/app-plugin-switcher.css +209 -0
- package/src/styles/components/app-sidebar.css +145 -0
- package/src/styles/components/app-top-bar.css +492 -0
- package/src/styles/components/audit-trail.css +143 -0
- package/src/styles/components/auto-group-modal.css +644 -0
- package/src/styles/components/avatar.css +73 -0
- package/src/styles/components/batch-progress-list.css +196 -0
- package/src/styles/components/breadcrumb.css +64 -0
- package/src/styles/components/button.css +188 -0
- package/src/styles/components/calendar.css +192 -0
- package/src/styles/components/chart-container.css +69 -0
- package/src/styles/components/checkbox.css +123 -0
- package/src/styles/components/chemical-formula.css +46 -0
- package/src/styles/components/collapsible-card.css +253 -0
- package/src/styles/components/color-slider.css +110 -0
- package/src/styles/components/concentration-input.css +156 -0
- package/src/styles/components/confirm-dialog.css +183 -0
- package/src/styles/components/dataframe.css +382 -0
- package/src/styles/components/date-picker.css +243 -0
- package/src/styles/components/datetime-picker.css +229 -0
- package/src/styles/components/divider.css +63 -0
- package/src/styles/components/dose-calculator.css +301 -0
- package/src/styles/components/dropdown-button.css +280 -0
- package/src/styles/components/empty-state.css +151 -0
- package/src/styles/components/experiment-code-badge.css +33 -0
- package/src/styles/components/experiment-data-viewer.css +138 -0
- package/src/styles/components/experiment-popover.css +562 -0
- package/src/styles/components/experiment-selector-modal.css +285 -0
- package/src/styles/components/experiment-timeline.css +529 -0
- package/src/styles/components/file-uploader.css +310 -0
- package/src/styles/components/fit-panel.css +67 -0
- package/src/styles/components/form-builder.css +69 -0
- package/src/styles/components/form-field.css +48 -0
- package/src/styles/components/formula-input.css +103 -0
- package/src/styles/components/group-assigner.css +200 -0
- package/src/styles/components/grouping-modal.css +323 -0
- package/src/styles/components/icon-button.css +192 -0
- package/src/styles/components/input.css +66 -0
- package/src/styles/components/loading-spinner.css +67 -0
- package/src/styles/components/modal.css +350 -0
- package/src/styles/components/molecule-input.css +186 -0
- package/src/styles/components/multi-select.css +131 -0
- package/src/styles/components/number-input.css +199 -0
- package/src/styles/components/pill.css +188 -0
- package/src/styles/components/plate-map-editor.css +464 -0
- package/src/styles/components/progress-bar.css +133 -0
- package/src/styles/components/protocol-step-editor.css +449 -0
- package/src/styles/components/rack-editor.css +265 -0
- package/src/styles/components/radio-group.css +240 -0
- package/src/styles/components/reagent-editor.css +510 -0
- package/src/styles/components/reagent-list.css +407 -0
- package/src/styles/components/resource-card.css +360 -0
- package/src/styles/components/sample-hierarchy-tree.css +314 -0
- package/src/styles/components/sample-legend.css +201 -0
- package/src/styles/components/sample-selector.css +751 -0
- package/src/styles/components/schedule-calendar.css +478 -0
- package/src/styles/components/scientific-number.css +63 -0
- package/src/styles/components/segmented-control.css +197 -0
- package/src/styles/components/select.css +77 -0
- package/src/styles/components/sequence-input.css +184 -0
- package/src/styles/components/settings-button.css +94 -0
- package/src/styles/components/settings-modal.css +95 -0
- package/src/styles/components/skeleton.css +49 -0
- package/src/styles/components/slider.css +74 -0
- package/src/styles/components/status-indicator.css +66 -0
- package/src/styles/components/step-wizard.css +192 -0
- package/src/styles/components/tabs.css +95 -0
- package/src/styles/components/tags-input.css +195 -0
- package/src/styles/components/textarea.css +82 -0
- package/src/styles/components/theme-toggle.css +69 -0
- package/src/styles/components/time-picker.css +171 -0
- package/src/styles/components/time-range-input.css +42 -0
- package/src/styles/components/toast.css +91 -0
- package/src/styles/components/toggle.css +146 -0
- package/src/styles/components/tooltip.css +91 -0
- package/src/styles/components/unit-input.css +123 -0
- package/src/styles/components/well-edit-popup.css +252 -0
- package/src/styles/components/well-plate.css +307 -0
- package/src/styles/index.css +87 -0
- package/src/styles/variables.css +1117 -0
- package/src/tailwind.preset.ts +61 -0
- package/src/types/auth.ts +55 -0
- package/src/types/auto-group.ts +40 -0
- package/src/types/components.ts +710 -0
- package/src/types/form-builder.ts +197 -0
- package/src/types/index.ts +207 -0
- package/src/types/platform.ts +116 -0
- package/src/utils/color.ts +96 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import DropdownButton from '../../components/DropdownButton.vue'
|
|
4
|
+
import type { SelectOption } from '../../types'
|
|
5
|
+
|
|
6
|
+
describe('DropdownButton', () => {
|
|
7
|
+
const mockOptions: SelectOption[] = [
|
|
8
|
+
{ value: 'a', label: 'Option A' },
|
|
9
|
+
{ value: 'b', label: 'Option B', description: 'Description for B' },
|
|
10
|
+
{ value: 'c', label: 'Option C', disabled: true },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
describe('rendering with default props', () => {
|
|
14
|
+
it('should render dropdown button', () => {
|
|
15
|
+
const wrapper = mount(DropdownButton, {
|
|
16
|
+
props: { options: mockOptions },
|
|
17
|
+
})
|
|
18
|
+
expect(wrapper.find('.mld-dropdown-button').exists()).toBe(true)
|
|
19
|
+
expect(wrapper.find('.mld-dropdown-button__trigger').exists()).toBe(true)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should show placeholder when no value is selected', () => {
|
|
23
|
+
const wrapper = mount(DropdownButton, {
|
|
24
|
+
props: { options: mockOptions },
|
|
25
|
+
})
|
|
26
|
+
expect(wrapper.find('.mld-dropdown-button__label').text()).toBe('Select...')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should show custom placeholder', () => {
|
|
30
|
+
const wrapper = mount(DropdownButton, {
|
|
31
|
+
props: { options: mockOptions, placeholder: 'Choose an option' },
|
|
32
|
+
})
|
|
33
|
+
expect(wrapper.find('.mld-dropdown-button__label').text()).toBe('Choose an option')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should be closed by default', () => {
|
|
37
|
+
const wrapper = mount(DropdownButton, {
|
|
38
|
+
props: { options: mockOptions },
|
|
39
|
+
})
|
|
40
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(false)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should have chevron icon', () => {
|
|
44
|
+
const wrapper = mount(DropdownButton, {
|
|
45
|
+
props: { options: mockOptions },
|
|
46
|
+
})
|
|
47
|
+
expect(wrapper.find('.mld-dropdown-button__chevron').exists()).toBe(true)
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('v-model behavior', () => {
|
|
52
|
+
it('should display selected option label', () => {
|
|
53
|
+
const wrapper = mount(DropdownButton, {
|
|
54
|
+
props: { options: mockOptions, modelValue: 'b' },
|
|
55
|
+
})
|
|
56
|
+
expect(wrapper.find('.mld-dropdown-button__label').text()).toBe('Option B')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should fallback to value when option not found', () => {
|
|
60
|
+
const wrapper = mount(DropdownButton, {
|
|
61
|
+
props: { options: mockOptions, modelValue: 'unknown' },
|
|
62
|
+
})
|
|
63
|
+
expect(wrapper.find('.mld-dropdown-button__label').text()).toBe('unknown')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should emit update:modelValue when option is selected', async () => {
|
|
67
|
+
const wrapper = mount(DropdownButton, {
|
|
68
|
+
props: { options: mockOptions },
|
|
69
|
+
})
|
|
70
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
71
|
+
await wrapper.findAll('.mld-dropdown-button__option')[0].trigger('click')
|
|
72
|
+
|
|
73
|
+
expect(wrapper.emitted('update:modelValue')).toHaveLength(1)
|
|
74
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['a'])
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should emit select event with full option object', async () => {
|
|
78
|
+
const wrapper = mount(DropdownButton, {
|
|
79
|
+
props: { options: mockOptions },
|
|
80
|
+
})
|
|
81
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
82
|
+
await wrapper.findAll('.mld-dropdown-button__option')[1].trigger('click')
|
|
83
|
+
|
|
84
|
+
expect(wrapper.emitted('select')).toHaveLength(1)
|
|
85
|
+
expect(wrapper.emitted('select')?.[0]).toEqual([mockOptions[1]])
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe('toggle open/close', () => {
|
|
90
|
+
it('should open menu when trigger is clicked', async () => {
|
|
91
|
+
const wrapper = mount(DropdownButton, {
|
|
92
|
+
props: { options: mockOptions },
|
|
93
|
+
})
|
|
94
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
95
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(true)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should close menu when trigger is clicked again', async () => {
|
|
99
|
+
const wrapper = mount(DropdownButton, {
|
|
100
|
+
props: { options: mockOptions },
|
|
101
|
+
})
|
|
102
|
+
const trigger = wrapper.find('.mld-dropdown-button__trigger')
|
|
103
|
+
await trigger.trigger('click')
|
|
104
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(true)
|
|
105
|
+
await trigger.trigger('click')
|
|
106
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(false)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should apply open class to trigger when menu is open', async () => {
|
|
110
|
+
const wrapper = mount(DropdownButton, {
|
|
111
|
+
props: { options: mockOptions },
|
|
112
|
+
})
|
|
113
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
114
|
+
expect(wrapper.find('.mld-dropdown-button__trigger--open').exists()).toBe(true)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('should rotate chevron when open', async () => {
|
|
118
|
+
const wrapper = mount(DropdownButton, {
|
|
119
|
+
props: { options: mockOptions },
|
|
120
|
+
})
|
|
121
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
122
|
+
expect(wrapper.find('.mld-dropdown-button__chevron--open').exists()).toBe(true)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should close menu after selecting an option', async () => {
|
|
126
|
+
const wrapper = mount(DropdownButton, {
|
|
127
|
+
props: { options: mockOptions },
|
|
128
|
+
})
|
|
129
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
130
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(true)
|
|
131
|
+
|
|
132
|
+
await wrapper.findAll('.mld-dropdown-button__option')[0].trigger('click')
|
|
133
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(false)
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
describe('options rendering', () => {
|
|
138
|
+
it('should render all options in menu', async () => {
|
|
139
|
+
const wrapper = mount(DropdownButton, {
|
|
140
|
+
props: { options: mockOptions },
|
|
141
|
+
})
|
|
142
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
143
|
+
|
|
144
|
+
const options = wrapper.findAll('.mld-dropdown-button__option')
|
|
145
|
+
expect(options).toHaveLength(3)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('should display option labels', async () => {
|
|
149
|
+
const wrapper = mount(DropdownButton, {
|
|
150
|
+
props: { options: mockOptions },
|
|
151
|
+
})
|
|
152
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
153
|
+
|
|
154
|
+
const labels = wrapper.findAll('.mld-dropdown-button__option-label')
|
|
155
|
+
expect(labels[0].text()).toBe('Option A')
|
|
156
|
+
expect(labels[1].text()).toBe('Option B')
|
|
157
|
+
expect(labels[2].text()).toBe('Option C')
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('should display option descriptions when provided', async () => {
|
|
161
|
+
const wrapper = mount(DropdownButton, {
|
|
162
|
+
props: { options: mockOptions },
|
|
163
|
+
})
|
|
164
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
165
|
+
|
|
166
|
+
const descriptions = wrapper.findAll('.mld-dropdown-button__option-description')
|
|
167
|
+
expect(descriptions).toHaveLength(1)
|
|
168
|
+
expect(descriptions[0].text()).toBe('Description for B')
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('should mark selected option', async () => {
|
|
172
|
+
const wrapper = mount(DropdownButton, {
|
|
173
|
+
props: { options: mockOptions, modelValue: 'a' },
|
|
174
|
+
})
|
|
175
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
176
|
+
|
|
177
|
+
const selectedOption = wrapper.find('.mld-dropdown-button__option--selected')
|
|
178
|
+
expect(selectedOption.exists()).toBe(true)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should show checkmark on selected option', async () => {
|
|
182
|
+
const wrapper = mount(DropdownButton, {
|
|
183
|
+
props: { options: mockOptions, modelValue: 'b' },
|
|
184
|
+
})
|
|
185
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
186
|
+
|
|
187
|
+
const options = wrapper.findAll('.mld-dropdown-button__option')
|
|
188
|
+
const checkmark = options[1].find('.mld-dropdown-button__option-check')
|
|
189
|
+
expect(checkmark.exists()).toBe(true)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('should apply disabled class to disabled options', async () => {
|
|
193
|
+
const wrapper = mount(DropdownButton, {
|
|
194
|
+
props: { options: mockOptions },
|
|
195
|
+
})
|
|
196
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
197
|
+
|
|
198
|
+
const options = wrapper.findAll('.mld-dropdown-button__option')
|
|
199
|
+
expect(options[2].classes()).toContain('mld-dropdown-button__option--disabled')
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('should not select disabled options', async () => {
|
|
203
|
+
const wrapper = mount(DropdownButton, {
|
|
204
|
+
props: { options: mockOptions },
|
|
205
|
+
})
|
|
206
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
207
|
+
|
|
208
|
+
const disabledOption = wrapper.findAll('.mld-dropdown-button__option')[2]
|
|
209
|
+
await disabledOption.trigger('click')
|
|
210
|
+
|
|
211
|
+
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
describe('variant prop', () => {
|
|
216
|
+
it('should apply secondary variant by default', () => {
|
|
217
|
+
const wrapper = mount(DropdownButton, {
|
|
218
|
+
props: { options: mockOptions },
|
|
219
|
+
})
|
|
220
|
+
expect(wrapper.find('.mld-dropdown-button__trigger--secondary').exists()).toBe(true)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('should apply primary variant', () => {
|
|
224
|
+
const wrapper = mount(DropdownButton, {
|
|
225
|
+
props: { options: mockOptions, variant: 'primary' },
|
|
226
|
+
})
|
|
227
|
+
expect(wrapper.find('.mld-dropdown-button__trigger--primary').exists()).toBe(true)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('should apply danger variant', () => {
|
|
231
|
+
const wrapper = mount(DropdownButton, {
|
|
232
|
+
props: { options: mockOptions, variant: 'danger' },
|
|
233
|
+
})
|
|
234
|
+
expect(wrapper.find('.mld-dropdown-button__trigger--danger').exists()).toBe(true)
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
describe('size prop', () => {
|
|
239
|
+
it('should apply medium size by default', () => {
|
|
240
|
+
const wrapper = mount(DropdownButton, {
|
|
241
|
+
props: { options: mockOptions },
|
|
242
|
+
})
|
|
243
|
+
expect(wrapper.find('.mld-dropdown-button__trigger--md').exists()).toBe(true)
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('should apply small size', () => {
|
|
247
|
+
const wrapper = mount(DropdownButton, {
|
|
248
|
+
props: { options: mockOptions, size: 'sm' },
|
|
249
|
+
})
|
|
250
|
+
expect(wrapper.find('.mld-dropdown-button__trigger--sm').exists()).toBe(true)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('should apply large size', () => {
|
|
254
|
+
const wrapper = mount(DropdownButton, {
|
|
255
|
+
props: { options: mockOptions, size: 'lg' },
|
|
256
|
+
})
|
|
257
|
+
expect(wrapper.find('.mld-dropdown-button__trigger--lg').exists()).toBe(true)
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
describe('disabled prop', () => {
|
|
262
|
+
it('should disable button when disabled is true', () => {
|
|
263
|
+
const wrapper = mount(DropdownButton, {
|
|
264
|
+
props: { options: mockOptions, disabled: true },
|
|
265
|
+
})
|
|
266
|
+
const trigger = wrapper.find('.mld-dropdown-button__trigger')
|
|
267
|
+
expect(trigger.attributes('disabled')).toBeDefined()
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('should apply disabled class', () => {
|
|
271
|
+
const wrapper = mount(DropdownButton, {
|
|
272
|
+
props: { options: mockOptions, disabled: true },
|
|
273
|
+
})
|
|
274
|
+
expect(wrapper.find('.mld-dropdown-button__trigger--disabled').exists()).toBe(true)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('should not open menu when disabled', async () => {
|
|
278
|
+
const wrapper = mount(DropdownButton, {
|
|
279
|
+
props: { options: mockOptions, disabled: true },
|
|
280
|
+
})
|
|
281
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
282
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(false)
|
|
283
|
+
})
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
describe('loading prop', () => {
|
|
287
|
+
it('should show spinner when loading', () => {
|
|
288
|
+
const wrapper = mount(DropdownButton, {
|
|
289
|
+
props: { options: mockOptions, loading: true },
|
|
290
|
+
})
|
|
291
|
+
expect(wrapper.find('.mld-dropdown-button__spinner').exists()).toBe(true)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('should disable button when loading', () => {
|
|
295
|
+
const wrapper = mount(DropdownButton, {
|
|
296
|
+
props: { options: mockOptions, loading: true },
|
|
297
|
+
})
|
|
298
|
+
const trigger = wrapper.find('.mld-dropdown-button__trigger')
|
|
299
|
+
expect(trigger.attributes('disabled')).toBeDefined()
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('should not open menu when loading', async () => {
|
|
303
|
+
const wrapper = mount(DropdownButton, {
|
|
304
|
+
props: { options: mockOptions, loading: true },
|
|
305
|
+
})
|
|
306
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
307
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(false)
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
describe('click outside handling', () => {
|
|
312
|
+
beforeEach(() => {
|
|
313
|
+
vi.useFakeTimers()
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
afterEach(() => {
|
|
317
|
+
vi.restoreAllMocks()
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('should close menu when clicking outside', async () => {
|
|
321
|
+
const wrapper = mount(DropdownButton, {
|
|
322
|
+
props: { options: mockOptions },
|
|
323
|
+
attachTo: document.body,
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
327
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(true)
|
|
328
|
+
|
|
329
|
+
// Simulate click outside
|
|
330
|
+
document.body.click()
|
|
331
|
+
await wrapper.vm.$nextTick()
|
|
332
|
+
|
|
333
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(false)
|
|
334
|
+
wrapper.unmount()
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('should not close menu when clicking inside', async () => {
|
|
338
|
+
const wrapper = mount(DropdownButton, {
|
|
339
|
+
props: { options: mockOptions },
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
343
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(true)
|
|
344
|
+
|
|
345
|
+
// Click on the container itself
|
|
346
|
+
await wrapper.find('.mld-dropdown-button').trigger('click')
|
|
347
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(true)
|
|
348
|
+
})
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
describe('keyboard handling', () => {
|
|
352
|
+
it('should close menu on Escape key', async () => {
|
|
353
|
+
const wrapper = mount(DropdownButton, {
|
|
354
|
+
props: { options: mockOptions },
|
|
355
|
+
attachTo: document.body,
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
359
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(true)
|
|
360
|
+
|
|
361
|
+
// Simulate Escape key press
|
|
362
|
+
const event = new KeyboardEvent('keydown', { key: 'Escape' })
|
|
363
|
+
document.dispatchEvent(event)
|
|
364
|
+
await wrapper.vm.$nextTick()
|
|
365
|
+
|
|
366
|
+
expect(wrapper.find('.mld-dropdown-button__menu').exists()).toBe(false)
|
|
367
|
+
wrapper.unmount()
|
|
368
|
+
})
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
describe('accessibility', () => {
|
|
372
|
+
it('should have proper button type', () => {
|
|
373
|
+
const wrapper = mount(DropdownButton, {
|
|
374
|
+
props: { options: mockOptions },
|
|
375
|
+
})
|
|
376
|
+
expect(wrapper.find('.mld-dropdown-button__trigger').attributes('type')).toBe('button')
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('should have aria-expanded attribute', async () => {
|
|
380
|
+
const wrapper = mount(DropdownButton, {
|
|
381
|
+
props: { options: mockOptions },
|
|
382
|
+
})
|
|
383
|
+
const trigger = wrapper.find('.mld-dropdown-button__trigger')
|
|
384
|
+
expect(trigger.attributes('aria-expanded')).toBe('false')
|
|
385
|
+
|
|
386
|
+
await trigger.trigger('click')
|
|
387
|
+
expect(trigger.attributes('aria-expanded')).toBe('true')
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it('should have aria-haspopup attribute', () => {
|
|
391
|
+
const wrapper = mount(DropdownButton, {
|
|
392
|
+
props: { options: mockOptions },
|
|
393
|
+
})
|
|
394
|
+
expect(wrapper.find('.mld-dropdown-button__trigger').attributes('aria-haspopup')).toBe('listbox')
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('should have role="listbox" on menu', async () => {
|
|
398
|
+
const wrapper = mount(DropdownButton, {
|
|
399
|
+
props: { options: mockOptions },
|
|
400
|
+
})
|
|
401
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
402
|
+
expect(wrapper.find('.mld-dropdown-button__menu').attributes('role')).toBe('listbox')
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
it('should have role="option" on options', async () => {
|
|
406
|
+
const wrapper = mount(DropdownButton, {
|
|
407
|
+
props: { options: mockOptions },
|
|
408
|
+
})
|
|
409
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
410
|
+
const options = wrapper.findAll('.mld-dropdown-button__option')
|
|
411
|
+
options.forEach(opt => {
|
|
412
|
+
expect(opt.attributes('role')).toBe('option')
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
it('should have aria-selected on options', async () => {
|
|
417
|
+
const wrapper = mount(DropdownButton, {
|
|
418
|
+
props: { options: mockOptions, modelValue: 'a' },
|
|
419
|
+
})
|
|
420
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
421
|
+
|
|
422
|
+
const options = wrapper.findAll('.mld-dropdown-button__option')
|
|
423
|
+
expect(options[0].attributes('aria-selected')).toBe('true')
|
|
424
|
+
expect(options[1].attributes('aria-selected')).toBe('false')
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
it('should have aria-disabled on disabled options', async () => {
|
|
428
|
+
const wrapper = mount(DropdownButton, {
|
|
429
|
+
props: { options: mockOptions },
|
|
430
|
+
})
|
|
431
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
432
|
+
|
|
433
|
+
const disabledOption = wrapper.findAll('.mld-dropdown-button__option')[2]
|
|
434
|
+
expect(disabledOption.attributes('aria-disabled')).toBe('true')
|
|
435
|
+
})
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
describe('edge cases', () => {
|
|
439
|
+
it('should handle empty options array', () => {
|
|
440
|
+
const wrapper = mount(DropdownButton, {
|
|
441
|
+
props: { options: [] },
|
|
442
|
+
})
|
|
443
|
+
expect(wrapper.find('.mld-dropdown-button').exists()).toBe(true)
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it('should handle numeric option values', async () => {
|
|
447
|
+
const numericOptions: SelectOption<number>[] = [
|
|
448
|
+
{ value: 1, label: 'One' },
|
|
449
|
+
{ value: 2, label: 'Two' },
|
|
450
|
+
]
|
|
451
|
+
const wrapper = mount(DropdownButton, {
|
|
452
|
+
props: { options: numericOptions, modelValue: 1 },
|
|
453
|
+
})
|
|
454
|
+
expect(wrapper.find('.mld-dropdown-button__label').text()).toBe('One')
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
it('should handle options with same labels', async () => {
|
|
458
|
+
const duplicateLabels: SelectOption[] = [
|
|
459
|
+
{ value: 'a', label: 'Same' },
|
|
460
|
+
{ value: 'b', label: 'Same' },
|
|
461
|
+
]
|
|
462
|
+
const wrapper = mount(DropdownButton, {
|
|
463
|
+
props: { options: duplicateLabels },
|
|
464
|
+
})
|
|
465
|
+
await wrapper.find('.mld-dropdown-button__trigger').trigger('click')
|
|
466
|
+
|
|
467
|
+
const options = wrapper.findAll('.mld-dropdown-button__option')
|
|
468
|
+
expect(options).toHaveLength(2)
|
|
469
|
+
})
|
|
470
|
+
})
|
|
471
|
+
})
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { getFieldRegistryEntry, getTypeDefault } from '../../composables/formBuilderRegistry'
|
|
3
|
+
import type { FormFieldType } from '../../types/form-builder'
|
|
4
|
+
|
|
5
|
+
describe('formBuilderRegistry', () => {
|
|
6
|
+
describe('getFieldRegistryEntry', () => {
|
|
7
|
+
it('should return an entry for every supported field type', () => {
|
|
8
|
+
const types: FormFieldType[] = [
|
|
9
|
+
'text', 'email', 'password', 'tel', 'url', 'search',
|
|
10
|
+
'number', 'textarea', 'select', 'multiselect',
|
|
11
|
+
'checkbox', 'toggle', 'radio', 'slider', 'tags',
|
|
12
|
+
'date', 'time', 'datetime', 'file',
|
|
13
|
+
'formula', 'sequence', 'molecule', 'concentration', 'unit',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
for (const type of types) {
|
|
17
|
+
const entry = getFieldRegistryEntry(type)
|
|
18
|
+
expect(entry, `missing registry entry for type "${type}"`).toBeDefined()
|
|
19
|
+
expect(entry.component).toBeDefined()
|
|
20
|
+
expect(typeof entry.vModel).toBe('boolean')
|
|
21
|
+
expect(typeof entry.defaults).toBe('object')
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('should return correct component for text type', () => {
|
|
26
|
+
const entry = getFieldRegistryEntry('text')
|
|
27
|
+
expect(entry.vModel).toBe(true)
|
|
28
|
+
expect(entry.defaults).toEqual({ type: 'text' })
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should return correct defaults for email type', () => {
|
|
32
|
+
const entry = getFieldRegistryEntry('email')
|
|
33
|
+
expect(entry.defaults).toEqual({ type: 'email' })
|
|
34
|
+
expect(entry.vModel).toBe(true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should return correct defaults for password type', () => {
|
|
38
|
+
const entry = getFieldRegistryEntry('password')
|
|
39
|
+
expect(entry.defaults).toEqual({ type: 'password' })
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should return correct defaults for tel type', () => {
|
|
43
|
+
const entry = getFieldRegistryEntry('tel')
|
|
44
|
+
expect(entry.defaults).toEqual({ type: 'tel' })
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should return correct defaults for url type', () => {
|
|
48
|
+
const entry = getFieldRegistryEntry('url')
|
|
49
|
+
expect(entry.defaults).toEqual({ type: 'url' })
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should return correct defaults for search type', () => {
|
|
53
|
+
const entry = getFieldRegistryEntry('search')
|
|
54
|
+
expect(entry.defaults).toEqual({ type: 'search' })
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should have vModel false for file type', () => {
|
|
58
|
+
const entry = getFieldRegistryEntry('file')
|
|
59
|
+
expect(entry.vModel).toBe(false)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should have vModel true for all non-file types', () => {
|
|
63
|
+
const vModelTypes: FormFieldType[] = [
|
|
64
|
+
'text', 'email', 'password', 'tel', 'url', 'search',
|
|
65
|
+
'number', 'textarea', 'select', 'multiselect',
|
|
66
|
+
'checkbox', 'toggle', 'radio', 'slider', 'tags',
|
|
67
|
+
'date', 'time', 'datetime',
|
|
68
|
+
'formula', 'sequence', 'molecule', 'concentration', 'unit',
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
for (const type of vModelTypes) {
|
|
72
|
+
const entry = getFieldRegistryEntry(type)
|
|
73
|
+
expect(entry.vModel, `vModel should be true for "${type}"`).toBe(true)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should return empty defaults object for number type', () => {
|
|
78
|
+
const entry = getFieldRegistryEntry('number')
|
|
79
|
+
expect(entry.defaults).toEqual({})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should return empty defaults object for checkbox type', () => {
|
|
83
|
+
const entry = getFieldRegistryEntry('checkbox')
|
|
84
|
+
expect(entry.defaults).toEqual({})
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('getTypeDefault', () => {
|
|
89
|
+
it('should return false for checkbox', () => {
|
|
90
|
+
expect(getTypeDefault('checkbox')).toBe(false)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should return false for toggle', () => {
|
|
94
|
+
expect(getTypeDefault('toggle')).toBe(false)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should return undefined for number', () => {
|
|
98
|
+
expect(getTypeDefault('number')).toBeUndefined()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should return undefined for slider', () => {
|
|
102
|
+
expect(getTypeDefault('slider')).toBeUndefined()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('should return empty array for multiselect', () => {
|
|
106
|
+
expect(getTypeDefault('multiselect')).toEqual([])
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should return empty array for tags', () => {
|
|
110
|
+
expect(getTypeDefault('tags')).toEqual([])
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('should return empty string for text', () => {
|
|
114
|
+
expect(getTypeDefault('text')).toBe('')
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('should return empty string for email', () => {
|
|
118
|
+
expect(getTypeDefault('email')).toBe('')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should return empty string for password', () => {
|
|
122
|
+
expect(getTypeDefault('password')).toBe('')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should return empty string for textarea', () => {
|
|
126
|
+
expect(getTypeDefault('textarea')).toBe('')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('should return empty string for select', () => {
|
|
130
|
+
expect(getTypeDefault('select')).toBe('')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('should return empty string for date', () => {
|
|
134
|
+
expect(getTypeDefault('date')).toBe('')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should return empty string for time', () => {
|
|
138
|
+
expect(getTypeDefault('time')).toBe('')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('should return empty string for datetime', () => {
|
|
142
|
+
expect(getTypeDefault('datetime')).toBe('')
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('should return empty string for file', () => {
|
|
146
|
+
expect(getTypeDefault('file')).toBe('')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should return empty string for radio', () => {
|
|
150
|
+
expect(getTypeDefault('radio')).toBe('')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should return empty string for formula', () => {
|
|
154
|
+
expect(getTypeDefault('formula')).toBe('')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should return empty string for sequence', () => {
|
|
158
|
+
expect(getTypeDefault('sequence')).toBe('')
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('should return empty string for molecule', () => {
|
|
162
|
+
expect(getTypeDefault('molecule')).toBe('')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('should return empty string for concentration', () => {
|
|
166
|
+
expect(getTypeDefault('concentration')).toBe('')
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('should return empty string for unit', () => {
|
|
170
|
+
expect(getTypeDefault('unit')).toBe('')
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('should return a new array instance each call for multiselect', () => {
|
|
174
|
+
const a = getTypeDefault('multiselect')
|
|
175
|
+
const b = getTypeDefault('multiselect')
|
|
176
|
+
expect(a).toEqual(b)
|
|
177
|
+
// Each call should return a distinct array
|
|
178
|
+
expect(a).not.toBe(b)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should return a new array instance each call for tags', () => {
|
|
182
|
+
const a = getTypeDefault('tags')
|
|
183
|
+
const b = getTypeDefault('tags')
|
|
184
|
+
expect(a).not.toBe(b)
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
})
|