@morscherlab/mint-sdk 1.2.0 → 1.2.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.
Files changed (152) hide show
  1. package/dist/{ExperimentSelectorModal-DCXmwKNS.js → ExperimentSelectorModal-DHE9k8TP.js} +3 -3
  2. package/dist/{ExperimentSelectorModal-DCXmwKNS.js.map → ExperimentSelectorModal-DHE9k8TP.js.map} +1 -1
  3. package/dist/{SettingsModal-BYR20I4c.js → SettingsModal-DELMkUFR.js} +39 -16
  4. package/dist/SettingsModal-DELMkUFR.js.map +1 -0
  5. package/dist/{Skeleton-BAF3CKY7.js → Skeleton-jhF9wUkU.js} +17 -7
  6. package/dist/Skeleton-jhF9wUkU.js.map +1 -0
  7. package/dist/__tests__/components/ChartContainer.test.d.ts +1 -0
  8. package/dist/__tests__/components/ChemicalFormula.test.d.ts +1 -0
  9. package/dist/__tests__/components/FormField.test.d.ts +1 -0
  10. package/dist/__tests__/components/LayoutResizeHandle.test.d.ts +1 -0
  11. package/dist/__tests__/components/NumberInput.test.d.ts +1 -0
  12. package/dist/__tests__/components/SearchableSelect.test.d.ts +1 -0
  13. package/dist/__tests__/components/WellPlate.decoration.test.d.ts +1 -0
  14. package/dist/__tests__/composables/localFiles.test.d.ts +1 -0
  15. package/dist/__tests__/composables/useManualLayoutResize.test.d.ts +1 -0
  16. package/dist/__tests__/composables/usePickerSelection.test.d.ts +1 -0
  17. package/dist/__tests__/styles/tokenOnlyColors.test.d.ts +1 -0
  18. package/dist/components/AdductText.vue.d.ts +17 -0
  19. package/dist/components/AppSidebar.vue.d.ts +7 -7
  20. package/dist/components/BasePill.vue.d.ts +3 -0
  21. package/dist/components/BaseTabs.vue.d.ts +1 -4
  22. package/dist/components/ChartContainer.vue.d.ts +19 -5
  23. package/dist/components/ChemicalFormula.vue.d.ts +28 -2
  24. package/dist/components/DataFrame.vue.d.ts +1 -1
  25. package/dist/components/FileUploader.vue.d.ts +3 -0
  26. package/dist/components/FormField.vue.d.ts +8 -1
  27. package/dist/components/LayoutResizeHandle.vue.d.ts +23 -0
  28. package/dist/components/PlotlyChart.vue.d.ts +34 -6
  29. package/dist/components/PluginWorkspaceView.props.d.ts +3 -3
  30. package/dist/components/SearchableSelect.vue.d.ts +57 -0
  31. package/dist/components/WellPlate.vue.d.ts +5 -1
  32. package/dist/components/index.d.ts +3 -1
  33. package/dist/components/index.js +6 -6
  34. package/dist/{components-DIum5hkx.js → components-CxQVQCpP.js} +1215 -546
  35. package/dist/components-CxQVQCpP.js.map +1 -0
  36. package/dist/composables/index.d.ts +1 -0
  37. package/dist/composables/index.js +8 -8
  38. package/dist/composables/pluginWorkspaceModel.d.ts +0 -3
  39. package/dist/composables/useManualLayoutResize.d.ts +56 -0
  40. package/dist/composables/useToast.d.ts +22 -1
  41. package/dist/{composables-mFXsTA4g.js → composables-DAqOU8Aq.js} +97 -8
  42. package/dist/composables-DAqOU8Aq.js.map +1 -0
  43. package/dist/index.js +11 -11
  44. package/dist/install.js +2 -2
  45. package/dist/styles.css +855 -320
  46. package/dist/templates/index.js +2 -2
  47. package/dist/{templates-CmGfmBRA.js → templates-NuaNHohp.js} +2 -2
  48. package/dist/{templates-CmGfmBRA.js.map → templates-NuaNHohp.js.map} +1 -1
  49. package/dist/types/componentLabTypes.d.ts +3 -0
  50. package/dist/types/components.d.ts +26 -1
  51. package/dist/types/form-builder.d.ts +2 -0
  52. package/dist/types/generated-ui.d.ts +2 -0
  53. package/dist/types/index.d.ts +1 -1
  54. package/dist/{useControlSchema-DldpxNOP.js → useControlSchema-vLiRPUmj.js} +181 -71
  55. package/dist/useControlSchema-vLiRPUmj.js.map +1 -0
  56. package/dist/{useExperimentSelector-CEcIaueJ.js → useExperimentSelector-G_CAOFTX.js} +2 -2
  57. package/dist/{useExperimentSelector-CEcIaueJ.js.map → useExperimentSelector-G_CAOFTX.js.map} +1 -1
  58. package/dist/{useFormBuilder-z5L_dUIy.js → useFormBuilder-D37ajgS8.js} +2 -2
  59. package/dist/{useFormBuilder-z5L_dUIy.js.map → useFormBuilder-D37ajgS8.js.map} +1 -1
  60. package/dist/{validation-uwoJGEb6.js → validation-ChMyixAf.js} +21 -9
  61. package/dist/validation-ChMyixAf.js.map +1 -0
  62. package/package.json +1 -1
  63. package/src/__tests__/components/AppSidebar.test.ts +84 -94
  64. package/src/__tests__/components/AppToastContainer.test.ts +55 -1
  65. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +0 -21
  66. package/src/__tests__/components/ChartContainer.test.ts +24 -0
  67. package/src/__tests__/components/ChemicalFormula.test.ts +27 -0
  68. package/src/__tests__/components/ControlWorkspaceView.test.ts +0 -2
  69. package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +0 -2
  70. package/src/__tests__/components/FormBuilder.test.ts +26 -0
  71. package/src/__tests__/components/FormField.test.ts +26 -0
  72. package/src/__tests__/components/GeneratedPathInput.test.ts +18 -0
  73. package/src/__tests__/components/LayoutResizeHandle.test.ts +19 -0
  74. package/src/__tests__/components/NumberInput.test.ts +149 -0
  75. package/src/__tests__/components/PlotlyChart.test.ts +48 -0
  76. package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +1 -2
  77. package/src/__tests__/components/PluginWorkspaceView.test.ts +1 -2
  78. package/src/__tests__/components/RackEditor.test.ts +9 -0
  79. package/src/__tests__/components/SearchableSelect.test.ts +61 -0
  80. package/src/__tests__/components/WellPlate.decoration.test.ts +28 -0
  81. package/src/__tests__/composables/localFiles.test.ts +57 -0
  82. package/src/__tests__/composables/useManualLayoutResize.test.ts +21 -0
  83. package/src/__tests__/composables/usePickerSelection.test.ts +98 -0
  84. package/src/__tests__/generatedUi.test.ts +111 -0
  85. package/src/__tests__/styles/tokenOnlyColors.test.ts +16 -0
  86. package/src/components/AdductText.story.vue +15 -0
  87. package/src/components/AdductText.vue +40 -0
  88. package/src/components/AppLayout.story.vue +1 -1
  89. package/src/components/AppSidebar.story.vue +7 -10
  90. package/src/components/AppSidebar.vue +178 -58
  91. package/src/components/AppToastContainer.vue +65 -23
  92. package/src/components/BasePill.story.vue +2 -0
  93. package/src/components/BasePill.vue +4 -0
  94. package/src/components/BaseTabs.story.vue +0 -42
  95. package/src/components/BaseTabs.vue +3 -6
  96. package/src/components/ChartContainer.story.vue +21 -0
  97. package/src/components/ChartContainer.vue +27 -6
  98. package/src/components/ChemicalFormula.story.vue +7 -0
  99. package/src/components/ChemicalFormula.vue +40 -11
  100. package/src/components/FileUploader.vue +10 -1
  101. package/src/components/FormField.vue +10 -3
  102. package/src/components/GeneratedPathInput.vue +11 -7
  103. package/src/components/LayoutResizeHandle.story.vue +26 -0
  104. package/src/components/LayoutResizeHandle.vue +62 -0
  105. package/src/components/NumberInput.story.vue +32 -13
  106. package/src/components/NumberInput.vue +145 -56
  107. package/src/components/PlotlyChart.vue +77 -11
  108. package/src/components/PluginWorkspaceView.props.ts +3 -3
  109. package/src/components/RackEditor.vue +38 -32
  110. package/src/components/SearchableSelect.story.vue +24 -0
  111. package/src/components/SearchableSelect.vue +223 -0
  112. package/src/components/WellPlate.story.vue +16 -0
  113. package/src/components/WellPlate.vue +17 -8
  114. package/src/components/index.ts +3 -1
  115. package/src/components/internal/FormFieldRendererInternal.vue +17 -0
  116. package/src/components/internal/WorkspaceSidebarInternal.vue +1 -2
  117. package/src/composables/filePicker/localFiles.ts +21 -14
  118. package/src/composables/filePicker/usePickerSelection.ts +15 -4
  119. package/src/composables/index.ts +10 -0
  120. package/src/composables/pluginWorkspaceModel.ts +0 -3
  121. package/src/composables/useManualLayoutResize.ts +165 -0
  122. package/src/composables/usePluginWorkspace.ts +1 -2
  123. package/src/composables/useToast.ts +21 -5
  124. package/src/generated/schema.ts +58 -18
  125. package/src/styles/components/adduct-text.css +45 -0
  126. package/src/styles/components/app-sidebar.css +282 -121
  127. package/src/styles/components/chart-container.css +92 -0
  128. package/src/styles/components/chemical-formula.css +34 -4
  129. package/src/styles/components/form-builder.css +1 -1
  130. package/src/styles/components/form-field.css +28 -0
  131. package/src/styles/components/layout-resize-handle.css +60 -0
  132. package/src/styles/components/number-input.css +75 -91
  133. package/src/styles/components/pill.css +49 -51
  134. package/src/styles/components/rack-editor.css +31 -9
  135. package/src/styles/components/searchable-select.css +63 -0
  136. package/src/styles/components/segmented-control.css +16 -11
  137. package/src/styles/components/tabs.css +18 -34
  138. package/src/styles/components/toast.css +136 -27
  139. package/src/styles/components/well-plate.css +9 -0
  140. package/src/styles/index.css +3 -0
  141. package/src/styles/variables.css +25 -5
  142. package/src/types/componentLabTypes.ts +6 -0
  143. package/src/types/components.ts +30 -0
  144. package/src/types/form-builder.ts +2 -0
  145. package/src/types/generated-ui.ts +2 -0
  146. package/src/types/index.ts +5 -0
  147. package/dist/SettingsModal-BYR20I4c.js.map +0 -1
  148. package/dist/Skeleton-BAF3CKY7.js.map +0 -1
  149. package/dist/components-DIum5hkx.js.map +0 -1
  150. package/dist/composables-mFXsTA4g.js.map +0 -1
  151. package/dist/useControlSchema-DldpxNOP.js.map +0 -1
  152. package/dist/validation-uwoJGEb6.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morscherlab/mint-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
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,6 @@
1
1
  import { describe, it, expect } from 'vitest'
2
2
  import { mount } from '@vue/test-utils'
3
3
  import AppSidebar from '../../components/AppSidebar.vue'
4
- import CollapsibleCard from '../../components/CollapsibleCard.vue'
5
4
  import type { SidebarToolSection } from '../../types'
6
5
  import type { FormSchema } from '../../types/form-builder'
7
6
  import {
@@ -146,39 +145,37 @@ describe('AppSidebar', () => {
146
145
  })
147
146
 
148
147
  describe('sidebar chrome and collapse state', () => {
149
- it('applies analysis variant defaults for common plugin sidebars', () => {
148
+ it('renders sections as flat groups with no nested card', () => {
150
149
  const wrapper = mount(AppSidebar, {
151
- props: {
152
- panels: samplePanels,
153
- activeView: 'analysis',
154
- variant: 'analysis',
155
- title: 'Peak Picking',
156
- },
150
+ props: { panels: samplePanels, activeView: 'analysis' },
157
151
  })
158
152
 
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)
153
+ expect(wrapper.findAll('.mint-sidebar__group')).toHaveLength(2)
154
+ expect(wrapper.find('.mint-collapsible-card').exists()).toBe(false)
164
155
  })
165
156
 
166
- it('lets explicit props override analysis variant defaults', () => {
157
+ it('defaults to compact density', () => {
167
158
  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
- },
159
+ props: { panels: samplePanels, activeView: 'analysis' },
176
160
  })
177
161
 
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)
162
+ expect(wrapper.find('.mint-sidebar--comfortable').exists()).toBe(false)
163
+ })
164
+
165
+ it('applies the comfortable density class', () => {
166
+ const wrapper = mount(AppSidebar, {
167
+ props: { panels: samplePanels, activeView: 'analysis', density: 'comfortable' },
168
+ })
169
+
170
+ expect(wrapper.find('.mint-sidebar--comfortable').exists()).toBe(true)
171
+ })
172
+
173
+ it('maps the deprecated dense=false to comfortable density', () => {
174
+ const wrapper = mount(AppSidebar, {
175
+ props: { panels: samplePanels, activeView: 'analysis', dense: false },
176
+ })
177
+
178
+ expect(wrapper.find('.mint-sidebar--comfortable').exists()).toBe(true)
182
179
  })
183
180
 
184
181
  it('renders title, subtitle, and badge in the built-in header', () => {
@@ -210,13 +207,13 @@ describe('AppSidebar', () => {
210
207
  })
211
208
 
212
209
  expect(wrapper.find('.mint-sidebar').attributes('style')).toContain('width: 18rem')
213
- expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(2)
210
+ expect(wrapper.findAll('.mint-sidebar__group')).toHaveLength(2)
214
211
 
215
212
  await wrapper.get('.mint-sidebar__collapse-button').trigger('click')
216
213
 
217
214
  expect(wrapper.find('.mint-sidebar--collapsed').exists()).toBe(true)
218
215
  expect(wrapper.find('.mint-sidebar').attributes('style')).toContain('width: 3rem')
219
- expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(0)
216
+ expect(wrapper.findAll('.mint-sidebar__group')).toHaveLength(0)
220
217
  expect(wrapper.emitted('update:collapsed')).toEqual([[true]])
221
218
  })
222
219
 
@@ -235,7 +232,7 @@ describe('AppSidebar', () => {
235
232
 
236
233
  expect(wrapper.find('.mint-sidebar--collapsed').exists()).toBe(true)
237
234
  expect(wrapper.find('.collapsed-action').text()).toBe('2')
238
- expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(0)
235
+ expect(wrapper.findAll('.mint-sidebar__group')).toHaveLength(0)
239
236
  })
240
237
 
241
238
  it('renders a default collapsed rail from active sections', () => {
@@ -278,10 +275,9 @@ describe('AppSidebar', () => {
278
275
  expect(wrapper.emitted('update:collapsed')).toEqual([[false]])
279
276
  })
280
277
 
281
- it('renders an empty analysis shell for route-owned sidebar content', () => {
278
+ it('renders an empty shell for route-owned sidebar content', () => {
282
279
  const wrapper = mount(AppSidebar, {
283
280
  props: {
284
- variant: 'analysis',
285
281
  title: 'Sequence',
286
282
  contentId: 'seqgen-sidebar',
287
283
  showWhenEmpty: true,
@@ -292,9 +288,8 @@ describe('AppSidebar', () => {
292
288
  })
293
289
 
294
290
  expect(wrapper.find('.mint-sidebar--hidden').exists()).toBe(false)
295
- expect(wrapper.find('.mint-sidebar--analysis').exists()).toBe(true)
296
291
  expect(wrapper.find('.mint-sidebar__sections').attributes('id')).toBe('seqgen-sidebar')
297
- expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(0)
292
+ expect(wrapper.findAll('.mint-sidebar__group')).toHaveLength(0)
298
293
  expect(wrapper.find('.generate-action').exists()).toBe(true)
299
294
  })
300
295
  })
@@ -303,41 +298,37 @@ describe('AppSidebar', () => {
303
298
  it('should render sections for active view', () => {
304
299
  const wrapper = mount(AppSidebar, {
305
300
  props: { panels: samplePanels, activeView: 'analysis' },
306
- global: { components: { CollapsibleCard } },
307
301
  })
308
- const cards = wrapper.findAllComponents(CollapsibleCard)
302
+ const cards = wrapper.findAll('.mint-sidebar__group')
309
303
  expect(cards).toHaveLength(2)
310
304
  })
311
305
 
312
- it('should render CollapsibleCard for each section', () => {
306
+ it('should render one group for each section', () => {
313
307
  const wrapper = mount(AppSidebar, {
314
308
  props: { panels: samplePanels, activeView: 'analysis' },
315
- global: { components: { CollapsibleCard } },
316
309
  })
317
- const cards = wrapper.findAllComponents(CollapsibleCard)
310
+ const cards = wrapper.findAll('.mint-sidebar__group')
318
311
  expect(cards).toHaveLength(2)
319
312
  })
320
313
 
321
- it('should pass section label as title to CollapsibleCard', () => {
314
+ it('should render the section label in the group header', () => {
322
315
  const wrapper = mount(AppSidebar, {
323
316
  props: { panels: samplePanels, activeView: 'analysis' },
324
- global: { components: { CollapsibleCard } },
325
317
  })
326
- const cards = wrapper.findAllComponents(CollapsibleCard)
327
- expect(cards[0].props('title')).toBe('Parameters')
328
- expect(cards[1].props('title')).toBe('Filters')
318
+ const cards = wrapper.findAll('.mint-sidebar__group')
319
+ expect(cards[0].find('.mint-sidebar__group-label').text()).toBe('Parameters')
320
+ expect(cards[1].find('.mint-sidebar__group-label').text()).toBe('Filters')
329
321
  })
330
322
 
331
- it('should pass section icon to CollapsibleCard', () => {
323
+ it('should render the section icon in the group header', () => {
332
324
  const wrapper = mount(AppSidebar, {
333
325
  props: { panels: samplePanels, activeView: 'analysis' },
334
- global: { components: { CollapsibleCard } },
335
326
  })
336
- const cards = wrapper.findAllComponents(CollapsibleCard)
337
- expect(cards[0].props('icon')).toBe('⚙')
327
+ const cards = wrapper.findAll('.mint-sidebar__group')
328
+ expect(cards[0].find('.mint-sidebar__group-text-icon').text()).toBe('⚙')
338
329
  })
339
330
 
340
- it('passes section badges and actions to CollapsibleCard and re-emits actions', async () => {
331
+ it('renders section badges and actions and re-emits actions', async () => {
341
332
  const wrapper = mount(AppSidebar, {
342
333
  props: {
343
334
  panels: {
@@ -353,42 +344,60 @@ describe('AppSidebar', () => {
353
344
  },
354
345
  activeView: 'analysis',
355
346
  },
356
- global: { components: { CollapsibleCard } },
357
347
  })
358
348
 
359
- const card = wrapper.findComponent(CollapsibleCard)
360
- expect(card.props('badge')).toBe(4)
361
- expect(card.props('badgeTone')).toBe('cta')
349
+ const tag = wrapper.get('.mint-sidebar__tag')
350
+ expect(tag.text()).toBe('4')
351
+ expect(tag.classes()).toContain('mint-sidebar__tag--cta')
362
352
 
363
- await card.vm.$emit('action', 'reset')
353
+ await wrapper.get('.mint-sidebar__group-action').trigger('click')
364
354
  expect(wrapper.emitted('section-action')).toEqual([['filters', 'reset']])
365
355
  })
366
356
 
367
357
  it('should respect defaultOpen on sections', () => {
368
358
  const wrapper = mount(AppSidebar, {
369
359
  props: { panels: samplePanels, activeView: 'analysis' },
370
- global: { components: { CollapsibleCard } },
371
360
  })
372
- const cards = wrapper.findAllComponents(CollapsibleCard)
373
- expect(cards[0].props('defaultOpen')).toBe(true)
374
- expect(cards[1].props('defaultOpen')).toBe(false)
361
+ const cards = wrapper.findAll('.mint-sidebar__group')
362
+ expect(cards[0].classes()).toContain('mint-sidebar__group--open')
363
+ expect(cards[1].classes()).toContain('mint-sidebar__group--closed')
364
+ })
365
+
366
+ it('toggles a section open state from its header', async () => {
367
+ const wrapper = mount(AppSidebar, {
368
+ props: { panels: samplePanels, activeView: 'analysis' },
369
+ })
370
+
371
+ await wrapper.findAll('.mint-sidebar__group-toggle')[1].trigger('click')
372
+ expect(wrapper.findAll('.mint-sidebar__group')[1].classes()).toContain('mint-sidebar__group--open')
373
+ })
374
+
375
+ it('emits update:toggle from the inline section switch', async () => {
376
+ const wrapper = mount(AppSidebar, {
377
+ props: {
378
+ panels: { analysis: [{ id: 'filters', label: 'Filters', showToggle: true }] },
379
+ activeView: 'analysis',
380
+ toggleState: { filters: false },
381
+ },
382
+ })
383
+
384
+ await wrapper.get('.mint-sidebar__switch').trigger('click')
385
+ expect(wrapper.emitted('update:toggle')).toEqual([['filters', true]])
375
386
  })
376
387
 
377
388
  it('should switch sections when activeView changes', async () => {
378
389
  const wrapper = mount(AppSidebar, {
379
390
  props: { panels: samplePanels, activeView: 'analysis' },
380
- global: { components: { CollapsibleCard } },
381
391
  })
382
- expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(2)
392
+ expect(wrapper.findAll('.mint-sidebar__group')).toHaveLength(2)
383
393
 
384
394
  await wrapper.setProps({ activeView: 'results' })
385
- expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(1)
395
+ expect(wrapper.findAll('.mint-sidebar__group')).toHaveLength(1)
386
396
  })
387
397
 
388
398
  it('should hide when switching to view with no panels', async () => {
389
399
  const wrapper = mount(AppSidebar, {
390
400
  props: { panels: samplePanels, activeView: 'analysis' },
391
- global: { components: { CollapsibleCard } },
392
401
  })
393
402
  expect(wrapper.find('.mint-sidebar--hidden').exists()).toBe(false)
394
403
 
@@ -401,7 +410,6 @@ describe('AppSidebar', () => {
401
410
  it('should render named section slots', () => {
402
411
  const wrapper = mount(AppSidebar, {
403
412
  props: { panels: samplePanels, activeView: 'analysis' },
404
- global: { components: { CollapsibleCard } },
405
413
  slots: {
406
414
  'section-parameters': '<div class="params-content">Parameters content</div>',
407
415
  'section-filters': '<div class="filters-content">Filters content</div>',
@@ -414,7 +422,6 @@ describe('AppSidebar', () => {
414
422
  it('should not render section slots for inactive view', () => {
415
423
  const wrapper = mount(AppSidebar, {
416
424
  props: { panels: samplePanels, activeView: 'results' },
417
- global: { components: { CollapsibleCard } },
418
425
  slots: {
419
426
  'section-parameters': '<div class="params-content">Parameters content</div>',
420
427
  'section-display': '<div class="display-content">Display content</div>',
@@ -432,13 +439,12 @@ describe('AppSidebar', () => {
432
439
  controls: sampleControls,
433
440
  activeView: 'analysis',
434
441
  },
435
- global: { components: { CollapsibleCard } },
436
442
  })
437
443
 
438
- const cards = wrapper.findAllComponents(CollapsibleCard)
444
+ const cards = wrapper.findAll('.mint-sidebar__group')
439
445
  expect(cards).toHaveLength(2)
440
- expect(cards[0].props('title')).toBe('Parameters')
441
- expect(cards[1].props('title')).toBe('Output')
446
+ expect(cards[0].find('.mint-sidebar__group-label').text()).toBe('Parameters')
447
+ expect(cards[1].find('.mint-sidebar__group-label').text()).toBe('Output')
442
448
  expect((wrapper.find('input').element as HTMLInputElement).value).toBe('0.05')
443
449
  })
444
450
 
@@ -447,24 +453,22 @@ describe('AppSidebar', () => {
447
453
  props: {
448
454
  controls: sampleControls,
449
455
  },
450
- global: { components: { CollapsibleCard } },
451
456
  })
452
457
 
453
- const cards = wrapper.findAllComponents(CollapsibleCard)
458
+ const cards = wrapper.findAll('.mint-sidebar__group')
454
459
  expect(wrapper.find('.mint-sidebar--hidden').exists()).toBe(false)
455
460
  expect(cards).toHaveLength(2)
456
- expect(cards[0].props('title')).toBe('Parameters')
461
+ expect(cards[0].find('.mint-sidebar__group-label').text()).toBe('Parameters')
457
462
  })
458
463
 
459
464
  it('accepts the full useControlSchema sidebar binding without activeView', () => {
460
465
  const toolkit = useControlSchema(sampleControls)
461
466
  const wrapper = mount(AppSidebar, {
462
467
  props: toolkit.sidebar,
463
- global: { components: { CollapsibleCard } },
464
468
  })
465
469
 
466
470
  expect(wrapper.find('.mint-sidebar--hidden').exists()).toBe(false)
467
- expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(2)
471
+ expect(wrapper.findAll('.mint-sidebar__group')).toHaveLength(2)
468
472
  expect(wrapper.attributes('viewids')).toBeUndefined()
469
473
  expect(wrapper.attributes('defaultview')).toBeUndefined()
470
474
  })
@@ -491,12 +495,11 @@ describe('AppSidebar', () => {
491
495
  model,
492
496
  modelValue: { threshold: '0.25' },
493
497
  },
494
- global: { components: { CollapsibleCard } },
495
498
  })
496
499
 
497
500
  expect(wrapper.find('.mint-sidebar--hidden').exists()).toBe(false)
498
- expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(1)
499
- expect(wrapper.findComponent(CollapsibleCard).props('title')).toBe('Parameters')
501
+ expect(wrapper.findAll('.mint-sidebar__group')).toHaveLength(1)
502
+ expect(wrapper.find('.mint-sidebar__group-label').text()).toBe('Parameters')
500
503
  expect((wrapper.find('input').element as HTMLInputElement).value).toBe('0.25')
501
504
  expect((wrapper.find('select').element as HTMLSelectElement).value).toBe('linear')
502
505
 
@@ -538,7 +541,6 @@ describe('AppSidebar', () => {
538
541
  },
539
542
  },
540
543
  },
541
- global: { components: { CollapsibleCard } },
542
544
  })
543
545
 
544
546
  expect((wrapper.find('input').element as HTMLInputElement).value).toBe('0.25')
@@ -552,7 +554,6 @@ describe('AppSidebar', () => {
552
554
  activeView: 'analysis',
553
555
  values: { untouched: 'keep' },
554
556
  },
555
- global: { components: { CollapsibleCard } },
556
557
  })
557
558
 
558
559
  await wrapper.find('input').setValue('0.5')
@@ -569,7 +570,6 @@ describe('AppSidebar', () => {
569
570
  const workspace = useControlWorkspace(sampleControls)
570
571
  const wrapper = mount(AppSidebar, {
571
572
  props: workspace.sidebar,
572
- global: { components: { CollapsibleCard } },
573
573
  })
574
574
 
575
575
  expect(wrapper.props('modelValue')).toBe(workspace.values)
@@ -594,7 +594,6 @@ describe('AppSidebar', () => {
594
594
  threshold: '0.25',
595
595
  },
596
596
  },
597
- global: { components: { CollapsibleCard } },
598
597
  })
599
598
 
600
599
  expect(wrapper.find('.mint-sidebar--hidden').exists()).toBe(true)
@@ -603,9 +602,9 @@ describe('AppSidebar', () => {
603
602
  controls: sampleControls,
604
603
  })
605
604
 
606
- const cards = wrapper.findAllComponents(CollapsibleCard)
605
+ const cards = wrapper.findAll('.mint-sidebar__group')
607
606
  expect(cards).toHaveLength(2)
608
- expect(cards[0].props('title')).toBe('Parameters')
607
+ expect(cards[0].find('.mint-sidebar__group-label').text()).toBe('Parameters')
609
608
  expect((wrapper.find('input').element as HTMLInputElement).value).toBe('0.25')
610
609
  expect((wrapper.find('select').element as HTMLSelectElement).value).toBe('linear')
611
610
  })
@@ -621,13 +620,12 @@ describe('AppSidebar', () => {
621
620
  ],
622
621
  },
623
622
  },
624
- global: { components: { CollapsibleCard } },
625
623
  })
626
624
 
627
- const cards = wrapper.findAllComponents(CollapsibleCard)
625
+ const cards = wrapper.findAll('.mint-sidebar__group')
628
626
  expect(cards).toHaveLength(2)
629
- expect(cards[0].props('title')).toBe('Manual Parameters')
630
- expect(cards[1].props('title')).toBe('Output')
627
+ expect(cards[0].find('.mint-sidebar__group-label').text()).toBe('Manual Parameters')
628
+ expect(cards[1].find('.mint-sidebar__group-label').text()).toBe('Output')
631
629
  })
632
630
 
633
631
  it('renders FormBuilder schemas when a section has no custom slot', () => {
@@ -638,7 +636,6 @@ describe('AppSidebar', () => {
638
636
  forms: sampleForms,
639
637
  values: { threshold: '0.25' },
640
638
  },
641
- global: { components: { CollapsibleCard } },
642
639
  })
643
640
 
644
641
  expect(wrapper.find('.mint-form-builder').exists()).toBe(true)
@@ -658,7 +655,6 @@ describe('AppSidebar', () => {
658
655
  },
659
656
  },
660
657
  },
661
- global: { components: { CollapsibleCard } },
662
658
  })
663
659
 
664
660
  expect((wrapper.find('input').element as HTMLInputElement).value).toBe('0.35')
@@ -681,7 +677,6 @@ describe('AppSidebar', () => {
681
677
  forms: sampleForms,
682
678
  showFormActions: true,
683
679
  },
684
- global: { components: { CollapsibleCard } },
685
680
  })
686
681
 
687
682
  expect(wrapper.find('.mint-form-actions').exists()).toBe(true)
@@ -695,7 +690,6 @@ describe('AppSidebar', () => {
695
690
  forms: sampleForms,
696
691
  values: { threshold: '0.25', untouched: 'keep' },
697
692
  },
698
- global: { components: { CollapsibleCard } },
699
693
  })
700
694
 
701
695
  await wrapper.find('input').setValue('0.5')
@@ -714,7 +708,6 @@ describe('AppSidebar', () => {
714
708
  forms: sampleForms,
715
709
  modelValue: { threshold: '0.25', untouched: 'keep' },
716
710
  },
717
- global: { components: { CollapsibleCard } },
718
711
  })
719
712
 
720
713
  expect((wrapper.find('input').element as HTMLInputElement).value).toBe('0.25')
@@ -738,7 +731,6 @@ describe('AppSidebar', () => {
738
731
  activeView: 'analysis',
739
732
  forms: sampleForms,
740
733
  },
741
- global: { components: { CollapsibleCard } },
742
734
  slots: {
743
735
  'section-parameters': '<div class="manual-controls">Manual controls</div>',
744
736
  },
@@ -752,7 +744,6 @@ describe('AppSidebar', () => {
752
744
  describe('slots', () => {
753
745
  it('renders the default slot as sidebar content without panels', () => {
754
746
  const wrapper = mount(AppSidebar, {
755
- props: { variant: 'analysis' },
756
747
  slots: {
757
748
  default: '<div class="route-sidebar-content">Route controls</div>',
758
749
  },
@@ -819,7 +810,7 @@ describe('AppSidebar', () => {
819
810
  props: { panels: samplePanels },
820
811
  })
821
812
  expect(wrapper.find('.mint-sidebar--hidden').exists()).toBe(false)
822
- expect(wrapper.findAllComponents(CollapsibleCard)).toHaveLength(2)
813
+ expect(wrapper.findAll('.mint-sidebar__group')).toHaveLength(2)
823
814
  expect(wrapper.find('.mint-sidebar__sections').exists()).toBe(true)
824
815
  })
825
816
  })
@@ -841,7 +832,6 @@ describe('AppSidebar', () => {
841
832
  it('should render all slots with sections', () => {
842
833
  const wrapper = mount(AppSidebar, {
843
834
  props: { panels: samplePanels, activeView: 'analysis' },
844
- global: { components: { CollapsibleCard } },
845
835
  slots: {
846
836
  header: '<div class="header">Header</div>',
847
837
  footer: '<div class="footer">Footer</div>',
@@ -1,5 +1,5 @@
1
1
  import { mount } from '@vue/test-utils'
2
- import { afterEach, describe, expect, it } from 'vitest'
2
+ import { afterEach, describe, expect, it, vi } from 'vitest'
3
3
  import { nextTick } from 'vue'
4
4
  import AppToastContainer from '../../components/AppToastContainer.vue'
5
5
  import { useToast } from '../../composables/useToast'
@@ -34,4 +34,58 @@ describe('AppToastContainer', () => {
34
34
 
35
35
  expect(toast.toasts.value).toHaveLength(0)
36
36
  })
37
+
38
+ it('renders message-only toasts as the title (back-compat)', async () => {
39
+ const wrapper = mountContainer()
40
+ toast.info('Saved', 10000)
41
+ await nextTick()
42
+
43
+ expect(wrapper.find('.mint-toast__title').text()).toBe('Saved')
44
+ expect(wrapper.find('.mint-toast__detail').exists()).toBe(false)
45
+ expect(wrapper.find('.mint-toast__progress').exists()).toBe(true)
46
+ })
47
+
48
+ it('renders detail and caps actions at two', async () => {
49
+ const wrapper = mountContainer()
50
+ toast.push({
51
+ title: 'Deleted 3 samples',
52
+ detail: 'Undo within 30 s.',
53
+ type: 'error',
54
+ duration: 10000,
55
+ actions: [
56
+ { label: 'Undo', onClick: () => {}, primary: true },
57
+ { label: 'Dismiss', onClick: () => {} },
58
+ { label: 'Extra', onClick: () => {} },
59
+ ],
60
+ })
61
+ await nextTick()
62
+
63
+ expect(wrapper.find('.mint-toast__detail').text()).toBe('Undo within 30 s.')
64
+ expect(wrapper.findAll('.mint-toast__action')).toHaveLength(2)
65
+ expect(wrapper.find('.mint-toast__action--primary').text()).toBe('Undo')
66
+ })
67
+
68
+ it('renders no TTL bar and never auto-dismisses when progress is indeterminate', async () => {
69
+ vi.useFakeTimers()
70
+ try {
71
+ const wrapper = mountContainer()
72
+ toast.push({ title: 'Uploading', type: 'info', progress: 'indeterminate', duration: 100 })
73
+ await nextTick()
74
+
75
+ expect(wrapper.find('.mint-toast__progress--indeterminate').exists()).toBe(true)
76
+ vi.advanceTimersByTime(1000)
77
+ expect(toast.toasts.value).toHaveLength(1)
78
+ } finally {
79
+ vi.useRealTimers()
80
+ }
81
+ })
82
+
83
+ it('renders the ×N pill for a collapsed stack', async () => {
84
+ const wrapper = mountContainer()
85
+ toast.push({ title: 'Retention time drift', type: 'warning', count: 4, duration: 10000 })
86
+ await nextTick()
87
+
88
+ expect(wrapper.find('.mint-toast__count').text()).toBe('×4')
89
+ expect(wrapper.find('.mint-toast__item--stacked').exists()).toBe(true)
90
+ })
37
91
  })
@@ -32,7 +32,6 @@ 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')
36
35
  expect(wrapper.find('.mint-bio-template-renderer').exists()).toBe(true)
37
36
  expect(wrapper.text()).toContain('plate-map')
38
37
  expect(wrapper.text()).toContain('dose-response')
@@ -69,26 +68,6 @@ describe('BioTemplatePresetWorkspaceView', () => {
69
68
  expect(wrapper.text()).toContain('dose-response')
70
69
  })
71
70
 
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
-
92
71
  it('updates the internally generated workspace when the preset changes after mount', async () => {
93
72
  const wrapper = mount(BioTemplatePresetWorkspaceView, {
94
73
  props: {
@@ -0,0 +1,24 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import ChartContainer from '../../components/ChartContainer.vue'
5
+
6
+ describe('ChartContainer', () => {
7
+ it('renders workbench header, subhead, and footer slots in a filling frame', () => {
8
+ const wrapper = mount(ChartContainer, {
9
+ props: { variant: 'frame' },
10
+ slots: {
11
+ header: 'Header',
12
+ subhead: 'Subhead',
13
+ default: 'Chart',
14
+ footer: 'Footer',
15
+ },
16
+ })
17
+
18
+ expect(wrapper.classes()).toContain('mint-chart--frame')
19
+ expect(wrapper.text()).toContain('Header')
20
+ expect(wrapper.text()).toContain('Subhead')
21
+ expect(wrapper.text()).toContain('Chart')
22
+ expect(wrapper.get('.mint-chart__footer').text()).toBe('Footer')
23
+ })
24
+ })
@@ -0,0 +1,27 @@
1
+ import { mount } from '@vue/test-utils'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import AdductText from '../../components/AdductText.vue'
5
+ import ChemicalFormula from '../../components/ChemicalFormula.vue'
6
+
7
+ describe('ChemicalFormula', () => {
8
+ it('renders formula typography and an adduct as separate parts', () => {
9
+ const wrapper = mount(ChemicalFormula, {
10
+ props: { formula: 'C6H12O6', adduct: '[M+H]+', pill: true },
11
+ })
12
+
13
+ expect(wrapper.findAll('.mint-chem-formula__subscript').map(node => node.text()))
14
+ .toEqual(['6', '12', '6'])
15
+ expect(wrapper.getComponent(AdductText).text()).toBe('[M+H]+')
16
+ expect(wrapper.classes()).toContain('mint-chem-formula--pill')
17
+ })
18
+
19
+ it('renders explicit empty text when no formula is available', () => {
20
+ expect(mount(ChemicalFormula, { props: { emptyText: 'Unknown' } }).text()).toBe('Unknown')
21
+ })
22
+
23
+ it('supports standalone sans-serif adduct labels', () => {
24
+ expect(mount(AdductText, { props: { value: '[M-H]-', font: 'sans' } }).classes())
25
+ .toContain('mint-adduct-text--sans')
26
+ })
27
+ })
@@ -105,7 +105,6 @@ describe('ControlWorkspaceView', () => {
105
105
  expect(wrapper.findComponent(AppTopBar).props('currentPillId')).toBe('analysis')
106
106
  expect(wrapper.findComponent(AppLayout).props('responsiveSidebar')).toBe(true)
107
107
  expect(wrapper.findComponent(AppSidebar).props('activeView')).toBe('analysis')
108
- expect(wrapper.findComponent(AppSidebar).props('variant')).toBe('analysis')
109
108
  expect(wrapper.findComponent(FormBuilder).props('modelValue')).toMatchObject(workspace.values)
110
109
  })
111
110
 
@@ -123,7 +122,6 @@ describe('ControlWorkspaceView', () => {
123
122
  })
124
123
 
125
124
  expect(wrapper.findComponent(AppLayout).props('responsiveSidebar')).toBe(false)
126
- expect(wrapper.findComponent(AppSidebar).props('variant')).toBe('default')
127
125
  expect(wrapper.findComponent(AppSidebar).props('title')).toBe('Peak Picking')
128
126
  expect(wrapper.findComponent(AppSidebar).props('subtitle')).toBe('Current experiment')
129
127
  expect(wrapper.findComponent(AppSidebar).props('badge')).toBe(3)
@@ -102,7 +102,6 @@ describe('DoseDesignWorkspaceView', () => {
102
102
  const wellPlate = wrapper.findComponent(WellPlateStub)
103
103
  const doseCalculator = wrapper.findComponent(DoseCalculatorStub)
104
104
 
105
- expect(wrapper.findComponent(AppSidebar).props('variant')).toBe('analysis')
106
105
  expect(wellPlate.props()).toMatchObject({
107
106
  modelValue: ['A1'],
108
107
  format: 384,
@@ -150,7 +149,6 @@ describe('DoseDesignWorkspaceView', () => {
150
149
  })
151
150
 
152
151
  expect(wrapper.findComponent(AppLayout).props('responsiveSidebar')).toBe(false)
153
- expect(wrapper.findComponent(AppSidebar).props('variant')).toBe('default')
154
152
  expect(wrapper.findComponent(AppSidebar).props('title')).toBe('Dose Controls')
155
153
  expect(wrapper.findComponent(AppSidebar).props('subtitle')).toBe('Plate setup')
156
154
  expect(wrapper.findComponent(AppSidebar).props('badge')).toBe('ready')