Created
September 16, 2025 17:16
-
-
Save faiwer/2d2590f0fbdaf50a1bc88581fa535bb3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* eslint-disable @typescript-eslint/no-non-null-assertion -- ignore it. */ | |
| /* eslint-disable @typescript-eslint/no-unused-vars -- ignore it. */ | |
| /* eslint-disable simple-import-sort/imports -- ignore it. */ | |
| import { act, renderHook, render } from '@testing-library/react'; | |
| import { useEffect, useState, useSyncExternalStore } from 'react'; | |
| it('test', () => { | |
| const store = new Store<State>({ | |
| id: '1', | |
| articles: { 1: { title: '1' } }, | |
| }); | |
| const subscribe = (callback: () => void) => store.subscribe(callback); | |
| const log = globalThis.console.log.bind(console); | |
| const Parent = () => { | |
| const id = useSyncExternalStore(subscribe, () => | |
| store.get((state) => { | |
| log('selectInParent', state); | |
| selectInChild(state); | |
| return state.id; | |
| }), | |
| ); | |
| return id ? <Child id={id} /> : null; | |
| }; | |
| const unmountChild = jest.fn(); | |
| const selectInChild = jest.fn(); | |
| const Child = ({ id }: { id: string }) => { | |
| const title = useSyncExternalStore(subscribe, () => | |
| store.get((state) => { | |
| log( | |
| 'selectInChild', | |
| state, | |
| 'id=', | |
| id, | |
| 'title=', | |
| state.articles?.[id]?.title, | |
| ); | |
| if (!state.articles?.[id]?.title) { | |
| console.trace(); | |
| } | |
| selectInChild(state); | |
| return state.articles[id].title; | |
| }), | |
| ); | |
| useEffect( | |
| () => () => { | |
| log('unmountChild'); | |
| unmountChild(); | |
| }, | |
| [], | |
| ); | |
| return <h1>{title}</h1>; | |
| }; | |
| const result = render(<Parent />); | |
| expect(result.container.innerHTML).toBe('<h1>1</h1>'); | |
| expect(unmountChild).not.toHaveBeenCalled(); | |
| const selectCallCount = selectInChild.mock.calls.length; | |
| expect(selectInChild).toHaveBeenLastCalledWith(store.get((root) => root)); | |
| act(() => store.set({ id: null, articles: {} })); | |
| expect(result.container.innerHTML).toBe(''); | |
| expect(unmountChild).toHaveBeenCalled(); | |
| // expect(selectInChild).toHaveBeenLastCalledWith({ | |
| // renderChild: true, | |
| // value: 1, | |
| // }); | |
| expect(selectInChild).toHaveBeenCalledTimes(selectCallCount); | |
| }); | |
| interface State { | |
| id: string | null; | |
| articles: Record<string, { title: string }>; | |
| } | |
| export class Store<State> { | |
| private subscribers = new Set<() => void>(); | |
| constructor(private data: State) {} | |
| subscribe(onStoreChange: () => void): () => void { | |
| this.subscribers.add(onStoreChange); | |
| return () => this.subscribers.delete(onStoreChange); | |
| } | |
| get<T>(selector: (data: State) => T): T { | |
| return selector(this.data); | |
| } | |
| set(data: State) { | |
| if (this.data === data) { | |
| return; | |
| } | |
| this.data = data; | |
| for (const subscriber of this.subscribers) { | |
| subscriber(); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment