diff --git a/src/main/useExecutorSuspense.ts b/src/main/useExecutorSuspense.ts index 53cd252..20323c3 100644 --- a/src/main/useExecutorSuspense.ts +++ b/src/main/useExecutorSuspense.ts @@ -5,28 +5,28 @@ import { noop } from './utils'; * Suspends rendering until all of provided executors are settled. * * @param executors Executors to wait for. - * @returns Provided executors. + * @param predicate The predicate which a pending executor must conform to suspend the rendering process. By default, + * only non-fulfilled executors are awaited. * @template T Executors to wait for. */ -export function useExecutorSuspense(executors: T): T { +export function useExecutorSuspense( + executors: T, + predicate = (executor: Executor) => !executor.isFulfilled +): T { if (Array.isArray(executors)) { - const promises = executors.reduce(reducePending, null); + const promises = []; - if (promises !== null) { - throw Promise.all(promises).then(noop, noop); + for (const executor of executors) { + if (executor.isPending && predicate(executor)) { + promises.push(executor.getOrAwait().then(noop, noop)); + } } - } else if (executors.isPending) { + if (promises.length !== 0) { + throw Promise.all(promises); + } + } else if (executors.isPending && predicate(executors)) { throw executors.getOrAwait().then(noop, noop); } - return executors; -} -function reducePending(promises: PromiseLike[] | null, executor: Executor): PromiseLike[] | null { - if (executor.isPending) { - if (promises === null) { - promises = []; - } - promises.push(executor.getOrAwait().then(noop, noop)); - } - return promises; + return executors; } diff --git a/src/test/useExecutor.test.ts b/src/test/useExecutor.test.tsx similarity index 92% rename from src/test/useExecutor.test.ts rename to src/test/useExecutor.test.tsx index 1526137..50d6803 100644 --- a/src/test/useExecutor.test.ts +++ b/src/test/useExecutor.test.tsx @@ -1,5 +1,5 @@ import { act, renderHook } from '@testing-library/react'; -import { createElement, StrictMode } from 'react'; +import React, { StrictMode } from 'react'; import { ExecutorManager, ExecutorManagerProvider, useExecutor } from '../main'; describe('useExecutor', () => { @@ -27,8 +27,11 @@ describe('useExecutor', () => { }); const hook = renderHook(() => useExecutor({ aaa: 111 }), { - wrapper: props => - createElement(StrictMode, null, createElement(ExecutorManagerProvider, { value: manager }, props.children)), + wrapper: props => ( + + {props.children} + + ), }); const executor1 = hook.result.current; @@ -46,8 +49,11 @@ describe('useExecutor', () => { }); const hook = renderHook(() => useExecutor(['xxx', 111]), { - wrapper: props => - createElement(StrictMode, null, createElement(ExecutorManagerProvider, { value: manager }, props.children)), + wrapper: props => ( + + {props.children} + + ), }); const executor1 = hook.result.current; diff --git a/src/test/useExecutorSuspense.test.ts b/src/test/useExecutorSuspense.test.ts deleted file mode 100644 index ac90ef9..0000000 --- a/src/test/useExecutorSuspense.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import '@testing-library/jest-dom'; -import { render } from '@testing-library/react'; -import { createElement, Suspense } from 'react'; -import { useExecutor, useExecutorSuspense } from '../main'; - -describe('useExecutorSuspense', () => { - test('suspends component rendering until executors are settled', async () => { - const Child = () => { - const executor1 = useExecutor('xxx', () => 'aaa'); - const executor2 = useExecutor('yyy', () => 'bbb'); - - useExecutorSuspense([executor1, executor2]); - - return executor1.get() + executor2.get(); - }; - - const Parent = () => createElement(Suspense, { fallback: 'ccc' }, createElement(Child)); - - const result = render(createElement(Parent)); - - expect(result.getByText('ccc')).toBeInTheDocument(); - - expect(await result.findByText('aaabbb')).toBeInTheDocument(); - }); -}); diff --git a/src/test/useExecutorSuspense.test.tsx b/src/test/useExecutorSuspense.test.tsx new file mode 100644 index 0000000..2f97a8c --- /dev/null +++ b/src/test/useExecutorSuspense.test.tsx @@ -0,0 +1,105 @@ +import '@testing-library/jest-dom'; +import { render } from '@testing-library/react'; +import React, { Suspense, useEffect } from 'react'; +import { + ExecutorManager, + ExecutorManagerProvider, + useExecutor, + useExecutorSubscription, + useExecutorSuspense, +} from '../main'; + +describe('useExecutorSuspense', () => { + test('suspends component rendering until executors are settled', async () => { + const Component = () => { + const executor1 = useExecutor('xxx', () => 'aaa'); + const executor2 = useExecutor('yyy', () => 'bbb'); + + useExecutorSuspense([executor1, executor2]); + + return executor1.get() + executor2.get(); + }; + + const result = render( + + + + ); + + expect(result.getByText('ccc')).toBeInTheDocument(); + + expect(await result.findByText('aaabbb')).toBeInTheDocument(); + }); + + test('does not suspend rendering if the pending executor is settled', async () => { + const manager = new ExecutorManager(); + const capture = jest.fn(); + + const executor = manager.getOrCreate('xxx', 'aaa'); + + const Component = () => { + useExecutorSubscription(executor); + + useEffect(() => { + executor.execute(async () => 'bbb'); + }, []); + + useExecutorSuspense(executor); + + capture(executor.value); + + return executor.value; + }; + + const result = render( + + + + + + ); + + expect(await result.findByText('bbb')).toBeInTheDocument(); + + expect(capture).toHaveBeenCalledTimes(3); + expect(capture).toHaveBeenNthCalledWith(1, 'aaa'); + expect(capture).toHaveBeenNthCalledWith(2, 'aaa'); + expect(capture).toHaveBeenNthCalledWith(3, 'bbb'); + }); + + test('respects a custom condition', async () => { + const manager = new ExecutorManager(); + const capture = jest.fn(); + const conditionMock = jest.fn().mockReturnValue(true); + + const executor = manager.getOrCreate('xxx', 'aaa'); + + const Component = () => { + useExecutorSubscription(executor); + + useEffect(() => { + executor.execute(async () => 'bbb'); + }, []); + + useExecutorSuspense(executor, conditionMock); + + capture(executor.value); + + return executor.value; + }; + + const result = render( + + + + + + ); + + expect(await result.findByText('bbb')).toBeInTheDocument(); + + expect(capture).toHaveBeenCalledTimes(2); + expect(capture).toHaveBeenNthCalledWith(1, 'aaa'); + expect(capture).toHaveBeenNthCalledWith(2, 'bbb'); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index ce7e89d..dca7892 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,6 +6,7 @@ "isolatedModules": true, "noUnusedLocals": true, "downlevelIteration": true, + "jsx": "react", "strict": true, "target": "ES6", "module": "ES6",