}) => {
93 | const [b] = props.state.a.b.use();
94 |
95 | bRenderCount++;
96 |
97 | return ;
98 | });
99 |
100 | render(
101 | <>
102 |
103 |
104 |
105 | >
106 | );
107 |
108 | const el = screen.getByTestId("element");
109 | const b = screen.getByTestId("b");
110 |
111 | expect(eRenderCount).toEqual(1);
112 | expect(bRenderCount).toEqual(1);
113 |
114 | expect(JSON.parse(b.dataset.b ?? "")).toEqual({ c: "cool" });
115 |
116 | act(() => {
117 | el.click();
118 | el.click();
119 | el.click();
120 | el.click();
121 | });
122 |
123 | expect(eRenderCount).toEqual(1);
124 |
125 | /**
126 | * Multiple '!' added to the string, but only re-renders once
127 | */
128 | expect(bRenderCount).toEqual(2);
129 | expect(JSON.parse(b.dataset.b ?? "")).toEqual({ c: "cool!!!!" });
130 | });
131 |
132 | describe("should update", () => {
133 | let gRenderCount = 0;
134 | let fRenderCount = 0;
135 |
136 | type GProps = {
137 | state: ProxyLens<{ g: boolean }>;
138 | };
139 |
140 | const G = React.memo((props: GProps) => {
141 | const [g, updateG] = props.state.use();
142 |
143 | const onClick = () => updateG((prev) => ({ ...prev, g: !prev.g }));
144 |
145 | gRenderCount++;
146 |
147 | return ;
148 | });
149 |
150 | type FProps = {
151 | shouldUpdate?: ShouldUpdate;
152 | };
153 |
154 | const F = (props: FProps) => {
155 | const [fState, updateF] = lens.f.use(props.shouldUpdate);
156 |
157 | const onClick = () => {
158 | updateF((f) => [...f, { g: true }]);
159 | };
160 |
161 | fRenderCount++;
162 |
163 | return (
164 |
165 | {fState.map((g) => {
166 | const lens = g.toLens();
167 | return ;
168 | })}
169 | ;
170 |
171 | );
172 | };
173 |
174 | beforeEach(() => {
175 | gRenderCount = 0;
176 | fRenderCount = 0;
177 | });
178 |
179 | test("re-renders a list when memebers added to a list", () => {
180 | render();
181 |
182 | const pushGButton = screen.getByTestId("push-g-button");
183 |
184 | expect(fRenderCount).toEqual(1);
185 | expect(gRenderCount).toEqual(2);
186 |
187 | act(() => {
188 | pushGButton.click();
189 | });
190 |
191 | expect(fRenderCount).toEqual(2);
192 | expect(gRenderCount).toEqual(3);
193 |
194 | act(() => {
195 | pushGButton.click();
196 | });
197 |
198 | expect(fRenderCount).toEqual(3);
199 | expect(gRenderCount).toEqual(4);
200 | });
201 |
202 | test("accepts length for lists", () => {
203 | // noop. just a typecheck here
204 | render();
205 | });
206 |
207 | test("function returning false never re-renders", () => {
208 | render( false} />);
209 |
210 | const pushGButton = screen.getByTestId("push-g-button");
211 |
212 | act(() => {
213 | pushGButton.click();
214 | });
215 |
216 | act(() => {
217 | pushGButton.click();
218 | });
219 |
220 | act(() => {
221 | pushGButton.click();
222 | });
223 |
224 | expect(fRenderCount).toEqual(1);
225 | expect(gRenderCount).toEqual(2); // Gs are never added because F does not re-render
226 | });
227 |
228 | test.each([(prev, next) => prev.length !== next.length, [], {}] as ShouldUpdate[])(
229 | "does not re-render unless the length has changed",
230 | (shouldUpdate) => {
231 | render();
232 |
233 | expect(fRenderCount).toEqual(1);
234 | expect(gRenderCount).toEqual(2);
235 |
236 | act(() => {
237 | const pushGButton = screen.getByTestId("push-g-button");
238 | pushGButton.click();
239 | });
240 |
241 | expect(fRenderCount).toEqual(2);
242 | expect(gRenderCount).toEqual(3);
243 |
244 | act(() => {
245 | const toggleGButtons = screen.queryAllByTestId("toggle-g");
246 | toggleGButtons.forEach((button) => button.click());
247 | });
248 |
249 | expect(fRenderCount).toEqual(2); // does not change
250 | expect(gRenderCount).toEqual(6); // re-renders all Gs
251 | }
252 | );
253 | });
254 |
255 | test("multiple hooks only trigger one re-render", () => {
256 | let renderCount = 0;
257 |
258 | const Test = () => {
259 | lens.use();
260 | const [c, setC] = lens.a.b.c.use();
261 |
262 | renderCount++;
263 |
264 | return