github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/web/src/OverviewActionBar.test.tsx (about)

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