vitess.io/vitess@v0.16.2/web/vtadmin/src/hooks/api.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 React from 'react'; 17 import { QueryClient, QueryClientProvider } from 'react-query'; 18 import { renderHook } from '@testing-library/react-hooks'; 19 20 import * as api from './api'; 21 import * as httpAPI from '../api/http'; 22 import { vtadmin as pb } from '../proto/vtadmin'; 23 24 jest.mock('../api/http'); 25 26 describe('useWorkflows', () => { 27 const tests: { 28 name: string; 29 response: pb.GetWorkflowsResponse | undefined; 30 expected: pb.Workflow[] | undefined; 31 }[] = [ 32 { 33 name: 'returns a flat list of workflows', 34 response: pb.GetWorkflowsResponse.create({ 35 workflows_by_cluster: { 36 east: { 37 workflows: [ 38 { 39 workflow: { name: 'one-goes-east' }, 40 }, 41 ], 42 }, 43 west: { 44 workflows: [ 45 { 46 workflow: { name: 'one-goes-west' }, 47 }, 48 ], 49 }, 50 }, 51 }), 52 expected: [ 53 pb.Workflow.create({ workflow: { name: 'one-goes-east' } }), 54 pb.Workflow.create({ workflow: { name: 'one-goes-west' } }), 55 ], 56 }, 57 ]; 58 59 const queryClient = new QueryClient(); 60 const wrapper: React.FunctionComponent = ({ children }) => ( 61 <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> 62 ); 63 64 test.each(tests.map(Object.values))( 65 '%s', 66 async (name: string, response: pb.GetWorkflowsResponse | undefined, expected: pb.Workflow[] | undefined) => { 67 (httpAPI.fetchWorkflows as any).mockResolvedValueOnce(response); 68 69 const { result, waitFor } = renderHook(() => api.useWorkflows(), { wrapper }); 70 71 // Check that our query helper handles when the query is still in flight 72 expect(result.current.data).toBeUndefined(); 73 74 // "Wait" for the underlying fetch request to resolve (scare-quotes because, 75 // in practice, we're not "waiting" for anything since the response is mocked.) 76 await waitFor(() => result.current.isSuccess); 77 78 expect(result.current.data).toEqual(expected); 79 } 80 ); 81 }); 82 83 describe('useWorkflow', () => { 84 it('fetches data if no cached data exists', async () => { 85 const queryClient = new QueryClient(); 86 const wrapper: React.FunctionComponent = ({ children }) => ( 87 <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> 88 ); 89 90 const fetchWorkflowResponse = pb.Workflow.create({ 91 cluster: { name: 'cluster1', id: 'cluster1' }, 92 keyspace: 'dogs', 93 workflow: { 94 name: 'one-goes-west', 95 max_v_replication_lag: 0, 96 }, 97 }); 98 99 (httpAPI.fetchWorkflow as any).mockResolvedValueOnce(fetchWorkflowResponse); 100 101 const { result, waitFor } = renderHook( 102 () => 103 api.useWorkflow({ 104 clusterID: 'cluster1', 105 keyspace: 'dogs', 106 name: 'one-goes-west', 107 }), 108 { wrapper } 109 ); 110 111 expect(result.current.data).toBeUndefined(); 112 113 await waitFor(() => result.current.isSuccess); 114 expect(result.current.data).toEqual(fetchWorkflowResponse); 115 }); 116 117 // This test corresponds to a common UI flow from a component that fetches all the workflows 118 // to a component that fetches a single workflow. 119 it('uses cached data as initialData', async () => { 120 const queryClient = new QueryClient(); 121 const wrapper: React.FunctionComponent = ({ children }) => ( 122 <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> 123 ); 124 125 (httpAPI.fetchWorkflows as any).mockResolvedValueOnce( 126 pb.GetWorkflowsResponse.create({ 127 workflows_by_cluster: { 128 cluster1: { 129 workflows: [ 130 { 131 cluster: { name: 'cluster1', id: 'cluster1' }, 132 keyspace: 'dogs', 133 workflow: { 134 name: 'one-goes-east', 135 max_v_replication_lag: 0, 136 }, 137 }, 138 { 139 cluster: { name: 'cluster1', id: 'cluster1' }, 140 keyspace: 'dogs', 141 workflow: { 142 name: 'one-goes-west', 143 max_v_replication_lag: 0, 144 }, 145 }, 146 ], 147 }, 148 cluster2: { 149 workflows: [ 150 { 151 cluster: { name: 'cluster2', id: 'cluster2' }, 152 keyspace: 'dogs', 153 workflow: { 154 name: 'one-goes-west', 155 max_v_replication_lag: 0, 156 }, 157 }, 158 ], 159 }, 160 }, 161 }) 162 ); 163 164 (httpAPI.fetchWorkflow as any).mockResolvedValueOnce( 165 pb.Workflow.create({ 166 cluster: { name: 'cluster1', id: 'cluster1' }, 167 keyspace: 'dogs', 168 workflow: { 169 name: 'one-goes-west', 170 max_v_replication_lag: 420, 171 }, 172 }) 173 ); 174 175 // Execute a useWorkflows query to populate the query cache. 176 // eslint-disable-next-line testing-library/render-result-naming-convention 177 const useWorkflowsCall = renderHook(() => api.useWorkflows(), { wrapper }); 178 await useWorkflowsCall.waitFor(() => useWorkflowsCall.result.current.isSuccess); 179 180 // Next, execute the useWorkflow query we *actually* want to inspect. 181 const { result, waitFor } = renderHook( 182 () => 183 api.useWorkflow( 184 { 185 clusterID: 'cluster1', 186 keyspace: 'dogs', 187 name: 'one-goes-west', 188 }, 189 // Force the query to refetch 190 { staleTime: 0 } 191 ), 192 { wrapper } 193 ); 194 195 // We expect the result to be successful, even though we've yet to resolve the /workflow API call, 196 // since the workflow we want exists in the cache. 197 expect(result.current.isSuccess).toBe(true); 198 199 expect(result.current.data).toEqual( 200 pb.Workflow.create({ 201 cluster: { name: 'cluster1', id: 'cluster1' }, 202 keyspace: 'dogs', 203 workflow: { 204 name: 'one-goes-west', 205 max_v_replication_lag: 0, 206 }, 207 }) 208 ); 209 210 // We _also_ check that a fetch request is in-flight to fetch updated data. 211 expect(result.current.isFetching).toBe(true); 212 expect(httpAPI.fetchWorkflow).toHaveBeenCalledTimes(1); 213 214 // Then, we resolve the API call, with updated data (in this case max_v_replication_lag) 215 // so we can check that the cache is updated. 216 await waitFor(() => !result.current.isFetching && result.current.isSuccess); 217 expect(result.current.data).toEqual( 218 pb.Workflow.create({ 219 cluster: { name: 'cluster1', id: 'cluster1' }, 220 keyspace: 'dogs', 221 workflow: { 222 name: 'one-goes-west', 223 max_v_replication_lag: 420, 224 }, 225 }) 226 ); 227 }); 228 });