96 lines
2.9 KiB
JavaScript
96 lines
2.9 KiB
JavaScript
import { ReadableStream } from 'node:stream/web';
|
|
import { renderHtmlStreams } from '~/streaming/render_html_streams';
|
|
import { HtmlStream } from '~/streaming/html_stream';
|
|
import waitForPromises from 'helpers/wait_for_promises';
|
|
|
|
jest.mock('~/streaming/html_stream');
|
|
jest.mock('~/streaming/constants', () => {
|
|
return {
|
|
HIGH_FRAME_TIME: 0,
|
|
LOW_FRAME_TIME: 0,
|
|
MAX_CHUNK_SIZE: 1,
|
|
MIN_CHUNK_SIZE: 1,
|
|
};
|
|
});
|
|
|
|
const firstStreamContent = 'foobar';
|
|
const secondStreamContent = 'bazqux';
|
|
|
|
describe('renderHtmlStreams', () => {
|
|
let htmlWriter;
|
|
const encoder = new TextEncoder();
|
|
const createSingleChunkStream = (chunk) => {
|
|
const encoded = encoder.encode(chunk);
|
|
const stream = new ReadableStream({
|
|
pull(controller) {
|
|
controller.enqueue(encoded);
|
|
controller.close();
|
|
},
|
|
});
|
|
return [stream, encoded];
|
|
};
|
|
|
|
beforeEach(() => {
|
|
htmlWriter = {
|
|
write: jest.fn(),
|
|
close: jest.fn(),
|
|
abort: jest.fn(),
|
|
};
|
|
jest.spyOn(HtmlStream.prototype, 'withChunkWriter').mockReturnValue(htmlWriter);
|
|
});
|
|
|
|
it('renders a single stream', async () => {
|
|
const [stream, encoded] = createSingleChunkStream(firstStreamContent);
|
|
|
|
await renderHtmlStreams([Promise.resolve(stream)], document.body);
|
|
|
|
expect(htmlWriter.write).toHaveBeenCalledWith(encoded);
|
|
expect(htmlWriter.close).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('renders stream sequence', async () => {
|
|
const [stream1, encoded1] = createSingleChunkStream(firstStreamContent);
|
|
const [stream2, encoded2] = createSingleChunkStream(secondStreamContent);
|
|
|
|
await renderHtmlStreams([Promise.resolve(stream1), Promise.resolve(stream2)], document.body);
|
|
|
|
expect(htmlWriter.write.mock.calls).toMatchObject([[encoded1], [encoded2]]);
|
|
expect(htmlWriter.close).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("doesn't wait for the whole sequence to resolve before streaming", async () => {
|
|
const [stream1, encoded1] = createSingleChunkStream(firstStreamContent);
|
|
const [stream2, encoded2] = createSingleChunkStream(secondStreamContent);
|
|
|
|
let res;
|
|
const delayedStream = new Promise((resolve) => {
|
|
res = resolve;
|
|
});
|
|
|
|
renderHtmlStreams([Promise.resolve(stream1), delayedStream], document.body);
|
|
|
|
await waitForPromises();
|
|
|
|
expect(htmlWriter.write.mock.calls).toMatchObject([[encoded1]]);
|
|
expect(htmlWriter.close).toHaveBeenCalledTimes(0);
|
|
|
|
res(stream2);
|
|
await waitForPromises();
|
|
|
|
expect(htmlWriter.write.mock.calls).toMatchObject([[encoded1], [encoded2]]);
|
|
expect(htmlWriter.close).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('closes HtmlStream on error', async () => {
|
|
const [stream1] = createSingleChunkStream(firstStreamContent);
|
|
const error = new Error();
|
|
|
|
try {
|
|
await renderHtmlStreams([Promise.resolve(stream1), Promise.reject(error)], document.body);
|
|
} catch (err) {
|
|
expect(err).toBe(error);
|
|
}
|
|
|
|
expect(htmlWriter.abort).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|