@morscherlab/mint-sdk 1.0.56 → 1.0.57
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/dist/__tests__/components/ArtifactSaveDialog.test.d.ts +1 -0
- package/dist/__tests__/components/ArtifactSelectorModal.test.d.ts +1 -0
- package/dist/__tests__/composables/useAnalysisArtifacts.test.d.ts +1 -0
- package/dist/analysisArtifactTypes-Cu40dzSG.js +7 -0
- package/dist/analysisArtifactTypes-Cu40dzSG.js.map +1 -0
- package/dist/components/ArtifactSaveDialog.vue.d.ts +56 -0
- package/dist/components/ArtifactSelectorModal.vue.d.ts +41 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +2 -2
- package/dist/{components-BQwOeyiy.js → components-PC3SVsaa.js} +1743 -1144
- package/dist/components-PC3SVsaa.js.map +1 -0
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/index.js +4 -3
- package/dist/composables/useAnalysisArtifacts.d.ts +59 -0
- package/dist/{composables-TpXyhRZZ.js → composables-OYTD0Y3r.js} +2 -2
- package/dist/{composables-TpXyhRZZ.js.map → composables-OYTD0Y3r.js.map} +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/install.js +1 -1
- package/dist/styles.css +412 -0
- package/dist/types/analysisArtifactTypes.d.ts +100 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/{useJobsStatusTray-CVecN6Ao.js → useAnalysisArtifacts-Ob8UtVwl.js} +157 -2
- package/dist/useAnalysisArtifacts-Ob8UtVwl.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/components/ArtifactSaveDialog.test.ts +516 -0
- package/src/__tests__/components/ArtifactSelectorModal.test.ts +178 -0
- package/src/__tests__/composables/useAnalysisArtifacts.test.ts +667 -0
- package/src/components/ArtifactSaveDialog.story.vue +293 -0
- package/src/components/ArtifactSaveDialog.vue +383 -0
- package/src/components/ArtifactSelectorModal.story.vue +295 -0
- package/src/components/ArtifactSelectorModal.vue +326 -0
- package/src/components/index.ts +2 -0
- package/src/composables/index.ts +6 -0
- package/src/composables/useAnalysisArtifacts.ts +305 -0
- package/src/styles/components/artifact-save-dialog.css +17 -0
- package/src/styles/components/artifact-selector-modal.css +216 -0
- package/src/styles/index.css +2 -0
- package/src/types/analysisArtifactTypes.ts +119 -0
- package/src/types/index.ts +17 -0
- package/dist/components-BQwOeyiy.js.map +0 -1
- package/dist/useJobsStatusTray-CVecN6Ao.js.map +0 -1
- /package/dist/{ExperimentPopover-8A4Rhffp.js → ExperimentPopover-Ryusjrhv.js} +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { mount, flushPromises } from '@vue/test-utils'
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
3
|
+
|
|
4
|
+
const mockGet = vi.fn()
|
|
5
|
+
const mockPost = vi.fn()
|
|
6
|
+
const mockPatch = vi.fn()
|
|
7
|
+
|
|
8
|
+
vi.mock('../../composables/useApi', () => ({
|
|
9
|
+
useApi: () => ({ get: mockGet, post: mockPost, patch: mockPatch }),
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
import ArtifactSelectorModal from '../../components/ArtifactSelectorModal.vue'
|
|
13
|
+
import type { AnalysisArtifactSummary } from '../../types/analysisArtifactTypes'
|
|
14
|
+
|
|
15
|
+
function makeArtifact(
|
|
16
|
+
overrides: Partial<AnalysisArtifactSummary> = {},
|
|
17
|
+
): AnalysisArtifactSummary {
|
|
18
|
+
return {
|
|
19
|
+
id: 1,
|
|
20
|
+
experiment_id: 42,
|
|
21
|
+
plugin_id: 'plugin-a',
|
|
22
|
+
artifact_key: 'fit',
|
|
23
|
+
display_name: 'Dose Response Fit',
|
|
24
|
+
status: 'active',
|
|
25
|
+
result_keys: ['summary'],
|
|
26
|
+
created_at: '2026-07-01T10:00:00Z',
|
|
27
|
+
updated_at: '2026-07-02T10:00:00Z',
|
|
28
|
+
...overrides,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const ARTIFACTS = [
|
|
33
|
+
makeArtifact({ id: 1 }),
|
|
34
|
+
makeArtifact({ id: 2, plugin_id: 'plugin-b', artifact_key: 'peaks', display_name: 'Peak Table' }),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
async function mountModal(props: Record<string, unknown> = {}) {
|
|
38
|
+
const wrapper = mount(ArtifactSelectorModal, {
|
|
39
|
+
props: { modelValue: true, experimentId: 42, ...props },
|
|
40
|
+
global: { stubs: { teleport: true } },
|
|
41
|
+
})
|
|
42
|
+
await flushPromises()
|
|
43
|
+
return wrapper
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe('ArtifactSelectorModal', () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
vi.clearAllMocks()
|
|
49
|
+
// Fresh rows per call: the list is stored by reference and spliced on mutation.
|
|
50
|
+
mockGet.mockImplementation(async () => ({ artifacts: ARTIFACTS.map((a) => ({ ...a })) }))
|
|
51
|
+
mockPost.mockResolvedValue(makeArtifact({ status: 'archived' }))
|
|
52
|
+
delete (window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__
|
|
53
|
+
window.history.replaceState({}, '', '/')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should emit the summary and close on select', async () => {
|
|
57
|
+
const wrapper = await mountModal()
|
|
58
|
+
|
|
59
|
+
await wrapper.get('.mint-artifact-selector__row').trigger('click')
|
|
60
|
+
await flushPromises()
|
|
61
|
+
|
|
62
|
+
expect(wrapper.emitted('select')?.[0]?.[0]).toMatchObject({ id: 1 })
|
|
63
|
+
expect(wrapper.emitted('update:modelValue')?.at(-1)).toEqual([false])
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('should emit selectDetail alongside select when fetchDetailOnSelect is set', async () => {
|
|
67
|
+
const wrapper = await mountModal({ fetchDetailOnSelect: true })
|
|
68
|
+
mockGet.mockResolvedValueOnce({ ...makeArtifact(), result: { x: 1 }, tree: [], summary: null })
|
|
69
|
+
|
|
70
|
+
await wrapper.get('.mint-artifact-selector__row').trigger('click')
|
|
71
|
+
await flushPromises()
|
|
72
|
+
|
|
73
|
+
expect(wrapper.emitted('selectDetail')).toHaveLength(1)
|
|
74
|
+
expect(wrapper.emitted('select')).toHaveLength(1)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// A failed detail fetch must not look like a successful pick.
|
|
78
|
+
it('should stay open and not emit select when the detail fetch fails', async () => {
|
|
79
|
+
const wrapper = await mountModal({ fetchDetailOnSelect: true })
|
|
80
|
+
mockGet.mockRejectedValueOnce(new Error('Detail unavailable'))
|
|
81
|
+
|
|
82
|
+
await wrapper.get('.mint-artifact-selector__row').trigger('click')
|
|
83
|
+
await flushPromises()
|
|
84
|
+
|
|
85
|
+
expect(wrapper.emitted('selectDetail')).toBeUndefined()
|
|
86
|
+
expect(wrapper.emitted('select')).toBeUndefined()
|
|
87
|
+
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
|
|
88
|
+
expect(wrapper.get('.mint-artifact-selector__error').text()).toContain('Detail unavailable')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should archive a row without selecting it', async () => {
|
|
92
|
+
const wrapper = await mountModal()
|
|
93
|
+
|
|
94
|
+
await wrapper.get('.mint-artifact-selector__action').trigger('click')
|
|
95
|
+
await flushPromises()
|
|
96
|
+
|
|
97
|
+
expect(mockPost).toHaveBeenCalledWith('/experiments/42/analysis-artifacts/1/archive')
|
|
98
|
+
expect(wrapper.emitted('archive')).toHaveLength(1)
|
|
99
|
+
expect(wrapper.emitted('select')).toBeUndefined()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// The row we clicked still says "active"; listeners must get the backend's version.
|
|
103
|
+
it('should emit the post-mutation summary on archive', async () => {
|
|
104
|
+
const wrapper = await mountModal()
|
|
105
|
+
|
|
106
|
+
await wrapper.get('.mint-artifact-selector__action').trigger('click')
|
|
107
|
+
await flushPromises()
|
|
108
|
+
|
|
109
|
+
expect(wrapper.emitted('archive')?.[0]?.[0]).toMatchObject({ id: 1, status: 'archived' })
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('should not emit archive when the mutation fails', async () => {
|
|
113
|
+
mockPost.mockRejectedValueOnce(new Error('Conflict'))
|
|
114
|
+
const wrapper = await mountModal()
|
|
115
|
+
|
|
116
|
+
await wrapper.get('.mint-artifact-selector__action').trigger('click')
|
|
117
|
+
await flushPromises()
|
|
118
|
+
|
|
119
|
+
expect(wrapper.emitted('archive')).toBeUndefined()
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
// The container's Enter handler must not hijack a focused row button.
|
|
123
|
+
it('should leave Enter alone when it targets a row action button', async () => {
|
|
124
|
+
const wrapper = await mountModal()
|
|
125
|
+
await wrapper.get('.mint-artifact-selector').trigger('keydown', { key: 'ArrowDown' })
|
|
126
|
+
|
|
127
|
+
await wrapper.get('.mint-artifact-selector__action').trigger('keydown', { key: 'Enter' })
|
|
128
|
+
await flushPromises()
|
|
129
|
+
|
|
130
|
+
expect(wrapper.emitted('select')).toBeUndefined()
|
|
131
|
+
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('should select the arrow-highlighted row on Enter', async () => {
|
|
135
|
+
const wrapper = await mountModal()
|
|
136
|
+
|
|
137
|
+
await wrapper.get('.mint-artifact-selector').trigger('keydown', { key: 'ArrowDown' })
|
|
138
|
+
await wrapper.get('.mint-artifact-selector').trigger('keydown', { key: 'Enter' })
|
|
139
|
+
await flushPromises()
|
|
140
|
+
|
|
141
|
+
expect(wrapper.emitted('select')?.[0]?.[0]).toMatchObject({ id: 1 })
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('should hide row actions without the experiments.edit permission', async () => {
|
|
145
|
+
;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
|
|
146
|
+
user: { id: '1', username: 'viewer', role: 'viewer', permissions: ['experiments.view'] },
|
|
147
|
+
}
|
|
148
|
+
const wrapper = await mountModal()
|
|
149
|
+
|
|
150
|
+
expect(wrapper.find('.mint-artifact-selector__action').exists()).toBe(false)
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should show row actions with the experiments.edit permission', async () => {
|
|
154
|
+
;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
|
|
155
|
+
user: {
|
|
156
|
+
id: '1',
|
|
157
|
+
username: 'member',
|
|
158
|
+
role: 'member',
|
|
159
|
+
permissions: ['experiments.view', 'experiments.edit'],
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
const wrapper = await mountModal()
|
|
163
|
+
|
|
164
|
+
expect(wrapper.find('.mint-artifact-selector__action').exists()).toBe(true)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('should scope rows to a reactive pluginId prop', async () => {
|
|
168
|
+
const wrapper = await mountModal({ pluginId: 'plugin-a' })
|
|
169
|
+
expect(wrapper.findAll('.mint-artifact-selector__row')).toHaveLength(1)
|
|
170
|
+
|
|
171
|
+
await wrapper.setProps({ pluginId: 'plugin-b' })
|
|
172
|
+
await flushPromises()
|
|
173
|
+
|
|
174
|
+
const rows = wrapper.findAll('.mint-artifact-selector__row')
|
|
175
|
+
expect(rows).toHaveLength(1)
|
|
176
|
+
expect(rows[0].text()).toContain('Peak Table')
|
|
177
|
+
})
|
|
178
|
+
})
|