233 lines
5.1 KiB
TypeScript
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!",
|
|
]
|
|
`);
|
|
});
|
|
});
|
|
});
|