This is an automated email from the ASF dual-hosted git repository.

reiern70 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit ee62ec8c0149d19a9cb7e32d863be103c25cc0c1
Author: reiern70 <[email protected]>
AuthorDate: Fri Oct 2 11:07:49 2026 -0500

    Show the reviewed veil cases in the examples
    
    The review of the veil behaviors found cases where the veil did not hold
    or disturbed the page it covers, but the examples showed none of them,
    so the fixes could not be seen or tried out.
    
    ajax/veil gets a section for each case an Ajax request can show: one
    behavior on a list handling the clicks of its rows through a child
    selector, with only the clicked row veiled; a panel whose markup id is
    "constructor"; a scrolling box whose veil covers its visible part; an
    absolutely positioned card that stays in place while veiled; and a
    panel under a sticky header, whose veil stays behind the header.
    VeilPageSeleniumTest drives each of them in headless Chrome, with both
    Ajax engines.
    
    websockets/veil pushes a progress update halfway through its long
    rounds, which redraws the veiled panel while the veil stays on it, and
    gets a link that sends a slow Ajax request from the panel while the
    server sends an unveil message no veil message preceded; the panel stays
    veiled until the request is done.
    
    A host replaced by another Ajax request while it is veiled is shown
    through the WebSocket push only: requests to the same page are
    serialized by the page lock, so a second Ajax request cannot re-render
    the host while the first one runs.
    
    See GitHub issue #1631.
---
 .../wicket/examples/ajax/builtin/VeilPage.css      |  60 +++++++++
 .../wicket/examples/ajax/builtin/VeilPage.html     |  67 ++++++++++
 .../wicket/examples/ajax/builtin/VeilPage.java     | 117 +++++++++++++++-
 .../examples/ajax/builtin/VeilPage.properties      |   2 +-
 .../examples/websocket/WebSocketVeilDemoPage.html  |   6 +
 .../examples/websocket/WebSocketVeilDemoPage.java  | 126 +++++++++++++++---
 .../websocket/WebSocketVeilDemoPage.properties     |   2 +-
 .../ajax/builtin/VeilPageSeleniumTest.java         | 147 +++++++++++++++++++++
 8 files changed, 503 insertions(+), 24 deletions(-)

diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.css
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.css
index 54f8963082..09e588d62d 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.css
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.css
@@ -28,3 +28,63 @@
        margin-top: 1em;
        background: #f7f7f7;
 }
+
+.veil-rows {
+       max-width: 40em;
+       padding: 0;
+       list-style: none;
+}
+
+.veil-row {
+       margin: 0.5em 0;
+       padding: 0.5em;
+       border: 1px solid #ccc;
+       cursor: pointer;
+}
+
+.veil-scroller {
+       max-width: 40em;
+       height: 12em;
+       overflow: auto;
+}
+
+.veil-stage {
+       position: relative;
+       max-width: 40em;
+       height: 7em;
+       padding: 0 14em 0 1em;
+       border: 1px dashed #ccc;
+}
+
+.veil-positioned {
+       position: absolute;
+       top: 1em;
+       right: 1em;
+       width: 11em;
+       background: #f7f7f7;
+}
+
+.veil-sticky-scroller {
+       max-width: 40em;
+       height: 12em;
+       overflow: auto;
+       padding-top: 0;
+}
+
+.veil-sticky-header {
+       position: sticky;
+       top: 0;
+       z-index: 100;
+       padding: 0.5em;
+       background: #336;
+       color: #fff;
+}
+
+.veil-under-header {
+       margin-top: 4em;
+       background: #f7f7f7;
+}
+
+.veil-filler {
+       height: 20em;
+}
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.html
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.html
index 0509383d71..d91f53c107 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.html
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.html
@@ -45,4 +45,71 @@
        </div>
 </div>
 
+<h2>Delegated clicks</h2>
+
+<p>
+       One <code>AjaxEventBehavior</code> on the list handles the clicks on 
all of its rows through a
+       child selector, and every row has a local veil of its own. A click on a 
row takes a second and
+       veils only the row that was clicked, not the list or the page.
+</p>
+<ul wicket:id="rows" class="veil-rows">
+       <li wicket:id="row" class="veil-row">
+               Row <span wicket:id="rowNumber"></span>, clicked
+               <span wicket:id="rowCounter" class="row-counter"></span> times. 
Click anywhere on the row.
+       </li>
+</ul>
+
+<h2>A markup id named after an object member</h2>
+
+<div wicket:id="constructor" class="veil-panel">
+       This panel's markup id is <code>constructor</code>, the name of a 
member every JavaScript
+       object has. It is veiled like any other:
+       <a href="#" wicket:id="constructorSlow">Slow request</a>, clicked
+       <span wicket:id="constructorCounter" 
class="constructor-counter"></span> times.
+</div>
+
+<h2>Scrolling host</h2>
+
+<p>
+       This box scrolls and has a local veil. Scroll down and click a request: 
the veil covers the
+       part of the box you see, with the spinner in its middle, and follows 
the box while you scroll
+       it. Requests sent: <span wicket:id="scrollerCounter" 
class="scroller-counter"></span>.
+</p>
+<div wicket:id="scroller" class="veil-panel veil-scroller">
+       <p wicket:id="line">
+               Line <span wicket:id="lineNumber"></span>:
+               <a href="#" wicket:id="lineSlow">slow request</a>
+       </p>
+</div>
+
+<h2>Positioned host</h2>
+
+<div class="veil-stage">
+       <p>
+               The card on the right is absolutely positioned and has a local 
veil. A request from it
+               takes two seconds, and the card stays where it is.
+       </p>
+       <div wicket:id="positioned" class="veil-panel veil-positioned">
+               <a href="#" wicket:id="positionedSlow">Slow request</a>, clicked
+               <span wicket:id="positionedCounter" 
class="positioned-counter"></span> times
+       </div>
+</div>
+
+<h2>Under a sticky header</h2>
+
+<p>
+       The panel in this box has a local veil. Click its request, which takes 
three seconds, then
+       scroll the box so the panel slides under the sticky header: the header 
stays in front of the
+       veil and its spinner.
+</p>
+<div class="veil-panel veil-sticky-scroller">
+       <div class="veil-sticky-header">Sticky header</div>
+       <p>Scroll down to the panel.</p>
+       <div wicket:id="underHeader" class="veil-panel veil-under-header">
+               <a href="#" wicket:id="underHeaderSlow">Slow request</a>, 
clicked
+               <span wicket:id="underHeaderCounter" 
class="under-header-counter"></span> times
+       </div>
+       <p class="veil-filler">Scroll on, so the panel can move under the 
header.</p>
+</div>
+
 </wicket:extend>
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.java
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.java
index 1d1d279d71..7f46c88c82 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.java
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.java
@@ -18,7 +18,9 @@ package org.apache.wicket.examples.ajax.builtin;
 
 import java.time.Duration;
 
+import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.ajax.AjaxEventBehavior;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
 import org.apache.wicket.ajax.markup.html.AjaxLink;
@@ -28,18 +30,25 @@ import org.apache.wicket.markup.head.CssHeaderItem;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.list.Loop;
+import org.apache.wicket.markup.html.list.LoopItem;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.request.resource.CssResourceReference;
 
 /**
  * Demonstrates {@link PageVeilBehavior} and {@link LocalVeilBehavior}, 
including nested local
- * veils with their own timings.
+ * veils with their own timings, and the cases a veil has to hold in: a 
delegated behavior, and
+ * hosts that scroll, are positioned or sit under a sticky header.
  */
 public class VeilPage extends BasePage
 {
        private static final long serialVersionUID = 1L;
 
+       private static final int ROWS = 3;
+
+       private final int[] rowClicks = new int[ROWS];
+
        /**
         * Constructor.
         */
@@ -68,6 +77,112 @@ public class VeilPage extends BasePage
                Label innerCounter = counter(inner, "innerCounter");
                inner.add(new SleepingLink("innerSlow", Duration.ofSeconds(2), 
innerCounter, false));
                inner.add(new SleepingLink("innerShort", 
Duration.ofMillis(200), innerCounter, false));
+
+               addDelegatedRows();
+               addObjectMemberId();
+               addScroller();
+               addPositionedHost();
+               addStickyHeader();
+       }
+
+       private void addDelegatedRows()
+       {
+               WebMarkupContainer rows = new WebMarkupContainer("rows");
+               add(rows);
+               Loop rowLoop = new Loop("row", ROWS)
+               {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       protected void populateItem(LoopItem item)
+                       {
+                               int index = item.getIndex();
+                               item.add(new LocalVeilBehavior());
+                               item.add(AttributeModifier.replace("data-row", 
index));
+                               item.add(new Label("rowNumber", index + 1));
+                               item.add(new Label("rowCounter", () -> 
rowClicks[index]));
+                       }
+               };
+               rows.add(rowLoop);
+
+               rows.add(new AjaxEventBehavior("click")
+               {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       protected void 
updateAjaxAttributes(AjaxRequestAttributes attributes)
+                       {
+                               super.updateAjaxAttributes(attributes);
+                               attributes.setChildSelector("li");
+                               attributes.getDynamicExtraParameters()
+                                       .add("return {row: 
attrs.event.target.closest('li').dataset.row};");
+                       }
+
+                       @Override
+                       protected void onEvent(AjaxRequestTarget target)
+                       {
+                               int index = getRequest().getRequestParameters()
+                                       .getParameterValue("row")
+                                       .toInt(-1);
+                               if (index >= 0 && index < ROWS)
+                               {
+                                       sleep(Duration.ofSeconds(1));
+                                       rowClicks[index]++;
+                                       
target.add(rowLoop.get(Integer.toString(index)));
+                               }
+                       }
+               });
+       }
+
+       private void addObjectMemberId()
+       {
+               WebMarkupContainer constructor = new 
WebMarkupContainer("constructor");
+               constructor.setMarkupId("constructor");
+               constructor.add(new LocalVeilBehavior());
+               add(constructor);
+
+               Label counter = counter(constructor, "constructorCounter");
+               constructor.add(new SleepingLink("constructorSlow", 
Duration.ofSeconds(1), counter, false));
+       }
+
+       private void addScroller()
+       {
+               WebMarkupContainer scroller = new 
WebMarkupContainer("scroller");
+               scroller.add(new LocalVeilBehavior());
+               add(scroller);
+
+               Label counter = counter(this, "scrollerCounter");
+               scroller.add(new Loop("line", 30)
+               {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       protected void populateItem(LoopItem item)
+                       {
+                               item.add(new Label("lineNumber", 
item.getIndex() + 1));
+                               item.add(new SleepingLink("lineSlow", 
Duration.ofSeconds(2), counter, false));
+                       }
+               });
+       }
+
+       private void addPositionedHost()
+       {
+               WebMarkupContainer card = new WebMarkupContainer("positioned");
+               card.add(new LocalVeilBehavior());
+               add(card);
+
+               Label counter = counter(card, "positionedCounter");
+               card.add(new SleepingLink("positionedSlow", 
Duration.ofSeconds(2), counter, false));
+       }
+
+       private void addStickyHeader()
+       {
+               WebMarkupContainer underHeader = new 
WebMarkupContainer("underHeader");
+               underHeader.add(new LocalVeilBehavior());
+               add(underHeader);
+
+               Label counter = counter(underHeader, "underHeaderCounter");
+               underHeader.add(new SleepingLink("underHeaderSlow", 
Duration.ofSeconds(3), counter, false));
        }
 
        @Override
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.properties
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.properties
index 51fd11848e..a7f52f1149 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.properties
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/ajax/builtin/VeilPage.properties
@@ -14,4 +14,4 @@
 #  limitations under the License.
 title=Veil Example
 description=blocks the page, or a part of it, while an Ajax request is running.
-explanation=<p> PageVeilBehavior puts a transparent veil over the page as soon 
as an Ajax request starts, so nothing can be clicked until it is done. A 
spinner shows only if the request takes longer than 300 ms, and then stays for 
at least 500 ms. LocalVeilBehavior does the same for a single component, for 
the requests fired from inside it. Local veils can be nested, the innermost one 
taking the request, and each can have its own timings. A request opts out with 
PageVeilBehavior.noVeil(a [...]
+explanation=<p> PageVeilBehavior puts a transparent veil over the page as soon 
as an Ajax request starts, so nothing can be clicked until it is done. A 
spinner shows only if the request takes longer than 300 ms, and then stays for 
at least 500 ms. LocalVeilBehavior does the same for a single component, for 
the requests fired from inside it. Local veils can be nested, the innermost one 
taking the request, and each can have its own timings. A request opts out with 
PageVeilBehavior.noVeil(a [...]
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/WebSocketVeilDemoPage.html
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/WebSocketVeilDemoPage.html
index 77e6994e59..4d375c41bf 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/WebSocketVeilDemoPage.html
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/WebSocketVeilDemoPage.html
@@ -12,6 +12,12 @@
                        Pushed counter: <span wicket:id="counter" 
class="push-counter"></span>
                </p>
                <p wicket:id="lastWork" class="push-last-work"></p>
+               <p>
+                       <a href="#" wicket:id="strayUnveil">Slow request with a 
stray unveil</a>: an Ajax
+                       request from this panel that takes two seconds. Half a 
second in, the server sends an
+                       unveil message that no veil message preceded. The panel 
stays veiled until the request
+                       is done. Requests sent: <span wicket:id="panelRequests" 
class="panel-requests"></span>
+               </p>
        </div>
 </wicket:extend>
 </body>
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/WebSocketVeilDemoPage.java
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/WebSocketVeilDemoPage.java
index 3c9b001fc0..6273209bb1 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/WebSocketVeilDemoPage.java
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/WebSocketVeilDemoPage.java
@@ -66,6 +66,8 @@ public class WebSocketVeilDemoPage extends WicketExamplePage
 
        private final Label lastWork;
 
+       private int panelRequests;
+
        /**
         * Constructor.
         */
@@ -84,6 +86,20 @@ public class WebSocketVeilDemoPage extends WicketExamplePage
                counterPanel.add(counter);
                lastWork = new Label("lastWork", Model.of("No update pushed 
yet."));
                counterPanel.add(lastWork);
+               counterPanel.add(new Label("panelRequests", () -> 
panelRequests));
+               counterPanel.add(new AjaxLink<Void>("strayUnveil")
+               {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       public void onClick(AjaxRequestTarget target)
+                       {
+                               sendStrayUnveil();
+                               sleep(Duration.ofSeconds(2));
+                               panelRequests++;
+                               target.add(counterPanel);
+                       }
+               });
 
                add(new AjaxLink<Void>("start")
                {
@@ -123,6 +139,49 @@ public class WebSocketVeilDemoPage extends 
WicketExamplePage
                }
        }
 
+       private void sendStrayUnveil()
+       {
+               String applicationName = getApplication().getName();
+               String sessionId = getSession().getId();
+               int pageId = getPageId();
+               String unveilMessage = veil.getUnveilMessage();
+               
JSR356Application.get().getScheduledExecutorService().schedule(() -> {
+                       IWebSocketConnection connection = 
connection(applicationName, sessionId, pageId);
+                       if (connection != null && connection.isOpen())
+                       {
+                               try
+                               {
+                                       connection.sendMessage(unveilMessage);
+                               }
+                               catch (IOException e)
+                               {
+                                       LOGGER.error("Sending the unveil 
message failed", e);
+                               }
+                       }
+               }, 500, TimeUnit.MILLISECONDS);
+       }
+
+       private static IWebSocketConnection connection(String applicationName, 
String sessionId,
+               int pageId)
+       {
+               Application application = Application.get(applicationName);
+               return WebSocketSettings.Holder.get(application)
+                       .getConnectionRegistry()
+                       .getConnection(application, sessionId, new 
PageIdKey(pageId));
+       }
+
+       private static void sleep(Duration duration)
+       {
+               try
+               {
+                       Thread.sleep(duration.toMillis());
+               }
+               catch (InterruptedException e)
+               {
+                       Thread.currentThread().interrupt();
+               }
+       }
+
        private String taskKey()
        {
                return getSession().getId() + "#" + getPageId();
@@ -133,15 +192,23 @@ public class WebSocketVeilDemoPage extends 
WicketExamplePage
        {
                super.onEvent(event);
 
-               if (event.getPayload() instanceof WebSocketPushPayload payload 
&&
-                       payload.getMessage() instanceof CounterUpdate update)
+               if (event.getPayload() instanceof WebSocketPushPayload payload)
                {
-                       counter.setDefaultModelObject(update.value);
-                       lastWork.setDefaultModelObject(String.format(
-                               "Update %d of %d took %d ms on the server.", 
update.value, ROUNDS,
-                               update.work.toMillis()));
-                       payload.getHandler().add(counterPanel);
-                       veil.unveil(payload.getHandler());
+                       if (payload.getMessage() instanceof CounterUpdate 
update)
+                       {
+                               counter.setDefaultModelObject(update.value);
+                               lastWork.setDefaultModelObject(String.format(
+                                       "Update %d of %d took %d ms on the 
server.", update.value, ROUNDS,
+                                       update.work.toMillis()));
+                               payload.getHandler().add(counterPanel);
+                               veil.unveil(payload.getHandler());
+                       }
+                       else if (payload.getMessage() instanceof Progress 
progress)
+                       {
+                               lastWork.setDefaultModelObject(String.format(
+                                       "Working on update %d of %d, halfway 
through.", progress.round, ROUNDS));
+                               payload.getHandler().add(counterPanel);
+                       }
                }
        }
 
@@ -170,10 +237,24 @@ public class WebSocketVeilDemoPage extends 
WicketExamplePage
                }
        }
 
+       /**
+        * Reports halfway through the work, without lifting the veil.
+        */
+       private static class Progress implements IWebSocketPushMessage
+       {
+               private final int round;
+
+               Progress(int round)
+               {
+                       this.round = round;
+               }
+       }
+
        /**
         * Recomputes the counter a number of times, alternating long and short 
work. Both outlast the
-        * spinner delay; the short work ends within the spinner's minimum 
time, so the spinner stays
-        * on the redrawn panel for the rest of it.
+        * spinner delay; the long work reports its progress halfway through, 
which redraws the veiled
+        * panel; the short work ends within the spinner's minimum time, so the 
spinner stays on the
+        * redrawn panel for the rest of it.
         */
        private static class PushTask implements Runnable
        {
@@ -211,15 +292,26 @@ public class WebSocketVeilDemoPage extends 
WicketExamplePage
                        {
                                for (int round = 1; round <= ROUNDS && 
!canceled; round++)
                                {
-                                       IWebSocketConnection connection = 
connection();
+                                       IWebSocketConnection connection = 
connection(applicationName, sessionId,
+                                               pageId);
                                        if (connection == null || 
!connection.isOpen())
                                        {
                                                return;
                                        }
 
                                        connection.sendMessage(veilMessage);
-                                       Duration work = Duration.ofMillis(round 
% 2 == 1 ? 1500 : 600);
-                                       
TimeUnit.MILLISECONDS.sleep(work.toMillis());
+                                       boolean longWork = round % 2 == 1;
+                                       Duration work = 
Duration.ofMillis(longWork ? 3000 : 600);
+                                       if (longWork)
+                                       {
+                                               
TimeUnit.MILLISECONDS.sleep(work.toMillis() / 2);
+                                               connection.sendMessage(new 
Progress(round));
+                                               
TimeUnit.MILLISECONDS.sleep(work.toMillis() / 2);
+                                       }
+                                       else
+                                       {
+                                               
TimeUnit.MILLISECONDS.sleep(work.toMillis());
+                                       }
                                        connection.sendMessage(new 
CounterUpdate(round, work));
 
                                        TimeUnit.SECONDS.sleep(1);
@@ -238,13 +330,5 @@ public class WebSocketVeilDemoPage extends 
WicketExamplePage
                                TASKS.remove(key);
                        }
                }
-
-               private IWebSocketConnection connection()
-               {
-                       Application application = 
Application.get(applicationName);
-                       return WebSocketSettings.Holder.get(application)
-                               .getConnectionRegistry()
-                               .getConnection(application, sessionId, new 
PageIdKey(pageId));
-               }
        }
 }
diff --git 
a/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/WebSocketVeilDemoPage.properties
 
b/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/WebSocketVeilDemoPage.properties
index 9bf32492af..a97b76bb88 100644
--- 
a/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/WebSocketVeilDemoPage.properties
+++ 
b/wicket-examples/src/main/java/org/apache/wicket/examples/websocket/WebSocketVeilDemoPage.properties
@@ -14,4 +14,4 @@
 #  limitations under the License.
 title=Veil a component while the server recomputes it
 description=raises a local veil from the server and lifts it with the pushed 
update.
-explanation=<p> "Start pushes" starts a background task bound to this page. 
Ten times, it tells the browser through the WebSocket connection that the 
counter panel is being recomputed, which raises the panel's LocalVeilBehavior, 
works for a while, and pushes the new counter. The update lifts the veil. Odd 
rounds take 1.5 s: the spinner shows after 300 ms and goes away with the 
update. Even rounds take 600 ms: the spinner shows after 300 ms, and since the 
update arrives within its minimum [...]
+explanation=<p> "Start pushes" starts a background task bound to this page. 
Ten times, it tells the browser through the WebSocket connection that the 
counter panel is being recomputed, which raises the panel's LocalVeilBehavior, 
works for a while, and pushes the new counter. The update lifts the veil. Odd 
rounds take 3 s: the spinner shows after 300 ms, a progress update halfway 
through redraws the panel, which stays veiled, and the spinner goes away with 
the final update. Even rounds ta [...]
diff --git 
a/wicket-examples/src/test/java/org/apache/wicket/examples/ajax/builtin/VeilPageSeleniumTest.java
 
b/wicket-examples/src/test/java/org/apache/wicket/examples/ajax/builtin/VeilPageSeleniumTest.java
index d114cb5b38..277a797d4e 100644
--- 
a/wicket-examples/src/test/java/org/apache/wicket/examples/ajax/builtin/VeilPageSeleniumTest.java
+++ 
b/wicket-examples/src/test/java/org/apache/wicket/examples/ajax/builtin/VeilPageSeleniumTest.java
@@ -57,6 +57,8 @@ class VeilPageSeleniumTest extends JettyTestCaseDecorator
 
        private static final String INNER_VEIL = ".veil-inner > .wicket-veil";
 
+       private static final String ROW_VEIL = ".veil-row > .wicket-veil";
+
        /**
         * Logs when a veil is added, gets its spinner and is removed, with the 
time of each, into
         * {@code window.veilLog}.
@@ -308,6 +310,151 @@ class VeilPageSeleniumTest extends JettyTestCaseDecorator
                        "the spinner was shown for " + shown + " ms instead of 
the configured 1 s");
        }
 
+       @ParameterizedTest
+       @EnumSource(Engine.class)
+       void aDelegatedClickVeilsTheRowThatWasClicked(Engine engine)
+       {
+               open(engine);
+
+               List<WebElement> rows = 
driver.findElements(By.cssSelector(".veil-row"));
+               String rowId = rows.get(1).getDomAttribute("id");
+               rows.get(1).click();
+
+               wait.until(ExpectedConditions.presenceOfElementLocated(
+                       By.cssSelector("#" + rowId + " > .wicket-veil")));
+               assertEquals(1, 
driver.findElements(By.cssSelector(ROW_VEIL)).size(),
+                       "another row was veiled too");
+               
assertTrue(driver.findElements(By.cssSelector(PAGE_VEIL)).isEmpty(),
+                       "the page was veiled");
+
+               wait.until(ExpectedConditions.textToBe(
+                       By.cssSelector("[data-row='1'] .row-counter"), "1"));
+               awaitNoVeil();
+       }
+
+       @ParameterizedTest
+       @EnumSource(Engine.class)
+       void aHostWithTheIdOfAnObjectMemberIsVeiled(Engine engine)
+       {
+               open(engine);
+
+               driver.findElement(By.cssSelector("#constructor a")).click();
+
+               wait.until(ExpectedConditions.presenceOfElementLocated(
+                       By.cssSelector("#constructor > .wicket-veil")));
+               
assertTrue(driver.findElements(By.cssSelector(PAGE_VEIL)).isEmpty(),
+                       "the page was veiled instead");
+               
wait.until(ExpectedConditions.textToBe(By.cssSelector(".constructor-counter"), 
"1"));
+               awaitNoVeil();
+       }
+
+       @ParameterizedTest
+       @EnumSource(Engine.class)
+       void theVeilOfAScrolledHostCoversItsVisiblePart(Engine engine)
+       {
+               open(engine);
+
+               WebElement scroller = 
driver.findElement(By.cssSelector(".veil-scroller"));
+               js().executeScript("arguments[0].scrollIntoView({ block: 
'center' });" +
+                       "arguments[0].scrollTop = arguments[0].scrollHeight;", 
scroller);
+               List<WebElement> links = scroller.findElements(By.tagName("a"));
+               new Actions(driver).moveToElement(links.get(links.size() - 
1)).click().perform();
+               wait.until(ExpectedConditions.presenceOfElementLocated(
+                       By.cssSelector(".veil-scroller > 
.wicket-veil.wicket-veil-busy")));
+
+               assertCoveredByItsVeil(scroller);
+               js().executeScript("arguments[0].scrollTop = 100;", scroller);
+               assertCoveredByItsVeil(scroller);
+
+               
wait.until(ExpectedConditions.textToBe(By.cssSelector(".scroller-counter"), 
"1"));
+               awaitNoVeil();
+       }
+
+       @ParameterizedTest
+       @EnumSource(Engine.class)
+       void aPositionedHostStaysWhereItIs(Engine engine)
+       {
+               open(engine);
+
+               WebElement card = 
driver.findElement(By.cssSelector(".veil-positioned"));
+               Object before = offsetInStage(card);
+               card.findElement(By.tagName("a")).click();
+               wait.until(ExpectedConditions.presenceOfElementLocated(
+                       By.cssSelector(".veil-positioned > .wicket-veil")));
+
+               assertEquals(before, offsetInStage(card), "the host moved while 
it was veiled");
+               assertEquals("absolute", card.getCssValue("position"));
+
+               
wait.until(ExpectedConditions.textToBe(By.cssSelector(".positioned-counter"), 
"1"));
+               awaitNoVeil();
+       }
+
+       @ParameterizedTest
+       @EnumSource(Engine.class)
+       void theVeilStaysBehindAStickyHeaderInFrontOfItsHost(Engine engine)
+       {
+               open(engine);
+
+               WebElement panel = 
driver.findElement(By.cssSelector(".veil-under-header"));
+               js().executeScript("arguments[0].scrollIntoView({ block: 
'center' });", panel);
+               panel.findElement(By.tagName("a")).click();
+               wait.until(ExpectedConditions.presenceOfElementLocated(
+                       By.cssSelector(".veil-under-header > 
.wicket-veil.wicket-veil-busy")));
+
+               Object hit = js().executeAsyncScript("""
+                       var callback = arguments[arguments.length - 1];
+                       var scroller = 
document.querySelector('.veil-sticky-scroller');
+                       var header = 
document.querySelector('.veil-sticky-header');
+                       var panel = 
document.querySelector('.veil-under-header');
+                       scroller.scrollTop += panel.getBoundingClientRect().top 
-
+                               scroller.getBoundingClientRect().top;
+                       requestAnimationFrame(function () {
+                               var rect = header.getBoundingClientRect();
+                               var x = rect.left + rect.width / 2;
+                               var y = rect.top + rect.height / 2;
+                               var under = panel.getBoundingClientRect();
+                               callback({
+                                       panelUnderHeader: under.top <= y && y 
<= under.bottom,
+                                       hit: document.elementFromPoint(x, y) 
=== header
+                               });
+                       });
+                       """);
+               assertEquals(Map.of("panelUnderHeader", true, "hit", true), hit,
+                       "the veil paints over the sticky header");
+
+               
wait.until(ExpectedConditions.textToBe(By.cssSelector(".under-header-counter"), 
"1"));
+               awaitNoVeil();
+       }
+
+       private void assertCoveredByItsVeil(WebElement host)
+       {
+               Object result = js().executeAsyncScript("""
+                       var host = arguments[0];
+                       var callback = arguments[arguments.length - 1];
+                       requestAnimationFrame(function () {
+                               var veil = host.querySelector(':scope > 
.wicket-veil');
+                               var rect = host.getBoundingClientRect();
+                               var points = [[0.1, 0.1], [0.5, 0.5], [0.9, 
0.9]].filter(function (point) {
+                                       var x = rect.left + host.clientWidth * 
point[0];
+                                       var y = rect.top + host.clientHeight * 
point[1];
+                                       return document.elementFromPoint(x, y) 
!== veil;
+                               });
+                               callback(points.length === 0 ? 'covered' :
+                                       'not covered at ' + 
JSON.stringify(points) + ', scrollTop ' + host.scrollTop);
+                       });
+                       """, host);
+               assertEquals("covered", result);
+       }
+
+       private Object offsetInStage(WebElement element)
+       {
+               return js().executeScript("""
+                       var r = arguments[0].getBoundingClientRect();
+                       var stage = 
arguments[0].parentNode.getBoundingClientRect();
+                       return [r.left - stage.left, r.top - stage.top, 
r.width, r.height].join(',');
+                       """, element);
+       }
+
        private WebElement outerPanel()
        {
                return driver.findElement(By.cssSelector(".veil-outer"));

Reply via email to