github.com/tilt-dev/tilt@v0.36.0/web/src/BuildButton.test.tsx (about) 1 import { 2 fireEvent, 3 render, 4 RenderOptions, 5 screen, 6 } from "@testing-library/react" 7 import userEvent from "@testing-library/user-event" 8 import fetchMock from "fetch-mock" 9 import { SnackbarProvider } from "notistack" 10 import React from "react" 11 import { MemoryRouter } from "react-router" 12 import BuildButton, { StartBuildButtonProps } from "./BuildButton" 13 import { oneUIButton } from "./testdata" 14 import { BuildButtonTooltip, startBuild } from "./trigger" 15 import { TriggerMode } from "./types" 16 17 function expectClickable(button: HTMLElement, expected: boolean) { 18 if (expected) { 19 expect(button).toHaveClass("is-clickable") 20 expect(button).not.toBeDisabled() 21 } else { 22 expect(button).not.toHaveClass("is-clickable") 23 expect(button).toBeDisabled() 24 } 25 } 26 function expectManualStartBuildIcon(expected: boolean) { 27 const iconId = expected ? "build-manual-icon" : "build-auto-icon" 28 expect(screen.getByTestId(iconId)).toBeInTheDocument() 29 } 30 function expectIsSelected(button: HTMLElement, expected: boolean) { 31 if (expected) { 32 expect(button).toHaveClass("is-selected") 33 } else { 34 expect(button).not.toHaveClass("is-selected") 35 } 36 } 37 function expectIsQueued(button: HTMLElement, expected: boolean) { 38 if (expected) { 39 expect(button).toHaveClass("is-queued") 40 } else { 41 expect(button).not.toHaveClass("is-queued") 42 } 43 } 44 function expectWithTooltip(expected: string) { 45 expect(screen.getByTitle(expected)).toBeInTheDocument() 46 } 47 48 const stopBuildButton = oneUIButton({ buttonName: "stopBuild" }) 49 50 function customRender( 51 buttonProps: Partial<StartBuildButtonProps>, 52 options?: RenderOptions 53 ) { 54 return render( 55 <BuildButton 56 stopBuildButton={stopBuildButton} 57 onStartBuild={buttonProps.onStartBuild ?? (() => {})} 58 hasBuilt={buttonProps.hasBuilt ?? false} 59 isBuilding={buttonProps.isBuilding ?? false} 60 isSelected={buttonProps.isSelected} 61 isQueued={buttonProps.isQueued ?? false} 62 hasPendingChanges={buttonProps.hasPendingChanges ?? false} 63 triggerMode={buttonProps.triggerMode ?? TriggerMode.TriggerModeAuto} 64 />, 65 { 66 wrapper: ({ children }) => ( 67 <MemoryRouter initialEntries={["/"]}> 68 <SnackbarProvider>{children}</SnackbarProvider> 69 </MemoryRouter> 70 ), 71 ...options, 72 } 73 ) 74 } 75 76 describe("SidebarBuildButton", () => { 77 beforeEach(() => { 78 fetchMock.mock("/api/trigger", JSON.stringify({})) 79 }) 80 81 afterEach(() => { 82 fetchMock.reset() 83 }) 84 85 describe("start builds", () => { 86 it("POSTs to endpoint when clicked", () => { 87 customRender({ 88 onStartBuild: () => startBuild("doggos"), 89 hasBuilt: true, 90 }) 91 92 const buildButton = screen.getByLabelText(BuildButtonTooltip.Default) 93 expect(buildButton).toBeInTheDocument() 94 95 // Construct a mouse event with method spies 96 const preventDefault = jest.fn() 97 const stopPropagation = jest.fn() 98 const clickEvent = new MouseEvent("click", { bubbles: true }) 99 clickEvent.preventDefault = preventDefault 100 clickEvent.stopPropagation = stopPropagation 101 102 fireEvent(buildButton, clickEvent) 103 104 expect(preventDefault).toHaveBeenCalled() 105 expect(stopPropagation).toHaveBeenCalled() 106 107 expect(fetchMock.calls().length).toEqual(1) 108 expect(fetchMock.calls()[0][0]).toEqual("/api/trigger") 109 expect(fetchMock.calls()[0][1]?.method).toEqual("post") 110 expect(fetchMock.calls()[0][1]?.body).toEqual( 111 JSON.stringify({ 112 manifest_names: ["doggos"], 113 build_reason: 16 /* BuildReasonFlagTriggerWeb */, 114 }) 115 ) 116 }) 117 118 it("disables button when resource is queued", () => { 119 const startBuildSpy = jest.fn() 120 customRender({ isQueued: true, onStartBuild: startBuildSpy }) 121 122 const buildButton = screen.getByLabelText( 123 BuildButtonTooltip.AlreadyQueued 124 ) 125 expect(buildButton).toBeDisabled() 126 127 userEvent.click(buildButton, undefined, { skipPointerEventsCheck: true }) 128 129 expect(startBuildSpy).not.toHaveBeenCalled() 130 }) 131 132 it("shows the button for TriggerModeManual", () => { 133 const startBuildSpy = jest.fn() 134 customRender({ 135 triggerMode: TriggerMode.TriggerModeManual, 136 onStartBuild: startBuildSpy, 137 }) 138 139 expectManualStartBuildIcon(true) 140 }) 141 142 test.each([true, false])( 143 "shows clickable + bold start build button for manual resource. hasPendingChanges: %s", 144 (hasPendingChanges) => { 145 customRender({ 146 triggerMode: TriggerMode.TriggerModeManual, 147 hasPendingChanges, 148 hasBuilt: !hasPendingChanges, 149 }) 150 151 const tooltipText = hasPendingChanges 152 ? BuildButtonTooltip.NeedsManualTrigger 153 : BuildButtonTooltip.Default 154 const buildButton = screen.getByLabelText(tooltipText) 155 156 expect(buildButton).toBeInTheDocument() 157 expectClickable(buildButton, true) 158 expectManualStartBuildIcon(hasPendingChanges) 159 expectIsQueued(buildButton, false) 160 expectWithTooltip(tooltipText) 161 } 162 ) 163 164 test.each([true, false])( 165 "shows selected trigger button for resource is selected: %p", 166 (isSelected) => { 167 customRender({ isSelected, hasBuilt: true }) 168 169 const buildButton = screen.getByLabelText(BuildButtonTooltip.Default) 170 171 expect(buildButton).toBeInTheDocument() 172 expectIsSelected(buildButton, isSelected) // Selected resource 173 } 174 ) 175 176 // A pending resource may mean that a pod is being rolled out, but is not 177 // ready yet. In that case, the start build button will delete the pod (cancelling 178 // the rollout) and rebuild. 179 it("shows start build button when pending but no current build", () => { 180 customRender({ hasPendingChanges: true, hasBuilt: true }) 181 182 const buildButton = screen.getByLabelText(BuildButtonTooltip.Default) 183 184 expect(buildButton).toBeInTheDocument() 185 expectClickable(buildButton, true) 186 expectManualStartBuildIcon(false) 187 expectIsQueued(buildButton, false) 188 expectWithTooltip(BuildButtonTooltip.Default) 189 }) 190 191 it("renders an unclickable start build button if resource waiting for first build", () => { 192 customRender({}) 193 194 const buildButton = screen.getByLabelText( 195 BuildButtonTooltip.UpdateInProgOrPending 196 ) 197 198 expect(buildButton).toBeInTheDocument() 199 expectClickable(buildButton, false) 200 expectManualStartBuildIcon(false) 201 expectIsQueued(buildButton, false) 202 expectWithTooltip(BuildButtonTooltip.UpdateInProgOrPending) 203 }) 204 205 it("renders queued resource with class .isQueued and NOT .clickable", () => { 206 customRender({ isQueued: true }) 207 208 const buildButton = screen.getByLabelText( 209 BuildButtonTooltip.AlreadyQueued 210 ) 211 212 expect(buildButton).toBeInTheDocument() 213 expectClickable(buildButton, false) 214 expectManualStartBuildIcon(false) 215 expectIsQueued(buildButton, true) 216 expectWithTooltip(BuildButtonTooltip.AlreadyQueued) 217 }) 218 }) 219 220 describe("stop builds", () => { 221 it("renders a stop button when the build is in progress", () => { 222 customRender({ isBuilding: true }) 223 224 const buildButton = screen.getByLabelText( 225 `Trigger ${stopBuildButton.spec?.text}` 226 ) 227 228 expect(buildButton).toBeInTheDocument() 229 // The button group has the .stop-button class 230 expect(screen.getByRole("group")).toHaveClass("stop-button") 231 expectWithTooltip(BuildButtonTooltip.Stop) 232 }) 233 }) 234 })