@morscherlab/mint-sdk 1.4.0-beta.9 → 1.4.0
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/{AlertBox-B2Bgr3ei.js → AlertBox-DwgNTEqf.js} +3 -3
- package/dist/{AlertBox-B2Bgr3ei.js.map → AlertBox-DwgNTEqf.js.map} +1 -1
- package/dist/{AppSidebar-Dq9W1jE2.js → AppSidebar-CZZaxni0.js} +1103 -144
- package/dist/AppSidebar-CZZaxni0.js.map +1 -0
- package/dist/{ConfirmDialog-nbJj51is.js → ConfirmDialog-CjWoxAD1.js} +2 -2
- package/dist/{ConfirmDialog-nbJj51is.js.map → ConfirmDialog-CjWoxAD1.js.map} +1 -1
- package/dist/{ExperimentPopover-5T1fAq2L.js → ExperimentPopover-DujiuQIO.js} +5 -5
- package/dist/{ExperimentPopover-5T1fAq2L.js.map → ExperimentPopover-DujiuQIO.js.map} +1 -1
- package/dist/{ExperimentSelectorModal-65peG4Up.js → ExperimentSelectorModal-B4j4eUOJ.js} +114 -98
- package/dist/ExperimentSelectorModal-B4j4eUOJ.js.map +1 -0
- package/dist/{SegmentedControl-BtnnjWzs.js → SegmentedControl-DyUPH3I5.js} +2 -2
- package/dist/{SegmentedControl-BtnnjWzs.js.map → SegmentedControl-DyUPH3I5.js.map} +1 -1
- package/dist/{SettingsModal-483nyI6b.js → SettingsModal-D2MOOtuO.js} +26 -25
- package/dist/SettingsModal-D2MOOtuO.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__/components/FloatingPanelsInModal.test.d.ts +1 -0
- package/dist/__tests__/composables/useFloatingPanel.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/AppTopBar.vue.d.ts +20 -5
- package/dist/components/BaseButton.vue.d.ts +4 -2
- package/dist/components/ExperimentSelectorModal.vue.d.ts +15 -1
- package/dist/components/JobsStatusTray.vue.d.ts +15 -4
- package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
- package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
- package/dist/components/SmartGroupFieldRecipe.vue.d.ts +3 -3
- 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 +2 -2
- package/dist/components/internal/OnboardingLayerInternal.vue.d.ts +7 -0
- package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
- package/dist/composables/pluginWorkspaceModel.d.ts +2 -2
- package/dist/composables/useFileImport.d.ts +7 -0
- package/dist/composables/useFloatingPanel.d.ts +29 -0
- package/dist/composables/useJobCenter.d.ts +41 -3
- package/dist/composables/useOnboarding.d.ts +119 -0
- package/dist/{controlSchemaModel-B8CTXagP.js → controlSchemaModel-Bsg0t5Ab.js} +2 -2
- package/dist/{controlSchemaModel-B8CTXagP.js.map → controlSchemaModel-Bsg0t5Ab.js.map} +1 -1
- package/dist/{data-CgILxDLW.js → data-Boeh4uPI.js} +13 -12
- package/dist/{data-CgILxDLW.js.map → data-Boeh4uPI.js.map} +1 -1
- package/dist/data.d.ts +1 -1
- package/dist/data.js +9 -9
- package/dist/{experiment-43OJtH7R.js → experiment-DyO54FQB.js} +25 -61
- package/dist/experiment-DyO54FQB.js.map +1 -0
- package/dist/{experimentStatus-Cqp6qNrR.js → experimentStatus-DCn2QKG7.js} +2 -2
- package/dist/{experimentStatus-Cqp6qNrR.js.map → experimentStatus-DCn2QKG7.js.map} +1 -1
- package/dist/index.js +20 -20
- package/dist/{install-BtLWTk6H.js → install-BEHp7Mo-.js} +15 -15
- package/dist/{install-BtLWTk6H.js.map → install-BEHp7Mo-.js.map} +1 -1
- package/dist/install.js +1 -1
- package/dist/{lab-MHzp-_J8.js → lab-D61eaX6f.js} +68 -61
- package/dist/{lab-MHzp-_J8.js.map → lab-D61eaX6f.js.map} +1 -1
- package/dist/lab.js +4 -4
- package/dist/{records-B6gpM_3e.js → records-D4aQXyZu.js} +193 -133
- package/dist/records-D4aQXyZu.js.map +1 -0
- package/dist/{runtime-7vfl_bI3.js → runtime-q-R0CNLh.js} +400 -33
- package/dist/runtime-q-R0CNLh.js.map +1 -0
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +7 -7
- package/dist/{shell-Cs9OJMnm.js → shell-6CGFvUUw.js} +10 -5
- package/dist/{shell-Cs9OJMnm.js.map → shell-6CGFvUUw.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 +1191 -479
- package/dist/types/components.d.ts +2 -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/{useDropdownState-8yWMu-7_.js → useDropdownState-uFIz6Yv8.js} +16 -4
- package/dist/useDropdownState-uFIz6Yv8.js.map +1 -0
- package/dist/{useExperimentSamples-DHnxKFAF.js → useExperimentSamples-QEFoq4tf.js} +48 -6
- package/dist/useExperimentSamples-QEFoq4tf.js.map +1 -0
- package/dist/{useListReorder-BitdUhxn.js → useListReorder-DsuzwWGS.js} +122 -172
- package/dist/useListReorder-DsuzwWGS.js.map +1 -0
- package/dist/{useRackEditor-DiwCt77M.js → useRackEditor-KpZ4iJ6o.js} +22 -32
- package/dist/useRackEditor-KpZ4iJ6o.js.map +1 -0
- package/dist/useRealtime-B58Lo-Tm.js +508 -0
- package/dist/useRealtime-B58Lo-Tm.js.map +1 -0
- package/dist/{useRequest-D506eHi8.js → useRequest-BuDmW8Jc.js} +8 -37
- package/dist/useRequest-BuDmW8Jc.js.map +1 -0
- package/package.json +3 -2
- package/src/__stories__/experiment-helpers.ts +3 -3
- package/src/__tests__/components/ActionMenu.test.ts +2 -2
- package/src/__tests__/components/AppTopBar.test.ts +206 -1
- package/src/__tests__/components/AppTopBarOnboarding.test.ts +160 -0
- package/src/__tests__/components/AppTopBarPageSelector.test.ts +125 -9
- package/src/__tests__/components/BaseButton.test.ts +19 -0
- package/src/__tests__/components/BaseModal.test.ts +13 -0
- package/src/__tests__/components/ExperimentSelectorModal.test.ts +34 -1
- package/src/__tests__/components/FloatingPanelsInModal.test.ts +94 -0
- package/src/__tests__/components/GeneratedPluginView.test.ts +1 -1
- package/src/__tests__/components/JobsStatusTray.test.ts +373 -71
- package/src/__tests__/components/PluginWorkspaceView.test.ts +63 -1
- package/src/__tests__/components/SampleSelector.test.ts +20 -0
- package/src/__tests__/components/SettingsModal.test.ts +9 -2
- package/src/__tests__/composables/useFileImport.test.ts +36 -0
- package/src/__tests__/composables/useFloatingPanel.test.ts +68 -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__/fixtures/table-import/cells.xlsx +0 -0
- package/src/__tests__/fixtures/table-import/samples.xlsx +0 -0
- package/src/__tests__/stores/settings.test.ts +21 -21
- package/src/__tests__/styles/cssTokens.test.ts +4 -1
- package/src/__tests__/styles/menusShellRefresh.test.ts +8 -2
- package/src/__tests__/styles/paletteTokens.test.ts +44 -0
- package/src/__tests__/styles/v2AuditRules.test.ts +104 -0
- package/src/components/ActionMenu.vue +6 -37
- package/src/components/AppTopBar.onboarding.story.vue +69 -0
- package/src/components/AppTopBar.story.vue +59 -4
- package/src/components/AppTopBar.vue +132 -10
- package/src/components/BaseButton.vue +16 -2
- package/src/components/BaseModal.vue +2 -1
- package/src/components/ExperimentSelectorModal.story.vue +38 -0
- package/src/components/ExperimentSelectorModal.vue +20 -1
- package/src/components/GeneratedPluginView.vue +4 -9
- package/src/components/GeneratedResultRenderer.vue +3 -4
- 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/PluginWorkspaceView.props.ts +3 -0
- package/src/components/PluginWorkspaceView.vue +17 -0
- package/src/components/SampleSelector.story.vue +21 -6
- package/src/components/SampleSelector.vue +10 -3
- package/src/components/SectionCard.story.vue +33 -0
- package/src/components/SettingsModal.vue +7 -12
- package/src/components/Tooltip.vue +9 -32
- package/src/components/internal/AppTopBarOnboardingInternal.vue +172 -0
- package/src/components/internal/AppTopBarPageSelectorInternal.vue +140 -72
- package/src/components/internal/DatePickerDateInternal.vue +7 -45
- package/src/components/internal/DatePickerDateTimeInternal.vue +7 -1
- package/src/components/internal/DatePickerTimeInternal.vue +8 -1
- package/src/components/internal/NumberUnitInputInternal.vue +4 -18
- package/src/components/internal/OnboardingLayerInternal.vue +173 -0
- package/src/components/internal/SelectMultipleInternal.vue +6 -1
- package/src/components/internal/SelectSearchableInternal.vue +6 -1
- package/src/components/internal/WellEditPopupInternal.vue +11 -22
- package/src/components/internal/WorkspaceShellInternal.vue +12 -0
- package/src/composables/pluginWorkspaceModel.ts +2 -2
- package/src/composables/useFileImport.ts +51 -1
- package/src/composables/useFloatingPanel.ts +107 -0
- package/src/composables/useJobCenter.ts +188 -6
- package/src/composables/useOnboarding.ts +465 -0
- package/src/composables/usePluginWorkspace.ts +1 -1
- package/src/data.ts +1 -0
- 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/action-menu.css +1 -1
- package/src/styles/components/app-layout.css +6 -3
- package/src/styles/components/app-page-selector.css +120 -45
- package/src/styles/components/app-top-bar.css +165 -3
- package/src/styles/components/button.css +8 -0
- package/src/styles/components/dataframe.css +1 -1
- package/src/styles/components/date-picker.css +4 -2
- package/src/styles/components/datetime-picker.css +4 -4
- package/src/styles/components/experiment-selector-modal.css +13 -0
- package/src/styles/components/jobs-status-tray.css +134 -296
- package/src/styles/components/layout-resize-handle.css +59 -16
- package/src/styles/components/modal.css +3 -3
- package/src/styles/components/number-input.css +1 -1
- package/src/styles/components/onboarding.css +368 -0
- package/src/styles/components/progress-bar.css +3 -3
- package/src/styles/components/sample-selector.css +43 -0
- package/src/styles/components/searchable-select.css +3 -7
- package/src/styles/components/section-card.css +30 -6
- package/src/styles/components/settings-modal.css +7 -2
- package/src/styles/components/time-picker.css +3 -6
- package/src/styles/components/toast.css +1 -1
- package/src/styles/components/tooltip.css +2 -2
- package/src/styles/components/unit-input.css +1 -1
- package/src/styles/components/well-edit-popup.css +2 -2
- package/src/styles/index.css +1 -0
- package/src/styles/variables.css +153 -15
- package/src/types/components.ts +2 -0
- package/src/types/index.ts +1 -0
- package/src/types/jobs.ts +10 -0
- package/src/types/platform.ts +1 -1
- package/dist/AppSidebar-Dq9W1jE2.js.map +0 -1
- package/dist/ExperimentSelectorModal-65peG4Up.js.map +0 -1
- package/dist/SettingsModal-483nyI6b.js.map +0 -1
- package/dist/experiment-43OJtH7R.js.map +0 -1
- package/dist/records-B6gpM_3e.js.map +0 -1
- package/dist/runtime-7vfl_bI3.js.map +0 -1
- package/dist/useDropdownState-8yWMu-7_.js.map +0 -1
- package/dist/useExperimentSamples-DHnxKFAF.js.map +0 -1
- package/dist/useListReorder-BitdUhxn.js.map +0 -1
- package/dist/useRackEditor-DiwCt77M.js.map +0 -1
- package/dist/useRealtime-C3iy9QkU.js +0 -699
- package/dist/useRealtime-C3iy9QkU.js.map +0 -1
- package/dist/useRequest-D506eHi8.js.map +0 -1
|
@@ -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',
|
|
Binary file
|
|
Binary file
|
|
@@ -2,7 +2,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
2
2
|
import { createPinia, setActivePinia } from 'pinia'
|
|
3
3
|
import { nextTick } from 'vue'
|
|
4
4
|
import {
|
|
5
|
-
paletteCssVariables,
|
|
6
5
|
tableDensityToSize,
|
|
7
6
|
tryUseSettingsStore,
|
|
8
7
|
useSettingsStore,
|
|
@@ -54,6 +53,7 @@ describe('useSettingsStore', () => {
|
|
|
54
53
|
document.documentElement.classList.remove('dark')
|
|
55
54
|
document.documentElement.removeAttribute('style')
|
|
56
55
|
delete document.documentElement.dataset.density
|
|
56
|
+
delete document.documentElement.dataset.palette
|
|
57
57
|
mockMatchMedia(false)
|
|
58
58
|
})
|
|
59
59
|
|
|
@@ -72,51 +72,51 @@ describe('useSettingsStore', () => {
|
|
|
72
72
|
expect(settings.serverHost).toBe('127.0.0.1')
|
|
73
73
|
expect(settings.serverPort).toBe(8001)
|
|
74
74
|
expect(settings.theme).toBe('dark')
|
|
75
|
-
expect(settings.colorPalette).toBe('
|
|
75
|
+
expect(settings.colorPalette).toBe('purple')
|
|
76
76
|
expect(settings.tableDensity).toBe('compact')
|
|
77
77
|
expect(localStorage.getItem('mld-settings')).toBeNull()
|
|
78
78
|
expect(JSON.parse(localStorage.getItem('mint-settings') || '{}')).toEqual(expect.objectContaining({
|
|
79
79
|
serverHost: '127.0.0.1',
|
|
80
80
|
serverPort: 8001,
|
|
81
81
|
theme: 'dark',
|
|
82
|
-
colorPalette: '
|
|
82
|
+
colorPalette: 'purple',
|
|
83
83
|
tableDensity: 'compact',
|
|
84
84
|
}))
|
|
85
85
|
})
|
|
86
86
|
|
|
87
87
|
describe('palette and density DOM sync', () => {
|
|
88
|
-
it('
|
|
88
|
+
it('mirrors the stored palette onto html[data-palette] without inline overrides', () => {
|
|
89
|
+
localStorage.setItem('mint-settings', JSON.stringify({ colorPalette: 'ocean' }))
|
|
90
|
+
|
|
89
91
|
const settings = useSettingsStore()
|
|
90
92
|
settings.initialize()
|
|
91
93
|
|
|
92
|
-
expect(
|
|
94
|
+
expect(document.documentElement.dataset.palette).toBe('ocean')
|
|
93
95
|
expect(document.documentElement.style.getPropertyValue('--color-primary')).toBe('')
|
|
94
96
|
})
|
|
95
97
|
|
|
96
|
-
it('
|
|
97
|
-
localStorage.setItem('mint-settings', JSON.stringify({ colorPalette: 'viridis' }))
|
|
98
|
-
|
|
98
|
+
it('updates html[data-palette] when the palette changes', async () => {
|
|
99
99
|
const settings = useSettingsStore()
|
|
100
100
|
settings.initialize()
|
|
101
|
+
expect(document.documentElement.dataset.palette).toBe('default')
|
|
101
102
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
expect(
|
|
105
|
-
expect(style.getPropertyValue('--color-cta')).toBe('hsl(80 95% 53%)')
|
|
103
|
+
settings.colorPalette = 'forest'
|
|
104
|
+
await nextTick()
|
|
105
|
+
expect(document.documentElement.dataset.palette).toBe('forest')
|
|
106
106
|
})
|
|
107
107
|
|
|
108
|
-
it(
|
|
108
|
+
it.each([
|
|
109
|
+
['viridis', 'purple'],
|
|
110
|
+
['pastel', 'default'],
|
|
111
|
+
['not-a-palette', 'default'],
|
|
112
|
+
])('maps the stored palette %s onto %s', (stored, expected) => {
|
|
113
|
+
localStorage.setItem('mint-settings', JSON.stringify({ colorPalette: stored }))
|
|
114
|
+
|
|
109
115
|
const settings = useSettingsStore()
|
|
110
116
|
settings.initialize()
|
|
111
117
|
|
|
112
|
-
settings.colorPalette
|
|
113
|
-
|
|
114
|
-
expect(document.documentElement.style.getPropertyValue('--color-primary')).toBe('hsl(200 84% 67%)')
|
|
115
|
-
|
|
116
|
-
settings.colorPalette = 'default'
|
|
117
|
-
await nextTick()
|
|
118
|
-
expect(document.documentElement.style.getPropertyValue('--color-primary')).toBe('')
|
|
119
|
-
expect(document.documentElement.style.getPropertyValue('--color-primary-soft')).toBe('')
|
|
118
|
+
expect(settings.colorPalette).toBe(expected)
|
|
119
|
+
expect(document.documentElement.dataset.palette).toBe(expected)
|
|
120
120
|
})
|
|
121
121
|
|
|
122
122
|
it('mirrors the table density onto html[data-density]', async () => {
|
|
@@ -14,7 +14,10 @@ const variablesCss = readFileSync(join(stylesDir, 'variables.css'), 'utf-8')
|
|
|
14
14
|
* component-local knobs declared in the same stylesheet that consumes them.
|
|
15
15
|
* Anything else must be a canonical token from variables.css.
|
|
16
16
|
*/
|
|
17
|
-
const RUNTIME_PROPERTY_ALLOWLIST = new Set<string>([
|
|
17
|
+
const RUNTIME_PROPERTY_ALLOWLIST = new Set<string>([
|
|
18
|
+
// Consumer knob: the gap width an edge LayoutResizeHandle centres on (falls back to --mint-gutter).
|
|
19
|
+
'--mint-layout-resize-gap',
|
|
20
|
+
])
|
|
18
21
|
|
|
19
22
|
const SOURCE_EXTENSIONS = ['.vue', '.css', '.ts']
|
|
20
23
|
const IGNORED_DIRS = new Set(['__tests__', 'generated-ui', 'node_modules'])
|
|
@@ -30,8 +30,14 @@ describe('menus and app shell styles', () => {
|
|
|
30
30
|
expect(css('app-pill-nav.css')).toMatch(/\.mint-pill-nav__item\s*\{[^}]*height:\s*1\.625rem;/)
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
// Design v2 option C: the page switcher sits in the crumb at the 32px top-bar control height.
|
|
34
|
+
it('should size the page selector trigger to the 32px top-bar control height', () => {
|
|
35
|
+
expect(css('app-page-selector.css')).toMatch(/\.mint-page-selector__trigger\s*\{[^}]*height:\s*2rem;/)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// A content-box menu measured 310px, and the platform's <a> rows (100% + padding) overflowed it.
|
|
39
|
+
it.each(['menu', 'item'])('should size the page selector %s as border-box', (part) => {
|
|
40
|
+
expect(css('app-page-selector.css')).toMatch(new RegExp(`\\.mint-page-selector__${part}\\s*\\{[^}]*box-sizing:\\s*border-box;`))
|
|
35
41
|
})
|
|
36
42
|
|
|
37
43
|
// Shrinking the tile beside the identity also shrank structured icons inside their solid chip, drawing a ring.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join, resolve } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { describe, expect, it } from 'vitest'
|
|
5
|
+
import { colorPalettes } from '../../stores/settings'
|
|
6
|
+
|
|
7
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
const variablesCss = readFileSync(join(resolve(here, '../..'), 'styles/variables.css'), 'utf-8')
|
|
9
|
+
|
|
10
|
+
const ACCENT_TOKENS = [
|
|
11
|
+
'--color-primary',
|
|
12
|
+
'--color-primary-hover',
|
|
13
|
+
'--color-primary-pressed',
|
|
14
|
+
'--color-primary-soft',
|
|
15
|
+
'--color-primary-border',
|
|
16
|
+
]
|
|
17
|
+
const STATUS_TOKENS = ['--mint-success', '--mint-error', '--mint-warning', '--mint-warning-text']
|
|
18
|
+
|
|
19
|
+
/** Declarations of every rule whose selector list has a selector that ends in `[data-palette="<key>"]`. */
|
|
20
|
+
function paletteDeclarations(key: string, dark: boolean): string {
|
|
21
|
+
const attr = `[data-palette="${key}"]`
|
|
22
|
+
return [...variablesCss.matchAll(/([^{}]+)\{([^}]*)\}/g)]
|
|
23
|
+
.filter(([, selector]) => selector.split(',').some((s) => {
|
|
24
|
+
const one = s.trim()
|
|
25
|
+
return one.endsWith(attr) && one.startsWith('html.dark') === dark
|
|
26
|
+
}))
|
|
27
|
+
.map(([, , body]) => body)
|
|
28
|
+
.join('\n')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('accent palettes', () => {
|
|
32
|
+
it.each(Object.keys(colorPalettes).flatMap((key) => [[key, 'light'], [key, 'dark']]))(
|
|
33
|
+
'should define every accent token for the %s palette in %s mode',
|
|
34
|
+
(key, theme) => {
|
|
35
|
+
const body = paletteDeclarations(key, theme === 'dark')
|
|
36
|
+
for (const token of ACCENT_TOKENS) expect(body, `${key} ${theme} ${token}`).toMatch(new RegExp(`${token}\\s*:`))
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
it('should give the colour-blind palette Okabe-Ito status colours', () => {
|
|
41
|
+
const body = paletteDeclarations('colorblind', false)
|
|
42
|
+
for (const token of STATUS_TOKENS) expect(body, token).toMatch(new RegExp(`${token}\\s*:`))
|
|
43
|
+
})
|
|
44
|
+
})
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join, resolve } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { describe, expect, it } from 'vitest'
|
|
5
|
+
|
|
6
|
+
// v2 design audit (2026-10-28) fixes in the SDK stylesheets.
|
|
7
|
+
|
|
8
|
+
const srcRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..')
|
|
9
|
+
const stylesDir = join(srcRoot, 'styles')
|
|
10
|
+
|
|
11
|
+
function css(path: string): string {
|
|
12
|
+
return readFileSync(join(stylesDir, path), 'utf-8').replace(/\/\*[\s\S]*?\*\//g, '')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function rule(text: string, selector: string): string {
|
|
16
|
+
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
17
|
+
return new RegExp(`(?:^|\\})\\s*${escaped}\\s*\\{([^}]*)\\}`).exec(text)?.[1] ?? ''
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const rem = (value: string): number => Number(value.replace('rem', '')) * 16
|
|
21
|
+
|
|
22
|
+
describe('v2 audit style rules', () => {
|
|
23
|
+
it('should stop the page bounce in a tab vertically and in a PWA on both axes', () => {
|
|
24
|
+
const variables = css('variables.css')
|
|
25
|
+
expect(rule(variables, 'html,\nbody')).toMatch(/overscroll-behavior-y:\s*none/)
|
|
26
|
+
expect(variables).toMatch(
|
|
27
|
+
/@media \(display-mode: standalone\)\s*\{\s*html,\s*body\s*\{\s*overscroll-behavior:\s*none;\s*\}/,
|
|
28
|
+
)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should keep the outermost cards at least 16px from the window edge', () => {
|
|
32
|
+
const inset = /--mint-page-inset:\s*([\d.]+rem)/.exec(css('variables.css'))?.[1] ?? '0rem'
|
|
33
|
+
expect(rem(inset)).toBeGreaterThanOrEqual(16)
|
|
34
|
+
expect(rule(css('components/app-layout.css'), '.mint-layout--floating')).toMatch(/padding:\s*var\(--mint-page-inset\)/)
|
|
35
|
+
const topBarCard = rule(css('components/app-top-bar.css'), '.mint-topbar--card')
|
|
36
|
+
expect(topBarCard).toMatch(/width:\s*calc\(100% - 2 \* var\(--mint-page-inset\)\)/)
|
|
37
|
+
expect(topBarCard).toMatch(/margin:\s*var\(--mint-page-inset\) var\(--mint-page-inset\) 0/)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should put the SDK utility classes in the utilities layer so host variants beat them', () => {
|
|
41
|
+
const vars = css('variables.css')
|
|
42
|
+
const order = vars.indexOf('@layer theme, base, components, utilities;')
|
|
43
|
+
const layer = vars.indexOf('@layer utilities {')
|
|
44
|
+
expect(order).toBeGreaterThan(-1)
|
|
45
|
+
expect(order).toBeLessThan(vars.indexOf('@layer base {'))
|
|
46
|
+
const utilities = vars.slice(layer, vars.indexOf('@media (prefers-reduced-motion'))
|
|
47
|
+
expect(layer).toBeGreaterThan(-1)
|
|
48
|
+
for (const selector of ['.flex-col {', '.text-text-muted {', '.truncate {', '.max-w-sm {']) {
|
|
49
|
+
expect(utilities).toContain(selector)
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should drop the last DataFrame row rule over the !important cell border', () => {
|
|
54
|
+
const body = rule(css('components/dataframe.css'), '.mint-dataframe__row:last-child .mint-dataframe__td')
|
|
55
|
+
expect(body).toMatch(/border-bottom:\s*none\s*!important/)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should round the modal close button with --radius-md', () => {
|
|
59
|
+
expect(rule(css('components/modal.css'), '.mint-modal__close')).toMatch(/border-radius:\s*var\(--radius-md\)/)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should draw no capsule on the progress rail or the sheet grab hint', () => {
|
|
63
|
+
expect(css('components/progress-bar.css')).not.toMatch(/9999px/)
|
|
64
|
+
expect(rule(css('components/modal.css'), '.mint-modal__grab-hint')).not.toMatch(/9999px/)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should keep a 28px header control inside the section card header strip', () => {
|
|
68
|
+
const text = css('components/section-card.css')
|
|
69
|
+
const cases: Array<[string, number]> = [
|
|
70
|
+
['.mint-section-card__header', 28],
|
|
71
|
+
['[data-density="comfortable"] .mint-section-card__header', 32],
|
|
72
|
+
]
|
|
73
|
+
for (const [selector, control] of cases) {
|
|
74
|
+
const body = rule(text, selector)
|
|
75
|
+
const minHeight = rem(/min-height:\s*([\d.]+rem)/.exec(body)?.[1] ?? '0rem')
|
|
76
|
+
const padTop = rem(/padding:\s*([\d.]+rem)/.exec(body)?.[1] ?? '0rem')
|
|
77
|
+
expect(control + 2 * padTop + 1, selector).toBeLessThanOrEqual(minHeight)
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('should end the settings pane without a closing row hairline and draw no box around the density preview', () => {
|
|
82
|
+
const text = css('components/settings-modal.css')
|
|
83
|
+
const last = rule(
|
|
84
|
+
text,
|
|
85
|
+
'.mint-settings-modal__group:last-of-type > .mint-settings-modal__group-grid > .mint-settings-modal__row:last-child',
|
|
86
|
+
)
|
|
87
|
+
expect(last).toMatch(/border-bottom:\s*none/)
|
|
88
|
+
expect(rule(text, '.mint-settings-modal__density-preview')).not.toMatch(/(^|;)\s*border(-radius)?:/)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should set the md number input text no smaller than the sm one', () => {
|
|
92
|
+
const text = css('components/number-input.css')
|
|
93
|
+
const size = (variant: string) =>
|
|
94
|
+
rem(/font-size:\s*([\d.]+rem)/.exec(rule(text, `.mint-number-input__input--${variant}`))?.[1] ?? '0rem')
|
|
95
|
+
expect(size('md')).toBeGreaterThan(size('sm'))
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should give the generated JSON block a token radius and no frame', () => {
|
|
99
|
+
const sfc = readFileSync(join(srcRoot, 'components/GeneratedResultRenderer.vue'), 'utf-8')
|
|
100
|
+
const body = /\.mint-generated-result__json\s*\{([^}]*overflow:[^}]*)\}/.exec(sfc)?.[1] ?? ''
|
|
101
|
+
expect(body).toMatch(/border-radius:\s*var\(--radius-md\)/)
|
|
102
|
+
expect(body).not.toMatch(/(^|;)\s*border:/)
|
|
103
|
+
})
|
|
104
|
+
})
|