main
js 220 lines 6.93 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @emails react-core
8 * @jest-environment ./scripts/jest/ReactDOMServerIntegrationEnvironment
9 */
10
11 'use strict';
12
13 // Polyfills for test environment
14 global.ReadableStream =
15 require('web-streams-polyfill/ponyfill/es6').ReadableStream;
16 global.TextEncoder = require('util').TextEncoder;
17
18 describe('ReactDOMSrcObject', () => {
19 let React;
20 let ReactDOMClient;
21 let ReactDOMFizzServer;
22 let act;
23 let container;
24 let assertConsoleErrorDev;
25
26 beforeEach(() => {
27 jest.resetModules();
28
29 React = require('react');
30 ReactDOMClient = require('react-dom/client');
31 ReactDOMFizzServer = require('react-dom/server.edge');
32 act = require('internal-test-utils').act;
33
34 assertConsoleErrorDev =
35 require('internal-test-utils').assertConsoleErrorDev;
36
37 container = document.createElement('div');
38 document.body.appendChild(container);
39 });
40
41 afterEach(() => {
42 document.body.removeChild(container);
43 jest.restoreAllMocks();
44 });
45
46 // @gate enableSrcObject
47 it('can render a Blob as an img src', async () => {
48 const root = ReactDOMClient.createRoot(container);
49 const ref = React.createRef();
50
51 const blob = new Blob();
52 await act(() => {
53 root.render(<img src={blob} ref={ref} />);
54 });
55
56 expect(ref.current.src).toMatch(/^blob:/);
57 });
58
59 // @gate enableSrcObject
60 it('can render a Blob as a picture img src', async () => {
61 const root = ReactDOMClient.createRoot(container);
62 const ref = React.createRef();
63
64 const blob = new Blob();
65 await act(() => {
66 root.render(
67 <picture>
68 <img src={blob} ref={ref} />
69 </picture>,
70 );
71 });
72
73 expect(ref.current.src).toMatch(/^blob:/);
74 });
75
76 // @gate enableSrcObject
77 it('can render a Blob as a video and audio src', async () => {
78 const root = ReactDOMClient.createRoot(container);
79 const videoRef = React.createRef();
80 const audioRef = React.createRef();
81
82 const blob = new Blob();
83 await act(() => {
84 root.render(
85 <>
86 <video src={blob} ref={videoRef} />
87 <audio src={blob} ref={audioRef} />
88 </>,
89 );
90 });
91
92 expect(videoRef.current.src).toMatch(/^blob:/);
93 expect(audioRef.current.src).toMatch(/^blob:/);
94 });
95
96 // @gate enableSrcObject || !__DEV__
97 it('warn when rendering a Blob as a source src of a video, audio or picture element', async () => {
98 const root = ReactDOMClient.createRoot(container);
99 const videoRef = React.createRef();
100 const audioRef = React.createRef();
101 const pictureRef = React.createRef();
102
103 const blob = new Blob();
104 await act(() => {
105 root.render(
106 <>
107 <video ref={videoRef}>
108 <source src={blob} />
109 </video>
110 <audio ref={audioRef}>
111 <source src={blob} />
112 </audio>
113 <picture ref={pictureRef}>
114 <source src={blob} />
115 <img />
116 </picture>
117 </>,
118 );
119 });
120
121 assertConsoleErrorDev([
122 'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
123 'Pass it directly to <img src>, <video src> or <audio src> instead.' +
124 '\n in source (at **)',
125 'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
126 'Pass it directly to <img src>, <video src> or <audio src> instead.' +
127 '\n in source (at **)',
128 'Passing Blob, MediaSource or MediaStream to <source src> is not supported. ' +
129 'Pass it directly to <img src>, <video src> or <audio src> instead.' +
130 '\n in source (at **)',
131 ]);
132 expect(videoRef.current.firstChild.src).not.toMatch(/^blob:/);
133 expect(videoRef.current.firstChild.src).toContain('[object%20Blob]'); // toString:ed
134 expect(audioRef.current.firstChild.src).not.toMatch(/^blob:/);
135 expect(audioRef.current.firstChild.src).toContain('[object%20Blob]'); // toString:ed
136 expect(pictureRef.current.firstChild.src).not.toMatch(/^blob:/);
137 expect(pictureRef.current.firstChild.src).toContain('[object%20Blob]'); // toString:ed
138 });
139
140 async function readContent(stream) {
141 const reader = stream.getReader();
142 let content = '';
143 while (true) {
144 const {done, value} = await reader.read();
145 if (done) {
146 return content;
147 }
148 content += Buffer.from(value).toString('utf8');
149 }
150 }
151
152 // @gate enableSrcObject
153 it('can SSR a Blob as an img src', async () => {
154 const blob = new Blob([new Uint8Array([69, 230, 156, 181, 68, 75])], {
155 type: 'image/jpeg',
156 });
157
158 const ref = React.createRef();
159
160 function App() {
161 return <img src={blob} ref={ref} />;
162 }
163
164 const stream = await ReactDOMFizzServer.renderToReadableStream(<App />);
165 container.innerHTML = await readContent(stream);
166
167 expect(container.firstChild.src).toBe('data:image/jpeg;base64,ReactURL');
168
169 await act(() => {
170 ReactDOMClient.hydrateRoot(container, <App />);
171 });
172
173 expect(container.firstChild.src).toBe('data:image/jpeg;base64,ReactURL');
174 });
175
176 // @gate enableSrcObject
177 it('errors in DEV when mismatching a Blob during hydration', async () => {
178 const blob = new Blob([new Uint8Array([69, 230, 156, 181, 68, 75])], {
179 type: 'image/jpeg',
180 });
181
182 const ref = React.createRef();
183
184 const stream = await ReactDOMFizzServer.renderToReadableStream(
185 <img src={blob} ref={ref} />,
186 );
187 container.innerHTML = await readContent(stream);
188
189 expect(container.firstChild.src).toBe('data:image/jpeg;base64,ReactURL');
190
191 const clientBlob = new Blob([new Uint8Array([69, 230, 156, 181, 68])], {
192 type: 'image/jpeg',
193 });
194
195 await act(() => {
196 ReactDOMClient.hydrateRoot(container, <img src={clientBlob} ref={ref} />);
197 });
198
199 assertConsoleErrorDev([
200 "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
201 "This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n" +
202 "- A server/client branch `if (typeof window !== 'undefined')`.\n" +
203 "- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
204 "- Date formatting in a user's locale which doesn't match the server.\n" +
205 '- External changing data without sending a snapshot of it along with the HTML.\n' +
206 '- Invalid HTML tag nesting.\n\n' +
207 'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n' +
208 'https://react.dev/link/hydration-mismatch\n\n' +
209 ' <img\n' +
210 '+ src={Blob:image/jpeg}\n' +
211 '- src="data:image/jpeg;base64,ReactURL"\n' +
212 ' ref={{current:null}}\n' +
213 ' >\n' +
214 '\n in img (at **)',
215 ]);
216
217 // The original URL left in place.
218 expect(container.firstChild.src).toBe('data:image/jpeg;base64,ReactURL');
219 });
220 });