@morscherlab/mint-sdk 1.0.63 → 1.1.0-beta.10
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 +119 -6
- package/dist/EmptyState-DgBRoYAy.js +647 -0
- package/dist/EmptyState-DgBRoYAy.js.map +1 -0
- package/dist/{ExperimentPopover-BbPkIFsI.js → ExperimentPopover-BFw3QX8L.js} +10 -9
- package/dist/{ExperimentPopover-BbPkIFsI.js.map → ExperimentPopover-BFw3QX8L.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-DZeSd-yr.js → ExperimentSelectorModal-BBWXC7KW.js} +12 -216
- package/dist/ExperimentSelectorModal-BBWXC7KW.js.map +1 -0
- package/dist/{SettingsModal-CJgLadsN.js → SettingsModal-Dpg-X1OM.js} +12 -9
- package/dist/{SettingsModal-CJgLadsN.js.map → SettingsModal-Dpg-X1OM.js.map} +1 -1
- package/dist/__tests__/components/BaseToggle.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedPathInput.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedPlotlyResult.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedPluginView.test.d.ts +1 -0
- package/dist/__tests__/components/GeneratedResultRenderer.test.d.ts +1 -0
- package/dist/__tests__/components/InstrumentAlertLog.test.d.ts +1 -0
- package/dist/__tests__/components/SectionCard.test.d.ts +1 -0
- package/dist/__tests__/composables/useGeneratedAnalysis.test.d.ts +1 -0
- package/dist/__tests__/generatedUi.test.d.ts +1 -0
- package/dist/__tests__/types/GeneratedJsonSchema.test.d.ts +1 -0
- package/dist/{analysisArtifactTypes-Cu40dzSG.js → analysisArtifactTypes-ejqR8MX3.js} +1 -1
- package/dist/{analysisArtifactTypes-Cu40dzSG.js.map → analysisArtifactTypes-ejqR8MX3.js.map} +1 -1
- package/dist/{auth-DZAwEeis.js → auth-B87xM--s.js} +1 -1
- package/dist/{auth-DZAwEeis.js.map → auth-B87xM--s.js.map} +1 -1
- package/dist/chunk-BoAXSpZd.js +33 -0
- package/dist/components/AppSidebar.vue.d.ts +1 -1
- package/dist/components/AutoGroupModal.vue.d.ts +2 -2
- package/dist/components/BaseSelect.vue.d.ts +1 -1
- package/dist/components/BaseToggle.vue.d.ts +2 -0
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
- package/dist/components/CollapsibleCard.vue.d.ts +1 -1
- package/dist/components/ComponentBindingRenderer.vue.d.ts +1 -1
- package/dist/components/ConfirmDialog.vue.d.ts +1 -1
- package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
- package/dist/components/ExperimentTimeline.vue.d.ts +1 -1
- package/dist/components/FileUploader.vue.d.ts +4 -2
- package/dist/components/FitPanel.vue.d.ts +2 -2
- package/dist/components/FormActions.vue.d.ts +1 -1
- package/dist/components/FormBuilder.vue.d.ts +1 -1
- package/dist/components/GeneratedPathInput.vue.d.ts +12 -0
- package/dist/components/GeneratedPlotlyResult.vue.d.ts +11 -0
- package/dist/components/GeneratedPluginView.vue.d.ts +10 -0
- package/dist/components/GeneratedResultRenderer.vue.d.ts +6 -0
- package/dist/components/JobsStatusTray.vue.d.ts +5 -1
- package/dist/components/LcmsSequenceTable.vue.d.ts +1 -1
- package/dist/components/PluginWorkspaceView.props.d.ts +1 -1
- package/dist/components/SampleLegend.vue.d.ts +1 -1
- package/dist/components/SampleSelector.vue.d.ts +2 -2
- package/dist/components/SectionCard.vue.d.ts +47 -0
- package/dist/components/SettingsModal.vue.d.ts +1 -1
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -2
- package/dist/components/index.d.ts +5 -0
- package/dist/components/index.js +8 -9
- package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +2 -2
- package/dist/{components-DdzHh3a_.js → components-Ci_BTx00.js} +2322 -2292
- package/dist/components-Ci_BTx00.js.map +1 -0
- package/dist/composables/index.d.ts +3 -2
- package/dist/composables/index.js +10 -11
- package/dist/composables/useGeneratedAnalysis.d.ts +62 -0
- package/dist/composables/useJobsStatusTray.d.ts +48 -4
- package/dist/composables/usePlatformContext.d.ts +3 -0
- package/dist/composables/usePluginClient.d.ts +17 -8
- package/dist/{composables-BUUphBqg.js → composables-C0VEJ73O.js} +154 -38
- package/dist/composables-C0VEJ73O.js.map +1 -0
- package/dist/experiment-utils-CxkruZ3G.js +392 -0
- package/dist/experiment-utils-CxkruZ3G.js.map +1 -0
- package/dist/generated/schema.d.ts +7 -0
- package/dist/generated-ui/main.d.ts +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -18
- package/dist/index.js.map +1 -1
- package/dist/install.js +7 -8
- package/dist/install.js.map +1 -1
- package/dist/plotly.min-BR53hja4.js +180634 -0
- package/dist/plotly.min-BR53hja4.js.map +1 -0
- package/dist/stores/auth.d.ts +2 -2
- package/dist/stores/index.js +1 -1
- package/dist/styles.css +8657 -8217
- package/dist/tailwind.preset.d.ts +5 -0
- package/dist/tailwind.preset.js +7 -2
- package/dist/tailwind.preset.js.map +1 -1
- package/dist/templates/index.js +4 -3
- package/dist/templates/packs.d.ts +0 -1
- package/dist/{templates-CNbPQNID.js → templates-Cdj93S9C.js} +2 -4
- package/dist/templates-Cdj93S9C.js.map +1 -0
- package/dist/types/components.d.ts +2 -0
- package/dist/types/form-builder.d.ts +1 -1
- package/dist/types/generated-ui.d.ts +183 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +1 -1
- package/dist/types/instrument.d.ts +1 -0
- package/dist/types/jobs.d.ts +7 -1
- package/dist/types/platform.d.ts +2 -0
- package/dist/{useControlSchema-BZNdalmL.js → useControlSchema-B_qgU4rW.js} +1882 -172
- package/dist/useControlSchema-B_qgU4rW.js.map +1 -0
- package/dist/{useDropdownState-Bb-c_PeE.js → useDropdownState-gVaMXtwW.js} +2 -2
- package/dist/{useDropdownState-Bb-c_PeE.js.map → useDropdownState-gVaMXtwW.js.map} +1 -1
- package/dist/{useExperimentSelector-DRbj6vse.js → useExperimentSelector-BuQZRgMt.js} +4 -255
- package/dist/useExperimentSelector-BuQZRgMt.js.map +1 -0
- package/dist/{useFormBuilder-DKekvXRs.js → useFormBuilder-D1G5J5Z5.js} +2 -2
- package/dist/{useFormBuilder-DKekvXRs.js.map → useFormBuilder-D1G5J5Z5.js.map} +1 -1
- package/dist/{useAnalysisArtifacts-C3l0a0vu.js → useProtocolTemplates-DeaOmMaw.js} +307 -475
- package/dist/useProtocolTemplates-DeaOmMaw.js.map +1 -0
- package/package.json +7 -3
- package/src/__tests__/components/BaseSelect.test.ts +52 -0
- package/src/__tests__/components/BaseToggle.test.ts +40 -0
- package/src/__tests__/components/GeneratedPathInput.test.ts +132 -0
- package/src/__tests__/components/GeneratedPlotlyResult.test.ts +117 -0
- package/src/__tests__/components/GeneratedPluginView.test.ts +240 -0
- package/src/__tests__/components/GeneratedResultRenderer.test.ts +93 -0
- package/src/__tests__/components/InstrumentAlertLog.test.ts +59 -0
- package/src/__tests__/components/PluginWorkspaceView.test.ts +68 -14
- package/src/__tests__/components/SectionCard.test.ts +181 -0
- package/src/__tests__/composables/formBuilderRegistry.test.ts +5 -1
- package/src/__tests__/composables/useApi.test.ts +8 -0
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +1742 -0
- package/src/__tests__/composables/useJobsStatusTray.test.ts +179 -1
- package/src/__tests__/composables/usePlatformContext.test.ts +28 -0
- package/src/__tests__/composables/usePluginClient.test.ts +402 -21
- package/src/__tests__/composables/usePluginConfig.test.ts +323 -7
- package/src/__tests__/docs/extractDocsTemplates.test.ts +0 -1
- package/src/__tests__/generatedUi.test.ts +119 -0
- package/src/__tests__/templates/templates.test.ts +1 -1
- package/src/__tests__/types/GeneratedJsonSchema.test.ts +62 -0
- package/src/components/BaseSelect.vue +16 -2
- package/src/components/BaseToggle.vue +3 -1
- package/src/components/FileUploader.vue +49 -11
- package/src/components/GeneratedPathInput.vue +218 -0
- package/src/components/GeneratedPlotlyResult.vue +87 -0
- package/src/components/GeneratedPluginView.vue +279 -0
- package/src/components/GeneratedResultRenderer.vue +137 -0
- package/src/components/InstrumentAlertLog.vue +2 -2
- package/src/components/JobsStatusTray.vue +14 -8
- package/src/components/PluginWorkspaceView.props.ts +1 -1
- package/src/components/PluginWorkspaceView.vue +1 -5
- package/src/components/SectionCard.story.vue +163 -0
- package/src/components/SectionCard.vue +98 -0
- package/src/components/index.ts +5 -0
- package/src/composables/formBuilderRegistry.ts +2 -0
- package/src/composables/index.ts +19 -0
- package/src/composables/useApi.ts +1 -0
- package/src/composables/useGeneratedAnalysis.ts +1270 -0
- package/src/composables/useJobsStatusTray.ts +234 -45
- package/src/composables/usePluginClient.ts +164 -30
- package/src/composables/usePluginConfig.ts +156 -20
- package/src/generated/schema.ts +130 -0
- package/src/generated-ui/main.ts +21 -0
- package/src/index.ts +3 -0
- package/src/jobs.ts +5 -0
- package/src/plotly-dist.d.ts +3 -0
- package/src/styles/components/section-card.css +161 -0
- package/src/styles/index.css +1 -0
- package/src/styles/variables.css +25 -0
- package/src/tailwind.preset.ts +5 -0
- package/src/templates/packs.ts +0 -3
- package/src/types/components.ts +12 -0
- package/src/types/form-builder.ts +1 -0
- package/src/types/generated-ui.ts +189 -0
- package/src/types/index.ts +4 -0
- package/src/types/instrument.ts +1 -0
- package/src/types/jobs.ts +10 -1
- package/src/types/platform.ts +2 -0
- package/dist/BaseButton-Dgqrze41.js +0 -74
- package/dist/BaseButton-Dgqrze41.js.map +0 -1
- package/dist/BaseModal-B9UA8Y_I.js +0 -165
- package/dist/BaseModal-B9UA8Y_I.js.map +0 -1
- package/dist/BaseSelect-ekgr9fDo.js +0 -179
- package/dist/BaseSelect-ekgr9fDo.js.map +0 -1
- package/dist/ExperimentPopover-Ryusjrhv.js +0 -4
- package/dist/ExperimentSelectorModal-Cg4-LXBD.js +0 -5
- package/dist/ExperimentSelectorModal-DZeSd-yr.js.map +0 -1
- package/dist/SettingsModal-Bl_9w2Y3.js +0 -5
- package/dist/components-DdzHh3a_.js.map +0 -1
- package/dist/composables-BUUphBqg.js.map +0 -1
- package/dist/experiment-utils-Bfa7CwPU.js +0 -108
- package/dist/experiment-utils-Bfa7CwPU.js.map +0 -1
- package/dist/templates-CNbPQNID.js.map +0 -1
- package/dist/useAnalysisArtifacts-C3l0a0vu.js.map +0 -1
- package/dist/useControlSchema-BZNdalmL.js.map +0 -1
- package/dist/useEventListener-CfVkP9Xz.js +0 -57
- package/dist/useEventListener-CfVkP9Xz.js.map +0 -1
- package/dist/useExperimentSelector-DRbj6vse.js.map +0 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import ChartContainer from '../../components/ChartContainer.vue'
|
|
5
|
+
import DataFrame from '../../components/DataFrame.vue'
|
|
6
|
+
import GeneratedResultRenderer from '../../components/GeneratedResultRenderer.vue'
|
|
7
|
+
|
|
8
|
+
describe('GeneratedResultRenderer', () => {
|
|
9
|
+
it('renders tabular results with the standard DataFrame component', () => {
|
|
10
|
+
const wrapper = mount(GeneratedResultRenderer, {
|
|
11
|
+
props: {
|
|
12
|
+
result: {
|
|
13
|
+
kind: 'table',
|
|
14
|
+
columns: ['sample', 'value'],
|
|
15
|
+
rows: [{ sample: 'A', value: 1.5 }],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
expect(wrapper.findComponent(DataFrame).props()).toMatchObject({
|
|
21
|
+
data: [{ sample: 'A', value: 1.5 }],
|
|
22
|
+
columns: [
|
|
23
|
+
{ key: 'sample', label: 'Sample', sortable: true },
|
|
24
|
+
{ key: 'value', label: 'Value', sortable: true },
|
|
25
|
+
],
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('renders Matplotlib images inside the standard chart container', () => {
|
|
30
|
+
const wrapper = mount(GeneratedResultRenderer, {
|
|
31
|
+
props: {
|
|
32
|
+
result: { kind: 'image', media_type: 'image/png', data: 'cG5n', alt: 'PCA' },
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
expect({
|
|
37
|
+
chart: wrapper.findComponent(ChartContainer).exists(),
|
|
38
|
+
source: wrapper.get('img').attributes('src'),
|
|
39
|
+
alt: wrapper.get('img').attributes('alt'),
|
|
40
|
+
}).toEqual({ chart: true, source: 'data:image/png;base64,cG5n', alt: 'PCA' })
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('never interprets JSON results as HTML', () => {
|
|
44
|
+
const wrapper = mount(GeneratedResultRenderer, {
|
|
45
|
+
props: {
|
|
46
|
+
result: { kind: 'json', value: { unsafe: '<script>window.bad = true</script>' } },
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
expect({ scripts: wrapper.findAll('script').length, text: wrapper.text() }).toEqual({
|
|
51
|
+
scripts: 0,
|
|
52
|
+
text: expect.stringContaining('<script>window.bad = true</script>'),
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should preserve the artifact filename when downloading a result', () => {
|
|
57
|
+
const wrapper = mount(GeneratedResultRenderer, {
|
|
58
|
+
props: {
|
|
59
|
+
result: {
|
|
60
|
+
kind: 'artifact',
|
|
61
|
+
id: 'artifact-1',
|
|
62
|
+
name: 'analysis.csv',
|
|
63
|
+
media_type: 'text/csv',
|
|
64
|
+
size: 3,
|
|
65
|
+
data: 'YSxi',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
expect(wrapper.get('a').attributes('download')).toBe('analysis.csv')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('uses the host download route for a managed file result', () => {
|
|
74
|
+
const wrapper = mount(GeneratedResultRenderer, {
|
|
75
|
+
props: {
|
|
76
|
+
result: {
|
|
77
|
+
kind: 'artifact',
|
|
78
|
+
id: 'artifact-2',
|
|
79
|
+
name: 'large.msd',
|
|
80
|
+
media_type: 'application/octet-stream',
|
|
81
|
+
size: 512_000_000,
|
|
82
|
+
download_url: '/api/leaf/jobs/job-1/result/file/scoped-token',
|
|
83
|
+
managed: true,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
expect(wrapper.get('a').attributes()).toMatchObject({
|
|
89
|
+
href: '/api/leaf/jobs/job-1/result/file/scoped-token',
|
|
90
|
+
download: 'large.msd',
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils'
|
|
2
|
+
import { describe, expect, expectTypeOf, it } from 'vitest'
|
|
3
|
+
import type { VNode } from 'vue'
|
|
4
|
+
import InstrumentAlertLog from '../../components/InstrumentAlertLog.vue'
|
|
5
|
+
import type { InstrumentAlert } from '../../types'
|
|
6
|
+
|
|
7
|
+
type EventKeyIsOptional = {} extends Pick<InstrumentAlert, 'event_key'> ? true : false
|
|
8
|
+
|
|
9
|
+
function alert(message: string): InstrumentAlert {
|
|
10
|
+
return {
|
|
11
|
+
event_key: 'orbitrap-01:acquisition-stopped:42',
|
|
12
|
+
instrument_id: 'orbitrap-01',
|
|
13
|
+
level: 'critical',
|
|
14
|
+
rule: 'acquisition-stopped',
|
|
15
|
+
message,
|
|
16
|
+
timestamp: '2026-07-22T08:30:00Z',
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function findAlertVNodeKey(vnode: VNode): VNode['key'] {
|
|
21
|
+
const classes = vnode.props?.class
|
|
22
|
+
if (typeof classes === 'string' && classes.split(' ').includes('mint-instrument-alert')) {
|
|
23
|
+
return vnode.key
|
|
24
|
+
}
|
|
25
|
+
if (Array.isArray(vnode.children)) {
|
|
26
|
+
for (const child of vnode.children) {
|
|
27
|
+
if (typeof child !== 'object' || child === null) continue
|
|
28
|
+
const key = findAlertVNodeKey(child as VNode)
|
|
29
|
+
if (key !== null) return key
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (vnode.component?.subTree) return findAlertVNodeKey(vnode.component.subTree)
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('InstrumentAlertLog', () => {
|
|
37
|
+
it('should require a stable event_key in the public alert type', () => {
|
|
38
|
+
expectTypeOf<EventKeyIsOptional>().toEqualTypeOf<false>()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should retain the rendered alert when mutable content changes for the same event_key', async () => {
|
|
42
|
+
const wrapper = mount(InstrumentAlertLog, {
|
|
43
|
+
props: { alerts: [alert('Acquisition stopped')] },
|
|
44
|
+
})
|
|
45
|
+
const renderedAlert = wrapper.get('.mint-instrument-alert').element
|
|
46
|
+
|
|
47
|
+
await wrapper.setProps({ alerts: [alert('Acquisition remains stopped')] })
|
|
48
|
+
|
|
49
|
+
expect(wrapper.get('.mint-instrument-alert').element).toBe(renderedAlert)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should key each rendered alert by event_key', () => {
|
|
53
|
+
const wrapper = mount(InstrumentAlertLog, {
|
|
54
|
+
props: { alerts: [alert('Acquisition stopped')] },
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
expect(findAlertVNodeKey(wrapper.vm.$.subTree)).toBe('orbitrap-01:acquisition-stopped:42')
|
|
58
|
+
})
|
|
59
|
+
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { flushPromises, mount } from '@vue/test-utils'
|
|
2
|
+
import type { VueWrapper } from '@vue/test-utils'
|
|
2
3
|
import { createPinia } from 'pinia'
|
|
3
4
|
import { computed, h, nextTick, ref } from 'vue'
|
|
4
5
|
import { describe, expect, it, vi } from 'vitest'
|
|
@@ -10,7 +11,7 @@ import ExperimentSelectorModal from '../../components/ExperimentSelectorModal.vu
|
|
|
10
11
|
import PluginWorkspaceView from '../../components/PluginWorkspaceView.vue'
|
|
11
12
|
import { defineDoseDesignControlModel } from '../../composables/useControlSchema'
|
|
12
13
|
import { usePlatformContext } from '../../composables/usePlatformContext'
|
|
13
|
-
import type { PillNavItem, SidebarToolSection } from '../../types'
|
|
14
|
+
import type { PageSelectorItemInput, PillNavItem, SidebarToolSection } from '../../types'
|
|
14
15
|
|
|
15
16
|
vi.mock('../../composables/usePlatformContext', () => ({
|
|
16
17
|
usePlatformContext: vi.fn(() => ({
|
|
@@ -79,6 +80,44 @@ const globalOptions = {
|
|
|
79
80
|
},
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
interface PageSelectorWorkspaceProps {
|
|
84
|
+
pageSelector?: PageSelectorItemInput[]
|
|
85
|
+
currentPageSelectorId?: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function mountIntegratedPageSelectorWorkspace(
|
|
89
|
+
props: PageSelectorWorkspaceProps = {},
|
|
90
|
+
): VueWrapper {
|
|
91
|
+
vi.mocked(usePlatformContext).mockReturnValueOnce({
|
|
92
|
+
isIntegrated: computed(() => true),
|
|
93
|
+
context: ref({ isIntegrated: true, theme: 'system' }),
|
|
94
|
+
plugin: computed(() => ({
|
|
95
|
+
id: 'workspace-plugin',
|
|
96
|
+
name: 'Workspace Plugin',
|
|
97
|
+
version: '1.0.0',
|
|
98
|
+
route_prefix: '/workspace-plugin',
|
|
99
|
+
api_prefix: '/api/workspace-plugin',
|
|
100
|
+
nav_items: [
|
|
101
|
+
{ path: '/platform-one', label: 'Platform One' },
|
|
102
|
+
{ path: '/platform-two', label: 'Platform Two' },
|
|
103
|
+
],
|
|
104
|
+
})),
|
|
105
|
+
user: computed(() => undefined),
|
|
106
|
+
theme: computed(() => 'system' as const),
|
|
107
|
+
features: computed(() => undefined),
|
|
108
|
+
navigate: vi.fn(),
|
|
109
|
+
notify: vi.fn(),
|
|
110
|
+
sendToPlatform: vi.fn(),
|
|
111
|
+
})
|
|
112
|
+
return mount(PluginWorkspaceView, {
|
|
113
|
+
props: {
|
|
114
|
+
title: 'Workspace',
|
|
115
|
+
...props,
|
|
116
|
+
},
|
|
117
|
+
global: globalOptions,
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
82
121
|
describe('PluginWorkspaceView', () => {
|
|
83
122
|
it('renders the standard AppLayout, AppTopBar, and AppSidebar plugin shell', () => {
|
|
84
123
|
const wrapper = mount(PluginWorkspaceView, {
|
|
@@ -146,22 +185,37 @@ describe('PluginWorkspaceView', () => {
|
|
|
146
185
|
expect(wrapper.emitted('pill-select')).toEqual([[{ id: 'results', label: 'Results' }]])
|
|
147
186
|
})
|
|
148
187
|
|
|
149
|
-
it('
|
|
150
|
-
const wrapper =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
188
|
+
it('should use platform pages when pageSelector is omitted', () => {
|
|
189
|
+
const wrapper = mountIntegratedPageSelectorWorkspace()
|
|
190
|
+
|
|
191
|
+
expect(wrapper.find('.mint-page-selector').exists()).toBe(true)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should suppress platform pages when pageSelector is explicitly empty', () => {
|
|
195
|
+
const wrapper = mountIntegratedPageSelectorWorkspace({ pageSelector: [] })
|
|
196
|
+
|
|
197
|
+
expect(wrapper.find('.mint-page-selector').exists()).toBe(false)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('should hide one explicit page without falling back to platform pages', () => {
|
|
201
|
+
const wrapper = mountIntegratedPageSelectorWorkspace({
|
|
202
|
+
pageSelector: [{ id: 'downloads', label: 'Downloads', to: '/' }],
|
|
203
|
+
currentPageSelectorId: 'downloads',
|
|
160
204
|
})
|
|
161
205
|
|
|
162
|
-
expect(wrapper.findComponent(AppTopBar).props('pageSelector')).toBeUndefined()
|
|
163
206
|
expect(wrapper.find('.mint-page-selector').exists()).toBe(false)
|
|
164
|
-
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('should render multiple explicit pages ahead of platform pages', () => {
|
|
210
|
+
const wrapper = mountIntegratedPageSelectorWorkspace({
|
|
211
|
+
pageSelector: [
|
|
212
|
+
{ id: 'overview', label: 'Overview', to: '/' },
|
|
213
|
+
{ id: 'reports', label: 'Reports', to: '/reports' },
|
|
214
|
+
],
|
|
215
|
+
currentPageSelectorId: 'overview',
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
expect(wrapper.get('.mint-page-selector__label').text()).toBe('Overview')
|
|
165
219
|
})
|
|
166
220
|
|
|
167
221
|
it('passes compact control models to the sidebar for generated forms', () => {
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import SectionCard from '../../components/SectionCard.vue'
|
|
4
|
+
|
|
5
|
+
describe('SectionCard', () => {
|
|
6
|
+
describe('basic rendering', () => {
|
|
7
|
+
it('should render container, header, and title when given the required title prop', () => {
|
|
8
|
+
const wrapper = mount(SectionCard, {
|
|
9
|
+
props: { title: 'Experiments' },
|
|
10
|
+
})
|
|
11
|
+
expect(wrapper.find('.mint-section-card').exists()).toBe(true)
|
|
12
|
+
expect(wrapper.find('.mint-section-card__header').exists()).toBe(true)
|
|
13
|
+
expect(wrapper.find('.mint-section-card__title').text()).toBe('Experiments')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should render default slot content in the body', () => {
|
|
17
|
+
const wrapper = mount(SectionCard, {
|
|
18
|
+
props: { title: 'Experiments' },
|
|
19
|
+
slots: { default: '<p class="body-probe">rows</p>' },
|
|
20
|
+
})
|
|
21
|
+
expect(wrapper.find('.mint-section-card__body .body-probe').exists()).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('count meta', () => {
|
|
26
|
+
it('should render the count in the header when provided', () => {
|
|
27
|
+
const wrapper = mount(SectionCard, {
|
|
28
|
+
props: { title: 'Plugins', count: '10 enabled' },
|
|
29
|
+
})
|
|
30
|
+
expect(wrapper.find('.mint-section-card__count').text()).toBe('10 enabled')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should not render a count element when count is omitted', () => {
|
|
34
|
+
const wrapper = mount(SectionCard, {
|
|
35
|
+
props: { title: 'Plugins' },
|
|
36
|
+
})
|
|
37
|
+
expect(wrapper.find('.mint-section-card__count').exists()).toBe(false)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should accept a numeric count', () => {
|
|
41
|
+
const wrapper = mount(SectionCard, {
|
|
42
|
+
props: { title: 'Artifacts', count: 4 },
|
|
43
|
+
})
|
|
44
|
+
expect(wrapper.find('.mint-section-card__count').text()).toBe('4')
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('title style variants', () => {
|
|
49
|
+
it('should apply the uppercase modifier when uppercase is true', () => {
|
|
50
|
+
const wrapper = mount(SectionCard, {
|
|
51
|
+
props: { title: 'Experiments', uppercase: true },
|
|
52
|
+
})
|
|
53
|
+
expect(wrapper.find('.mint-section-card__title--uppercase').exists()).toBe(true)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should not apply the uppercase modifier by default', () => {
|
|
57
|
+
const wrapper = mount(SectionCard, {
|
|
58
|
+
props: { title: 'Design data' },
|
|
59
|
+
})
|
|
60
|
+
expect(wrapper.find('.mint-section-card__title--uppercase').exists()).toBe(false)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('icon chip', () => {
|
|
65
|
+
it('should render an icon chip with an SVG path when icon is provided', () => {
|
|
66
|
+
const wrapper = mount(SectionCard, {
|
|
67
|
+
props: { title: 'Experiments', icon: 'M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7' },
|
|
68
|
+
})
|
|
69
|
+
expect(wrapper.find('.mint-section-card__icon-chip').exists()).toBe(true)
|
|
70
|
+
expect(wrapper.find('.mint-section-card__icon-chip path').exists()).toBe(true)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should render one path per entry when icon is an array of paths', () => {
|
|
74
|
+
const wrapper = mount(SectionCard, {
|
|
75
|
+
props: { title: 'Experiments', icon: ['M4 7v10', 'M4 7c0 2.21'] },
|
|
76
|
+
})
|
|
77
|
+
expect(wrapper.findAll('.mint-section-card__icon-chip path')).toHaveLength(2)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should not render an icon chip when icon is omitted', () => {
|
|
81
|
+
const wrapper = mount(SectionCard, {
|
|
82
|
+
props: { title: 'Experiments' },
|
|
83
|
+
})
|
|
84
|
+
expect(wrapper.find('.mint-section-card__icon-chip').exists()).toBe(false)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should apply the accent modifier class matching the accent prop', () => {
|
|
88
|
+
const wrapper = mount(SectionCard, {
|
|
89
|
+
props: { title: 'Projects', icon: 'M3 7v10', accent: 'project' },
|
|
90
|
+
})
|
|
91
|
+
expect(wrapper.find('.mint-section-card__icon-chip--project').exists()).toBe(true)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should default to the primary accent when accent is omitted', () => {
|
|
95
|
+
const wrapper = mount(SectionCard, {
|
|
96
|
+
props: { title: 'Projects', icon: 'M3 7v10' },
|
|
97
|
+
})
|
|
98
|
+
expect(wrapper.find('.mint-section-card__icon-chip--primary').exists()).toBe(true)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('header divider', () => {
|
|
103
|
+
it('should use the strong divider by default', () => {
|
|
104
|
+
const wrapper = mount(SectionCard, {
|
|
105
|
+
props: { title: 'Experiments' },
|
|
106
|
+
})
|
|
107
|
+
expect(wrapper.find('.mint-section-card__header--divider-light').exists()).toBe(false)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should apply the light divider modifier when divider is light', () => {
|
|
111
|
+
const wrapper = mount(SectionCard, {
|
|
112
|
+
props: { title: 'Design data', divider: 'light' },
|
|
113
|
+
})
|
|
114
|
+
expect(wrapper.find('.mint-section-card__header--divider-light').exists()).toBe(true)
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('fill mode', () => {
|
|
119
|
+
it('should not stretch by default', () => {
|
|
120
|
+
const wrapper = mount(SectionCard, {
|
|
121
|
+
props: { title: 'Experiments' },
|
|
122
|
+
})
|
|
123
|
+
expect(wrapper.find('.mint-section-card--fill').exists()).toBe(false)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('should apply the fill modifier when fill is true', () => {
|
|
127
|
+
const wrapper = mount(SectionCard, {
|
|
128
|
+
props: { title: 'Experiments', fill: true },
|
|
129
|
+
})
|
|
130
|
+
expect(wrapper.find('.mint-section-card--fill').exists()).toBe(true)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('should render the toolbar slot outside the scrolling body', () => {
|
|
134
|
+
const wrapper = mount(SectionCard, {
|
|
135
|
+
props: { title: 'Experiments', fill: true },
|
|
136
|
+
slots: { toolbar: '<div class="toolbar-probe">chips</div>', default: '<p>rows</p>' },
|
|
137
|
+
})
|
|
138
|
+
expect(wrapper.find('.mint-section-card__toolbar .toolbar-probe').exists()).toBe(true)
|
|
139
|
+
expect(wrapper.find('.mint-section-card__body .toolbar-probe').exists()).toBe(false)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should not render a toolbar region when the slot is unused', () => {
|
|
143
|
+
const wrapper = mount(SectionCard, {
|
|
144
|
+
props: { title: 'Experiments' },
|
|
145
|
+
})
|
|
146
|
+
expect(wrapper.find('.mint-section-card__toolbar').exists()).toBe(false)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should keep the header, body and footer structure when filling', () => {
|
|
150
|
+
const wrapper = mount(SectionCard, {
|
|
151
|
+
props: { title: 'Experiments', fill: true },
|
|
152
|
+
slots: { default: '<p>rows</p>', footer: '<span>1 / 3</span>' },
|
|
153
|
+
})
|
|
154
|
+
expect(wrapper.find('.mint-section-card__header').exists()).toBe(true)
|
|
155
|
+
expect(wrapper.find('.mint-section-card__body').exists()).toBe(true)
|
|
156
|
+
expect(wrapper.find('.mint-section-card__footer').exists()).toBe(true)
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
describe('slots', () => {
|
|
161
|
+
it('should render the actions slot at the end of the header', () => {
|
|
162
|
+
const wrapper = mount(SectionCard, {
|
|
163
|
+
props: { title: 'Experiments' },
|
|
164
|
+
slots: { actions: '<a class="actions-probe" href="#">All →</a>' },
|
|
165
|
+
})
|
|
166
|
+
expect(wrapper.find('.mint-section-card__header .actions-probe').exists()).toBe(true)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('should render the footer strip only when the footer slot is provided', () => {
|
|
170
|
+
const withFooter = mount(SectionCard, {
|
|
171
|
+
props: { title: 'Experiments' },
|
|
172
|
+
slots: { footer: '<span class="footer-probe">Showing 1–18 of 42</span>' },
|
|
173
|
+
})
|
|
174
|
+
const withoutFooter = mount(SectionCard, {
|
|
175
|
+
props: { title: 'Experiments' },
|
|
176
|
+
})
|
|
177
|
+
expect(withFooter.find('.mint-section-card__footer .footer-probe').exists()).toBe(true)
|
|
178
|
+
expect(withoutFooter.find('.mint-section-card__footer').exists()).toBe(false)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
})
|
|
@@ -9,7 +9,7 @@ describe('formBuilderRegistry', () => {
|
|
|
9
9
|
'text', 'email', 'password', 'tel', 'url', 'search',
|
|
10
10
|
'number', 'textarea', 'select', 'multiselect',
|
|
11
11
|
'checkbox', 'toggle', 'radio', 'slider', 'tags',
|
|
12
|
-
'date', 'time', 'datetime', 'file',
|
|
12
|
+
'date', 'time', 'datetime', 'file', 'path',
|
|
13
13
|
'formula', 'sequence', 'molecule', 'concentration', 'unit',
|
|
14
14
|
]
|
|
15
15
|
|
|
@@ -59,6 +59,10 @@ describe('formBuilderRegistry', () => {
|
|
|
59
59
|
expect(entry.vModel).toBe(false)
|
|
60
60
|
})
|
|
61
61
|
|
|
62
|
+
it('should bind generated path inputs with v-model', () => {
|
|
63
|
+
expect(getFieldRegistryEntry('path').vModel).toBe(true)
|
|
64
|
+
})
|
|
65
|
+
|
|
62
66
|
it('should have vModel true for all non-file types', () => {
|
|
63
67
|
const vModelTypes: FormFieldType[] = [
|
|
64
68
|
'text', 'email', 'password', 'tel', 'url', 'search',
|
|
@@ -47,6 +47,14 @@ describe('useApi URL helpers', () => {
|
|
|
47
47
|
expect(api.buildUrl('')).toBe('/api/plugin/')
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
+
it('keeps absolute URLs outside the configured API base', () => {
|
|
51
|
+
const api = useApi({ baseUrl: '/api/plugin' })
|
|
52
|
+
|
|
53
|
+
expect(api.buildUrl('https://files.example.test/result.csv')).toBe(
|
|
54
|
+
'https://files.example.test/result.csv',
|
|
55
|
+
)
|
|
56
|
+
})
|
|
57
|
+
|
|
50
58
|
it('joins configured WebSocket base URLs without duplicate slashes', () => {
|
|
51
59
|
vi.stubEnv('VITE_API_PREFIX', '/api/')
|
|
52
60
|
const api = useApi()
|