This is an automated email from the ASF dual-hosted git repository. reiern70 pushed a commit to branch reiern70/issue-1618-master-palette-trapfocus-tests in repository https://gitbox.apache.org/repos/asf/wicket.git
commit 79c5857f095a3588279c949b23989c14cb665c75 Author: reiern70 <[email protected]> AuthorDate: Sat Sep 26 09:34:31 2026 -0500 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 ++++++++++++++++++++ wicket-extensions/src/test/js/trapfocus-test.js | 78 +++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 3 deletions(-) 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 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- + 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. +--> +<html> + +<head> + <title id="titleId">Wicket.trapFocus JavaScript tests</title> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" href="/wicket-core/src/test/js/qunit/qunit.css" type="text/css" media="screen" /> +</head> + +<body> + <div id="qunit"></div> + + <div id="qunit-fixture"> + <div id="trapFocusContainer"> + <a id="trapFocusFirst" href="#first">first</a> + <a id="trapFocusLast" href="#last">last</a> + </div> + </div> + + <script> + // version lies between question mark and first ampersand (or end). + // 'vanilla' (trap-focus.html?vanilla) selects the jQuery-free wicket-ajax.js + // engine instead of the default JQuery + wicket-ajax-jquery.js combination + // (trap-focus.html?4.0.0, trap-focus.html?3.7.1, ...). + var version = location.search.match(/\?(.*?)(&|$)/)[1]; + var useVanilla = (version === 'vanilla'); + + if (!useVanilla) { + document.write("<scr"+"ipt src='/wicket-core/src/main/java/org/apache/wicket/resource/jquery/jquery-"+version+".js'></scr"+"ipt>"); + } + </script> + <script> + if (!useVanilla) { + document.write("<scr"+"ipt src='/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax-jquery.js'></scr"+"ipt>"); + } else { + document.write("<scr"+"ipt src='/wicket-core/src/main/java/org/apache/wicket/ajax/res/js/wicket-ajax.js'></scr"+"ipt>"); + } + </script> + <script type="text/javascript" src="/wicket-core/src/test/js/qunit/qunit.js"></script> + + <!-- the module under test --> + <script type="text/javascript" src="/wicket-extensions/src/main/java/org/apache/wicket/extensions/ajax/markup/html/modal/trap-focus.js"></script> + + <script type="text/javascript" src="trapfocus-test.js"></script> +</body> +</html> 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"); + }); +});
