@@ -13,6 +13,8 @@ import {
1313 serialize ,
1414 serializeInner ,
1515} from '@vue/runtime-test'
16+ import * as runtime from '@vue/runtime-test'
17+ import { compile } from '@vue/compiler-dom'
1618
1719function mount (
1820 props : BaseTransitionProps < TestElement > ,
@@ -1356,4 +1358,254 @@ describe('BaseTransition', () => {
13561358 expect ( firstBeforeLeave ) . not . toHaveBeenCalled ( )
13571359 expect ( nextBeforeLeave ) . toHaveBeenCalledTimes ( 1 )
13581360 } )
1361+
1362+ describe . each ( [ true , false ] ) ( 'comment roots (__DEV__: %s)' , dev => {
1363+ const roots = [
1364+ [ 'element' , '<!--start--><div>A</div><!--end-->' , 5 ] ,
1365+ [ 'component' , '<!--start--><Leaf /><!--end-->' , 5 ] ,
1366+ [
1367+ 'nested fragment' ,
1368+ '<!--start--><div v-if="false">If</div><!--between--><div v-else>A</div><!--end-->' ,
1369+ dev ? 8 : 5 ,
1370+ ] ,
1371+ ] as const
1372+
1373+ test . each (
1374+ roots . flatMap ( ( [ name , template , nodes ] ) =>
1375+ ( [ 'out-in' , 'in-out' ] as const ) . flatMap ( mode =>
1376+ [ false , true ] . map ( withKeepAlive => ( {
1377+ name,
1378+ template,
1379+ nodes,
1380+ mode,
1381+ withKeepAlive,
1382+ } ) ) ,
1383+ ) ,
1384+ ) ,
1385+ ) ( '$name, $mode, KeepAlive: $withKeepAlive' , async options => {
1386+ __DEV__ = dev
1387+ try {
1388+ const { template, nodes, mode, withKeepAlive } = options
1389+ const { code } = compile ( template , {
1390+ comments : true ,
1391+ hoistStatic : true ,
1392+ } )
1393+ const One = {
1394+ components : { Leaf : { render : ( ) => h ( 'div' , 'A' ) } } ,
1395+ render : new Function ( 'Vue' , code ) ( runtime ) ,
1396+ }
1397+ const Two = { render : ( ) => h ( 'span' , 'B' ) }
1398+ const toggle = ref ( true )
1399+ const { props, cbs } = mockProps ( { mode } , withKeepAlive )
1400+ const { root } = mount (
1401+ props ,
1402+ ( ) => h ( toggle . value ? One : Two ) ,
1403+ withKeepAlive ,
1404+ )
1405+ expect ( root . children ) . toHaveLength ( nodes )
1406+
1407+ for ( const active of [ false , true ] ) {
1408+ const leaving = active ? '<span>B</span>' : '<div>A</div>'
1409+ const entering = active ? '<div>A</div>' : '<span>B</span>'
1410+ const call = active ? 1 : 0
1411+ toggle . value = active
1412+ await nextTick ( )
1413+
1414+ if ( mode === 'out-in' ) {
1415+ expect ( props . onLeave ) . toHaveBeenCalledTimes ( call + 1 )
1416+ assertCalledWithEl ( props . onLeave , leaving , call )
1417+ expect ( props . onEnter ) . toHaveBeenCalledTimes ( call )
1418+ cbs . doneLeave [ leaving ] ( )
1419+ await nextTick ( )
1420+ expect ( props . onEnter ) . toHaveBeenCalledTimes ( call + 1 )
1421+ assertCalledWithEl ( props . onEnter , entering , call )
1422+ cbs . doneEnter [ entering ] ( )
1423+ } else {
1424+ expect ( props . onEnter ) . toHaveBeenCalledTimes ( call + 1 )
1425+ assertCalledWithEl ( props . onEnter , entering , call )
1426+ expect ( props . onLeave ) . toHaveBeenCalledTimes ( call )
1427+ cbs . doneEnter [ entering ] ( )
1428+ expect ( props . onLeave ) . toHaveBeenCalledTimes ( call + 1 )
1429+ assertCalledWithEl ( props . onLeave , leaving , call )
1430+ cbs . doneLeave [ leaving ] ( )
1431+ await nextTick ( )
1432+ }
1433+
1434+ expect ( props . onAfterLeave ) . toHaveBeenCalledTimes ( call + 1 )
1435+ expect ( props . onAfterEnter ) . toHaveBeenCalledTimes ( call + 1 )
1436+ expect ( root . children ) . toHaveLength ( active ? nodes : 1 )
1437+ expect ( serializeInner ( root ) ) . toBe (
1438+ active
1439+ ? `<!--start-->${ dev && options . name === 'nested fragment' ? '<!--between-->' : '' } <div>A</div><!--end-->`
1440+ : '<span>B</span>' ,
1441+ )
1442+ }
1443+ } finally {
1444+ __DEV__ = true
1445+ }
1446+ } )
1447+ } )
1448+
1449+ test . each ( [ false , true ] ) (
1450+ 'should update leave hooks through comment roots (KeepAlive: %s)' ,
1451+ async withKeepAlive => {
1452+ const root = nodeOps . createElement ( 'div' )
1453+ const toggle = ref ( true )
1454+ const firstLeave = vi . fn ( ( _el , done ) => done ( ) )
1455+ const nextLeave = vi . fn ( ( _el , done ) => done ( ) )
1456+ const onLeave = ref ( firstLeave )
1457+ const { code } = compile ( '<!--start--><div>A</div><!--end-->' )
1458+ const One = { render : new Function ( 'Vue' , code ) ( runtime ) }
1459+ const Two = { render : ( ) => h ( 'span' , 'B' ) }
1460+ const slot = ( ) => h ( toggle . value ? One : Two )
1461+ const App = ( ) =>
1462+ h ( BaseTransition , { onLeave : onLeave . value } , ( ) =>
1463+ withKeepAlive ? h ( KeepAlive , null , slot ( ) ) : slot ( ) ,
1464+ )
1465+ render ( h ( App ) , root )
1466+
1467+ onLeave . value = nextLeave
1468+ toggle . value = false
1469+ await nextTick ( )
1470+
1471+ expect ( firstLeave ) . not . toHaveBeenCalled ( )
1472+ expect ( nextLeave ) . toHaveBeenCalledTimes ( 1 )
1473+ assertCalledWithEl ( nextLeave , '<div>A</div>' )
1474+ expect ( root . children ) . toHaveLength ( 1 )
1475+ expect ( serializeInner ( root ) ) . toBe ( '<span>B</span>' )
1476+ render ( null , root )
1477+ } ,
1478+ )
1479+
1480+ test . each ( [ true , false ] ) (
1481+ 'should skip out-in leave for empty comment roots (__DEV__: %s)' ,
1482+ async dev => {
1483+ __DEV__ = dev
1484+ try {
1485+ const { code } = compile (
1486+ '<!--start--><div v-if="false">A</div><!--end-->' ,
1487+ { comments : true } ,
1488+ )
1489+ const One = { render : new Function ( 'Vue' , code ) ( runtime ) }
1490+ const toggle = ref ( true )
1491+ const { props } = mockProps ( { mode : 'out-in' } )
1492+ const { root } = mount ( props , ( ) =>
1493+ toggle . value ? h ( One ) : h ( 'span' , 'B' ) ,
1494+ )
1495+
1496+ toggle . value = false
1497+ await nextTick ( )
1498+
1499+ expect ( props . onLeave ) . not . toHaveBeenCalled ( )
1500+ expect ( props . onEnter ) . toHaveBeenCalledTimes ( 1 )
1501+ expect ( serializeInner ( root ) ) . toBe ( '<span>B</span>' )
1502+ expect ( root . children ) . toHaveLength ( 1 )
1503+ } finally {
1504+ __DEV__ = true
1505+ }
1506+ } ,
1507+ )
1508+
1509+ test ( 'should not animate a fragment with multiple element roots' , async ( ) => {
1510+ const { code } = compile (
1511+ '<!--start--><template v-if="true"><div>A</div><span>B</span></template><!--end-->' ,
1512+ )
1513+ const One = { render : new Function ( 'Vue' , code ) ( runtime ) }
1514+ const toggle = ref ( true )
1515+ const { props } = mockProps ( )
1516+ const { root } = mount ( props , ( ) => ( toggle . value ? h ( One ) : h ( 'p' , 'C' ) ) )
1517+ expect (
1518+ 'Component inside <Transition> renders non-element root node' ,
1519+ ) . toHaveBeenWarned ( )
1520+
1521+ toggle . value = false
1522+ await nextTick ( )
1523+
1524+ expect ( props . onLeave ) . not . toHaveBeenCalled ( )
1525+ expect ( serializeInner ( root ) ) . toBe ( '<p>C</p>' )
1526+ expect ( root . children ) . toHaveLength ( 1 )
1527+ } )
1528+
1529+ test . each ( [ 'default' , 'in-out' ] as const ) (
1530+ 'should finish pending leave when a comment root re-enters (mode: %s)' ,
1531+ async mode => {
1532+ const { code } = compile ( '<!--start--><div>A</div><!--end-->' , {
1533+ comments : true ,
1534+ hoistStatic : true ,
1535+ } )
1536+ const One = { render : new Function ( 'Vue' , code ) ( runtime ) }
1537+ const Two = { render : ( ) => h ( 'span' , 'B' ) }
1538+ const toggle = ref ( true )
1539+ const { props, cbs } = mockProps ( { mode } )
1540+ const { root } = mount ( props , ( ) =>
1541+ h ( toggle . value ? One : Two , { key : toggle . value ? 'one' : 'two' } ) ,
1542+ )
1543+ const leavingNodes = root . children . slice ( )
1544+
1545+ toggle . value = false
1546+ await nextTick ( )
1547+ cbs . doneEnter [ '<span>B</span>' ] ( )
1548+ expect ( props . onLeave ) . toHaveBeenCalledTimes ( 1 )
1549+ expect ( props . onAfterLeave ) . not . toHaveBeenCalled ( )
1550+ const finishFirstLeave = cbs . doneLeave [ '<div>A</div>' ]
1551+
1552+ toggle . value = true
1553+ await nextTick ( )
1554+ expect ( props . onAfterLeave ) . toHaveBeenCalledTimes ( 1 )
1555+ assertCalledWithEl ( props . onAfterLeave , '<div>A</div>' )
1556+ expect ( props . onLeaveCancelled ) . not . toHaveBeenCalled ( )
1557+ expect ( leavingNodes . every ( node => node . parentNode === null ) ) . toBe ( true )
1558+ expect ( serializeInner ( root ) ) . toBe (
1559+ '<span>B</span><!--start--><div>A</div><!--end-->' ,
1560+ )
1561+ expect ( root . children ) . toHaveLength ( 6 )
1562+
1563+ cbs . doneEnter [ '<div>A</div>' ] ( )
1564+ expect ( props . onLeave ) . toHaveBeenCalledTimes ( 2 )
1565+ cbs . doneLeave [ '<span>B</span>' ] ( )
1566+ finishFirstLeave ( )
1567+ expect ( props . onAfterLeave ) . toHaveBeenCalledTimes ( 2 )
1568+ expect ( serializeInner ( root ) ) . toBe ( '<!--start--><div>A</div><!--end-->' )
1569+ expect ( root . children ) . toHaveLength ( 5 )
1570+ } ,
1571+ )
1572+
1573+ test . each ( [ true , false ] ) (
1574+ 'should enter when a component updates its own root (__DEV__: %s)' ,
1575+ async dev => {
1576+ __DEV__ = dev
1577+ try {
1578+ const showA = ref ( true )
1579+ const { code } = compile (
1580+ '<div v-if="showA">A</div><span v-else>B</span>' ,
1581+ { prefixIdentifiers : true } ,
1582+ )
1583+ const Comp = {
1584+ setup : ( ) => ( { showA } ) ,
1585+ render : new Function ( 'Vue' , code ) ( runtime ) ,
1586+ }
1587+ const { props, cbs } = mockProps ( )
1588+ const { root } = mount ( props , ( ) => h ( Comp ) )
1589+
1590+ showA . value = false
1591+ await nextTick ( )
1592+
1593+ expect ( props . onLeave ) . toHaveBeenCalledTimes ( 1 )
1594+ expect ( props . onBeforeEnter ) . toHaveBeenCalledTimes ( 1 )
1595+ expect ( props . onEnter ) . toHaveBeenCalledTimes ( 1 )
1596+ assertCalledWithEl ( props . onEnter , '<span>B</span>' )
1597+ expect ( props . onAfterLeave ) . toHaveBeenCalledTimes ( 1 )
1598+ assertCalledWithEl ( props . onAfterLeave , '<div>A</div>' )
1599+ expect ( serializeInner ( root ) ) . toBe ( '<span>B</span>' )
1600+
1601+ cbs . doneLeave [ '<div>A</div>' ] ( )
1602+ expect ( props . onAfterLeave ) . toHaveBeenCalledTimes ( 1 )
1603+ expect ( props . onLeaveCancelled ) . not . toHaveBeenCalled ( )
1604+ cbs . doneEnter [ '<span>B</span>' ] ( )
1605+ expect ( props . onAfterEnter ) . toHaveBeenCalledTimes ( 1 )
1606+ } finally {
1607+ __DEV__ = true
1608+ }
1609+ } ,
1610+ )
13591611} )
0 commit comments