@morscherlab/mint-sdk 1.1.0-beta.1 → 1.1.0-beta.11
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 +18 -4
- package/dist/{EmptyState-D1c4RdC7.js → EmptyState-DgBRoYAy.js} +22 -13
- package/dist/EmptyState-DgBRoYAy.js.map +1 -0
- package/dist/{ExperimentSelectorModal-B7w6k3Y8.js → ExperimentSelectorModal-BBWXC7KW.js} +3 -3
- package/dist/{ExperimentSelectorModal-B7w6k3Y8.js.map → ExperimentSelectorModal-BBWXC7KW.js.map} +1 -1
- package/dist/{SettingsModal-DPDwCbXK.js → SettingsModal-Dpg-X1OM.js} +3 -3
- package/dist/{SettingsModal-DPDwCbXK.js.map → SettingsModal-Dpg-X1OM.js.map} +1 -1
- package/dist/__tests__/components/BaseToggle.test.d.ts +1 -0
- package/dist/__tests__/components/SectionCard.test.d.ts +1 -0
- package/dist/__tests__/composables/analysisArtifactTarget.test.d.ts +1 -0
- package/dist/__tests__/types/GeneratedJsonSchema.test.d.ts +1 -0
- package/dist/analysisArtifactTypes-ejqR8MX3.js.map +1 -1
- package/dist/components/BaseSelect.vue.d.ts +1 -1
- package/dist/components/BaseToggle.vue.d.ts +2 -0
- package/dist/components/FitPanel.vue.d.ts +1 -1
- package/dist/components/PluginWorkspaceView.props.d.ts +1 -1
- package/dist/components/SectionCard.vue.d.ts +47 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +6 -6
- package/dist/{components-UfkPN1bK.js → components-Ci_BTx00.js} +711 -633
- package/dist/components-Ci_BTx00.js.map +1 -0
- package/dist/composables/analysisArtifactTarget.d.ts +5 -0
- package/dist/composables/index.d.ts +3 -1
- package/dist/composables/index.js +8 -8
- package/dist/composables/platformContextHelpers.d.ts +2 -0
- package/dist/composables/useAnalysisArtifacts.d.ts +3 -1
- package/dist/composables/useGeneratedAnalysis.d.ts +1 -1
- package/dist/composables/usePlatformContext.d.ts +3 -0
- package/dist/composables/usePluginClient.d.ts +11 -5
- package/dist/{composables-F4JFK9K1.js → composables-C0VEJ73O.js} +172 -30
- package/dist/composables-C0VEJ73O.js.map +1 -0
- package/dist/index.js +11 -11
- package/dist/install.js +5 -5
- package/dist/styles.css +312 -11
- 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 +3 -3
- package/dist/{templates-Cttbjbyc.js → templates-Cdj93S9C.js} +2 -2
- package/dist/{templates-Cttbjbyc.js.map → templates-Cdj93S9C.js.map} +1 -1
- package/dist/types/analysisArtifactTypes.d.ts +8 -0
- package/dist/types/components.d.ts +2 -0
- package/dist/types/generated-ui.d.ts +13 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/platform.d.ts +2 -0
- package/dist/{useControlSchema-Dallso-L.js → useControlSchema-B_qgU4rW.js} +48 -19
- package/dist/useControlSchema-B_qgU4rW.js.map +1 -0
- package/dist/{useExperimentSelector-BMjzNcf2.js → useExperimentSelector-BuQZRgMt.js} +2 -2
- package/dist/{useExperimentSelector-BMjzNcf2.js.map → useExperimentSelector-BuQZRgMt.js.map} +1 -1
- package/dist/{useFormBuilder-BXaaDI41.js → useFormBuilder-D1G5J5Z5.js} +2 -2
- package/dist/{useFormBuilder-BXaaDI41.js.map → useFormBuilder-D1G5J5Z5.js.map} +1 -1
- package/dist/{useProtocolTemplates-BP7_4RGQ.js → useProtocolTemplates-DeaOmMaw.js} +4 -4
- package/dist/{useProtocolTemplates-BP7_4RGQ.js.map → useProtocolTemplates-DeaOmMaw.js.map} +1 -1
- package/package.json +1 -1
- package/src/__tests__/components/BaseSelect.test.ts +52 -0
- package/src/__tests__/components/BaseToggle.test.ts +40 -0
- package/src/__tests__/components/GeneratedPluginView.test.ts +10 -0
- package/src/__tests__/components/PluginWorkspaceView.test.ts +68 -14
- package/src/__tests__/components/SectionCard.test.ts +181 -0
- package/src/__tests__/composables/analysisArtifactTarget.test.ts +77 -0
- package/src/__tests__/composables/useAnalysisArtifacts.test.ts +74 -1
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +2 -0
- package/src/__tests__/composables/usePlatformContext.test.ts +28 -0
- package/src/__tests__/composables/usePluginClient.test.ts +365 -21
- package/src/__tests__/composables/usePluginConfig.test.ts +323 -7
- 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/GeneratedPluginView.vue +1 -1
- 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 +1 -0
- package/src/composables/analysisArtifactTarget.ts +53 -0
- package/src/composables/index.ts +6 -0
- package/src/composables/platformContextHelpers.ts +10 -3
- package/src/composables/useAnalysisArtifacts.ts +48 -0
- package/src/composables/useGeneratedAnalysis.ts +30 -12
- package/src/composables/usePluginClient.ts +130 -20
- package/src/composables/usePluginConfig.ts +156 -20
- 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/types/analysisArtifactTypes.ts +9 -0
- package/src/types/components.ts +12 -0
- package/src/types/generated-ui.ts +13 -0
- package/src/types/index.ts +3 -0
- package/src/types/platform.ts +2 -0
- package/dist/EmptyState-D1c4RdC7.js.map +0 -1
- package/dist/components-UfkPN1bK.js.map +0 -1
- package/dist/composables-F4JFK9K1.js.map +0 -1
- package/dist/useControlSchema-Dallso-L.js.map +0 -1
|
@@ -38,16 +38,24 @@ type FakeResponse = Record<string, unknown>
|
|
|
38
38
|
|
|
39
39
|
let getResponse: FakeResponse = { plugin_name: 'drp', config: {} }
|
|
40
40
|
let patchResponse: FakeResponse = { plugin_name: 'drp', config: {} }
|
|
41
|
+
let putResponse: FakeResponse = { plugin_name: 'drp', config: {} }
|
|
41
42
|
let getError: Error | null = null
|
|
42
43
|
let patchError: Error | null = null
|
|
43
|
-
let
|
|
44
|
+
let recordedBaseUrls: Array<string | undefined> = []
|
|
45
|
+
let recordedCalls: {
|
|
46
|
+
method: string
|
|
47
|
+
url: string
|
|
48
|
+
data?: unknown
|
|
49
|
+
headers?: AxiosRequestConfig['headers']
|
|
50
|
+
}[] = []
|
|
44
51
|
|
|
45
52
|
function mockAxios() {
|
|
46
53
|
vi.spyOn(axios.Axios.prototype, 'get').mockImplementation(async function (
|
|
47
54
|
this: unknown,
|
|
48
55
|
url: string,
|
|
49
|
-
|
|
56
|
+
config?: AxiosRequestConfig,
|
|
50
57
|
) {
|
|
58
|
+
recordedBaseUrls.push(config?.baseURL)
|
|
51
59
|
recordedCalls.push({ method: 'GET', url })
|
|
52
60
|
if (getError) throw getError
|
|
53
61
|
return { data: getResponse }
|
|
@@ -56,12 +64,23 @@ function mockAxios() {
|
|
|
56
64
|
this: unknown,
|
|
57
65
|
url: string,
|
|
58
66
|
data?: unknown,
|
|
59
|
-
|
|
67
|
+
config?: AxiosRequestConfig,
|
|
60
68
|
) {
|
|
69
|
+
recordedBaseUrls.push(config?.baseURL)
|
|
61
70
|
recordedCalls.push({ method: 'PATCH', url, data })
|
|
62
71
|
if (patchError) throw patchError
|
|
63
72
|
return { data: patchResponse }
|
|
64
73
|
})
|
|
74
|
+
vi.spyOn(axios.Axios.prototype, 'put').mockImplementation(async function (
|
|
75
|
+
this: unknown,
|
|
76
|
+
url: string,
|
|
77
|
+
data?: unknown,
|
|
78
|
+
config?: AxiosRequestConfig,
|
|
79
|
+
) {
|
|
80
|
+
recordedBaseUrls.push(config?.baseURL)
|
|
81
|
+
recordedCalls.push({ method: 'PUT', url, data, headers: config?.headers })
|
|
82
|
+
return { data: putResponse }
|
|
83
|
+
})
|
|
65
84
|
}
|
|
66
85
|
|
|
67
86
|
describe('usePluginConfig', () => {
|
|
@@ -70,13 +89,17 @@ describe('usePluginConfig', () => {
|
|
|
70
89
|
autoLoadOnMount = false
|
|
71
90
|
getResponse = { plugin_name: 'drp', config: {} }
|
|
72
91
|
patchResponse = { plugin_name: 'drp', config: {} }
|
|
92
|
+
putResponse = { plugin_name: 'drp', config: {} }
|
|
73
93
|
getError = null
|
|
74
94
|
patchError = null
|
|
75
95
|
recordedCalls = []
|
|
96
|
+
recordedBaseUrls = []
|
|
97
|
+
delete (window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__
|
|
76
98
|
mockAxios()
|
|
77
99
|
})
|
|
78
100
|
|
|
79
101
|
afterEach(() => {
|
|
102
|
+
delete (window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__
|
|
80
103
|
vi.restoreAllMocks()
|
|
81
104
|
})
|
|
82
105
|
|
|
@@ -106,12 +129,148 @@ describe('usePluginConfig', () => {
|
|
|
106
129
|
expect(recordedCalls[0]!.url).toBe('/plugins/my%2Fplugin/config')
|
|
107
130
|
})
|
|
108
131
|
|
|
109
|
-
it(
|
|
110
|
-
|
|
111
|
-
|
|
132
|
+
it.each([
|
|
133
|
+
'platform',
|
|
134
|
+
undefined,
|
|
135
|
+
] as const)(
|
|
136
|
+
'should use the platform config facade when settings_api is %s',
|
|
137
|
+
async (settingsApi) => {
|
|
138
|
+
;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
|
|
139
|
+
isIntegrated: true,
|
|
140
|
+
theme: 'light',
|
|
141
|
+
platformApiUrl: '/api',
|
|
142
|
+
plugin: {
|
|
143
|
+
id: 'drp',
|
|
144
|
+
name: 'drp',
|
|
145
|
+
version: '1.0.0',
|
|
146
|
+
route_prefix: '/drp',
|
|
147
|
+
api_prefix: '/api/drp',
|
|
148
|
+
...(settingsApi ? { settings_api: settingsApi } : {}),
|
|
149
|
+
},
|
|
150
|
+
}
|
|
151
|
+
getResponse = {
|
|
152
|
+
plugin_name: 'drp',
|
|
153
|
+
config: { n: 1 },
|
|
154
|
+
revision: 'revision-1',
|
|
155
|
+
}
|
|
156
|
+
const hook = usePluginConfig('drp')
|
|
157
|
+
|
|
158
|
+
await hook.load()
|
|
159
|
+
|
|
160
|
+
expect(recordedCalls).toEqual([
|
|
161
|
+
{ method: 'GET', url: '/plugins/drp/config' },
|
|
162
|
+
])
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
it('should use the plugin settings router for an integrated plugin capability', async () => {
|
|
167
|
+
;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
|
|
168
|
+
isIntegrated: true,
|
|
169
|
+
theme: 'light',
|
|
170
|
+
platformApiUrl: '/api',
|
|
171
|
+
plugin: {
|
|
172
|
+
id: 'drp',
|
|
173
|
+
name: 'drp',
|
|
174
|
+
version: '1.0.0',
|
|
175
|
+
route_prefix: '/drp',
|
|
176
|
+
api_prefix: '/api/drp',
|
|
177
|
+
settings_api: 'plugin',
|
|
178
|
+
},
|
|
179
|
+
}
|
|
180
|
+
getResponse = {
|
|
181
|
+
settings: { n: 1 },
|
|
182
|
+
mode: 'integrated',
|
|
183
|
+
revision: 'revision-1',
|
|
184
|
+
}
|
|
185
|
+
putResponse = {
|
|
186
|
+
settings: { n: 2 },
|
|
187
|
+
mode: 'integrated',
|
|
188
|
+
revision: 'revision-2',
|
|
189
|
+
}
|
|
112
190
|
const hook = usePluginConfig('drp')
|
|
113
191
|
await hook.load()
|
|
114
192
|
hook.config.value = { n: 2 }
|
|
193
|
+
|
|
194
|
+
const saved = await hook.save()
|
|
195
|
+
|
|
196
|
+
expect({
|
|
197
|
+
saved,
|
|
198
|
+
calls: recordedCalls,
|
|
199
|
+
baseUrls: recordedBaseUrls,
|
|
200
|
+
config: hook.config.value,
|
|
201
|
+
}).toEqual({
|
|
202
|
+
saved: true,
|
|
203
|
+
baseUrls: ['/api/drp', '/api/drp'],
|
|
204
|
+
calls: [
|
|
205
|
+
{ method: 'GET', url: '/settings' },
|
|
206
|
+
{
|
|
207
|
+
method: 'PUT',
|
|
208
|
+
url: '/settings',
|
|
209
|
+
data: { n: 2 },
|
|
210
|
+
headers: { 'If-Match': '"revision-1"' },
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
config: { n: 2 },
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should use the platform facade for an explicit different plugin name', async () => {
|
|
218
|
+
;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
|
|
219
|
+
isIntegrated: true,
|
|
220
|
+
theme: 'light',
|
|
221
|
+
platformApiUrl: '/api',
|
|
222
|
+
plugin: {
|
|
223
|
+
id: 'current-plugin',
|
|
224
|
+
name: 'current-plugin',
|
|
225
|
+
version: '1.0.0',
|
|
226
|
+
route_prefix: '/current-plugin',
|
|
227
|
+
api_prefix: '/api/current-plugin',
|
|
228
|
+
settings_api: 'plugin',
|
|
229
|
+
},
|
|
230
|
+
}
|
|
231
|
+
getResponse = {
|
|
232
|
+
plugin_name: 'other-plugin',
|
|
233
|
+
config: { n: 1 },
|
|
234
|
+
revision: 'revision-1',
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const hook = usePluginConfig('other-plugin')
|
|
238
|
+
await hook.load()
|
|
239
|
+
|
|
240
|
+
expect({
|
|
241
|
+
calls: recordedCalls,
|
|
242
|
+
config: hook.config.value,
|
|
243
|
+
}).toEqual({
|
|
244
|
+
calls: [
|
|
245
|
+
{ method: 'GET', url: '/plugins/other-plugin/config' },
|
|
246
|
+
],
|
|
247
|
+
config: { n: 1 },
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('should PATCH only dirty typed fields through a versioned platform facade', async () => {
|
|
252
|
+
getResponse = {
|
|
253
|
+
plugin_name: 'drp',
|
|
254
|
+
config: {
|
|
255
|
+
n: 1,
|
|
256
|
+
allowed_experiment_types: ['metabolomics'],
|
|
257
|
+
},
|
|
258
|
+
revision: 'revision-1',
|
|
259
|
+
}
|
|
260
|
+
patchResponse = {
|
|
261
|
+
plugin_name: 'drp',
|
|
262
|
+
config: {
|
|
263
|
+
n: 2,
|
|
264
|
+
allowed_experiment_types: ['metabolomics'],
|
|
265
|
+
},
|
|
266
|
+
revision: 'revision-2',
|
|
267
|
+
}
|
|
268
|
+
const hook = usePluginConfig('drp')
|
|
269
|
+
await hook.load()
|
|
270
|
+
hook.config.value = {
|
|
271
|
+
n: 2,
|
|
272
|
+
allowed_experiment_types: ['metabolomics'],
|
|
273
|
+
}
|
|
115
274
|
await nextTick()
|
|
116
275
|
expect(hook.isDirty.value).toBe(true)
|
|
117
276
|
|
|
@@ -120,11 +279,168 @@ describe('usePluginConfig', () => {
|
|
|
120
279
|
const patchCall = recordedCalls.find((c) => c.method === 'PATCH')!
|
|
121
280
|
expect(patchCall.url).toBe('/plugins/drp/config')
|
|
122
281
|
expect(patchCall.data).toEqual({ config: { n: 2 } })
|
|
123
|
-
expect(
|
|
282
|
+
expect(patchCall.headers).toBeUndefined()
|
|
283
|
+
expect(hook.config.value).toEqual({
|
|
284
|
+
n: 2,
|
|
285
|
+
allowed_experiment_types: ['metabolomics'],
|
|
286
|
+
})
|
|
124
287
|
expect(hook.isDirty.value).toBe(false)
|
|
125
288
|
expect(hook.lastSavedAt.value).toBeInstanceOf(Date)
|
|
126
289
|
})
|
|
127
290
|
|
|
291
|
+
it('should split changed platform-owned config from a versioned typed save', async () => {
|
|
292
|
+
getResponse = {
|
|
293
|
+
plugin_name: 'drp',
|
|
294
|
+
config: {
|
|
295
|
+
n: 1,
|
|
296
|
+
allowed_experiment_types: ['metabolomics'],
|
|
297
|
+
},
|
|
298
|
+
revision: 'revision-1',
|
|
299
|
+
}
|
|
300
|
+
patchResponse = {
|
|
301
|
+
plugin_name: 'drp',
|
|
302
|
+
config: {
|
|
303
|
+
n: 2,
|
|
304
|
+
allowed_experiment_types: ['proteomics'],
|
|
305
|
+
},
|
|
306
|
+
revision: 'untrusted-platform-revision',
|
|
307
|
+
}
|
|
308
|
+
const hook = usePluginConfig('drp')
|
|
309
|
+
await hook.load()
|
|
310
|
+
hook.config.value = {
|
|
311
|
+
n: 2,
|
|
312
|
+
allowed_experiment_types: ['proteomics'],
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const saved = await hook.save()
|
|
316
|
+
patchResponse = {
|
|
317
|
+
plugin_name: 'drp',
|
|
318
|
+
config: {
|
|
319
|
+
n: 3,
|
|
320
|
+
allowed_experiment_types: ['proteomics'],
|
|
321
|
+
},
|
|
322
|
+
revision: 'revision-3',
|
|
323
|
+
}
|
|
324
|
+
hook.config.value = {
|
|
325
|
+
n: 3,
|
|
326
|
+
allowed_experiment_types: ['proteomics'],
|
|
327
|
+
}
|
|
328
|
+
const savedAgain = await hook.save()
|
|
329
|
+
|
|
330
|
+
expect({
|
|
331
|
+
saved,
|
|
332
|
+
savedAgain,
|
|
333
|
+
calls: recordedCalls,
|
|
334
|
+
config: hook.config.value,
|
|
335
|
+
isDirty: hook.isDirty.value,
|
|
336
|
+
}).toEqual({
|
|
337
|
+
saved: true,
|
|
338
|
+
savedAgain: true,
|
|
339
|
+
calls: [
|
|
340
|
+
{ method: 'GET', url: '/plugins/drp/config' },
|
|
341
|
+
{
|
|
342
|
+
method: 'PATCH',
|
|
343
|
+
url: '/plugins/drp/config',
|
|
344
|
+
data: { config: { n: 2 } },
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
method: 'PATCH',
|
|
348
|
+
url: '/plugins/drp/config',
|
|
349
|
+
data: { config: { allowed_experiment_types: ['proteomics'] } },
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
method: 'PATCH',
|
|
353
|
+
url: '/plugins/drp/config',
|
|
354
|
+
data: { config: { n: 3 } },
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
config: {
|
|
358
|
+
n: 3,
|
|
359
|
+
allowed_experiment_types: ['proteomics'],
|
|
360
|
+
},
|
|
361
|
+
isDirty: false,
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('should avoid a typed PUT when only the platform-owned config changes', async () => {
|
|
366
|
+
getResponse = {
|
|
367
|
+
plugin_name: 'drp',
|
|
368
|
+
config: {
|
|
369
|
+
n: 1,
|
|
370
|
+
allowed_experiment_types: ['metabolomics'],
|
|
371
|
+
},
|
|
372
|
+
revision: 'revision-1',
|
|
373
|
+
}
|
|
374
|
+
patchResponse = {
|
|
375
|
+
plugin_name: 'drp',
|
|
376
|
+
config: {
|
|
377
|
+
n: 1,
|
|
378
|
+
allowed_experiment_types: ['proteomics'],
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
const hook = usePluginConfig('drp')
|
|
382
|
+
await hook.load()
|
|
383
|
+
hook.config.value = {
|
|
384
|
+
n: 1,
|
|
385
|
+
allowed_experiment_types: ['proteomics'],
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const saved = await hook.save()
|
|
389
|
+
|
|
390
|
+
expect({
|
|
391
|
+
saved,
|
|
392
|
+
calls: recordedCalls,
|
|
393
|
+
config: hook.config.value,
|
|
394
|
+
}).toEqual({
|
|
395
|
+
saved: true,
|
|
396
|
+
calls: [
|
|
397
|
+
{ method: 'GET', url: '/plugins/drp/config' },
|
|
398
|
+
{
|
|
399
|
+
method: 'PATCH',
|
|
400
|
+
url: '/plugins/drp/config',
|
|
401
|
+
data: { config: { allowed_experiment_types: ['proteomics'] } },
|
|
402
|
+
},
|
|
403
|
+
],
|
|
404
|
+
config: {
|
|
405
|
+
n: 1,
|
|
406
|
+
allowed_experiment_types: ['proteomics'],
|
|
407
|
+
},
|
|
408
|
+
})
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
it('should detect nested config edits and restore the saved JSON snapshot', async () => {
|
|
412
|
+
getResponse = {
|
|
413
|
+
plugin_name: 'drp',
|
|
414
|
+
config: {
|
|
415
|
+
rules: {
|
|
416
|
+
thresholds: [1, 2],
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
}
|
|
420
|
+
const hook = usePluginConfig('drp')
|
|
421
|
+
await hook.load()
|
|
422
|
+
const rules = hook.config.value.rules as { thresholds: number[] }
|
|
423
|
+
rules.thresholds[0] = 9
|
|
424
|
+
await nextTick()
|
|
425
|
+
|
|
426
|
+
const dirtyAfterMutation = hook.isDirty.value
|
|
427
|
+
hook.reset()
|
|
428
|
+
|
|
429
|
+
expect({
|
|
430
|
+
dirtyAfterMutation,
|
|
431
|
+
config: hook.config.value,
|
|
432
|
+
isDirty: hook.isDirty.value,
|
|
433
|
+
}).toEqual({
|
|
434
|
+
dirtyAfterMutation: true,
|
|
435
|
+
config: {
|
|
436
|
+
rules: {
|
|
437
|
+
thresholds: [1, 2],
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
isDirty: false,
|
|
441
|
+
})
|
|
442
|
+
})
|
|
443
|
+
|
|
128
444
|
it('isDirty reflects edits vs last saved snapshot', async () => {
|
|
129
445
|
getResponse = { plugin_name: 'drp', config: { k: 'v' } }
|
|
130
446
|
const hook = usePluginConfig('drp')
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import type { GeneratedJsonSchema } from '../../types/generated-ui'
|
|
4
|
+
|
|
5
|
+
describe('GeneratedJsonSchema', () => {
|
|
6
|
+
it('should accept minimum array size constraints emitted by Pydantic', () => {
|
|
7
|
+
const schema: GeneratedJsonSchema = { minItems: 1 }
|
|
8
|
+
|
|
9
|
+
expect(schema.minItems).toBe(1)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('should accept array size constraints emitted by Pydantic', () => {
|
|
13
|
+
const schema: GeneratedJsonSchema = { maxItems: 100 }
|
|
14
|
+
|
|
15
|
+
expect(schema.maxItems).toBe(100)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('should accept write-only fields emitted by Pydantic', () => {
|
|
19
|
+
const schema: GeneratedJsonSchema = { writeOnly: true }
|
|
20
|
+
|
|
21
|
+
expect(schema.writeOnly).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should accept tuple item schemas emitted by Pydantic', () => {
|
|
25
|
+
const schema: GeneratedJsonSchema = {
|
|
26
|
+
prefixItems: [{ type: 'string' }, { type: 'number' }],
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
expect(schema.prefixItems).toHaveLength(2)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should accept exclusive numeric bounds emitted by Pydantic', () => {
|
|
33
|
+
const schema: GeneratedJsonSchema = { exclusiveMinimum: 0 }
|
|
34
|
+
|
|
35
|
+
expect(schema.exclusiveMinimum).toBe(0)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should accept string patterns emitted by Pydantic', () => {
|
|
39
|
+
const schema: GeneratedJsonSchema = { pattern: '^[0-9a-f]{64}$' }
|
|
40
|
+
|
|
41
|
+
expect(schema.pattern).toBe('^[0-9a-f]{64}$')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should accept discriminators emitted for Pydantic unions', () => {
|
|
45
|
+
const schema: GeneratedJsonSchema = {
|
|
46
|
+
discriminator: {
|
|
47
|
+
propertyName: 'kind',
|
|
48
|
+
mapping: { targeted: '#/$defs/TargetedResult' },
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
expect(schema.discriminator?.propertyName).toBe('kind')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should accept exclusive union branches emitted by Pydantic', () => {
|
|
56
|
+
const schema: GeneratedJsonSchema = {
|
|
57
|
+
oneOf: [{ $ref: '#/$defs/TargetedResult' }],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
expect(schema.oneOf).toHaveLength(1)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/** Renders a native `<select>` element with size, error, and disabled states. */
|
|
3
|
-
import { computed } from 'vue'
|
|
3
|
+
import { computed, useAttrs } from 'vue'
|
|
4
|
+
import type { HTMLAttributes } from 'vue'
|
|
4
5
|
import type { SelectOption, SelectOptionInput } from '../types'
|
|
5
6
|
import { findOptionByDomValue, normalizeOptionInput } from '../utils/options'
|
|
6
7
|
|
|
8
|
+
defineOptions({ inheritAttrs: false })
|
|
9
|
+
|
|
7
10
|
interface Props {
|
|
8
11
|
modelValue?: string | number
|
|
9
12
|
options: SelectOptionInput<string | number>[]
|
|
@@ -19,6 +22,16 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
19
22
|
error: false,
|
|
20
23
|
size: 'md',
|
|
21
24
|
})
|
|
25
|
+
const attrs = useAttrs()
|
|
26
|
+
|
|
27
|
+
const controlAttrs = computed(() => {
|
|
28
|
+
const { class: _class, style: _style, ...nativeAttrs } = attrs
|
|
29
|
+
return nativeAttrs
|
|
30
|
+
})
|
|
31
|
+
const wrapperAttrs = computed<Pick<HTMLAttributes, 'class' | 'style'>>(() => ({
|
|
32
|
+
class: attrs.class as HTMLAttributes['class'],
|
|
33
|
+
style: attrs.style as HTMLAttributes['style'],
|
|
34
|
+
}))
|
|
22
35
|
|
|
23
36
|
const emit = defineEmits<{
|
|
24
37
|
'update:modelValue': [value: string | number]
|
|
@@ -36,8 +49,9 @@ function handleChange(event: Event) {
|
|
|
36
49
|
</script>
|
|
37
50
|
|
|
38
51
|
<template>
|
|
39
|
-
<div class="mint-select">
|
|
52
|
+
<div v-bind="wrapperAttrs" class="mint-select">
|
|
40
53
|
<select
|
|
54
|
+
v-bind="controlAttrs"
|
|
41
55
|
:value="modelValue"
|
|
42
56
|
:disabled="disabled"
|
|
43
57
|
:aria-invalid="error || undefined"
|
|
@@ -5,6 +5,8 @@ import Tooltip from './Tooltip.vue'
|
|
|
5
5
|
interface Props {
|
|
6
6
|
modelValue?: boolean
|
|
7
7
|
label?: string
|
|
8
|
+
/** Accessible switch name; defaults to the visual label, then "Toggle". */
|
|
9
|
+
ariaLabel?: string
|
|
8
10
|
description?: string
|
|
9
11
|
icon?: string | string[]
|
|
10
12
|
iconColor?: string
|
|
@@ -111,7 +113,7 @@ function iconStyle(): Record<string, string> {
|
|
|
111
113
|
role="switch"
|
|
112
114
|
:tabindex="disabled ? -1 : 0"
|
|
113
115
|
:aria-checked="modelValue"
|
|
114
|
-
:aria-label="label || 'Toggle'"
|
|
116
|
+
:aria-label="ariaLabel || label || 'Toggle'"
|
|
115
117
|
:class="[
|
|
116
118
|
'mint-toggle__track',
|
|
117
119
|
`mint-toggle__track--${size}`,
|
|
@@ -116,7 +116,7 @@ function resourceSummary(definition: GeneratedJobDefinition): string {
|
|
|
116
116
|
if (definition.lifecycle === 'staged' || definition.profile?.kind === 'staged') {
|
|
117
117
|
return 'Staged upload'
|
|
118
118
|
}
|
|
119
|
-
return
|
|
119
|
+
return `Up to ${definition.cpu} CPU`
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
onMounted(() => {
|
|
@@ -34,7 +34,7 @@ export interface PluginWorkspaceViewProps {
|
|
|
34
34
|
homePath?: string
|
|
35
35
|
/** Show the default MINT logo when no icon/logo slot is provided. */
|
|
36
36
|
showLogo?: boolean
|
|
37
|
-
/** Preferred route-level page selector entries. */
|
|
37
|
+
/** Preferred route-level page selector entries. Pass [] to suppress integrated platform fallback. */
|
|
38
38
|
pageSelector?: PageSelectorItemInput[]
|
|
39
39
|
/** Active page selector id. Defaults to activeView. */
|
|
40
40
|
currentPageSelectorId?: string
|
|
@@ -208,10 +208,6 @@ const {
|
|
|
208
208
|
emitPillSelect: (item) => emit('pill-select', item),
|
|
209
209
|
})
|
|
210
210
|
|
|
211
|
-
const visiblePageSelector = computed(() =>
|
|
212
|
-
(props.pageSelector?.length ?? 0) > 1 ? props.pageSelector : undefined,
|
|
213
|
-
)
|
|
214
|
-
|
|
215
211
|
const hasSidebarSurface = computed(() => {
|
|
216
212
|
return hasPluginWorkspaceSidebarSurface({
|
|
217
213
|
showSidebar: props.showSidebar,
|
|
@@ -274,7 +270,7 @@ function handleFormCancel(sectionId: string) {
|
|
|
274
270
|
:variant="topBarVariant"
|
|
275
271
|
:home-path="homePath"
|
|
276
272
|
:show-logo="showLogo"
|
|
277
|
-
:page-selector="
|
|
273
|
+
:page-selector="pageSelector"
|
|
278
274
|
:current-page-selector-id="resolvedCurrentPageSelectorId"
|
|
279
275
|
:plugin-switcher="pluginSwitcher"
|
|
280
276
|
:pill-nav="resolvedPillNav"
|