@morscherlab/mint-sdk 1.4.0-beta.2 → 1.4.0-beta.20
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 +14 -3
- package/dist/{ActionMenu-CScINahU.js → AlertBox-CzLg0UaL.js} +40 -209
- package/dist/AlertBox-CzLg0UaL.js.map +1 -0
- package/dist/{AppSidebar-BmiiPfz9.js → AppSidebar-6oTT-gPO.js} +1249 -239
- package/dist/AppSidebar-6oTT-gPO.js.map +1 -0
- package/dist/{ConfirmDialog-CJ7hqYUP.js → ConfirmDialog-CSls2Nuv.js} +2 -2
- package/dist/{ConfirmDialog-CJ7hqYUP.js.map → ConfirmDialog-CSls2Nuv.js.map} +1 -1
- package/dist/ExperimentPopover-9l4QZlvQ.js +337 -0
- package/dist/ExperimentPopover-9l4QZlvQ.js.map +1 -0
- package/dist/{ExperimentSelectorModal-D-9z7cNx.js → ExperimentSelectorModal-pR5fK3pA.js} +244 -211
- package/dist/ExperimentSelectorModal-pR5fK3pA.js.map +1 -0
- package/dist/{SegmentedControl-DERBN-Xd.js → SegmentedControl-CJ7EAmOq.js} +2 -2
- package/dist/{SegmentedControl-DERBN-Xd.js.map → SegmentedControl-CJ7EAmOq.js.map} +1 -1
- package/dist/{SettingsModal-CSLE31yg.js → SettingsModal-Dyitgzfp.js} +28 -27
- package/dist/SettingsModal-Dyitgzfp.js.map +1 -0
- package/dist/__stories__/experiment-helpers.d.ts +10 -0
- package/dist/__tests__/components/AppTopBarOnboarding.test.d.ts +1 -0
- package/dist/__tests__/composables/useOnboarding.test.d.ts +1 -0
- package/dist/__tests__/styles/paletteTokens.test.d.ts +1 -0
- package/dist/__tests__/styles/v2AuditRules.test.d.ts +1 -0
- package/dist/components/AppSidebar.vue.d.ts +1 -1
- package/dist/components/AppTopBar.vue.d.ts +20 -5
- package/dist/components/ArtifactSaveDialog.vue.d.ts +9 -1
- package/dist/components/AuditTrail.vue.d.ts +1 -1
- package/dist/components/BaseButton.vue.d.ts +4 -2
- package/dist/components/BaseSelect.vue.d.ts +2 -2
- package/dist/components/ChartContainer.vue.d.ts +2 -0
- package/dist/components/DatePicker.vue.d.ts +1 -1
- package/dist/components/ExperimentPopover.vue.d.ts +18 -0
- package/dist/components/ExperimentSelectorModal.vue.d.ts +15 -5
- package/dist/components/FilePicker.vue.d.ts +1 -1
- package/dist/components/FileUploader.vue.d.ts +2 -2
- package/dist/components/JobsStatusTray.vue.d.ts +15 -4
- package/dist/components/PluginIcon.vue.d.ts +2 -2
- package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
- package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
- package/dist/components/SampleSelector.vue.d.ts +6 -6
- package/dist/components/SequenceProgressBar.vue.d.ts +3 -0
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -1
- package/dist/components/StepWizard.vue.d.ts +3 -0
- package/dist/components/internal/AppTopBarOnboardingInternal.vue.d.ts +7 -0
- package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -1
- package/dist/components/internal/AutoGroupModalInternal.vue.d.ts +1 -1
- package/dist/components/internal/DatePickerTimeInternal.vue.d.ts +1 -1
- package/dist/components/internal/OnboardingLayerInternal.vue.d.ts +7 -0
- package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +2 -7
- package/dist/components/internal/SelectMultipleInternal.vue.d.ts +4 -0
- package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
- package/dist/composables/createClient.d.ts +2 -0
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/pluginIdentity.d.ts +24 -0
- package/dist/composables/pluginWorkspaceModel.d.ts +2 -2
- package/dist/composables/useAppExperiment.d.ts +8 -0
- package/dist/composables/useCalendarGrid.d.ts +1 -1
- package/dist/composables/useExperimentSelector.d.ts +6 -2
- package/dist/composables/useJobCenter.d.ts +41 -3
- package/dist/composables/useOnboarding.d.ts +119 -0
- package/dist/composables/usePlatform.d.ts +1 -0
- package/dist/{controlSchemaModel-CP5-_nAj.js → controlSchemaModel-DCUOx8ll.js} +2 -2
- package/dist/{controlSchemaModel-CP5-_nAj.js.map → controlSchemaModel-DCUOx8ll.js.map} +1 -1
- package/dist/{data-C2PwI_dn.js → data-DBnmeBZX.js} +179 -125
- package/dist/data-DBnmeBZX.js.map +1 -0
- package/dist/data.js +8 -8
- package/dist/{experiment-43OJtH7R.js → experiment-DyO54FQB.js} +25 -61
- package/dist/experiment-DyO54FQB.js.map +1 -0
- package/dist/{experimentStatus-BZmU4czV.js → experimentStatus-BFrKpHAH.js} +3 -3
- package/dist/{experimentStatus-BZmU4czV.js.map → experimentStatus-BFrKpHAH.js.map} +1 -1
- package/dist/index.js +20 -20
- package/dist/{install-DAID6c2-.js → install-qjELyNwc.js} +15 -14
- package/dist/{install-DAID6c2-.js.map → install-qjELyNwc.js.map} +1 -1
- package/dist/install.js +1 -1
- package/dist/{lab-DLwRl_Ql.js → lab-UyTbokQC.js} +136 -148
- package/dist/lab-UyTbokQC.js.map +1 -0
- package/dist/lab.js +4 -3
- package/dist/{records-BzTcxekG.js → records-CR_pYzIP.js} +533 -335
- package/dist/records-CR_pYzIP.js.map +1 -0
- package/dist/{runtime-D6gVat6r.js → runtime-CoHhBP10.js} +417 -54
- package/dist/runtime-CoHhBP10.js.map +1 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +7 -7
- package/dist/{shell-5ju97nOS.js → shell-BDuZf2zb.js} +10 -5
- package/dist/{shell-5ju97nOS.js.map → shell-BDuZf2zb.js.map} +1 -1
- package/dist/shell.d.ts +1 -0
- package/dist/shell.js +6 -6
- package/dist/stores/index.d.ts +1 -1
- package/dist/stores/index.js +2 -2
- package/dist/stores/settings.d.ts +4 -12
- package/dist/styles.css +2732 -2083
- package/dist/types/componentWorkflowTypes.d.ts +2 -0
- package/dist/types/components.d.ts +2 -0
- package/dist/types/generated/platformContext.d.ts +1 -0
- package/dist/types/generated-ui.d.ts +4 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/jobs.d.ts +9 -0
- package/dist/types/platform.d.ts +1 -1
- package/dist/{BaseModal-DfRIuVoj.js → useDropdownState-Bb2N4m40.js} +74 -4
- package/dist/useDropdownState-Bb2N4m40.js.map +1 -0
- package/dist/{useExperimentSamples-CxcnxZ8h.js → useExperimentSamples-Dno1fFMq.js} +4 -4
- package/dist/{useExperimentSamples-CxcnxZ8h.js.map → useExperimentSamples-Dno1fFMq.js.map} +1 -1
- package/dist/{useListReorder-CcbEYxX-.js → useListReorder-CQ9OCScc.js} +183 -351
- package/dist/useListReorder-CQ9OCScc.js.map +1 -0
- package/dist/{useRackEditor-BD5NIo5s.js → useRackEditor-wL3kPrRT.js} +73 -59
- package/dist/useRackEditor-wL3kPrRT.js.map +1 -0
- package/dist/useRealtime-C3whhVvb.js +508 -0
- package/dist/useRealtime-C3whhVvb.js.map +1 -0
- package/dist/useRequest-CDkrXNU7.js +244 -0
- package/dist/useRequest-CDkrXNU7.js.map +1 -0
- package/dist/utils/time.d.ts +7 -0
- package/package.json +1 -1
- package/src/__stories__/experiment-helpers.ts +3 -3
- package/src/__tests__/components/AppTopBar.test.ts +257 -3
- package/src/__tests__/components/AppTopBarOnboarding.test.ts +160 -0
- package/src/__tests__/components/AppTopBarPageSelector.test.ts +125 -9
- package/src/__tests__/components/AppTopBarPillNav.test.ts +35 -0
- package/src/__tests__/components/ArtifactSaveDialog.test.ts +78 -0
- package/src/__tests__/components/BaseButton.test.ts +19 -0
- package/src/__tests__/components/BaseSelect.multiple.test.ts +87 -32
- package/src/__tests__/components/BaseSelect.test.ts +2 -1
- package/src/__tests__/components/BatchProgressList.test.ts +16 -0
- package/src/__tests__/components/CalendarGridPanel.test.ts +2 -2
- package/src/__tests__/components/ChartContainer.test.ts +11 -0
- package/src/__tests__/components/ChemicalFormula.test.ts +6 -3
- package/src/__tests__/components/DatePicker.datetime.test.ts +6 -0
- package/src/__tests__/components/DatePicker.test.ts +21 -0
- package/src/__tests__/components/DatePicker.time.test.ts +2 -2
- package/src/__tests__/components/EmptyState.test.ts +1 -1
- package/src/__tests__/components/ExperimentPopover.test.ts +125 -16
- package/src/__tests__/components/ExperimentSelectorModal.test.ts +153 -14
- package/src/__tests__/components/GeneratedPluginView.test.ts +23 -2
- package/src/__tests__/components/InstrumentStateBadge.test.ts +4 -4
- package/src/__tests__/components/JobsStatusTray.test.ts +373 -71
- package/src/__tests__/components/PluginWorkspaceView.test.ts +63 -1
- package/src/__tests__/components/ResourcePickerListInternal.test.ts +0 -20
- package/src/__tests__/components/SampleSelector.test.ts +33 -8
- package/src/__tests__/components/SequenceProgressBar.test.ts +12 -0
- package/src/__tests__/components/SettingsModal.test.ts +9 -2
- package/src/__tests__/components/StepWizard.test.ts +11 -0
- package/src/__tests__/components/WellPlate.decoration.test.ts +11 -4
- package/src/__tests__/components/deprecationTags.test.ts +1 -1
- package/src/__tests__/composables/useAppExperiment.test.ts +27 -1
- package/src/__tests__/composables/useCalendarGrid.test.ts +4 -2
- package/src/__tests__/composables/useExperimentSelector.test.ts +111 -0
- package/src/__tests__/composables/useJobCenter.test.ts +262 -3
- package/src/__tests__/composables/useOnboarding.test.ts +317 -0
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +4 -3
- package/src/__tests__/stores/settings.test.ts +21 -21
- package/src/__tests__/styles/baseControlsRules.test.ts +6 -2
- package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
- package/src/__tests__/styles/cssTokens.test.ts +6 -3
- package/src/__tests__/styles/menusShellRefresh.test.ts +22 -12
- package/src/__tests__/styles/paletteTokens.test.ts +44 -0
- package/src/__tests__/styles/v2AuditRules.test.ts +104 -0
- package/src/__tests__/utils/plotlyTemplate.test.ts +4 -4
- package/src/__tests__/utils/time.test.ts +18 -1
- package/src/components/AdductText.story.vue +7 -0
- package/src/components/AdductText.vue +1 -1
- package/src/components/AlertBox.story.vue +2 -2
- package/src/components/AlertBox.vue +18 -16
- package/src/components/AppSidebar.story.vue +0 -28
- package/src/components/AppTopBar.onboarding.story.vue +69 -0
- package/src/components/AppTopBar.story.vue +105 -15
- package/src/components/AppTopBar.vue +248 -122
- package/src/components/ArtifactSaveDialog.story.vue +23 -0
- package/src/components/ArtifactSaveDialog.vue +102 -12
- package/src/components/AuditTrail.story.vue +2 -1
- package/src/components/AuditTrail.vue +2 -21
- package/src/components/Avatar.vue +10 -14
- package/src/components/BaseButton.vue +16 -2
- package/src/components/BaseSelect.multiple.story.vue +13 -2
- package/src/components/BaseSelect.vue +4 -3
- package/src/components/BatchProgressList.vue +35 -25
- package/src/components/ChartContainer.story.vue +2 -1
- package/src/components/ChartContainer.vue +8 -6
- package/src/components/ConfirmDialog.story.vue +1 -1
- package/src/components/EmptyState.vue +1 -1
- package/src/components/ExperimentPopover.story.vue +165 -53
- package/src/components/ExperimentPopover.vue +169 -187
- package/src/components/ExperimentSelectorModal.story.vue +73 -31
- package/src/components/ExperimentSelectorModal.vue +144 -111
- package/src/components/FormField.story.vue +8 -48
- package/src/components/GeneratedPluginView.vue +11 -10
- package/src/components/GeneratedResultRenderer.vue +3 -4
- package/src/components/InstrumentStateBadge.vue +1 -1
- package/src/components/JobsStatusTray.story.vue +97 -43
- package/src/components/JobsStatusTray.vue +287 -290
- package/src/components/LayoutResizeHandle.story.vue +65 -4
- package/src/components/LoadingSpinner.vue +1 -1
- package/src/components/NumberInput.vue +3 -6
- package/src/components/PluginIcon.story.vue +2 -10
- package/src/components/PluginIcon.vue +2 -2
- package/src/components/PluginWorkspaceView.props.ts +3 -0
- package/src/components/PluginWorkspaceView.story.vue +1 -1
- package/src/components/PluginWorkspaceView.vue +17 -0
- package/src/components/RuntimeUpdateNotice.vue +1 -1
- package/src/components/SampleSelector.story.vue +22 -7
- package/src/components/SampleSelector.vue +34 -37
- package/src/components/SampleSelectorGroupNode.vue +1 -1
- package/src/components/SectionCard.story.vue +35 -2
- package/src/components/SequenceProgressBar.story.vue +9 -0
- package/src/components/SequenceProgressBar.vue +6 -1
- package/src/components/SettingsModal.schema.ts +1 -1
- package/src/components/SettingsModal.vue +9 -14
- package/src/components/SmartGroupFieldRecipe.story.vue +1 -1
- package/src/components/SmartGroupManual.story.vue +1 -1
- package/src/components/StepWizard.story.vue +33 -0
- package/src/components/StepWizard.vue +8 -1
- package/src/components/WellPlate.story.vue +3 -1
- package/src/components/WellPlate.vue +7 -0
- package/src/components/internal/AppTopBarOnboardingInternal.vue +172 -0
- package/src/components/internal/AppTopBarPageSelectorInternal.vue +140 -72
- package/src/components/internal/AppTopBarPillNavInternal.vue +33 -2
- package/src/components/internal/DatePickerDateInternal.vue +16 -40
- package/src/components/internal/DatePickerDateTimeInternal.vue +3 -13
- package/src/components/internal/DatePickerTimeInternal.vue +5 -8
- package/src/components/internal/OnboardingLayerInternal.vue +173 -0
- package/src/components/internal/ResourcePickerListInternal.vue +45 -85
- package/src/components/internal/SelectMultipleInternal.vue +182 -65
- package/src/components/internal/SelectSearchableInternal.vue +2 -3
- package/src/components/internal/WorkspaceShellInternal.vue +12 -0
- package/src/composables/createClient.ts +2 -0
- package/src/composables/index.ts +1 -0
- package/src/composables/pluginIdentity.ts +31 -0
- package/src/composables/pluginWorkspaceModel.ts +2 -2
- package/src/composables/useAppExperiment.ts +20 -0
- package/src/composables/useCalendarGrid.ts +4 -3
- package/src/composables/useExperimentSelector.ts +77 -31
- package/src/composables/useJobCenter.ts +188 -6
- package/src/composables/useOnboarding.ts +465 -0
- package/src/composables/usePluginWorkspace.ts +1 -1
- package/src/runtime.ts +2 -1
- package/src/shell.ts +17 -0
- package/src/stores/index.ts +0 -2
- package/src/stores/settings.ts +20 -50
- package/src/styles/components/adduct-text.css +13 -5
- package/src/styles/components/alert-box.css +39 -58
- package/src/styles/components/app-layout.css +7 -5
- package/src/styles/components/app-page-selector.css +121 -44
- package/src/styles/components/app-pill-nav.css +11 -12
- package/src/styles/components/app-sidebar.css +9 -18
- package/src/styles/components/app-top-bar.css +219 -55
- package/src/styles/components/artifact-save-dialog.css +153 -2
- package/src/styles/components/audit-trail.css +16 -19
- package/src/styles/components/avatar.css +6 -4
- package/src/styles/components/batch-progress-list.css +38 -36
- package/src/styles/components/button.css +36 -59
- package/src/styles/components/calendar.css +12 -12
- package/src/styles/components/chart-container.css +34 -23
- package/src/styles/components/checkbox.css +2 -12
- package/src/styles/components/collapsible-card.css +27 -29
- package/src/styles/components/confirm-dialog.css +13 -14
- package/src/styles/components/dataframe.css +13 -5
- package/src/styles/components/date-picker.css +80 -92
- package/src/styles/components/datetime-picker.css +31 -43
- package/src/styles/components/divider.css +20 -20
- package/src/styles/components/dose-calculator.css +3 -3
- package/src/styles/components/empty-state.css +20 -17
- package/src/styles/components/experiment-code-badge.css +2 -0
- package/src/styles/components/experiment-popover.css +174 -329
- package/src/styles/components/experiment-selector-modal.css +88 -56
- package/src/styles/components/file-picker.css +3 -3
- package/src/styles/components/file-uploader.css +38 -49
- package/src/styles/components/form-builder.css +3 -3
- package/src/styles/components/formula-input.css +1 -1
- package/src/styles/components/icon-button.css +18 -24
- package/src/styles/components/input.css +5 -4
- package/src/styles/components/instrument-monitor.css +13 -15
- package/src/styles/components/jobs-status-tray.css +140 -296
- package/src/styles/components/layout-resize-handle.css +59 -16
- package/src/styles/components/modal.css +4 -6
- package/src/styles/components/molecule-input.css +2 -2
- package/src/styles/components/multi-select.css +65 -117
- package/src/styles/components/number-input.css +26 -19
- package/src/styles/components/onboarding.css +368 -0
- package/src/styles/components/pill.css +3 -2
- package/src/styles/components/plate-editor.css +0 -1
- package/src/styles/components/plugin-icon.css +26 -23
- package/src/styles/components/popover.css +2 -2
- package/src/styles/components/presence-avatars.css +1 -1
- package/src/styles/components/progress-bar.css +3 -3
- package/src/styles/components/protocol-step-editor.css +1 -3
- package/src/styles/components/resource-card.css +2 -2
- package/src/styles/components/resource-picker-list.css +6 -57
- package/src/styles/components/sample-hierarchy-tree.css +4 -4
- package/src/styles/components/sample-selector.css +138 -79
- package/src/styles/components/schedule-calendar.css +8 -8
- package/src/styles/components/searchable-select.css +12 -12
- package/src/styles/components/section-card.css +54 -49
- package/src/styles/components/segmented-control.css +18 -15
- package/src/styles/components/select.css +3 -3
- package/src/styles/components/sequence-input.css +2 -2
- package/src/styles/components/sequence-progress-bar.css +17 -8
- package/src/styles/components/settings-modal.css +7 -2
- package/src/styles/components/smart-group.css +18 -18
- package/src/styles/components/step-wizard.css +52 -16
- package/src/styles/components/tabs.css +2 -2
- package/src/styles/components/textarea.css +3 -3
- package/src/styles/components/time-picker.css +36 -21
- package/src/styles/components/toast.css +12 -10
- package/src/styles/components/toggle.css +1 -11
- package/src/styles/components/tooltip.css +10 -9
- package/src/styles/components/unit-input.css +8 -7
- package/src/styles/components/well-plate.css +9 -2
- package/src/styles/index.css +1 -0
- package/src/styles/variables.css +241 -78
- package/src/types/componentWorkflowTypes.ts +2 -0
- package/src/types/components.ts +2 -0
- package/src/types/generated/platformContext.ts +1 -0
- package/src/types/generated-ui.ts +4 -0
- package/src/types/index.ts +1 -0
- package/src/types/jobs.ts +10 -0
- package/src/types/platform.ts +1 -1
- package/src/utils/time.ts +21 -0
- package/dist/ActionMenu-CScINahU.js.map +0 -1
- package/dist/AppSidebar-BmiiPfz9.js.map +0 -1
- package/dist/BaseModal-DfRIuVoj.js.map +0 -1
- package/dist/ExperimentPopover-C4sGw-is.js +0 -359
- package/dist/ExperimentPopover-C4sGw-is.js.map +0 -1
- package/dist/ExperimentSelectorModal-D-9z7cNx.js.map +0 -1
- package/dist/SettingsModal-CSLE31yg.js.map +0 -1
- package/dist/data-C2PwI_dn.js.map +0 -1
- package/dist/experiment-43OJtH7R.js.map +0 -1
- package/dist/lab-DLwRl_Ql.js.map +0 -1
- package/dist/records-BzTcxekG.js.map +0 -1
- package/dist/runtime-D6gVat6r.js.map +0 -1
- package/dist/useListReorder-CcbEYxX-.js.map +0 -1
- package/dist/useRackEditor-BD5NIo5s.js.map +0 -1
- package/dist/useRealtime-PalKue84.js +0 -696
- package/dist/useRealtime-PalKue84.js.map +0 -1
- package/dist/useRequest-D5JDfsgZ.js +0 -76
- package/dist/useRequest-D5JDfsgZ.js.map +0 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { mount } from '@vue/test-utils'
|
|
1
|
+
import { flushPromises, mount } from '@vue/test-utils'
|
|
2
2
|
import { defineComponent, nextTick, shallowRef } from 'vue'
|
|
3
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
combinePluginJobSources,
|
|
7
7
|
useJobCenter,
|
|
8
8
|
type UseJobCenterOptions,
|
|
9
9
|
} from '../../composables/useJobCenter'
|
|
10
|
-
import type { JobStateInput, JobStatus } from '../../types/jobs'
|
|
10
|
+
import type { JobStateInput, JobStatus, TopBarJob } from '../../types/jobs'
|
|
11
11
|
|
|
12
12
|
function job(status: JobStatus, id: string = status): JobStateInput {
|
|
13
13
|
return {
|
|
@@ -281,3 +281,262 @@ describe('useJobCenter', () => {
|
|
|
281
281
|
expect(tray.deletableJobs.value).toEqual([])
|
|
282
282
|
})
|
|
283
283
|
})
|
|
284
|
+
|
|
285
|
+
describe('useJobCenter top-bar chip', () => {
|
|
286
|
+
const platformWindow = window as unknown as { __MINT_PLATFORM__?: unknown }
|
|
287
|
+
function integrateAs(userId: number, pluginId = 'peak-qc') {
|
|
288
|
+
platformWindow.__MINT_PLATFORM__ = { user: { id: userId }, plugin: { id: pluginId } }
|
|
289
|
+
}
|
|
290
|
+
const owned = (status: JobStatus, id: string, owner: string | null, user = owner): JobStateInput => ({
|
|
291
|
+
...job(status, id), owner_user_id: owner, user_id: user,
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
beforeEach(() => localStorage.clear())
|
|
295
|
+
afterEach(() => {
|
|
296
|
+
delete platformWindow.__MINT_PLATFORM__
|
|
297
|
+
vi.useRealTimers()
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it.each<[string, JobStateInput[], TopBarJob | null]>([
|
|
301
|
+
['an awaiting-input job first', [job('running', 'r'), job('awaiting_input', 'a')],
|
|
302
|
+
{ status: 'awaiting_input', label: 'a waiting for input', percent: null }],
|
|
303
|
+
['the first active job', [job('queued', 'q'), job('running', 'r')],
|
|
304
|
+
{ status: 'queued', label: 'q queued', percent: null }],
|
|
305
|
+
['the progress message and percent of a running job',
|
|
306
|
+
[{ ...job('running', 'r'), progress: { ...job('running', 'r').progress, message: 'Integrating' } }],
|
|
307
|
+
{ status: 'running', label: 'Integrating', percent: 42 }],
|
|
308
|
+
['nothing without active, held or failed jobs', [job('completed', 'c'), job('cancelled', 'x')], null],
|
|
309
|
+
])('should show %s when the viewer runs jobs', (_case, jobs, expected) => {
|
|
310
|
+
const { tray } = mountComposable({ source: { jobs }, loadOnMount: false })
|
|
311
|
+
expect(tray.topBarJob.value).toEqual(expected)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('should show only the viewer own jobs when the page is integrated', () => {
|
|
315
|
+
integrateAs(1)
|
|
316
|
+
const { tray } = mountComposable({
|
|
317
|
+
source: { jobs: [owned('running', 'theirs', '2'), owned('queued', 'mine', null, '1')] },
|
|
318
|
+
loadOnMount: false,
|
|
319
|
+
})
|
|
320
|
+
expect(tray.topBarJob.value).toMatchObject({ status: 'queued', label: 'mine queued' })
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
it.each([
|
|
324
|
+
['auth is off', false, 'running'],
|
|
325
|
+
['auth is required', true, undefined],
|
|
326
|
+
])('should treat jobs of an integrated page without a signed-in user as own only when %s', (_case, authRequired, status) => {
|
|
327
|
+
platformWindow.__MINT_PLATFORM__ = { user: null, plugin: { id: 'peak-qc' }, authRequired }
|
|
328
|
+
const { tray } = mountComposable({ source: { jobs: [owned('running', 'theirs', '2')] }, loadOnMount: false })
|
|
329
|
+
expect(tray.topBarJob.value?.status).toBe(status)
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
it('should hold a completed job when several jobs leave the active list in one update', async () => {
|
|
333
|
+
const jobs = shallowRef([job('running', 'a'), job('running', 'b')])
|
|
334
|
+
const { tray } = mountComposable({ source: { jobs }, loadOnMount: false })
|
|
335
|
+
|
|
336
|
+
jobs.value = [job('failed', 'a'), job('completed', 'b')]
|
|
337
|
+
await nextTick()
|
|
338
|
+
|
|
339
|
+
expect(tray.topBarJob.value).toEqual({ status: 'completed', label: 'b completed' })
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
it('should close the open tray instead of selecting the held job when the chip is clicked', async () => {
|
|
343
|
+
const jobs = shallowRef([job('running', 'r')])
|
|
344
|
+
const { tray } = mountComposable({ source: { jobs }, loadOnMount: false })
|
|
345
|
+
const onSelect = vi.fn()
|
|
346
|
+
jobs.value = [job('completed', 'r')]
|
|
347
|
+
await nextTick()
|
|
348
|
+
tray.open()
|
|
349
|
+
|
|
350
|
+
tray.handleTopBarClick(onSelect)
|
|
351
|
+
|
|
352
|
+
expect([tray.isOpen.value, onSelect.mock.calls.length]).toEqual([false, 0])
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
it('should drop the held job when it leaves the job list', async () => {
|
|
356
|
+
const jobs = shallowRef([job('running', 'r')])
|
|
357
|
+
const { tray } = mountComposable({ source: { jobs }, loadOnMount: false })
|
|
358
|
+
const onSelect = vi.fn()
|
|
359
|
+
jobs.value = [job('completed', 'r')]
|
|
360
|
+
await nextTick()
|
|
361
|
+
jobs.value = []
|
|
362
|
+
await nextTick()
|
|
363
|
+
|
|
364
|
+
tray.handleTopBarClick(onSelect)
|
|
365
|
+
|
|
366
|
+
expect([tray.topBarJob.value, onSelect.mock.calls.length, tray.isOpen.value]).toEqual([null, 0, true])
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
it('should mark a failure seen when the job fails while the tray is open', async () => {
|
|
370
|
+
localStorage.setItem('mint.jobs.standalone.seen-failed', '[]')
|
|
371
|
+
const jobs = shallowRef([job('running', 'r')])
|
|
372
|
+
const { tray } = mountComposable({ source: { jobs }, loadOnMount: false })
|
|
373
|
+
tray.open()
|
|
374
|
+
await nextTick()
|
|
375
|
+
|
|
376
|
+
jobs.value = [job('failed', 'r')]
|
|
377
|
+
await nextTick()
|
|
378
|
+
tray.close()
|
|
379
|
+
await nextTick()
|
|
380
|
+
|
|
381
|
+
expect(tray.topBarJob.value).toBeNull()
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it.each([
|
|
385
|
+
['an empty list', [] as JobStateInput[], { status: 'failed', label: '1 failed' }],
|
|
386
|
+
['an old failure', [job('failed', 'old')], { status: 'failed', label: '1 failed' }],
|
|
387
|
+
])('should take the baseline when the initial load returns %s', async (_case, loaded, later) => {
|
|
388
|
+
const jobs = shallowRef<JobStateInput[]>([])
|
|
389
|
+
const { tray } = mountComposable({ source: { jobs, refresh: async () => { jobs.value = loaded } } })
|
|
390
|
+
await flushPromises()
|
|
391
|
+
const baseline = tray.topBarJob.value
|
|
392
|
+
|
|
393
|
+
jobs.value = [...loaded, job('failed', 'new')]
|
|
394
|
+
await nextTick()
|
|
395
|
+
|
|
396
|
+
expect([baseline, tray.topBarJob.value]).toEqual([null, later])
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
it('should not hold a job when the source is replaced by one where it already completed', async () => {
|
|
400
|
+
const source = shallowRef({ jobs: [job('running', 'x')] })
|
|
401
|
+
const { tray } = mountComposable({ source, loadOnMount: false })
|
|
402
|
+
|
|
403
|
+
source.value = { jobs: [job('completed', 'x')] }
|
|
404
|
+
await nextTick()
|
|
405
|
+
|
|
406
|
+
expect(tray.topBarJob.value).toBeNull()
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('should alert only failures after the first job list when nothing was stored', async () => {
|
|
410
|
+
const jobs = shallowRef<JobStateInput[]>([])
|
|
411
|
+
const { tray } = mountComposable({ source: { jobs }, loadOnMount: false })
|
|
412
|
+
|
|
413
|
+
jobs.value = [job('failed', 'old')]
|
|
414
|
+
await nextTick()
|
|
415
|
+
const baseline = tray.topBarJob.value
|
|
416
|
+
jobs.value = [job('failed', 'old'), job('failed', 'new')]
|
|
417
|
+
await nextTick()
|
|
418
|
+
|
|
419
|
+
expect({
|
|
420
|
+
baseline,
|
|
421
|
+
later: tray.topBarJob.value,
|
|
422
|
+
stored: JSON.parse(localStorage.getItem('mint.jobs.standalone.seen-failed') ?? 'null'),
|
|
423
|
+
}).toEqual({ baseline: null, later: { status: 'failed', label: '1 failed' }, stored: ['old'] })
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
it.each<[string, TopBarJob | null, TopBarJob]>([
|
|
427
|
+
['use the override', { status: 'completed', label: 'Saved to the experiment' },
|
|
428
|
+
{ status: 'completed', label: 'Saved to the experiment' }],
|
|
429
|
+
['keep the default', null, { status: 'completed', label: 'r completed' }],
|
|
430
|
+
])('should %s for the held job when the topBarJob option returns that value', async (_case, override, expected) => {
|
|
431
|
+
const jobs = shallowRef([job('running', 'r')])
|
|
432
|
+
const topBarJob = vi.fn((shown: { status: JobStatus }) => (shown.status === 'completed' ? override : null))
|
|
433
|
+
const { tray } = mountComposable({ source: { jobs }, loadOnMount: false, topBarJob })
|
|
434
|
+
|
|
435
|
+
jobs.value = [job('completed', 'r')]
|
|
436
|
+
await nextTick()
|
|
437
|
+
|
|
438
|
+
expect(tray.topBarJob.value).toEqual(expected)
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it('should hold a just-completed job for 6 s and select it on click when nothing else runs', async () => {
|
|
442
|
+
vi.useFakeTimers()
|
|
443
|
+
const jobs = shallowRef([job('running', 'r')])
|
|
444
|
+
const { tray } = mountComposable({ source: { jobs }, loadOnMount: false })
|
|
445
|
+
const onSelect = vi.fn()
|
|
446
|
+
|
|
447
|
+
jobs.value = [{ ...job('completed', 'r') }]
|
|
448
|
+
await nextTick()
|
|
449
|
+
const held = tray.topBarJob.value
|
|
450
|
+
tray.handleTopBarClick(onSelect)
|
|
451
|
+
vi.advanceTimersByTime(5999)
|
|
452
|
+
const stillHeld = tray.topBarJob.value
|
|
453
|
+
vi.advanceTimersByTime(1)
|
|
454
|
+
tray.handleTopBarClick(onSelect)
|
|
455
|
+
|
|
456
|
+
expect({
|
|
457
|
+
held,
|
|
458
|
+
stillHeld: stillHeld?.status,
|
|
459
|
+
after: tray.topBarJob.value,
|
|
460
|
+
selected: onSelect.mock.calls.map(([selected]) => selected.job_id),
|
|
461
|
+
open: tray.isOpen.value,
|
|
462
|
+
}).toEqual({
|
|
463
|
+
held: { status: 'completed', label: 'r completed' },
|
|
464
|
+
stillHeld: 'completed',
|
|
465
|
+
after: null,
|
|
466
|
+
selected: ['r'],
|
|
467
|
+
open: true,
|
|
468
|
+
})
|
|
469
|
+
})
|
|
470
|
+
|
|
471
|
+
it.each([
|
|
472
|
+
['standalone', () => undefined, 'mint.jobs.standalone.seen-failed'],
|
|
473
|
+
['integrated', () => integrateAs(1), 'mint.jobs.peak-qc.seen-failed'],
|
|
474
|
+
])('should count unseen failures until the tray opens when %s', async (_mode, setup, key) => {
|
|
475
|
+
setup()
|
|
476
|
+
localStorage.setItem(key, '[]')
|
|
477
|
+
const jobs = [owned('failed', 'f1', '1'), owned('failed', 'f2', '1'), owned('failed', 'other', '2')]
|
|
478
|
+
const { tray } = mountComposable({ source: { jobs }, loadOnMount: false })
|
|
479
|
+
const before = tray.topBarJob.value
|
|
480
|
+
|
|
481
|
+
tray.open()
|
|
482
|
+
await nextTick()
|
|
483
|
+
const reopened = mountComposable({ source: { jobs }, loadOnMount: false }).tray
|
|
484
|
+
|
|
485
|
+
expect({
|
|
486
|
+
before,
|
|
487
|
+
after: tray.topBarJob.value,
|
|
488
|
+
reopened: reopened.topBarJob.value,
|
|
489
|
+
stored: JSON.parse(localStorage.getItem(key) ?? 'null'),
|
|
490
|
+
}).toEqual({
|
|
491
|
+
before: { status: 'failed', label: _mode === 'standalone' ? '3 failed' : '2 failed' },
|
|
492
|
+
after: null,
|
|
493
|
+
reopened: null,
|
|
494
|
+
stored: _mode === 'standalone' ? ['f1', 'f2', 'other'] : ['f1', 'f2'],
|
|
495
|
+
})
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
it.each<[string, TopBarJob | null, TopBarJob]>([
|
|
499
|
+
['replace the default mapping', { status: 'running', label: 'Decoding in the browser', percent: 10 },
|
|
500
|
+
{ status: 'running', label: 'Decoding in the browser', percent: 10 }],
|
|
501
|
+
['keep the default mapping', null, { status: 'awaiting_input', label: 'a waiting for input', percent: null }],
|
|
502
|
+
])('should %s when the topBarJob option returns that value', (_case, override, expected) => {
|
|
503
|
+
const { tray } = mountComposable({
|
|
504
|
+
source: { jobs: [job('awaiting_input', 'a')] },
|
|
505
|
+
loadOnMount: false,
|
|
506
|
+
topBarJob: () => override,
|
|
507
|
+
})
|
|
508
|
+
expect(tray.topBarJob.value).toEqual(expected)
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
it('should count only completed jobs when the list holds finished and active jobs', () => {
|
|
512
|
+
const { tray } = mountComposable({
|
|
513
|
+
source: { jobs: [job('completed', 'c1'), job('completed', 'c2'), job('failed', 'f'), job('running', 'r')] },
|
|
514
|
+
loadOnMount: false,
|
|
515
|
+
})
|
|
516
|
+
expect(tray.completedCount.value).toBe(2)
|
|
517
|
+
})
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
describe('useJobCenter row view', () => {
|
|
521
|
+
const awaiting = job('awaiting_input', 'a')
|
|
522
|
+
const running = job('running', 'r')
|
|
523
|
+
|
|
524
|
+
it.each<[string, JobStateInput, UseJobCenterOptions['rowJob'], ReturnType<ReturnType<typeof useJobCenter>['rowView']>]>([
|
|
525
|
+
['absent', running, undefined, { status: 'running', percent: 42, message: 'running' }],
|
|
526
|
+
['null', awaiting, () => null,
|
|
527
|
+
{ status: 'awaiting_input', percent: null, message: 'Waiting for external input' }],
|
|
528
|
+
['a status and percent', awaiting, () => ({ status: 'running', percent: 40 }),
|
|
529
|
+
{ status: 'running', percent: 40, message: 'Waiting for external input' }],
|
|
530
|
+
['a message only', running, () => ({ message: 'Decoding 3/8' }),
|
|
531
|
+
{ status: 'running', percent: 42, message: 'Decoding 3/8' }],
|
|
532
|
+
['undefined and null fields', running, () => ({ status: null, percent: undefined, message: null }),
|
|
533
|
+
{ status: 'running', percent: 42, message: 'running' }],
|
|
534
|
+
['a status only', running, () => ({ status: 'awaiting_input' }),
|
|
535
|
+
{ status: 'awaiting_input', percent: 42, message: 'running' }],
|
|
536
|
+
['a null percent', running, () => ({ percent: null }),
|
|
537
|
+
{ status: 'running', percent: null, message: 'running' }],
|
|
538
|
+
])('should resolve the row view from the job when the row override is %s', (_case, input, rowJob, expected) => {
|
|
539
|
+
const { tray } = mountComposable({ source: { jobs: [input] }, loadOnMount: false, rowJob })
|
|
540
|
+
expect(tray.rowView(tray.jobs.value[0]!)).toEqual(expected)
|
|
541
|
+
})
|
|
542
|
+
})
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { defineComponent, h, nextTick, ref } from 'vue'
|
|
3
|
+
import { mount, type VueWrapper } from '@vue/test-utils'
|
|
4
|
+
import {
|
|
5
|
+
ANCHOR_WAIT_MS,
|
|
6
|
+
provideOnboarding,
|
|
7
|
+
type OnboardingDefinition,
|
|
8
|
+
type OnboardingState,
|
|
9
|
+
type ProvideOnboardingOptions,
|
|
10
|
+
} from '../../composables/useOnboarding'
|
|
11
|
+
|
|
12
|
+
const KEY = 'mint.onboarding.app.7'
|
|
13
|
+
|
|
14
|
+
const definition: OnboardingDefinition = {
|
|
15
|
+
tours: [
|
|
16
|
+
{
|
|
17
|
+
id: 'intro',
|
|
18
|
+
title: 'Intro',
|
|
19
|
+
steps: [
|
|
20
|
+
{ id: 'a', anchor: 'a', title: 'A', body: '' },
|
|
21
|
+
{ id: 'b', anchor: 'b', title: 'B', body: '' },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
{ id: 'list', title: 'List', route: 'experiments', steps: [{ id: 'new', anchor: 'new', title: 'New', body: '' }] },
|
|
25
|
+
],
|
|
26
|
+
checklist: [{ id: 'create', title: 'Create' }],
|
|
27
|
+
whatsNew: [{ id: 'jobs', anchor: 'a', title: 'Jobs', body: '' }],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let wrapper: VueWrapper | undefined
|
|
31
|
+
|
|
32
|
+
function addAnchor(id: string) {
|
|
33
|
+
const element = document.createElement('button')
|
|
34
|
+
element.dataset.onboarding = id
|
|
35
|
+
document.body.append(element)
|
|
36
|
+
return element
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function setup(options: Partial<ProvideOnboardingOptions> = {}, def = definition): OnboardingState {
|
|
40
|
+
let state!: OnboardingState
|
|
41
|
+
wrapper = mount(defineComponent({
|
|
42
|
+
setup() {
|
|
43
|
+
state = provideOnboarding(def, { appId: 'app', userId: 7, ...options })
|
|
44
|
+
return () => h('div')
|
|
45
|
+
},
|
|
46
|
+
}))
|
|
47
|
+
return state
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function flush() {
|
|
51
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
52
|
+
await nextTick()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const stored = () => JSON.parse(localStorage.getItem(KEY) ?? 'null') as string[] | null
|
|
56
|
+
|
|
57
|
+
describe('provideOnboarding', () => {
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
vi.useFakeTimers()
|
|
60
|
+
localStorage.clear()
|
|
61
|
+
addAnchor('a')
|
|
62
|
+
addAnchor('b')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
wrapper?.unmount()
|
|
67
|
+
document.body.innerHTML = ''
|
|
68
|
+
vi.useRealTimers()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should start the first tour of the route and mark each shown step as seen', async () => {
|
|
72
|
+
const state = setup()
|
|
73
|
+
await flush()
|
|
74
|
+
|
|
75
|
+
expect(state.active.value?.step.id).toBe('a')
|
|
76
|
+
state.next()
|
|
77
|
+
await flush()
|
|
78
|
+
|
|
79
|
+
expect(state.active.value?.step.id).toBe('b')
|
|
80
|
+
expect(stored()).toEqual(expect.arrayContaining(['step:intro/a', 'step:intro/b']))
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should mark the existing news as seen on the first run only', async () => {
|
|
84
|
+
const first = setup({ autoStart: false })
|
|
85
|
+
expect(first.unseenNews.value).toBe(0)
|
|
86
|
+
wrapper?.unmount()
|
|
87
|
+
|
|
88
|
+
const later = setup({ autoStart: false }, { ...definition, whatsNew: [...definition.whatsNew!, { id: 'pins', anchor: 'b', title: 'Pins', body: '' }] })
|
|
89
|
+
|
|
90
|
+
expect(later.news.value.map((item) => item.unseen)).toEqual([false, true])
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should show a returning user only the steps with new ids', async () => {
|
|
94
|
+
localStorage.setItem(KEY, JSON.stringify(['step:intro/a']))
|
|
95
|
+
const state = setup()
|
|
96
|
+
await flush()
|
|
97
|
+
|
|
98
|
+
expect(state.active.value).toMatchObject({ step: { id: 'b' }, index: 0, count: 1 })
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('should mark every step seen on dismiss and not start the tour again', async () => {
|
|
102
|
+
const state = setup()
|
|
103
|
+
await flush()
|
|
104
|
+
state.dismiss()
|
|
105
|
+
wrapper?.unmount()
|
|
106
|
+
|
|
107
|
+
const again = setup()
|
|
108
|
+
await flush()
|
|
109
|
+
|
|
110
|
+
expect(stored()).toEqual(expect.arrayContaining(['step:intro/a', 'step:intro/b']))
|
|
111
|
+
expect(again.active.value).toBeNull()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('should skip a step whose anchor does not appear in time and keep it unseen', async () => {
|
|
115
|
+
document.querySelector('[data-onboarding="a"]')!.remove()
|
|
116
|
+
const state = setup()
|
|
117
|
+
await flush()
|
|
118
|
+
expect(state.active.value).toBeNull()
|
|
119
|
+
|
|
120
|
+
await vi.advanceTimersByTimeAsync(ANCHOR_WAIT_MS)
|
|
121
|
+
await nextTick()
|
|
122
|
+
|
|
123
|
+
expect(state.active.value?.step.id).toBe('b')
|
|
124
|
+
expect(stored()).not.toContain('step:intro/a')
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should leave a missing anchor out of the count and skip it at once after Next', async () => {
|
|
128
|
+
const state = setup({}, { tours: [{ id: 't', title: 'T', steps: [
|
|
129
|
+
{ id: 'a', anchor: 'a', title: 'A', body: '' },
|
|
130
|
+
{ id: 'gone', anchor: 'gone', title: 'Gone', body: '' },
|
|
131
|
+
{ id: 'b', anchor: 'b', title: 'B', body: '' },
|
|
132
|
+
] }] })
|
|
133
|
+
await flush()
|
|
134
|
+
expect(state.active.value).toMatchObject({ step: { id: 'a' }, index: 0, count: 2 })
|
|
135
|
+
|
|
136
|
+
state.next()
|
|
137
|
+
await flush()
|
|
138
|
+
|
|
139
|
+
expect(state.active.value).toMatchObject({ step: { id: 'b' }, index: 1, count: 2 })
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should still show a later step whose anchor renders after the tour started', async () => {
|
|
143
|
+
const state = setup({}, { tours: [{ id: 't', title: 'T', steps: [
|
|
144
|
+
{ id: 'a', anchor: 'a', title: 'A', body: '' },
|
|
145
|
+
{ id: 'late', anchor: 'late', title: 'Late', body: '' },
|
|
146
|
+
{ id: 'b', anchor: 'b', title: 'B', body: '' },
|
|
147
|
+
] }] })
|
|
148
|
+
await flush()
|
|
149
|
+
expect(state.active.value).toMatchObject({ step: { id: 'a' }, count: 2 })
|
|
150
|
+
|
|
151
|
+
addAnchor('late')
|
|
152
|
+
state.next()
|
|
153
|
+
await flush()
|
|
154
|
+
|
|
155
|
+
expect(state.active.value).toMatchObject({ step: { id: 'late' }, index: 1, count: 3 })
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('should keep the position and count in step after Back when a shown anchor went away', async () => {
|
|
159
|
+
addAnchor('c')
|
|
160
|
+
const state = setup({}, { tours: [{ id: 't', title: 'T', steps: [
|
|
161
|
+
{ id: 'a', anchor: 'a', title: 'A', body: '' },
|
|
162
|
+
{ id: 'b', anchor: 'b', title: 'B', body: '' },
|
|
163
|
+
{ id: 'c', anchor: 'c', title: 'C', body: '' },
|
|
164
|
+
] }] })
|
|
165
|
+
await flush()
|
|
166
|
+
state.next()
|
|
167
|
+
await flush()
|
|
168
|
+
state.next()
|
|
169
|
+
await flush()
|
|
170
|
+
document.querySelector('[data-onboarding="b"]')!.remove()
|
|
171
|
+
|
|
172
|
+
state.back()
|
|
173
|
+
await flush()
|
|
174
|
+
expect(state.active.value).toMatchObject({ step: { id: 'a' }, index: 0, count: 2 })
|
|
175
|
+
state.next()
|
|
176
|
+
await flush()
|
|
177
|
+
|
|
178
|
+
expect(state.active.value).toMatchObject({ step: { id: 'c' }, index: 1, count: 2 })
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should show a step whose anchor appears while it waits', async () => {
|
|
182
|
+
document.querySelector('[data-onboarding="a"]')!.remove()
|
|
183
|
+
const state = setup()
|
|
184
|
+
await flush()
|
|
185
|
+
|
|
186
|
+
addAnchor('a')
|
|
187
|
+
await flush()
|
|
188
|
+
|
|
189
|
+
expect(state.active.value?.step.id).toBe('a')
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('should wait for an open modal to close before it starts a tour', async () => {
|
|
193
|
+
const modal = document.createElement('div')
|
|
194
|
+
modal.setAttribute('aria-modal', 'true')
|
|
195
|
+
document.body.append(modal)
|
|
196
|
+
const state = setup()
|
|
197
|
+
await flush()
|
|
198
|
+
expect(state.active.value).toBeNull()
|
|
199
|
+
|
|
200
|
+
modal.remove()
|
|
201
|
+
await flush()
|
|
202
|
+
|
|
203
|
+
expect(state.active.value?.step.id).toBe('a')
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('should drop a pending auto-start when the user ends a tour picked by hand', async () => {
|
|
207
|
+
const modal = document.createElement('div')
|
|
208
|
+
modal.setAttribute('aria-modal', 'true')
|
|
209
|
+
document.body.append(modal)
|
|
210
|
+
const state = setup({}, { tours: [
|
|
211
|
+
definition.tours![0],
|
|
212
|
+
{ id: 'more', title: 'More', steps: [{ id: 'b', anchor: 'b', title: 'B', body: '' }] },
|
|
213
|
+
] })
|
|
214
|
+
await flush()
|
|
215
|
+
state.startTour('intro')
|
|
216
|
+
await flush()
|
|
217
|
+
state.dismiss()
|
|
218
|
+
|
|
219
|
+
modal.remove()
|
|
220
|
+
await flush()
|
|
221
|
+
|
|
222
|
+
expect(state.active.value).toBeNull()
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('should start the tour of a route when the app reaches that route', async () => {
|
|
226
|
+
localStorage.setItem(KEY, JSON.stringify(['step:intro/a', 'step:intro/b']))
|
|
227
|
+
addAnchor('new')
|
|
228
|
+
const route = ref('home')
|
|
229
|
+
const state = setup({ route })
|
|
230
|
+
await flush()
|
|
231
|
+
expect(state.tours.value.map((tour) => tour.id)).toEqual(['intro'])
|
|
232
|
+
expect(state.active.value).toBeNull()
|
|
233
|
+
|
|
234
|
+
route.value = 'experiments'
|
|
235
|
+
await flush()
|
|
236
|
+
|
|
237
|
+
expect(state.active.value?.step.id).toBe('new')
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it.each([
|
|
241
|
+
['finishes', (state: OnboardingState) => { state.next(); return flush().then(() => state.next()) }, 'new'],
|
|
242
|
+
['is dismissed', (state: OnboardingState) => state.dismiss(), undefined],
|
|
243
|
+
])('should start the next tour of the page only when the first %s', async (_, end, next) => {
|
|
244
|
+
addAnchor('new')
|
|
245
|
+
const state = setup({ route: 'experiments' })
|
|
246
|
+
await flush()
|
|
247
|
+
expect(state.active.value?.step.id).toBe('a')
|
|
248
|
+
|
|
249
|
+
await end(state)
|
|
250
|
+
await flush()
|
|
251
|
+
|
|
252
|
+
expect(state.active.value?.step.id).toBe(next)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('should leave out a step that is not available', async () => {
|
|
256
|
+
const state = setup({}, { tours: [{ id: 't', title: 'T', steps: [
|
|
257
|
+
{ id: 'a', anchor: 'a', title: 'A', body: '', available: () => false },
|
|
258
|
+
{ id: 'b', anchor: 'b', title: 'B', body: '' },
|
|
259
|
+
] }] })
|
|
260
|
+
await flush()
|
|
261
|
+
|
|
262
|
+
expect(state.active.value).toMatchObject({ step: { id: 'b' }, count: 1 })
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('should keep the keys another tab stored when it writes', () => {
|
|
266
|
+
const state = setup({ autoStart: false })
|
|
267
|
+
localStorage.setItem(KEY, JSON.stringify([...stored()!, 'task:create']))
|
|
268
|
+
|
|
269
|
+
state.setChecklistHidden(true)
|
|
270
|
+
|
|
271
|
+
expect(stored()).toEqual(expect.arrayContaining(['task:create', 'hide:checklist']))
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('should move on when the anchor of an advanceOn click step is clicked', async () => {
|
|
275
|
+
const state = setup({}, { tours: [{ id: 't', title: 'T', steps: [
|
|
276
|
+
{ id: 'a', anchor: 'a', title: 'A', body: '', advanceOn: 'click' },
|
|
277
|
+
{ id: 'b', anchor: 'b', title: 'B', body: '' },
|
|
278
|
+
] }] })
|
|
279
|
+
await flush()
|
|
280
|
+
|
|
281
|
+
document.querySelector<HTMLElement>('[data-onboarding="a"]')!.click()
|
|
282
|
+
await flush()
|
|
283
|
+
|
|
284
|
+
expect(state.active.value?.step.id).toBe('b')
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('should keep a task done after its predicate turns false', async () => {
|
|
288
|
+
const created = ref(true)
|
|
289
|
+
const state = setup({ autoStart: false }, { checklist: [{ id: 'create', title: 'Create', done: () => created.value }] })
|
|
290
|
+
created.value = false
|
|
291
|
+
state.refresh()
|
|
292
|
+
await nextTick()
|
|
293
|
+
|
|
294
|
+
expect(state.tasks.value[0].done).toBe(true)
|
|
295
|
+
expect(state.checklistVisible.value).toBe(false)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
it.each([
|
|
299
|
+
['complete', (state: OnboardingState) => state.complete('create'), false],
|
|
300
|
+
['hide', (state: OnboardingState) => state.setChecklistHidden(true), false],
|
|
301
|
+
['nothing', () => {}, true],
|
|
302
|
+
])('should show the checklist chip after %s: %s', (_, act, visible) => {
|
|
303
|
+
const state = setup({ autoStart: false })
|
|
304
|
+
act(state)
|
|
305
|
+
|
|
306
|
+
expect(state.checklistVisible.value).toBe(visible)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('should stay off without a user', async () => {
|
|
310
|
+
const state = setup({ userId: null })
|
|
311
|
+
await flush()
|
|
312
|
+
|
|
313
|
+
expect(state.enabled.value).toBe(false)
|
|
314
|
+
expect(state.active.value).toBeNull()
|
|
315
|
+
expect(localStorage.length).toBe(0)
|
|
316
|
+
})
|
|
317
|
+
})
|
|
@@ -266,13 +266,13 @@ describe('frontend docs catalog coverage', () => {
|
|
|
266
266
|
})
|
|
267
267
|
})
|
|
268
268
|
|
|
269
|
-
it('documents only the
|
|
269
|
+
it('documents only the center-driven JobsStatusTray props', () => {
|
|
270
270
|
const manifest = extractFrontendManifest()
|
|
271
271
|
const jobsStatusTray = manifest.categories.components.find(item => item.name === 'JobsStatusTray')
|
|
272
272
|
|
|
273
273
|
expect(jobsStatusTray?.props?.map(prop => prop.name).sort()).toEqual([
|
|
274
|
-
'
|
|
275
|
-
'
|
|
274
|
+
'center',
|
|
275
|
+
'confirm',
|
|
276
276
|
'teleportTo',
|
|
277
277
|
'title',
|
|
278
278
|
])
|
|
@@ -375,6 +375,7 @@ describe('frontend docs catalog coverage', () => {
|
|
|
375
375
|
'usePluginJobs',
|
|
376
376
|
'useGeneratedAnalysis',
|
|
377
377
|
'usePluginJobCenter',
|
|
378
|
+
'JobsStatusTrayTab',
|
|
378
379
|
'useRequestSyncState',
|
|
379
380
|
'useAnalysisArtifacts',
|
|
380
381
|
'useTimeUtils',
|