starling-devex 0.1.2

Starling: a local dev orchestrator with a central daemon, shared named-URL proxy, and a k9s-style TUI (a Rust port of Tilt + portless)
import { render, RenderOptions, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import React, { ChangeEvent, useState } from "react"
import { act } from "react-dom/test-utils"
import { MemoryRouter } from "react-router"
import { useLocation, useNavigate } from "react-router-dom"
import { ResourceNavProvider, useResourceNav } from "./ResourceNav"
import { ResourceName } from "./types"

const INVALID_RESOURCE = "res3"

function TestResourceNavConsumer() {
  const { selectedResource, invalidResource, openResource } = useResourceNav()
  const [resourceToSelect, setResourceToSelect] = useState("")

  return (
    <>
      <p aria-label="selectedResource">{selectedResource}</p>
      <p aria-label="invalidResource">{invalidResource}</p>
      <input
        aria-label="Resource to select"
        type="text"
        value={resourceToSelect}
        onChange={(e: ChangeEvent<HTMLInputElement>) =>
          setResourceToSelect(e.target.value)
        }
      />
      <button onClick={() => openResource(resourceToSelect)}>
        openResource
      </button>
    </>
  )
}

let location: any = window.location
let navigate: any = null

function LocationCapture() {
  location = useLocation()
  navigate = useNavigate()
  return null
}

function customRender(
  wrapperOptions: {
    initialEntries?: string[]
    validateOverride?: (name: string) => boolean
  } = {},
  options?: RenderOptions
) {
  const validateResource =
    wrapperOptions.validateOverride ??
    function (name: string) {
      return name !== INVALID_RESOURCE
    }
  return render(<TestResourceNavConsumer />, {
    wrapper: ({ children }) => (
      <MemoryRouter
        initialEntries={wrapperOptions.initialEntries || ["/"]}
        future={{ v7_startTransition: true, v7_relativeSplatPath: true }}
      >
        <ResourceNavProvider validateResource={validateResource}>
          {children}
        </ResourceNavProvider>
        <LocationCapture />
      </MemoryRouter>
    ),
    ...options,
  })
}

describe("ResourceNavContext", () => {
  it("navigates to resource on click", () => {
    customRender()

    expect(screen.getByLabelText("selectedResource")).toHaveTextContent("")

    userEvent.type(screen.getByRole("textbox"), "res1")
    userEvent.click(screen.getByRole("button", { name: "openResource" }))

    expect(screen.getByLabelText("selectedResource")).toHaveTextContent("res1")
    expect(location.pathname.endsWith("/r/res1/overview")).toBe(true)
  })

  it("filters resources that don't validate", () => {
    customRender({ initialEntries: [`/r/${INVALID_RESOURCE}/overview`] })

    expect(screen.getByLabelText("selectedResource")).toHaveTextContent("")
    expect(screen.getByLabelText("invalidResource")).toHaveTextContent(
      INVALID_RESOURCE
    )
  })

  it("always validates the 'all' resource", () => {
    customRender({
      initialEntries: [`/r/${ResourceName.all}/overview`],
      validateOverride: (_name: string) => false,
    })

    expect(screen.getByLabelText("selectedResource")).toHaveTextContent(
      ResourceName.all
    )
    expect(screen.getByLabelText("invalidResource")).toHaveTextContent("")
  })

  it("encodes resource names", () => {
    customRender()

    userEvent.type(screen.getByRole("textbox"), "foo/bar")
    userEvent.click(screen.getByRole("button", { name: "openResource" }))

    expect(screen.getByLabelText("selectedResource")).toHaveTextContent(
      "foo/bar"
    )
    expect(location.pathname.endsWith("/r/foo%2Fbar/overview")).toBe(true)
  })

  it("preserves filters by resource", () => {
    customRender()

    let nav = (res: string) => {
      userEvent.clear(screen.getByRole("textbox"))
      userEvent.type(screen.getByRole("textbox"), res)
      userEvent.click(screen.getByRole("button", { name: "openResource" }))
    }

    // We can't directly check the MemoryRouter's location, so just check the selectedResource label
    nav("foo")
    expect(screen.getByLabelText("selectedResource")).toHaveTextContent("foo")
    // Simulate navigation with query param
    act(() => navigate("/r/foo/overview?term=hi"))
    nav("bar")
    expect(screen.getByLabelText("selectedResource")).toHaveTextContent("bar")
    nav("foo")
    expect(screen.getByLabelText("selectedResource")).toHaveTextContent("foo")
  })

  // Make sure that useResourceNav() doesn't break memoization.
  it("memoizes renders", () => {
    let renderCount = 0
    let FakeEl = React.memo(() => {
      useResourceNav()
      renderCount++
      return <div></div>
    })

    let validateResource = () => true
    let { rerender } = render(
      <MemoryRouter
        future={{ v7_startTransition: true, v7_relativeSplatPath: true }}
      >
        <ResourceNavProvider validateResource={validateResource}>
          <FakeEl />
        </ResourceNavProvider>
      </MemoryRouter>
    )

    expect(renderCount).toEqual(1)

    // Make sure we don't re-render on a no-op update.
    rerender(
      <MemoryRouter
        future={{ v7_startTransition: true, v7_relativeSplatPath: true }}
      >
        <ResourceNavProvider validateResource={validateResource}>
          <FakeEl />
        </ResourceNavProvider>
        <LocationCapture />
      </MemoryRouter>
    )
    expect(renderCount).toEqual(1)

    // Make sure we do re-render on a real location update.
    act(() => navigate("/r/foo"))
    expect(renderCount).toEqual(2)
  })
})