shouldMapStateToProps.test.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. import { ControlPanelState } from '../../src/types';
  20. // Mock the utilities to avoid complex dependencies
  21. jest.mock('../../src/utils', () => ({
  22. formatSelectOptions: jest.fn((options: any[]) =>
  23. options.map((opt: any) => [opt, opt]),
  24. ),
  25. displayTimeRelatedControls: jest.fn(() => true),
  26. getColorControlsProps: jest.fn(() => ({})),
  27. D3_FORMAT_OPTIONS: [],
  28. D3_FORMAT_DOCS: '',
  29. D3_TIME_FORMAT_OPTIONS: [],
  30. D3_TIME_FORMAT_DOCS: '',
  31. DEFAULT_TIME_FORMAT: '%Y-%m-%d',
  32. DEFAULT_NUMBER_FORMAT: '',
  33. }));
  34. // Mock shared controls
  35. const mockSharedControls = {
  36. matrixify_dimension_x: {
  37. shouldMapStateToProps: (
  38. prevState: ControlPanelState,
  39. state: ControlPanelState,
  40. ) => {
  41. const fieldsToCheck = [
  42. 'matrixify_topn_value_x',
  43. 'matrixify_topn_metric_x',
  44. 'matrixify_topn_order_x',
  45. 'matrixify_dimension_selection_mode_x',
  46. ];
  47. return fieldsToCheck.some(
  48. field => prevState?.form_data?.[field] !== state?.form_data?.[field],
  49. );
  50. },
  51. mapStateToProps: ({ datasource, controls, form_data }: any) => {
  52. const getValue = (key: string, defaultValue?: any) =>
  53. form_data?.[key] ?? controls?.[key]?.value ?? defaultValue;
  54. return {
  55. datasource,
  56. selectionMode: getValue(
  57. 'matrixify_dimension_selection_mode_x',
  58. 'members',
  59. ),
  60. topNMetric: getValue('matrixify_topn_metric_x'),
  61. topNValue: getValue('matrixify_topn_value_x'),
  62. topNOrder: getValue('matrixify_topn_order_x'),
  63. formData: form_data,
  64. };
  65. },
  66. },
  67. matrixify_dimension_y: {
  68. shouldMapStateToProps: (
  69. prevState: ControlPanelState,
  70. state: ControlPanelState,
  71. ) => {
  72. const fieldsToCheck = [
  73. 'matrixify_topn_value_y',
  74. 'matrixify_topn_metric_y',
  75. 'matrixify_topn_order_y',
  76. 'matrixify_dimension_selection_mode_y',
  77. ];
  78. return fieldsToCheck.some(
  79. field => prevState?.form_data?.[field] !== state?.form_data?.[field],
  80. );
  81. },
  82. mapStateToProps: ({ datasource, controls, form_data }: any) => {
  83. const getValue = (key: string, defaultValue?: any) =>
  84. form_data?.[key] ?? controls?.[key]?.value ?? defaultValue;
  85. return {
  86. datasource,
  87. selectionMode: getValue(
  88. 'matrixify_dimension_selection_mode_y',
  89. 'members',
  90. ),
  91. topNMetric: getValue('matrixify_topn_metric_y'),
  92. topNValue: getValue('matrixify_topn_value_y'),
  93. topNOrder: getValue('matrixify_topn_order_y'),
  94. formData: form_data,
  95. };
  96. },
  97. },
  98. };
  99. const createMockState = (
  100. formData: any = {},
  101. controls: any = {},
  102. ): ControlPanelState => ({
  103. slice: { slice_id: 123 },
  104. form_data: formData,
  105. datasource: null,
  106. controls,
  107. common: {},
  108. metadata: {},
  109. });
  110. const createMockControlState = (value: any = null) => ({ value });
  111. test('matrixify_dimension_x should return true when topN value changes', () => {
  112. const control = mockSharedControls.matrixify_dimension_x;
  113. const prevState = createMockState({
  114. matrixify_topn_value_x: 5,
  115. matrixify_topn_metric_x: 'metric1',
  116. matrixify_topn_order_x: 'desc',
  117. matrixify_dimension_selection_mode_x: 'topn',
  118. });
  119. const nextState = createMockState({
  120. matrixify_topn_value_x: 10, // Changed
  121. matrixify_topn_metric_x: 'metric1',
  122. matrixify_topn_order_x: 'desc',
  123. matrixify_dimension_selection_mode_x: 'topn',
  124. });
  125. expect(control.shouldMapStateToProps!(prevState, nextState)).toBe(true);
  126. });
  127. test('matrixify_dimension_x should return true when topN metric changes', () => {
  128. const control = mockSharedControls.matrixify_dimension_x;
  129. const prevState = createMockState({
  130. matrixify_topn_value_x: 5,
  131. matrixify_topn_metric_x: 'metric1',
  132. matrixify_topn_order_x: 'desc',
  133. matrixify_dimension_selection_mode_x: 'topn',
  134. });
  135. const nextState = createMockState({
  136. matrixify_topn_value_x: 5,
  137. matrixify_topn_metric_x: 'metric2', // Changed
  138. matrixify_topn_order_x: 'desc',
  139. matrixify_dimension_selection_mode_x: 'topn',
  140. });
  141. expect(control.shouldMapStateToProps!(prevState, nextState)).toBe(true);
  142. });
  143. test('matrixify_dimension_x should return true when topN order changes', () => {
  144. const control = mockSharedControls.matrixify_dimension_x;
  145. const prevState = createMockState({
  146. matrixify_topn_value_x: 5,
  147. matrixify_topn_metric_x: 'metric1',
  148. matrixify_topn_order_x: 'desc',
  149. matrixify_dimension_selection_mode_x: 'topn',
  150. });
  151. const nextState = createMockState({
  152. matrixify_topn_value_x: 5,
  153. matrixify_topn_metric_x: 'metric1',
  154. matrixify_topn_order_x: 'asc', // Changed
  155. matrixify_dimension_selection_mode_x: 'topn',
  156. });
  157. expect(control.shouldMapStateToProps!(prevState, nextState)).toBe(true);
  158. });
  159. test('matrixify_dimension_x should return true when selection mode changes', () => {
  160. const control = mockSharedControls.matrixify_dimension_x;
  161. const prevState = createMockState({
  162. matrixify_topn_value_x: 5,
  163. matrixify_topn_metric_x: 'metric1',
  164. matrixify_topn_order_x: 'desc',
  165. matrixify_dimension_selection_mode_x: 'topn',
  166. });
  167. const nextState = createMockState({
  168. matrixify_topn_value_x: 5,
  169. matrixify_topn_metric_x: 'metric1',
  170. matrixify_topn_order_x: 'desc',
  171. matrixify_dimension_selection_mode_x: 'members', // Changed
  172. });
  173. expect(control.shouldMapStateToProps!(prevState, nextState)).toBe(true);
  174. });
  175. test('matrixify_dimension_x should return false when no relevant fields change', () => {
  176. const control = mockSharedControls.matrixify_dimension_x;
  177. const prevState = createMockState({
  178. matrixify_topn_value_x: 5,
  179. matrixify_topn_metric_x: 'metric1',
  180. matrixify_topn_order_x: 'desc',
  181. matrixify_dimension_selection_mode_x: 'topn',
  182. unrelated_field: 'value1',
  183. });
  184. const nextState = createMockState({
  185. matrixify_topn_value_x: 5,
  186. matrixify_topn_metric_x: 'metric1',
  187. matrixify_topn_order_x: 'desc',
  188. matrixify_dimension_selection_mode_x: 'topn',
  189. unrelated_field: 'value2', // Changed, but not relevant
  190. });
  191. expect(control.shouldMapStateToProps!(prevState, nextState)).toBe(false);
  192. });
  193. test('matrixify_dimension_x should return false when states are identical', () => {
  194. const control = mockSharedControls.matrixify_dimension_x;
  195. const state = createMockState({
  196. matrixify_topn_value_x: 5,
  197. matrixify_topn_metric_x: 'metric1',
  198. matrixify_topn_order_x: 'desc',
  199. matrixify_dimension_selection_mode_x: 'topn',
  200. });
  201. expect(control.shouldMapStateToProps!(state, state)).toBe(false);
  202. });
  203. test('matrixify_dimension_x should handle missing form_data gracefully', () => {
  204. const control = mockSharedControls.matrixify_dimension_x;
  205. const prevState = createMockState(); // No form_data
  206. const nextState = createMockState({
  207. matrixify_topn_value_x: 5,
  208. });
  209. expect(control.shouldMapStateToProps!(prevState, nextState)).toBe(true);
  210. });
  211. test('matrixify_dimension_x should handle undefined values gracefully', () => {
  212. const control = mockSharedControls.matrixify_dimension_x;
  213. const prevState = createMockState({
  214. matrixify_topn_value_x: undefined,
  215. matrixify_topn_metric_x: null,
  216. });
  217. const nextState = createMockState({
  218. matrixify_topn_value_x: 5,
  219. matrixify_topn_metric_x: 'metric1',
  220. });
  221. expect(control.shouldMapStateToProps!(prevState, nextState)).toBe(true);
  222. });
  223. test('matrixify_dimension_y should check y-axis specific fields', () => {
  224. const control = mockSharedControls.matrixify_dimension_y;
  225. const prevState = createMockState({
  226. matrixify_topn_value_y: 5,
  227. matrixify_topn_metric_y: 'metric1',
  228. });
  229. const nextState = createMockState({
  230. matrixify_topn_value_y: 10, // Changed
  231. matrixify_topn_metric_y: 'metric1',
  232. });
  233. expect(control.shouldMapStateToProps!(prevState, nextState)).toBe(true);
  234. });
  235. test('matrixify_dimension_y should not trigger on x-axis changes', () => {
  236. const control = mockSharedControls.matrixify_dimension_y;
  237. const prevState = createMockState({
  238. matrixify_topn_value_x: 5, // x-axis field
  239. matrixify_topn_value_y: 5, // y-axis field (unchanged)
  240. });
  241. const nextState = createMockState({
  242. matrixify_topn_value_x: 10, // x-axis field changed
  243. matrixify_topn_value_y: 5, // y-axis field (unchanged)
  244. });
  245. expect(control.shouldMapStateToProps!(prevState, nextState)).toBe(false);
  246. });
  247. test('mapStateToProps should map form_data values correctly', () => {
  248. const control = mockSharedControls.matrixify_dimension_x;
  249. const state = createMockState({
  250. matrixify_dimension_selection_mode_x: 'topn',
  251. matrixify_topn_metric_x: 'metric1',
  252. matrixify_topn_value_x: 10,
  253. matrixify_topn_order_x: 'desc',
  254. });
  255. const mockDatasource: any = { id: 1, columns: [] };
  256. state.datasource = mockDatasource;
  257. const result = control.mapStateToProps!(state);
  258. expect(result).toEqual({
  259. datasource: mockDatasource,
  260. selectionMode: 'topn',
  261. topNMetric: 'metric1',
  262. topNValue: 10,
  263. topNOrder: 'desc',
  264. formData: state.form_data,
  265. });
  266. });
  267. test('mapStateToProps should fall back to control values when form_data is missing', () => {
  268. const control = mockSharedControls.matrixify_dimension_x;
  269. const state = createMockState(
  270. {}, // Empty form_data
  271. {
  272. matrixify_dimension_selection_mode_x: createMockControlState('members'),
  273. matrixify_topn_metric_x: createMockControlState('metric2'),
  274. matrixify_topn_value_x: createMockControlState(15),
  275. },
  276. );
  277. const result = control.mapStateToProps!(state);
  278. expect(result.selectionMode).toBe('members');
  279. expect(result.topNMetric).toBe('metric2');
  280. expect(result.topNValue).toBe(15);
  281. });
  282. test('mapStateToProps should use default values when both form_data and controls are missing', () => {
  283. const control = mockSharedControls.matrixify_dimension_x;
  284. const state = createMockState({}, {});
  285. const result = control.mapStateToProps!(state);
  286. expect(result.selectionMode).toBe('members'); // Default value
  287. expect(result.topNMetric).toBeUndefined();
  288. expect(result.topNValue).toBeUndefined();
  289. expect(result.topNOrder).toBeUndefined();
  290. });
  291. test('mapStateToProps should prioritize form_data over control values', () => {
  292. const control = mockSharedControls.matrixify_dimension_x;
  293. const state = createMockState(
  294. {
  295. matrixify_dimension_selection_mode_x: 'topn', // form_data value
  296. },
  297. {
  298. matrixify_dimension_selection_mode_x: createMockControlState('members'), // control value
  299. },
  300. );
  301. const result = control.mapStateToProps!(state);
  302. expect(result.selectionMode).toBe('topn'); // Should use form_data value
  303. });
  304. test('should efficiently check only relevant fields', () => {
  305. const control = mockSharedControls.matrixify_dimension_x;
  306. const prevState = createMockState({
  307. // Many fields, only some relevant
  308. field1: 'value1',
  309. field2: 'value2',
  310. matrixify_topn_value_x: 5, // Relevant
  311. field3: 'value3',
  312. matrixify_topn_metric_x: 'metric1', // Relevant
  313. field4: 'value4',
  314. matrixify_other_control: 'value5',
  315. });
  316. const nextState = createMockState({
  317. field1: 'value1_changed', // Not relevant
  318. field2: 'value2_changed', // Not relevant
  319. matrixify_topn_value_x: 5, // Relevant, unchanged
  320. field3: 'value3_changed', // Not relevant
  321. matrixify_topn_metric_x: 'metric1', // Relevant, unchanged
  322. field4: 'value4_changed', // Not relevant
  323. matrixify_other_control: 'value5_changed', // Not relevant
  324. });
  325. // Should return false because no relevant fields changed
  326. expect(control.shouldMapStateToProps!(prevState, nextState)).toBe(false);
  327. });