@svgrid/grid 2.6.22 → 2.7.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/CHANGELOG.md +88 -88
- package/README.md +199 -199
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +181 -181
- package/dist/GridMenus.svelte +184 -6
- package/dist/SvAutoComplete.svelte +169 -165
- package/dist/SvAvatar.svelte +75 -75
- package/dist/SvCalendar.svelte +503 -503
- package/dist/SvCarousel.svelte +141 -141
- package/dist/SvCheckBox.svelte +102 -102
- package/dist/SvCircularProgress.svelte +109 -109
- package/dist/SvColorInput.svelte +181 -181
- package/dist/SvComboBox.svelte +279 -275
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +163 -163
- package/dist/SvDateRangeInput.svelte +25 -3
- package/dist/SvDrawer.svelte +254 -254
- package/dist/SvDropDownList.svelte +378 -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 +48 -1
- package/dist/SvGrid.controller.svelte.js +214 -52
- package/dist/SvGrid.css +221 -0
- package/dist/SvGrid.helpers.d.ts +55 -0
- package/dist/SvGrid.helpers.js +83 -0
- package/dist/SvGrid.svelte +145 -0
- package/dist/SvGrid.types.d.ts +105 -0
- package/dist/SvGridCellEditor.svelte +744 -662
- package/dist/SvGridChart.svelte +1716 -1716
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridChartView.svelte +70 -70
- package/dist/SvGridDropdown.svelte +728 -728
- package/dist/SvGridSelect.svelte +270 -266
- package/dist/SvGridSelect.svelte.d.ts +1 -1
- package/dist/SvGroupCell.svelte +116 -116
- package/dist/SvListBox.svelte +334 -326
- package/dist/SvMaskedInput.svelte +122 -122
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +146 -146
- package/dist/SvMultiSelect.svelte +293 -289
- package/dist/SvMultiSelect.svelte.d.ts +1 -1
- package/dist/SvNumberInput.svelte +172 -168
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPasswordInput.svelte +151 -151
- package/dist/SvPhoneInput.svelte +133 -133
- package/dist/SvPopover.svelte +197 -197
- package/dist/SvProgress.svelte +116 -116
- package/dist/SvRadioGroup.svelte +107 -107
- package/dist/SvRating.svelte +112 -112
- package/dist/SvResult.svelte +73 -73
- package/dist/SvRichCell.svelte +125 -0
- package/dist/SvRichCell.svelte.d.ts +15 -0
- package/dist/SvRichText.svelte +211 -211
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvSlider.svelte +203 -203
- package/dist/SvSwitchButton.svelte +108 -108
- package/dist/SvTagsInput.svelte +115 -115
- package/dist/SvTextArea.svelte +5 -1
- 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 +208 -208
- package/dist/SvTree.svelte +444 -444
- package/dist/SvTreeSelect.svelte +239 -235
- package/dist/SvTreeSelect.svelte.d.ts +1 -1
- package/dist/builtin-editors.d.ts +2 -2
- package/dist/builtin-editors.js +19 -3
- package/dist/cdn/GridMenus-C4oLsJyj.js +600 -0
- package/dist/cdn/GridMenus-yEivjNwl.js +601 -0
- package/dist/cdn/SvDateRangeInput-Bh0A0JkF.js +199 -0
- package/dist/cdn/SvDateRangeInput-DflbiP7N.js +198 -0
- package/dist/cdn/SvDateTimePicker-BWwpfB_o.js +812 -0
- package/dist/cdn/SvDateTimePicker-Bivn8dAP.js +816 -0
- package/dist/cdn/SvGridCellEditor-AXL8cHGO.js +550 -0
- package/dist/cdn/SvGridCellEditor-Ba7rY3eu.js +549 -0
- package/dist/cdn/{SvGridChart-BwVos976.js → SvGridChart-Bs2GIR2Q.js} +1 -1
- package/dist/cdn/{SvGridChart-BEJmNNx9.js → SvGridChart-CwhFz7GV.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-BOknOkrP.js → SvGridChartPanel-1WXuStzM.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-D2PjII4O.js → SvGridChartPanel-e5_nqNP9.js} +1 -1
- package/dist/cdn/{SvGridChartView-00LPUHL1.js → SvGridChartView-SmPW10dI.js} +1 -1
- package/dist/cdn/{SvGridChartView-BhUEJ5ki.js → SvGridChartView-eSPuJE6g.js} +1 -1
- package/dist/cdn/{SvGridDropdown-D0VdjeR8.js → SvGridDropdown-B1ZcLpgs.js} +1 -1
- package/dist/cdn/{SvGridDropdown-D13MtJ2j.js → SvGridDropdown-CtdbHcIS.js} +1 -1
- package/dist/cdn/date-format-CcP1tafP.js +1395 -0
- package/dist/cdn/date-format-DZT7T1wf.js +1393 -0
- package/dist/cdn/{editor-registry-CiI0xlKg.js → editor-registry-BhgE3S84.js} +51 -25
- package/dist/cdn/{src-C9Hihx1W.js → src-COC0vnpg.js} +7233 -6701
- package/dist/cdn/{src-D1lXwq1l.js → src-pYGV2rMR.js} +5747 -5215
- package/dist/cdn/svgrid.js +15 -13
- package/dist/cdn/svgrid.svelte-external.js +15 -13
- package/dist/cell-values.d.ts +16 -0
- package/dist/cell-values.js +28 -0
- package/dist/chart-export.js +8 -8
- package/dist/clipboard.d.ts +22 -0
- package/dist/clipboard.js +405 -27
- package/dist/editing.js +12 -7
- package/dist/editors/cell-editors.d.ts +11 -0
- package/dist/editors/cell-editors.js +18 -1
- package/dist/grid-messages.d.ts +15 -0
- package/dist/grid-messages.js +15 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/list-option.d.ts +6 -0
- package/dist/menus.js +40 -7
- package/dist/sanitize-html.d.ts +37 -0
- package/dist/sanitize-html.js +234 -0
- package/dist/selection-bar-view.svelte.d.ts +25 -0
- package/dist/selection-bar-view.svelte.js +13 -0
- package/dist/selection.d.ts +2 -1
- package/dist/selection.js +129 -13
- package/package.json +11 -11
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +181 -181
- package/src/GridMenus.svelte +184 -6
- package/src/SvAutoComplete.svelte +169 -165
- package/src/SvAvatar.svelte +75 -75
- package/src/SvCalendar.svelte +503 -503
- package/src/SvCalendar.test.ts +226 -226
- package/src/SvCarousel.svelte +141 -141
- package/src/SvCheckBox.svelte +102 -102
- package/src/SvCircularProgress.svelte +109 -109
- package/src/SvColorInput.svelte +181 -181
- package/src/SvComboBox.svelte +279 -275
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +163 -163
- package/src/SvDateRangeInput.svelte +25 -3
- package/src/SvDrawer.svelte +254 -254
- package/src/SvDropDownList.svelte +378 -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 +251 -61
- package/src/SvGrid.css +221 -0
- package/src/SvGrid.helpers.ts +87 -0
- package/src/SvGrid.svelte +145 -0
- package/src/SvGrid.types.ts +2092 -1980
- package/src/SvGridCellEditor.svelte +744 -662
- package/src/SvGridChart.svelte +1716 -1716
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridChartView.svelte +70 -70
- package/src/SvGridDropdown.svelte +728 -728
- package/src/SvGridSelect.svelte +270 -266
- package/src/SvGroupCell.svelte +116 -116
- package/src/SvListBox.svelte +334 -326
- 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 +293 -289
- package/src/SvNumberInput.svelte +172 -168
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPasswordInput.svelte +151 -151
- package/src/SvPhoneInput.svelte +133 -133
- package/src/SvPopover.svelte +197 -197
- package/src/SvProgress.svelte +116 -116
- package/src/SvRadioGroup.svelte +107 -107
- package/src/SvRating.svelte +112 -112
- package/src/SvResult.svelte +73 -73
- package/src/SvRichCell.svelte +125 -0
- package/src/SvRichText.svelte +211 -211
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvSlider.svelte +203 -203
- package/src/SvSwitchButton.svelte +108 -108
- package/src/SvTagsInput.svelte +115 -115
- package/src/SvTextArea.svelte +5 -1
- 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 +208 -208
- package/src/SvTree.svelte +444 -444
- package/src/SvTreeSelect.svelte +239 -235
- 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 +1419 -1419
- package/src/build-api.coverage.test.ts +633 -633
- package/src/build-api.ts +846 -846
- package/src/builtin-editors.grid.test.ts +83 -83
- package/src/builtin-editors.test.ts +18 -1
- package/src/builtin-editors.ts +19 -3
- package/src/cell-formatting.ts +171 -171
- package/src/cell-render.test.ts +513 -513
- package/src/cell-render.ts +496 -496
- package/src/cell-values.ts +148 -118
- package/src/chart-export.ts +202 -202
- package/src/chart-view.svelte.ts +36 -36
- package/src/chart.ts +2321 -2321
- package/src/clipboard.test.ts +93 -20
- package/src/clipboard.ts +433 -33
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/column-groups.ts +78 -78
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1865 -1865
- 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 +250 -250
- package/src/createMenu.svelte.ts +224 -224
- 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 +322 -322
- package/src/date-string-column.test.ts +87 -0
- package/src/datetime/date-core.ts +206 -206
- package/src/datetime/date-restrict.ts +61 -61
- package/src/datetime/timezone.ts +135 -135
- package/src/dock-manager-model.ts +596 -596
- package/src/dock-model.ts +374 -374
- package/src/editing.test.ts +974 -949
- package/src/editing.ts +609 -606
- package/src/editor-block-prop.test.ts +132 -0
- package/src/editor-contract.ts +171 -171
- package/src/editor-registry.grid.test.ts +144 -144
- package/src/editor-registry.ts +122 -122
- package/src/editors/cell-editors.ts +27 -1
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +601 -601
- package/src/filter-operators.ts +160 -160
- package/src/filtering/excel-filters.ts +325 -325
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +127 -127
- package/src/grid-messages.ts +34 -0
- package/src/group-display.test.ts +167 -167
- package/src/group-display.ts +200 -200
- package/src/headless.ts +87 -87
- package/src/index.ts +13 -0
- package/src/js-scroller.svelte.ts +173 -173
- package/src/keyboard-handlers.ts +270 -270
- 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 +179 -173
- package/src/menus.test.ts +43 -0
- package/src/menus.ts +597 -567
- package/src/merge-objects.ts +48 -48
- package/src/move-cells.test.ts +850 -0
- package/src/number-editor-literal.test.ts +84 -0
- package/src/number-editor.grid.test.ts +160 -0
- package/src/overlays.test.ts +90 -90
- package/src/positioning.ts +268 -268
- package/src/render-component.ts +28 -28
- package/src/row-drag-touch.ts +211 -211
- package/src/row-drag.test.ts +353 -353
- package/src/row-drag.ts +415 -415
- package/src/row-resize.test.ts +524 -524
- package/src/row-resize.ts +228 -228
- package/src/sanitize-html.test.ts +182 -0
- package/src/sanitize-html.ts +235 -0
- package/src/scheduler-ical.ts +181 -181
- package/src/scheduler-model.test.ts +562 -562
- package/src/scheduler-model.ts +873 -873
- package/src/selection-bar-view.svelte.ts +36 -0
- package/src/selection.test.ts +900 -743
- package/src/selection.ts +131 -13
- package/src/server-data-source.test.ts +383 -383
- package/src/server-data-source.ts +469 -469
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +488 -488
- package/src/spreadsheet.ts +312 -312
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/summaries.ts +113 -113
- package/src/svgrid-wrapper.types.ts +563 -563
- package/src/svgrid.async-editor-options.test.ts +273 -273
- package/src/svgrid.auto-row-height.test.ts +204 -204
- package/src/svgrid.behavior.test.ts +910 -910
- package/src/svgrid.charting.test.ts +534 -534
- package/src/svgrid.comments-autocomplete.test.ts +127 -127
- package/src/svgrid.context-menu.test.ts +147 -147
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.filter-depth.test.ts +163 -163
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +377 -331
- package/src/svgrid.filter-menu-scroll.test.ts +112 -112
- package/src/svgrid.grand-total.test.ts +188 -188
- package/src/svgrid.group-display-mode.test.ts +171 -171
- package/src/svgrid.group-footers.test.ts +121 -121
- package/src/svgrid.group-pagination.test.ts +153 -153
- package/src/svgrid.in-suggest-snapshot.svelte.test.ts +91 -0
- package/src/svgrid.menu-scroll-close.test.ts +204 -0
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.selection-bar-seam.test.ts +185 -0
- package/src/svgrid.tree-data.test.ts +186 -186
- package/src/svgrid.upsell-license.test.ts +117 -0
- package/src/svgrid.wrapper.test.ts +63 -63
- package/src/svgriddropdown.async-panel.svelte.test.ts +195 -195
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +288 -288
- package/src/toast-store.svelte.ts +250 -250
- package/src/toast-store.test.ts +147 -147
- package/src/tree-row-model.test.ts +168 -168
- package/src/ui-buttons.test.ts +144 -144
- package/src/ui-inputs.test.ts +118 -118
- package/src/ui-localization.test.ts +113 -113
- 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 +322 -322
- package/dist/cdn/GridMenus-BuoBPqxx.js +0 -493
- package/dist/cdn/GridMenus-n4llxoOI.js +0 -494
- package/dist/cdn/SvDateTimePicker-CHnUkWcH.js +0 -2206
- package/dist/cdn/SvDateTimePicker-CRQH_U7E.js +0 -2201
- package/dist/cdn/SvGridCellEditor-BGUCzuPB.js +0 -523
- package/dist/cdn/SvGridCellEditor-G8IMWIws.js +0 -522
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FREE half of the selection bar: the prop, the normalisation of its three
|
|
3
|
+
* shapes, and what shows when no Enterprise renderer is registered.
|
|
4
|
+
*
|
|
5
|
+
* The bar itself is Pro and lives behind `registerSelectionBarView`, so this
|
|
6
|
+
* suite deliberately never registers one - it asserts the upsell path and the
|
|
7
|
+
* config the renderer would receive. The rendered bar is covered by
|
|
8
|
+
* `selection-bar.dom.test.ts` in @svgrid/enterprise.
|
|
9
|
+
*/
|
|
10
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
11
|
+
import { mount, unmount } from 'svelte'
|
|
12
|
+
import SvGrid from './SvGrid.svelte'
|
|
13
|
+
import {
|
|
14
|
+
createCoreRowModel,
|
|
15
|
+
hasSelectionBarView,
|
|
16
|
+
rowSelectionFeature,
|
|
17
|
+
rowSortingFeature,
|
|
18
|
+
tableFeatures,
|
|
19
|
+
} from './index'
|
|
20
|
+
import type { ColumnDef, SvGridApi } from './index'
|
|
21
|
+
|
|
22
|
+
type Row = { id: number; name: string }
|
|
23
|
+
const features = tableFeatures({ rowSortingFeature, rowSelectionFeature })
|
|
24
|
+
const rows: Row[] = [
|
|
25
|
+
{ id: 1, name: 'Ada' },
|
|
26
|
+
{ id: 2, name: 'Grace' },
|
|
27
|
+
]
|
|
28
|
+
const cols: ColumnDef<typeof features, Row>[] = [{ field: 'name', header: 'Name', width: 160 }]
|
|
29
|
+
|
|
30
|
+
const tick = () => new Promise<void>((r) => setTimeout(r))
|
|
31
|
+
const UPSELL = '.sv-grid-selection-bar-upsell'
|
|
32
|
+
|
|
33
|
+
function mountGrid(selectionBar: unknown) {
|
|
34
|
+
return new Promise<{ api: SvGridApi<typeof features, Row>; target: HTMLElement; destroy: () => void }>(
|
|
35
|
+
(res, rej) => {
|
|
36
|
+
const target = document.createElement('div')
|
|
37
|
+
document.body.appendChild(target)
|
|
38
|
+
const app = mount(SvGrid, {
|
|
39
|
+
target,
|
|
40
|
+
props: {
|
|
41
|
+
data: rows,
|
|
42
|
+
columns: cols,
|
|
43
|
+
features,
|
|
44
|
+
_rowModels: { coreRowModel: createCoreRowModel() },
|
|
45
|
+
getRowId: (r: Row) => String(r.id),
|
|
46
|
+
containerHeight: 200,
|
|
47
|
+
virtualization: false,
|
|
48
|
+
showRowSelection: true,
|
|
49
|
+
selectionBar,
|
|
50
|
+
onApiReady(api: SvGridApi<typeof features, Row>) {
|
|
51
|
+
res({ api, target, destroy: () => { unmount(app); target.remove() } })
|
|
52
|
+
},
|
|
53
|
+
} as any,
|
|
54
|
+
})
|
|
55
|
+
queueMicrotask(() => { if (!target.querySelector('[role="grid"]')) rej(new Error('no grid')) })
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe('selectionBar - without the Enterprise renderer', () => {
|
|
61
|
+
it('reports that no renderer is registered', () => {
|
|
62
|
+
// If this is ever true here, the suite is testing the Pro path by accident
|
|
63
|
+
// and every assertion below is meaningless.
|
|
64
|
+
expect(hasSelectionBarView()).toBe(false)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('shows nothing at all until a row is selected', async () => {
|
|
68
|
+
const { target, destroy } = await mountGrid(true)
|
|
69
|
+
await tick()
|
|
70
|
+
expect(target.querySelector(UPSELL)).toBeNull()
|
|
71
|
+
destroy()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('shows the upsell in the bar position once rows are selected', async () => {
|
|
75
|
+
const { api, target, destroy } = await mountGrid(true)
|
|
76
|
+
await tick()
|
|
77
|
+
api.selectRows(['1'])
|
|
78
|
+
await vi.waitFor(() => expect(target.querySelector(UPSELL)).not.toBeNull())
|
|
79
|
+
|
|
80
|
+
const note = target.querySelector(UPSELL)!
|
|
81
|
+
expect(note.getAttribute('role')).toBe('note')
|
|
82
|
+
expect(note.textContent).toContain('@svgrid/enterprise')
|
|
83
|
+
expect(note.textContent).toContain('enableSelectionBar()')
|
|
84
|
+
destroy()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('says a license is needed, and links to it', async () => {
|
|
88
|
+
// Naming the package is what unblocks a developer trying it; saying a key
|
|
89
|
+
// is needed is what they have to know before shipping. Enterprise is
|
|
90
|
+
// SOFT-gated, so the wording must not imply the feature is inert without
|
|
91
|
+
// one - it says "watermark", which is what actually happens.
|
|
92
|
+
const { api, target, destroy } = await mountGrid(true)
|
|
93
|
+
await tick()
|
|
94
|
+
api.selectRows(['1'])
|
|
95
|
+
await vi.waitFor(() => expect(target.querySelector(UPSELL)).not.toBeNull())
|
|
96
|
+
|
|
97
|
+
const license = target.querySelector('.sv-grid-upsell-license')!
|
|
98
|
+
expect(license).not.toBeNull()
|
|
99
|
+
expect(license.textContent).toContain('license key')
|
|
100
|
+
expect(license.textContent).toContain('watermark')
|
|
101
|
+
|
|
102
|
+
const link = license.querySelector('a')!
|
|
103
|
+
expect(link.getAttribute('href')).toBe('https://svgrid.com/pricing/')
|
|
104
|
+
// Opening a pricing page must not navigate the host app away.
|
|
105
|
+
expect(link.getAttribute('target')).toBe('_blank')
|
|
106
|
+
expect(link.getAttribute('rel')).toContain('noopener')
|
|
107
|
+
|
|
108
|
+
destroy()
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('is localizable, like the rest of the grid chrome', async () => {
|
|
112
|
+
const target = document.createElement('div')
|
|
113
|
+
document.body.appendChild(target)
|
|
114
|
+
let api: SvGridApi<typeof features, Row>
|
|
115
|
+
const app = mount(SvGrid, {
|
|
116
|
+
target,
|
|
117
|
+
props: {
|
|
118
|
+
data: rows,
|
|
119
|
+
columns: cols,
|
|
120
|
+
features,
|
|
121
|
+
_rowModels: { coreRowModel: createCoreRowModel() },
|
|
122
|
+
getRowId: (r: Row) => String(r.id),
|
|
123
|
+
containerHeight: 200,
|
|
124
|
+
virtualization: false,
|
|
125
|
+
showRowSelection: true,
|
|
126
|
+
selectionBar: true,
|
|
127
|
+
localization: { text: { upsellLicense: 'Lizenz erforderlich.' } },
|
|
128
|
+
onApiReady: (a: SvGridApi<typeof features, Row>) => (api = a),
|
|
129
|
+
} as any,
|
|
130
|
+
})
|
|
131
|
+
await tick()
|
|
132
|
+
api!.selectRows(['1'])
|
|
133
|
+
await vi.waitFor(() => {
|
|
134
|
+
expect(target.querySelector('.sv-grid-upsell-license')?.textContent).toContain(
|
|
135
|
+
'Lizenz erforderlich.',
|
|
136
|
+
)
|
|
137
|
+
})
|
|
138
|
+
unmount(app)
|
|
139
|
+
target.remove()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('places the upsell on the edge the real bar would use', async () => {
|
|
143
|
+
// So installing enterprise does not move the thing the user was looking at.
|
|
144
|
+
const { api, target, destroy } = await mountGrid({ position: 'top', actions: [] })
|
|
145
|
+
await tick()
|
|
146
|
+
api.selectRows(['1'])
|
|
147
|
+
await vi.waitFor(() => expect(target.querySelector(UPSELL)).not.toBeNull())
|
|
148
|
+
expect(target.querySelector(UPSELL)!.getAttribute('data-position')).toBe('top')
|
|
149
|
+
destroy()
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('stays out of the way entirely when the prop is unset or false', async () => {
|
|
153
|
+
for (const prop of [undefined, false]) {
|
|
154
|
+
const { api, target, destroy } = await mountGrid(prop)
|
|
155
|
+
await tick()
|
|
156
|
+
api.selectRows(['1'])
|
|
157
|
+
await tick()
|
|
158
|
+
await tick()
|
|
159
|
+
expect(target.querySelector(UPSELL), `selectionBar={${String(prop)}}`).toBeNull()
|
|
160
|
+
destroy()
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe('selectionBar - the three prop shapes all reach the renderer', () => {
|
|
166
|
+
// Each shape has to produce a bar. What lands ON it is asserted against the
|
|
167
|
+
// real renderer in the enterprise suite; here the question is only whether
|
|
168
|
+
// the prop was understood at all.
|
|
169
|
+
const shapes: Array<[string, unknown]> = [
|
|
170
|
+
['true', true],
|
|
171
|
+
['an action array', [{ key: 'k', label: 'K', action: () => {} }]],
|
|
172
|
+
['a config object', { position: 'bottom', actions: [], maxVisible: 2 }],
|
|
173
|
+
['a config object with no actions', { position: 'top' }],
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
for (const [name, prop] of shapes) {
|
|
177
|
+
it(`understands ${name}`, async () => {
|
|
178
|
+
const { api, target, destroy } = await mountGrid(prop)
|
|
179
|
+
await tick()
|
|
180
|
+
api.selectRows(['1'])
|
|
181
|
+
await vi.waitFor(() => expect(target.querySelector(UPSELL)).not.toBeNull())
|
|
182
|
+
destroy()
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
})
|
|
@@ -1,186 +1,186 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DOM: `treeData` renders a client-side hierarchy (#26).
|
|
3
|
-
*
|
|
4
|
-
* The point of the design is that tree rows stay REAL data rows: they render
|
|
5
|
-
* their own cells, so editing/formatting/selection all keep working, and only
|
|
6
|
-
* gain an expander plus indentation in the tree column. Grouping's banners are
|
|
7
|
-
* synthetic full-width rows - a tree row must never be rendered like one.
|
|
8
|
-
*/
|
|
9
|
-
import { afterEach, describe, expect, it } from 'vitest'
|
|
10
|
-
import { mount, unmount } from 'svelte'
|
|
11
|
-
import SvGrid from './SvGrid.svelte'
|
|
12
|
-
import {
|
|
13
|
-
createCoreRowModel,
|
|
14
|
-
createFilteredRowModel,
|
|
15
|
-
createPaginatedRowModel,
|
|
16
|
-
createSortedRowModel,
|
|
17
|
-
flattenTreeData,
|
|
18
|
-
sortFns,
|
|
19
|
-
tableFeatures,
|
|
20
|
-
rowSortingFeature,
|
|
21
|
-
} from './index'
|
|
22
|
-
|
|
23
|
-
type Node = { id: number; managerId: number | null; name: string; team: string }
|
|
24
|
-
|
|
25
|
-
const data: Node[] = [
|
|
26
|
-
{ id: 1, managerId: null, name: 'Ada', team: 'Exec' },
|
|
27
|
-
{ id: 2, managerId: 1, name: 'Grace', team: 'Eng' },
|
|
28
|
-
{ id: 3, managerId: 2, name: 'Alan', team: 'Eng' },
|
|
29
|
-
{ id: 4, managerId: 1, name: 'Linus', team: 'Ops' },
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
const columns = [
|
|
33
|
-
{ field: 'name', header: 'Name', width: 200 },
|
|
34
|
-
{ field: 'team', header: 'Team', width: 120 },
|
|
35
|
-
]
|
|
36
|
-
|
|
37
|
-
const features = tableFeatures({ rowSortingFeature })
|
|
38
|
-
const tick = () => new Promise<void>((r) => queueMicrotask(r))
|
|
39
|
-
|
|
40
|
-
function mountGrid(extra: Record<string, unknown> = {}) {
|
|
41
|
-
const target = document.createElement('div')
|
|
42
|
-
document.body.appendChild(target)
|
|
43
|
-
const app = mount(SvGrid, {
|
|
44
|
-
target,
|
|
45
|
-
props: {
|
|
46
|
-
data,
|
|
47
|
-
columns,
|
|
48
|
-
features,
|
|
49
|
-
treeData: { parentField: 'managerId', column: 'name' },
|
|
50
|
-
_rowModels: {
|
|
51
|
-
coreRowModel: createCoreRowModel(),
|
|
52
|
-
filteredRowModel: createFilteredRowModel(),
|
|
53
|
-
sortedRowModel: createSortedRowModel(sortFns),
|
|
54
|
-
paginatedRowModel: createPaginatedRowModel(),
|
|
55
|
-
},
|
|
56
|
-
containerHeight: 600,
|
|
57
|
-
virtualization: false,
|
|
58
|
-
...extra,
|
|
59
|
-
} as never,
|
|
60
|
-
})
|
|
61
|
-
return { target, destroy: () => { unmount(app); target.remove() } }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
let cleanup: (() => void) | null = null
|
|
65
|
-
afterEach(() => { cleanup?.(); cleanup = null })
|
|
66
|
-
|
|
67
|
-
const bodyRows = (t: HTMLElement) =>
|
|
68
|
-
[...t.querySelectorAll('tbody tr.sv-grid-row')].filter(
|
|
69
|
-
(r) => !r.classList.contains('sv-grid-row-spacer'),
|
|
70
|
-
)
|
|
71
|
-
const toggles = (t: HTMLElement) =>
|
|
72
|
-
[...t.querySelectorAll<HTMLButtonElement>('.sv-grid-tree-toggle')]
|
|
73
|
-
|
|
74
|
-
describe('treeData', () => {
|
|
75
|
-
it('shows only roots until expanded', async () => {
|
|
76
|
-
const { target, destroy } = mountGrid()
|
|
77
|
-
cleanup = destroy
|
|
78
|
-
await tick()
|
|
79
|
-
expect(bodyRows(target)).toHaveLength(1)
|
|
80
|
-
expect(target.textContent).toContain('Ada')
|
|
81
|
-
expect(target.textContent).not.toContain('Grace')
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('expands to reveal children', async () => {
|
|
85
|
-
const { target, destroy } = mountGrid()
|
|
86
|
-
cleanup = destroy
|
|
87
|
-
await tick()
|
|
88
|
-
toggles(target)[0]!.click()
|
|
89
|
-
await tick()
|
|
90
|
-
expect(target.textContent).toContain('Grace')
|
|
91
|
-
expect(target.textContent).toContain('Linus')
|
|
92
|
-
// Alan is a grandchild - still hidden.
|
|
93
|
-
expect(target.textContent).not.toContain('Alan')
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it('renders tree rows as normal cell rows, not full-width banners', async () => {
|
|
97
|
-
const { target, destroy } = mountGrid()
|
|
98
|
-
cleanup = destroy
|
|
99
|
-
await tick()
|
|
100
|
-
expect(target.querySelectorAll('tr.sv-grid-group-row')).toHaveLength(0)
|
|
101
|
-
// The root has its own cells - both columns render.
|
|
102
|
-
const cells = bodyRows(target)[0]!.querySelectorAll('.sv-grid-cell')
|
|
103
|
-
expect(cells.length).toBeGreaterThanOrEqual(2)
|
|
104
|
-
expect(bodyRows(target)[0]!.textContent).toContain('Exec')
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('uses the treegrid role with aria-level and aria-expanded', async () => {
|
|
108
|
-
const { target, destroy } = mountGrid()
|
|
109
|
-
cleanup = destroy
|
|
110
|
-
await tick()
|
|
111
|
-
expect(target.querySelector('[role="treegrid"]')).not.toBeNull()
|
|
112
|
-
const root = bodyRows(target)[0]!
|
|
113
|
-
expect(root.getAttribute('aria-level')).toBe('1')
|
|
114
|
-
expect(root.getAttribute('aria-expanded')).toBe('false')
|
|
115
|
-
|
|
116
|
-
toggles(target)[0]!.click()
|
|
117
|
-
await tick()
|
|
118
|
-
expect(bodyRows(target)[0]!.getAttribute('aria-expanded')).toBe('true')
|
|
119
|
-
// A child sits one level deeper.
|
|
120
|
-
expect(bodyRows(target)[1]!.getAttribute('aria-level')).toBe('2')
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
it('indents from ONE source only', async () => {
|
|
124
|
-
// `sv-grid-group-child-indent` exists for rows nested under a group banner
|
|
125
|
-
// and keys off `row.depth`, which tree rows also have. It used to stack on
|
|
126
|
-
// top of the tree indent, putting a 20px gap between a row's chevron and
|
|
127
|
-
// its text - the tree affordance owns indentation here.
|
|
128
|
-
const { target, destroy } = mountGrid()
|
|
129
|
-
cleanup = destroy
|
|
130
|
-
await tick()
|
|
131
|
-
toggles(target)[0]!.click()
|
|
132
|
-
await tick()
|
|
133
|
-
|
|
134
|
-
expect(target.querySelectorAll('.sv-grid-group-child-indent')).toHaveLength(0)
|
|
135
|
-
// Every name cell still carries exactly one tree indent.
|
|
136
|
-
const nameCells = [...target.querySelectorAll('[data-col-id="name"]')]
|
|
137
|
-
for (const cell of nameCells) {
|
|
138
|
-
expect(cell.querySelectorAll('.sv-grid-tree-indent')).toHaveLength(1)
|
|
139
|
-
}
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('indents by depth x indentPx, honouring the override', async () => {
|
|
143
|
-
const { target, destroy } = mountGrid({
|
|
144
|
-
treeData: { parentField: 'managerId', column: 'name', indentPx: 30 },
|
|
145
|
-
})
|
|
146
|
-
cleanup = destroy
|
|
147
|
-
await tick()
|
|
148
|
-
toggles(target)[0]!.click()
|
|
149
|
-
await tick()
|
|
150
|
-
const widths = [...target.querySelectorAll<HTMLElement>('[data-col-id="name"] .sv-grid-tree-indent')]
|
|
151
|
-
.map((e) => e.style.width)
|
|
152
|
-
// Root at 0, its children one level in.
|
|
153
|
-
expect(widths[0]).toBe('0px')
|
|
154
|
-
expect(widths[1]).toBe('30px')
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('gives a leaf no expander but keeps it indented', async () => {
|
|
158
|
-
const { target, destroy } = mountGrid()
|
|
159
|
-
cleanup = destroy
|
|
160
|
-
await tick()
|
|
161
|
-
toggles(target)[0]!.click()
|
|
162
|
-
await tick()
|
|
163
|
-
// Linus is a leaf: spacer, no toggle button.
|
|
164
|
-
const linus = bodyRows(target).find((r) => r.textContent?.includes('Linus'))!
|
|
165
|
-
expect(linus.querySelector('.sv-grid-tree-toggle')).toBeNull()
|
|
166
|
-
expect(linus.querySelector('.sv-grid-tree-spacer')).not.toBeNull()
|
|
167
|
-
expect(linus.querySelector('.sv-grid-tree-indent')).not.toBeNull()
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
it('accepts nested data once flattened', async () => {
|
|
171
|
-
const nested = [
|
|
172
|
-
{ id: 1, name: 'Ada', team: 'Exec', reports: [{ id: 2, name: 'Grace', team: 'Eng' }] },
|
|
173
|
-
]
|
|
174
|
-
const flat = flattenTreeData(nested as never, { childrenField: 'reports' })
|
|
175
|
-
const { target, destroy } = mountGrid({
|
|
176
|
-
data: flat,
|
|
177
|
-
treeData: { parentField: '__parentId', column: 'name' },
|
|
178
|
-
})
|
|
179
|
-
cleanup = destroy
|
|
180
|
-
await tick()
|
|
181
|
-
expect(bodyRows(target)).toHaveLength(1)
|
|
182
|
-
toggles(target)[0]!.click()
|
|
183
|
-
await tick()
|
|
184
|
-
expect(target.textContent).toContain('Grace')
|
|
185
|
-
})
|
|
186
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* DOM: `treeData` renders a client-side hierarchy (#26).
|
|
3
|
+
*
|
|
4
|
+
* The point of the design is that tree rows stay REAL data rows: they render
|
|
5
|
+
* their own cells, so editing/formatting/selection all keep working, and only
|
|
6
|
+
* gain an expander plus indentation in the tree column. Grouping's banners are
|
|
7
|
+
* synthetic full-width rows - a tree row must never be rendered like one.
|
|
8
|
+
*/
|
|
9
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
10
|
+
import { mount, unmount } from 'svelte'
|
|
11
|
+
import SvGrid from './SvGrid.svelte'
|
|
12
|
+
import {
|
|
13
|
+
createCoreRowModel,
|
|
14
|
+
createFilteredRowModel,
|
|
15
|
+
createPaginatedRowModel,
|
|
16
|
+
createSortedRowModel,
|
|
17
|
+
flattenTreeData,
|
|
18
|
+
sortFns,
|
|
19
|
+
tableFeatures,
|
|
20
|
+
rowSortingFeature,
|
|
21
|
+
} from './index'
|
|
22
|
+
|
|
23
|
+
type Node = { id: number; managerId: number | null; name: string; team: string }
|
|
24
|
+
|
|
25
|
+
const data: Node[] = [
|
|
26
|
+
{ id: 1, managerId: null, name: 'Ada', team: 'Exec' },
|
|
27
|
+
{ id: 2, managerId: 1, name: 'Grace', team: 'Eng' },
|
|
28
|
+
{ id: 3, managerId: 2, name: 'Alan', team: 'Eng' },
|
|
29
|
+
{ id: 4, managerId: 1, name: 'Linus', team: 'Ops' },
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
const columns = [
|
|
33
|
+
{ field: 'name', header: 'Name', width: 200 },
|
|
34
|
+
{ field: 'team', header: 'Team', width: 120 },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
const features = tableFeatures({ rowSortingFeature })
|
|
38
|
+
const tick = () => new Promise<void>((r) => queueMicrotask(r))
|
|
39
|
+
|
|
40
|
+
function mountGrid(extra: Record<string, unknown> = {}) {
|
|
41
|
+
const target = document.createElement('div')
|
|
42
|
+
document.body.appendChild(target)
|
|
43
|
+
const app = mount(SvGrid, {
|
|
44
|
+
target,
|
|
45
|
+
props: {
|
|
46
|
+
data,
|
|
47
|
+
columns,
|
|
48
|
+
features,
|
|
49
|
+
treeData: { parentField: 'managerId', column: 'name' },
|
|
50
|
+
_rowModels: {
|
|
51
|
+
coreRowModel: createCoreRowModel(),
|
|
52
|
+
filteredRowModel: createFilteredRowModel(),
|
|
53
|
+
sortedRowModel: createSortedRowModel(sortFns),
|
|
54
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
55
|
+
},
|
|
56
|
+
containerHeight: 600,
|
|
57
|
+
virtualization: false,
|
|
58
|
+
...extra,
|
|
59
|
+
} as never,
|
|
60
|
+
})
|
|
61
|
+
return { target, destroy: () => { unmount(app); target.remove() } }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let cleanup: (() => void) | null = null
|
|
65
|
+
afterEach(() => { cleanup?.(); cleanup = null })
|
|
66
|
+
|
|
67
|
+
const bodyRows = (t: HTMLElement) =>
|
|
68
|
+
[...t.querySelectorAll('tbody tr.sv-grid-row')].filter(
|
|
69
|
+
(r) => !r.classList.contains('sv-grid-row-spacer'),
|
|
70
|
+
)
|
|
71
|
+
const toggles = (t: HTMLElement) =>
|
|
72
|
+
[...t.querySelectorAll<HTMLButtonElement>('.sv-grid-tree-toggle')]
|
|
73
|
+
|
|
74
|
+
describe('treeData', () => {
|
|
75
|
+
it('shows only roots until expanded', async () => {
|
|
76
|
+
const { target, destroy } = mountGrid()
|
|
77
|
+
cleanup = destroy
|
|
78
|
+
await tick()
|
|
79
|
+
expect(bodyRows(target)).toHaveLength(1)
|
|
80
|
+
expect(target.textContent).toContain('Ada')
|
|
81
|
+
expect(target.textContent).not.toContain('Grace')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('expands to reveal children', async () => {
|
|
85
|
+
const { target, destroy } = mountGrid()
|
|
86
|
+
cleanup = destroy
|
|
87
|
+
await tick()
|
|
88
|
+
toggles(target)[0]!.click()
|
|
89
|
+
await tick()
|
|
90
|
+
expect(target.textContent).toContain('Grace')
|
|
91
|
+
expect(target.textContent).toContain('Linus')
|
|
92
|
+
// Alan is a grandchild - still hidden.
|
|
93
|
+
expect(target.textContent).not.toContain('Alan')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('renders tree rows as normal cell rows, not full-width banners', async () => {
|
|
97
|
+
const { target, destroy } = mountGrid()
|
|
98
|
+
cleanup = destroy
|
|
99
|
+
await tick()
|
|
100
|
+
expect(target.querySelectorAll('tr.sv-grid-group-row')).toHaveLength(0)
|
|
101
|
+
// The root has its own cells - both columns render.
|
|
102
|
+
const cells = bodyRows(target)[0]!.querySelectorAll('.sv-grid-cell')
|
|
103
|
+
expect(cells.length).toBeGreaterThanOrEqual(2)
|
|
104
|
+
expect(bodyRows(target)[0]!.textContent).toContain('Exec')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('uses the treegrid role with aria-level and aria-expanded', async () => {
|
|
108
|
+
const { target, destroy } = mountGrid()
|
|
109
|
+
cleanup = destroy
|
|
110
|
+
await tick()
|
|
111
|
+
expect(target.querySelector('[role="treegrid"]')).not.toBeNull()
|
|
112
|
+
const root = bodyRows(target)[0]!
|
|
113
|
+
expect(root.getAttribute('aria-level')).toBe('1')
|
|
114
|
+
expect(root.getAttribute('aria-expanded')).toBe('false')
|
|
115
|
+
|
|
116
|
+
toggles(target)[0]!.click()
|
|
117
|
+
await tick()
|
|
118
|
+
expect(bodyRows(target)[0]!.getAttribute('aria-expanded')).toBe('true')
|
|
119
|
+
// A child sits one level deeper.
|
|
120
|
+
expect(bodyRows(target)[1]!.getAttribute('aria-level')).toBe('2')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('indents from ONE source only', async () => {
|
|
124
|
+
// `sv-grid-group-child-indent` exists for rows nested under a group banner
|
|
125
|
+
// and keys off `row.depth`, which tree rows also have. It used to stack on
|
|
126
|
+
// top of the tree indent, putting a 20px gap between a row's chevron and
|
|
127
|
+
// its text - the tree affordance owns indentation here.
|
|
128
|
+
const { target, destroy } = mountGrid()
|
|
129
|
+
cleanup = destroy
|
|
130
|
+
await tick()
|
|
131
|
+
toggles(target)[0]!.click()
|
|
132
|
+
await tick()
|
|
133
|
+
|
|
134
|
+
expect(target.querySelectorAll('.sv-grid-group-child-indent')).toHaveLength(0)
|
|
135
|
+
// Every name cell still carries exactly one tree indent.
|
|
136
|
+
const nameCells = [...target.querySelectorAll('[data-col-id="name"]')]
|
|
137
|
+
for (const cell of nameCells) {
|
|
138
|
+
expect(cell.querySelectorAll('.sv-grid-tree-indent')).toHaveLength(1)
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('indents by depth x indentPx, honouring the override', async () => {
|
|
143
|
+
const { target, destroy } = mountGrid({
|
|
144
|
+
treeData: { parentField: 'managerId', column: 'name', indentPx: 30 },
|
|
145
|
+
})
|
|
146
|
+
cleanup = destroy
|
|
147
|
+
await tick()
|
|
148
|
+
toggles(target)[0]!.click()
|
|
149
|
+
await tick()
|
|
150
|
+
const widths = [...target.querySelectorAll<HTMLElement>('[data-col-id="name"] .sv-grid-tree-indent')]
|
|
151
|
+
.map((e) => e.style.width)
|
|
152
|
+
// Root at 0, its children one level in.
|
|
153
|
+
expect(widths[0]).toBe('0px')
|
|
154
|
+
expect(widths[1]).toBe('30px')
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('gives a leaf no expander but keeps it indented', async () => {
|
|
158
|
+
const { target, destroy } = mountGrid()
|
|
159
|
+
cleanup = destroy
|
|
160
|
+
await tick()
|
|
161
|
+
toggles(target)[0]!.click()
|
|
162
|
+
await tick()
|
|
163
|
+
// Linus is a leaf: spacer, no toggle button.
|
|
164
|
+
const linus = bodyRows(target).find((r) => r.textContent?.includes('Linus'))!
|
|
165
|
+
expect(linus.querySelector('.sv-grid-tree-toggle')).toBeNull()
|
|
166
|
+
expect(linus.querySelector('.sv-grid-tree-spacer')).not.toBeNull()
|
|
167
|
+
expect(linus.querySelector('.sv-grid-tree-indent')).not.toBeNull()
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('accepts nested data once flattened', async () => {
|
|
171
|
+
const nested = [
|
|
172
|
+
{ id: 1, name: 'Ada', team: 'Exec', reports: [{ id: 2, name: 'Grace', team: 'Eng' }] },
|
|
173
|
+
]
|
|
174
|
+
const flat = flattenTreeData(nested as never, { childrenField: 'reports' })
|
|
175
|
+
const { target, destroy } = mountGrid({
|
|
176
|
+
data: flat,
|
|
177
|
+
treeData: { parentField: '__parentId', column: 'name' },
|
|
178
|
+
})
|
|
179
|
+
cleanup = destroy
|
|
180
|
+
await tick()
|
|
181
|
+
expect(bodyRows(target)).toHaveLength(1)
|
|
182
|
+
toggles(target)[0]!.click()
|
|
183
|
+
await tick()
|
|
184
|
+
expect(target.textContent).toContain('Grace')
|
|
185
|
+
})
|
|
186
|
+
})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every Enterprise gate carries the same licensing line.
|
|
3
|
+
*
|
|
4
|
+
* There are four of them - Kanban board, scheduler, pivot mode and the
|
|
5
|
+
* selection bar - and they used to be four hand-written paragraphs. Four gates
|
|
6
|
+
* worded four ways is how a product ends up explaining its own licensing
|
|
7
|
+
* inconsistently, so they now share one snippet. This is the test that keeps
|
|
8
|
+
* them sharing it: a fifth Enterprise feature that hand-rolls its own note
|
|
9
|
+
* will show up here as a missing licensing line.
|
|
10
|
+
*
|
|
11
|
+
* None of the renderers are registered in this file, which is the point - it
|
|
12
|
+
* asserts what a FREE grid shows.
|
|
13
|
+
*/
|
|
14
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
15
|
+
import { mount, unmount } from 'svelte'
|
|
16
|
+
import SvGrid from './SvGrid.svelte'
|
|
17
|
+
import {
|
|
18
|
+
createCoreRowModel,
|
|
19
|
+
hasBoardView,
|
|
20
|
+
hasSchedulerView,
|
|
21
|
+
hasSelectionBarView,
|
|
22
|
+
tableFeatures,
|
|
23
|
+
} from './index'
|
|
24
|
+
import type { ColumnDef, SvGridApi } from './index'
|
|
25
|
+
|
|
26
|
+
type Row = { id: number; name: string; team: string; score: number }
|
|
27
|
+
const features = tableFeatures({})
|
|
28
|
+
const rows: Row[] = [
|
|
29
|
+
{ id: 1, name: 'Ada', team: 'A', score: 3 },
|
|
30
|
+
{ id: 2, name: 'Grace', team: 'B', score: 5 },
|
|
31
|
+
]
|
|
32
|
+
const cols: ColumnDef<typeof features, Row>[] = [
|
|
33
|
+
{ field: 'name', header: 'Name', width: 140 },
|
|
34
|
+
{ field: 'team', header: 'Team', width: 120 },
|
|
35
|
+
{ field: 'score', header: 'Score', width: 100 },
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
const tick = () => new Promise<void>((r) => setTimeout(r))
|
|
39
|
+
const LICENSE = '.sv-grid-upsell-license'
|
|
40
|
+
|
|
41
|
+
function mountGrid(extra: Record<string, unknown>) {
|
|
42
|
+
return new Promise<{ api: SvGridApi<typeof features, Row>; target: HTMLElement; destroy: () => void }>(
|
|
43
|
+
(res, rej) => {
|
|
44
|
+
const target = document.createElement('div')
|
|
45
|
+
document.body.appendChild(target)
|
|
46
|
+
const app = mount(SvGrid, {
|
|
47
|
+
target,
|
|
48
|
+
props: {
|
|
49
|
+
data: rows,
|
|
50
|
+
columns: cols,
|
|
51
|
+
features,
|
|
52
|
+
_rowModels: { coreRowModel: createCoreRowModel() },
|
|
53
|
+
getRowId: (r: Row) => String(r.id),
|
|
54
|
+
containerHeight: 260,
|
|
55
|
+
virtualization: false,
|
|
56
|
+
onApiReady(api: SvGridApi<typeof features, Row>) {
|
|
57
|
+
res({ api, target, destroy: () => { unmount(app); target.remove() } })
|
|
58
|
+
},
|
|
59
|
+
...extra,
|
|
60
|
+
} as any,
|
|
61
|
+
})
|
|
62
|
+
queueMicrotask(() => { if (!target.querySelector('[role="grid"]')) rej(new Error('no grid')) })
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe('Enterprise upsell notes', () => {
|
|
68
|
+
it('starts from a grid with no Pro renderer registered', () => {
|
|
69
|
+
// Otherwise every assertion below is checking the wrong branch.
|
|
70
|
+
expect(hasBoardView()).toBe(false)
|
|
71
|
+
expect(hasSchedulerView()).toBe(false)
|
|
72
|
+
expect(hasSelectionBarView()).toBe(false)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const gates: Array<[string, Record<string, unknown>, ((api: SvGridApi<typeof features, Row>) => void)?]> = [
|
|
76
|
+
['Kanban board', { board: { groupBy: 'team' } }],
|
|
77
|
+
['scheduler', { scheduler: { startField: 'name' } }],
|
|
78
|
+
['selection bar', { showRowSelection: true, selectionBar: true }, (api) => api.selectRows(['1'])],
|
|
79
|
+
// pivotViewOn = a pivot config AND pivot mode on; without the Pro engine
|
|
80
|
+
// `pivotResult` stays null and the upsell renders in its place.
|
|
81
|
+
['pivot mode', { pivot: { rows: ['team'], values: [{ field: 'score', agg: 'sum' }] }, pivotMode: true }],
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
for (const [name, props, activate] of gates) {
|
|
85
|
+
it(`the ${name} gate explains the licensing`, async () => {
|
|
86
|
+
const { api, target, destroy } = await mountGrid(props)
|
|
87
|
+
try {
|
|
88
|
+
await tick()
|
|
89
|
+
activate?.(api)
|
|
90
|
+
await vi.waitFor(() => {
|
|
91
|
+
expect(target.querySelector(LICENSE), `${name} upsell`).not.toBeNull()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const note = target.querySelector(LICENSE)!
|
|
95
|
+
// Soft-gated: the feature runs without a key and the grid watermarks.
|
|
96
|
+
// The wording must not imply it is inert, because it is not.
|
|
97
|
+
expect(note.textContent, name).toContain('license key')
|
|
98
|
+
expect(note.textContent, name).toContain('watermark')
|
|
99
|
+
expect(note.querySelector('a')?.getAttribute('href'), name).toBe(
|
|
100
|
+
'https://svgrid.com/pricing/',
|
|
101
|
+
)
|
|
102
|
+
} finally {
|
|
103
|
+
destroy()
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
it('says nothing about licensing on a grid using no Pro feature', async () => {
|
|
109
|
+
const { target, destroy } = await mountGrid({})
|
|
110
|
+
try {
|
|
111
|
+
await tick()
|
|
112
|
+
expect(target.querySelector(LICENSE)).toBeNull()
|
|
113
|
+
} finally {
|
|
114
|
+
destroy()
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
})
|