Skip to content

Commit

Permalink
Added conditional executor suspension
Browse files Browse the repository at this point in the history
  • Loading branch information
smikhalevski committed Jun 18, 2024
1 parent 334d5d1 commit e8e10fd
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 46 deletions.
32 changes: 16 additions & 16 deletions src/main/useExecutorSuspense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends Executor | Executor[]>(executors: T): T {
export function useExecutorSuspense<T extends Executor | Executor[]>(
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<unknown>[] | null, executor: Executor): PromiseLike<unknown>[] | null {
if (executor.isPending) {
if (promises === null) {
promises = [];
}
promises.push(executor.getOrAwait().then(noop, noop));
}
return promises;
return executors;
}
16 changes: 11 additions & 5 deletions src/test/useExecutor.test.ts → src/test/useExecutor.test.tsx
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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 => (
<StrictMode>
<ExecutorManagerProvider value={manager}>{props.children}</ExecutorManagerProvider>
</StrictMode>
),
});

const executor1 = hook.result.current;
Expand All @@ -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 => (
<StrictMode>
<ExecutorManagerProvider value={manager}>{props.children}</ExecutorManagerProvider>
</StrictMode>
),
});

const executor1 = hook.result.current;
Expand Down
25 changes: 0 additions & 25 deletions src/test/useExecutorSuspense.test.ts

This file was deleted.

105 changes: 105 additions & 0 deletions src/test/useExecutorSuspense.test.tsx
Original file line number Diff line number Diff line change
@@ -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(
<Suspense fallback={'ccc'}>
<Component />
</Suspense>
);

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(
<ExecutorManagerProvider value={manager}>
<Suspense>
<Component />
</Suspense>
</ExecutorManagerProvider>
);

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(
<ExecutorManagerProvider value={manager}>
<Suspense>
<Component />
</Suspense>
</ExecutorManagerProvider>
);

expect(await result.findByText('bbb')).toBeInTheDocument();

expect(capture).toHaveBeenCalledTimes(2);
expect(capture).toHaveBeenNthCalledWith(1, 'aaa');
expect(capture).toHaveBeenNthCalledWith(2, 'bbb');
});
});
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"isolatedModules": true,
"noUnusedLocals": true,
"downlevelIteration": true,
"jsx": "react",
"strict": true,
"target": "ES6",
"module": "ES6",
Expand Down

0 comments on commit e8e10fd

Please sign in to comment.