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