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

     1  import { render, screen, waitFor } from "@testing-library/react"
     2  import userEvent from "@testing-library/user-event"
     3  import { AnalyticsAction, AnalyticsType } from "./analytics"
     4  import {
     5    cleanupMockAnalyticsCalls,
     6    expectIncrs,
     7    mockAnalyticsCalls,
     8    nonAnalyticsCalls,
     9  } from "./analytics_test_helpers"
    10  import { ApiButtonToggleState, ApiButtonType } from "./ApiButton"
    11  import {
    12    getUIButtonDataFromCall,
    13    mockUIButtonUpdates,
    14  } from "./ApiButton.testhelpers"
    15  import {
    16    BulkApiButton,
    17    canBulkButtonBeToggled,
    18    canButtonBeToggled,
    19  } from "./BulkApiButton"
    20  import { BulkAction } from "./OverviewTableBulkActions"
    21  import { flushPromises } from "./promise"
    22  import { disableButton, oneUIButton } from "./testdata"
    23  
    24  const NON_TOGGLE_BUTTON = oneUIButton({ componentID: "database" })
    25  const DISABLE_BUTTON_DB = disableButton("database", true)
    26  const DISABLE_BUTTON_FRONTEND = disableButton("frontend", true)
    27  const ENABLE_BUTTON_BACKEND = disableButton("backend", false)
    28  const TEST_UIBUTTONS = [
    29    DISABLE_BUTTON_DB,
    30    DISABLE_BUTTON_FRONTEND,
    31    ENABLE_BUTTON_BACKEND,
    32  ]
    33  
    34  describe("BulkApiButton", () => {
    35    beforeEach(() => {
    36      mockAnalyticsCalls()
    37      mockUIButtonUpdates()
    38    })
    39  
    40    afterEach(() => {
    41      cleanupMockAnalyticsCalls()
    42    })
    43  
    44    it("is disabled when there are no UIButtons", () => {
    45      render(
    46        <BulkApiButton
    47          bulkAction={BulkAction.Disable}
    48          buttonText="I cannot be clicked"
    49          requiresConfirmation={false}
    50          uiButtons={[]}
    51        />
    52      )
    53  
    54      const bulkButton = screen.getByLabelText("Trigger I cannot be clicked")
    55  
    56      expect(bulkButton).toBeTruthy()
    57      expect((bulkButton as HTMLButtonElement).disabled).toBe(true)
    58    })
    59  
    60    it("is disabled when there are no UIButtons that can be toggled to the target toggle state", () => {
    61      render(
    62        <BulkApiButton
    63          bulkAction={BulkAction.Disable}
    64          buttonText="I cannot be toggled that way"
    65          requiresConfirmation={false}
    66          targetToggleState={ApiButtonToggleState.On}
    67          uiButtons={[DISABLE_BUTTON_DB]}
    68        />
    69      )
    70  
    71      const bulkButton = screen.getByLabelText(
    72        "Trigger I cannot be toggled that way"
    73      )
    74  
    75      expect(bulkButton).toBeTruthy()
    76      expect((bulkButton as HTMLButtonElement).disabled).toBe(true)
    77    })
    78  
    79    it("is enabled when there are UIButtons", () => {
    80      render(
    81        <BulkApiButton
    82          bulkAction={BulkAction.Disable}
    83          buttonText="Run lint"
    84          requiresConfirmation={false}
    85          uiButtons={[NON_TOGGLE_BUTTON]}
    86        />
    87      )
    88  
    89      const bulkButton = screen.getByLabelText("Trigger Run lint")
    90  
    91      expect(bulkButton).toBeTruthy()
    92      expect((bulkButton as HTMLButtonElement).disabled).toBe(false)
    93    })
    94  
    95    it("is enabled when there are UIButtons that can be toggled to the target toggle state", () => {
    96      render(
    97        <BulkApiButton
    98          bulkAction={BulkAction.Disable}
    99          buttonText="Enable resources"
   100          requiresConfirmation={false}
   101          targetToggleState={ApiButtonToggleState.On}
   102          uiButtons={[DISABLE_BUTTON_DB, ENABLE_BUTTON_BACKEND]}
   103        />
   104      )
   105  
   106      const bulkButton = screen.getByLabelText("Trigger Enable resources")
   107  
   108      expect(bulkButton).toBeTruthy()
   109      expect((bulkButton as HTMLButtonElement).disabled).toBe(false)
   110    })
   111  
   112    describe("when it's clicked", () => {
   113      let mockCallback: jest.Mock
   114  
   115      beforeEach(async () => {
   116        mockCallback = jest.fn()
   117        render(
   118          <BulkApiButton
   119            bulkAction={BulkAction.Disable}
   120            buttonText="Turn everything off"
   121            onClickCallback={mockCallback}
   122            requiresConfirmation={false}
   123            targetToggleState={ApiButtonToggleState.Off}
   124            uiButtons={TEST_UIBUTTONS}
   125          />
   126        )
   127  
   128        const bulkButton = screen.getByLabelText("Trigger Turn everything off")
   129        userEvent.click(bulkButton)
   130  
   131        // Wait for the async calls to complete
   132        await waitFor(flushPromises)
   133      })
   134  
   135      it("triggers all buttons that can be toggled when it's clicked", () => {
   136        const buttonUpdateCalls = nonAnalyticsCalls()
   137  
   138        // Out of the three test buttons, only two of them can be toggled to the target toggle state
   139        expect(buttonUpdateCalls.length).toBe(2)
   140  
   141        const buttonUpdateNames = buttonUpdateCalls.map(
   142          (call) => getUIButtonDataFromCall(call)?.metadata?.name
   143        )
   144  
   145        expect(buttonUpdateNames).toStrictEqual([
   146          DISABLE_BUTTON_DB.metadata?.name,
   147          DISABLE_BUTTON_FRONTEND.metadata?.name,
   148        ])
   149      })
   150  
   151      it("generates the correct analytics payload", () => {
   152        expectIncrs({
   153          name: "ui.web.bulkButton",
   154          tags: {
   155            action: AnalyticsAction.Click,
   156            bulkAction: BulkAction.Disable,
   157            bulkCount: "3",
   158            toggleValue: ApiButtonToggleState.On,
   159            component: ApiButtonType.Global,
   160            type: AnalyticsType.Grid,
   161          },
   162        })
   163      })
   164  
   165      it("calls a specified onClick callback", () => {
   166        expect(mockCallback).toHaveBeenCalledTimes(1)
   167      })
   168    })
   169  
   170    describe("when it requires confirmation", () => {
   171      beforeEach(async () => {
   172        render(
   173          <BulkApiButton
   174            bulkAction={BulkAction.Disable}
   175            buttonText="Click everything when I'm sure"
   176            requiresConfirmation={true}
   177            uiButtons={TEST_UIBUTTONS}
   178          />
   179        )
   180  
   181        const bulkButton = screen.getByLabelText(
   182          "Trigger Click everything when I'm sure"
   183        )
   184        userEvent.click(bulkButton)
   185      })
   186  
   187      it("displays confirm and cancel buttons when clicked once", () => {
   188        expect(
   189          screen.getByLabelText("Confirm Click everything when I'm sure")
   190        ).toBeTruthy()
   191        expect(
   192          screen.getByLabelText("Cancel Click everything when I'm sure")
   193        ).toBeTruthy()
   194      })
   195  
   196      it("triggers all buttons when `Confirm` is clicked", async () => {
   197        userEvent.click(
   198          screen.getByLabelText("Confirm Click everything when I'm sure")
   199        )
   200  
   201        await waitFor(flushPromises)
   202  
   203        const buttonUpdateCalls = nonAnalyticsCalls()
   204        expect(buttonUpdateCalls.length).toBe(3)
   205  
   206        const buttonUpdateNames = buttonUpdateCalls.map(
   207          (call) => getUIButtonDataFromCall(call)?.metadata?.name
   208        )
   209  
   210        expect(buttonUpdateNames).toStrictEqual([
   211          DISABLE_BUTTON_DB.metadata?.name,
   212          DISABLE_BUTTON_FRONTEND.metadata?.name,
   213          ENABLE_BUTTON_BACKEND.metadata?.name,
   214        ])
   215      })
   216  
   217      it("does NOT trigger any buttons when `Cancel` is clicked", async () => {
   218        userEvent.click(
   219          screen.getByLabelText("Cancel Click everything when I'm sure")
   220        )
   221  
   222        // There shouldn't be any async calls made when canceling, but wait in case
   223        await waitFor(flushPromises)
   224  
   225        expect(
   226          screen.getByLabelText("Trigger Click everything when I'm sure")
   227        ).toBeTruthy()
   228      })
   229  
   230      it("generates the correct analytics payload for confirmation", async () => {
   231        userEvent.click(
   232          screen.getByLabelText("Confirm Click everything when I'm sure")
   233        )
   234        await waitFor(flushPromises)
   235  
   236        expectIncrs(
   237          {
   238            name: "ui.web.bulkButton",
   239            tags: {
   240              action: AnalyticsAction.Click,
   241              bulkAction: BulkAction.Disable,
   242              bulkCount: "3",
   243              component: ApiButtonType.Global,
   244              type: AnalyticsType.Grid,
   245            },
   246          },
   247          {
   248            name: "ui.web.bulkButton",
   249            tags: {
   250              action: AnalyticsAction.Click,
   251              bulkAction: BulkAction.Disable,
   252              bulkCount: "3",
   253              confirm: "true",
   254              component: ApiButtonType.Global,
   255              type: AnalyticsType.Grid,
   256            },
   257          }
   258        )
   259      })
   260  
   261      it("generates the correct analytics payload for cancelation", () => {
   262        userEvent.click(
   263          screen.getByLabelText("Cancel Click everything when I'm sure")
   264        )
   265  
   266        expectIncrs(
   267          {
   268            name: "ui.web.bulkButton",
   269            tags: {
   270              action: AnalyticsAction.Click,
   271              bulkAction: BulkAction.Disable,
   272              bulkCount: "3",
   273              component: ApiButtonType.Global,
   274              type: AnalyticsType.Grid,
   275            },
   276          },
   277          {
   278            name: "ui.web.bulkButton",
   279            tags: {
   280              action: AnalyticsAction.Click,
   281              bulkAction: BulkAction.Disable,
   282              bulkCount: "3",
   283              confirm: "false",
   284              component: ApiButtonType.Global,
   285              type: AnalyticsType.Grid,
   286            },
   287          }
   288        )
   289      })
   290    })
   291  
   292    describe("helpers", () => {
   293      describe("canButtonBeToggled", () => {
   294        it("returns true when there is no target toggle state", () => {
   295          expect(canButtonBeToggled(DISABLE_BUTTON_FRONTEND)).toBe(true)
   296        })
   297  
   298        it("returns false when button is not a toggle button", () => {
   299          expect(canButtonBeToggled(NON_TOGGLE_BUTTON)).toBe(false)
   300        })
   301  
   302        it("returns false when button is already in the target toggle state", () => {
   303          expect(
   304            canButtonBeToggled(DISABLE_BUTTON_FRONTEND, ApiButtonToggleState.On)
   305          ).toBe(false)
   306          expect(
   307            canButtonBeToggled(ENABLE_BUTTON_BACKEND, ApiButtonToggleState.Off)
   308          ).toBe(false)
   309        })
   310  
   311        it("returns true when button is not in the target toggle state", () => {
   312          expect(
   313            canButtonBeToggled(DISABLE_BUTTON_FRONTEND, ApiButtonToggleState.Off)
   314          ).toBe(true)
   315          expect(
   316            canButtonBeToggled(ENABLE_BUTTON_BACKEND, ApiButtonToggleState.On)
   317          ).toBe(true)
   318        })
   319      })
   320  
   321      describe("canBulkButtonBeToggled", () => {
   322        it("returns false if no buttons in the list of buttons can be toggled", () => {
   323          expect(
   324            canBulkButtonBeToggled(
   325              [NON_TOGGLE_BUTTON, DISABLE_BUTTON_FRONTEND],
   326              ApiButtonToggleState.On
   327            )
   328          ).toBe(false)
   329        })
   330  
   331        it("returns true if at least one button in the list of buttons can be toggled", () => {
   332          expect(
   333            canBulkButtonBeToggled(
   334              [NON_TOGGLE_BUTTON, DISABLE_BUTTON_FRONTEND, ENABLE_BUTTON_BACKEND],
   335              ApiButtonToggleState.On
   336            )
   337          ).toBe(true)
   338        })
   339      })
   340    })
   341  })