@svgrid/grid 2.2.27 → 2.2.29
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 +199 -199
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +179 -179
- package/dist/GridMenus.svelte +782 -734
- package/dist/SvAutoComplete.svelte +165 -165
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +101 -101
- package/dist/SvColorInput.svelte +181 -181
- package/dist/SvComboBox.svelte +275 -275
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +160 -160
- package/dist/SvDrawer.svelte +254 -250
- package/dist/SvDropDownList.svelte +374 -374
- package/dist/SvDurationInput.svelte +126 -126
- package/dist/SvField.svelte +293 -293
- package/dist/SvForm.svelte +437 -437
- package/dist/SvGrid.controller.svelte.d.ts +14 -1
- package/dist/SvGrid.controller.svelte.js +112 -28
- package/dist/SvGrid.css +2704 -2723
- package/dist/SvGrid.svelte +3361 -3333
- package/dist/SvGrid.types.d.ts +20 -5
- package/dist/SvGridChart.svelte +1724 -1724
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +696 -696
- package/dist/SvGridSelect.svelte +259 -259
- package/dist/SvGroupCell.svelte +109 -109
- package/dist/SvListBox.svelte +326 -287
- package/dist/SvListBox.svelte.d.ts +10 -1
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMultiSelect.svelte +281 -281
- package/dist/SvNumberInput.svelte +168 -168
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +133 -131
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +200 -200
- package/dist/SvSwitchButton.svelte +107 -107
- package/dist/SvTagsInput.svelte +112 -112
- package/dist/SvTextInput.svelte +147 -147
- package/dist/SvTimePicker.svelte +245 -245
- package/dist/SvToaster.svelte +159 -159
- package/dist/SvToggleButton.svelte +85 -85
- package/dist/SvTooltip.svelte +161 -161
- package/dist/SvTour.svelte +204 -204
- package/dist/SvTree.svelte +437 -437
- package/dist/SvTreeSelect.svelte +233 -233
- package/dist/cdn/GridMenus-Da_p7SKS.js +486 -0
- package/dist/cdn/GridMenus-Ds3OpzZT.js +490 -0
- package/dist/cdn/{SvDateTimePicker-B1sDc9Pz.js → SvDateTimePicker-B8GopjhC.js} +2 -2
- package/dist/cdn/{SvDateTimePicker-Bh0FTs1E.js → SvDateTimePicker-DB0aIEk8.js} +2 -2
- package/dist/cdn/{SvGridChart-Bgk4hDs8.js → SvGridChart-BcLCYvw9.js} +1 -1
- package/dist/cdn/{SvGridChart-BKQsO5fZ.js → SvGridChart-BhnFdLd0.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-DkCc2Z7b.js → SvGridChartPanel-Cmx9DHAS.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-DSkHIQDd.js → SvGridChartPanel-DvjwxLvK.js} +1 -1
- package/dist/cdn/{SvGridChartView-BrbVaELm.js → SvGridChartView-B40DTJSX.js} +1 -1
- package/dist/cdn/{SvGridChartView-i9rWvSKg.js → SvGridChartView-PwqI4s02.js} +1 -1
- package/dist/cdn/{SvGridDropdown-D08sVmAy.js → SvGridDropdown-DjALc_4f.js} +1 -1
- package/dist/cdn/{SvGridDropdown-CGxxopQ4.js → SvGridDropdown-hpnHvOGt.js} +1 -1
- package/dist/cdn/{src-cgs1wcUS.js → src-BLJfdyL0.js} +4034 -3889
- package/dist/cdn/{src-Cw8d9U8j.js → src-DYDCiC0D.js} +7053 -6908
- package/dist/cdn/svgrid.js +9 -9
- package/dist/cdn/svgrid.svelte-external.js +9 -9
- package/dist/chart-export.js +8 -8
- package/dist/column-groups.js +8 -10
- package/dist/column-id.d.ts +9 -0
- package/dist/column-id.js +25 -0
- package/dist/core.js +2 -1
- package/dist/createListbox.svelte.d.ts +15 -2
- package/dist/createListbox.svelte.js +27 -6
- package/dist/editing.d.ts +1 -0
- package/dist/editing.js +45 -10
- package/dist/filtering/excel-filters.d.ts +30 -10
- package/dist/filtering/excel-filters.js +96 -15
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/menus.d.ts +2 -0
- package/dist/menus.js +25 -0
- package/dist/row-resize.js +40 -0
- package/dist/selection.js +1 -1
- package/package.json +10 -10
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +179 -179
- package/src/GridMenus.svelte +782 -734
- package/src/SvAutoComplete.svelte +165 -165
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +101 -101
- package/src/SvColorInput.svelte +181 -181
- package/src/SvComboBox.svelte +275 -275
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +160 -160
- package/src/SvDrawer.svelte +254 -250
- package/src/SvDrawer.test.ts +18 -1
- package/src/SvDropDownList.svelte +374 -374
- package/src/SvDurationInput.svelte +126 -126
- package/src/SvField.svelte +293 -293
- package/src/SvForm.svelte +437 -437
- package/src/SvForm.test.ts +411 -411
- package/src/SvGrid.controller.svelte.ts +3335 -3239
- package/src/SvGrid.css +2704 -2723
- package/src/SvGrid.svelte +3361 -3333
- package/src/SvGrid.types.ts +1628 -1613
- package/src/SvGridChart.svelte +1724 -1724
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +696 -696
- package/src/SvGridSelect.svelte +259 -259
- package/src/SvGroupCell.svelte +109 -109
- package/src/SvListBox.svelte +326 -287
- package/src/SvMaskedInput.svelte +122 -122
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -97
- package/src/SvMenuList.svelte +146 -146
- package/src/SvMultiSelect.svelte +281 -281
- package/src/SvNavPane.test.ts +15 -4
- package/src/SvNumberInput.svelte +168 -168
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +133 -131
- package/src/SvPopover.svelte +197 -197
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +200 -200
- package/src/SvSwitchButton.svelte +107 -107
- package/src/SvTagsInput.svelte +112 -112
- package/src/SvTextInput.svelte +147 -147
- package/src/SvTimePicker.svelte +245 -245
- package/src/SvToaster.svelte +159 -159
- package/src/SvToaster.test.ts +95 -95
- package/src/SvToggleButton.svelte +85 -85
- package/src/SvTooltip.svelte +161 -161
- package/src/SvTour.svelte +204 -204
- package/src/SvTree.svelte +437 -437
- package/src/SvTreeSelect.svelte +233 -233
- package/src/a11y/dismissable.test.ts +119 -119
- package/src/a11y/dismissable.ts +114 -114
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/ai.test.ts +502 -502
- package/src/ai.ts +1391 -1391
- package/src/build-api.coverage.test.ts +633 -633
- package/src/build-api.ts +895 -895
- package/src/builtin-editors.grid.test.ts +85 -85
- package/src/cell-formatting.ts +169 -169
- package/src/cell-render.ts +469 -469
- package/src/chart-export.ts +201 -201
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2296 -2296
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/column-groups.test.ts +48 -0
- package/src/column-groups.ts +8 -10
- package/src/column-id.ts +30 -0
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1112 -1111
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCombobox.svelte.ts +191 -191
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +168 -168
- package/src/createForm.svelte.ts +386 -386
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createListbox.svelte.ts +39 -7
- package/src/createMenu.svelte.ts +225 -225
- package/src/createPopoverSelect.svelte.ts +213 -213
- package/src/createSlider.svelte.ts +191 -191
- package/src/createTooltip.svelte.ts +144 -144
- package/src/createTree.svelte.ts +319 -319
- package/src/editing.test.ts +949 -859
- package/src/editing.ts +709 -675
- package/src/editor-contract.ts +157 -157
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +598 -598
- package/src/filter-operators.ts +157 -157
- package/src/filtering/excel-filters.test.ts +44 -1
- package/src/filtering/excel-filters.ts +91 -14
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/index.ts +715 -713
- package/src/js-scroller.svelte.ts +173 -173
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -49
- package/src/list-nav.ts +29 -29
- package/src/list-option.test.ts +56 -56
- package/src/list-option.ts +172 -172
- package/src/menus.test.ts +736 -700
- package/src/menus.ts +679 -652
- package/src/merge-objects.ts +48 -48
- package/src/overlays.test.ts +90 -90
- package/src/render-component.ts +28 -28
- package/src/row-resize.test.ts +100 -0
- package/src/row-resize.ts +36 -0
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +872 -872
- package/src/selection.test.ts +754 -754
- package/src/selection.ts +600 -600
- package/src/server-data-source.test.ts +289 -289
- package/src/server-data-source.ts +413 -413
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +489 -489
- package/src/spreadsheet.ts +312 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/svgrid-wrapper.types.ts +524 -524
- package/src/svgrid.api-extensions.test.ts +8 -3
- package/src/svgrid.behavior.test.ts +706 -706
- package/src/svgrid.cell-dom-ids.test.ts +96 -0
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.conditional-stat-scope.test.ts +111 -0
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +308 -0
- package/src/svgrid.filter-menu-scroll.test.ts +3 -1
- package/src/svgrid.group-header-ids.test.ts +121 -0
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.wrapper.test.ts +40 -40
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +215 -215
- package/src/toast-store.svelte.ts +246 -246
- package/src/toast-store.test.ts +147 -147
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- package/src/ui-range.test.ts +70 -70
- package/src/ui-selection.test.ts +155 -155
- package/src/ui-tier1.test.ts +142 -142
- package/src/virtual.test.ts +88 -88
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +296 -296
- package/themes/ember.css +71 -71
- package/dist/cdn/GridMenus-BzxEYNtW.js +0 -421
- package/dist/cdn/GridMenus-Dr-nGEXn.js +0 -417
package/src/toast-store.test.ts
CHANGED
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the headless toast store: queueing, variants, auto-dismiss timers,
|
|
3
|
-
* manual dismiss, sticky toasts, and the SR announcement side effect.
|
|
4
|
-
*/
|
|
5
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
6
|
-
import { toast, dismissToast, updateToast, clearToasts, pauseToast, resumeToast, toastStore } from './toast-store.svelte'
|
|
7
|
-
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
vi.useFakeTimers()
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
clearToasts()
|
|
14
|
-
vi.useRealTimers()
|
|
15
|
-
document.body.innerHTML = ''
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
describe('toast store', () => {
|
|
19
|
-
it('queues a toast with defaults', () => {
|
|
20
|
-
const id = toast('Hello')
|
|
21
|
-
expect(toastStore.toasts).toHaveLength(1)
|
|
22
|
-
const t = toastStore.toasts[0]!
|
|
23
|
-
expect(t.id).toBe(id)
|
|
24
|
-
expect(t.message).toBe('Hello')
|
|
25
|
-
expect(t.variant).toBe('info')
|
|
26
|
-
expect(t.dismissible).toBe(true)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('variant helpers set the variant', () => {
|
|
30
|
-
toast.success('ok')
|
|
31
|
-
toast.error('bad')
|
|
32
|
-
toast.warning('hmm')
|
|
33
|
-
expect(toastStore.toasts.map((t) => t.variant)).toEqual(['success', 'error', 'warning'])
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('auto-dismisses after its duration', () => {
|
|
37
|
-
toast('bye', { duration: 3000 })
|
|
38
|
-
expect(toastStore.toasts).toHaveLength(1)
|
|
39
|
-
vi.advanceTimersByTime(2999)
|
|
40
|
-
expect(toastStore.toasts).toHaveLength(1)
|
|
41
|
-
vi.advanceTimersByTime(1)
|
|
42
|
-
expect(toastStore.toasts).toHaveLength(0)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('duration 0 is sticky (no auto-dismiss)', () => {
|
|
46
|
-
toast('stay', { duration: 0 })
|
|
47
|
-
vi.advanceTimersByTime(60_000)
|
|
48
|
-
expect(toastStore.toasts).toHaveLength(1)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('dismissToast removes by id and cancels its timer', () => {
|
|
52
|
-
const id = toast('x', { duration: 5000 })
|
|
53
|
-
dismissToast(id)
|
|
54
|
-
expect(toastStore.toasts).toHaveLength(0)
|
|
55
|
-
// Advancing past the original duration must not throw or double-remove.
|
|
56
|
-
vi.advanceTimersByTime(5000)
|
|
57
|
-
expect(toastStore.toasts).toHaveLength(0)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('announces via a live region (assertive for errors)', () => {
|
|
61
|
-
toast.error('boom')
|
|
62
|
-
vi.runOnlyPendingTimers()
|
|
63
|
-
// The store routes errors to the assertive live region.
|
|
64
|
-
expect(document.querySelector('[aria-live="assertive"]')).not.toBeNull()
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('pause banks the remaining time and resume finishes it (hover to keep)', () => {
|
|
68
|
-
const id = toast('hover me', { duration: 3000 })
|
|
69
|
-
vi.advanceTimersByTime(1000) // 2000 left
|
|
70
|
-
pauseToast(id)
|
|
71
|
-
vi.advanceTimersByTime(10_000) // stays while paused
|
|
72
|
-
expect(toastStore.toasts).toHaveLength(1)
|
|
73
|
-
resumeToast(id)
|
|
74
|
-
vi.advanceTimersByTime(1999)
|
|
75
|
-
expect(toastStore.toasts).toHaveLength(1)
|
|
76
|
-
vi.advanceTimersByTime(1) // banked 2000ms elapsed
|
|
77
|
-
expect(toastStore.toasts).toHaveLength(0)
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('clearToasts empties the queue', () => {
|
|
81
|
-
toast('a'); toast('b'); toast('c')
|
|
82
|
-
expect(toastStore.toasts).toHaveLength(3)
|
|
83
|
-
clearToasts()
|
|
84
|
-
expect(toastStore.toasts).toHaveLength(0)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('update patches a toast in place (same id) and restarts its timer', () => {
|
|
88
|
-
const id = toast('working', { duration: 0 })
|
|
89
|
-
updateToast(id, { message: 'done', variant: 'success', duration: 3000 })
|
|
90
|
-
expect(toastStore.toasts).toHaveLength(1)
|
|
91
|
-
const t = toastStore.toasts[0]!
|
|
92
|
-
expect(t.id).toBe(id)
|
|
93
|
-
expect(t.message).toBe('done')
|
|
94
|
-
expect(t.variant).toBe('success')
|
|
95
|
-
// The new duration now applies.
|
|
96
|
-
vi.advanceTimersByTime(3000)
|
|
97
|
-
expect(toastStore.toasts).toHaveLength(0)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('update is a no-op for an unknown id', () => {
|
|
101
|
-
updateToast(999, { message: 'nope' })
|
|
102
|
-
expect(toastStore.toasts).toHaveLength(0)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it('promise shows a sticky loader, then updates in place to success', async () => {
|
|
106
|
-
vi.useRealTimers() // real microtask flush for the promise
|
|
107
|
-
let resolve!: (v: string) => void
|
|
108
|
-
const p = new Promise<string>((r) => (resolve = r))
|
|
109
|
-
const ret = toast.promise(p, { loading: 'Saving...', success: (v) => `Saved ${v}`, error: 'Failed' })
|
|
110
|
-
expect(ret).toBe(p) // returns the original promise
|
|
111
|
-
expect(toastStore.toasts).toHaveLength(1)
|
|
112
|
-
const id = toastStore.toasts[0]!.id
|
|
113
|
-
expect(toastStore.toasts[0]!.message).toBe('Saving...')
|
|
114
|
-
expect(toastStore.toasts[0]!.variant).toBe('info')
|
|
115
|
-
resolve('now')
|
|
116
|
-
await p
|
|
117
|
-
await Promise.resolve()
|
|
118
|
-
const t = toastStore.toasts.find((x) => x.id === id)!
|
|
119
|
-
expect(t.message).toBe('Saved now')
|
|
120
|
-
expect(t.variant).toBe('success')
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
it('promise updates in place to error on rejection', async () => {
|
|
124
|
-
vi.useRealTimers()
|
|
125
|
-
const p = Promise.reject(new Error('x'))
|
|
126
|
-
toast.promise(p, { loading: 'Load', success: 'ok', error: (e) => `Err: ${(e as Error).message}` })
|
|
127
|
-
await p.catch(() => {})
|
|
128
|
-
await Promise.resolve()
|
|
129
|
-
const t = toastStore.toasts[0]!
|
|
130
|
-
expect(t.variant).toBe('error')
|
|
131
|
-
expect(t.message).toBe('Err: x')
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('action + cancel are stored on the toast', () => {
|
|
135
|
-
const onClick = vi.fn()
|
|
136
|
-
toast('Undo?', { action: { label: 'Undo', onClick }, cancel: { label: 'Dismiss' } })
|
|
137
|
-
const t = toastStore.toasts[0]!
|
|
138
|
-
expect(t.action?.label).toBe('Undo')
|
|
139
|
-
expect(t.cancel?.label).toBe('Dismiss')
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('custom carries a render snippet', () => {
|
|
143
|
-
const render = (() => {}) as never
|
|
144
|
-
const id = toast.custom(render, { duration: 0 })
|
|
145
|
-
expect(toastStore.toasts.find((x) => x.id === id)!.render).toBe(render)
|
|
146
|
-
})
|
|
147
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the headless toast store: queueing, variants, auto-dismiss timers,
|
|
3
|
+
* manual dismiss, sticky toasts, and the SR announcement side effect.
|
|
4
|
+
*/
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
6
|
+
import { toast, dismissToast, updateToast, clearToasts, pauseToast, resumeToast, toastStore } from './toast-store.svelte'
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.useFakeTimers()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
clearToasts()
|
|
14
|
+
vi.useRealTimers()
|
|
15
|
+
document.body.innerHTML = ''
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('toast store', () => {
|
|
19
|
+
it('queues a toast with defaults', () => {
|
|
20
|
+
const id = toast('Hello')
|
|
21
|
+
expect(toastStore.toasts).toHaveLength(1)
|
|
22
|
+
const t = toastStore.toasts[0]!
|
|
23
|
+
expect(t.id).toBe(id)
|
|
24
|
+
expect(t.message).toBe('Hello')
|
|
25
|
+
expect(t.variant).toBe('info')
|
|
26
|
+
expect(t.dismissible).toBe(true)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('variant helpers set the variant', () => {
|
|
30
|
+
toast.success('ok')
|
|
31
|
+
toast.error('bad')
|
|
32
|
+
toast.warning('hmm')
|
|
33
|
+
expect(toastStore.toasts.map((t) => t.variant)).toEqual(['success', 'error', 'warning'])
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('auto-dismisses after its duration', () => {
|
|
37
|
+
toast('bye', { duration: 3000 })
|
|
38
|
+
expect(toastStore.toasts).toHaveLength(1)
|
|
39
|
+
vi.advanceTimersByTime(2999)
|
|
40
|
+
expect(toastStore.toasts).toHaveLength(1)
|
|
41
|
+
vi.advanceTimersByTime(1)
|
|
42
|
+
expect(toastStore.toasts).toHaveLength(0)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('duration 0 is sticky (no auto-dismiss)', () => {
|
|
46
|
+
toast('stay', { duration: 0 })
|
|
47
|
+
vi.advanceTimersByTime(60_000)
|
|
48
|
+
expect(toastStore.toasts).toHaveLength(1)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('dismissToast removes by id and cancels its timer', () => {
|
|
52
|
+
const id = toast('x', { duration: 5000 })
|
|
53
|
+
dismissToast(id)
|
|
54
|
+
expect(toastStore.toasts).toHaveLength(0)
|
|
55
|
+
// Advancing past the original duration must not throw or double-remove.
|
|
56
|
+
vi.advanceTimersByTime(5000)
|
|
57
|
+
expect(toastStore.toasts).toHaveLength(0)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('announces via a live region (assertive for errors)', () => {
|
|
61
|
+
toast.error('boom')
|
|
62
|
+
vi.runOnlyPendingTimers()
|
|
63
|
+
// The store routes errors to the assertive live region.
|
|
64
|
+
expect(document.querySelector('[aria-live="assertive"]')).not.toBeNull()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('pause banks the remaining time and resume finishes it (hover to keep)', () => {
|
|
68
|
+
const id = toast('hover me', { duration: 3000 })
|
|
69
|
+
vi.advanceTimersByTime(1000) // 2000 left
|
|
70
|
+
pauseToast(id)
|
|
71
|
+
vi.advanceTimersByTime(10_000) // stays while paused
|
|
72
|
+
expect(toastStore.toasts).toHaveLength(1)
|
|
73
|
+
resumeToast(id)
|
|
74
|
+
vi.advanceTimersByTime(1999)
|
|
75
|
+
expect(toastStore.toasts).toHaveLength(1)
|
|
76
|
+
vi.advanceTimersByTime(1) // banked 2000ms elapsed
|
|
77
|
+
expect(toastStore.toasts).toHaveLength(0)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('clearToasts empties the queue', () => {
|
|
81
|
+
toast('a'); toast('b'); toast('c')
|
|
82
|
+
expect(toastStore.toasts).toHaveLength(3)
|
|
83
|
+
clearToasts()
|
|
84
|
+
expect(toastStore.toasts).toHaveLength(0)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('update patches a toast in place (same id) and restarts its timer', () => {
|
|
88
|
+
const id = toast('working', { duration: 0 })
|
|
89
|
+
updateToast(id, { message: 'done', variant: 'success', duration: 3000 })
|
|
90
|
+
expect(toastStore.toasts).toHaveLength(1)
|
|
91
|
+
const t = toastStore.toasts[0]!
|
|
92
|
+
expect(t.id).toBe(id)
|
|
93
|
+
expect(t.message).toBe('done')
|
|
94
|
+
expect(t.variant).toBe('success')
|
|
95
|
+
// The new duration now applies.
|
|
96
|
+
vi.advanceTimersByTime(3000)
|
|
97
|
+
expect(toastStore.toasts).toHaveLength(0)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('update is a no-op for an unknown id', () => {
|
|
101
|
+
updateToast(999, { message: 'nope' })
|
|
102
|
+
expect(toastStore.toasts).toHaveLength(0)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('promise shows a sticky loader, then updates in place to success', async () => {
|
|
106
|
+
vi.useRealTimers() // real microtask flush for the promise
|
|
107
|
+
let resolve!: (v: string) => void
|
|
108
|
+
const p = new Promise<string>((r) => (resolve = r))
|
|
109
|
+
const ret = toast.promise(p, { loading: 'Saving...', success: (v) => `Saved ${v}`, error: 'Failed' })
|
|
110
|
+
expect(ret).toBe(p) // returns the original promise
|
|
111
|
+
expect(toastStore.toasts).toHaveLength(1)
|
|
112
|
+
const id = toastStore.toasts[0]!.id
|
|
113
|
+
expect(toastStore.toasts[0]!.message).toBe('Saving...')
|
|
114
|
+
expect(toastStore.toasts[0]!.variant).toBe('info')
|
|
115
|
+
resolve('now')
|
|
116
|
+
await p
|
|
117
|
+
await Promise.resolve()
|
|
118
|
+
const t = toastStore.toasts.find((x) => x.id === id)!
|
|
119
|
+
expect(t.message).toBe('Saved now')
|
|
120
|
+
expect(t.variant).toBe('success')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('promise updates in place to error on rejection', async () => {
|
|
124
|
+
vi.useRealTimers()
|
|
125
|
+
const p = Promise.reject(new Error('x'))
|
|
126
|
+
toast.promise(p, { loading: 'Load', success: 'ok', error: (e) => `Err: ${(e as Error).message}` })
|
|
127
|
+
await p.catch(() => {})
|
|
128
|
+
await Promise.resolve()
|
|
129
|
+
const t = toastStore.toasts[0]!
|
|
130
|
+
expect(t.variant).toBe('error')
|
|
131
|
+
expect(t.message).toBe('Err: x')
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('action + cancel are stored on the toast', () => {
|
|
135
|
+
const onClick = vi.fn()
|
|
136
|
+
toast('Undo?', { action: { label: 'Undo', onClick }, cancel: { label: 'Dismiss' } })
|
|
137
|
+
const t = toastStore.toasts[0]!
|
|
138
|
+
expect(t.action?.label).toBe('Undo')
|
|
139
|
+
expect(t.cancel?.label).toBe('Dismiss')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('custom carries a render snippet', () => {
|
|
143
|
+
const render = (() => {}) as never
|
|
144
|
+
const id = toast.custom(render, { duration: 0 })
|
|
145
|
+
expect(toastStore.toasts.find((x) => x.id === id)!.render).toBe(render)
|
|
146
|
+
})
|
|
147
|
+
})
|
package/src/ui-buttons.test.ts
CHANGED
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Group B (buttons & toggles) component tests. Each asserts the emitted value /
|
|
3
|
-
* ARIA state a real interaction produces.
|
|
4
|
-
*/
|
|
5
|
-
import { describe, expect, it } from 'vitest'
|
|
6
|
-
import { mount, unmount, flushSync } from 'svelte'
|
|
7
|
-
import SvToggleButton from './SvToggleButton.svelte'
|
|
8
|
-
import SvSwitchButton from './SvSwitchButton.svelte'
|
|
9
|
-
import SvCheckBox from './SvCheckBox.svelte'
|
|
10
|
-
import SvRadioGroup from './SvRadioGroup.svelte'
|
|
11
|
-
import SvRating from './SvRating.svelte'
|
|
12
|
-
import SvSegmented from './SvSegmented.svelte'
|
|
13
|
-
|
|
14
|
-
function mnt(Comp: any, props: Record<string, unknown>) {
|
|
15
|
-
const target = document.createElement('div')
|
|
16
|
-
document.body.appendChild(target)
|
|
17
|
-
const app = mount(Comp, { target, props: props as any })
|
|
18
|
-
flushSync()
|
|
19
|
-
return { target, destroy: () => { unmount(app); target.remove() } }
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe('SvToggleButton', () => {
|
|
23
|
-
it('reflects pressed and emits the flipped value', () => {
|
|
24
|
-
let got: boolean | undefined
|
|
25
|
-
const { target, destroy } = mnt(SvToggleButton, { pressed: false, onChange: (v: boolean) => (got = v) })
|
|
26
|
-
try {
|
|
27
|
-
const btn = target.querySelector<HTMLButtonElement>('.sv-toggle')!
|
|
28
|
-
expect(btn.getAttribute('aria-pressed')).toBe('false')
|
|
29
|
-
btn.click(); flushSync()
|
|
30
|
-
expect(got).toBe(true)
|
|
31
|
-
} finally { destroy() }
|
|
32
|
-
})
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
describe('SvSwitchButton', () => {
|
|
36
|
-
it('has role switch and toggles', () => {
|
|
37
|
-
let got: boolean | undefined
|
|
38
|
-
const { target, destroy } = mnt(SvSwitchButton, { checked: false, onChange: (v: boolean) => (got = v) })
|
|
39
|
-
try {
|
|
40
|
-
const sw = target.querySelector<HTMLButtonElement>('[role="switch"]')!
|
|
41
|
-
expect(sw.getAttribute('aria-checked')).toBe('false')
|
|
42
|
-
sw.click(); flushSync()
|
|
43
|
-
expect(got).toBe(true)
|
|
44
|
-
} finally { destroy() }
|
|
45
|
-
})
|
|
46
|
-
it('ArrowRight turns on, ArrowLeft off', () => {
|
|
47
|
-
const states: boolean[] = []
|
|
48
|
-
const { target, destroy } = mnt(SvSwitchButton, { checked: false, onChange: (v: boolean) => states.push(v) })
|
|
49
|
-
try {
|
|
50
|
-
const sw = target.querySelector<HTMLButtonElement>('[role="switch"]')!
|
|
51
|
-
sw.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }))
|
|
52
|
-
flushSync()
|
|
53
|
-
expect(states.at(-1)).toBe(true)
|
|
54
|
-
} finally { destroy() }
|
|
55
|
-
})
|
|
56
|
-
it('readonly blocks the change but stays focusable + aria-readonly', () => {
|
|
57
|
-
let got: boolean | undefined
|
|
58
|
-
const { target, destroy } = mnt(SvSwitchButton, { checked: false, readonly: true, onChange: (v: boolean) => (got = v) })
|
|
59
|
-
try {
|
|
60
|
-
const sw = target.querySelector<HTMLButtonElement>('[role="switch"]')!
|
|
61
|
-
expect(sw.getAttribute('aria-readonly')).toBe('true')
|
|
62
|
-
expect(sw.hasAttribute('disabled')).toBe(false) // focusable, unlike disabled
|
|
63
|
-
sw.click(); flushSync()
|
|
64
|
-
expect(got).toBeUndefined() // no change emitted
|
|
65
|
-
} finally { destroy() }
|
|
66
|
-
})
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
describe('SvCheckBox', () => {
|
|
70
|
-
it('toggles and reports aria-checked mixed when indeterminate', () => {
|
|
71
|
-
let got: boolean | undefined
|
|
72
|
-
const a = mnt(SvCheckBox, { checked: false, onChange: (v: boolean) => (got = v) })
|
|
73
|
-
try {
|
|
74
|
-
const box = a.target.querySelector<HTMLButtonElement>('[role="checkbox"]')!
|
|
75
|
-
box.click(); flushSync()
|
|
76
|
-
expect(got).toBe(true)
|
|
77
|
-
} finally { a.destroy() }
|
|
78
|
-
const b = mnt(SvCheckBox, { indeterminate: true })
|
|
79
|
-
try {
|
|
80
|
-
expect(b.target.querySelector('[role="checkbox"]')!.getAttribute('aria-checked')).toBe('mixed')
|
|
81
|
-
} finally { b.destroy() }
|
|
82
|
-
})
|
|
83
|
-
it('readonly blocks the toggle', () => {
|
|
84
|
-
let got: boolean | undefined
|
|
85
|
-
const { target, destroy } = mnt(SvCheckBox, { checked: false, readonly: true, onChange: (v: boolean) => (got = v) })
|
|
86
|
-
try {
|
|
87
|
-
const box = target.querySelector<HTMLButtonElement>('[role="checkbox"]')!
|
|
88
|
-
expect(box.getAttribute('aria-readonly')).toBe('true')
|
|
89
|
-
box.click(); flushSync()
|
|
90
|
-
expect(got).toBeUndefined()
|
|
91
|
-
} finally { destroy() }
|
|
92
|
-
})
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
describe('SvRadioGroup', () => {
|
|
96
|
-
it('selects on click and arrow-navigates', () => {
|
|
97
|
-
const picks: unknown[] = []
|
|
98
|
-
const opts = [{ value: 'a', label: 'A' }, { value: 'b', label: 'B' }, { value: 'c', label: 'C' }]
|
|
99
|
-
const { target, destroy } = mnt(SvRadioGroup, { options: opts, value: 'a', onChange: (v: unknown) => picks.push(v) })
|
|
100
|
-
try {
|
|
101
|
-
const radios = target.querySelectorAll<HTMLButtonElement>('[role="radio"]')
|
|
102
|
-
expect(radios[0]!.getAttribute('aria-checked')).toBe('true')
|
|
103
|
-
radios[2]!.click(); flushSync()
|
|
104
|
-
expect(picks.at(-1)).toBe('c')
|
|
105
|
-
// roving tabindex: selected is 0, others -1
|
|
106
|
-
radios[0]!.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }))
|
|
107
|
-
flushSync()
|
|
108
|
-
expect(picks.at(-1)).toBe('b')
|
|
109
|
-
} finally { destroy() }
|
|
110
|
-
})
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
describe('SvRating', () => {
|
|
114
|
-
it('clicking a star emits its value; keyboard steps', () => {
|
|
115
|
-
const vals: number[] = []
|
|
116
|
-
const { target, destroy } = mnt(SvRating, { value: 2, onChange: (v: number) => vals.push(v) })
|
|
117
|
-
try {
|
|
118
|
-
const stars = target.querySelectorAll<HTMLButtonElement>('.sv-rating__star')
|
|
119
|
-
expect(stars.length).toBe(5)
|
|
120
|
-
stars[3]!.click(); flushSync()
|
|
121
|
-
expect(vals.at(-1)).toBe(4)
|
|
122
|
-
target.querySelector<HTMLElement>('.sv-rating')!.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }))
|
|
123
|
-
flushSync()
|
|
124
|
-
expect(vals.at(-1)).toBe(3) // 2 + 1 step
|
|
125
|
-
} finally { destroy() }
|
|
126
|
-
})
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
describe('SvSegmented', () => {
|
|
130
|
-
const opts = [{ value: 'list', label: 'List' }, { value: 'grid', label: 'Grid' }, { value: 'map', label: 'Map' }]
|
|
131
|
-
it('is a radiogroup; clicking selects and reports the value', () => {
|
|
132
|
-
let got: unknown
|
|
133
|
-
const { target, destroy } = mnt(SvSegmented, { options: opts, value: 'list', onChange: (v: unknown) => (got = v) })
|
|
134
|
-
try {
|
|
135
|
-
expect(target.querySelector('[role="radiogroup"]')).not.toBeNull()
|
|
136
|
-
const radios = target.querySelectorAll<HTMLButtonElement>('[role="radio"]')
|
|
137
|
-
expect(radios.length).toBe(3)
|
|
138
|
-
expect(radios[0]!.getAttribute('aria-checked')).toBe('true')
|
|
139
|
-
radios[2]!.click(); flushSync()
|
|
140
|
-
expect(got).toBe('map')
|
|
141
|
-
expect(radios[2]!.getAttribute('aria-checked')).toBe('true')
|
|
142
|
-
} finally { destroy() }
|
|
143
|
-
})
|
|
144
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* Group B (buttons & toggles) component tests. Each asserts the emitted value /
|
|
3
|
+
* ARIA state a real interaction produces.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from 'vitest'
|
|
6
|
+
import { mount, unmount, flushSync } from 'svelte'
|
|
7
|
+
import SvToggleButton from './SvToggleButton.svelte'
|
|
8
|
+
import SvSwitchButton from './SvSwitchButton.svelte'
|
|
9
|
+
import SvCheckBox from './SvCheckBox.svelte'
|
|
10
|
+
import SvRadioGroup from './SvRadioGroup.svelte'
|
|
11
|
+
import SvRating from './SvRating.svelte'
|
|
12
|
+
import SvSegmented from './SvSegmented.svelte'
|
|
13
|
+
|
|
14
|
+
function mnt(Comp: any, props: Record<string, unknown>) {
|
|
15
|
+
const target = document.createElement('div')
|
|
16
|
+
document.body.appendChild(target)
|
|
17
|
+
const app = mount(Comp, { target, props: props as any })
|
|
18
|
+
flushSync()
|
|
19
|
+
return { target, destroy: () => { unmount(app); target.remove() } }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('SvToggleButton', () => {
|
|
23
|
+
it('reflects pressed and emits the flipped value', () => {
|
|
24
|
+
let got: boolean | undefined
|
|
25
|
+
const { target, destroy } = mnt(SvToggleButton, { pressed: false, onChange: (v: boolean) => (got = v) })
|
|
26
|
+
try {
|
|
27
|
+
const btn = target.querySelector<HTMLButtonElement>('.sv-toggle')!
|
|
28
|
+
expect(btn.getAttribute('aria-pressed')).toBe('false')
|
|
29
|
+
btn.click(); flushSync()
|
|
30
|
+
expect(got).toBe(true)
|
|
31
|
+
} finally { destroy() }
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('SvSwitchButton', () => {
|
|
36
|
+
it('has role switch and toggles', () => {
|
|
37
|
+
let got: boolean | undefined
|
|
38
|
+
const { target, destroy } = mnt(SvSwitchButton, { checked: false, onChange: (v: boolean) => (got = v) })
|
|
39
|
+
try {
|
|
40
|
+
const sw = target.querySelector<HTMLButtonElement>('[role="switch"]')!
|
|
41
|
+
expect(sw.getAttribute('aria-checked')).toBe('false')
|
|
42
|
+
sw.click(); flushSync()
|
|
43
|
+
expect(got).toBe(true)
|
|
44
|
+
} finally { destroy() }
|
|
45
|
+
})
|
|
46
|
+
it('ArrowRight turns on, ArrowLeft off', () => {
|
|
47
|
+
const states: boolean[] = []
|
|
48
|
+
const { target, destroy } = mnt(SvSwitchButton, { checked: false, onChange: (v: boolean) => states.push(v) })
|
|
49
|
+
try {
|
|
50
|
+
const sw = target.querySelector<HTMLButtonElement>('[role="switch"]')!
|
|
51
|
+
sw.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }))
|
|
52
|
+
flushSync()
|
|
53
|
+
expect(states.at(-1)).toBe(true)
|
|
54
|
+
} finally { destroy() }
|
|
55
|
+
})
|
|
56
|
+
it('readonly blocks the change but stays focusable + aria-readonly', () => {
|
|
57
|
+
let got: boolean | undefined
|
|
58
|
+
const { target, destroy } = mnt(SvSwitchButton, { checked: false, readonly: true, onChange: (v: boolean) => (got = v) })
|
|
59
|
+
try {
|
|
60
|
+
const sw = target.querySelector<HTMLButtonElement>('[role="switch"]')!
|
|
61
|
+
expect(sw.getAttribute('aria-readonly')).toBe('true')
|
|
62
|
+
expect(sw.hasAttribute('disabled')).toBe(false) // focusable, unlike disabled
|
|
63
|
+
sw.click(); flushSync()
|
|
64
|
+
expect(got).toBeUndefined() // no change emitted
|
|
65
|
+
} finally { destroy() }
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('SvCheckBox', () => {
|
|
70
|
+
it('toggles and reports aria-checked mixed when indeterminate', () => {
|
|
71
|
+
let got: boolean | undefined
|
|
72
|
+
const a = mnt(SvCheckBox, { checked: false, onChange: (v: boolean) => (got = v) })
|
|
73
|
+
try {
|
|
74
|
+
const box = a.target.querySelector<HTMLButtonElement>('[role="checkbox"]')!
|
|
75
|
+
box.click(); flushSync()
|
|
76
|
+
expect(got).toBe(true)
|
|
77
|
+
} finally { a.destroy() }
|
|
78
|
+
const b = mnt(SvCheckBox, { indeterminate: true })
|
|
79
|
+
try {
|
|
80
|
+
expect(b.target.querySelector('[role="checkbox"]')!.getAttribute('aria-checked')).toBe('mixed')
|
|
81
|
+
} finally { b.destroy() }
|
|
82
|
+
})
|
|
83
|
+
it('readonly blocks the toggle', () => {
|
|
84
|
+
let got: boolean | undefined
|
|
85
|
+
const { target, destroy } = mnt(SvCheckBox, { checked: false, readonly: true, onChange: (v: boolean) => (got = v) })
|
|
86
|
+
try {
|
|
87
|
+
const box = target.querySelector<HTMLButtonElement>('[role="checkbox"]')!
|
|
88
|
+
expect(box.getAttribute('aria-readonly')).toBe('true')
|
|
89
|
+
box.click(); flushSync()
|
|
90
|
+
expect(got).toBeUndefined()
|
|
91
|
+
} finally { destroy() }
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('SvRadioGroup', () => {
|
|
96
|
+
it('selects on click and arrow-navigates', () => {
|
|
97
|
+
const picks: unknown[] = []
|
|
98
|
+
const opts = [{ value: 'a', label: 'A' }, { value: 'b', label: 'B' }, { value: 'c', label: 'C' }]
|
|
99
|
+
const { target, destroy } = mnt(SvRadioGroup, { options: opts, value: 'a', onChange: (v: unknown) => picks.push(v) })
|
|
100
|
+
try {
|
|
101
|
+
const radios = target.querySelectorAll<HTMLButtonElement>('[role="radio"]')
|
|
102
|
+
expect(radios[0]!.getAttribute('aria-checked')).toBe('true')
|
|
103
|
+
radios[2]!.click(); flushSync()
|
|
104
|
+
expect(picks.at(-1)).toBe('c')
|
|
105
|
+
// roving tabindex: selected is 0, others -1
|
|
106
|
+
radios[0]!.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }))
|
|
107
|
+
flushSync()
|
|
108
|
+
expect(picks.at(-1)).toBe('b')
|
|
109
|
+
} finally { destroy() }
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe('SvRating', () => {
|
|
114
|
+
it('clicking a star emits its value; keyboard steps', () => {
|
|
115
|
+
const vals: number[] = []
|
|
116
|
+
const { target, destroy } = mnt(SvRating, { value: 2, onChange: (v: number) => vals.push(v) })
|
|
117
|
+
try {
|
|
118
|
+
const stars = target.querySelectorAll<HTMLButtonElement>('.sv-rating__star')
|
|
119
|
+
expect(stars.length).toBe(5)
|
|
120
|
+
stars[3]!.click(); flushSync()
|
|
121
|
+
expect(vals.at(-1)).toBe(4)
|
|
122
|
+
target.querySelector<HTMLElement>('.sv-rating')!.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }))
|
|
123
|
+
flushSync()
|
|
124
|
+
expect(vals.at(-1)).toBe(3) // 2 + 1 step
|
|
125
|
+
} finally { destroy() }
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
describe('SvSegmented', () => {
|
|
130
|
+
const opts = [{ value: 'list', label: 'List' }, { value: 'grid', label: 'Grid' }, { value: 'map', label: 'Map' }]
|
|
131
|
+
it('is a radiogroup; clicking selects and reports the value', () => {
|
|
132
|
+
let got: unknown
|
|
133
|
+
const { target, destroy } = mnt(SvSegmented, { options: opts, value: 'list', onChange: (v: unknown) => (got = v) })
|
|
134
|
+
try {
|
|
135
|
+
expect(target.querySelector('[role="radiogroup"]')).not.toBeNull()
|
|
136
|
+
const radios = target.querySelectorAll<HTMLButtonElement>('[role="radio"]')
|
|
137
|
+
expect(radios.length).toBe(3)
|
|
138
|
+
expect(radios[0]!.getAttribute('aria-checked')).toBe('true')
|
|
139
|
+
radios[2]!.click(); flushSync()
|
|
140
|
+
expect(got).toBe('map')
|
|
141
|
+
expect(radios[2]!.getAttribute('aria-checked')).toBe('true')
|
|
142
|
+
} finally { destroy() }
|
|
143
|
+
})
|
|
144
|
+
})
|