@@ -90,11 +90,6 @@ describe('useId', () => {
9090 }
9191 }
9292
93- function Text ( { text} ) {
94- Scheduler . unstable_yieldValue ( text ) ;
95- return text ;
96- }
97-
9893 function normalizeTreeIdForTesting ( id ) {
9994 const [ serverClientPrefix , base32 , hookIndex ] = id . split ( ':' ) ;
10095 if ( serverClientPrefix === 'r' ) {
@@ -359,96 +354,159 @@ describe('useId', () => {
359354 ` ) ;
360355 } ) ;
361356
362- test ( 'inserting a sibling before a dehydrated Suspense boundary' , async ( ) => {
357+ test ( 'inserting/deleting siblings outside a dehydrated Suspense boundary' , async ( ) => {
363358 const span = React . createRef ( null ) ;
364- function App ( { showMore} ) {
365- // Note: Using a dynamic array so this is treated as an insertion instead
366- // of an update, because Fiber currently allocates a node even for
367- // empty children.
368- const children = [ < Text key = "A" text = "A" /> ] ;
369- if ( showMore ) {
370- // These are client-only nodes. They aren't not included in the initial
371- // server render.
372- children . push ( < Text key = "B" text = "B" /> , < DivWithId key = "C" /> ) ;
373- }
374- children . push (
359+ function App ( { swap} ) {
360+ // Note: Using a dynamic array so these are treated as insertions and
361+ // deletions instead of updates, because Fiber currently allocates a node
362+ // even for empty children.
363+ const children = [
364+ < DivWithId key = "A" /> ,
365+ swap ? < DivWithId key = "C" /> : < DivWithId key = "B" /> ,
366+ < DivWithId key = "D" /> ,
367+ ] ;
368+ return (
369+ < >
370+ { children }
371+ < Suspense key = "boundary" fallback = "Loading..." >
372+ < DivWithId />
373+ < span ref = { span } />
374+ </ Suspense >
375+ </ >
376+ ) ;
377+ }
378+
379+ await serverAct ( async ( ) => {
380+ const { pipe} = ReactDOMFizzServer . renderToPipeableStream ( < App /> ) ;
381+ pipe ( writable ) ;
382+ } ) ;
383+ const dehydratedSpan = container . getElementsByTagName ( 'span' ) [ 0 ] ;
384+ await clientAct ( async ( ) => {
385+ const root = ReactDOM . hydrateRoot ( container , < App /> ) ;
386+ expect ( Scheduler ) . toFlushUntilNextPaint ( [ ] ) ;
387+ expect ( container ) . toMatchInlineSnapshot ( `
388+ <div
389+ id="container"
390+ >
391+ <div
392+ id="101"
393+ />
394+ <div
395+ id="1001"
396+ />
397+ <div
398+ id="1101"
399+ />
400+ <!--$-->
401+ <div
402+ id="110"
403+ />
404+ <span />
405+ <!--/$-->
406+ </div>
407+ ` ) ;
408+
409+ // The inner boundary hasn't hydrated yet
410+ expect ( span . current ) . toBe ( null ) ;
411+
412+ // Swap B for C
413+ root . render ( < App swap = { true } /> ) ;
414+ } ) ;
415+ // The swap should not have caused a mismatch.
416+ expect ( container ) . toMatchInlineSnapshot ( `
417+ <div
418+ id="container"
419+ >
420+ <div
421+ id="101"
422+ />
423+ <div
424+ id="CLIENT_GENERATED_ID"
425+ />
426+ <div
427+ id="1101"
428+ />
429+ <!--$-->
430+ <div
431+ id="110"
432+ />
433+ <span />
434+ <!--/$-->
435+ </div>
436+ ` ) ;
437+ // Should have hydrated successfully
438+ expect ( span . current ) . toBe ( dehydratedSpan ) ;
439+ } ) ;
440+
441+ test ( 'inserting/deleting siblings inside a dehydrated Suspense boundary' , async ( ) => {
442+ const span = React . createRef ( null ) ;
443+ function App ( { swap} ) {
444+ // Note: Using a dynamic array so these are treated as insertions and
445+ // deletions instead of updates, because Fiber currently allocates a node
446+ // even for empty children.
447+ const children = [
448+ < DivWithId key = "A" /> ,
449+ swap ? < DivWithId key = "C" /> : < DivWithId key = "B" /> ,
450+ < DivWithId key = "D" /> ,
451+ ] ;
452+ return (
375453 < Suspense key = "boundary" fallback = "Loading..." >
376- < DivWithId />
377- < DivWithId />
454+ { children }
378455 < span ref = { span } />
379- </ Suspense > ,
380- < DivWithId key = "after" /> ,
456+ </ Suspense >
381457 ) ;
382-
383- return children ;
384458 }
385459
386460 await serverAct ( async ( ) => {
387461 const { pipe} = ReactDOMFizzServer . renderToPipeableStream ( < App /> ) ;
388462 pipe ( writable ) ;
389463 } ) ;
390- expect ( Scheduler ) . toHaveYielded ( [ 'A' ] ) ;
391464 const dehydratedSpan = container . getElementsByTagName ( 'span' ) [ 0 ] ;
392465 await clientAct ( async ( ) => {
393466 const root = ReactDOM . hydrateRoot ( container , < App /> ) ;
394- expect ( Scheduler ) . toFlushUntilNextPaint ( [ 'A' ] ) ;
467+ expect ( Scheduler ) . toFlushUntilNextPaint ( [ ] ) ;
395468 expect ( container ) . toMatchInlineSnapshot ( `
396469 <div
397470 id="container"
398471 >
399- A
400- <!-- -->
401472 <!--$-->
402473 <div
403- id="110 "
474+ id="101 "
404475 />
405476 <div
406- id="1010 "
477+ id="1001 "
407478 />
408- <span />
409- <!--/$-->
410479 <div
411- id="11 "
480+ id="1101 "
412481 />
482+ <span />
483+ <!--/$-->
413484 </div>
414485 ` ) ;
415486
416487 // The inner boundary hasn't hydrated yet
417488 expect ( span . current ) . toBe ( null ) ;
418489
419- // Insert another sibling before the Suspense boundary
420- root . render ( < App showMore = { true } /> ) ;
490+ // Swap B for C
491+ root . render ( < App swap = { true } /> ) ;
421492 } ) ;
422- expect ( Scheduler ) . toHaveYielded ( [
423- 'A' ,
424- 'B' ,
425- // The update triggers selective hydration so we render again
426- 'A' ,
427- 'B' ,
428- ] ) ;
429- // The insertions should not cause a mismatch.
493+ // The swap should not have caused a mismatch.
430494 expect ( container ) . toMatchInlineSnapshot ( `
431495 <div
432496 id="container"
433497 >
434- A
435- <!-- -->
436498 <!--$-->
437- B
438499 <div
439- id="CLIENT_GENERATED_ID "
500+ id="101 "
440501 />
441502 <div
442- id="110 "
503+ id="CLIENT_GENERATED_ID "
443504 />
444505 <div
445- id="1010 "
506+ id="1101 "
446507 />
447508 <span />
448509 <!--/$-->
449- <div
450- id="11"
451- />
452510 </div>
453511 ` ) ;
454512 // Should have hydrated successfully
0 commit comments