hts/packages/isdk/rsc/shared-client/streamable.ui.test.tsx

233 lines
5.1 KiB
TypeScript

import { createStreamableValue } from '../streamable';
import { readStreamableValue } from './streamable';
function nextTick() {
return Promise.resolve();
}
async function getRawChunks(s: any) {
const { next, ...otherFields } = s;
const chunks = [otherFields];
if (next) {
chunks.push(...(await getRawChunks(await next)));
}
return chunks;
}
describe('rsc - readStreamableValue()', () => {
it('should return an async iterable', () => {
const streamable = createStreamableValue();
const result = readStreamableValue(streamable.value);
streamable.done();
expect(result).toBeDefined();
expect(result[Symbol.asyncIterator]).toBeDefined();
});
it('should directly emit the final value when reading .value', async () => {
const streamable = createStreamableValue('1');
streamable.update('2');
streamable.update('3');
expect(streamable.value).toMatchInlineSnapshot(`
{
"curr": "3",
"next": Promise {},
"type": Symbol(ui.streamable.value),
}
`);
streamable.done('4');
expect(streamable.value).toMatchInlineSnapshot(`
{
"curr": "4",
"type": Symbol(ui.streamable.value),
}
`);
});
it('should be able to stream any JSON values', async () => {
const streamable = createStreamableValue();
streamable.update({ v: 123 });
expect(streamable.value).toMatchInlineSnapshot(`
{
"curr": {
"v": 123,
},
"next": Promise {},
"type": Symbol(ui.streamable.value),
}
`);
streamable.done();
});
it('should support .error()', async () => {
const streamable = createStreamableValue();
streamable.error('This is an error');
expect(streamable.value).toMatchInlineSnapshot(`
{
"error": "This is an error",
"type": Symbol(ui.streamable.value),
}
`);
});
it('should support reading streamed values and errors', async () => {
const streamable = createStreamableValue(1);
(async () => {
await nextTick();
streamable.update(2);
await nextTick();
streamable.update(3);
await nextTick();
streamable.error('This is an error');
})();
const values = [];
try {
for await (const v of readStreamableValue(streamable.value)) {
values.push(v);
}
} catch (e) {
expect(e).toMatchInlineSnapshot(`"This is an error"`);
}
expect(values).toMatchInlineSnapshot(`
[
1,
2,
3,
]
`);
});
it('should be able to read values asynchronously with different value types', async () => {
const streamable = createStreamableValue({});
(async () => {
// Defer this a bit.
await Promise.resolve();
streamable.update([1]);
streamable.update(['2']);
streamable.done({ 3: 3 });
})();
const values = [];
for await (const v of readStreamableValue(streamable.value)) {
values.push(v);
}
expect(values).toMatchInlineSnapshot(`
[
{},
[
1,
],
[
"2",
],
{
"3": 3,
},
]
`);
});
it('should be able to replay errors', async () => {
const streamable = createStreamableValue(0);
(async () => {
// Defer this a bit.
await Promise.resolve();
streamable.update(1);
streamable.update(2);
streamable.error({ customErrorMessage: 'this is an error' });
})();
const values = [];
try {
for await (const v of readStreamableValue(streamable.value)) {
values.push(v);
}
} catch (e) {
expect(e).toMatchInlineSnapshot(`
{
"customErrorMessage": "this is an error",
}
`);
}
expect(values).toMatchInlineSnapshot(`
[
0,
1,
2,
]
`);
});
describe('patch', () => {
it('should be able to append strings as patch', async () => {
const streamable = createStreamableValue();
const value = streamable.value;
streamable.update('hello');
streamable.update('hello world');
streamable.update('hello world!');
streamable.update('new string');
streamable.done('new string with patch!');
expect(await getRawChunks(value)).toMatchInlineSnapshot(`
[
{
"curr": undefined,
"type": Symbol(ui.streamable.value),
},
{
"curr": "hello",
},
{
"diff": [
0,
" world",
],
},
{
"diff": [
0,
"!",
],
},
{
"curr": "new string",
},
{
"diff": [
0,
" with patch!",
],
},
]
`);
const values = [];
for await (const v of readStreamableValue(value)) {
values.push(v);
}
expect(values).toMatchInlineSnapshot(`
[
"hello",
"hello world",
"hello world!",
"new string",
"new string with patch!",
]
`);
});
});
});