@morscherlab/mint-sdk 1.0.0-beta.3 → 1.0.0-beta.4
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 +9 -2
- package/dist/__tests__/composables/experiment-utils.test.d.ts +1 -0
- package/dist/__tests__/composables/useApi.test.d.ts +1 -0
- package/dist/components/AppContainer.vue.d.ts +1 -1
- package/dist/components/AppLayout.vue.d.ts +20 -1
- package/dist/components/AppSidebar.vue.d.ts +56 -4
- package/dist/components/AppTopBar.vue.d.ts +7 -25
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +3 -1
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -0
- package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +5 -0
- package/dist/components/ComponentBindingRenderer.vue.d.ts +44 -0
- package/dist/components/ControlWorkspaceView.vue.d.ts +24 -7
- package/dist/components/DoseDesignWorkspaceView.vue.d.ts +149 -0
- package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
- package/dist/components/FormBuilder.vue.d.ts +9 -9
- package/dist/components/PlateMapEditor.vue.d.ts +1 -1
- package/dist/components/PluginWorkspaceView.vue.d.ts +310 -0
- package/dist/components/SettingsModal.vue.d.ts +1 -1
- package/dist/components/WellPlate.vue.d.ts +2 -2
- package/dist/components/index.d.ts +3 -12
- package/dist/components/index.js +3 -3
- package/dist/components/{AppPageSelector.vue.d.ts → internal/AppPageSelectorInternal.vue.d.ts} +1 -1
- package/dist/components/{AppPillNav.vue.d.ts → internal/AppPillNavInternal.vue.d.ts} +3 -1
- package/dist/components/{CalendarGridPanel.vue.d.ts → internal/CalendarGridPanelInternal.vue.d.ts} +1 -1
- package/dist/components/internal/FormSectionRenderer.vue.d.ts +4 -4
- package/dist/components/{WellEditPopup.vue.d.ts → internal/WellEditPopupInternal.vue.d.ts} +1 -1
- package/dist/{components-D_Sr0adg.js → components-BkGF4B4y.js} +4484 -3967
- package/dist/components-BkGF4B4y.js.map +1 -0
- package/dist/composables/experiment-utils.d.ts +8 -0
- package/dist/composables/index.d.ts +5 -7
- package/dist/composables/index.js +4 -4
- package/dist/composables/useAppExperiment.d.ts +31 -2
- package/dist/composables/useBioTemplateComponents.d.ts +5 -3
- package/dist/composables/useBioTemplatePackWorkspace.d.ts +3 -2
- package/dist/composables/useBioTemplatePresetWorkspace.d.ts +6 -5
- package/dist/composables/useBioTemplateWorkspace.d.ts +5 -4
- package/dist/composables/useControlSchema.d.ts +43 -21
- package/dist/composables/usePluginClient.d.ts +5 -2
- package/dist/{composables-C3dpXQN5.js → composables-CHsME9H1.js} +40 -28
- package/dist/composables-CHsME9H1.js.map +1 -0
- package/dist/index.d.ts +5 -12
- package/dist/index.js +5 -5
- package/dist/install.js +2 -2
- package/dist/styles.css +3625 -3651
- package/dist/templates/componentBindings.d.ts +13 -0
- package/dist/templates/index.d.ts +3 -3
- package/dist/templates/index.js +2 -2
- package/dist/{templates-50NPjaxL.js → templates-B5jmTWuk.js} +111 -56
- package/dist/templates-B5jmTWuk.js.map +1 -0
- package/dist/types/components.d.ts +6 -25
- package/dist/types/index.d.ts +1 -1
- package/dist/{useScheduleDrag-D4oWdh41.js → useScheduleDrag-BgzpQT53.js} +160 -117
- package/dist/useScheduleDrag-BgzpQT53.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/components/ActionItem.test.ts +6 -6
- package/src/__tests__/components/AppLayout.test.ts +44 -0
- package/src/__tests__/components/AppPageSelector.test.ts +8 -8
- package/src/__tests__/components/AppPillNav.test.ts +53 -6
- package/src/__tests__/components/AppSidebar.test.ts +126 -0
- package/src/__tests__/components/AppToastContainer.test.ts +0 -11
- package/src/__tests__/components/AppTopBar.test.ts +182 -119
- package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +7 -1
- package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +15 -1
- package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +26 -1
- package/src/__tests__/components/CalendarGridPanel.test.ts +3 -3
- package/src/__tests__/components/ComponentBindingRenderer.test.ts +161 -0
- package/src/__tests__/components/ControlWorkspaceView.test.ts +134 -63
- package/src/__tests__/components/DateTimePicker.test.ts +2 -2
- package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +185 -0
- package/src/__tests__/components/PluginWorkspaceView.test.ts +548 -0
- package/src/__tests__/composables/experiment-utils.test.ts +30 -0
- package/src/__tests__/composables/useApi.test.ts +30 -0
- package/src/__tests__/composables/useAppExperiment.test.ts +100 -1
- package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +6 -3
- package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +6 -6
- package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +6 -1
- package/src/__tests__/composables/useControlSchema.test.ts +150 -36
- package/src/__tests__/composables/usePluginClient.test.ts +99 -2
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +120 -25
- package/src/__tests__/templates/templates.test.ts +12 -0
- package/src/components/AppAvatarMenu.vue +3 -3
- package/src/components/AppLayout.story.vue +39 -0
- package/src/components/AppLayout.vue +83 -2
- package/src/components/AppPluginSwitcher.vue +5 -5
- package/src/components/AppSidebar.story.vue +113 -5
- package/src/components/AppSidebar.vue +144 -24
- package/src/components/AppTopBar.story.vue +2 -5
- package/src/components/AppTopBar.vue +35 -425
- package/src/components/BioTemplateExperimentWorkspaceView.story.vue +2 -2
- package/src/components/BioTemplateExperimentWorkspaceView.vue +6 -0
- package/src/components/BioTemplatePackWorkspaceView.story.vue +4 -4
- package/src/components/BioTemplatePackWorkspaceView.vue +1 -0
- package/src/components/BioTemplatePresetWorkspaceView.story.vue +14 -2
- package/src/components/BioTemplatePresetWorkspaceView.vue +11 -2
- package/src/components/BioTemplateRenderer.vue +15 -227
- package/src/components/ComponentBindingRenderer.story.vue +57 -0
- package/src/components/ComponentBindingRenderer.vue +308 -0
- package/src/components/ControlWorkspaceView.story.vue +20 -9
- package/src/components/ControlWorkspaceView.vue +43 -12
- package/src/components/DatePicker.vue +2 -2
- package/src/components/DateTimePicker.vue +2 -2
- package/src/components/DoseDesignWorkspaceView.story.vue +77 -0
- package/src/components/DoseDesignWorkspaceView.vue +255 -0
- package/src/components/ExperimentPopover.vue +2 -6
- package/src/components/ExperimentSelectorModal.vue +6 -5
- package/src/components/FormBuilder.story.vue +190 -0
- package/src/components/PluginWorkspaceView.story.vue +334 -0
- package/src/components/PluginWorkspaceView.vue +708 -0
- package/src/components/SettingsModal.story.vue +87 -0
- package/src/components/WellPlate.vue +2 -2
- package/src/components/index.ts +3 -12
- package/src/components/{AppPageSelector.vue → internal/AppPageSelectorInternal.vue} +9 -9
- package/src/components/internal/AppPillNavInternal.vue +194 -0
- package/src/components/{CalendarGridPanel.vue → internal/CalendarGridPanelInternal.vue} +1 -1
- package/src/components/{WellEditPopup.vue → internal/WellEditPopupInternal.vue} +3 -3
- package/src/composables/experiment-utils.ts +26 -0
- package/src/composables/index.ts +21 -7
- package/src/composables/useApi.ts +9 -2
- package/src/composables/useAppExperiment.ts +85 -13
- package/src/composables/useBioTemplateComponents.ts +12 -0
- package/src/composables/useBioTemplatePackWorkspace.ts +6 -2
- package/src/composables/useBioTemplatePresetWorkspace.ts +10 -21
- package/src/composables/useBioTemplateWorkspace.ts +6 -4
- package/src/composables/useControlSchema.ts +157 -69
- package/src/composables/usePluginClient.ts +50 -9
- package/src/index.ts +6 -563
- package/src/styles/components/app-layout.css +82 -0
- package/src/styles/components/app-pill-nav.css +70 -0
- package/src/styles/components/app-sidebar.css +119 -0
- package/src/styles/components/app-top-bar.css +0 -235
- package/src/styles/index.css +0 -1
- package/src/templates/componentBindings.ts +38 -0
- package/src/templates/index.ts +4 -0
- package/src/types/components.ts +6 -31
- package/src/types/index.ts +2 -6
- package/dist/__tests__/composables/usePluginApi.test.d.ts +0 -13
- package/dist/components/FormFieldRenderer.vue.d.ts +0 -28
- package/dist/components/FormSection.vue.d.ts +0 -30
- package/dist/components/GroupingModal.vue.d.ts +0 -12
- package/dist/components/SettingsButton.vue.d.ts +0 -30
- package/dist/components/ToastNotification.vue.d.ts +0 -2
- package/dist/components-D_Sr0adg.js.map +0 -1
- package/dist/composables/usePluginApi.d.ts +0 -22
- package/dist/composables-C3dpXQN5.js.map +0 -1
- package/dist/templates-50NPjaxL.js.map +0 -1
- package/dist/useScheduleDrag-D4oWdh41.js.map +0 -1
- package/src/__tests__/components/FormCompatibility.test.ts +0 -94
- package/src/__tests__/components/GroupingModal.test.ts +0 -73
- package/src/__tests__/components/SettingsButton.test.ts +0 -44
- package/src/__tests__/composables/usePluginApi.test.ts +0 -81
- package/src/components/AppPillNav.vue +0 -71
- package/src/components/FormFieldRenderer.vue +0 -35
- package/src/components/FormSection.vue +0 -37
- package/src/components/GroupingModal.story.vue +0 -52
- package/src/components/GroupingModal.vue +0 -61
- package/src/components/SettingsButton.story.vue +0 -58
- package/src/components/SettingsButton.vue +0 -64
- package/src/components/ToastNotification.vue +0 -9
- package/src/composables/usePluginApi.ts +0 -32
- package/src/styles/components/settings-button.css +0 -31
- /package/dist/__tests__/components/{FormCompatibility.test.d.ts → ComponentBindingRenderer.test.d.ts} +0 -0
- /package/dist/__tests__/components/{GroupingModal.test.d.ts → DoseDesignWorkspaceView.test.d.ts} +0 -0
- /package/dist/__tests__/components/{SettingsButton.test.d.ts → PluginWorkspaceView.test.d.ts} +0 -0
- /package/dist/components/{ActionItem.vue.d.ts → internal/ActionItemInternal.vue.d.ts} +0 -0
- /package/src/components/{ActionItem.vue → internal/ActionItemInternal.vue} +0 -0
|
@@ -3,8 +3,11 @@ import { mount } from '@vue/test-utils'
|
|
|
3
3
|
import { computed, ref } from 'vue'
|
|
4
4
|
import { createPinia } from 'pinia'
|
|
5
5
|
import AppTopBar from '../../components/AppTopBar.vue'
|
|
6
|
+
import ExperimentPopover from '../../components/ExperimentPopover.vue'
|
|
7
|
+
import ExperimentSelectorModal from '../../components/ExperimentSelectorModal.vue'
|
|
6
8
|
import { usePlatformContext } from '../../composables/usePlatformContext'
|
|
7
|
-
import {
|
|
9
|
+
import { APP_EXPERIMENT_KEY, type AppExperimentState } from '../../composables/useAppExperiment'
|
|
10
|
+
import { defineControlModel, defineControls } from '../../composables/useControlSchema'
|
|
8
11
|
import ThemeToggle from '../../components/ThemeToggle.vue'
|
|
9
12
|
import SettingsModal from '../../components/SettingsModal.vue'
|
|
10
13
|
import type { TopBarSettingsConfig } from '../../types/components'
|
|
@@ -43,7 +46,15 @@ function createWrapper(props = {}, slots = {}) {
|
|
|
43
46
|
plugins: [pinia],
|
|
44
47
|
stubs: {
|
|
45
48
|
'router-link': {
|
|
46
|
-
|
|
49
|
+
props: ['to', 'custom'],
|
|
50
|
+
template: `
|
|
51
|
+
<slot
|
|
52
|
+
v-if="custom"
|
|
53
|
+
:href="typeof to === 'string' ? to : '#'"
|
|
54
|
+
:navigate="() => {}"
|
|
55
|
+
/>
|
|
56
|
+
<a v-else :href="typeof to === 'string' ? to : '#'"><slot /></a>
|
|
57
|
+
`,
|
|
47
58
|
},
|
|
48
59
|
BaseModal: {
|
|
49
60
|
template: '<div><slot /></div>',
|
|
@@ -525,6 +536,86 @@ describe('AppTopBar', () => {
|
|
|
525
536
|
})
|
|
526
537
|
|
|
527
538
|
describe('combined features', () => {
|
|
539
|
+
it('passes injected app experiment bindings to the popover and selector modal', () => {
|
|
540
|
+
vi.mocked(usePlatformContext).mockReturnValueOnce({
|
|
541
|
+
isIntegrated: computed(() => true),
|
|
542
|
+
context: ref({ isIntegrated: true, theme: 'system' }),
|
|
543
|
+
plugin: computed(() => undefined),
|
|
544
|
+
user: computed(() => undefined),
|
|
545
|
+
theme: computed(() => 'system' as const),
|
|
546
|
+
features: computed(() => undefined),
|
|
547
|
+
navigate: vi.fn(),
|
|
548
|
+
notify: vi.fn(),
|
|
549
|
+
sendToPlatform: vi.fn(),
|
|
550
|
+
})
|
|
551
|
+
const appExperiment: AppExperimentState = {
|
|
552
|
+
experimentName: ref('Dose response'),
|
|
553
|
+
experimentCode: ref('EXP-012'),
|
|
554
|
+
experimentStatus: ref('ongoing'),
|
|
555
|
+
experimentId: ref(12),
|
|
556
|
+
showModal: ref(false),
|
|
557
|
+
saveLoading: ref(false),
|
|
558
|
+
saveSuccessMessage: ref('Saved'),
|
|
559
|
+
showSave: ref(true),
|
|
560
|
+
showDetach: computed(() => true),
|
|
561
|
+
saveDisabled: computed(() => true),
|
|
562
|
+
saveDisabledMessage: computed(() => 'Locked'),
|
|
563
|
+
popover: computed(() => ({
|
|
564
|
+
experimentName: 'Dose response',
|
|
565
|
+
experimentCode: 'EXP-012',
|
|
566
|
+
experimentStatus: 'ongoing',
|
|
567
|
+
showSave: true,
|
|
568
|
+
showDetach: true,
|
|
569
|
+
saveDisabled: true,
|
|
570
|
+
saveDisabledMessage: 'Locked',
|
|
571
|
+
saveLoading: false,
|
|
572
|
+
saveSuccessMessage: 'Saved',
|
|
573
|
+
})),
|
|
574
|
+
selectorModal: computed(() => ({
|
|
575
|
+
modelValue: false,
|
|
576
|
+
currentExperimentId: 12,
|
|
577
|
+
})),
|
|
578
|
+
openModal: vi.fn(),
|
|
579
|
+
closeModal: vi.fn(),
|
|
580
|
+
handleSelect: vi.fn(),
|
|
581
|
+
handleSave: vi.fn(),
|
|
582
|
+
handleDetach: vi.fn(),
|
|
583
|
+
}
|
|
584
|
+
const wrapper = mount(AppTopBar, {
|
|
585
|
+
props: { title: 'Test App' },
|
|
586
|
+
global: {
|
|
587
|
+
plugins: [createPinia()],
|
|
588
|
+
provide: {
|
|
589
|
+
[APP_EXPERIMENT_KEY as symbol]: appExperiment,
|
|
590
|
+
},
|
|
591
|
+
stubs: {
|
|
592
|
+
'router-link': {
|
|
593
|
+
template: '<a><slot /></a>',
|
|
594
|
+
},
|
|
595
|
+
BaseModal: {
|
|
596
|
+
template: '<div><slot /></div>',
|
|
597
|
+
},
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
expect(wrapper.findComponent(ExperimentPopover).props()).toMatchObject({
|
|
603
|
+
experimentName: 'Dose response',
|
|
604
|
+
experimentCode: 'EXP-012',
|
|
605
|
+
experimentStatus: 'ongoing',
|
|
606
|
+
showSave: true,
|
|
607
|
+
showDetach: true,
|
|
608
|
+
saveDisabled: true,
|
|
609
|
+
saveDisabledMessage: 'Locked',
|
|
610
|
+
saveLoading: false,
|
|
611
|
+
saveSuccessMessage: 'Saved',
|
|
612
|
+
})
|
|
613
|
+
expect(wrapper.findComponent(ExperimentSelectorModal).props()).toMatchObject({
|
|
614
|
+
modelValue: false,
|
|
615
|
+
currentExperimentId: 12,
|
|
616
|
+
})
|
|
617
|
+
})
|
|
618
|
+
|
|
528
619
|
it('should render ThemeToggle and settings button together', () => {
|
|
529
620
|
const wrapper = createWrapper({
|
|
530
621
|
title: 'Test App',
|
|
@@ -591,8 +682,8 @@ describe('AppTopBar', () => {
|
|
|
591
682
|
})
|
|
592
683
|
})
|
|
593
684
|
|
|
594
|
-
describe('
|
|
595
|
-
it('
|
|
685
|
+
describe('navigation prop surface', () => {
|
|
686
|
+
it('renders a title-only layout without navigation props', () => {
|
|
596
687
|
const wrapper = createWrapper({
|
|
597
688
|
title: 'Classic App',
|
|
598
689
|
subtitle: 'Old API',
|
|
@@ -605,120 +696,113 @@ describe('AppTopBar', () => {
|
|
|
605
696
|
expect(wrapper.find('.mint-topbar__standalone-badge').exists()).toBe(true)
|
|
606
697
|
})
|
|
607
698
|
|
|
608
|
-
it('
|
|
699
|
+
it('composes page selector, pill nav, theme, and settings controls', () => {
|
|
609
700
|
const wrapper = createWrapper({
|
|
610
|
-
pluginName: 'Test Plugin',
|
|
611
701
|
title: 'Dashboard',
|
|
612
|
-
|
|
702
|
+
pageSelector: [
|
|
613
703
|
{ id: 'dashboard', label: 'Dashboard' },
|
|
614
704
|
{ id: 'settings', label: 'Settings' },
|
|
615
705
|
],
|
|
616
|
-
|
|
706
|
+
currentPageSelectorId: 'dashboard',
|
|
707
|
+
pillNav: [
|
|
617
708
|
{ id: 'overview', label: 'Overview' },
|
|
618
709
|
{ id: 'details', label: 'Details' },
|
|
619
710
|
],
|
|
711
|
+
currentPillId: 'overview',
|
|
620
712
|
showThemeToggle: true,
|
|
621
713
|
showSettings: true,
|
|
622
714
|
})
|
|
623
715
|
|
|
624
|
-
expect(wrapper.text()).
|
|
625
|
-
expect(wrapper.text()).
|
|
716
|
+
expect(wrapper.find('.mint-page-selector__label').text()).toBe('Dashboard')
|
|
717
|
+
expect(wrapper.findAll('.mint-pill-nav__item').map(item => item.text())).toEqual([
|
|
718
|
+
'Overview',
|
|
719
|
+
'Details',
|
|
720
|
+
])
|
|
721
|
+
expect(wrapper.findAll('.mint-pill-nav__item')[0].classes()).toContain('mint-pill-nav__item--active')
|
|
626
722
|
expect(wrapper.findComponent(ThemeToggle).exists()).toBe(true)
|
|
627
723
|
expect(wrapper.find('.mint-topbar__settings-btn').exists()).toBe(true)
|
|
628
724
|
})
|
|
629
725
|
|
|
630
|
-
it('renders
|
|
726
|
+
it('renders pill nav items with icon metadata', async () => {
|
|
631
727
|
const runIcon = 'M8 5v14l11-7z'
|
|
632
|
-
const controls = defineControls({
|
|
633
|
-
threshold: {
|
|
634
|
-
default: 0.05,
|
|
635
|
-
section: 'parameters',
|
|
636
|
-
view: 'analysis',
|
|
637
|
-
},
|
|
638
|
-
chartScale: {
|
|
639
|
-
default: 'linear',
|
|
640
|
-
section: 'display',
|
|
641
|
-
view: 'results',
|
|
642
|
-
},
|
|
643
|
-
})
|
|
644
728
|
const wrapper = createWrapper({
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
}),
|
|
651
|
-
currentTabId: 'analysis',
|
|
729
|
+
pillNav: [
|
|
730
|
+
{ id: 'analysis', label: 'Run', icon: runIcon },
|
|
731
|
+
{ id: 'results', label: 'Results' },
|
|
732
|
+
],
|
|
733
|
+
currentPillId: 'analysis',
|
|
652
734
|
})
|
|
653
735
|
|
|
654
|
-
const
|
|
655
|
-
expect(
|
|
656
|
-
expect(
|
|
657
|
-
expect(
|
|
658
|
-
expect(
|
|
736
|
+
const pills = wrapper.findAll('.mint-pill-nav__item')
|
|
737
|
+
expect(pills.map(pill => pill.text())).toEqual(['Run', 'Results'])
|
|
738
|
+
expect(pills[0].classes()).toContain('mint-pill-nav__item--active')
|
|
739
|
+
expect(pills[0].get('.mint-pill-nav__icon path').attributes('d')).toBe(runIcon)
|
|
740
|
+
expect(pills[1].find('.mint-pill-nav__icon').exists()).toBe(false)
|
|
659
741
|
|
|
660
|
-
await
|
|
661
|
-
expect(wrapper.emitted('
|
|
742
|
+
await pills[1].trigger('click')
|
|
743
|
+
expect(wrapper.emitted('pill-select')?.[0][0]).toMatchObject({ id: 'results', label: 'Results' })
|
|
662
744
|
})
|
|
663
745
|
|
|
664
|
-
it('uses shared dropdown state for the
|
|
746
|
+
it('uses shared dropdown state for the page selector menu', async () => {
|
|
665
747
|
const wrapper = createWrapper({
|
|
666
|
-
|
|
667
|
-
pages: [
|
|
748
|
+
pageSelector: [
|
|
668
749
|
{ id: 'dashboard', label: 'Dashboard' },
|
|
669
750
|
{ id: 'settings', label: 'Settings' },
|
|
670
751
|
],
|
|
752
|
+
currentPageSelectorId: 'dashboard',
|
|
671
753
|
})
|
|
672
|
-
const dropdown = () => wrapper.
|
|
754
|
+
const dropdown = () => wrapper.find('.mint-page-selector__menu')
|
|
673
755
|
|
|
674
|
-
expect(dropdown().
|
|
756
|
+
expect(dropdown().exists()).toBe(false)
|
|
675
757
|
|
|
676
|
-
await wrapper.get('.mint-
|
|
677
|
-
expect(dropdown().
|
|
758
|
+
await wrapper.get('.mint-page-selector__trigger').trigger('click')
|
|
759
|
+
expect(dropdown().exists()).toBe(true)
|
|
678
760
|
|
|
679
761
|
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
|
|
680
762
|
await wrapper.vm.$nextTick()
|
|
681
|
-
expect(dropdown().
|
|
763
|
+
expect(dropdown().exists()).toBe(false)
|
|
682
764
|
|
|
683
|
-
await wrapper.get('.mint-
|
|
684
|
-
expect(dropdown().
|
|
765
|
+
await wrapper.get('.mint-page-selector__trigger').trigger('click')
|
|
766
|
+
expect(dropdown().exists()).toBe(true)
|
|
685
767
|
|
|
686
768
|
document.body.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
687
769
|
await wrapper.vm.$nextTick()
|
|
688
|
-
expect(dropdown().
|
|
770
|
+
expect(dropdown().exists()).toBe(false)
|
|
689
771
|
|
|
690
772
|
wrapper.unmount()
|
|
691
773
|
})
|
|
692
774
|
|
|
693
|
-
it('renders PluginIcon-compatible metadata icons in the
|
|
775
|
+
it('renders PluginIcon-compatible metadata icons in the page selector menu', async () => {
|
|
694
776
|
const iconPath = 'M4 19h16M7 16V8m5 8V4m5 12v-6'
|
|
695
777
|
const pngIcon = 'data:image/png;base64,iVBORw0KGgo='
|
|
696
778
|
const wrapper = createWrapper({
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
{ id: 'dashboard', label: 'Dashboard', to: '/', icon: iconPath, description: 'Overview' },
|
|
779
|
+
pageSelector: [
|
|
780
|
+
{ id: 'dashboard', label: 'Dashboard', to: '/', icon: iconPath, hint: 'Overview' },
|
|
700
781
|
{ id: 'image', label: 'Image', to: '/image', icon: pngIcon },
|
|
701
|
-
{ id: '
|
|
782
|
+
{ id: 'text', label: 'Text', icon: 'home' },
|
|
702
783
|
],
|
|
703
|
-
|
|
784
|
+
currentPageSelectorId: 'dashboard',
|
|
704
785
|
})
|
|
705
786
|
|
|
706
|
-
await wrapper.get('.mint-
|
|
787
|
+
await wrapper.get('.mint-page-selector__trigger').trigger('click')
|
|
707
788
|
|
|
708
|
-
const icons = wrapper
|
|
789
|
+
const icons = wrapper
|
|
790
|
+
.findAllComponents({ name: 'PluginIcon' })
|
|
791
|
+
.filter(icon => icon.classes().includes('mint-page-selector__metadata-icon'))
|
|
709
792
|
expect(icons).toHaveLength(2)
|
|
710
793
|
expect(icons[0].props('icon')).toBe(iconPath)
|
|
711
794
|
expect(icons[1].props('icon')).toBe(pngIcon)
|
|
712
795
|
expect(icons.every((icon) => icon.props('variant') === 'tinted')).toBe(true)
|
|
713
796
|
|
|
714
797
|
const pathIcons = wrapper.findAll('.mint-plugin-icon__svg')
|
|
715
|
-
expect(pathIcons).
|
|
716
|
-
|
|
798
|
+
expect(pathIcons.map(icon => icon.find('path').attributes('d'))).toEqual(
|
|
799
|
+
expect.arrayContaining([iconPath]),
|
|
800
|
+
)
|
|
717
801
|
|
|
718
802
|
const imageIcons = wrapper.findAll('.mint-plugin-icon__img')
|
|
719
803
|
expect(imageIcons).toHaveLength(1)
|
|
720
804
|
expect(imageIcons[0].attributes('src')).toBe(pngIcon)
|
|
721
|
-
expect(wrapper.find('.mint-
|
|
805
|
+
expect(wrapper.find('.mint-page-selector__item-hint').text()).toBe('Overview')
|
|
722
806
|
})
|
|
723
807
|
})
|
|
724
808
|
|
|
@@ -926,7 +1010,7 @@ describe('AppTopBar', () => {
|
|
|
926
1010
|
expect(wrapper.find('.mint-topbar__logo-text').text()).toBe('M')
|
|
927
1011
|
})
|
|
928
1012
|
|
|
929
|
-
it('uses platform plugin nav metadata
|
|
1013
|
+
it('uses platform plugin nav metadata as the page selector when pageSelector is omitted', async () => {
|
|
930
1014
|
const previousPath = window.location.pathname
|
|
931
1015
|
window.history.pushState({}, '', '/test/analysis')
|
|
932
1016
|
try {
|
|
@@ -949,25 +1033,28 @@ describe('AppTopBar', () => {
|
|
|
949
1033
|
})
|
|
950
1034
|
|
|
951
1035
|
const wrapper = createWrapper()
|
|
952
|
-
await wrapper.get('.mint-
|
|
1036
|
+
await wrapper.get('.mint-page-selector__trigger').trigger('click')
|
|
953
1037
|
|
|
954
|
-
expect(wrapper.get('.mint-
|
|
955
|
-
expect(wrapper.
|
|
956
|
-
expect(wrapper.findAll('.mint-topbar-dropdown-item__label').map(item => item.text())).toEqual([
|
|
1038
|
+
expect(wrapper.get('.mint-page-selector__label').text()).toBe('Analysis')
|
|
1039
|
+
expect(wrapper.findAll('.mint-page-selector__item-label').map(item => item.text())).toEqual([
|
|
957
1040
|
'Dashboard',
|
|
958
1041
|
'Analysis',
|
|
959
1042
|
])
|
|
1043
|
+
expect(wrapper.findAll('.mint-page-selector__item-hint').map(item => item.text())).toEqual([
|
|
1044
|
+
'Dose Designer',
|
|
1045
|
+
'Dose Designer',
|
|
1046
|
+
])
|
|
960
1047
|
const dropdownIcons = wrapper
|
|
961
1048
|
.findAllComponents({ name: 'PluginIcon' })
|
|
962
|
-
.filter(icon => icon.classes().includes('mint-
|
|
1049
|
+
.filter(icon => icon.classes().includes('mint-page-selector__metadata-icon'))
|
|
963
1050
|
expect(dropdownIcons.map(icon => icon.props('icon'))).toEqual([pluginIcon, analysisIcon])
|
|
964
|
-
expect(wrapper.findAll('.mint-
|
|
1051
|
+
expect(wrapper.findAll('.mint-page-selector__item')[1].classes()).toContain('mint-page-selector__item--active')
|
|
965
1052
|
} finally {
|
|
966
1053
|
window.history.pushState({}, '', previousPath)
|
|
967
1054
|
}
|
|
968
1055
|
})
|
|
969
1056
|
|
|
970
|
-
it('keeps explicit
|
|
1057
|
+
it('keeps explicit pageSelector ahead of platform plugin nav metadata', async () => {
|
|
971
1058
|
mockPlatformCtx({
|
|
972
1059
|
isIntegrated: true,
|
|
973
1060
|
plugin: {
|
|
@@ -981,13 +1068,13 @@ describe('AppTopBar', () => {
|
|
|
981
1068
|
})
|
|
982
1069
|
|
|
983
1070
|
const wrapper = createWrapper({
|
|
984
|
-
|
|
985
|
-
|
|
1071
|
+
pageSelector: [{ id: 'overview', label: 'Overview', to: '/' }],
|
|
1072
|
+
currentPageSelectorId: 'overview',
|
|
986
1073
|
})
|
|
987
|
-
await wrapper.get('.mint-
|
|
1074
|
+
await wrapper.get('.mint-page-selector__trigger').trigger('click')
|
|
988
1075
|
|
|
989
|
-
expect(wrapper.get('.mint-
|
|
990
|
-
expect(wrapper.findAll('.mint-
|
|
1076
|
+
expect(wrapper.get('.mint-page-selector__label').text()).toBe('Overview')
|
|
1077
|
+
expect(wrapper.findAll('.mint-page-selector__item-label').map(item => item.text())).toEqual(['Overview'])
|
|
991
1078
|
})
|
|
992
1079
|
})
|
|
993
1080
|
|
|
@@ -1026,59 +1113,35 @@ describe('AppTopBar', () => {
|
|
|
1026
1113
|
])
|
|
1027
1114
|
})
|
|
1028
1115
|
|
|
1029
|
-
it('
|
|
1030
|
-
const wrapper = createWrapper({
|
|
1031
|
-
pluginName: 'Analysis',
|
|
1032
|
-
currentPageId: 'Overview',
|
|
1033
|
-
pages: ['Overview', 'Settings'],
|
|
1034
|
-
})
|
|
1035
|
-
|
|
1036
|
-
await wrapper.get('.mint-topbar-plugin-name').trigger('click')
|
|
1037
|
-
await wrapper.findAll('.mint-topbar-dropdown-item')[1].trigger('click')
|
|
1038
|
-
|
|
1039
|
-
expect(wrapper.emitted('page-select')).toEqual([
|
|
1040
|
-
[{ id: 'Settings', label: 'Settings' }],
|
|
1041
|
-
])
|
|
1042
|
-
})
|
|
1043
|
-
|
|
1044
|
-
it('accepts string shorthand for legacy tabs', async () => {
|
|
1045
|
-
const wrapper = createWrapper({
|
|
1046
|
-
currentTabId: 'Run',
|
|
1047
|
-
tabs: ['Run', 'Results'],
|
|
1048
|
-
})
|
|
1049
|
-
|
|
1050
|
-
expect(wrapper.findAll('.mint-topbar-tab').map(tab => tab.text())).toEqual([
|
|
1051
|
-
'Run',
|
|
1052
|
-
'Results',
|
|
1053
|
-
])
|
|
1054
|
-
|
|
1055
|
-
await wrapper.findAll('.mint-topbar-tab')[1].trigger('click')
|
|
1056
|
-
|
|
1057
|
-
expect(wrapper.emitted('tab-select')).toEqual([
|
|
1058
|
-
[{ id: 'Results', label: 'Results' }],
|
|
1059
|
-
])
|
|
1060
|
-
})
|
|
1061
|
-
|
|
1062
|
-
it('accepts string shorthand for legacy tab dropdown options', async () => {
|
|
1116
|
+
it('emits pill-option-select for pillNav dropdown children', async () => {
|
|
1063
1117
|
const wrapper = createWrapper({
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
{ id: '
|
|
1118
|
+
currentPillId: 'pca',
|
|
1119
|
+
pillNav: [
|
|
1120
|
+
{ id: 'charts', label: 'Charts' },
|
|
1121
|
+
{
|
|
1122
|
+
id: 'visualize',
|
|
1123
|
+
label: 'Visualize',
|
|
1124
|
+
children: [
|
|
1125
|
+
{ id: 'pca', label: 'PCA', description: 'Principal component analysis' },
|
|
1126
|
+
{ id: 'heatmap', label: 'Heatmap', description: 'Metabolite heatmap' },
|
|
1127
|
+
],
|
|
1128
|
+
},
|
|
1067
1129
|
],
|
|
1068
1130
|
})
|
|
1069
1131
|
|
|
1070
|
-
await wrapper.
|
|
1071
|
-
|
|
1072
|
-
'Summary',
|
|
1073
|
-
'Details',
|
|
1074
|
-
])
|
|
1075
|
-
|
|
1076
|
-
await wrapper.findAll('.mint-topbar-dropdown-item')[1].trigger('click')
|
|
1132
|
+
await wrapper.findAll('.mint-pill-nav__item')[1].trigger('click')
|
|
1133
|
+
await wrapper.findAll('.mint-pill-nav__dropdown-item')[1].trigger('click')
|
|
1077
1134
|
|
|
1078
|
-
expect(wrapper.emitted('
|
|
1079
|
-
id: '
|
|
1080
|
-
label: '
|
|
1135
|
+
expect(wrapper.emitted('pill-option-select')?.[0]?.[0]).toEqual({
|
|
1136
|
+
id: 'heatmap',
|
|
1137
|
+
label: 'Heatmap',
|
|
1138
|
+
description: 'Metabolite heatmap',
|
|
1139
|
+
})
|
|
1140
|
+
expect(wrapper.emitted('pill-option-select')?.[0]?.[1]).toMatchObject({
|
|
1141
|
+
id: 'visualize',
|
|
1142
|
+
label: 'Visualize',
|
|
1081
1143
|
})
|
|
1082
1144
|
})
|
|
1145
|
+
|
|
1083
1146
|
})
|
|
1084
1147
|
})
|
|
@@ -118,12 +118,15 @@ describe('BioTemplateExperimentWorkspaceView', () => {
|
|
|
118
118
|
kind: 'collection',
|
|
119
119
|
},
|
|
120
120
|
slots: {
|
|
121
|
-
default: ({ bindings, componentPropsById, getComponentProps }) => h(
|
|
121
|
+
default: ({ bindings, componentBindingsById, componentPropsById, getComponentProps }) => h(
|
|
122
122
|
'pre',
|
|
123
123
|
{ class: 'component-props' },
|
|
124
124
|
JSON.stringify({
|
|
125
125
|
doseMode: componentPropsById['dose-response:DoseCalculator'].mode,
|
|
126
|
+
bindingComponent: componentBindingsById['dose-response:DoseCalculator'].component,
|
|
127
|
+
bindingMode: componentBindingsById['dose-response:DoseCalculator'].props.mode,
|
|
126
128
|
bindingDoseMode: bindings.componentPropsById['dose-response:DoseCalculator'].mode,
|
|
129
|
+
nestedBindingMode: bindings.componentBindingsById['dose-response:DoseCalculator'].props.mode,
|
|
127
130
|
hasPlateWells: Boolean(componentPropsById['plate-map:WellPlate'].wells),
|
|
128
131
|
doseModeFromGetter: getComponentProps('DoseCalculator')?.mode,
|
|
129
132
|
bindingDoseModeFromGetter: bindings.getComponentProps('DoseCalculator')?.mode,
|
|
@@ -143,7 +146,10 @@ describe('BioTemplateExperimentWorkspaceView', () => {
|
|
|
143
146
|
|
|
144
147
|
expect(JSON.parse(wrapper.find('.component-props').text())).toEqual({
|
|
145
148
|
doseMode: 'serial',
|
|
149
|
+
bindingComponent: 'DoseCalculator',
|
|
150
|
+
bindingMode: 'serial',
|
|
146
151
|
bindingDoseMode: 'serial',
|
|
152
|
+
nestedBindingMode: 'serial',
|
|
147
153
|
hasPlateWells: true,
|
|
148
154
|
doseModeFromGetter: 'serial',
|
|
149
155
|
bindingDoseModeFromGetter: 'serial',
|
|
@@ -124,14 +124,26 @@ describe('BioTemplatePackWorkspaceView', () => {
|
|
|
124
124
|
pack: 'cell-culture-screen',
|
|
125
125
|
},
|
|
126
126
|
slots: {
|
|
127
|
-
default: ({
|
|
127
|
+
default: ({
|
|
128
|
+
bindings,
|
|
129
|
+
pack,
|
|
130
|
+
componentBindingsById,
|
|
131
|
+
componentPropsById,
|
|
132
|
+
form,
|
|
133
|
+
getComponentProps,
|
|
134
|
+
pillNav,
|
|
135
|
+
sidebar,
|
|
136
|
+
topBarSettings,
|
|
137
|
+
}) => h(
|
|
128
138
|
'pre',
|
|
129
139
|
{ class: 'component-props' },
|
|
130
140
|
JSON.stringify({
|
|
131
141
|
pack: pack.name,
|
|
132
142
|
activeView: sidebar.activeView,
|
|
133
143
|
doseMode: componentPropsById['dose-response:DoseCalculator'].mode,
|
|
144
|
+
bindingMode: componentBindingsById['dose-response:DoseCalculator'].props.mode,
|
|
134
145
|
bindingDoseMode: bindings.componentPropsById['dose-response:DoseCalculator'].mode,
|
|
146
|
+
nestedBindingMode: bindings.componentBindingsById['dose-response:DoseCalculator'].props.mode,
|
|
135
147
|
hasPlateWells: Boolean(componentPropsById['plate-map:WellPlate'].wells),
|
|
136
148
|
doseModeFromGetter: getComponentProps('DoseCalculator')?.mode,
|
|
137
149
|
bindingDoseModeFromGetter: bindings.getComponentProps('DoseCalculator')?.mode,
|
|
@@ -148,7 +160,9 @@ describe('BioTemplatePackWorkspaceView', () => {
|
|
|
148
160
|
pack: 'cell-culture-screen',
|
|
149
161
|
activeView: 'design',
|
|
150
162
|
doseMode: 'serial',
|
|
163
|
+
bindingMode: 'serial',
|
|
151
164
|
bindingDoseMode: 'serial',
|
|
165
|
+
nestedBindingMode: 'serial',
|
|
152
166
|
hasPlateWells: true,
|
|
153
167
|
doseModeFromGetter: 'serial',
|
|
154
168
|
bindingDoseModeFromGetter: 'serial',
|
|
@@ -32,6 +32,7 @@ describe('BioTemplatePresetWorkspaceView', () => {
|
|
|
32
32
|
expect(wrapper.find('.mint-bio-template-preset-workspace').exists()).toBe(true)
|
|
33
33
|
expect(wrapper.text()).toContain('Well-plate screen preset saves')
|
|
34
34
|
expect(wrapper.find('.mint-sidebar').exists()).toBe(true)
|
|
35
|
+
expect(wrapper.findComponent(AppSidebar).props('variant')).toBe('analysis')
|
|
35
36
|
expect(wrapper.find('.mint-bio-template-renderer').exists()).toBe(true)
|
|
36
37
|
expect(wrapper.text()).toContain('plate-map')
|
|
37
38
|
expect(wrapper.text()).toContain('dose-response')
|
|
@@ -68,6 +69,26 @@ describe('BioTemplatePresetWorkspaceView', () => {
|
|
|
68
69
|
expect(wrapper.text()).toContain('dose-response')
|
|
69
70
|
})
|
|
70
71
|
|
|
72
|
+
it('passes through sidebar variant overrides', () => {
|
|
73
|
+
const wrapper = mount(BioTemplatePresetWorkspaceView, {
|
|
74
|
+
props: {
|
|
75
|
+
preset: 'wellplate-screen',
|
|
76
|
+
sidebarVariant: 'default',
|
|
77
|
+
},
|
|
78
|
+
global: {
|
|
79
|
+
stubs: {
|
|
80
|
+
DataFrame: true,
|
|
81
|
+
DoseCalculator: true,
|
|
82
|
+
PlateMapEditor: true,
|
|
83
|
+
SampleSelector: true,
|
|
84
|
+
WellPlate: true,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
expect(wrapper.findComponent(AppSidebar).props('variant')).toBe('default')
|
|
90
|
+
})
|
|
91
|
+
|
|
71
92
|
it('updates the internally generated workspace when the preset changes after mount', async () => {
|
|
72
93
|
const wrapper = mount(BioTemplatePresetWorkspaceView, {
|
|
73
94
|
props: {
|
|
@@ -194,13 +215,15 @@ describe('BioTemplatePresetWorkspaceView', () => {
|
|
|
194
215
|
preset: 'wellplate-screen',
|
|
195
216
|
},
|
|
196
217
|
slots: {
|
|
197
|
-
default: ({ bindings, collection, componentPropsById, getComponentProps }) => h(
|
|
218
|
+
default: ({ bindings, collection, componentBindingsById, componentPropsById, getComponentProps }) => h(
|
|
198
219
|
'pre',
|
|
199
220
|
{ class: 'component-props' },
|
|
200
221
|
JSON.stringify({
|
|
201
222
|
preset: collection.metadata?.preset,
|
|
202
223
|
doseMode: componentPropsById['dose-response:DoseCalculator'].mode,
|
|
224
|
+
bindingMode: componentBindingsById['dose-response:DoseCalculator'].props.mode,
|
|
203
225
|
bindingDoseMode: bindings.componentPropsById.value['dose-response:DoseCalculator'].mode,
|
|
226
|
+
nestedBindingMode: bindings.componentBindingsById.value['dose-response:DoseCalculator'].props.mode,
|
|
204
227
|
hasPlateWells: Boolean(componentPropsById['plate-map:WellPlate'].wells),
|
|
205
228
|
doseModeFromGetter: getComponentProps('DoseCalculator')?.mode,
|
|
206
229
|
bindingDoseModeFromGetter: bindings.getComponentProps('DoseCalculator')?.mode,
|
|
@@ -221,7 +244,9 @@ describe('BioTemplatePresetWorkspaceView', () => {
|
|
|
221
244
|
expect(JSON.parse(wrapper.find('.component-props').text())).toEqual({
|
|
222
245
|
preset: 'wellplate-screen',
|
|
223
246
|
doseMode: 'serial',
|
|
247
|
+
bindingMode: 'serial',
|
|
224
248
|
bindingDoseMode: 'serial',
|
|
249
|
+
nestedBindingMode: 'serial',
|
|
225
250
|
hasPlateWells: true,
|
|
226
251
|
doseModeFromGetter: 'serial',
|
|
227
252
|
bindingDoseModeFromGetter: 'serial',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils'
|
|
2
2
|
import { describe, expect, it } from 'vitest'
|
|
3
|
-
import
|
|
3
|
+
import CalendarGridPanelInternal from '../../components/internal/CalendarGridPanelInternal.vue'
|
|
4
4
|
import type { CalendarGridDay } from '../../composables/useCalendarGrid'
|
|
5
5
|
|
|
6
6
|
const days: CalendarGridDay[] = [
|
|
@@ -9,9 +9,9 @@ const days: CalendarGridDay[] = [
|
|
|
9
9
|
{ date: new Date(2026, 3, 22), isCurrentMonth: true, isDisabled: true },
|
|
10
10
|
]
|
|
11
11
|
|
|
12
|
-
describe('
|
|
12
|
+
describe('CalendarGridPanelInternal', () => {
|
|
13
13
|
it('renders calendar state and emits navigation and selection events', async () => {
|
|
14
|
-
const wrapper = mount(
|
|
14
|
+
const wrapper = mount(CalendarGridPanelInternal, {
|
|
15
15
|
props: {
|
|
16
16
|
weekDays: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
|
|
17
17
|
monthYear: 'April 2026',
|