github.com/tilt-dev/tilt@v0.36.0/web/src/BulkApiButton.test.tsx (about) 1 import { render, screen, waitFor } from "@testing-library/react" 2 import userEvent from "@testing-library/user-event" 3 import { ApiButtonToggleState, ApiButtonType } from "./ApiButton" 4 import fetchMock from "fetch-mock" 5 import { 6 getUIButtonDataFromCall, 7 mockUIButtonUpdates, 8 } from "./ApiButton.testhelpers" 9 import { 10 BulkApiButton, 11 canBulkButtonBeToggled, 12 canButtonBeToggled, 13 } from "./BulkApiButton" 14 import { BulkAction } from "./OverviewTableBulkActions" 15 import { flushPromises } from "./promise" 16 import { disableButton, oneUIButton } from "./testdata" 17 18 const NON_TOGGLE_BUTTON = oneUIButton({ componentID: "database" }) 19 const DISABLE_BUTTON_DB = disableButton("database", true) 20 const DISABLE_BUTTON_FRONTEND = disableButton("frontend", true) 21 const ENABLE_BUTTON_BACKEND = disableButton("backend", false) 22 const TEST_UIBUTTONS = [ 23 DISABLE_BUTTON_DB, 24 DISABLE_BUTTON_FRONTEND, 25 ENABLE_BUTTON_BACKEND, 26 ] 27 28 describe("BulkApiButton", () => { 29 beforeEach(() => { 30 mockUIButtonUpdates() 31 }) 32 33 afterEach(() => { 34 fetchMock.reset() 35 }) 36 37 it("is disabled when there are no UIButtons", () => { 38 render( 39 <BulkApiButton 40 bulkAction={BulkAction.Disable} 41 buttonText="I cannot be clicked" 42 requiresConfirmation={false} 43 uiButtons={[]} 44 /> 45 ) 46 47 const bulkButton = screen.getByLabelText("Trigger I cannot be clicked") 48 49 expect(bulkButton).toBeTruthy() 50 expect((bulkButton as HTMLButtonElement).disabled).toBe(true) 51 }) 52 53 it("is disabled when there are no UIButtons that can be toggled to the target toggle state", () => { 54 render( 55 <BulkApiButton 56 bulkAction={BulkAction.Disable} 57 buttonText="I cannot be toggled that way" 58 requiresConfirmation={false} 59 targetToggleState={ApiButtonToggleState.On} 60 uiButtons={[DISABLE_BUTTON_DB]} 61 /> 62 ) 63 64 const bulkButton = screen.getByLabelText( 65 "Trigger I cannot be toggled that way" 66 ) 67 68 expect(bulkButton).toBeTruthy() 69 expect((bulkButton as HTMLButtonElement).disabled).toBe(true) 70 }) 71 72 it("is enabled when there are UIButtons", () => { 73 render( 74 <BulkApiButton 75 bulkAction={BulkAction.Disable} 76 buttonText="Run lint" 77 requiresConfirmation={false} 78 uiButtons={[NON_TOGGLE_BUTTON]} 79 /> 80 ) 81 82 const bulkButton = screen.getByLabelText("Trigger Run lint") 83 84 expect(bulkButton).toBeTruthy() 85 expect((bulkButton as HTMLButtonElement).disabled).toBe(false) 86 }) 87 88 it("is enabled when there are UIButtons that can be toggled to the target toggle state", () => { 89 render( 90 <BulkApiButton 91 bulkAction={BulkAction.Disable} 92 buttonText="Enable resources" 93 requiresConfirmation={false} 94 targetToggleState={ApiButtonToggleState.On} 95 uiButtons={[DISABLE_BUTTON_DB, ENABLE_BUTTON_BACKEND]} 96 /> 97 ) 98 99 const bulkButton = screen.getByLabelText("Trigger Enable resources") 100 101 expect(bulkButton).toBeTruthy() 102 expect((bulkButton as HTMLButtonElement).disabled).toBe(false) 103 }) 104 105 describe("when it's clicked", () => { 106 let mockCallback: jest.Mock 107 108 beforeEach(async () => { 109 mockCallback = jest.fn() 110 render( 111 <BulkApiButton 112 bulkAction={BulkAction.Disable} 113 buttonText="Turn everything off" 114 onClickCallback={mockCallback} 115 requiresConfirmation={false} 116 targetToggleState={ApiButtonToggleState.Off} 117 uiButtons={TEST_UIBUTTONS} 118 /> 119 ) 120 121 const bulkButton = screen.getByLabelText("Trigger Turn everything off") 122 userEvent.click(bulkButton) 123 124 // Wait for the async calls to complete 125 await waitFor(flushPromises) 126 }) 127 128 it("triggers all buttons that can be toggled when it's clicked", () => { 129 const buttonUpdateCalls = fetchMock.calls() 130 131 // Out of the three test buttons, only two of them can be toggled to the target toggle state 132 expect(buttonUpdateCalls.length).toBe(2) 133 134 const buttonUpdateNames = buttonUpdateCalls.map( 135 (call) => getUIButtonDataFromCall(call)?.metadata?.name 136 ) 137 138 expect(buttonUpdateNames).toStrictEqual([ 139 DISABLE_BUTTON_DB.metadata?.name, 140 DISABLE_BUTTON_FRONTEND.metadata?.name, 141 ]) 142 }) 143 144 it("calls a specified onClick callback", () => { 145 expect(mockCallback).toHaveBeenCalledTimes(1) 146 }) 147 }) 148 149 describe("when it requires confirmation", () => { 150 beforeEach(async () => { 151 render( 152 <BulkApiButton 153 bulkAction={BulkAction.Disable} 154 buttonText="Click everything when I'm sure" 155 requiresConfirmation={true} 156 uiButtons={TEST_UIBUTTONS} 157 /> 158 ) 159 160 const bulkButton = screen.getByLabelText( 161 "Trigger Click everything when I'm sure" 162 ) 163 userEvent.click(bulkButton) 164 }) 165 166 it("displays confirm and cancel buttons when clicked once", () => { 167 expect( 168 screen.getByLabelText("Confirm Click everything when I'm sure") 169 ).toBeTruthy() 170 expect( 171 screen.getByLabelText("Cancel Click everything when I'm sure") 172 ).toBeTruthy() 173 }) 174 175 it("triggers all buttons when `Confirm` is clicked", async () => { 176 userEvent.click( 177 screen.getByLabelText("Confirm Click everything when I'm sure") 178 ) 179 180 await waitFor(flushPromises) 181 182 const buttonUpdateCalls = fetchMock.calls() 183 expect(buttonUpdateCalls.length).toBe(3) 184 185 const buttonUpdateNames = buttonUpdateCalls.map( 186 (call) => getUIButtonDataFromCall(call)?.metadata?.name 187 ) 188 189 expect(buttonUpdateNames).toStrictEqual([ 190 DISABLE_BUTTON_DB.metadata?.name, 191 DISABLE_BUTTON_FRONTEND.metadata?.name, 192 ENABLE_BUTTON_BACKEND.metadata?.name, 193 ]) 194 }) 195 196 it("does NOT trigger any buttons when `Cancel` is clicked", async () => { 197 userEvent.click( 198 screen.getByLabelText("Cancel Click everything when I'm sure") 199 ) 200 201 // There shouldn't be any async calls made when canceling, but wait in case 202 await waitFor(flushPromises) 203 204 expect( 205 screen.getByLabelText("Trigger Click everything when I'm sure") 206 ).toBeTruthy() 207 }) 208 }) 209 210 describe("helpers", () => { 211 describe("canButtonBeToggled", () => { 212 it("returns true when there is no target toggle state", () => { 213 expect(canButtonBeToggled(DISABLE_BUTTON_FRONTEND)).toBe(true) 214 }) 215 216 it("returns false when button is not a toggle button", () => { 217 expect(canButtonBeToggled(NON_TOGGLE_BUTTON)).toBe(false) 218 }) 219 220 it("returns false when button is already in the target toggle state", () => { 221 expect( 222 canButtonBeToggled(DISABLE_BUTTON_FRONTEND, ApiButtonToggleState.On) 223 ).toBe(false) 224 expect( 225 canButtonBeToggled(ENABLE_BUTTON_BACKEND, ApiButtonToggleState.Off) 226 ).toBe(false) 227 }) 228 229 it("returns true when button is not in the target toggle state", () => { 230 expect( 231 canButtonBeToggled(DISABLE_BUTTON_FRONTEND, ApiButtonToggleState.Off) 232 ).toBe(true) 233 expect( 234 canButtonBeToggled(ENABLE_BUTTON_BACKEND, ApiButtonToggleState.On) 235 ).toBe(true) 236 }) 237 }) 238 239 describe("canBulkButtonBeToggled", () => { 240 it("returns false if no buttons in the list of buttons can be toggled", () => { 241 expect( 242 canBulkButtonBeToggled( 243 [NON_TOGGLE_BUTTON, DISABLE_BUTTON_FRONTEND], 244 ApiButtonToggleState.On 245 ) 246 ).toBe(false) 247 }) 248 249 it("returns true if at least one button in the list of buttons can be toggled", () => { 250 expect( 251 canBulkButtonBeToggled( 252 [NON_TOGGLE_BUTTON, DISABLE_BUTTON_FRONTEND, ENABLE_BUTTON_BACKEND], 253 ApiButtonToggleState.On 254 ) 255 ).toBe(true) 256 }) 257 }) 258 }) 259 })