@@ -1395,6 +1395,114 @@ describe("View Hooks", function () {
13951395 expect ( Object . keys ( view . viewHooks ) ) . toEqual ( [ ] ) ;
13961396 } ) ;
13971397
1398+ test ( "dynamically added phx-hook is mounted" , async ( ) => {
1399+ let mounted = false ;
1400+ let updated = false ;
1401+ const Hooks = < HooksOptions > {
1402+ DynHook : {
1403+ mounted ( ) {
1404+ mounted = true ;
1405+ } ,
1406+ updated ( ) {
1407+ updated = true ;
1408+ } ,
1409+ } ,
1410+ } ;
1411+ liveSocket = new LiveSocket ( "/live" , Socket , { hooks : Hooks } ) ;
1412+ const el = liveViewDOM ( ) ;
1413+
1414+ const view = simulateJoinedView ( el , liveSocket ) ;
1415+
1416+ // initial render: element exists but has no phx-hook
1417+ view . onJoin ( {
1418+ rendered : {
1419+ s : [ '<h2 id="dyn">no hook yet</h2>' ] ,
1420+ fingerprint : 123 ,
1421+ } ,
1422+ liveview_version,
1423+ } ) ;
1424+ expect ( mounted ) . toBe ( false ) ;
1425+ expect ( Object . keys ( view . viewHooks ) ) . toHaveLength ( 0 ) ;
1426+
1427+ // update: element gains phx-hook attribute dynamically
1428+ view . update (
1429+ {
1430+ s : [ '<h2 id="dyn" phx-hook="DynHook">now with hook</h2>' ] ,
1431+ fingerprint : 123 ,
1432+ } ,
1433+ [ ] ,
1434+ ) ;
1435+ expect ( mounted ) . toBe ( true ) ;
1436+ expect ( Object . keys ( view . viewHooks ) ) . toHaveLength ( 1 ) ;
1437+ // mounted, not updated — this is the first time the hook is attached
1438+ expect ( updated ) . toBe ( false ) ;
1439+ } ) ;
1440+
1441+ test ( "changing phx-hook" , async ( ) => {
1442+ let dynMounted = false ;
1443+ let dynDestroyed = false ;
1444+ let otherMounted = false ;
1445+ let otherDestroyed = false ;
1446+ const Hooks = < HooksOptions > {
1447+ DynHook : {
1448+ mounted ( ) {
1449+ dynMounted = true ;
1450+ } ,
1451+ destroyed ( ) {
1452+ dynDestroyed = true ;
1453+ } ,
1454+ } ,
1455+ OtherHook : {
1456+ mounted ( ) {
1457+ otherMounted = true ;
1458+ } ,
1459+ destroyed ( ) {
1460+ otherDestroyed = true ;
1461+ } ,
1462+ } ,
1463+ } ;
1464+ liveSocket = new LiveSocket ( "/live" , Socket , { hooks : Hooks } ) ;
1465+ const el = liveViewDOM ( ) ;
1466+
1467+ const view = simulateJoinedView ( el , liveSocket ) ;
1468+
1469+ // initial render: element exists but has no phx-hook
1470+ view . onJoin ( {
1471+ rendered : {
1472+ s : [ '<h2 id="dyn" phx-hook="DynHook">initial</h2>' ] ,
1473+ fingerprint : 123 ,
1474+ } ,
1475+ liveview_version,
1476+ } ) ;
1477+ expect ( dynMounted ) . toBe ( true ) ;
1478+ expect ( dynDestroyed ) . toBe ( false ) ;
1479+ expect ( otherMounted ) . toBe ( false ) ;
1480+ expect ( Object . keys ( view . viewHooks ) ) . toHaveLength ( 1 ) ;
1481+
1482+ // update: element gains phx-hook attribute dynamically
1483+ view . update (
1484+ {
1485+ s : [ '<h2 id="dyn" phx-hook="OtherHook">now with different hook</h2>' ] ,
1486+ fingerprint : 123 ,
1487+ } ,
1488+ [ ] ,
1489+ ) ;
1490+ expect ( dynDestroyed ) . toBe ( true ) ;
1491+ expect ( otherMounted ) . toBe ( true ) ;
1492+ expect ( Object . keys ( view . viewHooks ) ) . toHaveLength ( 1 ) ;
1493+
1494+ // update: hook removed altogether
1495+ view . update (
1496+ {
1497+ s : [ '<h2 id="dyn">no hook any more</h2>' ] ,
1498+ fingerprint : 123 ,
1499+ } ,
1500+ [ ] ,
1501+ ) ;
1502+ expect ( otherDestroyed ) . toBe ( true ) ;
1503+ expect ( Object . keys ( view . viewHooks ) ) . toHaveLength ( 0 ) ;
1504+ } ) ;
1505+
13981506 test ( "class based hook" , async ( ) => {
13991507 let upcaseWasDestroyed = false ;
14001508 let upcaseBeforeUpdate = false ;
0 commit comments