|
1 | 1 | import { StrictMode } from 'react' |
2 | 2 | import { act, fireEvent, render, screen } from '@testing-library/react' |
3 | 3 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' |
4 | | -import { proxy, snapshot, useSnapshot } from 'valtio' |
| 4 | +import { proxy, snapshot, subscribe, useSnapshot } from 'valtio' |
5 | 5 | import { proxyMap, proxySet } from 'valtio/utils' |
6 | 6 |
|
7 | 7 | const initialValues = [ |
@@ -329,6 +329,83 @@ describe('proxyMap', () => { |
329 | 329 | ).toBe(false) |
330 | 330 | }) |
331 | 331 |
|
| 332 | + // The todo-with-proxyMap example stores objects and mutates them in place |
| 333 | + // via get() and forEach(). The other ui tests here only store primitives. |
| 334 | + it('should proxy object values and notify when one is mutated', async () => { |
| 335 | + const state = proxy({ |
| 336 | + todos: proxyMap<number, { name: string; completed: boolean }>(), |
| 337 | + }) |
| 338 | + state.todos.set(1, { name: 'a', completed: false }) |
| 339 | + |
| 340 | + const handler = vi.fn() |
| 341 | + subscribe(state, handler) |
| 342 | + |
| 343 | + state.todos.get(1)!.completed = true |
| 344 | + await vi.advanceTimersByTimeAsync(0) |
| 345 | + |
| 346 | + expect(handler).toBeCalledTimes(1) |
| 347 | + expect(state.todos.get(1)).toEqual({ name: 'a', completed: true }) |
| 348 | + expect(snapshot(state).todos.get(1)).toEqual({ |
| 349 | + name: 'a', |
| 350 | + completed: true, |
| 351 | + }) |
| 352 | + }) |
| 353 | + |
| 354 | + it('should notify when object values are mutated during forEach', async () => { |
| 355 | + const state = proxy({ |
| 356 | + todos: proxyMap<number, { completed: boolean }>([ |
| 357 | + [1, { completed: false }], |
| 358 | + [2, { completed: false }], |
| 359 | + ]), |
| 360 | + }) |
| 361 | + |
| 362 | + const handler = vi.fn() |
| 363 | + subscribe(state, handler) |
| 364 | + |
| 365 | + state.todos.forEach((todo) => { |
| 366 | + todo.completed = true |
| 367 | + }) |
| 368 | + await vi.advanceTimersByTimeAsync(0) |
| 369 | + |
| 370 | + expect(handler).toBeCalledTimes(1) |
| 371 | + expect(Array.from(state.todos.values())).toEqual([ |
| 372 | + { completed: true }, |
| 373 | + { completed: true }, |
| 374 | + ]) |
| 375 | + }) |
| 376 | + |
| 377 | + it('should re-render a component when an object value is mutated', async () => { |
| 378 | + const state = proxy({ |
| 379 | + todos: proxyMap<number, { name: string; completed: boolean }>([ |
| 380 | + [1, { name: 'a', completed: false }], |
| 381 | + ]), |
| 382 | + }) |
| 383 | + |
| 384 | + const TestComponent = () => { |
| 385 | + const snap = useSnapshot(state) |
| 386 | + return ( |
| 387 | + <div> |
| 388 | + {Array.from(snap.todos.values()) |
| 389 | + .map((todo) => `${todo.name}:${todo.completed}`) |
| 390 | + .join(',')} |
| 391 | + </div> |
| 392 | + ) |
| 393 | + } |
| 394 | + |
| 395 | + render( |
| 396 | + <StrictMode> |
| 397 | + <TestComponent /> |
| 398 | + </StrictMode>, |
| 399 | + ) |
| 400 | + |
| 401 | + expect(screen.getByText('a:false')).toBeInTheDocument() |
| 402 | + |
| 403 | + state.todos.get(1)!.completed = true |
| 404 | + await act(() => vi.advanceTimersByTimeAsync(0)) |
| 405 | + |
| 406 | + expect(screen.getByText('a:true')).toBeInTheDocument() |
| 407 | + }) |
| 408 | + |
332 | 409 | it('should not implement getOrInsert', () => { |
333 | 410 | const map = proxyMap<string, number>() as any |
334 | 411 | expect(() => map.getOrInsert('a', 1)).toThrow('not implemented') |
|
0 commit comments