4425
);
4426
});
4427
});
4428
+
4429
+ describe('title children', () => {
4430
+ function prepareJSDOMForTitle() {
4431
+ // Test Environment
4432
+ const jsdom = new JSDOM('<!DOCTYPE html><html><head>\u0000', {
4433
+ runScripts: 'dangerously',
4434
+ });
4435
+ window = jsdom.window;
4436
+ document = jsdom.window.document;
4437
+ container = document.getElementsByTagName('head')[0];
4438
+ }
4439
+
4440
+ // @gate experimental
4441
+ it('should accept a single string child', async () => {
4442
+ // a Single string child
4443
+ function App() {
4444
+ return <title>hello</title>;
4445
+ }
4446
+
4447
+ prepareJSDOMForTitle();
4448
+ await act(async () => {
4449
+ const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4450
+ pipe(writable);
4451
+ });
4452
+ expect(getVisibleChildren(container)).toEqual(<title>hello</title>);
4453
+
4454
+ const errors = [];
4455
+ ReactDOMClient.hydrateRoot(container, <App />, {
4456
+ onRecoverableError(error) {
4457
+ errors.push(error.message);
4458
+ },
4459
+ });
4460
+ expect(Scheduler).toFlushAndYield([]);
4461
+ expect(errors).toEqual([]);
4462
+ expect(getVisibleChildren(container)).toEqual(<title>hello</title>);
4463
+ });
4464
+
4465
+ // @gate experimental
4466
+ it('should accept children array of length 1 containing a string', async () => {
4467
+ // a Single string child
4468
+ function App() {
4469
+ return <title>{['hello']}</title>;
4470
+ }
4471
+
4472
+ prepareJSDOMForTitle();
4473
+ await act(async () => {
4474
+ const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4475
+ pipe(writable);
4476
+ });
4477
+ expect(getVisibleChildren(container)).toEqual(<title>hello</title>);
4478
+
4479
+ const errors = [];
4480
+ ReactDOMClient.hydrateRoot(container, <App />, {
4481
+ onRecoverableError(error) {
4482
+ errors.push(error.message);
4483
+ },
4484
+ });
4485
+ expect(Scheduler).toFlushAndYield([]);
4486
+ expect(errors).toEqual([]);
4487
+ expect(getVisibleChildren(container)).toEqual(<title>hello</title>);
4488
+ });
4489
+
4490
+ // @gate experimental
4491
+ it('should warn in dev when given an array of length 2 or more', async () => {
4492
+ const originalConsoleError = console.error;
4493
+ const mockError = jest.fn();
4494
+ console.error = (...args) => {
4495
+ if (args.length > 1) {
4496
+ if (typeof args[1] === 'object') {
4497
+ mockError(args[0].split('\n')[0]);
4498
+ return;
4499
+ }
4500
+ }
4501
+ mockError(...args.map(normalizeCodeLocInfo));
4502
+ };
4503
+
4504
+ // a Single string child
4505
+ function App() {
4506
+ return <title>{['hello1', 'hello2']}</title>;
4507
+ }
4508
+
4509
+ try {
4510
+ prepareJSDOMForTitle();
4511
+
4512
+ await act(async () => {
4513
+ const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4514
+ pipe(writable);
4515
+ });
4516
+ if (__DEV__) {
4517
+ expect(mockError).toHaveBeenCalledWith(
4518
+ 'Warning: A title element received an array with more than 1 element as children. ' +
4519
+ 'In browsers title Elements can only have Text Nodes as children. If ' +
4520
+ 'the children being rendered output more than a single text node in aggregate the browser ' +
4521
+ 'will display markup and comments as text in the title and hydration will likely fail and ' +
4522
+ 'fall back to client rendering%s',
4523
+ '\n' + ' in title (at **)\n' + ' in App (at **)',
4524
+ );
4525
+ } else {
4526
+ expect(mockError).not.toHaveBeenCalled();
4527
+ }
4528
+
4529
+ expect(getVisibleChildren(container)).toEqual(
4530
+ <title>{'hello1<!-- -->hello2'}</title>,
4531
+ );
4532
+
4533
+ const errors = [];
4534
+ ReactDOMClient.hydrateRoot(container, <App />, {
4535
+ onRecoverableError(error) {
4536
+ errors.push(error.message);
4537
+ },
4538
+ });
4539
+ expect(Scheduler).toFlushAndYield([]);
4540
+ expect(errors).toEqual(
4541
+ [
4542
+ gate(flags => flags.enableClientRenderFallbackOnTextMismatch)
4543
+ ? 'Text content does not match server-rendered HTML.'
4544
+ : null,
4545
+ 'Hydration failed because the initial UI does not match what was rendered on the server.',
4546
+ 'There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
4547
+ ].filter(Boolean),
4548
+ );
4549
+ expect(getVisibleChildren(container)).toEqual(
4550
+ <title>{['hello1', 'hello2']}</title>,
4551
+ );
4552
+ } finally {
4553
+ console.error = originalConsoleError;
4554
+ }
4555
+ });
4556
+
4557
+ // @gate experimental
4558
+ it('should warn in dev if you pass a React Component as a child to <title>', async () => {
4559
+ const originalConsoleError = console.error;
4560
+ const mockError = jest.fn();
4561
+ console.error = (...args) => {
4562
+ if (args.length > 1) {
4563
+ if (typeof args[1] === 'object') {
4564
+ mockError(args[0].split('\n')[0]);
4565
+ return;
4566
+ }
4567
+ }
4568
+ mockError(...args.map(normalizeCodeLocInfo));
4569
+ };
4570
+
4571
+ function IndirectTitle() {
4572
+ return 'hello';
4573
+ }
4574
+
4575
+ function App() {
4576
+ return (
4577
+ <title>
4578
+ <IndirectTitle />
4579
+ </title>
4580
+ );
4581
+ }
4582
+
4583
+ try {
4584
+ prepareJSDOMForTitle();
4585
+
4586
+ await act(async () => {
4587
+ const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
4588
+ pipe(writable);
4589
+ });
4590
+ if (__DEV__) {
4591
+ expect(mockError).toHaveBeenCalledWith(
4592
+ 'Warning: A title element received a React element for children. ' +
4593
+ 'In the browser title Elements can only have Text Nodes as children. If ' +
4594
+ 'the children being rendered output more than a single text node in aggregate the browser ' +
4595
+ 'will display markup and comments as text in the title and hydration will likely fail and ' +
4596
+ 'fall back to client rendering%s',
4597
+ '\n' + ' in title (at **)\n' + ' in App (at **)',
4598
+ );
4599
+ } else {
4600
+ expect(mockError).not.toHaveBeenCalled();
4601
+ }
4602
+
4603
+ expect(getVisibleChildren(container)).toEqual(<title>hello</title>);
4604
+
4605
+ const errors = [];
4606
+ ReactDOMClient.hydrateRoot(container, <App />, {
4607
+ onRecoverableError(error) {
4608
+ errors.push(error.message);
4609
+ },
4610
+ });
4611
+ expect(Scheduler).toFlushAndYield([]);
4612
+ expect(errors).toEqual([]);
4613
+ expect(getVisibleChildren(container)).toEqual(<title>hello</title>);
4614
+ } finally {
4615
+ console.error = originalConsoleError;
4616
+ }
4617
+ });
4618
+ });
4619
});