ChartDataProvider.test.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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 '@testing-library/jest-dom';
  20. import { render, screen, act } from '@testing-library/react';
  21. import ChartClient from '../../../src/chart/clients/ChartClient';
  22. import ChartDataProvider, {
  23. ChartDataProviderProps,
  24. } from '../../../src/chart/components/ChartDataProvider';
  25. import { bigNumberFormData } from '../fixtures/formData';
  26. // Keep existing mock setup
  27. const defaultMockLoadFormData = jest.fn(({ formData }: { formData: unknown }) =>
  28. Promise.resolve(formData),
  29. );
  30. type MockLoadFormData =
  31. | typeof defaultMockLoadFormData
  32. | jest.Mock<Promise<unknown>, unknown[]>;
  33. let mockLoadFormData: MockLoadFormData = defaultMockLoadFormData;
  34. function createPromise<T>(input: T) {
  35. return Promise.resolve(input);
  36. }
  37. function createArrayPromise<T>(input: T) {
  38. return Promise.resolve([input]);
  39. }
  40. const mockLoadDatasource = jest.fn<Promise<unknown>, unknown[]>(createPromise);
  41. const mockLoadQueryData = jest.fn<Promise<unknown>, unknown[]>(
  42. createArrayPromise,
  43. );
  44. const actual = jest.requireActual('../../../src/chart/clients/ChartClient');
  45. jest.spyOn(actual, 'default').mockImplementation(() => ({
  46. loadDatasource: mockLoadDatasource,
  47. loadFormData: mockLoadFormData,
  48. loadQueryData: mockLoadQueryData,
  49. }));
  50. const ChartClientMock = ChartClient as jest.Mock<ChartClient>;
  51. describe('ChartDataProvider', () => {
  52. beforeEach(() => {
  53. ChartClientMock.mockClear();
  54. mockLoadFormData = defaultMockLoadFormData;
  55. mockLoadFormData.mockClear();
  56. mockLoadDatasource.mockClear();
  57. mockLoadQueryData.mockClear();
  58. });
  59. const props: ChartDataProviderProps = {
  60. formData: { ...bigNumberFormData },
  61. children: ({ loading, payload, error }) => (
  62. <div>
  63. {loading && <span role="status">Loading...</span>}
  64. {payload && <pre role="contentinfo">{JSON.stringify(payload)}</pre>}
  65. {error && <div role="alert">{error.message}</div>}
  66. </div>
  67. ),
  68. };
  69. function setup(overrideProps?: Partial<ChartDataProviderProps>) {
  70. return render(<ChartDataProvider {...props} {...overrideProps} />);
  71. }
  72. it('instantiates a new ChartClient()', () => {
  73. setup();
  74. expect(ChartClientMock).toHaveBeenCalledTimes(1);
  75. });
  76. describe('ChartClient.loadFormData', () => {
  77. it('calls method on mount', () => {
  78. setup();
  79. expect(mockLoadFormData).toHaveBeenCalledTimes(1);
  80. expect(mockLoadFormData.mock.calls[0][0]).toEqual({
  81. sliceId: props.sliceId,
  82. formData: props.formData,
  83. });
  84. });
  85. it('should pass formDataRequestOptions to ChartClient.loadFormData', () => {
  86. const options = { host: 'override' };
  87. setup({ formDataRequestOptions: options });
  88. expect(mockLoadFormData).toHaveBeenCalledTimes(1);
  89. expect(mockLoadFormData.mock.calls[0][1]).toEqual(options);
  90. });
  91. it('calls ChartClient.loadFormData when formData or sliceId change', async () => {
  92. const { rerender } = setup();
  93. const newProps = { sliceId: 123, formData: undefined };
  94. expect(mockLoadFormData).toHaveBeenCalledTimes(1);
  95. rerender(<ChartDataProvider {...props} {...newProps} />);
  96. expect(mockLoadFormData).toHaveBeenCalledTimes(2);
  97. expect(mockLoadFormData.mock.calls[1][0]).toEqual(newProps);
  98. });
  99. });
  100. describe('ChartClient.loadDatasource', () => {
  101. it('does not call method if loadDatasource is false', async () => {
  102. setup({ loadDatasource: false });
  103. await act(async () => {
  104. await new Promise(resolve => setTimeout(resolve, 0));
  105. });
  106. expect(mockLoadDatasource).not.toHaveBeenCalled();
  107. });
  108. it('calls method on mount if loadDatasource is true', async () => {
  109. setup({ loadDatasource: true });
  110. await act(async () => {
  111. await new Promise(resolve => setTimeout(resolve, 0));
  112. });
  113. expect(mockLoadDatasource).toHaveBeenCalledTimes(1);
  114. expect(mockLoadDatasource.mock.calls[0]).toEqual([
  115. props.formData.datasource,
  116. undefined,
  117. ]);
  118. });
  119. it('should pass datasourceRequestOptions to ChartClient.loadDatasource', async () => {
  120. const options = { host: 'override' };
  121. setup({ loadDatasource: true, datasourceRequestOptions: options });
  122. await act(async () => {
  123. await new Promise(resolve => setTimeout(resolve, 0));
  124. });
  125. expect(mockLoadDatasource).toHaveBeenCalledTimes(1);
  126. expect(mockLoadDatasource.mock.calls[0][1]).toEqual(options);
  127. });
  128. it('calls ChartClient.loadDatasource if loadDatasource is true and formData or sliceId change', async () => {
  129. const { rerender } = setup({ loadDatasource: true });
  130. const newDatasource = 'test';
  131. await act(async () => {
  132. await new Promise(resolve => setTimeout(resolve, 0));
  133. });
  134. await act(async () => {
  135. rerender(
  136. <ChartDataProvider
  137. {...props}
  138. formData={{ ...props.formData, datasource: newDatasource }}
  139. loadDatasource
  140. />,
  141. );
  142. await new Promise(resolve => setTimeout(resolve, 0));
  143. });
  144. expect(mockLoadDatasource).toHaveBeenCalledTimes(2);
  145. expect(mockLoadDatasource.mock.calls[0]).toEqual([
  146. props.formData.datasource,
  147. undefined,
  148. ]);
  149. expect(mockLoadDatasource.mock.calls[1]).toEqual([
  150. newDatasource,
  151. undefined,
  152. ]);
  153. });
  154. });
  155. describe('ChartClient.loadQueryData', () => {
  156. it('calls method on mount', async () => {
  157. setup();
  158. await act(async () => {
  159. await new Promise(resolve => setTimeout(resolve, 0));
  160. });
  161. expect(mockLoadQueryData).toHaveBeenCalledTimes(1);
  162. expect(mockLoadQueryData.mock.calls[0]).toEqual([
  163. props.formData,
  164. undefined,
  165. ]);
  166. });
  167. it('should pass queryDataRequestOptions to ChartClient.loadQueryData', async () => {
  168. const options = { host: 'override' };
  169. setup({ queryRequestOptions: options });
  170. await act(async () => {
  171. await new Promise(resolve => setTimeout(resolve, 0));
  172. });
  173. expect(mockLoadQueryData).toHaveBeenCalledTimes(1);
  174. expect(mockLoadQueryData).toHaveBeenCalledWith(
  175. expect.anything(),
  176. options,
  177. );
  178. });
  179. it('calls ChartClient.loadQueryData when formData or sliceId change', async () => {
  180. const { rerender } = setup();
  181. const newFormData = { key: 'test' };
  182. await act(async () => {
  183. await new Promise(resolve => setTimeout(resolve, 0));
  184. });
  185. await act(async () => {
  186. rerender(<ChartDataProvider {...props} formData={newFormData} />);
  187. await new Promise(resolve => setTimeout(resolve, 0));
  188. });
  189. expect(mockLoadQueryData).toHaveBeenCalledTimes(2);
  190. expect(mockLoadQueryData.mock.calls[0]).toEqual([
  191. props.formData,
  192. undefined,
  193. ]);
  194. expect(mockLoadQueryData.mock.calls[1]).toEqual([newFormData, undefined]);
  195. });
  196. });
  197. describe('children', () => {
  198. it('shows loading state initially', async () => {
  199. mockLoadFormData.mockImplementation(() => new Promise(() => {}));
  200. mockLoadQueryData.mockImplementation(() => new Promise(() => {}));
  201. mockLoadDatasource.mockImplementation(() => new Promise(() => {}));
  202. setup();
  203. await screen.findByRole('status');
  204. });
  205. it('shows payload when loaded', async () => {
  206. mockLoadFormData.mockResolvedValue(props.formData);
  207. mockLoadQueryData.mockResolvedValue([props.formData]);
  208. mockLoadDatasource.mockResolvedValue(props.formData.datasource);
  209. setup({ loadDatasource: true });
  210. const payloadElement = await screen.findByRole('contentinfo');
  211. const actualPayload = JSON.parse(payloadElement.textContent || '');
  212. expect(actualPayload).toEqual({
  213. formData: props.formData,
  214. datasource: props.formData.datasource,
  215. queriesData: [props.formData],
  216. });
  217. });
  218. it('shows error message upon request error', async () => {
  219. const errorMessage = 'error';
  220. mockLoadFormData.mockRejectedValue(new Error(errorMessage));
  221. setup();
  222. const errorElement = await screen.findByRole('alert');
  223. expect(errorElement).toHaveAttribute('role', 'alert');
  224. expect(errorElement).toHaveTextContent(errorMessage);
  225. });
  226. it('shows error message upon JS error', async () => {
  227. mockLoadFormData.mockImplementation(() => {
  228. throw new Error('non-async error');
  229. });
  230. setup();
  231. const errorElement = await screen.findByRole('alert');
  232. expect(errorElement).toHaveAttribute('role', 'alert');
  233. expect(errorElement).toHaveTextContent('non-async error');
  234. });
  235. });
  236. describe('callbacks', () => {
  237. it('calls onLoaded when loaded', async () => {
  238. const onLoaded = jest.fn();
  239. mockLoadFormData.mockResolvedValue(props.formData);
  240. mockLoadQueryData.mockResolvedValue([props.formData]);
  241. mockLoadDatasource.mockResolvedValue(props.formData.datasource);
  242. setup({ onLoaded, loadDatasource: true });
  243. await act(async () => {
  244. await new Promise(resolve => setTimeout(resolve, 0));
  245. });
  246. expect(onLoaded).toHaveBeenCalledTimes(1);
  247. expect(onLoaded).toHaveBeenCalledWith({
  248. formData: props.formData,
  249. datasource: props.formData.datasource,
  250. queriesData: [props.formData],
  251. });
  252. });
  253. it('calls onError upon request error', async () => {
  254. const onError = jest.fn();
  255. mockLoadFormData.mockRejectedValue(new Error('error'));
  256. setup({ onError });
  257. await act(async () => {
  258. await new Promise(resolve => setTimeout(resolve, 0));
  259. });
  260. expect(onError).toHaveBeenCalledTimes(1);
  261. expect(onError).toHaveBeenCalledWith(new Error('error'));
  262. });
  263. it('calls onError upon JS error', async () => {
  264. const onError = jest.fn();
  265. mockLoadFormData.mockImplementation(() => {
  266. throw new Error('non-async error');
  267. });
  268. setup({ onError });
  269. await act(async () => {
  270. await new Promise(resolve => setTimeout(resolve, 0));
  271. });
  272. expect(onError).toHaveBeenCalledTimes(1);
  273. expect(onError).toHaveBeenCalledWith(new Error('non-async error'));
  274. });
  275. });
  276. });