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

     1  import { render, screen } from "@testing-library/react"
     2  import userEvent from "@testing-library/user-event"
     3  import React from "react"
     4  import { AnalyticsAction } from "./analytics"
     5  import {
     6    cleanupMockAnalyticsCalls,
     7    expectIncrs,
     8    mockAnalyticsCalls,
     9  } from "./analytics_test_helpers"
    10  import {
    11    InstrumentedButton,
    12    InstrumentedCheckbox,
    13    InstrumentedTextField,
    14    textFieldEditDebounceMilliseconds,
    15  } from "./instrumentedComponents"
    16  
    17  describe("instrumented components", () => {
    18    beforeEach(() => {
    19      mockAnalyticsCalls()
    20      jest.useFakeTimers()
    21    })
    22  
    23    afterEach(() => {
    24      cleanupMockAnalyticsCalls()
    25      jest.useRealTimers()
    26    })
    27  
    28    describe("instrumented button", () => {
    29      it("reports analytics with default tags and correct name", () => {
    30        render(
    31          <InstrumentedButton analyticsName="ui.web.foo.bar">
    32            Hello
    33          </InstrumentedButton>
    34        )
    35  
    36        userEvent.click(screen.getByRole("button"))
    37  
    38        expectIncrs({
    39          name: "ui.web.foo.bar",
    40          tags: { action: AnalyticsAction.Click },
    41        })
    42      })
    43  
    44      it("reports analytics with any additional custom tags", () => {
    45        const customTags = { hello: "goodbye" }
    46        render(
    47          <InstrumentedButton
    48            analyticsName="ui.web.foo.bar"
    49            analyticsTags={customTags}
    50          >
    51            Hello
    52          </InstrumentedButton>
    53        )
    54  
    55        userEvent.click(screen.getByRole("button"))
    56  
    57        expectIncrs({
    58          name: "ui.web.foo.bar",
    59          tags: { action: AnalyticsAction.Click, ...customTags },
    60        })
    61      })
    62  
    63      it("invokes the click callback when provided", () => {
    64        const onClickSpy = jest.fn()
    65        render(
    66          <InstrumentedButton
    67            analyticsName="ui.web.foo.bar"
    68            analyticsTags={{ hello: "goodbye" }}
    69            onClick={onClickSpy}
    70          >
    71            Hello
    72          </InstrumentedButton>
    73        )
    74  
    75        expect(onClickSpy).not.toBeCalled()
    76  
    77        userEvent.click(screen.getByRole("button"))
    78  
    79        expect(onClickSpy).toBeCalledTimes(1)
    80      })
    81    })
    82  
    83    describe("instrumented TextField", () => {
    84      it("reports analytics, debounced, when edited", () => {
    85        render(
    86          <InstrumentedTextField
    87            analyticsName={"ui.web.TestTextField"}
    88            analyticsTags={{ foo: "bar" }}
    89            InputProps={{ "aria-label": "Help search" }}
    90          />
    91        )
    92  
    93        const inputField = screen.getByLabelText("Help search")
    94        // two changes in rapid succession should result in only one analytics event
    95        userEvent.type(inputField, "foo")
    96        userEvent.type(inputField, "bar")
    97  
    98        expectIncrs(...[])
    99        jest.advanceTimersByTime(10000)
   100        expectIncrs({
   101          name: "ui.web.TestTextField",
   102          tags: { action: AnalyticsAction.Edit, foo: "bar" },
   103        })
   104      })
   105  
   106      // This test is to make sure that the debounce interval is not
   107      // shared between instances of the same debounced component.
   108      // When a user edits one text field and then edits another,
   109      // the debounce internal should start and operate independently
   110      // for each text field.
   111      it("debounces analytics for text fields on an instance-by-instance basis", () => {
   112        const halfDebounce = textFieldEditDebounceMilliseconds / 2
   113        render(
   114          <>
   115            <InstrumentedTextField
   116              id="resourceNameFilter"
   117              analyticsName={"ui.web.resourceNameFilter"}
   118              analyticsTags={{ testing: "true" }}
   119              InputProps={{ "aria-label": "Resource name filter" }}
   120            />
   121            <InstrumentedTextField
   122              id="uibuttonInput"
   123              analyticsName={"ui.web.uibutton.inputValue"}
   124              analyticsTags={{ testing: "true" }}
   125              InputProps={{ "aria-label": "Button value" }}
   126            />
   127          </>
   128        )
   129  
   130        // Trigger an event in the first field
   131        userEvent.type(screen.getByLabelText("Resource name filter"), "first!")
   132  
   133        // Expect that no analytics calls have been made, since the debounce
   134        // time for the first field has not been met
   135        jest.advanceTimersByTime(halfDebounce)
   136        expectIncrs(...[])
   137  
   138        // Trigger an event in the second field
   139        userEvent.type(screen.getByLabelText("Button value"), "second!")
   140  
   141        // Expect that _only_ the first field's analytics event has occurred,
   142        // since that debounce interval has been met for the first field.
   143        // If the debounce was shared between multiple instances of the text
   144        // field, this analytics call wouldn't occur.
   145        jest.advanceTimersByTime(halfDebounce)
   146        expectIncrs({
   147          name: "ui.web.resourceNameFilter",
   148          tags: { action: AnalyticsAction.Edit, testing: "true" },
   149        })
   150  
   151        // Expect that the second field's analytics event has occurred, now
   152        // that the debounce interval has been met for the first field
   153        jest.advanceTimersByTime(halfDebounce)
   154        expectIncrs(
   155          {
   156            name: "ui.web.resourceNameFilter",
   157            tags: { action: AnalyticsAction.Edit, testing: "true" },
   158          },
   159          {
   160            name: "ui.web.uibutton.inputValue",
   161            tags: { action: AnalyticsAction.Edit, testing: "true" },
   162          }
   163        )
   164      })
   165    })
   166  
   167    describe("instrumented Checkbox", () => {
   168      it("reports analytics when clicked", () => {
   169        render(
   170          <InstrumentedCheckbox
   171            analyticsName={"ui.web.TestCheckbox"}
   172            analyticsTags={{ foo: "bar" }}
   173            inputProps={{ "aria-label": "Check me" }}
   174          />
   175        )
   176  
   177        userEvent.click(screen.getByLabelText("Check me"))
   178        expectIncrs({
   179          name: "ui.web.TestCheckbox",
   180          tags: { action: AnalyticsAction.Edit, foo: "bar" },
   181        })
   182      })
   183    })
   184  })