@@ -1162,6 +1162,172 @@ describe('DomTargeter', () => {
11621162 } ) ;
11631163 } ) ;
11641164
1165+ describe ( 'releaseTargets' , ( ) => {
1166+ it ( 'clears tracked elements and allows retargeting' , ( ) => {
1167+ const dom = createDocument ( `
1168+ <div id="content">
1169+ <div class="target-a"></div>
1170+ <div class="target-b"></div>
1171+ </div>
1172+ ` ) ;
1173+
1174+ const document = dom . window . document ;
1175+ const onTarget = jest . fn ( ) ;
1176+
1177+ const targeter = new DomTargeter ( [ { selector : '.target-a' } , { selector : '.target-b' } ] , onTarget , document ) ;
1178+
1179+ expect ( onTarget ) . toHaveBeenCalledTimes ( 2 ) ;
1180+ expect ( targeter . getTargetedElems ( ) . length ) . toBe ( 2 ) ;
1181+
1182+ // retarget without release should not re-trigger
1183+ targeter . retarget ( ) ;
1184+ expect ( onTarget ) . toHaveBeenCalledTimes ( 2 ) ;
1185+
1186+ // release and retarget should re-trigger for the same elements
1187+ targeter . releaseTargets ( ) ;
1188+ expect ( targeter . getTargetedElems ( ) . length ) . toBe ( 0 ) ;
1189+
1190+ targeter . retarget ( ) ;
1191+ expect ( onTarget ) . toHaveBeenCalledTimes ( 4 ) ;
1192+ expect ( targeter . getTargetedElems ( ) . length ) . toBe ( 2 ) ;
1193+ } ) ;
1194+
1195+ it ( 'removes elements from global tracking so other targeters can pick them up' , ( ) => {
1196+ const dom = createDocument ( `
1197+ <div id="content">
1198+ <div class="shared"></div>
1199+ </div>
1200+ ` ) ;
1201+
1202+ const document = dom . window . document ;
1203+ const onTargetA = jest . fn ( ) ;
1204+ const onTargetB = jest . fn ( ) ;
1205+
1206+ // first targeter claims the element
1207+ const targeterA = new DomTargeter ( [ { selector : '.shared' } ] , onTargetA , document ) ;
1208+ expect ( onTargetA ) . toHaveBeenCalledTimes ( 1 ) ;
1209+
1210+ // second targeter cannot target it (globally tracked)
1211+ new DomTargeter ( [ { selector : '.shared' } ] , onTargetB , document ) ;
1212+ expect ( onTargetB ) . toHaveBeenCalledTimes ( 0 ) ;
1213+
1214+ // release from first targeter
1215+ targeterA . releaseTargets ( ) ;
1216+
1217+ // now a new targeter can claim it
1218+ const onTargetC = jest . fn ( ) ;
1219+ new DomTargeter ( [ { selector : '.shared' } ] , onTargetC , document ) ;
1220+ expect ( onTargetC ) . toHaveBeenCalledTimes ( 1 ) ;
1221+ } ) ;
1222+
1223+ it ( 'works with inject targets' , ( ) => {
1224+ const dom = createDocument ( `
1225+ <div id="content">
1226+ <div class="original"></div>
1227+ </div>
1228+ ` ) ;
1229+
1230+ const document = dom . window . document ;
1231+ const onTarget = jest . fn ( ) ;
1232+
1233+ const targeter = new DomTargeter (
1234+ [
1235+ {
1236+ selector : '.original' ,
1237+ inject : {
1238+ action : 'append' ,
1239+ element : ( ) => {
1240+ const el = document . createElement ( 'div' ) ;
1241+ el . className = 'injected' ;
1242+ return el ;
1243+ } ,
1244+ } ,
1245+ } ,
1246+ ] ,
1247+ onTarget ,
1248+ document
1249+ ) ;
1250+
1251+ expect ( onTarget ) . toHaveBeenCalledTimes ( 1 ) ;
1252+ expect ( targeter . getTargetedElems ( ) . length ) . toBe ( 1 ) ;
1253+
1254+ targeter . releaseTargets ( ) ;
1255+ expect ( targeter . getTargetedElems ( ) . length ) . toBe ( 0 ) ;
1256+ } ) ;
1257+
1258+ it ( 'is safe to call when there are no tracked elements' , ( ) => {
1259+ const dom = createDocument ( `<div id="content"></div>` ) ;
1260+ const document = dom . window . document ;
1261+
1262+ const targeter = new DomTargeter ( [ { selector : '.nonexistent' } ] , ( ) => { } , document ) ;
1263+
1264+ expect ( targeter . getTargetedElems ( ) . length ) . toBe ( 0 ) ;
1265+ expect ( ( ) => targeter . releaseTargets ( ) ) . not . toThrow ( ) ;
1266+ expect ( targeter . getTargetedElems ( ) . length ) . toBe ( 0 ) ;
1267+ } ) ;
1268+
1269+ it ( 'releases only specified elements when an array is provided' , ( ) => {
1270+ const dom = createDocument ( `
1271+ <div id="content">
1272+ <div class="target-a"></div>
1273+ <div class="target-b"></div>
1274+ </div>
1275+ ` ) ;
1276+
1277+ const document = dom . window . document ;
1278+ const onTarget = jest . fn ( ) ;
1279+
1280+ const targeter = new DomTargeter ( [ { selector : '.target-a' } , { selector : '.target-b' } ] , onTarget , document ) ;
1281+
1282+ expect ( onTarget ) . toHaveBeenCalledTimes ( 2 ) ;
1283+ expect ( targeter . getTargetedElems ( ) . length ) . toBe ( 2 ) ;
1284+
1285+ const elemA = document . querySelector ( '.target-a' ) ! ;
1286+
1287+ // release only element A
1288+ targeter . releaseTargets ( [ elemA ] ) ;
1289+ expect ( targeter . getTargetedElems ( ) . length ) . toBe ( 1 ) ;
1290+ expect ( targeter . getTargetedElems ( ) [ 0 ] ) . toBe ( document . querySelector ( '.target-b' ) ) ;
1291+
1292+ // retarget should only re-trigger for released element A
1293+ targeter . retarget ( ) ;
1294+ expect ( onTarget ) . toHaveBeenCalledTimes ( 3 ) ;
1295+ expect ( targeter . getTargetedElems ( ) . length ) . toBe ( 2 ) ;
1296+ } ) ;
1297+
1298+ it ( 'releases specified elements from global tracking so other targeters can pick them up' , ( ) => {
1299+ const dom = createDocument ( `
1300+ <div id="content">
1301+ <div class="target-a"></div>
1302+ <div class="target-b"></div>
1303+ </div>
1304+ ` ) ;
1305+
1306+ const document = dom . window . document ;
1307+ const onTargetA = jest . fn ( ) ;
1308+ const onTargetB = jest . fn ( ) ;
1309+
1310+ // first targeter claims both elements
1311+ const targeterA = new DomTargeter ( [ { selector : '.target-a' } , { selector : '.target-b' } ] , onTargetA , document ) ;
1312+ expect ( onTargetA ) . toHaveBeenCalledTimes ( 2 ) ;
1313+
1314+ // second targeter cannot target either (globally tracked)
1315+ new DomTargeter ( [ { selector : '.target-a' } , { selector : '.target-b' } ] , onTargetB , document ) ;
1316+ expect ( onTargetB ) . toHaveBeenCalledTimes ( 0 ) ;
1317+
1318+ const elemA = document . querySelector ( '.target-a' ) ! ;
1319+
1320+ // release only element A from first targeter
1321+ targeterA . releaseTargets ( [ elemA ] ) ;
1322+
1323+ // a new targeter can claim element A but not element B
1324+ const onTargetC = jest . fn ( ) ;
1325+ new DomTargeter ( [ { selector : '.target-a' } , { selector : '.target-b' } ] , onTargetC , document ) ;
1326+ expect ( onTargetC ) . toHaveBeenCalledTimes ( 1 ) ;
1327+ expect ( onTargetC . mock . calls [ 0 ] [ 1 ] ) . toBe ( elemA ) ;
1328+ } ) ;
1329+ } ) ;
1330+
11651331 describe ( 'onTarget targeter parameter' , ( ) => {
11661332 it ( 'passes the DomTargeter instance as the fourth argument to onTarget' , ( ) => {
11671333 const dom = createDocument ( `
0 commit comments