@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.
Files changed (165) hide show
  1. package/README.md +9 -2
  2. package/dist/__tests__/composables/experiment-utils.test.d.ts +1 -0
  3. package/dist/__tests__/composables/useApi.test.d.ts +1 -0
  4. package/dist/components/AppContainer.vue.d.ts +1 -1
  5. package/dist/components/AppLayout.vue.d.ts +20 -1
  6. package/dist/components/AppSidebar.vue.d.ts +56 -4
  7. package/dist/components/AppTopBar.vue.d.ts +7 -25
  8. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +3 -1
  9. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -0
  10. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +5 -0
  11. package/dist/components/ComponentBindingRenderer.vue.d.ts +44 -0
  12. package/dist/components/ControlWorkspaceView.vue.d.ts +24 -7
  13. package/dist/components/DoseDesignWorkspaceView.vue.d.ts +149 -0
  14. package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
  15. package/dist/components/FormBuilder.vue.d.ts +9 -9
  16. package/dist/components/PlateMapEditor.vue.d.ts +1 -1
  17. package/dist/components/PluginWorkspaceView.vue.d.ts +310 -0
  18. package/dist/components/SettingsModal.vue.d.ts +1 -1
  19. package/dist/components/WellPlate.vue.d.ts +2 -2
  20. package/dist/components/index.d.ts +3 -12
  21. package/dist/components/index.js +3 -3
  22. package/dist/components/{AppPageSelector.vue.d.ts → internal/AppPageSelectorInternal.vue.d.ts} +1 -1
  23. package/dist/components/{AppPillNav.vue.d.ts → internal/AppPillNavInternal.vue.d.ts} +3 -1
  24. package/dist/components/{CalendarGridPanel.vue.d.ts → internal/CalendarGridPanelInternal.vue.d.ts} +1 -1
  25. package/dist/components/internal/FormSectionRenderer.vue.d.ts +4 -4
  26. package/dist/components/{WellEditPopup.vue.d.ts → internal/WellEditPopupInternal.vue.d.ts} +1 -1
  27. package/dist/{components-D_Sr0adg.js → components-BkGF4B4y.js} +4484 -3967
  28. package/dist/components-BkGF4B4y.js.map +1 -0
  29. package/dist/composables/experiment-utils.d.ts +8 -0
  30. package/dist/composables/index.d.ts +5 -7
  31. package/dist/composables/index.js +4 -4
  32. package/dist/composables/useAppExperiment.d.ts +31 -2
  33. package/dist/composables/useBioTemplateComponents.d.ts +5 -3
  34. package/dist/composables/useBioTemplatePackWorkspace.d.ts +3 -2
  35. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +6 -5
  36. package/dist/composables/useBioTemplateWorkspace.d.ts +5 -4
  37. package/dist/composables/useControlSchema.d.ts +43 -21
  38. package/dist/composables/usePluginClient.d.ts +5 -2
  39. package/dist/{composables-C3dpXQN5.js → composables-CHsME9H1.js} +40 -28
  40. package/dist/composables-CHsME9H1.js.map +1 -0
  41. package/dist/index.d.ts +5 -12
  42. package/dist/index.js +5 -5
  43. package/dist/install.js +2 -2
  44. package/dist/styles.css +3625 -3651
  45. package/dist/templates/componentBindings.d.ts +13 -0
  46. package/dist/templates/index.d.ts +3 -3
  47. package/dist/templates/index.js +2 -2
  48. package/dist/{templates-50NPjaxL.js → templates-B5jmTWuk.js} +111 -56
  49. package/dist/templates-B5jmTWuk.js.map +1 -0
  50. package/dist/types/components.d.ts +6 -25
  51. package/dist/types/index.d.ts +1 -1
  52. package/dist/{useScheduleDrag-D4oWdh41.js → useScheduleDrag-BgzpQT53.js} +160 -117
  53. package/dist/useScheduleDrag-BgzpQT53.js.map +1 -0
  54. package/package.json +1 -1
  55. package/src/__tests__/components/ActionItem.test.ts +6 -6
  56. package/src/__tests__/components/AppLayout.test.ts +44 -0
  57. package/src/__tests__/components/AppPageSelector.test.ts +8 -8
  58. package/src/__tests__/components/AppPillNav.test.ts +53 -6
  59. package/src/__tests__/components/AppSidebar.test.ts +126 -0
  60. package/src/__tests__/components/AppToastContainer.test.ts +0 -11
  61. package/src/__tests__/components/AppTopBar.test.ts +182 -119
  62. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +7 -1
  63. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +15 -1
  64. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +26 -1
  65. package/src/__tests__/components/CalendarGridPanel.test.ts +3 -3
  66. package/src/__tests__/components/ComponentBindingRenderer.test.ts +161 -0
  67. package/src/__tests__/components/ControlWorkspaceView.test.ts +134 -63
  68. package/src/__tests__/components/DateTimePicker.test.ts +2 -2
  69. package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +185 -0
  70. package/src/__tests__/components/PluginWorkspaceView.test.ts +548 -0
  71. package/src/__tests__/composables/experiment-utils.test.ts +30 -0
  72. package/src/__tests__/composables/useApi.test.ts +30 -0
  73. package/src/__tests__/composables/useAppExperiment.test.ts +100 -1
  74. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +6 -3
  75. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +6 -6
  76. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +6 -1
  77. package/src/__tests__/composables/useControlSchema.test.ts +150 -36
  78. package/src/__tests__/composables/usePluginClient.test.ts +99 -2
  79. package/src/__tests__/docs/frontendDocsCatalog.test.ts +120 -25
  80. package/src/__tests__/templates/templates.test.ts +12 -0
  81. package/src/components/AppAvatarMenu.vue +3 -3
  82. package/src/components/AppLayout.story.vue +39 -0
  83. package/src/components/AppLayout.vue +83 -2
  84. package/src/components/AppPluginSwitcher.vue +5 -5
  85. package/src/components/AppSidebar.story.vue +113 -5
  86. package/src/components/AppSidebar.vue +144 -24
  87. package/src/components/AppTopBar.story.vue +2 -5
  88. package/src/components/AppTopBar.vue +35 -425
  89. package/src/components/BioTemplateExperimentWorkspaceView.story.vue +2 -2
  90. package/src/components/BioTemplateExperimentWorkspaceView.vue +6 -0
  91. package/src/components/BioTemplatePackWorkspaceView.story.vue +4 -4
  92. package/src/components/BioTemplatePackWorkspaceView.vue +1 -0
  93. package/src/components/BioTemplatePresetWorkspaceView.story.vue +14 -2
  94. package/src/components/BioTemplatePresetWorkspaceView.vue +11 -2
  95. package/src/components/BioTemplateRenderer.vue +15 -227
  96. package/src/components/ComponentBindingRenderer.story.vue +57 -0
  97. package/src/components/ComponentBindingRenderer.vue +308 -0
  98. package/src/components/ControlWorkspaceView.story.vue +20 -9
  99. package/src/components/ControlWorkspaceView.vue +43 -12
  100. package/src/components/DatePicker.vue +2 -2
  101. package/src/components/DateTimePicker.vue +2 -2
  102. package/src/components/DoseDesignWorkspaceView.story.vue +77 -0
  103. package/src/components/DoseDesignWorkspaceView.vue +255 -0
  104. package/src/components/ExperimentPopover.vue +2 -6
  105. package/src/components/ExperimentSelectorModal.vue +6 -5
  106. package/src/components/FormBuilder.story.vue +190 -0
  107. package/src/components/PluginWorkspaceView.story.vue +334 -0
  108. package/src/components/PluginWorkspaceView.vue +708 -0
  109. package/src/components/SettingsModal.story.vue +87 -0
  110. package/src/components/WellPlate.vue +2 -2
  111. package/src/components/index.ts +3 -12
  112. package/src/components/{AppPageSelector.vue → internal/AppPageSelectorInternal.vue} +9 -9
  113. package/src/components/internal/AppPillNavInternal.vue +194 -0
  114. package/src/components/{CalendarGridPanel.vue → internal/CalendarGridPanelInternal.vue} +1 -1
  115. package/src/components/{WellEditPopup.vue → internal/WellEditPopupInternal.vue} +3 -3
  116. package/src/composables/experiment-utils.ts +26 -0
  117. package/src/composables/index.ts +21 -7
  118. package/src/composables/useApi.ts +9 -2
  119. package/src/composables/useAppExperiment.ts +85 -13
  120. package/src/composables/useBioTemplateComponents.ts +12 -0
  121. package/src/composables/useBioTemplatePackWorkspace.ts +6 -2
  122. package/src/composables/useBioTemplatePresetWorkspace.ts +10 -21
  123. package/src/composables/useBioTemplateWorkspace.ts +6 -4
  124. package/src/composables/useControlSchema.ts +157 -69
  125. package/src/composables/usePluginClient.ts +50 -9
  126. package/src/index.ts +6 -563
  127. package/src/styles/components/app-layout.css +82 -0
  128. package/src/styles/components/app-pill-nav.css +70 -0
  129. package/src/styles/components/app-sidebar.css +119 -0
  130. package/src/styles/components/app-top-bar.css +0 -235
  131. package/src/styles/index.css +0 -1
  132. package/src/templates/componentBindings.ts +38 -0
  133. package/src/templates/index.ts +4 -0
  134. package/src/types/components.ts +6 -31
  135. package/src/types/index.ts +2 -6
  136. package/dist/__tests__/composables/usePluginApi.test.d.ts +0 -13
  137. package/dist/components/FormFieldRenderer.vue.d.ts +0 -28
  138. package/dist/components/FormSection.vue.d.ts +0 -30
  139. package/dist/components/GroupingModal.vue.d.ts +0 -12
  140. package/dist/components/SettingsButton.vue.d.ts +0 -30
  141. package/dist/components/ToastNotification.vue.d.ts +0 -2
  142. package/dist/components-D_Sr0adg.js.map +0 -1
  143. package/dist/composables/usePluginApi.d.ts +0 -22
  144. package/dist/composables-C3dpXQN5.js.map +0 -1
  145. package/dist/templates-50NPjaxL.js.map +0 -1
  146. package/dist/useScheduleDrag-D4oWdh41.js.map +0 -1
  147. package/src/__tests__/components/FormCompatibility.test.ts +0 -94
  148. package/src/__tests__/components/GroupingModal.test.ts +0 -73
  149. package/src/__tests__/components/SettingsButton.test.ts +0 -44
  150. package/src/__tests__/composables/usePluginApi.test.ts +0 -81
  151. package/src/components/AppPillNav.vue +0 -71
  152. package/src/components/FormFieldRenderer.vue +0 -35
  153. package/src/components/FormSection.vue +0 -37
  154. package/src/components/GroupingModal.story.vue +0 -52
  155. package/src/components/GroupingModal.vue +0 -61
  156. package/src/components/SettingsButton.story.vue +0 -58
  157. package/src/components/SettingsButton.vue +0 -64
  158. package/src/components/ToastNotification.vue +0 -9
  159. package/src/composables/usePluginApi.ts +0 -32
  160. package/src/styles/components/settings-button.css +0 -31
  161. /package/dist/__tests__/components/{FormCompatibility.test.d.ts → ComponentBindingRenderer.test.d.ts} +0 -0
  162. /package/dist/__tests__/components/{GroupingModal.test.d.ts → DoseDesignWorkspaceView.test.d.ts} +0 -0
  163. /package/dist/__tests__/components/{SettingsButton.test.d.ts → PluginWorkspaceView.test.d.ts} +0 -0
  164. /package/dist/components/{ActionItem.vue.d.ts → internal/ActionItemInternal.vue.d.ts} +0 -0
  165. /package/src/components/{ActionItem.vue → internal/ActionItemInternal.vue} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morscherlab/mint-sdk",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.4",
4
4
  "description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec INtegrated Toolkit.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,7 +1,7 @@
1
1
  import { mount } from '@vue/test-utils'
2
2
  import { defineComponent, h } from 'vue'
3
3
  import { describe, expect, it, vi } from 'vitest'
4
- import ActionItem from '../../components/ActionItem.vue'
4
+ import ActionItemInternal from '../../components/internal/ActionItemInternal.vue'
5
5
 
6
6
  function createRouterLinkStub(navigate = vi.fn()) {
7
7
  return defineComponent({
@@ -18,9 +18,9 @@ function createRouterLinkStub(navigate = vi.fn()) {
18
18
  })
19
19
  }
20
20
 
21
- describe('ActionItem', () => {
21
+ describe('ActionItemInternal', () => {
22
22
  it('renders a normal button action and emits clicks', async () => {
23
- const wrapper = mount(ActionItem, {
23
+ const wrapper = mount(ActionItemInternal, {
24
24
  slots: { default: 'Run' },
25
25
  })
26
26
 
@@ -33,7 +33,7 @@ describe('ActionItem', () => {
33
33
  })
34
34
 
35
35
  it('renders disabled href actions without navigating or emitting', async () => {
36
- const wrapper = mount(ActionItem, {
36
+ const wrapper = mount(ActionItemInternal, {
37
37
  props: {
38
38
  href: '/disabled',
39
39
  disabled: true,
@@ -52,7 +52,7 @@ describe('ActionItem', () => {
52
52
 
53
53
  it('renders router actions through router-link and navigates after emitting', async () => {
54
54
  const navigate = vi.fn()
55
- const wrapper = mount(ActionItem, {
55
+ const wrapper = mount(ActionItemInternal, {
56
56
  props: { to: '/settings' },
57
57
  slots: { default: 'Settings' },
58
58
  global: {
@@ -73,7 +73,7 @@ describe('ActionItem', () => {
73
73
 
74
74
  it('renders disabled router actions without href, navigation, or emitted clicks', async () => {
75
75
  const navigate = vi.fn()
76
- const wrapper = mount(ActionItem, {
76
+ const wrapper = mount(ActionItemInternal, {
77
77
  props: {
78
78
  to: '/settings',
79
79
  disabled: true,
@@ -124,6 +124,50 @@ describe('AppLayout', () => {
124
124
  })
125
125
  })
126
126
 
127
+ describe('responsive sidebar shell', () => {
128
+ it('does not render mobile toggle by default', () => {
129
+ const wrapper = mount(AppLayout, {
130
+ slots: { sidebar: '<div>Sidebar</div>' },
131
+ })
132
+
133
+ expect(wrapper.find('.mint-layout__sidebar-toggle').exists()).toBe(false)
134
+ expect(wrapper.find('.mint-layout--responsive-sidebar').exists()).toBe(false)
135
+ })
136
+
137
+ it('renders a mobile toggle when responsiveSidebar is enabled', async () => {
138
+ const wrapper = mount(AppLayout, {
139
+ props: { responsiveSidebar: true },
140
+ slots: { sidebar: '<div>Sidebar</div>' },
141
+ })
142
+
143
+ const toggle = wrapper.get('.mint-layout__sidebar-toggle')
144
+ expect(toggle.attributes('aria-expanded')).toBe('false')
145
+ expect(wrapper.find('.mint-layout__sidebar-backdrop').exists()).toBe(false)
146
+
147
+ await toggle.trigger('click')
148
+
149
+ expect(wrapper.find('.mint-layout--sidebar-open').exists()).toBe(true)
150
+ expect(wrapper.get('.mint-layout__sidebar-toggle').attributes('aria-expanded')).toBe('true')
151
+ expect(wrapper.find('.mint-layout__sidebar-backdrop').exists()).toBe(true)
152
+ expect(wrapper.emitted('update:sidebarOpen')).toEqual([[true]])
153
+ })
154
+
155
+ it('supports controlled mobile sidebar open state and closes from backdrop', async () => {
156
+ const wrapper = mount(AppLayout, {
157
+ props: {
158
+ responsiveSidebar: true,
159
+ sidebarOpen: true,
160
+ },
161
+ slots: { sidebar: '<div>Sidebar</div>' },
162
+ })
163
+
164
+ expect(wrapper.find('.mint-layout--sidebar-open').exists()).toBe(true)
165
+ await wrapper.get('.mint-layout__sidebar-backdrop').trigger('click')
166
+
167
+ expect(wrapper.emitted('update:sidebarOpen')).toEqual([[false]])
168
+ })
169
+ })
170
+
127
171
  describe('integration: all slots', () => {
128
172
  it('should render topbar, sidebar, and main content together', () => {
129
173
  const wrapper = mount(AppLayout, {
@@ -1,10 +1,10 @@
1
1
  import { mount } from '@vue/test-utils'
2
2
  import { describe, expect, it } from 'vitest'
3
- import AppPageSelector from '../../components/AppPageSelector.vue'
3
+ import AppPageSelectorInternal from '../../components/internal/AppPageSelectorInternal.vue'
4
4
 
5
- describe('AppPageSelector', () => {
5
+ describe('AppPageSelectorInternal', () => {
6
6
  it('accepts string shorthand pages', async () => {
7
- const wrapper = mount(AppPageSelector, {
7
+ const wrapper = mount(AppPageSelectorInternal, {
8
8
  props: {
9
9
  currentPageId: 'Workspace',
10
10
  pages: ['Workspace', 'Results'],
@@ -25,7 +25,7 @@ describe('AppPageSelector', () => {
25
25
  })
26
26
 
27
27
  it('closes linked page actions without emitting select', async () => {
28
- const wrapper = mount(AppPageSelector, {
28
+ const wrapper = mount(AppPageSelectorInternal, {
29
29
  props: {
30
30
  currentPageId: 'workspace',
31
31
  pages: [
@@ -43,7 +43,7 @@ describe('AppPageSelector', () => {
43
43
  })
44
44
 
45
45
  it('keeps disabled linked page actions inert', async () => {
46
- const wrapper = mount(AppPageSelector, {
46
+ const wrapper = mount(AppPageSelectorInternal, {
47
47
  props: {
48
48
  currentPageId: 'workspace',
49
49
  pages: [
@@ -66,7 +66,7 @@ describe('AppPageSelector', () => {
66
66
 
67
67
  it('emits select for button page actions', async () => {
68
68
  const page = { id: 'workspace', label: 'Workspace' }
69
- const wrapper = mount(AppPageSelector, {
69
+ const wrapper = mount(AppPageSelectorInternal, {
70
70
  props: {
71
71
  pages: [page],
72
72
  },
@@ -81,7 +81,7 @@ describe('AppPageSelector', () => {
81
81
  it('renders PluginIcon-compatible metadata icons and keeps symbolic icons as fallback initials', async () => {
82
82
  const iconPath = 'M4 19h16M7 16V8m5 8V4m5 12v-6'
83
83
  const pngIcon = 'data:image/png;base64,iVBORw0KGgo='
84
- const wrapper = mount(AppPageSelector, {
84
+ const wrapper = mount(AppPageSelectorInternal, {
85
85
  props: {
86
86
  pages: [
87
87
  { id: 'analysis', label: 'Analysis', icon: iconPath },
@@ -117,7 +117,7 @@ describe('AppPageSelector', () => {
117
117
 
118
118
  it('renders https page icons through PluginIcon', async () => {
119
119
  const httpsIcon = 'https://example.com/plugin-icon.png'
120
- const wrapper = mount(AppPageSelector, {
120
+ const wrapper = mount(AppPageSelectorInternal, {
121
121
  props: {
122
122
  pages: [
123
123
  { id: 'docs', label: 'Docs', icon: httpsIcon },
@@ -1,10 +1,10 @@
1
1
  import { mount } from '@vue/test-utils'
2
2
  import { describe, expect, it } from 'vitest'
3
- import AppPillNav from '../../components/AppPillNav.vue'
3
+ import AppPillNavInternal from '../../components/internal/AppPillNavInternal.vue'
4
4
 
5
- describe('AppPillNav', () => {
5
+ describe('AppPillNavInternal', () => {
6
6
  it('accepts string shorthand items', async () => {
7
- const wrapper = mount(AppPillNav, {
7
+ const wrapper = mount(AppPillNavInternal, {
8
8
  props: {
9
9
  currentItemId: 'Overview',
10
10
  items: ['Overview', 'Analysis'],
@@ -22,7 +22,7 @@ describe('AppPillNav', () => {
22
22
  })
23
23
 
24
24
  it('emits select for button items but not linked items', async () => {
25
- const wrapper = mount(AppPillNav, {
25
+ const wrapper = mount(AppPillNavInternal, {
26
26
  props: {
27
27
  currentItemId: 'overview',
28
28
  items: [
@@ -40,7 +40,7 @@ describe('AppPillNav', () => {
40
40
 
41
41
  it('renders SVG icons from item metadata', () => {
42
42
  const icon = ['M4 12h16', 'M12 4v16']
43
- const wrapper = mount(AppPillNav, {
43
+ const wrapper = mount(AppPillNavInternal, {
44
44
  props: {
45
45
  items: [
46
46
  { id: 'run', label: 'Run', icon },
@@ -59,7 +59,7 @@ describe('AppPillNav', () => {
59
59
  })
60
60
 
61
61
  it('prevents disabled link navigation and selection', async () => {
62
- const wrapper = mount(AppPillNav, {
62
+ const wrapper = mount(AppPillNavInternal, {
63
63
  props: {
64
64
  items: [
65
65
  { id: 'docs', label: 'Docs', href: '/docs', disabled: true },
@@ -75,4 +75,51 @@ describe('AppPillNav', () => {
75
75
 
76
76
  expect(wrapper.emitted('select')).toBeUndefined()
77
77
  })
78
+
79
+ it('supports dropdown child items for grouped pill navigation', async () => {
80
+ const wrapper = mount(AppPillNavInternal, {
81
+ props: {
82
+ currentItemId: 'pca',
83
+ items: [
84
+ { id: 'charts', label: 'Charts' },
85
+ {
86
+ id: 'visualize',
87
+ label: 'Visualize',
88
+ children: [
89
+ { id: 'pca', label: 'PCA', description: 'Principal component analysis' },
90
+ { id: 'heatmap', label: 'Heatmap', description: 'Metabolite heatmap' },
91
+ ],
92
+ },
93
+ ],
94
+ },
95
+ })
96
+
97
+ const visualizeButton = wrapper.findAll('.mint-pill-nav__item')[1]
98
+ expect(visualizeButton.classes()).toContain('mint-pill-nav__item--active')
99
+ expect(wrapper.find('.mint-pill-nav__dropdown').exists()).toBe(false)
100
+
101
+ await visualizeButton.trigger('click')
102
+
103
+ expect(wrapper.find('.mint-pill-nav__dropdown').exists()).toBe(true)
104
+ expect(wrapper.findAll('.mint-pill-nav__dropdown-item').map(item => item.text())).toEqual([
105
+ 'PCAPrincipal component analysis',
106
+ 'HeatmapMetabolite heatmap',
107
+ ])
108
+
109
+ await wrapper.findAll('.mint-pill-nav__dropdown-item')[1].trigger('click')
110
+
111
+ expect(wrapper.emitted('option-select')).toEqual([
112
+ [
113
+ { id: 'heatmap', label: 'Heatmap', description: 'Metabolite heatmap' },
114
+ {
115
+ id: 'visualize',
116
+ label: 'Visualize',
117
+ children: [
118
+ { id: 'pca', label: 'PCA', description: 'Principal component analysis' },
119
+ { id: 'heatmap', label: 'Heatmap', description: 'Metabolite heatmap' },
120
+ ],
121
+ },
122
+ ],
123
+ ])
124
+ })
78
125
  })
@@ -145,6 +145,120 @@ describe('AppSidebar', () => {
145
145
  })
146
146
  })
147
147
 
148
+ describe('sidebar chrome and collapse state', () => {
149
+ it('applies analysis variant defaults for common plugin sidebars', () => {
150
+ const wrapper = mount(AppSidebar, {
151
+ props: {
152
+ panels: samplePanels,
153
+ activeView: 'analysis',
154
+ variant: 'analysis',
155
+ title: 'Peak Picking',
156
+ },
157
+ })
158
+
159
+ expect(wrapper.find('.mint-sidebar--analysis').exists()).toBe(true)
160
+ expect(wrapper.find('.mint-sidebar--static').exists()).toBe(true)
161
+ expect(wrapper.find('.mint-sidebar--floating').exists()).toBe(false)
162
+ expect(wrapper.find('.mint-sidebar').attributes('style')).toContain('width: 20rem')
163
+ expect(wrapper.find('.mint-sidebar__collapse-button').exists()).toBe(true)
164
+ })
165
+
166
+ it('lets explicit props override analysis variant defaults', () => {
167
+ const wrapper = mount(AppSidebar, {
168
+ props: {
169
+ panels: samplePanels,
170
+ activeView: 'analysis',
171
+ variant: 'analysis',
172
+ floating: true,
173
+ width: '18rem',
174
+ collapsible: false,
175
+ },
176
+ })
177
+
178
+ expect(wrapper.find('.mint-sidebar--analysis').exists()).toBe(true)
179
+ expect(wrapper.find('.mint-sidebar--floating').exists()).toBe(true)
180
+ expect(wrapper.find('.mint-sidebar').attributes('style')).toContain('width: 18rem')
181
+ expect(wrapper.find('.mint-sidebar__collapse-button').exists()).toBe(false)
182
+ })
183
+
184
+ it('renders title, subtitle, and badge in the built-in header', () => {
185
+ const wrapper = mount(AppSidebar, {
186
+ props: {
187
+ panels: samplePanels,
188
+ activeView: 'analysis',
189
+ title: 'Peak Picking',
190
+ subtitle: 'run-001.msd',
191
+ badge: 3,
192
+ },
193
+ })
194
+
195
+ expect(wrapper.find('.mint-sidebar__title').text()).toBe('Peak Picking')
196
+ expect(wrapper.find('.mint-sidebar__subtitle').text()).toBe('run-001.msd')
197
+ expect(wrapper.find('.mint-sidebar__badge').text()).toBe('3')
198
+ })
199
+
200
+ it('toggles uncontrolled collapsed state and emits the update', async () => {
201
+ const wrapper = mount(AppSidebar, {
202
+ props: {
203
+ panels: samplePanels,
204
+ activeView: 'analysis',
205
+ title: 'Peak Picking',
206
+ collapsible: true,
207
+ width: '18rem',
208
+ collapsedWidth: '3rem',
209
+ },
210
+ })
211
+
212
+ expect(wrapper.find('.mint-sidebar').attributes('style')).toContain('width: 18rem')
213
+ expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(2)
214
+
215
+ await wrapper.get('.mint-sidebar__collapse-button').trigger('click')
216
+
217
+ expect(wrapper.find('.mint-sidebar--collapsed').exists()).toBe(true)
218
+ expect(wrapper.find('.mint-sidebar').attributes('style')).toContain('width: 3rem')
219
+ expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(0)
220
+ expect(wrapper.emitted('update:collapsed')).toEqual([[true]])
221
+ })
222
+
223
+ it('supports controlled collapsed state and a collapsed slot', () => {
224
+ const wrapper = mount(AppSidebar, {
225
+ props: {
226
+ panels: samplePanels,
227
+ activeView: 'analysis',
228
+ collapsible: true,
229
+ collapsed: true,
230
+ },
231
+ slots: {
232
+ collapsed: '<button class="collapsed-action">2</button>',
233
+ },
234
+ })
235
+
236
+ expect(wrapper.find('.mint-sidebar--collapsed').exists()).toBe(true)
237
+ expect(wrapper.find('.collapsed-action').text()).toBe('2')
238
+ expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(0)
239
+ })
240
+
241
+ it('renders an empty analysis shell for route-owned sidebar content', () => {
242
+ const wrapper = mount(AppSidebar, {
243
+ props: {
244
+ variant: 'analysis',
245
+ title: 'Sequence',
246
+ contentId: 'seqgen-sidebar',
247
+ showWhenEmpty: true,
248
+ },
249
+ slots: {
250
+ footer: '<button class="generate-action">Generate</button>',
251
+ },
252
+ })
253
+
254
+ expect(wrapper.find('.mint-sidebar--hidden').exists()).toBe(false)
255
+ expect(wrapper.find('.mint-sidebar--analysis').exists()).toBe(true)
256
+ expect(wrapper.find('.mint-sidebar__sections').attributes('id')).toBe('seqgen-sidebar')
257
+ expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(0)
258
+ expect(wrapper.find('.generate-action').exists()).toBe(true)
259
+ })
260
+ })
261
+
148
262
  describe('panels and activeView', () => {
149
263
  it('should render sections for active view', () => {
150
264
  const wrapper = mount(AppSidebar, {
@@ -569,6 +683,18 @@ describe('AppSidebar', () => {
569
683
  })
570
684
 
571
685
  describe('slots', () => {
686
+ it('renders the default slot as sidebar content without panels', () => {
687
+ const wrapper = mount(AppSidebar, {
688
+ props: { variant: 'analysis' },
689
+ slots: {
690
+ default: '<div class="route-sidebar-content">Route controls</div>',
691
+ },
692
+ })
693
+
694
+ expect(wrapper.find('.mint-sidebar--hidden').exists()).toBe(false)
695
+ expect(wrapper.find('.route-sidebar-content').text()).toBe('Route controls')
696
+ })
697
+
572
698
  it('should render header slot', () => {
573
699
  const wrapper = mount(AppSidebar, {
574
700
  props: { panels: samplePanels, activeView: 'analysis' },
@@ -2,7 +2,6 @@ import { mount } from '@vue/test-utils'
2
2
  import { afterEach, describe, expect, it } from 'vitest'
3
3
  import { nextTick } from 'vue'
4
4
  import AppToastContainer from '../../components/AppToastContainer.vue'
5
- import ToastNotification from '../../components/ToastNotification.vue'
6
5
  import { useToast } from '../../composables/useToast'
7
6
 
8
7
  describe('AppToastContainer', () => {
@@ -35,14 +34,4 @@ describe('AppToastContainer', () => {
35
34
 
36
35
  expect(toast.toasts.value).toHaveLength(0)
37
36
  })
38
-
39
- it('keeps ToastNotification as a compatibility wrapper', async () => {
40
- const wrapper = mountContainer(ToastNotification)
41
-
42
- toast.info('Legacy import still works', 10000)
43
- await nextTick()
44
-
45
- expect(wrapper.findComponent(AppToastContainer).exists()).toBe(true)
46
- expect(wrapper.find('.mint-toast__message').text()).toBe('Legacy import still works')
47
- })
48
37
  })