@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.
Files changed (301) hide show
  1. package/dist/components.js +3 -3
  2. package/dist/components.js.map +1 -1
  3. package/dist/{lasso-tool-BwRzEW7k.js → lasso-tool-wFqOD6wk.js} +13 -13
  4. package/dist/lasso-tool-wFqOD6wk.js.map +1 -0
  5. package/dist/types/components/common-types.d.ts +41 -0
  6. package/dist/types/components/types.d.ts +1 -1
  7. package/dist/types/widgets/echart/echart-ui.d.ts +1 -1
  8. package/dist/types/widgets/echart/types.d.ts +4 -0
  9. package/dist/widgets/actions.js +1 -1
  10. package/dist/widgets/bar.js +1 -1
  11. package/dist/widgets/category.js +1 -1
  12. package/dist/widgets/echart.js +96 -85
  13. package/dist/widgets/echart.js.map +1 -1
  14. package/dist/widgets/formula.js +1 -1
  15. package/dist/widgets/histogram.js +1 -1
  16. package/dist/widgets/markdown.js +1 -1
  17. package/dist/widgets/pie.js +1 -1
  18. package/dist/widgets/scatterplot.js +1 -1
  19. package/dist/widgets/spread.js +1 -1
  20. package/dist/widgets/table.js +1 -1
  21. package/dist/widgets/timeseries.js +1 -1
  22. package/dist/widgets/toolbar-actions.js.map +1 -1
  23. package/dist/widgets/wrapper.js +1 -1
  24. package/package.json +8 -3
  25. package/src/components/basemaps/basemaps.test.tsx +196 -0
  26. package/src/components/basemaps/basemaps.tsx +128 -0
  27. package/src/components/basemaps/const.ts +13 -0
  28. package/src/components/basemaps/group-wrapper.test.tsx +38 -0
  29. package/src/components/basemaps/group-wrapper.tsx +28 -0
  30. package/src/components/basemaps/group.test.tsx +52 -0
  31. package/src/components/basemaps/group.tsx +42 -0
  32. package/src/components/basemaps/header.test.tsx +54 -0
  33. package/src/components/basemaps/header.tsx +36 -0
  34. package/src/components/basemaps/styles.ts +76 -0
  35. package/src/components/basemaps/types.ts +30 -0
  36. package/src/components/common-types.ts +1 -0
  37. package/src/components/geolocation-controls/const.ts +6 -0
  38. package/src/components/geolocation-controls/geolocation-controls.test.tsx +133 -0
  39. package/src/components/geolocation-controls/geolocation-controls.tsx +95 -0
  40. package/src/components/geolocation-controls/types.ts +17 -0
  41. package/src/components/index.ts +64 -0
  42. package/src/components/lasso-tool/chip.tsx +37 -0
  43. package/src/components/lasso-tool/const.tsx +70 -0
  44. package/src/components/lasso-tool/icons.tsx +91 -0
  45. package/src/components/lasso-tool/lasso-tool-inline.test.tsx +168 -0
  46. package/src/components/lasso-tool/lasso-tool-inline.tsx +245 -0
  47. package/src/components/lasso-tool/lasso-tool.test.tsx +212 -0
  48. package/src/components/lasso-tool/lasso-tool.tsx +479 -0
  49. package/src/components/lasso-tool/styles.ts +143 -0
  50. package/src/components/lasso-tool/types.ts +114 -0
  51. package/src/components/list-data/list-data-skeleton.test.tsx +10 -0
  52. package/src/components/list-data/list-data-skeleton.tsx +40 -0
  53. package/src/components/list-data/list-data.test.tsx +94 -0
  54. package/src/components/list-data/list-data.tsx +106 -0
  55. package/src/components/list-data/styles.ts +37 -0
  56. package/src/components/list-data/types.ts +25 -0
  57. package/src/components/measurement-tools/const.tsx +108 -0
  58. package/src/components/measurement-tools/icons.tsx +54 -0
  59. package/src/components/measurement-tools/measurement-tools.test.tsx +165 -0
  60. package/src/components/measurement-tools/measurement-tools.tsx +443 -0
  61. package/src/components/measurement-tools/styles.ts +91 -0
  62. package/src/components/measurement-tools/types.ts +77 -0
  63. package/src/components/no-data-alert/no-data-alert.test.tsx +31 -0
  64. package/src/components/no-data-alert/no-data-alert.tsx +59 -0
  65. package/src/components/responsive-drawer/responsive-drawer.test.tsx +91 -0
  66. package/src/components/responsive-drawer/responsive-drawer.tsx +53 -0
  67. package/src/components/smart-tooltip/smart-tooltip.test.tsx +168 -0
  68. package/src/components/smart-tooltip/smart-tooltip.tsx +40 -0
  69. package/src/components/tooltip/tooltip.test.tsx +86 -0
  70. package/src/components/tooltip/tooltip.tsx +30 -0
  71. package/src/components/types.ts +1 -0
  72. package/src/components/zoom-controls/styles.ts +27 -0
  73. package/src/components/zoom-controls/types.ts +19 -0
  74. package/src/components/zoom-controls/zoom-controls.test.tsx +101 -0
  75. package/src/components/zoom-controls/zoom-controls.tsx +114 -0
  76. package/src/hooks/index.ts +2 -0
  77. package/src/hooks/use-debounce.ts +55 -0
  78. package/src/hooks/use-skeleton.test.tsx +32 -0
  79. package/src/hooks/use-skeleton.ts +19 -0
  80. package/src/hooks/use-widget-ref.ts +33 -0
  81. package/src/widgets/README.md +277 -0
  82. package/src/widgets/_shared/chart-config/config-factory.ts +67 -0
  83. package/src/widgets/_shared/chart-config/csv-modifiers.ts +56 -0
  84. package/src/widgets/_shared/chart-config/index.ts +21 -0
  85. package/src/widgets/_shared/chart-config/option-builders.ts +203 -0
  86. package/src/widgets/_shared/skeleton/index.ts +5 -0
  87. package/src/widgets/_shared/skeleton/styles.ts +20 -0
  88. package/src/widgets/actions/change-column/change-column-icon.tsx +10 -0
  89. package/src/widgets/actions/change-column/change-column.test.tsx +163 -0
  90. package/src/widgets/actions/change-column/change-column.tsx +141 -0
  91. package/src/widgets/actions/change-column/sortable-column-item.tsx +49 -0
  92. package/src/widgets/actions/change-column/types.ts +20 -0
  93. package/src/widgets/actions/download/download.test.tsx +322 -0
  94. package/src/widgets/actions/download/download.tsx +118 -0
  95. package/src/widgets/actions/download/exports.test.tsx +275 -0
  96. package/src/widgets/actions/download/exports.tsx +103 -0
  97. package/src/widgets/actions/download/types.ts +21 -0
  98. package/src/widgets/actions/fullscreen/fullscreen.test.tsx +269 -0
  99. package/src/widgets/actions/fullscreen/fullscreen.tsx +82 -0
  100. package/src/widgets/actions/fullscreen/styles.ts +17 -0
  101. package/src/widgets/actions/fullscreen/types.ts +27 -0
  102. package/src/widgets/actions/index.ts +51 -0
  103. package/src/widgets/actions/lock-selection/lock-selection.test.tsx +186 -0
  104. package/src/widgets/actions/lock-selection/lock-selection.tsx +133 -0
  105. package/src/widgets/actions/lock-selection/types.ts +41 -0
  106. package/src/widgets/actions/relative-data/relative-data.test.tsx +267 -0
  107. package/src/widgets/actions/relative-data/relative-data.tsx +133 -0
  108. package/src/widgets/actions/relative-data/style.ts +9 -0
  109. package/src/widgets/actions/relative-data/types.ts +31 -0
  110. package/src/widgets/actions/relative-data/utils.test.ts +223 -0
  111. package/src/widgets/actions/relative-data/utils.ts +58 -0
  112. package/src/widgets/actions/searcher/searcher-toggle.test.tsx +354 -0
  113. package/src/widgets/actions/searcher/searcher-toggle.tsx +73 -0
  114. package/src/widgets/actions/searcher/searcher.tsx +205 -0
  115. package/src/widgets/actions/searcher/types.ts +72 -0
  116. package/src/widgets/actions/shared/styles.ts +12 -0
  117. package/src/widgets/actions/stack-toggle/grouped-bar-chart-icon.tsx +14 -0
  118. package/src/widgets/actions/stack-toggle/stack-toggle.test.tsx +270 -0
  119. package/src/widgets/actions/stack-toggle/stack-toggle.tsx +146 -0
  120. package/src/widgets/actions/stack-toggle/types.ts +29 -0
  121. package/src/widgets/actions/zoom-toggle/index.ts +2 -0
  122. package/src/widgets/actions/zoom-toggle/style.ts +14 -0
  123. package/src/widgets/actions/zoom-toggle/types.ts +44 -0
  124. package/src/widgets/actions/zoom-toggle/zoom-toggle.tsx +186 -0
  125. package/src/widgets/bar/config.ts +122 -0
  126. package/src/widgets/bar/index.ts +9 -0
  127. package/src/widgets/bar/skeleton.tsx +60 -0
  128. package/src/widgets/bar/style.ts +33 -0
  129. package/src/widgets/bar/types.ts +16 -0
  130. package/src/widgets/category/category-ui.test.tsx +399 -0
  131. package/src/widgets/category/category-ui.tsx +156 -0
  132. package/src/widgets/category/components/category-bar.tsx +28 -0
  133. package/src/widgets/category/components/category-legend.tsx +30 -0
  134. package/src/widgets/category/components/category-row-multi.tsx +50 -0
  135. package/src/widgets/category/components/category-row-other.tsx +23 -0
  136. package/src/widgets/category/components/category-row-single.tsx +47 -0
  137. package/src/widgets/category/components/index.ts +14 -0
  138. package/src/widgets/category/config.ts +85 -0
  139. package/src/widgets/category/index.ts +30 -0
  140. package/src/widgets/category/skeleton.tsx +24 -0
  141. package/src/widgets/category/style.ts +133 -0
  142. package/src/widgets/category/types.ts +40 -0
  143. package/src/widgets/echart/const.ts +1 -0
  144. package/src/widgets/echart/echart-ui.test.tsx +537 -0
  145. package/src/widgets/echart/echart-ui.tsx +92 -0
  146. package/src/widgets/echart/echart.test.tsx +562 -0
  147. package/src/widgets/echart/echart.tsx +68 -0
  148. package/src/widgets/echart/index.ts +16 -0
  149. package/src/widgets/echart/options.ts +53 -0
  150. package/src/widgets/echart/types.ts +45 -0
  151. package/src/widgets/echart/utils.ts +169 -0
  152. package/src/widgets/error/error.test.tsx +331 -0
  153. package/src/widgets/error/error.tsx +40 -0
  154. package/src/widgets/error/index.ts +2 -0
  155. package/src/widgets/error/types.ts +14 -0
  156. package/src/widgets/formula/components/item.test.tsx +249 -0
  157. package/src/widgets/formula/components/item.tsx +18 -0
  158. package/src/widgets/formula/components/prefix.test.tsx +341 -0
  159. package/src/widgets/formula/components/prefix.tsx +18 -0
  160. package/src/widgets/formula/components/row.test.tsx +364 -0
  161. package/src/widgets/formula/components/row.tsx +21 -0
  162. package/src/widgets/formula/components/series.tsx +34 -0
  163. package/src/widgets/formula/components/suffix.test.tsx +383 -0
  164. package/src/widgets/formula/components/suffix.tsx +28 -0
  165. package/src/widgets/formula/components/value.test.tsx +329 -0
  166. package/src/widgets/formula/components/value.tsx +29 -0
  167. package/src/widgets/formula/config.ts +27 -0
  168. package/src/widgets/formula/formula-ui.test.tsx +399 -0
  169. package/src/widgets/formula/formula-ui.tsx +27 -0
  170. package/src/widgets/formula/index.ts +24 -0
  171. package/src/widgets/formula/serializer.test.tsx +144 -0
  172. package/src/widgets/formula/serializer.ts +28 -0
  173. package/src/widgets/formula/skeleton.tsx +10 -0
  174. package/src/widgets/formula/style.ts +23 -0
  175. package/src/widgets/formula/types.ts +50 -0
  176. package/src/widgets/histogram/config.ts +143 -0
  177. package/src/widgets/histogram/index.ts +8 -0
  178. package/src/widgets/histogram/skeleton.tsx +52 -0
  179. package/src/widgets/histogram/style.ts +8 -0
  180. package/src/widgets/histogram/types.ts +17 -0
  181. package/src/widgets/index.ts +25 -0
  182. package/src/widgets/loader/index.ts +4 -0
  183. package/src/widgets/loader/loader.tsx +70 -0
  184. package/src/widgets/loader/types.ts +11 -0
  185. package/src/widgets/loader/utils.test.ts +112 -0
  186. package/src/widgets/loader/utils.ts +35 -0
  187. package/src/widgets/markdown/config.ts +18 -0
  188. package/src/widgets/markdown/index.ts +14 -0
  189. package/src/widgets/markdown/markdown-ui.test.tsx +341 -0
  190. package/src/widgets/markdown/markdown-ui.tsx +52 -0
  191. package/src/widgets/markdown/markdown.tsx +20 -0
  192. package/src/widgets/markdown/skeleton.tsx +12 -0
  193. package/src/widgets/markdown/style.ts +28 -0
  194. package/src/widgets/markdown/types.ts +28 -0
  195. package/src/widgets/no-data/index.ts +2 -0
  196. package/src/widgets/no-data/no-data.test.tsx +447 -0
  197. package/src/widgets/no-data/no-data.tsx +116 -0
  198. package/src/widgets/no-data/style.ts +18 -0
  199. package/src/widgets/no-data/types.ts +72 -0
  200. package/src/widgets/note/index.ts +2 -0
  201. package/src/widgets/note/note.test.tsx +391 -0
  202. package/src/widgets/note/note.tsx +114 -0
  203. package/src/widgets/note/style.ts +29 -0
  204. package/src/widgets/note/types.ts +9 -0
  205. package/src/widgets/pie/config.ts +177 -0
  206. package/src/widgets/pie/index.ts +8 -0
  207. package/src/widgets/pie/skeleton.tsx +70 -0
  208. package/src/widgets/pie/style.ts +8 -0
  209. package/src/widgets/pie/types.ts +16 -0
  210. package/src/widgets/range/components/range-item.tsx +213 -0
  211. package/src/widgets/range/config.ts +10 -0
  212. package/src/widgets/range/index.ts +16 -0
  213. package/src/widgets/range/range-ui.test.tsx +203 -0
  214. package/src/widgets/range/range-ui.tsx +11 -0
  215. package/src/widgets/range/serializer.test.ts +70 -0
  216. package/src/widgets/range/serializer.ts +27 -0
  217. package/src/widgets/range/skeleton.tsx +14 -0
  218. package/src/widgets/range/style.ts +37 -0
  219. package/src/widgets/range/types.ts +39 -0
  220. package/src/widgets/scatterplot/config.ts +138 -0
  221. package/src/widgets/scatterplot/index.ts +8 -0
  222. package/src/widgets/scatterplot/skeleton.tsx +59 -0
  223. package/src/widgets/scatterplot/style.ts +21 -0
  224. package/src/widgets/scatterplot/types.ts +17 -0
  225. package/src/widgets/selection-summary/index.ts +6 -0
  226. package/src/widgets/selection-summary/selection-summary.tsx +46 -0
  227. package/src/widgets/selection-summary/style.ts +10 -0
  228. package/src/widgets/selection-summary/types.ts +14 -0
  229. package/src/widgets/skeleton-loader/index.ts +2 -0
  230. package/src/widgets/skeleton-loader/skeleton-loader.test.tsx +139 -0
  231. package/src/widgets/skeleton-loader/skeleton-loader.tsx +28 -0
  232. package/src/widgets/skeleton-loader/types.ts +8 -0
  233. package/src/widgets/spread/components/max-value.tsx +29 -0
  234. package/src/widgets/spread/components/min-value.tsx +29 -0
  235. package/src/widgets/spread/components/separator.tsx +6 -0
  236. package/src/widgets/spread/config.ts +34 -0
  237. package/src/widgets/spread/index.ts +23 -0
  238. package/src/widgets/spread/skeleton.tsx +10 -0
  239. package/src/widgets/spread/spread-ui.test.tsx +368 -0
  240. package/src/widgets/spread/spread-ui.tsx +29 -0
  241. package/src/widgets/spread/style.ts +22 -0
  242. package/src/widgets/spread/types.ts +47 -0
  243. package/src/widgets/stores/index.ts +9 -0
  244. package/src/widgets/stores/types.ts +192 -0
  245. package/src/widgets/stores/widget-store.test.ts +601 -0
  246. package/src/widgets/stores/widget-store.ts +239 -0
  247. package/src/widgets/subheader/index.ts +3 -0
  248. package/src/widgets/subheader/style.ts +20 -0
  249. package/src/widgets/subheader/subheader.test.tsx +45 -0
  250. package/src/widgets/subheader/subheader.tsx +16 -0
  251. package/src/widgets/subheader/types.ts +11 -0
  252. package/src/widgets/table/components/cell-header.tsx +58 -0
  253. package/src/widgets/table/components/cell.tsx +80 -0
  254. package/src/widgets/table/components/index.ts +4 -0
  255. package/src/widgets/table/components/pagination-actions.tsx +67 -0
  256. package/src/widgets/table/components/pagination.tsx +41 -0
  257. package/src/widgets/table/components/row.tsx +60 -0
  258. package/src/widgets/table/config.ts +71 -0
  259. package/src/widgets/table/helpers.test.ts +244 -0
  260. package/src/widgets/table/helpers.ts +107 -0
  261. package/src/widgets/table/hooks/index.ts +7 -0
  262. package/src/widgets/table/hooks/use-pagination.test.ts +294 -0
  263. package/src/widgets/table/hooks/use-pagination.ts +155 -0
  264. package/src/widgets/table/hooks/use-selection.test.ts +504 -0
  265. package/src/widgets/table/hooks/use-selection.ts +189 -0
  266. package/src/widgets/table/hooks/use-sort.test.ts +296 -0
  267. package/src/widgets/table/hooks/use-sort.ts +138 -0
  268. package/src/widgets/table/index.ts +53 -0
  269. package/src/widgets/table/serializer.ts +54 -0
  270. package/src/widgets/table/skeleton.tsx +48 -0
  271. package/src/widgets/table/style.ts +34 -0
  272. package/src/widgets/table/table-ui.tsx +64 -0
  273. package/src/widgets/table/types.ts +223 -0
  274. package/src/widgets/timeseries/config.ts +135 -0
  275. package/src/widgets/timeseries/index.ts +8 -0
  276. package/src/widgets/timeseries/skeleton.tsx +55 -0
  277. package/src/widgets/timeseries/style.ts +36 -0
  278. package/src/widgets/timeseries/types.ts +17 -0
  279. package/src/widgets/toolbar-actions/index.ts +6 -0
  280. package/src/widgets/toolbar-actions/styles.ts +38 -0
  281. package/src/widgets/toolbar-actions/toolbar-actions.test.tsx +691 -0
  282. package/src/widgets/toolbar-actions/toolbar-actions.tsx +145 -0
  283. package/src/widgets/toolbar-actions/types.ts +60 -0
  284. package/src/widgets/wrapper/components/actions.test.tsx +101 -0
  285. package/src/widgets/wrapper/components/actions.tsx +30 -0
  286. package/src/widgets/wrapper/components/options.test.tsx +323 -0
  287. package/src/widgets/wrapper/components/options.tsx +73 -0
  288. package/src/widgets/wrapper/components/title.test.tsx +126 -0
  289. package/src/widgets/wrapper/components/title.tsx +32 -0
  290. package/src/widgets/wrapper/index.ts +16 -0
  291. package/src/widgets/wrapper/styles.ts +98 -0
  292. package/src/widgets/wrapper/types.ts +55 -0
  293. package/src/widgets/wrapper/wrapper-ui.test.tsx +232 -0
  294. package/src/widgets/wrapper/wrapper-ui.tsx +57 -0
  295. package/src/widgets/wrapper/wrapper.test.tsx +365 -0
  296. package/src/widgets/wrapper/wrapper.tsx +50 -0
  297. package/dist/lasso-tool-BwRzEW7k.js.map +0 -1
  298. package/dist/types/common/common.d.ts +0 -3
  299. package/dist/types/common/index.d.ts +0 -26
  300. package/dist/types/common/lasso-tools.d.ts +0 -36
  301. package/dist/types/common/measurement-tools.d.ts +0 -65
@@ -0,0 +1,249 @@
1
+ import { describe, test, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { Item } from './item'
4
+
5
+ describe('Item', () => {
6
+ test('renders string children', () => {
7
+ render(<Item>Test Content</Item>)
8
+ expect(screen.getByText('Test Content')).toBeTruthy()
9
+ })
10
+
11
+ test('renders numeric children', () => {
12
+ render(<Item>{123}</Item>)
13
+ expect(screen.getByText('123')).toBeTruthy()
14
+ })
15
+
16
+ test('renders ReactNode children', () => {
17
+ render(
18
+ <Item>
19
+ <span>Complex</span> <strong>Content</strong>
20
+ </Item>,
21
+ )
22
+ expect(screen.getByText('Complex')).toBeTruthy()
23
+ expect(screen.getByText('Content')).toBeTruthy()
24
+ })
25
+
26
+ test('renders as Typography with h5 variant', () => {
27
+ render(<Item>Content</Item>)
28
+ const element = screen.getByText('Content')
29
+ expect(element.tagName).toBe('H5')
30
+ })
31
+
32
+ test('applies medium font weight', () => {
33
+ render(<Item>Content</Item>)
34
+ const element = screen.getByText('Content')
35
+ expect(element.classList.contains('MuiTypography-root')).toBe(true)
36
+ })
37
+
38
+ test('applies default color', () => {
39
+ render(<Item>Content</Item>)
40
+ const element = screen.getByText('Content')
41
+ // Color is passed as a prop to Typography, not an HTML attribute
42
+ expect(element).toBeTruthy()
43
+ })
44
+
45
+ test('sets data-disabled attribute when disabled is true', () => {
46
+ render(<Item disabled={true}>Content</Item>)
47
+ const element = screen.getByText('Content')
48
+ expect(element.getAttribute('data-disabled')).toBe('true')
49
+ })
50
+
51
+ test('does not set data-disabled attribute when disabled is false', () => {
52
+ render(<Item disabled={false}>Content</Item>)
53
+ const element = screen.getByText('Content')
54
+ expect(element.getAttribute('data-disabled')).toBe('false')
55
+ })
56
+
57
+ test('does not set data-disabled attribute when disabled is undefined', () => {
58
+ render(<Item>Content</Item>)
59
+ const element = screen.getByText('Content')
60
+ // When undefined, data-disabled is not set or set to null/false
61
+ expect(element.getAttribute('data-disabled')).toBeFalsy()
62
+ })
63
+
64
+ test('accepts custom TypographyProps color', () => {
65
+ render(<Item TypographyProps={{ color: 'primary' }}>Content</Item>)
66
+ const element = screen.getByText('Content')
67
+ // Color is passed as a prop, not an HTML attribute
68
+ expect(element).toBeTruthy()
69
+ })
70
+
71
+ test('accepts custom TypographyProps variant', () => {
72
+ render(<Item TypographyProps={{ variant: 'h6' }}>Content</Item>)
73
+ const element = screen.getByText('Content')
74
+ // Custom variant should override default h5
75
+ expect(element.tagName).toBe('H6')
76
+ })
77
+
78
+ test('accepts custom TypographyProps className', () => {
79
+ render(<Item TypographyProps={{ className: 'custom-class' }}>Content</Item>)
80
+ const element = screen.getByText('Content')
81
+ expect(element.classList.contains('custom-class')).toBe(true)
82
+ })
83
+
84
+ test('accepts custom TypographyProps sx', () => {
85
+ render(<Item TypographyProps={{ sx: { fontSize: 20 } }}>Content</Item>)
86
+ const element = screen.getByText('Content')
87
+ expect(element).toBeTruthy()
88
+ })
89
+
90
+ test('accepts custom TypographyProps fontWeight', () => {
91
+ render(<Item TypographyProps={{ fontWeight: 'bold' }}>Content</Item>)
92
+ const element = screen.getByText('Content')
93
+ expect(element).toBeTruthy()
94
+ })
95
+
96
+ test('handles empty string children', () => {
97
+ const { container } = render(<Item>{''}</Item>)
98
+ expect(container.querySelector('.MuiTypography-root')).toBeTruthy()
99
+ })
100
+
101
+ test('handles zero as children', () => {
102
+ render(<Item>{0}</Item>)
103
+ expect(screen.getByText('0')).toBeTruthy()
104
+ })
105
+
106
+ test('handles negative numbers as children', () => {
107
+ render(<Item>{-100}</Item>)
108
+ expect(screen.getByText('-100')).toBeTruthy()
109
+ })
110
+
111
+ test('handles decimal numbers as children', () => {
112
+ render(<Item>{123.456}</Item>)
113
+ expect(screen.getByText('123.456')).toBeTruthy()
114
+ })
115
+
116
+ test('handles large numbers as children', () => {
117
+ render(<Item>{1000000}</Item>)
118
+ expect(screen.getByText('1000000')).toBeTruthy()
119
+ })
120
+
121
+ test('combines disabled and custom TypographyProps', () => {
122
+ render(
123
+ <Item disabled={true} TypographyProps={{ color: 'error' }}>
124
+ Content
125
+ </Item>,
126
+ )
127
+ const element = screen.getByText('Content')
128
+ expect(element.getAttribute('data-disabled')).toBe('true')
129
+ // Color is passed as a prop, not an HTML attribute
130
+ expect(element).toBeTruthy()
131
+ })
132
+
133
+ test('applies Typography classes', () => {
134
+ render(<Item>Content</Item>)
135
+ const element = screen.getByText('Content')
136
+ expect(element.classList.contains('MuiTypography-root')).toBe(true)
137
+ expect(element.classList.contains('MuiTypography-h5')).toBe(true)
138
+ })
139
+
140
+ test('handles null children', () => {
141
+ const { container } = render(<Item>{null}</Item>)
142
+ expect(container.querySelector('.MuiTypography-root')).toBeTruthy()
143
+ })
144
+
145
+ test('handles undefined children', () => {
146
+ const { container } = render(<Item>{undefined}</Item>)
147
+ expect(container.querySelector('.MuiTypography-root')).toBeTruthy()
148
+ })
149
+
150
+ test('handles multiple children elements', () => {
151
+ render(
152
+ <Item>
153
+ <span>First</span>
154
+ <span>Second</span>
155
+ <span>Third</span>
156
+ </Item>,
157
+ )
158
+ expect(screen.getByText('First')).toBeTruthy()
159
+ expect(screen.getByText('Second')).toBeTruthy()
160
+ expect(screen.getByText('Third')).toBeTruthy()
161
+ })
162
+
163
+ test('handles mixed children types', () => {
164
+ render(
165
+ <Item>
166
+ Text <span>Element</span> {123}
167
+ </Item>,
168
+ )
169
+ expect(screen.getByText(/Text/)).toBeTruthy()
170
+ expect(screen.getByText('Element')).toBeTruthy()
171
+ expect(screen.getByText(/123/)).toBeTruthy()
172
+ })
173
+
174
+ test('handles deeply nested children', () => {
175
+ render(
176
+ <Item>
177
+ <div>
178
+ <span>
179
+ <strong>Nested</strong>
180
+ </span>
181
+ </div>
182
+ </Item>,
183
+ )
184
+ expect(screen.getByText('Nested')).toBeTruthy()
185
+ })
186
+
187
+ test('handles special characters in children', () => {
188
+ render(<Item>Special: @#$%^&*()</Item>)
189
+ expect(screen.getByText(/Special: @#\$%\^&\*\(\)/)).toBeTruthy()
190
+ })
191
+
192
+ test('handles long text content', () => {
193
+ const longText = 'A'.repeat(1000)
194
+ render(<Item>{longText}</Item>)
195
+ expect(screen.getByText(longText)).toBeTruthy()
196
+ })
197
+
198
+ test('handles unicode characters', () => {
199
+ render(<Item>Unicode: 你好 🌍</Item>)
200
+ expect(screen.getByText(/Unicode: 你好 🌍/)).toBeTruthy()
201
+ })
202
+
203
+ test('merges custom TypographyProps with default props', () => {
204
+ render(
205
+ <Item
206
+ TypographyProps={{
207
+ color: 'secondary',
208
+ variant: 'body1',
209
+ className: 'custom',
210
+ }}
211
+ >
212
+ Content
213
+ </Item>,
214
+ )
215
+ const element = screen.getByText('Content')
216
+ // Color is passed as a prop, not an HTML attribute
217
+ expect(element.classList.contains('custom')).toBe(true)
218
+ // variant body1 uses p tag
219
+ expect(element.tagName).toBe('P')
220
+ })
221
+
222
+ test('TypographyProps can override fontWeight', () => {
223
+ render(<Item TypographyProps={{ fontWeight: 'light' }}>Content</Item>)
224
+ const element = screen.getByText('Content')
225
+ expect(element).toBeTruthy()
226
+ })
227
+
228
+ test('renders with all props combined', () => {
229
+ render(
230
+ <Item
231
+ disabled={true}
232
+ TypographyProps={{
233
+ color: 'primary',
234
+ variant: 'h6',
235
+ fontWeight: 'bold',
236
+ className: 'test-class',
237
+ sx: { fontSize: 24 },
238
+ }}
239
+ >
240
+ Full Test
241
+ </Item>,
242
+ )
243
+ const element = screen.getByText('Full Test')
244
+ expect(element.getAttribute('data-disabled')).toBe('true')
245
+ // Color is passed as a prop, not an HTML attribute
246
+ expect(element.tagName).toBe('H6')
247
+ expect(element.classList.contains('test-class')).toBe(true)
248
+ })
249
+ })
@@ -0,0 +1,18 @@
1
+ import { Typography } from '@mui/material'
2
+ import type { ItemProps } from '../types'
3
+ import { styles } from '../style'
4
+
5
+ export function Item(props: ItemProps) {
6
+ return (
7
+ <Typography
8
+ sx={styles.item}
9
+ variant='h5'
10
+ fontWeight='medium'
11
+ data-disabled={props.disabled}
12
+ color='default'
13
+ {...props.TypographyProps}
14
+ >
15
+ {props.children}
16
+ </Typography>
17
+ )
18
+ }
@@ -0,0 +1,341 @@
1
+ import { describe, test, expect, beforeEach } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import { Prefix } from './prefix'
4
+ import { useWidgetStore } from '../../stores/widget-store'
5
+ import type { FormulaWidgetState } from '../types'
6
+
7
+ describe('Prefix', () => {
8
+ beforeEach(() => {
9
+ useWidgetStore.getState().clearWidgets()
10
+ })
11
+
12
+ test('renders prefix from widget data at default index 0', () => {
13
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
14
+ type: 'formula',
15
+ data: [{ value: 100, prefix: '$' }],
16
+ })
17
+
18
+ render(<Prefix id='test-prefix' />)
19
+ expect(screen.getByText('$')).toBeTruthy()
20
+ })
21
+
22
+ test('renders prefix at specified index', () => {
23
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
24
+ type: 'formula',
25
+ data: [
26
+ { value: 100, prefix: 'First' },
27
+ { value: 200, prefix: 'Second' },
28
+ { value: 300, prefix: 'Third' },
29
+ ],
30
+ })
31
+
32
+ render(<Prefix id='test-prefix' index={1} />)
33
+ expect(screen.getByText('Second')).toBeTruthy()
34
+ })
35
+
36
+ test('renders prefix at index 0 when index not specified', () => {
37
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
38
+ type: 'formula',
39
+ data: [
40
+ { value: 100, prefix: 'First' },
41
+ { value: 200, prefix: 'Second' },
42
+ ],
43
+ })
44
+
45
+ render(<Prefix id='test-prefix' />)
46
+ expect(screen.getByText('First')).toBeTruthy()
47
+ })
48
+
49
+ test('returns null when widget does not exist', () => {
50
+ const { container } = render(<Prefix id='non-existent' />)
51
+ expect(container.firstChild).toBeNull()
52
+ })
53
+
54
+ test('returns null when widget data is undefined', () => {
55
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
56
+ type: 'formula',
57
+ })
58
+
59
+ const { container } = render(<Prefix id='test-prefix' />)
60
+ expect(container.firstChild).toBeNull()
61
+ })
62
+
63
+ test('returns null when prefix is undefined', () => {
64
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
65
+ type: 'formula',
66
+ data: [{ value: 100 }],
67
+ })
68
+
69
+ const { container } = render(<Prefix id='test-prefix' />)
70
+ expect(container.firstChild).toBeNull()
71
+ })
72
+
73
+ test('returns null when index is out of bounds', () => {
74
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
75
+ type: 'formula',
76
+ data: [{ value: 100, prefix: '$' }],
77
+ })
78
+
79
+ const { container } = render(<Prefix id='test-prefix' index={5} />)
80
+ expect(container.firstChild).toBeNull()
81
+ })
82
+
83
+ test('returns null for empty data array', () => {
84
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
85
+ type: 'formula',
86
+ data: [],
87
+ })
88
+
89
+ const { container } = render(<Prefix id='test-prefix' />)
90
+ expect(container.firstChild).toBeNull()
91
+ })
92
+
93
+ test('returns null for empty string prefix', () => {
94
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
95
+ type: 'formula',
96
+ data: [{ value: 100, prefix: '' }],
97
+ })
98
+
99
+ const { container } = render(<Prefix id='test-prefix' />)
100
+ expect(container.firstChild).toBeNull()
101
+ })
102
+
103
+ test('renders string prefix', () => {
104
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
105
+ type: 'formula',
106
+ data: [{ value: 100, prefix: 'Total:' }],
107
+ })
108
+
109
+ render(<Prefix id='test-prefix' />)
110
+ expect(screen.getByText('Total:')).toBeTruthy()
111
+ })
112
+
113
+ test('renders single character prefix', () => {
114
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
115
+ type: 'formula',
116
+ data: [{ value: 100, prefix: '$' }],
117
+ })
118
+
119
+ render(<Prefix id='test-prefix' />)
120
+ expect(screen.getByText('$')).toBeTruthy()
121
+ })
122
+
123
+ test('renders multi-word prefix', () => {
124
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
125
+ type: 'formula',
126
+ data: [{ value: 100, prefix: 'Grand Total:' }],
127
+ })
128
+
129
+ render(<Prefix id='test-prefix' />)
130
+ expect(screen.getByText('Grand Total:')).toBeTruthy()
131
+ })
132
+
133
+ test('renders as Typography h5', () => {
134
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
135
+ type: 'formula',
136
+ data: [{ value: 100, prefix: '$' }],
137
+ })
138
+
139
+ render(<Prefix id='test-prefix' />)
140
+ const element = screen.getByText('$')
141
+ expect(element.tagName).toBe('H5')
142
+ })
143
+
144
+ test('passes disabled prop to Item', () => {
145
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
146
+ type: 'formula',
147
+ data: [{ value: 100, prefix: '$' }],
148
+ })
149
+
150
+ render(<Prefix id='test-prefix' disabled={true} />)
151
+ const element = screen.getByText('$')
152
+ expect(element.getAttribute('data-disabled')).toBe('true')
153
+ })
154
+
155
+ test('passes TypographyProps to Item', () => {
156
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
157
+ type: 'formula',
158
+ data: [{ value: 100, prefix: '$' }],
159
+ })
160
+
161
+ render(
162
+ <Prefix id='test-prefix' TypographyProps={{ className: 'custom' }} />,
163
+ )
164
+ const element = screen.getByText('$')
165
+ expect(element.classList.contains('custom')).toBe(true)
166
+ })
167
+
168
+ test('handles all data properties together', () => {
169
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
170
+ type: 'formula',
171
+ data: [
172
+ {
173
+ value: 100,
174
+ color: '#FF0000',
175
+ prefix: '$',
176
+ suffix: 'USD',
177
+ },
178
+ ],
179
+ })
180
+
181
+ render(<Prefix id='test-prefix' />)
182
+ expect(screen.getByText('$')).toBeTruthy()
183
+ })
184
+
185
+ test('updates when widget data changes', () => {
186
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
187
+ type: 'formula',
188
+ data: [{ value: 100, prefix: '$' }],
189
+ })
190
+
191
+ const { rerender } = render(<Prefix id='test-prefix' />)
192
+ expect(screen.getByText('$')).toBeTruthy()
193
+
194
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
195
+ data: [{ value: 200, prefix: '€' }],
196
+ })
197
+
198
+ rerender(<Prefix id='test-prefix' />)
199
+ expect(screen.queryByText('$')).toBeNull()
200
+ expect(screen.getByText('€')).toBeTruthy()
201
+ })
202
+
203
+ test('updates when prefix is removed', () => {
204
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
205
+ type: 'formula',
206
+ data: [{ value: 100, prefix: '$' }],
207
+ })
208
+
209
+ const { container, rerender } = render(<Prefix id='test-prefix' />)
210
+ expect(screen.getByText('$')).toBeTruthy()
211
+
212
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
213
+ data: [{ value: 100 }],
214
+ })
215
+
216
+ rerender(<Prefix id='test-prefix' />)
217
+ expect(container.firstChild).toBeNull()
218
+ })
219
+
220
+ test('updates when index changes', () => {
221
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
222
+ type: 'formula',
223
+ data: [
224
+ { value: 100, prefix: '$' },
225
+ { value: 200, prefix: '€' },
226
+ ],
227
+ })
228
+
229
+ const { rerender } = render(<Prefix id='test-prefix' index={0} />)
230
+ expect(screen.getByText('$')).toBeTruthy()
231
+
232
+ rerender(<Prefix id='test-prefix' index={1} />)
233
+ expect(screen.queryByText('$')).toBeNull()
234
+ expect(screen.getByText('€')).toBeTruthy()
235
+ })
236
+
237
+ test('handles widget removal gracefully', () => {
238
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
239
+ type: 'formula',
240
+ data: [{ value: 100, prefix: '$' }],
241
+ })
242
+
243
+ const { container, rerender } = render(<Prefix id='test-prefix' />)
244
+ expect(screen.getByText('$')).toBeTruthy()
245
+
246
+ useWidgetStore.getState().removeWidget('test-prefix')
247
+
248
+ rerender(<Prefix id='test-prefix' />)
249
+ expect(container.firstChild).toBeNull()
250
+ })
251
+
252
+ test('handles prefix with special characters', () => {
253
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
254
+ type: 'formula',
255
+ data: [{ value: 100, prefix: '¥' }],
256
+ })
257
+
258
+ render(<Prefix id='test-prefix' />)
259
+ expect(screen.getByText('¥')).toBeTruthy()
260
+ })
261
+
262
+ test('handles prefix with unicode characters', () => {
263
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
264
+ type: 'formula',
265
+ data: [{ value: 100, prefix: '€ 💰' }],
266
+ })
267
+
268
+ render(<Prefix id='test-prefix' />)
269
+ expect(screen.getByText(/€ 💰/)).toBeTruthy()
270
+ })
271
+
272
+ test('handles prefix with numbers', () => {
273
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
274
+ type: 'formula',
275
+ data: [{ value: 100, prefix: '#1:' }],
276
+ })
277
+
278
+ render(<Prefix id='test-prefix' />)
279
+ expect(screen.getByText('#1:')).toBeTruthy()
280
+ })
281
+
282
+ test('handles long prefix text', () => {
283
+ const longPrefix = 'This is a very long prefix text: '
284
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
285
+ type: 'formula',
286
+ data: [{ value: 100, prefix: longPrefix }],
287
+ })
288
+
289
+ render(<Prefix id='test-prefix' />)
290
+ expect(screen.getByText(longPrefix.trim())).toBeTruthy()
291
+ })
292
+
293
+ test('renders different prefixes for different items', () => {
294
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
295
+ type: 'formula',
296
+ data: [
297
+ { value: 100, prefix: '$' },
298
+ { value: 200, prefix: '€' },
299
+ { value: 300, prefix: '£' },
300
+ ],
301
+ })
302
+
303
+ const { unmount } = render(<Prefix id='test-prefix' index={0} />)
304
+ expect(screen.getByText('$')).toBeTruthy()
305
+ unmount()
306
+
307
+ render(<Prefix id='test-prefix' index={1} />)
308
+ expect(screen.getByText('€')).toBeTruthy()
309
+ })
310
+
311
+ test('handles negative index (invalid but should not crash)', () => {
312
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
313
+ type: 'formula',
314
+ data: [{ value: 100, prefix: '$' }],
315
+ })
316
+
317
+ const { container } = render(<Prefix id='test-prefix' index={-1} />)
318
+ expect(container.firstChild).toBeNull()
319
+ })
320
+
321
+ test('handles prefix with only whitespace', () => {
322
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
323
+ type: 'formula',
324
+ data: [{ value: 100, prefix: ' ' }],
325
+ })
326
+
327
+ const { container } = render(<Prefix id='test-prefix' />)
328
+ // Whitespace is normalized by text content, just verify component renders
329
+ expect(container.querySelector('.MuiTypography-root')).toBeTruthy()
330
+ })
331
+
332
+ test('prefix is sanitized to string only in store', () => {
333
+ useWidgetStore.getState().setWidget<FormulaWidgetState>('test-prefix', {
334
+ type: 'formula',
335
+ data: [{ value: 100, prefix: 'Valid String' }],
336
+ })
337
+
338
+ render(<Prefix id='test-prefix' />)
339
+ expect(screen.getByText('Valid String')).toBeTruthy()
340
+ })
341
+ })
@@ -0,0 +1,18 @@
1
+ import { type FormulaWidgetState, type ValueProps } from '../types'
2
+ import { useWidgetStore } from '../../stores/widget-store'
3
+ import { Item } from './item'
4
+ import { useShallow } from 'zustand/shallow'
5
+
6
+ export function Prefix({ id, index = 0, ...props }: ValueProps) {
7
+ const prefix = useWidgetStore(
8
+ useShallow(
9
+ (state) => state.getWidget<FormulaWidgetState>(id)?.data?.[index]?.prefix,
10
+ ),
11
+ )
12
+
13
+ if (!prefix) {
14
+ return null
15
+ }
16
+
17
+ return <Item {...props}>{prefix}</Item>
18
+ }