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

     1  import { act, render, screen } from "@testing-library/react"
     2  import userEvent from "@testing-library/user-event"
     3  import React, { ChangeEvent, useState } from "react"
     4  import {
     5    ResourceSelectionProvider,
     6    useResourceSelection,
     7  } from "./ResourceSelectionContext"
     8  
     9  const SELECTION_STATE = "selected-state"
    10  const IS_SELECTED = "selected-text"
    11  const TO_SELECT_INPUT = "select-input"
    12  const SELECT_BUTTON = "select-button"
    13  const DESELECT_BUTTON = "deselect-button"
    14  const CLEAR_BUTTON = "clear-button"
    15  
    16  // This is a very basic test component that prints out the state
    17  // from the ResourceSelection context and provides buttons to trigger
    18  // methods returned by the context, so they can be tested
    19  const TestConsumer = () => {
    20    const { selected, isSelected, select, deselect, clearSelections } =
    21      useResourceSelection()
    22  
    23    const [value, setValue] = useState("")
    24    const onChange = (e: ChangeEvent<HTMLInputElement>) =>
    25      setValue(e.target.value)
    26  
    27    // To support selecting multiple items at once, accept a comma separated list
    28    const parsedValues = (value: string) => value.split(",")
    29    return (
    30      <>
    31        {/* Print the `selected` state */}
    32        <p aria-label={SELECTION_STATE}>{JSON.stringify(Array.from(selected))}</p>
    33        {/* Use an input field to change the resource that can be selected/deselected */}
    34        <input aria-label={TO_SELECT_INPUT} type="text" onChange={onChange} />
    35        {/* Print the state for whatever resource is currently in the `input` */}
    36        <p aria-label={IS_SELECTED}>{isSelected(value).toString()}</p>
    37        {/* Select the resource that's currently in the `input` */}
    38        <button
    39          aria-label={SELECT_BUTTON}
    40          onClick={() => select(...parsedValues(value))}
    41        >
    42          Select
    43        </button>
    44        {/* Deselect the resource that's currently in the `input` */}
    45        <button
    46          aria-label={DESELECT_BUTTON}
    47          onClick={() => deselect(...parsedValues(value))}
    48        >
    49          Deselect
    50        </button>
    51        {/* Clear all selections */}
    52        <button aria-label={CLEAR_BUTTON} onClick={() => clearSelections()}>
    53          Clear selections
    54        </button>
    55      </>
    56    )
    57  }
    58  
    59  describe("ResourceSelectionContext", () => {
    60    // Helpers
    61    const INITIAL_SELECTIONS = ["vigoda", "magic_beans", "servantes"]
    62    const selectedState = () =>
    63      screen.queryByLabelText(SELECTION_STATE)?.textContent
    64    const isSelected = () => screen.queryByLabelText(IS_SELECTED)?.textContent
    65    const setCurrentResource = (value: string) => {
    66      const inputField = screen.queryByLabelText(TO_SELECT_INPUT)
    67      userEvent.type(inputField as HTMLInputElement, value)
    68    }
    69    const selectResource = (value: string) => {
    70      setCurrentResource(value)
    71      const selectButton = screen.queryByLabelText(SELECT_BUTTON)
    72      userEvent.click(selectButton as HTMLButtonElement)
    73    }
    74    const deselectResource = (value: string) => {
    75      setCurrentResource(value)
    76      const deselectButton = screen.queryByLabelText(DESELECT_BUTTON)
    77      userEvent.click(deselectButton as HTMLButtonElement)
    78    }
    79    const clearResources = () => {
    80      const clearButton = screen.queryByLabelText(CLEAR_BUTTON)
    81      userEvent.click(clearButton as HTMLButtonElement)
    82    }
    83  
    84    beforeEach(() => {
    85      render(
    86        <ResourceSelectionProvider initialValuesForTesting={INITIAL_SELECTIONS}>
    87          <TestConsumer />
    88        </ResourceSelectionProvider>
    89      )
    90    })
    91  
    92    describe("`selected` property", () => {
    93      it("reports an accurate list of selected resources", () => {
    94        expect(selectedState()).toBe(JSON.stringify(INITIAL_SELECTIONS))
    95  
    96        clearResources()
    97        expect(selectedState()).toBe(JSON.stringify([]))
    98      })
    99    })
   100  
   101    describe("isSelected", () => {
   102      it("returns `true` when a resource is selected", () => {
   103        setCurrentResource(INITIAL_SELECTIONS[1])
   104        expect(isSelected()).toBe("true")
   105      })
   106  
   107      it("returns `false` when a resource is NOT selected", () => {
   108        setCurrentResource("sprout")
   109        expect(isSelected()).toBe("false")
   110      })
   111    })
   112  
   113    describe("select", () => {
   114      it("adds a resource to the list of selections", () => {
   115        selectResource("cool_beans")
   116        expect(isSelected()).toBe("true")
   117        expect(selectedState()).toBe(
   118          JSON.stringify([...INITIAL_SELECTIONS, "cool_beans"])
   119        )
   120      })
   121  
   122      it("adds multiple resources to the list of selections", () => {
   123        selectResource("cool_beans,super_beans,fancy_beans")
   124        expect(selectedState()).toBe(
   125          JSON.stringify([
   126            ...INITIAL_SELECTIONS,
   127            "cool_beans",
   128            "super_beans",
   129            "fancy_beans",
   130          ])
   131        )
   132      })
   133  
   134      it("does NOT select the same resource twice", () => {
   135        selectResource(INITIAL_SELECTIONS[0])
   136        expect(isSelected()).toBe("true")
   137        expect(selectedState()).toBe(JSON.stringify(INITIAL_SELECTIONS))
   138      })
   139    })
   140  
   141    describe("deselect", () => {
   142      it("removes a resource from the list of selections", () => {
   143        deselectResource(INITIAL_SELECTIONS[0])
   144        expect(isSelected()).toBe("false")
   145        expect(selectedState()).toBe(JSON.stringify(INITIAL_SELECTIONS.slice(1)))
   146      })
   147  
   148      it("removes multiple resources from the list of selections", () => {
   149        deselectResource(`${INITIAL_SELECTIONS[0]},${INITIAL_SELECTIONS[1]}`)
   150        expect(selectedState()).toBe(JSON.stringify([INITIAL_SELECTIONS[2]]))
   151      })
   152    })
   153  
   154    describe("clearSelections", () => {
   155      it("removes all resource selections", () => {
   156        clearResources()
   157        expect(selectedState()).toBe(JSON.stringify([]))
   158      })
   159    })
   160  
   161    it("memoizes renders", () => {
   162      let renderCount = 0
   163      let selection: any
   164      let FakeEl = React.memo(() => {
   165        selection = useResourceSelection()
   166        renderCount++
   167        return <div></div>
   168      })
   169  
   170      let tree = () => {
   171        return (
   172          <ResourceSelectionProvider initialValuesForTesting={INITIAL_SELECTIONS}>
   173            <FakeEl />
   174          </ResourceSelectionProvider>
   175        )
   176      }
   177  
   178      let { rerender } = render(tree())
   179  
   180      expect(renderCount).toEqual(1)
   181      rerender(tree())
   182  
   183      // Make sure we don't re-render on a no-op update.
   184      expect(renderCount).toEqual(1)
   185  
   186      act(() => selection.clearSelections())
   187      expect(renderCount).toEqual(2)
   188    })
   189  })