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 })