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  })