@morscherlab/mint-sdk 1.0.1 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morscherlab/mint-sdk",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
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,5 +1,5 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
- import { mount } from '@vue/test-utils'
2
+ import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'
3
3
  import { computed, ref } from 'vue'
4
4
  import { createPinia } from 'pinia'
5
5
  import AppTopBar from '../../components/AppTopBar.vue'
@@ -64,6 +64,17 @@ function createWrapper(props = {}, slots = {}) {
64
64
  })
65
65
  }
66
66
 
67
+ async function waitForAsyncComponents(wrapper: VueWrapper) {
68
+ await flushPromises()
69
+ await wrapper.vm.$nextTick()
70
+ }
71
+
72
+ async function openSettingsModal(wrapper: VueWrapper) {
73
+ await wrapper.get('.mint-topbar__settings-btn').trigger('click')
74
+ await waitForAsyncComponents(wrapper)
75
+ return wrapper.findComponent(SettingsModal)
76
+ }
77
+
67
78
  describe('AppTopBar', () => {
68
79
  beforeEach(() => {
69
80
  vi.clearAllMocks()
@@ -147,12 +158,15 @@ describe('AppTopBar', () => {
147
158
  expect(settingsBtn.exists()).toBe(true)
148
159
  })
149
160
 
150
- it('should render SettingsModal when showSettings is true', () => {
161
+ it('should render SettingsModal after settings is opened', async () => {
151
162
  const wrapper = createWrapper({
152
163
  title: 'Test App',
153
164
  showSettings: true,
154
165
  })
155
- expect(wrapper.findComponent(SettingsModal).exists()).toBe(true)
166
+ expect(wrapper.findComponent(SettingsModal).exists()).toBe(false)
167
+
168
+ const settingsModal = await openSettingsModal(wrapper)
169
+ expect(settingsModal.exists()).toBe(true)
156
170
  })
157
171
 
158
172
  it('should have aria-label on settings button', () => {
@@ -183,26 +197,22 @@ describe('AppTopBar', () => {
183
197
  showSettings: true,
184
198
  })
185
199
 
186
- const settingsBtn = wrapper.find('.mint-topbar__settings-btn')
187
- await settingsBtn.trigger('click')
188
-
189
- const settingsModal = wrapper.findComponent(SettingsModal)
200
+ const settingsModal = await openSettingsModal(wrapper)
190
201
  expect(settingsModal.props('modelValue')).toBe(true)
191
202
  })
192
203
 
193
- it('should initialize SettingsModal as closed', () => {
204
+ it('should not mount SettingsModal while closed', () => {
194
205
  const wrapper = createWrapper({
195
206
  title: 'Test App',
196
207
  showSettings: true,
197
208
  })
198
209
 
199
- const settingsModal = wrapper.findComponent(SettingsModal)
200
- expect(settingsModal.props('modelValue')).toBe(false)
210
+ expect(wrapper.findComponent(SettingsModal).exists()).toBe(false)
201
211
  })
202
212
  })
203
213
 
204
214
  describe('settingsConfig prop', () => {
205
- it('should pass title to SettingsModal', () => {
215
+ it('should pass title to SettingsModal', async () => {
206
216
  const settingsConfig: TopBarSettingsConfig = {
207
217
  title: 'Custom Settings',
208
218
  }
@@ -213,11 +223,11 @@ describe('AppTopBar', () => {
213
223
  settingsConfig,
214
224
  })
215
225
 
216
- const settingsModal = wrapper.findComponent(SettingsModal)
226
+ const settingsModal = await openSettingsModal(wrapper)
217
227
  expect(settingsModal.props('title')).toBe('Custom Settings')
218
228
  })
219
229
 
220
- it('should pass tabs to SettingsModal', () => {
230
+ it('should pass tabs to SettingsModal', async () => {
221
231
  const settingsConfig: TopBarSettingsConfig = {
222
232
  tabs: [
223
233
  { id: 'general', label: 'General' },
@@ -231,11 +241,11 @@ describe('AppTopBar', () => {
231
241
  settingsConfig,
232
242
  })
233
243
 
234
- const settingsModal = wrapper.findComponent(SettingsModal)
244
+ const settingsModal = await openSettingsModal(wrapper)
235
245
  expect(settingsModal.props('tabs')).toEqual(settingsConfig.tabs)
236
246
  })
237
247
 
238
- it('should normalize shorthand settings tabs for SettingsModal', () => {
248
+ it('should normalize shorthand settings tabs for SettingsModal', async () => {
239
249
  const wrapper = createWrapper({
240
250
  title: 'Test App',
241
251
  showSettings: true,
@@ -245,7 +255,7 @@ describe('AppTopBar', () => {
245
255
  },
246
256
  })
247
257
 
248
- const settingsModal = wrapper.findComponent(SettingsModal)
258
+ const settingsModal = await openSettingsModal(wrapper)
249
259
  expect(settingsModal.props('tabs')).toEqual([
250
260
  { id: 'General', label: 'General' },
251
261
  { id: 'Advanced', label: 'Advanced' },
@@ -287,18 +297,20 @@ describe('AppTopBar', () => {
287
297
  settingsConfig,
288
298
  })
289
299
 
300
+ await openSettingsModal(wrapper)
290
301
  await wrapper.setProps({
291
302
  settingsConfig: {
292
303
  ...settingsConfig,
293
304
  values: { threshold: 0.75, method: 'logistic', externalContext: 'experiment-2' },
294
305
  },
295
306
  })
307
+ await waitForAsyncComponents(wrapper)
296
308
 
297
309
  expect((wrapper.find('input').element as HTMLInputElement).value).toBe('0.75')
298
310
  expect((wrapper.find('select').element as HTMLSelectElement).value).toBe('logistic')
299
311
  })
300
312
 
301
- it('should render compact controls in SettingsModal', () => {
313
+ it('should render compact controls in SettingsModal', async () => {
302
314
  const controls = defineControls({
303
315
  threshold: {
304
316
  label: 'Threshold',
@@ -318,13 +330,13 @@ describe('AppTopBar', () => {
318
330
  settingsConfig,
319
331
  })
320
332
 
321
- const settingsModal = wrapper.findComponent(SettingsModal)
333
+ const settingsModal = await openSettingsModal(wrapper)
322
334
  expect(settingsModal.props('controls')).toStrictEqual(controls)
323
335
  expect(wrapper.text()).toContain('Threshold')
324
336
  expect((wrapper.find('input').element as HTMLInputElement).value).toBe('0.25')
325
337
  })
326
338
 
327
- it('should pass control model to SettingsModal', () => {
339
+ it('should pass control model to SettingsModal', async () => {
328
340
  const model = defineControlModel({
329
341
  controls: {
330
342
  threshold: {
@@ -346,13 +358,13 @@ describe('AppTopBar', () => {
346
358
  settingsConfig,
347
359
  })
348
360
 
349
- const settingsModal = wrapper.findComponent(SettingsModal)
361
+ const settingsModal = await openSettingsModal(wrapper)
350
362
  expect(settingsModal.props('model')).toStrictEqual(model)
351
363
  expect(wrapper.text()).toContain('Threshold')
352
364
  expect((wrapper.find('input').element as HTMLInputElement).value).toBe('0.25')
353
365
  })
354
366
 
355
- it('should pass showAppearance to SettingsModal', () => {
367
+ it('should pass showAppearance to SettingsModal', async () => {
356
368
  const settingsConfig: TopBarSettingsConfig = {
357
369
  showAppearance: false,
358
370
  }
@@ -363,11 +375,11 @@ describe('AppTopBar', () => {
363
375
  settingsConfig,
364
376
  })
365
377
 
366
- const settingsModal = wrapper.findComponent(SettingsModal)
378
+ const settingsModal = await openSettingsModal(wrapper)
367
379
  expect(settingsModal.props('showAppearance')).toBe(false)
368
380
  })
369
381
 
370
- it('should pass settings userType to SettingsModal', () => {
382
+ it('should pass settings userType to SettingsModal', async () => {
371
383
  const settingsConfig: TopBarSettingsConfig = {
372
384
  userType: 'admin',
373
385
  }
@@ -378,22 +390,22 @@ describe('AppTopBar', () => {
378
390
  settingsConfig,
379
391
  })
380
392
 
381
- const settingsModal = wrapper.findComponent(SettingsModal)
393
+ const settingsModal = await openSettingsModal(wrapper)
382
394
  expect(settingsModal.props('userType')).toBe('admin')
383
395
  })
384
396
 
385
- it('should default showAppearance to true when not specified', () => {
397
+ it('should default showAppearance to true when not specified', async () => {
386
398
  const wrapper = createWrapper({
387
399
  title: 'Test App',
388
400
  showSettings: true,
389
401
  settingsConfig: {},
390
402
  })
391
403
 
392
- const settingsModal = wrapper.findComponent(SettingsModal)
404
+ const settingsModal = await openSettingsModal(wrapper)
393
405
  expect(settingsModal.props('showAppearance')).toBe(true)
394
406
  })
395
407
 
396
- it('should pass size to SettingsModal', () => {
408
+ it('should pass size to SettingsModal', async () => {
397
409
  const settingsConfig: TopBarSettingsConfig = {
398
410
  size: 'xl',
399
411
  }
@@ -404,24 +416,24 @@ describe('AppTopBar', () => {
404
416
  settingsConfig,
405
417
  })
406
418
 
407
- const settingsModal = wrapper.findComponent(SettingsModal)
419
+ const settingsModal = await openSettingsModal(wrapper)
408
420
  expect(settingsModal.props('size')).toBe('xl')
409
421
  })
410
422
 
411
- it('should use default size when not specified', () => {
423
+ it('should use default size when not specified', async () => {
412
424
  const wrapper = createWrapper({
413
425
  title: 'Test App',
414
426
  showSettings: true,
415
427
  })
416
428
 
417
- const settingsModal = wrapper.findComponent(SettingsModal)
429
+ const settingsModal = await openSettingsModal(wrapper)
418
430
  // SettingsModal defaults to 'lg' size
419
431
  expect(settingsModal.props('size')).toBe('lg')
420
432
  })
421
433
  })
422
434
 
423
435
  describe('settings tab slot forwarding', () => {
424
- it('should forward settings-tab slots to SettingsModal', () => {
436
+ it('should forward settings-tab slots to SettingsModal', async () => {
425
437
  const settingsConfig: TopBarSettingsConfig = {
426
438
  tabs: [{ id: 'general', label: 'General' }],
427
439
  }
@@ -434,11 +446,11 @@ describe('AppTopBar', () => {
434
446
  'settings-tab-general': '<div class="custom-tab-content">Custom General Tab</div>',
435
447
  })
436
448
 
437
- const settingsModal = wrapper.findComponent(SettingsModal)
449
+ const settingsModal = await openSettingsModal(wrapper)
438
450
  expect(settingsModal.vm.$slots['tab-general']).toBeDefined()
439
451
  })
440
452
 
441
- it('should forward shorthand settings-tab slots to SettingsModal', () => {
453
+ it('should forward shorthand settings-tab slots to SettingsModal', async () => {
442
454
  const wrapper = createWrapper({
443
455
  title: 'Test App',
444
456
  showSettings: true,
@@ -450,11 +462,11 @@ describe('AppTopBar', () => {
450
462
  'settings-tab-General': '<div>General Content</div>',
451
463
  })
452
464
 
453
- const settingsModal = wrapper.findComponent(SettingsModal)
465
+ const settingsModal = await openSettingsModal(wrapper)
454
466
  expect(settingsModal.vm.$slots['tab-General']).toBeDefined()
455
467
  })
456
468
 
457
- it('should forward multiple settings-tab slots', () => {
469
+ it('should forward multiple settings-tab slots', async () => {
458
470
  const settingsConfig: TopBarSettingsConfig = {
459
471
  tabs: [
460
472
  { id: 'general', label: 'General' },
@@ -471,12 +483,12 @@ describe('AppTopBar', () => {
471
483
  'settings-tab-advanced': '<div>Advanced Content</div>',
472
484
  })
473
485
 
474
- const settingsModal = wrapper.findComponent(SettingsModal)
486
+ const settingsModal = await openSettingsModal(wrapper)
475
487
  expect(settingsModal.vm.$slots['tab-general']).toBeDefined()
476
488
  expect(settingsModal.vm.$slots['tab-advanced']).toBeDefined()
477
489
  })
478
490
 
479
- it('should forward settings-appearance slot', () => {
491
+ it('should forward settings-appearance slot', async () => {
480
492
  const wrapper = createWrapper({
481
493
  title: 'Test App',
482
494
  showSettings: true,
@@ -484,7 +496,7 @@ describe('AppTopBar', () => {
484
496
  'settings-appearance': '<div class="custom-appearance">Custom Appearance</div>',
485
497
  })
486
498
 
487
- const settingsModal = wrapper.findComponent(SettingsModal)
499
+ const settingsModal = await openSettingsModal(wrapper)
488
500
  expect(settingsModal.vm.$slots.appearance).toBeDefined()
489
501
  })
490
502
  })
@@ -551,7 +563,7 @@ describe('AppTopBar', () => {
551
563
  })
552
564
 
553
565
  describe('combined features', () => {
554
- it('passes injected app experiment bindings to the popover and selector modal', () => {
566
+ it('passes injected app experiment bindings to the popover and selector modal', async () => {
555
567
  vi.mocked(usePlatformContext).mockReturnValueOnce({
556
568
  isIntegrated: computed(() => true),
557
569
  context: ref({ isIntegrated: true, theme: 'system' }),
@@ -587,7 +599,7 @@ describe('AppTopBar', () => {
587
599
  saveSuccessMessage: 'Saved',
588
600
  })),
589
601
  selectorModal: computed(() => ({
590
- modelValue: false,
602
+ modelValue: true,
591
603
  currentExperimentId: 12,
592
604
  })),
593
605
  openModal: vi.fn(),
@@ -614,6 +626,7 @@ describe('AppTopBar', () => {
614
626
  },
615
627
  })
616
628
 
629
+ await waitForAsyncComponents(wrapper)
617
630
  expect(wrapper.findComponent(ExperimentPopover).props()).toMatchObject({
618
631
  experimentName: 'Dose response',
619
632
  experimentCode: 'EXP-012',
@@ -626,7 +639,7 @@ describe('AppTopBar', () => {
626
639
  saveSuccessMessage: 'Saved',
627
640
  })
628
641
  expect(wrapper.findComponent(ExperimentSelectorModal).props()).toMatchObject({
629
- modelValue: false,
642
+ modelValue: true,
630
643
  currentExperimentId: 12,
631
644
  })
632
645
  })
@@ -822,14 +835,14 @@ describe('AppTopBar', () => {
822
835
  })
823
836
 
824
837
  describe('edge cases', () => {
825
- it('should handle empty settingsConfig object', () => {
838
+ it('should handle empty settingsConfig object', async () => {
826
839
  const wrapper = createWrapper({
827
840
  title: 'Test App',
828
841
  showSettings: true,
829
842
  settingsConfig: {},
830
843
  })
831
844
 
832
- const settingsModal = wrapper.findComponent(SettingsModal)
845
+ const settingsModal = await openSettingsModal(wrapper)
833
846
  // SettingsModal has default values for these props
834
847
  expect(settingsModal.props('title')).toBe('Settings')
835
848
  expect(settingsModal.props('tabs')).toEqual([])
@@ -837,7 +850,7 @@ describe('AppTopBar', () => {
837
850
  expect(settingsModal.props('size')).toBe('lg')
838
851
  })
839
852
 
840
- it('should handle settingsConfig with only some fields', () => {
853
+ it('should handle settingsConfig with only some fields', async () => {
841
854
  const settingsConfig: TopBarSettingsConfig = {
842
855
  title: 'Partial Config',
843
856
  }
@@ -848,7 +861,7 @@ describe('AppTopBar', () => {
848
861
  settingsConfig,
849
862
  })
850
863
 
851
- const settingsModal = wrapper.findComponent(SettingsModal)
864
+ const settingsModal = await openSettingsModal(wrapper)
852
865
  expect(settingsModal.props('title')).toBe('Partial Config')
853
866
  // SettingsModal defaults tabs to empty array
854
867
  expect(settingsModal.props('tabs')).toEqual([])
@@ -880,7 +893,7 @@ describe('AppTopBar', () => {
880
893
  })
881
894
 
882
895
  describe('type safety', () => {
883
- it('should accept valid settingsConfig with all fields', () => {
896
+ it('should accept valid settingsConfig with all fields', async () => {
884
897
  const settingsConfig: TopBarSettingsConfig = {
885
898
  title: 'Complete Settings',
886
899
  tabs: [
@@ -897,22 +910,22 @@ describe('AppTopBar', () => {
897
910
  settingsConfig,
898
911
  })
899
912
 
900
- expect(wrapper.findComponent(SettingsModal).exists()).toBe(true)
913
+ expect((await openSettingsModal(wrapper)).exists()).toBe(true)
901
914
  })
902
915
 
903
- it('should accept all valid size options', () => {
916
+ it('should accept all valid size options', async () => {
904
917
  const sizes: Array<'md' | 'lg' | 'xl'> = ['md', 'lg', 'xl']
905
918
 
906
- sizes.forEach((size) => {
919
+ for (const size of sizes) {
907
920
  const wrapper = createWrapper({
908
921
  title: 'Test App',
909
922
  showSettings: true,
910
923
  settingsConfig: { size },
911
924
  })
912
925
 
913
- const settingsModal = wrapper.findComponent(SettingsModal)
926
+ const settingsModal = await openSettingsModal(wrapper)
914
927
  expect(settingsModal.props('size')).toBe(size)
915
- })
928
+ }
916
929
  })
917
930
  })
918
931
 
@@ -1,4 +1,4 @@
1
- import { mount } from '@vue/test-utils'
1
+ import { flushPromises, mount } from '@vue/test-utils'
2
2
  import { createPinia } from 'pinia'
3
3
  import { computed, h, nextTick, ref } from 'vue'
4
4
  import { describe, expect, it, vi } from 'vitest'
@@ -316,6 +316,9 @@ describe('PluginWorkspaceView', () => {
316
316
  global: globalOptions,
317
317
  })
318
318
 
319
+ await flushPromises()
320
+ await nextTick()
321
+
319
322
  expect(wrapper.findComponent(ExperimentPopover).props()).toMatchObject({
320
323
  experimentName: 'Dose response',
321
324
  experimentCode: 'EXP-012',
@@ -327,6 +330,10 @@ describe('PluginWorkspaceView', () => {
327
330
  saveLoading: false,
328
331
  })
329
332
 
333
+ wrapper.findComponent(ExperimentPopover).vm.$emit('select')
334
+ await flushPromises()
335
+ await nextTick()
336
+
330
337
  wrapper.findComponent(ExperimentSelectorModal).vm.$emit('select', {
331
338
  id: 13,
332
339
  name: 'Selected experiment',