Skip to content

Commit 01a30fc

Browse files
authored
feat: special handling for async iterables with a current value( #55)
1 parent 39eae6f commit 01a30fc

File tree

6 files changed

+287
-43
lines changed

6 files changed

+287
-43
lines changed

spec/tests/Iterate.spec.tsx

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('`Iterate` component', () => {
4545

4646
it(
4747
gray(
48-
'When used in the no-render-function form and given an iterable that yields a value in conjunction with some initial value will render correctly'
48+
'When used in the no-render-function form and given an iterable that yields a value starting with some initial value will render correctly'
4949
),
5050
async () => {
5151
const channel = new IteratorChannelTestHelper<string>();
@@ -60,7 +60,7 @@ describe('`Iterate` component', () => {
6060

6161
it(
6262
gray(
63-
'When used in the no-render-function form and updated with non-iterable values consecutively in conjunction with some initial value will render correctly'
63+
'When used in the no-render-function form and updated with non-iterable values consecutively starting with some initial value will render correctly'
6464
),
6565
async () => {
6666
const rendered = render(<></>);
@@ -106,9 +106,7 @@ describe('`Iterate` component', () => {
106106
);
107107

108108
it(
109-
gray(
110-
'When given a non-iterable value in conjunction with some initial value will render correctly'
111-
),
109+
gray('When given a non-iterable value starting with some initial value will render correctly'),
112110
async () => {
113111
let timesRerendered = 0;
114112
let lastRenderFnInput: undefined | IterationResult<string>;
@@ -178,7 +176,7 @@ describe('`Iterate` component', () => {
178176

179177
it(
180178
gray(
181-
'When given an iterable that yields a value in conjunction with some initial value will render correctly'
179+
'When given an iterable that yields a value starting with some initial value will render correctly'
182180
),
183181
async () => {
184182
const channel = new IteratorChannelTestHelper<string>();
@@ -297,7 +295,7 @@ describe('`Iterate` component', () => {
297295

298296
it(
299297
gray(
300-
'When given an iterable that completes without yielding values in conjunction with some initial value will render correctly'
298+
'When given an iterable that completes without yielding values starting with some initial value will render correctly'
301299
),
302300
async () => {
303301
const emptyIter = (async function* () {})();
@@ -410,7 +408,7 @@ describe('`Iterate` component', () => {
410408

411409
it(
412410
gray(
413-
'When given an iterable that errors without yielding values in conjunction with some initial value will render correctly'
411+
'When given an iterable that errors without yielding values starting with some initial value will render correctly'
414412
),
415413
async () => {
416414
const erroringIter = (async function* () {
@@ -624,6 +622,62 @@ describe('`Iterate` component', () => {
624622
}
625623
);
626624

625+
describe(
626+
gray(
627+
'When given an iterable with a `.value.current` property at any point, uses that as the current value and skips the pending stage'
628+
),
629+
() =>
630+
[{ initialValue: undefined }, { initialValue: '_' }].forEach(({ initialValue }) => {
631+
it(
632+
gray(`${!initialValue ? 'without initial value' : 'with initial value and ignoring it'}`),
633+
async () => {
634+
const renderFn = vi.fn() as Mock<
635+
(next: IterationResult<AsyncIterable<string>, string>) => any
636+
>;
637+
const [channel1, channel2] = ['__current__1', '__current__2'].map(current =>
638+
Object.assign(new IteratorChannelTestHelper<string>(), {
639+
value: { current },
640+
})
641+
);
642+
643+
const Component = (props: { value: AsyncIterable<string> }) => (
644+
<Iterate value={props.value} initialValue={initialValue}>
645+
{renderFn.mockImplementation(() => (
646+
<div id="test-created-elem">Render count: {renderFn.mock.calls.length}</div>
647+
))}
648+
</Iterate>
649+
);
650+
651+
const rendered = render(<></>);
652+
const renderedHtmls = [];
653+
654+
for (const run of [
655+
() => act(() => rendered.rerender(<Component value={channel1} />)),
656+
() => act(() => channel1.put('a')),
657+
() => act(() => rendered.rerender(<Component value={channel2} />)),
658+
() => act(() => channel2.put('b')),
659+
]) {
660+
await run();
661+
renderedHtmls.push(rendered.container.innerHTML);
662+
}
663+
664+
expect(renderFn.mock.calls.flat()).toStrictEqual([
665+
{ value: '__current__1', pendingFirst: false, done: false, error: undefined },
666+
{ value: 'a', pendingFirst: false, done: false, error: undefined },
667+
{ value: '__current__2', pendingFirst: false, done: false, error: undefined },
668+
{ value: 'b', pendingFirst: false, done: false, error: undefined },
669+
]);
670+
expect(renderedHtmls).toStrictEqual([
671+
'<div id="test-created-elem">Render count: 1</div>',
672+
'<div id="test-created-elem">Render count: 2</div>',
673+
'<div id="test-created-elem">Render count: 3</div>',
674+
'<div id="test-created-elem">Render count: 4</div>',
675+
]);
676+
}
677+
);
678+
})
679+
);
680+
627681
it(gray('When unmounted will close the last active iterator it held'), async () => {
628682
let lastRenderFnInput: undefined | IterationResult<string | undefined>;
629683

spec/tests/IterateMulti.spec.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,72 @@ describe('`IterateMulti` hook', () => {
665665
}
666666
);
667667

668+
describe(
669+
gray(
670+
'When given multiple iterables with `.value.current` properties at any point, uses these as current values respectively and skips the pending stages'
671+
),
672+
() =>
673+
[{ initialValues: undefined }, { initialValues: ['_1', '_2'] }].forEach(
674+
({ initialValues }) => {
675+
it(
676+
gray(
677+
`${!initialValues?.length ? 'without initial values' : 'with initial values and ignoring them'}`
678+
),
679+
async () => {
680+
const renderFn = vi.fn() as Mock<
681+
(nexts: IterationResultSet<AsyncIterable<string>[]>) => any
682+
>;
683+
const [channel1, channel2] = ['__current__1', '__current__2'].map(current =>
684+
Object.assign(new IteratorChannelTestHelper<string>(), {
685+
value: { current },
686+
})
687+
);
688+
689+
const Component = (props: { values: AsyncIterable<string>[] }) => (
690+
<IterateMulti values={props.values}>
691+
{renderFn.mockImplementation(() => (
692+
<div id="test-created-elem">Render count: {renderFn.mock.calls.length}</div>
693+
))}
694+
</IterateMulti>
695+
);
696+
697+
const rendered = render(<></>);
698+
const renderedHtmls = [];
699+
700+
for (const run of [
701+
() => act(() => rendered.rerender(<Component values={[channel1]} />)),
702+
() => act(() => channel1.put('a')),
703+
() => act(() => rendered.rerender(<Component values={[channel2, channel1]} />)),
704+
() => act(() => channel2.put('b')),
705+
]) {
706+
await run();
707+
renderedHtmls.push(rendered.container.innerHTML);
708+
}
709+
710+
expect(renderFn.mock.calls.flat()).toStrictEqual([
711+
[{ value: '__current__1', pendingFirst: false, done: false, error: undefined }],
712+
[{ value: 'a', pendingFirst: false, done: false, error: undefined }],
713+
[
714+
{ value: '__current__2', pendingFirst: false, done: false, error: undefined },
715+
{ value: 'a', pendingFirst: false, done: false, error: undefined },
716+
],
717+
[
718+
{ value: 'b', pendingFirst: false, done: false, error: undefined },
719+
{ value: 'a', pendingFirst: false, done: false, error: undefined },
720+
],
721+
]);
722+
expect(renderedHtmls).toStrictEqual([
723+
'<div id="test-created-elem">Render count: 1</div>',
724+
'<div id="test-created-elem">Render count: 2</div>',
725+
'<div id="test-created-elem">Render count: 3</div>',
726+
'<div id="test-created-elem">Render count: 4</div>',
727+
]);
728+
}
729+
);
730+
}
731+
)
732+
);
733+
668734
it(gray('When unmounted will close all active iterators it has been holding'), async () => {
669735
const renderFn = vi.fn() as Mock<
670736
(

spec/tests/useAsyncIter.spec.ts

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe('`useAsyncIter` hook', () => {
5555

5656
it(
5757
gray(
58-
'When given a non-iterable value in conjunction with some initial value will return correct results'
58+
'When given a non-iterable value starting in some initial value will return correct results'
5959
),
6060
async () => {
6161
let timesRerendered = 0;
@@ -99,7 +99,7 @@ describe('`useAsyncIter` hook', () => {
9999

100100
it(
101101
gray(
102-
'When given an iterable that yields a value in conjunction with some initial value will return correct results'
102+
'When given an iterable that yields a value starting in some initial value will return correct results'
103103
),
104104
async () => {
105105
const channel = new IteratorChannelTestHelper<string>();
@@ -183,7 +183,7 @@ describe('`useAsyncIter` hook', () => {
183183

184184
it(
185185
gray(
186-
'When given an iterable that completes without yielding values in conjunction with some initial value will return correct results'
186+
'When given an iterable that completes without yielding values starting in some initial value will return correct results'
187187
),
188188
async () => {
189189
let timesRerendered = 0;
@@ -269,7 +269,7 @@ describe('`useAsyncIter` hook', () => {
269269

270270
it(
271271
gray(
272-
'When given an iterable that errors without yielding values in conjunction with some initial value will return correct results'
272+
'When given an iterable that errors without yielding values starting in some initial value will return correct results'
273273
),
274274
async () => {
275275
let timesRerendered = 0;
@@ -430,6 +430,48 @@ describe('`useAsyncIter` hook', () => {
430430
}
431431
);
432432

433+
describe(
434+
gray(
435+
'When given an iterable with a `.value.current` property at any point, uses that as the current value and skips the pending stage'
436+
),
437+
() =>
438+
[{ initialValue: undefined }, { initialValue: '_' }].forEach(({ initialValue }) => {
439+
it(
440+
gray(`${!initialValue ? 'without initial value' : 'with initial value and ignoring it'}`),
441+
async () => {
442+
const [channel1, channel2] = ['__current__1', '__current__2'].map(current =>
443+
Object.assign(new IteratorChannelTestHelper<string>(), {
444+
value: { current },
445+
})
446+
);
447+
448+
const renderedHook = renderHook(props => useAsyncIter(props.channel, initialValue), {
449+
initialProps: { channel: undefined as undefined | AsyncIterable<string> },
450+
});
451+
452+
const results: any[] = [];
453+
454+
for (const run of [
455+
() => act(() => renderedHook.rerender({ channel: channel1 })),
456+
() => act(() => channel1.put('a')),
457+
() => act(() => renderedHook.rerender({ channel: channel2 })),
458+
() => act(() => channel2.put('b')),
459+
]) {
460+
await run();
461+
results.push(renderedHook.result.current);
462+
}
463+
464+
expect(results).toStrictEqual([
465+
{ value: '__current__1', pendingFirst: false, done: false, error: undefined },
466+
{ value: 'a', pendingFirst: false, done: false, error: undefined },
467+
{ value: '__current__2', pendingFirst: false, done: false, error: undefined },
468+
{ value: 'b', pendingFirst: false, done: false, error: undefined },
469+
]);
470+
}
471+
);
472+
})
473+
);
474+
433475
it(gray('When unmounted will close the last active iterator it held'), async () => {
434476
const channel = new IteratorChannelTestHelper<string>();
435477

spec/tests/useAsyncIterMulti.spec.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,59 @@ describe('`useAsyncIterMulti` hook', () => {
444444
}
445445
);
446446

447+
describe(
448+
gray(
449+
'When given multiple iterables with `.value.current` properties at any point, uses these as current values respectively and skips the pending stages'
450+
),
451+
() =>
452+
[{ initialValues: undefined }, { initialValues: ['_1', '_2'] }].forEach(
453+
({ initialValues }) => {
454+
it(
455+
gray(
456+
`${!initialValues?.length ? 'without initial values' : 'with initial values and ignoring them'}`
457+
),
458+
async () => {
459+
const [channel1, channel2] = ['__current__1', '__current__2'].map(current =>
460+
Object.assign(new IteratorChannelTestHelper<string>(), {
461+
value: { current },
462+
})
463+
);
464+
465+
const renderedHook = renderHook(
466+
props => useAsyncIterMulti(props.channels, { initialValues }),
467+
{ initialProps: { channels: [] as AsyncIterable<string>[] } }
468+
);
469+
470+
const results: any[] = [];
471+
472+
for (const run of [
473+
() => act(() => renderedHook.rerender({ channels: [channel1] })),
474+
() => act(() => channel1.put('a')),
475+
() => act(() => renderedHook.rerender({ channels: [channel2, channel1] })),
476+
() => act(() => channel2.put('b')),
477+
]) {
478+
await run();
479+
results.push(renderedHook.result.current);
480+
}
481+
482+
expect(results).toStrictEqual([
483+
[{ value: '__current__1', pendingFirst: false, done: false, error: undefined }],
484+
[{ value: 'a', pendingFirst: false, done: false, error: undefined }],
485+
[
486+
{ value: '__current__2', pendingFirst: false, done: false, error: undefined },
487+
{ value: 'a', pendingFirst: false, done: false, error: undefined },
488+
],
489+
[
490+
{ value: 'b', pendingFirst: false, done: false, error: undefined },
491+
{ value: 'a', pendingFirst: false, done: false, error: undefined },
492+
],
493+
]);
494+
}
495+
);
496+
}
497+
)
498+
);
499+
447500
it(gray('When unmounted will close all active iterators it has been holding'), async () => {
448501
const channel1 = new IteratorChannelTestHelper<'a' | 'b' | 'c'>();
449502
const channel2 = new IteratorChannelTestHelper<'a' | 'b' | 'c'>();

0 commit comments

Comments
 (0)