useQueryParamState.spec.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import {LocationFixture} from 'sentry-fixture/locationFixture';
  2. import {act, renderHook} from 'sentry-test/reactTestingLibrary';
  3. import type {Sort} from 'sentry/utils/discover/fields';
  4. import {decodeSorts} from 'sentry/utils/queryString';
  5. import {useLocation} from 'sentry/utils/useLocation';
  6. import {useNavigate} from 'sentry/utils/useNavigate';
  7. import {UrlParamBatchProvider} from 'sentry/views/dashboards/widgetBuilder/contexts/urlParamBatchContext';
  8. import {useQueryParamState} from 'sentry/views/dashboards/widgetBuilder/hooks/useQueryParamState';
  9. import {formatSort} from 'sentry/views/explore/contexts/pageParamsContext/sortBys';
  10. jest.mock('sentry/utils/useLocation');
  11. jest.mock('sentry/utils/useNavigate');
  12. const mockedUseLocation = jest.mocked(useLocation);
  13. const mockedUseNavigate = jest.mocked(useNavigate);
  14. describe('useQueryParamState', () => {
  15. beforeEach(() => {
  16. jest.useFakeTimers();
  17. });
  18. afterEach(() => {
  19. jest.useRealTimers();
  20. });
  21. it('should get the initial value from the query param', () => {
  22. mockedUseLocation.mockReturnValue(
  23. LocationFixture({query: {testField: 'initial state'}})
  24. );
  25. const {result} = renderHook(() => useQueryParamState({fieldName: 'testField'}), {
  26. wrapper: UrlParamBatchProvider,
  27. });
  28. expect(result.current[0]).toBe('initial state');
  29. });
  30. it('should update the local state and the query param', () => {
  31. const mockedNavigate = jest.fn();
  32. mockedUseNavigate.mockReturnValue(mockedNavigate);
  33. const {result} = renderHook(() => useQueryParamState({fieldName: 'testField'}), {
  34. wrapper: UrlParamBatchProvider,
  35. });
  36. act(() => {
  37. result.current[1]('newValue');
  38. });
  39. // The local state should be updated
  40. expect(result.current[0]).toBe('newValue');
  41. // The query param should not be updated yet
  42. expect(mockedNavigate).not.toHaveBeenCalledWith(
  43. {
  44. ...LocationFixture(),
  45. query: {testField: 'initial state'},
  46. },
  47. {replace: true}
  48. );
  49. // Run the timers to trigger queued updates
  50. jest.runAllTimers();
  51. // The query param should be updated
  52. expect(mockedNavigate).toHaveBeenCalledWith(
  53. {
  54. ...LocationFixture(),
  55. query: {testField: 'newValue'},
  56. },
  57. {replace: true}
  58. );
  59. // The local state should be still reflect the new value
  60. expect(result.current[0]).toBe('newValue');
  61. });
  62. it('should use the decoder function to decode the query param value if provided', () => {
  63. mockedUseLocation.mockReturnValue(
  64. LocationFixture({query: {testField: 'initial state'}})
  65. );
  66. const testDeserializer = (value: string) => `${value.toUpperCase()} - decoded`;
  67. const {result} = renderHook(
  68. () => useQueryParamState({fieldName: 'testField', deserializer: testDeserializer}),
  69. {
  70. wrapper: UrlParamBatchProvider,
  71. }
  72. );
  73. expect(result.current[0]).toBe('INITIAL STATE - decoded');
  74. });
  75. it('can take any kind of value and serialize it to a string compatible with query params', () => {
  76. type TestType = {
  77. count: number;
  78. isActive: boolean;
  79. value: string;
  80. };
  81. const mockedNavigate = jest.fn();
  82. mockedUseNavigate.mockReturnValue(mockedNavigate);
  83. const testSerializer = (value: TestType) =>
  84. `${value.value} - ${value.count} - ${value.isActive}`;
  85. const {result} = renderHook(
  86. () => useQueryParamState({fieldName: 'testField', serializer: testSerializer}),
  87. {
  88. wrapper: UrlParamBatchProvider,
  89. }
  90. );
  91. act(() => {
  92. result.current[1]({value: 'newValue', count: 2, isActive: true});
  93. });
  94. expect(mockedNavigate).toHaveBeenCalledWith(
  95. {
  96. ...LocationFixture(),
  97. query: {testField: 'newValue - 2 - true'},
  98. },
  99. {replace: true}
  100. );
  101. });
  102. it('can decode and update sorts', () => {
  103. mockedUseLocation.mockReturnValue(LocationFixture({query: {sort: '-testField'}}));
  104. const mockedNavigate = jest.fn();
  105. mockedUseNavigate.mockReturnValue(mockedNavigate);
  106. const {result} = renderHook(
  107. () =>
  108. useQueryParamState<Sort[]>({
  109. fieldName: 'sort',
  110. decoder: decodeSorts,
  111. serializer: value => value.map(formatSort),
  112. }),
  113. {
  114. wrapper: UrlParamBatchProvider,
  115. }
  116. );
  117. expect(result.current[0]).toEqual([{field: 'testField', kind: 'desc'}]);
  118. act(() => {
  119. result.current[1]([{field: 'testField', kind: 'asc'}]);
  120. });
  121. expect(mockedNavigate).toHaveBeenCalledWith(
  122. {
  123. ...LocationFixture(),
  124. query: {sort: ['testField']},
  125. },
  126. {replace: true}
  127. );
  128. });
  129. });