github.com/tilt-dev/tilt@v0.36.0/web/src/OverviewActionBar.test.tsx (about) 1 import { 2 act, 3 render, 4 RenderOptions, 5 screen, 6 within, 7 } from "@testing-library/react" 8 import userEvent from "@testing-library/user-event" 9 import { MemoryRouter } from "react-router" 10 import { useLocation } from "react-router-dom" 11 import { SnackbarProvider } from "notistack" 12 import React from "react" 13 import { ButtonSet } from "./ApiButton" 14 import { EMPTY_FILTER_TERM, FilterLevel, FilterSource } from "./logfilters" 15 import OverviewActionBar, { 16 createLogSearch, 17 FILTER_INPUT_DEBOUNCE, 18 } from "./OverviewActionBar" 19 import { EmptyBar, FullBar } from "./OverviewActionBar.stories" 20 import { disableButton, oneResource, oneUIButton } from "./testdata" 21 22 const DEFAULT_FILTER_SET = { 23 level: FilterLevel.all, 24 source: FilterSource.all, 25 term: EMPTY_FILTER_TERM, 26 } 27 28 let location: any = window.location 29 30 function LocationCapture() { 31 location = useLocation() 32 return null 33 } 34 35 // Helper to extract search params from the rendered MemoryRouter 36 function getSearch() { 37 return location.search 38 } 39 40 function customRender( 41 component: JSX.Element, 42 wrapperProps: { initialEntries?: string[] } = {}, 43 options?: RenderOptions 44 ) { 45 return render(component, { 46 wrapper: ({ children }) => ( 47 <MemoryRouter initialEntries={wrapperProps.initialEntries || ["/"]}> 48 <SnackbarProvider>{children}</SnackbarProvider> 49 <LocationCapture /> 50 </MemoryRouter> 51 ), 52 ...options, 53 }) 54 } 55 56 describe("OverviewActionBar", () => { 57 it("renders the top row with endpoints", () => { 58 customRender(<FullBar />) 59 60 expect( 61 screen.getByLabelText(/links and custom buttons/i) 62 ).toBeInTheDocument() 63 expect(screen.getAllByRole("link")).toHaveLength(2) 64 }) 65 66 it("renders the top row with pod ID", () => { 67 customRender(<FullBar />) 68 69 expect( 70 screen.getByLabelText(/links and custom buttons/i) 71 ).toBeInTheDocument() 72 expect(screen.getAllByRole("button", { name: /Pod ID/i })).toHaveLength(1) 73 }) 74 75 it("does NOT render the top row when there are no endpoints, pods, or buttons", () => { 76 customRender(<EmptyBar />) 77 78 expect(screen.queryByLabelText(/links and custom buttons/i)).toBeNull() 79 }) 80 81 describe("log filters", () => { 82 beforeEach(() => customRender(<FullBar />)) 83 84 it("navigates to warning filter when warning log filter button is clicked", () => { 85 userEvent.click(screen.getByRole("button", { name: /warnings/i })) 86 87 expect(getSearch()).toEqual("?level=warn") 88 }) 89 90 it("navigates to build warning filter when both building and warning log filter buttons are clicked", () => { 91 userEvent.click( 92 screen.getByRole("button", { name: "Select warn log sources" }) 93 ) 94 userEvent.click(screen.getByRole("menuitem", { name: /build/i })) 95 96 expect(getSearch()).toEqual("?level=warn&source=build") 97 }) 98 }) 99 100 describe("disabled resource view", () => { 101 beforeEach(() => { 102 const resource = oneResource({ name: "not-enabled", disabled: true }) 103 const buttonSet: ButtonSet = { 104 default: [ 105 oneUIButton({ componentID: "not-enabled", buttonText: "Click me" }), 106 ], 107 toggleDisable: disableButton("not-enabled", false), 108 } 109 customRender( 110 <OverviewActionBar 111 resource={resource} 112 filterSet={DEFAULT_FILTER_SET} 113 buttons={buttonSet} 114 /> 115 ) 116 }) 117 118 it("should display the disable toggle button", () => { 119 expect( 120 screen.getByRole("button", { name: /trigger enable resource/i }) 121 ).toBeInTheDocument() 122 }) 123 124 it("should NOT display any `default` custom buttons", () => { 125 expect(screen.queryByRole("button", { name: /click me/i })).toBeNull() 126 }) 127 128 it("should NOT display the filter menu", () => { 129 expect(screen.queryByRole("button", { name: /warnings/i })).toBeNull() 130 expect(screen.queryByRole("button", { name: /errors/i })).toBeNull() 131 expect(screen.queryByRole("button", { name: /all levels/i })).toBeNull() 132 expect(screen.queryByRole("textbox")).toBeNull() 133 }) 134 135 it("should NOT display endpoint information", () => { 136 expect(screen.queryAllByRole("link")).toHaveLength(0) 137 }) 138 139 it("should NOT display podId information", () => { 140 expect(screen.queryAllByRole("button", { name: /Pod ID/i })).toHaveLength( 141 0 142 ) 143 }) 144 }) 145 146 describe("custom buttons", () => { 147 const customButtons = [ 148 oneUIButton({ componentID: "vigoda", disabled: true }), 149 ] 150 const toggleDisable = disableButton("vigoda", true) 151 beforeEach(() => { 152 customRender( 153 <OverviewActionBar 154 filterSet={DEFAULT_FILTER_SET} 155 buttons={{ default: customButtons, toggleDisable }} 156 /> 157 ) 158 }) 159 it("disables a button that should be disabled", () => { 160 const disabledButton = screen.getByLabelText( 161 `Trigger ${customButtons[0].spec?.text}` 162 ) 163 expect(disabledButton).toBeDisabled() 164 }) 165 166 it("renders disable-resource buttons separately from other buttons", () => { 167 const topRow = screen.getByLabelText(/links and custom buttons/i) 168 const buttonsInTopRow = within(topRow).getAllByRole("button") 169 const toggleDisableButton = screen.getByLabelText( 170 "Trigger Disable Resource" 171 ) 172 173 expect(buttonsInTopRow).toHaveLength(1) 174 expect(toggleDisableButton).toBeInTheDocument() 175 expect( 176 within(topRow).queryByLabelText("Trigger Disable Resource") 177 ).toBeNull() 178 }) 179 }) 180 181 describe("term filter input", () => { 182 it("renders with no initial value if there is no existing term filter", () => { 183 customRender(<FullBar />) 184 185 expect( 186 screen.getByRole("textbox", { name: /filter resource logs/i }) 187 ).toHaveValue("") 188 }) 189 190 it("renders with an initial value if there is an existing term filter", () => { 191 customRender(<FullBar />, { initialEntries: ["/?term=bleep+bloop"] }) 192 193 expect( 194 screen.getByRole("textbox", { name: /filter resource logs/i }) 195 ).toHaveValue("bleep bloop") 196 }) 197 198 it("changes the global term filter state when its value changes", () => { 199 jest.useFakeTimers() 200 201 customRender(<FullBar />) 202 203 userEvent.type(screen.getByRole("textbox"), "docker") 204 205 jest.advanceTimersByTime(FILTER_INPUT_DEBOUNCE) 206 207 expect(getSearch()).toEqual("?term=docker") 208 }) 209 210 it("uses debouncing to update the global term filter state", () => { 211 jest.useFakeTimers() 212 213 customRender(<FullBar />) 214 215 userEvent.type(screen.getByRole("textbox"), "doc") 216 217 jest.advanceTimersByTime(FILTER_INPUT_DEBOUNCE / 2) 218 219 // The debouncing time hasn't passed yet, so we don't expect to see any changes 220 expect(getSearch()).toEqual("") 221 222 userEvent.type(screen.getByRole("textbox"), "ker") 223 224 // The debouncing time hasn't passed yet, so we don't expect to see any changes 225 expect(getSearch()).toEqual("") 226 227 jest.advanceTimersByTime(FILTER_INPUT_DEBOUNCE) 228 229 // Since the debouncing time has passed, we expect to see the final 230 // change reflected 231 expect(getSearch()).toEqual("?term=docker") 232 }) 233 234 it("retains any current level and source filters when its value changes", () => { 235 jest.useFakeTimers() 236 237 customRender(<FullBar />, { 238 initialEntries: ["/?level=warn&source=build"], 239 }) 240 241 userEvent.type(screen.getByRole("textbox"), "help") 242 243 jest.advanceTimersByTime(FILTER_INPUT_DEBOUNCE) 244 245 expect(getSearch()).toEqual("?level=warn&source=build&term=help") 246 }) 247 }) 248 249 describe("createLogSearch", () => { 250 let currentSearch: URLSearchParams 251 beforeEach(() => (currentSearch = new URLSearchParams())) 252 253 it("sets the params that are passed in", () => { 254 expect( 255 createLogSearch(currentSearch.toString(), { 256 level: FilterLevel.all, 257 term: "find me", 258 source: FilterSource.build, 259 }).toString() 260 ).toBe("source=build&term=find+me") 261 262 expect( 263 createLogSearch(currentSearch.toString(), { 264 level: FilterLevel.warn, 265 }).toString() 266 ).toBe("level=warn") 267 268 expect( 269 createLogSearch(currentSearch.toString(), { 270 term: "", 271 source: FilterSource.runtime, 272 }).toString() 273 ).toBe("source=runtime") 274 }) 275 276 it("overrides params if a new value is defined", () => { 277 currentSearch.set("level", FilterLevel.warn) 278 expect( 279 createLogSearch(currentSearch.toString(), { 280 level: FilterLevel.error, 281 }).toString() 282 ).toBe("level=error") 283 currentSearch.delete("level") 284 285 currentSearch.set("level", "a meaningless value") 286 currentSearch.set("term", "") 287 expect( 288 createLogSearch(currentSearch.toString(), { 289 level: FilterLevel.all, 290 term: "service", 291 }).toString() 292 ).toBe("term=service") 293 }) 294 295 it("preserves existing params if no new value is defined", () => { 296 currentSearch.set("source", FilterSource.build) 297 expect( 298 createLogSearch(currentSearch.toString(), { 299 term: "test", 300 }).toString() 301 ).toBe("source=build&term=test") 302 }) 303 }) 304 })