vitess.io/vitess@v0.16.2/web/vtadmin/src/hooks/useURLQuery.test.tsx (about)

     1  /**
     2   * Copyright 2021 The Vitess Authors.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  import { act } from '@testing-library/react';
    17  import { renderHook } from '@testing-library/react-hooks';
    18  import { createMemoryHistory } from 'history';
    19  import { Router } from 'react-router-dom';
    20  import { QueryParams } from '../util/queryString';
    21  
    22  import { useURLQuery, URLQueryOptions } from './useURLQuery';
    23  
    24  describe('useURLQuery', () => {
    25      describe('parsing', () => {
    26          const tests: {
    27              name: string;
    28              url: string;
    29              opts: URLQueryOptions | undefined;
    30              expected: ReturnType<typeof useURLQuery>['query'];
    31          }[] = [
    32              {
    33                  name: 'parses complex URLs',
    34                  url: '/test?page=1&isTrue=true&isFalse=false&list=one&list=two&list=three&foo=bar',
    35                  opts: undefined,
    36                  expected: { page: '1', isTrue: 'true', isFalse: 'false', list: ['one', 'two', 'three'], foo: 'bar' },
    37              },
    38              {
    39                  name: 'parses numbers',
    40                  url: '/test?page=1',
    41                  opts: { parseNumbers: true },
    42                  expected: { page: 1 },
    43              },
    44              {
    45                  name: 'parses booleans',
    46                  url: '/test?foo=true&bar=false',
    47                  opts: { parseBooleans: true },
    48                  expected: { foo: true, bar: false },
    49              },
    50              {
    51                  name: 'parses arrays by duplicate keys',
    52                  url: '/test?list=1&list=2&list=3',
    53                  opts: { parseNumbers: true },
    54                  expected: { list: [1, 2, 3] },
    55              },
    56          ];
    57  
    58          test.each(tests.map(Object.values))(
    59              '%s',
    60              (
    61                  name: string,
    62                  url: string,
    63                  opts: URLQueryOptions | undefined,
    64                  expected: ReturnType<typeof useURLQuery>
    65              ) => {
    66                  const history = createMemoryHistory({
    67                      initialEntries: [url],
    68                  });
    69  
    70                  const { result } = renderHook(() => useURLQuery(opts), {
    71                      wrapper: ({ children }) => {
    72                          return <Router history={history}>{children}</Router>;
    73                      },
    74                  });
    75  
    76                  expect(result.current.query).toEqual(expected);
    77              }
    78          );
    79      });
    80  
    81      describe('pushQuery', () => {
    82          const tests: {
    83              name: string;
    84              initialEntries: string[];
    85              nextQuery: QueryParams;
    86              expected: QueryParams;
    87          }[] = [
    88              {
    89                  name: 'stringifies and pushes query parameters onto history',
    90                  initialEntries: ['/test'],
    91                  nextQuery: { goodnight: 'moon' },
    92                  expected: { goodnight: 'moon' },
    93              },
    94              {
    95                  name: 'merges the next query with the current query',
    96                  initialEntries: ['/test?hello=world'],
    97                  nextQuery: { goodnight: 'moon' },
    98                  expected: { hello: 'world', goodnight: 'moon' },
    99              },
   100              {
   101                  name: 'it does not merge array-like queries',
   102                  initialEntries: ['/test?arr=one&arr=two'],
   103                  nextQuery: { arr: ['three', 'four', 'five'] },
   104                  expected: { arr: ['three', 'four', 'five'] },
   105              },
   106          ];
   107  
   108          test.concurrent.each(tests.map(Object.values))(
   109              '%s',
   110              (name: string, initialEntries: string[], nextQuery: QueryParams, expected: QueryParams) => {
   111                  const history = createMemoryHistory({ initialEntries });
   112                  const initialPathname = history.location.pathname;
   113  
   114                  jest.spyOn(history, 'push');
   115  
   116                  const { result } = renderHook(() => useURLQuery(), {
   117                      wrapper: ({ children }) => {
   118                          return <Router history={history}>{children}</Router>;
   119                      },
   120                  });
   121  
   122                  act(() => {
   123                      result.current.pushQuery(nextQuery);
   124                  });
   125  
   126                  expect(history.push).toHaveBeenCalledTimes(1);
   127                  expect(result.current.query).toEqual(expected);
   128                  expect(history.location.pathname).toEqual(initialPathname);
   129              }
   130          );
   131      });
   132  
   133      describe('replaceQuery', () => {
   134          const tests: {
   135              name: string;
   136              initialEntries: string[];
   137              nextQuery: QueryParams;
   138              expected: QueryParams;
   139          }[] = [
   140              {
   141                  name: 'stringifies and replaces query parameters onto history',
   142                  initialEntries: ['/test'],
   143                  nextQuery: { goodnight: 'moon' },
   144                  expected: { goodnight: 'moon' },
   145              },
   146              {
   147                  name: 'merges the next query with the current query',
   148                  initialEntries: ['/test?hello=world'],
   149                  nextQuery: { goodnight: 'moon' },
   150                  expected: { hello: 'world', goodnight: 'moon' },
   151              },
   152              {
   153                  name: 'it does not merge array-like queries',
   154                  initialEntries: ['/test?arr=one&arr=two'],
   155                  nextQuery: { arr: ['three', 'four', 'five'] },
   156                  expected: { arr: ['three', 'four', 'five'] },
   157              },
   158          ];
   159  
   160          test.concurrent.each(tests.map(Object.values))(
   161              '%s',
   162              (name: string, initialEntries: string[], nextQuery: QueryParams, expected: QueryParams) => {
   163                  const history = createMemoryHistory({ initialEntries });
   164                  const initialPathname = history.location.pathname;
   165  
   166                  jest.spyOn(history, 'replace');
   167  
   168                  const { result } = renderHook(() => useURLQuery(), {
   169                      wrapper: ({ children }) => {
   170                          return <Router history={history}>{children}</Router>;
   171                      },
   172                  });
   173  
   174                  act(() => {
   175                      result.current.replaceQuery(nextQuery);
   176                  });
   177  
   178                  expect(history.replace).toHaveBeenCalledTimes(1);
   179                  expect(result.current.query).toEqual(expected);
   180                  expect(history.location.pathname).toEqual(initialPathname);
   181              }
   182          );
   183      });
   184  
   185      it('uses parsing/formatting options when specified', () => {
   186          const history = createMemoryHistory({ initialEntries: ['/test?foo=true&count=123'] });
   187          const { result } = renderHook(
   188              () =>
   189                  useURLQuery({
   190                      parseBooleans: false,
   191                      parseNumbers: false,
   192                  }),
   193              {
   194                  wrapper: ({ children }) => {
   195                      return <Router history={history}>{children}</Router>;
   196                  },
   197              }
   198          );
   199  
   200          expect(result.current.query).toEqual({ foo: 'true', count: '123' });
   201  
   202          act(() => {
   203              result.current.pushQuery({ foo: false, count: 456 });
   204          });
   205  
   206          expect(result.current.query).toEqual({ foo: 'false', count: '456' });
   207  
   208          act(() => {
   209              result.current.pushQuery({ foo: true, count: 789 });
   210          });
   211  
   212          expect(result.current.query).toEqual({ foo: 'true', count: '789' });
   213      });
   214  
   215      it('memoizes the query object by search string', () => {
   216          const history = createMemoryHistory({ initialEntries: ['/test?hello=world'] });
   217  
   218          jest.spyOn(history, 'push');
   219  
   220          const { result } = renderHook(() => useURLQuery(), {
   221              wrapper: ({ children }) => {
   222                  return <Router history={history}>{children}</Router>;
   223              },
   224          });
   225  
   226          const firstResult = result.current.query;
   227          expect(firstResult).toEqual({ hello: 'world' });
   228  
   229          act(() => {
   230              result.current.pushQuery({ hello: 'world' });
   231          });
   232  
   233          // Make sure the returned object is memoized when the search string
   234          // is updated but the value doesn't change.
   235          expect(history.push).toHaveBeenCalledTimes(1);
   236          expect(result.current.query).toEqual({ hello: 'world' });
   237          expect(result.current.query).toBe(firstResult);
   238  
   239          // Make sure the returned query actually changes when the search string changes,
   240          // and that we're not memoizing too aggressively.
   241          act(() => {
   242              result.current.pushQuery({ hello: 'moon' });
   243          });
   244  
   245          expect(history.push).toHaveBeenCalledTimes(2);
   246          expect(result.current.query).toEqual({ hello: 'moon' });
   247      });
   248  });