complex-nested-opt-out.html (5579B)
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <title>HTML Test: focusgroup - Complex opt-out scenarios with nested structures</title> 4 <link rel="author" title="Microsoft" href="http://www.microsoft.com/"> 5 <link rel="help" href="https://open-ui.org/components/scoped-focusgroup.explainer/"> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="/resources/testdriver.js"></script> 9 <script src="/resources/testdriver-vendor.js"></script> 10 <script src="/resources/testdriver-actions.js"></script> 11 <script src="../resources/focusgroup-utils.js"></script> 12 13 <div id=root focusgroup="toolbar"> 14 <button id=item1 tabindex=0>Item 1</button> 15 16 <div> 17 <button id=item2 tabindex=0>Item 2</button> 18 19 <div id=optout1 focusgroup="none"> 20 <button id=optout_item1 tabindex=0>Opted out 1</button> 21 22 <div> 23 <button id=optout_item2 tabindex=0>Opted out 2 (nested)</button> 24 25 <!-- Nested focusgroup within opt-out should function independently and not participate in ancestor group --> 26 <div id=nested_in_optout focusgroup="menu"> 27 <button id=nested_optout_item1 tabindex=0>Nested in opt-out 1</button> 28 <button id=nested_optout_item2 tabindex=0>Nested in opt-out 2</button> 29 </div> 30 </div> 31 32 <!-- Another opt-out within opt-out (redundant but valid) --> 33 <div id=optout2 focusgroup="none"> 34 <button id=double_optout tabindex=0>Double opt-out</button> 35 </div> 36 </div> 37 38 <button id=item3 tabindex=0>Item 3</button> 39 </div> 40 41 <div> 42 <div> 43 <button id=item4 tabindex=0>Item 4 (deeply nested)</button> 44 </div> 45 </div> 46 </div> 47 48 <script> 49 promise_test(async t => { 50 const item1 = document.getElementById("item1"); 51 const item2 = document.getElementById("item2"); 52 const item3 = document.getElementById("item3"); 53 const item4 = document.getElementById("item4"); 54 55 await focusAndKeyPress(item1, kArrowRight); 56 assert_equals(document.activeElement, item2, "Should navigate to item2"); 57 58 await focusAndKeyPress(item2, kArrowRight); 59 assert_equals(document.activeElement, item3, "Should skip entire opted-out section (including nested scope) and navigate to item3"); 60 61 await focusAndKeyPress(item3, kArrowRight); 62 assert_equals(document.activeElement, item4, "Should navigate to deeply nested item4"); 63 }, "Outer focusgroup navigation skips opted-out subtree"); 64 65 promise_test(async t => { 66 const optout_item1 = document.getElementById("optout_item1"); 67 const optout_item2 = document.getElementById("optout_item2"); 68 const double_optout = document.getElementById("double_optout"); 69 70 optout_item1.focus(); 71 await focusAndKeyPress(optout_item1, kArrowRight); 72 assert_equals(document.activeElement, optout_item1, "Arrow keys should not move within opted-out generic subtree"); 73 74 optout_item2.focus(); 75 await focusAndKeyPress(optout_item2, kArrowRight); 76 assert_equals(document.activeElement, optout_item2, "Arrow keys should not move within opted-out generic subtree (nested)"); 77 78 double_optout.focus(); 79 await focusAndKeyPress(double_optout, kArrowRight); 80 assert_equals(document.activeElement, double_optout, "Double opt-out still blocks navigation"); 81 }, "Opt-out subtree blocks navigation for its own items"); 82 83 promise_test(async t => { 84 const nested_scope = document.getElementById("nested_in_optout"); 85 const nested_item1 = document.getElementById("nested_optout_item1"); 86 const nested_item2 = document.getElementById("nested_optout_item2"); 87 88 nested_item1.focus(); 89 await focusAndKeyPress(nested_item1, kArrowRight); 90 assert_equals(document.activeElement, nested_item2, "Nested focusgroup should allow forward navigation inside opt-out subtree"); 91 92 await focusAndKeyPress(nested_item2, kArrowLeft); 93 assert_equals(document.activeElement, nested_item1, "Nested focusgroup should allow backward navigation inside opt-out subtree"); 94 95 await focusAndKeyPress(nested_item1, kArrowLeft); 96 assert_equals(document.activeElement, nested_item1, "Backward navigation inside nested scope should not jump to outer items"); 97 }, "Nested focusgroup inside opted-out subtree still works internally"); 98 99 promise_test(async t => { 100 const item2 = document.getElementById("item2"); 101 const item3 = document.getElementById("item3"); 102 const item4 = document.getElementById("item4"); 103 104 item4.focus(); 105 await focusAndKeyPress(item4, kArrowLeft); 106 assert_equals(document.activeElement, item3, "Should navigate backward to item3"); 107 108 await focusAndKeyPress(item3, kArrowLeft); 109 assert_equals(document.activeElement, item2, "Should skip opted-out section backward and navigate to item2"); 110 }, "Backward outer navigation skips opted-out subtree"); 111 112 promise_test(async t => { 113 const root = document.getElementById("root"); 114 const allButtons = Array.from(root.querySelectorAll("button")); 115 const outerGroupButtons = allButtons.filter(btn => { 116 for (let n = btn.parentElement; n; n = n.parentElement) { 117 if (n.hasAttribute("focusgroup") && n.getAttribute("focusgroup") === "none") 118 return false; 119 if (n.id === "nested_in_optout") 120 return false; 121 if (n.id === "root") 122 break; 123 } 124 return true; 125 }); 126 const ids = outerGroupButtons.map(b => b.id).sort(); 127 assert_array_equals(ids, ["item1","item2","item3","item4"], "Outer scope should only include four non-opted-out items"); 128 }, "Outer focusgroup membership excludes opted-out and nested scope items"); 129 </script>