vitess.io/vitess@v0.16.2/web/vtadmin/src/components/ActionPanel.test.tsx (about) 1 /** 2 * Copyright 2022 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 17 import { render, screen, waitFor } from '@testing-library/react'; 18 import userEvent from '@testing-library/user-event'; 19 import { rest } from 'msw'; 20 import { setupServer } from 'msw/node'; 21 import { QueryClient, QueryClientProvider, useMutation } from 'react-query'; 22 23 import ActionPanel, { ActionPanelProps } from './ActionPanel'; 24 25 const ORIGINAL_PROCESS_ENV = process.env; 26 const TEST_PROCESS_ENV = { 27 ...process.env, 28 REACT_APP_VTADMIN_API_ADDRESS: '', 29 }; 30 31 describe('ActionPanel', () => { 32 const server = setupServer( 33 rest.post('/api/test', (req, res, ctx) => { 34 return res(ctx.json({ ok: true })); 35 }) 36 ); 37 38 const queryClient = new QueryClient({ 39 defaultOptions: { queries: { retry: false } }, 40 }); 41 42 /** 43 * The useMutation query hook must be defined in the body of a function 44 * that is _within_ the context of a QueryClientProvider. This Wrapper component 45 * provides such a function and should be `render`ed in the context QueryClientProvider. 46 */ 47 const Wrapper: React.FC<Omit<ActionPanelProps, 'mutation'>> = (props) => { 48 const mutation = useMutation(() => fetch('/api/test', { method: 'post' })); 49 return <ActionPanel {...props} mutation={mutation as any} />; 50 }; 51 52 beforeAll(() => { 53 process.env = { ...TEST_PROCESS_ENV } as NodeJS.ProcessEnv; 54 server.listen(); 55 }); 56 57 afterEach(() => { 58 process.env = { ...TEST_PROCESS_ENV } as NodeJS.ProcessEnv; 59 jest.clearAllMocks(); 60 }); 61 62 afterAll(() => { 63 process.env = { ...ORIGINAL_PROCESS_ENV }; 64 server.close(); 65 }); 66 67 it('initiates the mutation', async () => { 68 jest.spyOn(global, 'fetch'); 69 70 render( 71 <QueryClientProvider client={queryClient}> 72 <Wrapper 73 confirmationValue="zone1-101" 74 description="Do an action." 75 documentationLink="https://test.com" 76 loadedText="Do Action" 77 loadingText="Doing Action..." 78 title="A Title" 79 /> 80 </QueryClientProvider> 81 ); 82 83 const user = userEvent.setup(); 84 85 const button = screen.getByRole('button'); 86 const input = screen.getByRole('textbox'); 87 88 // Enter the confirmation text 89 await user.type(input, 'zone1-101'); 90 expect(button).not.toHaveAttribute('disabled'); 91 92 await user.click(button); 93 94 // Validate form while API request is in flight 95 expect(button).toHaveTextContent('Doing Action...'); 96 97 expect(global.fetch).toHaveBeenCalledTimes(1); 98 expect(global.fetch).toHaveBeenCalledWith('/api/test', { method: 'post' }); 99 100 // Wait for API request to complete 101 await waitFor(() => expect(button).toHaveTextContent('Do Action')); 102 }); 103 104 it('enables form submission if and only if input matches confirmation', async () => { 105 render( 106 <QueryClientProvider client={queryClient}> 107 <Wrapper 108 confirmationValue="zone1-101" 109 description={<>Hello world!</>} 110 documentationLink="https://test.com" 111 loadedText="Do Action" 112 loadingText="Doing Action..." 113 title="A Title" 114 /> 115 </QueryClientProvider> 116 ); 117 118 const user = userEvent.setup(); 119 120 const button = screen.getByRole('button'); 121 const input = screen.getByRole('textbox'); 122 123 expect(button).toHaveAttribute('disabled'); 124 125 const invalidInputs = [' ', 'zone-100', 'zone1']; 126 for (let i = 0; i < invalidInputs.length; i++) { 127 await user.clear(input); 128 await user.type(input, invalidInputs[i]); 129 expect(button).toHaveAttribute('disabled'); 130 } 131 132 await user.clear(input); 133 await user.type(input, 'zone1-101'); 134 expect(button).not.toHaveAttribute('disabled'); 135 }); 136 137 it('does not render confirmation if "confirmationValue" not set', async () => { 138 render( 139 <QueryClientProvider client={queryClient}> 140 <Wrapper 141 description={<>Hello world!</>} 142 documentationLink="https://test.com" 143 loadedText="Do Action" 144 loadingText="Doing Action..." 145 title="A Title" 146 /> 147 </QueryClientProvider> 148 ); 149 150 const button = screen.getByRole('button'); 151 const input = screen.queryByRole('textbox'); 152 153 expect(input).toBeNull(); 154 expect(button).not.toHaveAttribute('disabled'); 155 }); 156 157 it('disables interaction when "disabled" prop is set', () => { 158 render( 159 <QueryClientProvider client={queryClient}> 160 <Wrapper 161 confirmationValue="zone1-101" 162 description={<>Hello world!</>} 163 disabled 164 documentationLink="https://test.com" 165 loadedText="Do Action" 166 loadingText="Doing Action..." 167 title="A Title" 168 /> 169 </QueryClientProvider> 170 ); 171 172 const button = screen.getByRole('button'); 173 const input = screen.queryByRole('textbox'); 174 175 expect(input).toBeNull(); 176 expect(button).toHaveAttribute('disabled'); 177 }); 178 });