From 22ebf9253ddb43fc7ce58af228545f45634033d0 Mon Sep 17 00:00:00 2001 From: reiern70 Date: Sat, 26 Sep 2026 09:33:40 -0500 Subject: [PATCH 1/2] Add a regression test for Wicket.Palette's moveUp/moveDown wicket-10.x (10.9.0 through 10.11.0) had a bug where Wicket.Palette.moveUpHelper called box.trigger('focus') on a plain DOM + + + + + + + + + + + + + + + + From 79c5857f095a3588279c949b23989c14cb665c75 Mon Sep 17 00:00:00 2001 From: reiern70 Date: Sat, 26 Sep 2026 09:34:31 -0500 Subject: [PATCH 2/2] Add a regression test for Wicket.trapFocus's Tab-key wrapping wicket-10.x had a bug where the keydown handler in trap-focus.js called .trigger('focus') on plain DOM elements returned by $focusable.get(...), throwing a TypeError instead of wrapping focus around the trap's boundary. See GitHub issue #1620, fixed on wicket-10.x in commit cc29182a0a. master was never affected - it dropped jQuery from this file and calls .focus() directly on native DOM elements - but had no test coverage of its own that would have caught the same class of regression. Adds a QUnit regression test asserting that Tab/Shift+Tab at the trap's boundaries move focus to the other end without throwing, run against both the JQuery-based and the JQuery-free wicket-ajax engine. --- testing/wicket-js-tests/Gruntfile.js | 9 ++- wicket-extensions/src/test/js/trap-focus.html | 62 +++++++++++++++ .../src/test/js/trapfocus-test.js | 78 +++++++++++++++++++ 3 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 wicket-extensions/src/test/js/trap-focus.html create mode 100644 wicket-extensions/src/test/js/trapfocus-test.js diff --git a/testing/wicket-js-tests/Gruntfile.js b/testing/wicket-js-tests/Gruntfile.js index e8a255ffba..1a6893b9da 100644 --- a/testing/wicket-js-tests/Gruntfile.js +++ b/testing/wicket-js-tests/Gruntfile.js @@ -48,7 +48,8 @@ module.exports = function(grunt) { "../../wicket-core/src/test/js/timer.js" ], extensionsTestsJs = [ - "../../wicket-extensions/src/test/js/palette-test.js" + "../../wicket-extensions/src/test/js/palette-test.js", + "../../wicket-extensions/src/test/js/trapfocus-test.js" ], gymTestsJs = [ "../../wicket-examples/src/main/webapp/js-test/tests/ajax/form.js", @@ -113,7 +114,8 @@ module.exports = function(grunt) { options: { urls: [ 'http://localhost:38887/test/js/all.html?4.0.0', - 'http://localhost:38888/wicket-extensions/src/test/js/palette.html?4.0.0' + 'http://localhost:38888/wicket-extensions/src/test/js/palette.html?4.0.0', + 'http://localhost:38888/wicket-extensions/src/test/js/trap-focus.html?4.0.0' ], puppeteer: { headless: true, @@ -130,7 +132,8 @@ module.exports = function(grunt) { options: { urls: [ 'http://localhost:38887/test/js/all.html?vanilla', - 'http://localhost:38888/wicket-extensions/src/test/js/palette.html?vanilla' + 'http://localhost:38888/wicket-extensions/src/test/js/palette.html?vanilla', + 'http://localhost:38888/wicket-extensions/src/test/js/trap-focus.html?vanilla' ], puppeteer: { headless: true, diff --git a/wicket-extensions/src/test/js/trap-focus.html b/wicket-extensions/src/test/js/trap-focus.html new file mode 100644 index 0000000000..6dcbc2d819 --- /dev/null +++ b/wicket-extensions/src/test/js/trap-focus.html @@ -0,0 +1,62 @@ + + + + + + Wicket.trapFocus JavaScript tests + + + + + +
+ +
+
+ first + last +
+
+ + + + + + + + + + + diff --git a/wicket-extensions/src/test/js/trapfocus-test.js b/wicket-extensions/src/test/js/trapfocus-test.js new file mode 100644 index 0000000000..36d45763fa --- /dev/null +++ b/wicket-extensions/src/test/js/trapfocus-test.js @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*global QUnit: true, KeyboardEvent: true */ + +Wicket.Event.add(window, 'domready', function() { + "use strict"; + + const { module, test } = QUnit; + + const TAB_KEYCODE = 9; + + function tab(target, shiftKey) { + target.dispatchEvent(new KeyboardEvent('keydown', { + key: 'Tab', + code: 'Tab', + keyCode: TAB_KEYCODE, + which: TAB_KEYCODE, + shiftKey: !!shiftKey, + bubbles: true, + cancelable: true + })); + } + + module("Wicket.trapFocus"); + + test("Tab on the last focusable element wraps focus to the first, without throwing", assert => { + window.Wicket.trapFocus('trapFocusContainer', 'wicket-trap-focus-test'); + + var first = document.getElementById('trapFocusFirst'); + var last = document.getElementById('trapFocusLast'); + last.focus(); + + var threw = false; + try { + tab(last, false); + } catch (e) { + threw = true; + } + + assert.notOk(threw, "tabbing past the last focusable element threw an exception"); + assert.equal(document.activeElement, first, + "focus did not wrap around to the first focusable element"); + }); + + test("Shift+Tab on the first focusable element wraps focus to the last, without throwing", assert => { + window.Wicket.trapFocus('trapFocusContainer', 'wicket-trap-focus-test'); + + var first = document.getElementById('trapFocusFirst'); + var last = document.getElementById('trapFocusLast'); + first.focus(); + + var threw = false; + try { + tab(first, true); + } catch (e) { + threw = true; + } + + assert.notOk(threw, "shift-tabbing past the first focusable element threw an exception"); + assert.equal(document.activeElement, last, + "focus did not wrap around to the last focusable element"); + }); +});