@carto/ps-react-ui 4.3.3 → 4.3.5
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/dist/components.js +3 -3
- package/dist/components.js.map +1 -1
- package/dist/{lasso-tool-BwRzEW7k.js → lasso-tool-wFqOD6wk.js} +13 -13
- package/dist/lasso-tool-wFqOD6wk.js.map +1 -0
- package/dist/types/components/common-types.d.ts +41 -0
- package/dist/types/components/types.d.ts +1 -1
- package/dist/types/widgets/echart/echart-ui.d.ts +1 -1
- package/dist/types/widgets/echart/types.d.ts +4 -0
- package/dist/widgets/actions.js +1 -1
- package/dist/widgets/bar.js +1 -1
- package/dist/widgets/category.js +1 -1
- package/dist/widgets/echart.js +96 -85
- package/dist/widgets/echart.js.map +1 -1
- package/dist/widgets/formula.js +1 -1
- package/dist/widgets/histogram.js +1 -1
- package/dist/widgets/markdown.js +1 -1
- package/dist/widgets/pie.js +1 -1
- package/dist/widgets/scatterplot.js +1 -1
- package/dist/widgets/spread.js +1 -1
- package/dist/widgets/table.js +1 -1
- package/dist/widgets/timeseries.js +1 -1
- package/dist/widgets/toolbar-actions.js.map +1 -1
- package/dist/widgets/wrapper.js +1 -1
- package/package.json +8 -3
- package/src/components/basemaps/basemaps.test.tsx +196 -0
- package/src/components/basemaps/basemaps.tsx +128 -0
- package/src/components/basemaps/const.ts +13 -0
- package/src/components/basemaps/group-wrapper.test.tsx +38 -0
- package/src/components/basemaps/group-wrapper.tsx +28 -0
- package/src/components/basemaps/group.test.tsx +52 -0
- package/src/components/basemaps/group.tsx +42 -0
- package/src/components/basemaps/header.test.tsx +54 -0
- package/src/components/basemaps/header.tsx +36 -0
- package/src/components/basemaps/styles.ts +76 -0
- package/src/components/basemaps/types.ts +30 -0
- package/src/components/common-types.ts +1 -0
- package/src/components/geolocation-controls/const.ts +6 -0
- package/src/components/geolocation-controls/geolocation-controls.test.tsx +133 -0
- package/src/components/geolocation-controls/geolocation-controls.tsx +95 -0
- package/src/components/geolocation-controls/types.ts +17 -0
- package/src/components/index.ts +64 -0
- package/src/components/lasso-tool/chip.tsx +37 -0
- package/src/components/lasso-tool/const.tsx +70 -0
- package/src/components/lasso-tool/icons.tsx +91 -0
- package/src/components/lasso-tool/lasso-tool-inline.test.tsx +168 -0
- package/src/components/lasso-tool/lasso-tool-inline.tsx +245 -0
- package/src/components/lasso-tool/lasso-tool.test.tsx +212 -0
- package/src/components/lasso-tool/lasso-tool.tsx +479 -0
- package/src/components/lasso-tool/styles.ts +143 -0
- package/src/components/lasso-tool/types.ts +114 -0
- package/src/components/list-data/list-data-skeleton.test.tsx +10 -0
- package/src/components/list-data/list-data-skeleton.tsx +40 -0
- package/src/components/list-data/list-data.test.tsx +94 -0
- package/src/components/list-data/list-data.tsx +106 -0
- package/src/components/list-data/styles.ts +37 -0
- package/src/components/list-data/types.ts +25 -0
- package/src/components/measurement-tools/const.tsx +108 -0
- package/src/components/measurement-tools/icons.tsx +54 -0
- package/src/components/measurement-tools/measurement-tools.test.tsx +165 -0
- package/src/components/measurement-tools/measurement-tools.tsx +443 -0
- package/src/components/measurement-tools/styles.ts +91 -0
- package/src/components/measurement-tools/types.ts +77 -0
- package/src/components/no-data-alert/no-data-alert.test.tsx +31 -0
- package/src/components/no-data-alert/no-data-alert.tsx +59 -0
- package/src/components/responsive-drawer/responsive-drawer.test.tsx +91 -0
- package/src/components/responsive-drawer/responsive-drawer.tsx +53 -0
- package/src/components/smart-tooltip/smart-tooltip.test.tsx +168 -0
- package/src/components/smart-tooltip/smart-tooltip.tsx +40 -0
- package/src/components/tooltip/tooltip.test.tsx +86 -0
- package/src/components/tooltip/tooltip.tsx +30 -0
- package/src/components/types.ts +1 -0
- package/src/components/zoom-controls/styles.ts +27 -0
- package/src/components/zoom-controls/types.ts +19 -0
- package/src/components/zoom-controls/zoom-controls.test.tsx +101 -0
- package/src/components/zoom-controls/zoom-controls.tsx +114 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-debounce.ts +55 -0
- package/src/hooks/use-skeleton.test.tsx +32 -0
- package/src/hooks/use-skeleton.ts +19 -0
- package/src/hooks/use-widget-ref.ts +33 -0
- package/src/widgets/README.md +277 -0
- package/src/widgets/_shared/chart-config/config-factory.ts +67 -0
- package/src/widgets/_shared/chart-config/csv-modifiers.ts +56 -0
- package/src/widgets/_shared/chart-config/index.ts +21 -0
- package/src/widgets/_shared/chart-config/option-builders.ts +203 -0
- package/src/widgets/_shared/skeleton/index.ts +5 -0
- package/src/widgets/_shared/skeleton/styles.ts +20 -0
- package/src/widgets/actions/change-column/change-column-icon.tsx +10 -0
- package/src/widgets/actions/change-column/change-column.test.tsx +163 -0
- package/src/widgets/actions/change-column/change-column.tsx +141 -0
- package/src/widgets/actions/change-column/sortable-column-item.tsx +49 -0
- package/src/widgets/actions/change-column/types.ts +20 -0
- package/src/widgets/actions/download/download.test.tsx +322 -0
- package/src/widgets/actions/download/download.tsx +118 -0
- package/src/widgets/actions/download/exports.test.tsx +275 -0
- package/src/widgets/actions/download/exports.tsx +103 -0
- package/src/widgets/actions/download/types.ts +21 -0
- package/src/widgets/actions/fullscreen/fullscreen.test.tsx +269 -0
- package/src/widgets/actions/fullscreen/fullscreen.tsx +82 -0
- package/src/widgets/actions/fullscreen/styles.ts +17 -0
- package/src/widgets/actions/fullscreen/types.ts +27 -0
- package/src/widgets/actions/index.ts +51 -0
- package/src/widgets/actions/lock-selection/lock-selection.test.tsx +186 -0
- package/src/widgets/actions/lock-selection/lock-selection.tsx +133 -0
- package/src/widgets/actions/lock-selection/types.ts +41 -0
- package/src/widgets/actions/relative-data/relative-data.test.tsx +267 -0
- package/src/widgets/actions/relative-data/relative-data.tsx +133 -0
- package/src/widgets/actions/relative-data/style.ts +9 -0
- package/src/widgets/actions/relative-data/types.ts +31 -0
- package/src/widgets/actions/relative-data/utils.test.ts +223 -0
- package/src/widgets/actions/relative-data/utils.ts +58 -0
- package/src/widgets/actions/searcher/searcher-toggle.test.tsx +354 -0
- package/src/widgets/actions/searcher/searcher-toggle.tsx +73 -0
- package/src/widgets/actions/searcher/searcher.tsx +205 -0
- package/src/widgets/actions/searcher/types.ts +72 -0
- package/src/widgets/actions/shared/styles.ts +12 -0
- package/src/widgets/actions/stack-toggle/grouped-bar-chart-icon.tsx +14 -0
- package/src/widgets/actions/stack-toggle/stack-toggle.test.tsx +270 -0
- package/src/widgets/actions/stack-toggle/stack-toggle.tsx +146 -0
- package/src/widgets/actions/stack-toggle/types.ts +29 -0
- package/src/widgets/actions/zoom-toggle/index.ts +2 -0
- package/src/widgets/actions/zoom-toggle/style.ts +14 -0
- package/src/widgets/actions/zoom-toggle/types.ts +44 -0
- package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +186 -0
- package/src/widgets/bar/config.ts +122 -0
- package/src/widgets/bar/index.ts +9 -0
- package/src/widgets/bar/skeleton.tsx +60 -0
- package/src/widgets/bar/style.ts +33 -0
- package/src/widgets/bar/types.ts +16 -0
- package/src/widgets/category/category-ui.test.tsx +399 -0
- package/src/widgets/category/category-ui.tsx +156 -0
- package/src/widgets/category/components/category-bar.tsx +28 -0
- package/src/widgets/category/components/category-legend.tsx +30 -0
- package/src/widgets/category/components/category-row-multi.tsx +50 -0
- package/src/widgets/category/components/category-row-other.tsx +23 -0
- package/src/widgets/category/components/category-row-single.tsx +47 -0
- package/src/widgets/category/components/index.ts +14 -0
- package/src/widgets/category/config.ts +85 -0
- package/src/widgets/category/index.ts +30 -0
- package/src/widgets/category/skeleton.tsx +24 -0
- package/src/widgets/category/style.ts +133 -0
- package/src/widgets/category/types.ts +40 -0
- package/src/widgets/echart/const.ts +1 -0
- package/src/widgets/echart/echart-ui.test.tsx +537 -0
- package/src/widgets/echart/echart-ui.tsx +92 -0
- package/src/widgets/echart/echart.test.tsx +562 -0
- package/src/widgets/echart/echart.tsx +68 -0
- package/src/widgets/echart/index.ts +16 -0
- package/src/widgets/echart/options.ts +53 -0
- package/src/widgets/echart/types.ts +45 -0
- package/src/widgets/echart/utils.ts +169 -0
- package/src/widgets/error/error.test.tsx +331 -0
- package/src/widgets/error/error.tsx +40 -0
- package/src/widgets/error/index.ts +2 -0
- package/src/widgets/error/types.ts +14 -0
- package/src/widgets/formula/components/item.test.tsx +249 -0
- package/src/widgets/formula/components/item.tsx +18 -0
- package/src/widgets/formula/components/prefix.test.tsx +341 -0
- package/src/widgets/formula/components/prefix.tsx +18 -0
- package/src/widgets/formula/components/row.test.tsx +364 -0
- package/src/widgets/formula/components/row.tsx +21 -0
- package/src/widgets/formula/components/series.tsx +34 -0
- package/src/widgets/formula/components/suffix.test.tsx +383 -0
- package/src/widgets/formula/components/suffix.tsx +28 -0
- package/src/widgets/formula/components/value.test.tsx +329 -0
- package/src/widgets/formula/components/value.tsx +29 -0
- package/src/widgets/formula/config.ts +27 -0
- package/src/widgets/formula/formula-ui.test.tsx +399 -0
- package/src/widgets/formula/formula-ui.tsx +27 -0
- package/src/widgets/formula/index.ts +24 -0
- package/src/widgets/formula/serializer.test.tsx +144 -0
- package/src/widgets/formula/serializer.ts +28 -0
- package/src/widgets/formula/skeleton.tsx +10 -0
- package/src/widgets/formula/style.ts +23 -0
- package/src/widgets/formula/types.ts +50 -0
- package/src/widgets/histogram/config.ts +143 -0
- package/src/widgets/histogram/index.ts +8 -0
- package/src/widgets/histogram/skeleton.tsx +52 -0
- package/src/widgets/histogram/style.ts +8 -0
- package/src/widgets/histogram/types.ts +17 -0
- package/src/widgets/index.ts +25 -0
- package/src/widgets/loader/index.ts +4 -0
- package/src/widgets/loader/loader.tsx +70 -0
- package/src/widgets/loader/types.ts +11 -0
- package/src/widgets/loader/utils.test.ts +112 -0
- package/src/widgets/loader/utils.ts +35 -0
- package/src/widgets/markdown/config.ts +18 -0
- package/src/widgets/markdown/index.ts +14 -0
- package/src/widgets/markdown/markdown-ui.test.tsx +341 -0
- package/src/widgets/markdown/markdown-ui.tsx +52 -0
- package/src/widgets/markdown/markdown.tsx +20 -0
- package/src/widgets/markdown/skeleton.tsx +12 -0
- package/src/widgets/markdown/style.ts +28 -0
- package/src/widgets/markdown/types.ts +28 -0
- package/src/widgets/no-data/index.ts +2 -0
- package/src/widgets/no-data/no-data.test.tsx +447 -0
- package/src/widgets/no-data/no-data.tsx +116 -0
- package/src/widgets/no-data/style.ts +18 -0
- package/src/widgets/no-data/types.ts +72 -0
- package/src/widgets/note/index.ts +2 -0
- package/src/widgets/note/note.test.tsx +391 -0
- package/src/widgets/note/note.tsx +114 -0
- package/src/widgets/note/style.ts +29 -0
- package/src/widgets/note/types.ts +9 -0
- package/src/widgets/pie/config.ts +177 -0
- package/src/widgets/pie/index.ts +8 -0
- package/src/widgets/pie/skeleton.tsx +70 -0
- package/src/widgets/pie/style.ts +8 -0
- package/src/widgets/pie/types.ts +16 -0
- package/src/widgets/range/components/range-item.tsx +213 -0
- package/src/widgets/range/config.ts +10 -0
- package/src/widgets/range/index.ts +16 -0
- package/src/widgets/range/range-ui.test.tsx +203 -0
- package/src/widgets/range/range-ui.tsx +11 -0
- package/src/widgets/range/serializer.test.ts +70 -0
- package/src/widgets/range/serializer.ts +27 -0
- package/src/widgets/range/skeleton.tsx +14 -0
- package/src/widgets/range/style.ts +37 -0
- package/src/widgets/range/types.ts +39 -0
- package/src/widgets/scatterplot/config.ts +138 -0
- package/src/widgets/scatterplot/index.ts +8 -0
- package/src/widgets/scatterplot/skeleton.tsx +59 -0
- package/src/widgets/scatterplot/style.ts +21 -0
- package/src/widgets/scatterplot/types.ts +17 -0
- package/src/widgets/selection-summary/index.ts +6 -0
- package/src/widgets/selection-summary/selection-summary.tsx +46 -0
- package/src/widgets/selection-summary/style.ts +10 -0
- package/src/widgets/selection-summary/types.ts +14 -0
- package/src/widgets/skeleton-loader/index.ts +2 -0
- package/src/widgets/skeleton-loader/skeleton-loader.test.tsx +139 -0
- package/src/widgets/skeleton-loader/skeleton-loader.tsx +28 -0
- package/src/widgets/skeleton-loader/types.ts +8 -0
- package/src/widgets/spread/components/max-value.tsx +29 -0
- package/src/widgets/spread/components/min-value.tsx +29 -0
- package/src/widgets/spread/components/separator.tsx +6 -0
- package/src/widgets/spread/config.ts +34 -0
- package/src/widgets/spread/index.ts +23 -0
- package/src/widgets/spread/skeleton.tsx +10 -0
- package/src/widgets/spread/spread-ui.test.tsx +368 -0
- package/src/widgets/spread/spread-ui.tsx +29 -0
- package/src/widgets/spread/style.ts +22 -0
- package/src/widgets/spread/types.ts +47 -0
- package/src/widgets/stores/index.ts +9 -0
- package/src/widgets/stores/types.ts +192 -0
- package/src/widgets/stores/widget-store.test.ts +601 -0
- package/src/widgets/stores/widget-store.ts +239 -0
- package/src/widgets/subheader/index.ts +3 -0
- package/src/widgets/subheader/style.ts +20 -0
- package/src/widgets/subheader/subheader.test.tsx +45 -0
- package/src/widgets/subheader/subheader.tsx +16 -0
- package/src/widgets/subheader/types.ts +11 -0
- package/src/widgets/table/components/cell-header.tsx +58 -0
- package/src/widgets/table/components/cell.tsx +80 -0
- package/src/widgets/table/components/index.ts +4 -0
- package/src/widgets/table/components/pagination-actions.tsx +67 -0
- package/src/widgets/table/components/pagination.tsx +41 -0
- package/src/widgets/table/components/row.tsx +60 -0
- package/src/widgets/table/config.ts +71 -0
- package/src/widgets/table/helpers.test.ts +244 -0
- package/src/widgets/table/helpers.ts +107 -0
- package/src/widgets/table/hooks/index.ts +7 -0
- package/src/widgets/table/hooks/use-pagination.test.ts +294 -0
- package/src/widgets/table/hooks/use-pagination.ts +155 -0
- package/src/widgets/table/hooks/use-selection.test.ts +504 -0
- package/src/widgets/table/hooks/use-selection.ts +189 -0
- package/src/widgets/table/hooks/use-sort.test.ts +296 -0
- package/src/widgets/table/hooks/use-sort.ts +138 -0
- package/src/widgets/table/index.ts +53 -0
- package/src/widgets/table/serializer.ts +54 -0
- package/src/widgets/table/skeleton.tsx +48 -0
- package/src/widgets/table/style.ts +34 -0
- package/src/widgets/table/table-ui.tsx +64 -0
- package/src/widgets/table/types.ts +223 -0
- package/src/widgets/timeseries/config.ts +135 -0
- package/src/widgets/timeseries/index.ts +8 -0
- package/src/widgets/timeseries/skeleton.tsx +55 -0
- package/src/widgets/timeseries/style.ts +36 -0
- package/src/widgets/timeseries/types.ts +17 -0
- package/src/widgets/toolbar-actions/index.ts +6 -0
- package/src/widgets/toolbar-actions/styles.ts +38 -0
- package/src/widgets/toolbar-actions/toolbar-actions.test.tsx +691 -0
- package/src/widgets/toolbar-actions/toolbar-actions.tsx +145 -0
- package/src/widgets/toolbar-actions/types.ts +60 -0
- package/src/widgets/wrapper/components/actions.test.tsx +101 -0
- package/src/widgets/wrapper/components/actions.tsx +30 -0
- package/src/widgets/wrapper/components/options.test.tsx +323 -0
- package/src/widgets/wrapper/components/options.tsx +73 -0
- package/src/widgets/wrapper/components/title.test.tsx +126 -0
- package/src/widgets/wrapper/components/title.tsx +32 -0
- package/src/widgets/wrapper/index.ts +16 -0
- package/src/widgets/wrapper/styles.ts +98 -0
- package/src/widgets/wrapper/types.ts +55 -0
- package/src/widgets/wrapper/wrapper-ui.test.tsx +232 -0
- package/src/widgets/wrapper/wrapper-ui.tsx +57 -0
- package/src/widgets/wrapper/wrapper.test.tsx +365 -0
- package/src/widgets/wrapper/wrapper.tsx +50 -0
- package/dist/lasso-tool-BwRzEW7k.js.map +0 -1
- package/dist/types/common/common.d.ts +0 -3
- package/dist/types/common/index.d.ts +0 -26
- package/dist/types/common/lasso-tools.d.ts +0 -36
- package/dist/types/common/measurement-tools.d.ts +0 -65
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
3
|
+
import { LockSelection } from './lock-selection'
|
|
4
|
+
import { useWidgetStore } from '../../stores/widget-store'
|
|
5
|
+
import type { EchartWidgetData } from '../../echart/types'
|
|
6
|
+
import type { LockSelectionState } from './types'
|
|
7
|
+
import { WidgetLoader } from '../../loader/loader'
|
|
8
|
+
|
|
9
|
+
// Test data
|
|
10
|
+
const mockData: EchartWidgetData = [
|
|
11
|
+
[
|
|
12
|
+
{ name: 'Electronics', value: 440 },
|
|
13
|
+
{ name: 'Clothing', value: 280 },
|
|
14
|
+
{ name: 'Books', value: 100 },
|
|
15
|
+
],
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
describe('LockSelection', () => {
|
|
19
|
+
const widgetId = 'test-lock-selection'
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
useWidgetStore.getState().clearWidgets()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('renders nothing when no selections', () => {
|
|
26
|
+
const { container } = render(
|
|
27
|
+
<LockSelection id={widgetId} selectedItems={[]} />,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
expect(container.firstChild).toBeNull()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('renders lock button when there are selections', () => {
|
|
34
|
+
render(<LockSelection id={widgetId} selectedItems={['Electronics']} />)
|
|
35
|
+
|
|
36
|
+
const button = screen.getByRole('button')
|
|
37
|
+
expect(button).toBeTruthy()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('shows lock tooltip when unlocked', () => {
|
|
41
|
+
render(<LockSelection id={widgetId} selectedItems={['Electronics']} />)
|
|
42
|
+
|
|
43
|
+
const button = screen.getByRole('button', { name: 'Lock selection' })
|
|
44
|
+
expect(button).toBeTruthy()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('shows unlock tooltip when locked', () => {
|
|
48
|
+
// Pre-set the store to locked state
|
|
49
|
+
useWidgetStore.getState().setWidget(widgetId, { isLocked: true })
|
|
50
|
+
|
|
51
|
+
render(<LockSelection id={widgetId} selectedItems={['Electronics']} />)
|
|
52
|
+
|
|
53
|
+
const button = screen.getByRole('button', { name: 'Unlock selection' })
|
|
54
|
+
expect(button).toBeTruthy()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('toggles isLocked in store when clicked', () => {
|
|
58
|
+
render(<LockSelection id={widgetId} selectedItems={['Electronics']} />)
|
|
59
|
+
|
|
60
|
+
const button = screen.getByRole('button')
|
|
61
|
+
fireEvent.click(button)
|
|
62
|
+
|
|
63
|
+
const widget = useWidgetStore
|
|
64
|
+
.getState()
|
|
65
|
+
.getWidget<LockSelectionState>(widgetId)
|
|
66
|
+
expect(widget?.isLocked).toBe(true)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('filters data in store when locking', async () => {
|
|
70
|
+
const selectedItems = ['Electronics']
|
|
71
|
+
|
|
72
|
+
render(
|
|
73
|
+
<WidgetLoader
|
|
74
|
+
id={widgetId}
|
|
75
|
+
type='test'
|
|
76
|
+
data={mockData}
|
|
77
|
+
isLoading={false}
|
|
78
|
+
isFetching={false}
|
|
79
|
+
>
|
|
80
|
+
<LockSelection id={widgetId} selectedItems={selectedItems} />
|
|
81
|
+
</WidgetLoader>,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const button = screen.getByRole('button')
|
|
85
|
+
fireEvent.click(button)
|
|
86
|
+
|
|
87
|
+
await useWidgetStore.getState().executeToolPipeline(widgetId, mockData)
|
|
88
|
+
|
|
89
|
+
const widget = useWidgetStore
|
|
90
|
+
.getState()
|
|
91
|
+
.getWidget<LockSelectionState>(widgetId)
|
|
92
|
+
expect(widget?.data).toEqual([[{ name: 'Electronics', value: 440 }]])
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('clears locked items when unlocking', () => {
|
|
96
|
+
// Pre-set the store to locked state
|
|
97
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
98
|
+
isLocked: true,
|
|
99
|
+
lockedItems: ['Electronics'],
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
render(<LockSelection id={widgetId} selectedItems={['Electronics']} />)
|
|
103
|
+
|
|
104
|
+
const button = screen.getByRole('button')
|
|
105
|
+
fireEvent.click(button)
|
|
106
|
+
|
|
107
|
+
const widget = useWidgetStore
|
|
108
|
+
.getState()
|
|
109
|
+
.getWidget<LockSelectionState>(widgetId)
|
|
110
|
+
expect(widget?.isLocked).toBe(false)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('has active state when locked', () => {
|
|
114
|
+
useWidgetStore.getState().setWidget(widgetId, { isLocked: true })
|
|
115
|
+
|
|
116
|
+
render(<LockSelection id={widgetId} selectedItems={['Electronics']} />)
|
|
117
|
+
|
|
118
|
+
const button = screen.getByRole('button')
|
|
119
|
+
expect(button.getAttribute('data-active')).toBe('true')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('has inactive state when unlocked', () => {
|
|
123
|
+
render(<LockSelection id={widgetId} selectedItems={['Electronics']} />)
|
|
124
|
+
|
|
125
|
+
const button = screen.getByRole('button')
|
|
126
|
+
expect(button.getAttribute('data-active')).toBe('false')
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('uses custom labels', () => {
|
|
130
|
+
render(
|
|
131
|
+
<LockSelection
|
|
132
|
+
id={widgetId}
|
|
133
|
+
selectedItems={['Electronics']}
|
|
134
|
+
labels={{
|
|
135
|
+
lock: 'Bloquear selección',
|
|
136
|
+
unlock: 'Desbloquear selección',
|
|
137
|
+
ariaLabel: 'Toggle lock',
|
|
138
|
+
}}
|
|
139
|
+
/>,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
const button = screen.getByRole('button', { name: 'Toggle lock' })
|
|
143
|
+
expect(button).toBeTruthy()
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('filters multiple series when locking', async () => {
|
|
147
|
+
const multiSeriesData: EchartWidgetData = [
|
|
148
|
+
[
|
|
149
|
+
{ name: 'Electronics', value: 440 },
|
|
150
|
+
{ name: 'Clothing', value: 280 },
|
|
151
|
+
],
|
|
152
|
+
[
|
|
153
|
+
{ name: 'Electronics', value: 520 },
|
|
154
|
+
{ name: 'Clothing', value: 350 },
|
|
155
|
+
],
|
|
156
|
+
]
|
|
157
|
+
const selectedItems = ['Electronics']
|
|
158
|
+
|
|
159
|
+
render(
|
|
160
|
+
<WidgetLoader
|
|
161
|
+
id={widgetId}
|
|
162
|
+
type='test'
|
|
163
|
+
data={multiSeriesData}
|
|
164
|
+
isLoading={false}
|
|
165
|
+
isFetching={false}
|
|
166
|
+
>
|
|
167
|
+
<LockSelection id={widgetId} selectedItems={selectedItems} />
|
|
168
|
+
</WidgetLoader>,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
const button = screen.getByRole('button')
|
|
172
|
+
fireEvent.click(button)
|
|
173
|
+
|
|
174
|
+
await useWidgetStore
|
|
175
|
+
.getState()
|
|
176
|
+
.executeToolPipeline(widgetId, multiSeriesData)
|
|
177
|
+
|
|
178
|
+
const widget = useWidgetStore
|
|
179
|
+
.getState()
|
|
180
|
+
.getWidget<LockSelectionState>(widgetId)
|
|
181
|
+
expect(widget?.data).toEqual([
|
|
182
|
+
[{ name: 'Electronics', value: 440 }],
|
|
183
|
+
[{ name: 'Electronics', value: 520 }],
|
|
184
|
+
])
|
|
185
|
+
})
|
|
186
|
+
})
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { IconButton } from '@mui/material'
|
|
2
|
+
import { CheckBoxOutlined } from '@mui/icons-material'
|
|
3
|
+
import { useCallback, useEffect, useMemo } from 'react'
|
|
4
|
+
import type { LockSelectionProps, LockSelectionState } from './types'
|
|
5
|
+
import { actionButtonStyles } from '../shared/styles'
|
|
6
|
+
import { Tooltip } from '../../../components'
|
|
7
|
+
import { useWidgetStore } from '../../stores/widget-store'
|
|
8
|
+
import { useShallow } from 'zustand/shallow'
|
|
9
|
+
import type { EchartWidgetData } from '../../echart/types'
|
|
10
|
+
|
|
11
|
+
export const LOCK_SELECTION_TOOL_ID = 'lock-selection'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Widget action button to lock the current selection.
|
|
15
|
+
*
|
|
16
|
+
* When locked, the widget data is filtered to only show the selected items.
|
|
17
|
+
* The button is only visible when there are selected items.
|
|
18
|
+
* Registers a transformation tool in the widget pipeline when mounted.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <LockSelection
|
|
23
|
+
* id="my-widget"
|
|
24
|
+
* order={30}
|
|
25
|
+
* selectedItems={selectedItems}
|
|
26
|
+
* />
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function LockSelection({
|
|
30
|
+
id,
|
|
31
|
+
selectedItems,
|
|
32
|
+
order = 30,
|
|
33
|
+
labels,
|
|
34
|
+
Icon,
|
|
35
|
+
IconButtonProps,
|
|
36
|
+
}: LockSelectionProps) {
|
|
37
|
+
const setWidget = useWidgetStore((state) => state.setWidget)
|
|
38
|
+
const registerTool = useWidgetStore((state) => state.registerTool)
|
|
39
|
+
const unregisterTool = useWidgetStore((state) => state.unregisterTool)
|
|
40
|
+
const setToolEnabled = useWidgetStore((state) => state.setToolEnabled)
|
|
41
|
+
const updateToolConfig = useWidgetStore((state) => state.updateToolConfig)
|
|
42
|
+
|
|
43
|
+
const storeIsLocked = useWidgetStore(
|
|
44
|
+
useShallow((state) => state.getWidget<LockSelectionState>(id)?.isLocked),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
const isLocked = storeIsLocked ?? false
|
|
48
|
+
const lockedItems = useMemo(
|
|
49
|
+
() => (isLocked ? selectedItems : []),
|
|
50
|
+
[isLocked, selectedItems],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
// Register tool on mount
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
registerTool(id, {
|
|
56
|
+
id: LOCK_SELECTION_TOOL_ID,
|
|
57
|
+
order,
|
|
58
|
+
enabled: isLocked,
|
|
59
|
+
fn: (data, config) => {
|
|
60
|
+
const items = (config?.lockedItems as string[]) || []
|
|
61
|
+
if (items.length === 0) return data
|
|
62
|
+
|
|
63
|
+
return filterDataByLockedItems(data as EchartWidgetData, items)
|
|
64
|
+
},
|
|
65
|
+
config: { lockedItems },
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
return () => unregisterTool(id, LOCK_SELECTION_TOOL_ID)
|
|
69
|
+
}, [id, order, registerTool, unregisterTool, isLocked, lockedItems])
|
|
70
|
+
|
|
71
|
+
// Update enabled flag and config when they change
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
setToolEnabled(id, LOCK_SELECTION_TOOL_ID, isLocked)
|
|
74
|
+
updateToolConfig(id, LOCK_SELECTION_TOOL_ID, { lockedItems })
|
|
75
|
+
}, [id, isLocked, lockedItems, setToolEnabled, updateToolConfig])
|
|
76
|
+
|
|
77
|
+
const handleToggle = useCallback(() => {
|
|
78
|
+
if (isLocked) {
|
|
79
|
+
// Unlock: clear locked items and disable tool
|
|
80
|
+
setWidget(id, {
|
|
81
|
+
isLocked: false,
|
|
82
|
+
lockedItems: [],
|
|
83
|
+
})
|
|
84
|
+
} else {
|
|
85
|
+
// Lock: save selected items and enable tool
|
|
86
|
+
setWidget(id, {
|
|
87
|
+
isLocked: true,
|
|
88
|
+
lockedItems: selectedItems,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
}, [id, isLocked, selectedItems, setWidget])
|
|
92
|
+
|
|
93
|
+
// Don't render if no selections
|
|
94
|
+
if (selectedItems.length === 0) {
|
|
95
|
+
return null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const tooltipLabel = isLocked
|
|
99
|
+
? (labels?.unlock ?? 'Unlock selection')
|
|
100
|
+
: (labels?.lock ?? 'Lock selection')
|
|
101
|
+
|
|
102
|
+
const ariaLabel = labels?.ariaLabel ?? tooltipLabel
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<Tooltip title={tooltipLabel}>
|
|
106
|
+
<IconButton
|
|
107
|
+
size='small'
|
|
108
|
+
aria-label={ariaLabel}
|
|
109
|
+
onClick={handleToggle}
|
|
110
|
+
sx={actionButtonStyles.trigger}
|
|
111
|
+
data-active={isLocked}
|
|
112
|
+
{...IconButtonProps}
|
|
113
|
+
>
|
|
114
|
+
{Icon ?? <CheckBoxOutlined fontSize='small' />}
|
|
115
|
+
</IconButton>
|
|
116
|
+
</Tooltip>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Filters widget data to only include items with names matching the locked items.
|
|
122
|
+
*/
|
|
123
|
+
function filterDataByLockedItems(
|
|
124
|
+
data: EchartWidgetData,
|
|
125
|
+
lockedItems: string[],
|
|
126
|
+
): EchartWidgetData {
|
|
127
|
+
return data.map((series) =>
|
|
128
|
+
series.filter((item) => {
|
|
129
|
+
const name = item.name
|
|
130
|
+
return typeof name === 'string' && lockedItems.includes(name)
|
|
131
|
+
}),
|
|
132
|
+
)
|
|
133
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { IconButtonProps } from '@mui/material'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import type { BaseWidgetState } from '../../stores/types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lock selection specific state properties.
|
|
7
|
+
*/
|
|
8
|
+
export interface LockSelectionStateProps {
|
|
9
|
+
/** Whether the selection is currently locked */
|
|
10
|
+
isLocked?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Widget state extension for lock selection functionality.
|
|
15
|
+
* Extends the base widget state with lock-specific properties.
|
|
16
|
+
*/
|
|
17
|
+
export type LockSelectionState<T = object> = BaseWidgetState<
|
|
18
|
+
T & LockSelectionStateProps
|
|
19
|
+
>
|
|
20
|
+
|
|
21
|
+
export interface LockSelectionProps {
|
|
22
|
+
/** Widget ID to store lock selection state */
|
|
23
|
+
id: string
|
|
24
|
+
/** Currently selected items (by name) */
|
|
25
|
+
selectedItems: string[]
|
|
26
|
+
/** Execution order in the tool pipeline. Lower values execute first. Defaults to 30. */
|
|
27
|
+
order?: number
|
|
28
|
+
/** Custom labels for the action */
|
|
29
|
+
labels?: {
|
|
30
|
+
/** Tooltip when unlocked (button will lock selection) */
|
|
31
|
+
lock?: string
|
|
32
|
+
/** Tooltip when locked (button will unlock selection) */
|
|
33
|
+
unlock?: string
|
|
34
|
+
/** Accessibility label */
|
|
35
|
+
ariaLabel?: string
|
|
36
|
+
}
|
|
37
|
+
/** Props passed to the IconButton component */
|
|
38
|
+
IconButtonProps?: IconButtonProps
|
|
39
|
+
/** Custom icon to display */
|
|
40
|
+
Icon?: ReactNode
|
|
41
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
3
|
+
import { RelativeData } from './relative-data'
|
|
4
|
+
import { useWidgetStore } from '../../stores/widget-store'
|
|
5
|
+
import type { EchartWidgetData } from '../../echart/types'
|
|
6
|
+
import type { RelativeDataState } from './types'
|
|
7
|
+
|
|
8
|
+
describe('RelativeData', () => {
|
|
9
|
+
const widgetId = 'test-relative-widget'
|
|
10
|
+
|
|
11
|
+
const mockData: EchartWidgetData = [
|
|
12
|
+
[
|
|
13
|
+
{ category: 'A', value: 25 },
|
|
14
|
+
{ category: 'B', value: 75 },
|
|
15
|
+
],
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
useWidgetStore.getState().clearWidgets()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('renders relative data toggle button', () => {
|
|
23
|
+
render(<RelativeData id={widgetId} />)
|
|
24
|
+
|
|
25
|
+
const button = screen.getByRole('button')
|
|
26
|
+
expect(button).toBeTruthy()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('shows relative tooltip when in absolute mode', () => {
|
|
30
|
+
render(<RelativeData id={widgetId} />)
|
|
31
|
+
|
|
32
|
+
const button = screen.getByRole('button', { name: 'Show relative values' })
|
|
33
|
+
expect(button).toBeTruthy()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('toggles to relative mode and updates widget store with percentage values', async () => {
|
|
37
|
+
render(<RelativeData id={widgetId} />)
|
|
38
|
+
|
|
39
|
+
const button = screen.getByRole('button')
|
|
40
|
+
fireEvent.click(button)
|
|
41
|
+
|
|
42
|
+
// Execute the pipeline with the source data
|
|
43
|
+
await useWidgetStore.getState().executeToolPipeline(widgetId, mockData)
|
|
44
|
+
|
|
45
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
46
|
+
const data = widget?.data as EchartWidgetData
|
|
47
|
+
|
|
48
|
+
// Total is 100, so 25 becomes 25% and 75 becomes 75%
|
|
49
|
+
expect(data?.[0]?.[0]?.value).toBe(25)
|
|
50
|
+
expect(data?.[0]?.[1]?.value).toBe(75)
|
|
51
|
+
expect(data?.[0]?.[0]?.category).toBe('A')
|
|
52
|
+
expect(data?.[0]?.[1]?.category).toBe('B')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('toggles back to absolute mode and restores original values', async () => {
|
|
56
|
+
render(<RelativeData id={widgetId} />)
|
|
57
|
+
|
|
58
|
+
const button = screen.getByRole('button')
|
|
59
|
+
|
|
60
|
+
// Toggle to relative
|
|
61
|
+
fireEvent.click(button)
|
|
62
|
+
await useWidgetStore.getState().executeToolPipeline(widgetId, mockData)
|
|
63
|
+
|
|
64
|
+
// Toggle back to absolute
|
|
65
|
+
fireEvent.click(button)
|
|
66
|
+
await useWidgetStore.getState().executeToolPipeline(widgetId, mockData)
|
|
67
|
+
|
|
68
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
69
|
+
const data = widget?.data as EchartWidgetData
|
|
70
|
+
|
|
71
|
+
expect(data?.[0]?.[0]?.value).toBe(25)
|
|
72
|
+
expect(data?.[0]?.[1]?.value).toBe(75)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('has active state when in relative mode', () => {
|
|
76
|
+
render(<RelativeData id={widgetId} />)
|
|
77
|
+
|
|
78
|
+
const button = screen.getByRole('button')
|
|
79
|
+
|
|
80
|
+
expect(button.getAttribute('data-active')).toBe('false')
|
|
81
|
+
|
|
82
|
+
fireEvent.click(button)
|
|
83
|
+
|
|
84
|
+
expect(button.getAttribute('data-active')).toBe('true')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('starts in relative mode when defaultIsRelative is true', () => {
|
|
88
|
+
render(<RelativeData id={widgetId} defaultIsRelative />)
|
|
89
|
+
|
|
90
|
+
const button = screen.getByRole('button')
|
|
91
|
+
expect(button.getAttribute('data-active')).toBe('true')
|
|
92
|
+
|
|
93
|
+
// Should show absolute tooltip when in relative mode
|
|
94
|
+
expect(button.getAttribute('aria-label')).toBe('Show absolute values')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('initializes widget store with defaultIsRelative on mount', async () => {
|
|
98
|
+
render(<RelativeData id={widgetId} defaultIsRelative />)
|
|
99
|
+
|
|
100
|
+
await waitFor(() => {
|
|
101
|
+
const widget = useWidgetStore
|
|
102
|
+
.getState()
|
|
103
|
+
.getWidget<RelativeDataState>(widgetId)
|
|
104
|
+
expect(widget?.isRelative).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('initializes widget store with false when defaultIsRelative is false', async () => {
|
|
109
|
+
render(<RelativeData id={widgetId} defaultIsRelative={false} />)
|
|
110
|
+
|
|
111
|
+
await waitFor(() => {
|
|
112
|
+
const widget = useWidgetStore
|
|
113
|
+
.getState()
|
|
114
|
+
.getWidget<RelativeDataState>(widgetId)
|
|
115
|
+
expect(widget?.isRelative).toBe(false)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('uses custom labels when provided', () => {
|
|
120
|
+
const customLabels = {
|
|
121
|
+
relative: 'Switch to %',
|
|
122
|
+
absolute: 'Switch to numbers',
|
|
123
|
+
ariaLabel: 'Toggle data mode',
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
render(<RelativeData id={widgetId} labels={customLabels} />)
|
|
127
|
+
|
|
128
|
+
const button = screen.getByRole('button', { name: 'Toggle data mode' })
|
|
129
|
+
expect(button).toBeTruthy()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test('handles data with multiple series', async () => {
|
|
133
|
+
const multiSeriesData: EchartWidgetData = [
|
|
134
|
+
[
|
|
135
|
+
{ name: 'X', count: 10 },
|
|
136
|
+
{ name: 'Y', count: 20 },
|
|
137
|
+
],
|
|
138
|
+
[
|
|
139
|
+
{ name: 'X', count: 30 },
|
|
140
|
+
{ name: 'Y', count: 40 },
|
|
141
|
+
],
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
render(<RelativeData id={widgetId} />)
|
|
145
|
+
|
|
146
|
+
const button = screen.getByRole('button')
|
|
147
|
+
fireEvent.click(button)
|
|
148
|
+
|
|
149
|
+
await useWidgetStore
|
|
150
|
+
.getState()
|
|
151
|
+
.executeToolPipeline(widgetId, multiSeriesData)
|
|
152
|
+
|
|
153
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
154
|
+
const data = widget?.data as EchartWidgetData
|
|
155
|
+
|
|
156
|
+
// Total is 100, so values become 10%, 20%, 30%, 40%
|
|
157
|
+
expect(data?.[0]?.[0]?.count).toBe(10)
|
|
158
|
+
expect(data?.[0]?.[1]?.count).toBe(20)
|
|
159
|
+
expect(data?.[1]?.[0]?.count).toBe(30)
|
|
160
|
+
expect(data?.[1]?.[1]?.count).toBe(40)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test('handles zero total gracefully', async () => {
|
|
164
|
+
const zeroData: EchartWidgetData = [
|
|
165
|
+
[
|
|
166
|
+
{ category: 'A', value: 0 },
|
|
167
|
+
{ category: 'B', value: 0 },
|
|
168
|
+
],
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
render(<RelativeData id={widgetId} />)
|
|
172
|
+
|
|
173
|
+
const button = screen.getByRole('button')
|
|
174
|
+
fireEvent.click(button)
|
|
175
|
+
|
|
176
|
+
await useWidgetStore.getState().executeToolPipeline(widgetId, zeroData)
|
|
177
|
+
|
|
178
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
179
|
+
const data = widget?.data as EchartWidgetData
|
|
180
|
+
|
|
181
|
+
// Should preserve original data when total is 0
|
|
182
|
+
expect(data?.[0]?.[0]?.value).toBe(0)
|
|
183
|
+
expect(data?.[0]?.[1]?.value).toBe(0)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('preserves string values during transformation', async () => {
|
|
187
|
+
const mixedData: EchartWidgetData = [
|
|
188
|
+
[
|
|
189
|
+
{ label: 'First', value: 50, extra: 'text' },
|
|
190
|
+
{ label: 'Second', value: 50, extra: 'more' },
|
|
191
|
+
],
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
render(<RelativeData id={widgetId} />)
|
|
195
|
+
|
|
196
|
+
const button = screen.getByRole('button')
|
|
197
|
+
fireEvent.click(button)
|
|
198
|
+
|
|
199
|
+
await useWidgetStore.getState().executeToolPipeline(widgetId, mixedData)
|
|
200
|
+
|
|
201
|
+
const widget = useWidgetStore.getState().getWidget(widgetId)
|
|
202
|
+
const data = widget?.data as EchartWidgetData
|
|
203
|
+
|
|
204
|
+
expect(data?.[0]?.[0]?.label).toBe('First')
|
|
205
|
+
expect(data?.[0]?.[0]?.extra).toBe('text')
|
|
206
|
+
expect(data?.[0]?.[1]?.label).toBe('Second')
|
|
207
|
+
expect(data?.[0]?.[1]?.extra).toBe('more')
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test('renders custom icon when provided', () => {
|
|
211
|
+
const customIcon = <span data-testid='custom-icon'>%</span>
|
|
212
|
+
|
|
213
|
+
render(<RelativeData id={widgetId} Icon={customIcon} />)
|
|
214
|
+
|
|
215
|
+
expect(screen.getByTestId('custom-icon')).toBeTruthy()
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test('passes IconButtonProps to the button', () => {
|
|
219
|
+
render(<RelativeData id={widgetId} IconButtonProps={{ disabled: true }} />)
|
|
220
|
+
|
|
221
|
+
const button = screen.getByRole('button')
|
|
222
|
+
expect(button.hasAttribute('disabled')).toBeTruthy()
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test('recalculates relative values when store data changes externally while in relative mode', async () => {
|
|
226
|
+
const initialData: EchartWidgetData = [
|
|
227
|
+
[
|
|
228
|
+
{ category: 'A', value: 50 },
|
|
229
|
+
{ category: 'B', value: 50 },
|
|
230
|
+
],
|
|
231
|
+
]
|
|
232
|
+
|
|
233
|
+
// Pre-populate the widget in the store with relative data
|
|
234
|
+
useWidgetStore.getState().setWidget(widgetId, {
|
|
235
|
+
data: initialData,
|
|
236
|
+
isRelative: true,
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
render(<RelativeData id={widgetId} defaultIsRelative />)
|
|
240
|
+
|
|
241
|
+
let widget
|
|
242
|
+
let data
|
|
243
|
+
await waitFor(() => {
|
|
244
|
+
widget = useWidgetStore.getState().getWidget(widgetId)
|
|
245
|
+
data = widget?.data as EchartWidgetData
|
|
246
|
+
// Initial data: 50/100 = 50%, 50/100 = 50%
|
|
247
|
+
expect(data?.[0]?.[0]?.value).toBe(50)
|
|
248
|
+
expect(data?.[0]?.[1]?.value).toBe(50)
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
// Simulate external store update with new absolute data
|
|
252
|
+
const newAbsoluteData: EchartWidgetData = [
|
|
253
|
+
[
|
|
254
|
+
{ category: 'A', value: 25 },
|
|
255
|
+
{ category: 'B', value: 75 },
|
|
256
|
+
],
|
|
257
|
+
]
|
|
258
|
+
useWidgetStore.getState().setWidget(widgetId, { data: newAbsoluteData })
|
|
259
|
+
|
|
260
|
+
// Component should detect external change and recalculate
|
|
261
|
+
// New data: 25/100 = 25%, 75/100 = 75%
|
|
262
|
+
widget = useWidgetStore.getState().getWidget(widgetId)
|
|
263
|
+
data = widget?.data as EchartWidgetData
|
|
264
|
+
expect(data?.[0]?.[0]?.value).toBe(25)
|
|
265
|
+
expect(data?.[0]?.[1]?.value).toBe(75)
|
|
266
|
+
})
|
|
267
|
+
})
|