github.com/tilt-dev/tilt@v0.36.0/web/src/ResourceNav.test.tsx (about)

     1  import { render, RenderOptions, screen } from "@testing-library/react"
     2  import userEvent from "@testing-library/user-event"
     3  import React, { ChangeEvent, useState } from "react"
     4  import { act } from "react-dom/test-utils"
     5  import { MemoryRouter } from "react-router"
     6  import { useLocation, useNavigate } from "react-router-dom"
     7  import { ResourceNavProvider, useResourceNav } from "./ResourceNav"
     8  import { ResourceName } from "./types"
     9  
    10  const INVALID_RESOURCE = "res3"
    11  
    12  function TestResourceNavConsumer() {
    13    const { selectedResource, invalidResource, openResource } = useResourceNav()
    14    const [resourceToSelect, setResourceToSelect] = useState("")
    15  
    16    return (
    17      <>
    18        <p aria-label="selectedResource">{selectedResource}</p>
    19        <p aria-label="invalidResource">{invalidResource}</p>
    20        <input
    21          aria-label="Resource to select"
    22          type="text"
    23          value={resourceToSelect}
    24          onChange={(e: ChangeEvent<HTMLInputElement>) =>
    25            setResourceToSelect(e.target.value)
    26          }
    27        />
    28        <button onClick={() => openResource(resourceToSelect)}>
    29          openResource
    30        </button>
    31      </>
    32    )
    33  }
    34  
    35  let location: any = window.location
    36  let navigate: any = null
    37  
    38  function LocationCapture() {
    39    location = useLocation()
    40    navigate = useNavigate()
    41    return null
    42  }
    43  
    44  function customRender(
    45    wrapperOptions: {
    46      initialEntries?: string[]
    47      validateOverride?: (name: string) => boolean
    48    } = {},
    49    options?: RenderOptions
    50  ) {
    51    const validateResource =
    52      wrapperOptions.validateOverride ??
    53      function (name: string) {
    54        return name !== INVALID_RESOURCE
    55      }
    56    return render(<TestResourceNavConsumer />, {
    57      wrapper: ({ children }) => (
    58        <MemoryRouter initialEntries={wrapperOptions.initialEntries || ["/"]}>
    59          <ResourceNavProvider validateResource={validateResource}>
    60            {children}
    61          </ResourceNavProvider>
    62          <LocationCapture />
    63        </MemoryRouter>
    64      ),
    65      ...options,
    66    })
    67  }
    68  
    69  describe("ResourceNavContext", () => {
    70    it("navigates to resource on click", () => {
    71      customRender()
    72  
    73      expect(screen.getByLabelText("selectedResource")).toHaveTextContent("")
    74  
    75      userEvent.type(screen.getByRole("textbox"), "res1")
    76      userEvent.click(screen.getByRole("button", { name: "openResource" }))
    77  
    78      expect(screen.getByLabelText("selectedResource")).toHaveTextContent("res1")
    79      expect(location.pathname.endsWith("/r/res1/overview")).toBe(true)
    80    })
    81  
    82    it("filters resources that don't validate", () => {
    83      customRender({ initialEntries: [`/r/${INVALID_RESOURCE}/overview`] })
    84  
    85      expect(screen.getByLabelText("selectedResource")).toHaveTextContent("")
    86      expect(screen.getByLabelText("invalidResource")).toHaveTextContent(
    87        INVALID_RESOURCE
    88      )
    89    })
    90  
    91    it("always validates the 'all' resource", () => {
    92      customRender({
    93        initialEntries: [`/r/${ResourceName.all}/overview`],
    94        validateOverride: (_name: string) => false,
    95      })
    96  
    97      expect(screen.getByLabelText("selectedResource")).toHaveTextContent(
    98        ResourceName.all
    99      )
   100      expect(screen.getByLabelText("invalidResource")).toHaveTextContent("")
   101    })
   102  
   103    it("encodes resource names", () => {
   104      customRender()
   105  
   106      userEvent.type(screen.getByRole("textbox"), "foo/bar")
   107      userEvent.click(screen.getByRole("button", { name: "openResource" }))
   108  
   109      expect(screen.getByLabelText("selectedResource")).toHaveTextContent(
   110        "foo/bar"
   111      )
   112      expect(location.pathname.endsWith("/r/foo%2Fbar/overview")).toBe(true)
   113    })
   114  
   115    it("preserves filters by resource", () => {
   116      customRender()
   117  
   118      let nav = (res: string) => {
   119        userEvent.clear(screen.getByRole("textbox"))
   120        userEvent.type(screen.getByRole("textbox"), res)
   121        userEvent.click(screen.getByRole("button", { name: "openResource" }))
   122      }
   123  
   124      // We can't directly check the MemoryRouter's location, so just check the selectedResource label
   125      nav("foo")
   126      expect(screen.getByLabelText("selectedResource")).toHaveTextContent("foo")
   127      // Simulate navigation with query param
   128      act(() => navigate("/r/foo/overview?term=hi"))
   129      nav("bar")
   130      expect(screen.getByLabelText("selectedResource")).toHaveTextContent("bar")
   131      nav("foo")
   132      expect(screen.getByLabelText("selectedResource")).toHaveTextContent("foo")
   133    })
   134  
   135    // Make sure that useResourceNav() doesn't break memoization.
   136    it("memoizes renders", () => {
   137      let renderCount = 0
   138      let FakeEl = React.memo(() => {
   139        useResourceNav()
   140        renderCount++
   141        return <div></div>
   142      })
   143  
   144      let validateResource = () => true
   145      let { rerender } = render(
   146        <MemoryRouter>
   147          <ResourceNavProvider validateResource={validateResource}>
   148            <FakeEl />
   149          </ResourceNavProvider>
   150        </MemoryRouter>
   151      )
   152  
   153      expect(renderCount).toEqual(1)
   154  
   155      // Make sure we don't re-render on a no-op update.
   156      rerender(
   157        <MemoryRouter>
   158          <ResourceNavProvider validateResource={validateResource}>
   159            <FakeEl />
   160          </ResourceNavProvider>
   161          <LocationCapture />
   162        </MemoryRouter>
   163      )
   164      expect(renderCount).toEqual(1)
   165  
   166      // Make sure we do re-render on a real location update.
   167      act(() => navigate("/r/foo"))
   168      expect(renderCount).toEqual(2)
   169    })
   170  })