'use strict';
global.ReadableStream =
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
global.TextEncoder = require('util').TextEncoder;
describe('ReactDOMSrcObject', () => {
let React;
let ReactDOMClient;
let ReactDOMFizzServer;
let act;
let container;
let assertConsoleErrorDev;
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOMClient = require('react-dom/client');
ReactDOMFizzServer = require('react-dom/server.edge');
act = require('internal-test-utils').act;
assertConsoleErrorDev =
require('internal-test-utils').assertConsoleErrorDev;
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
jest.restoreAllMocks();
});
it('can render a Blob as an img src', async () => {
const root = ReactDOMClient.createRoot(container);
const ref = React.createRef();
const blob = new Blob();
await act(() => {
root.render(<img src={blob} ref={ref} />);
});
expect(ref.current.src).toMatch(/^blob:/);
});
it('can render a Blob as a picture img src', async () => {
const root = ReactDOMClient.createRoot(container);
const ref = React.createRef();
const blob = new Blob();
await act(() => {
root.render(
<picture>
<img src={blob} ref={ref} />
</picture>,
);
});
expect(ref.current.src).toMatch(/^blob:/);
});
it('can render a Blob as a video and audio src', async () => {
const root = ReactDOMClient.createRoot(container);
const videoRef = React.createRef();
const audioRef = React.createRef();
const blob = new Blob();
await act(() => {
root.render(
<>
<video src={blob} ref={videoRef} />
<audio src={blob} ref={audioRef} />
</>,
);
});
expect(videoRef.current.src).toMatch(/^blob:/);
expect(audioRef.current.src).toMatch(/^blob:/);
});
it('warn when rendering a Blob as a source src of a video, audio or picture element', async () => {
const root = ReactDOMClient.createRoot(container);
const videoRef = React.createRef();
const audioRef = React.createRef();
const pictureRef = React.createRef();
const blob = new Blob();
await act(() => {
root.render(
<>
<video ref={videoRef}>
<source src={blob} />
</video>
<audio ref={audioRef}>
<source src={blob} />
</audio>
<picture ref={pictureRef}>
<source src={blob} />
<img />
</picture>
</>,
);
});
assertConsoleErrorDev([
'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
'Pass it directly to <img src>, <video src> or <audio src> instead.',
'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
'Pass it directly to <img src>, <video src> or <audio src> instead.',
'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
'Pass it directly to <img src>, <video src> or <audio src> instead.',
]);
expect(videoRef.current.firstChild.src).not.toMatch(/^blob:/);
expect(videoRef.current.firstChild.src).toContain('[object%20Blob]');
expect(audioRef.current.firstChild.src).not.toMatch(/^blob:/);
expect(audioRef.current.firstChild.src).toContain('[object%20Blob]');
expect(pictureRef.current.firstChild.src).not.toMatch(/^blob:/);
expect(pictureRef.current.firstChild.src).toContain('[object%20Blob]');
});
async function readContent(stream) {
const reader = stream.getReader();
let content = '';
while (true) {
const {done, value} = await reader.read();
if (done) {
return content;
}
content += Buffer.from(value).toString('utf8');
}
}
it('can SSR a Blob as an img src', async () => {
const blob = new Blob([new Uint8Array([69, 230, 156, 181, 68, 75])], {
type: 'image/jpeg',
});
const ref = React.createRef();
function App() {
return <img src={blob} ref={ref} />;
}
const stream = await ReactDOMFizzServer.renderToReadableStream(<App />);
container.innerHTML = await readContent(stream);
expect(container.firstChild.src).toBe('data:image/jpeg;base64,ReactURL');
await act(() => {
ReactDOMClient.hydrateRoot(container, <App />);
});
expect(container.firstChild.src).toBe('data:image/jpeg;base64,ReactURL');
});
it('errors in DEV when mismatching a Blob during hydration', async () => {
const blob = new Blob([new Uint8Array([69, 230, 156, 181, 68, 75])], {
type: 'image/jpeg',
});
const ref = React.createRef();
const stream = await ReactDOMFizzServer.renderToReadableStream(
<img src={blob} ref={ref} />,
);
container.innerHTML = await readContent(stream);
expect(container.firstChild.src).toBe('data:image/jpeg;base64,ReactURL');
const clientBlob = new Blob([new Uint8Array([69, 230, 156, 181, 68])], {
type: 'image/jpeg',
});
await act(() => {
ReactDOMClient.hydrateRoot(container, <img src={clientBlob} ref={ref} />);
});
assertConsoleErrorDev([
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n" +
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
"- Date formatting in a user's locale which doesn't match the server.\n" +
'- External changing data without sending a snapshot of it along with the HTML.\n' +
'- Invalid HTML tag nesting.\n\n' +
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n' +
'https://react.dev/link/hydration-mismatch\n\n' +
' <img\n' +
'+ src={Blob:image/jpeg}\n' +
'- src="data:image/jpeg;base64,ReactURL"\n' +
' ref={{current:null}}\n' +
' >\n' +
'\n in img (at **)',
]);
expect(container.firstChild.src).toBe('data:image/jpeg;base64,ReactURL');
});
});