test_setting_control.html (20511B)
1 <!doctype html> 2 <html> 3 <head> 4 <meta charset="utf-8" /> 5 <title>setting-control test</title> 6 <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> 7 <link 8 rel="stylesheet" 9 href="chrome://mochikit/content/tests/SimpleTest/test.css" 10 /> 11 <link rel="stylesheet" href="chrome://global/skin/global.css" /> 12 <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> 13 <script src="../../../../../toolkit/content/tests/widgets/lit-test-helpers.js"></script> 14 <script src="./head.js"></script> 15 <script 16 type="module" 17 src="chrome://browser/content/preferences/widgets/setting-group.mjs" 18 ></script> 19 <script 20 type="module" 21 src="chrome://browser/content/preferences/widgets/setting-control.mjs" 22 ></script> 23 <script 24 type="module" 25 src="chrome://global/content/elements/moz-support-link.mjs" 26 ></script> 27 <script 28 type="application/javascript" 29 src="chrome://global/content/preferencesBindings.js" 30 ></script> 31 <script> 32 /* import-globals-from /toolkit/content/preferencesBindings.js */ 33 let html, testHelpers; 34 35 const LABEL_L10N_ID = "browsing-use-autoscroll"; 36 const GROUP_L10N_ID = "pane-experimental-reset"; 37 38 async function renderTemplate(itemConfig) { 39 let config = { 40 items: [itemConfig], 41 }; 42 let result = await testHelpers.renderTemplate(html` 43 <setting-group 44 .config=${config} 45 .getSetting=${(...args) => Preferences.getSetting(...args)} 46 ></setting-group> 47 `); 48 await result.firstElementChild.updateComplete; 49 return result.querySelector("setting-control"); 50 } 51 52 add_setup(async function setup() { 53 const mockL10nSource = L10nFileSource.createMock( 54 "test", 55 "app", 56 ["en-US"], 57 "/localization/{locale}/", 58 [ 59 { 60 path: "/localization/en-US/mock.ftl", 61 source: "l10n-test-id =\n .label = Test label", 62 }, 63 ] 64 ); 65 L10nRegistry.getInstance().registerSources([mockL10nSource]); 66 MozXULElement.insertFTLIfNeeded("mock.ftl"); 67 SimpleTest.registerCleanupFunction(() => { 68 L10nRegistry.getInstance().removeSources([mockL10nSource.name]); 69 }); 70 testHelpers = new InputTestHelpers(); 71 ({ html } = await testHelpers.setupLit()); 72 testHelpers.setupTests({ 73 templateFn: () => html`<setting-group></setting-group>`, 74 }); 75 MozXULElement.insertFTLIfNeeded("branding/brand.ftl"); 76 MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl"); 77 }); 78 79 add_task(async function testSimpleCheckbox() { 80 const PREF = "test.setting-control.one"; 81 const SETTING = "setting-control-one"; 82 await SpecialPowers.pushPrefEnv({ 83 set: [[PREF, true]], 84 }); 85 Preferences.addAll([{ id: PREF, type: "bool" }]); 86 Preferences.addSetting({ 87 id: SETTING, 88 pref: PREF, 89 }); 90 let itemConfig = { l10nId: LABEL_L10N_ID, id: SETTING }; 91 let setting = Preferences.getSetting(SETTING); 92 let control = await renderTemplate(itemConfig); 93 is( 94 control.controlEl.localName, 95 "moz-checkbox", 96 "The control rendered a checkbox" 97 ); 98 is(control.controlEl.dataset.l10nId, LABEL_L10N_ID, "Label is set"); 99 is(control.controlEl.checked, true, "checkbox is checked"); 100 is(control.controlEl.disabled, false, "checkbox is enabled"); 101 is(Services.prefs.getBoolPref(PREF), true, "pref is true"); 102 103 let settingChanged = waitForSettingChange(setting); 104 synthesizeMouseAtCenter(control.controlEl, {}); 105 await settingChanged; 106 is( 107 control.controlEl.checked, 108 false, 109 "checkbox becomes unchecked after click" 110 ); 111 is(Services.prefs.getBoolPref(PREF), false, "pref is false"); 112 113 settingChanged = waitForSettingChange(setting); 114 Services.prefs.setBoolPref(PREF, true); 115 await settingChanged; 116 is( 117 control.controlEl.checked, 118 true, 119 "checkbox becomes checked after pref change" 120 ); 121 is(Services.prefs.getBoolPref(PREF), true, "pref is true"); 122 123 // Pref locking 124 settingChanged = waitForSettingChange(setting); 125 Services.prefs.lockPref(PREF); 126 await settingChanged; 127 is( 128 control.controlEl.disabled, 129 true, 130 "checkbox is disabled when locked" 131 ); 132 133 settingChanged = waitForSettingChange(setting); 134 Services.prefs.unlockPref(PREF); 135 await settingChanged; 136 is( 137 control.controlEl.disabled, 138 false, 139 "checkbox is enabled when unlocked" 140 ); 141 }); 142 143 add_task(async function testSimpleToggle() { 144 const PREF = "test.setting-control.toggle"; 145 const SETTING = "setting-control-toggle"; 146 await SpecialPowers.pushPrefEnv({ 147 set: [[PREF, true]], 148 }); 149 Preferences.addAll([{ id: PREF, type: "bool" }]); 150 Preferences.addSetting({ 151 id: SETTING, 152 pref: PREF, 153 }); 154 let itemConfig = { 155 l10nId: LABEL_L10N_ID, 156 id: SETTING, 157 control: "moz-toggle", 158 }; 159 let setting = Preferences.getSetting(SETTING); 160 let control = await renderTemplate(itemConfig); 161 162 is( 163 control.controlEl.localName, 164 "moz-toggle", 165 "The control rendered a toggle" 166 ); 167 is(control.controlEl.dataset.l10nId, LABEL_L10N_ID, "Label is set"); 168 is(control.controlEl.pressed, true, "toggle is pressed"); 169 is(control.controlEl.disabled, false, "toggle is enabled"); 170 is(Services.prefs.getBoolPref(PREF), true, "pref is true"); 171 172 let settingChanged = waitForSettingChange(setting); 173 synthesizeMouseAtCenter(control.controlEl, {}); 174 await settingChanged; 175 176 is( 177 control.controlEl.pressed, 178 false, 179 "toggle becomes unchecked after click" 180 ); 181 is(Services.prefs.getBoolPref(PREF), false, "pref is false"); 182 183 settingChanged = waitForSettingChange(setting); 184 Services.prefs.setBoolPref(PREF, true); 185 await settingChanged; 186 187 is( 188 control.controlEl.pressed, 189 true, 190 "toggle becomes pressed after pref change" 191 ); 192 is(Services.prefs.getBoolPref(PREF), true, "pref is true"); 193 }); 194 195 add_task(async function testSettingSameControlValue() { 196 const SETTING = "setting-control-same-value"; 197 Preferences.addSetting({ 198 id: SETTING, 199 get: () => false, 200 set: () => false, 201 }); 202 let itemConfig = { 203 l10nId: LABEL_L10N_ID, 204 id: SETTING, 205 }; 206 let setting = Preferences.getSetting(SETTING); 207 let control = await renderTemplate(itemConfig); 208 ok(control, "Got a control"); 209 let checkbox = control.controlEl; 210 is(checkbox.localName, "moz-checkbox", "moz-checkbox is rendered"); 211 is(checkbox.checked, false, "checkbox is unchecked on initial render"); 212 213 let settingChanged = waitForSettingChange(setting); 214 synthesizeMouseAtCenter(checkbox, {}); 215 setting.emit("change"); 216 await settingChanged; 217 is(checkbox.checked, false, "checkbox stays unchecked after click"); 218 }); 219 220 add_task(async function testSupportLinkCheckbox() { 221 const SETTING = "setting-control-support-link"; 222 Preferences.addSetting({ 223 id: SETTING, 224 get: () => true, 225 }); 226 let itemConfig = { 227 l10nId: LABEL_L10N_ID, 228 id: SETTING, 229 supportPage: "foo", 230 }; 231 let control = await renderTemplate( 232 itemConfig, 233 Preferences.getSetting(SETTING) 234 ); 235 ok(control, "Got a control"); 236 let checkbox = control.controlEl; 237 is(checkbox.localName, "moz-checkbox", "moz-checkbox is rendered"); 238 is( 239 checkbox.supportPage, 240 "foo", 241 "The checkbox receives the supportPage" 242 ); 243 }); 244 245 add_task(async function testCommonControlProperties() { 246 const SETTING = "setting-common-props"; 247 Preferences.addSetting({ 248 id: SETTING, 249 get: () => true, 250 }); 251 252 await testCommonSettingControlProperties(async commonConfig => { 253 const control = await renderTemplate({ 254 id: SETTING, 255 ...commonConfig, 256 }); 257 return control.querySelector("moz-checkbox"); 258 }); 259 }); 260 261 add_task(async function testSupportLinkSubcategory() { 262 const SETTING = "setting-control-subcategory"; 263 Preferences.addSetting({ 264 id: SETTING, 265 get: () => true, 266 }); 267 268 let configOne = { 269 l10nId: LABEL_L10N_ID, 270 id: SETTING, 271 subcategory: "exsubcategory", 272 }; 273 let control = await renderTemplate( 274 configOne, 275 Preferences.getSetting(SETTING) 276 ); 277 ok(control, "Got the control"); 278 is( 279 control.controlEl.dataset.subcategory, 280 "exsubcategory", 281 "Subcategory is set" 282 ); 283 284 let configTwo = { 285 l10nId: LABEL_L10N_ID, 286 id: SETTING, 287 subcategory: "exsubcategory2", 288 supportPage: "foo", 289 }; 290 control = await renderTemplate( 291 configTwo, 292 Preferences.getSetting(SETTING) 293 ); 294 ok(control, "Got the control"); 295 is( 296 control.controlEl.dataset.subcategory, 297 "exsubcategory2", 298 "Subcategory is set" 299 ); 300 301 is(control.controlEl.supportPage, "foo", "Input got the supportPage"); 302 }); 303 304 add_task(async function testNestedCheckboxes() { 305 const PREF_PARENT = "test.setting-control.parent"; 306 const SETTING_PARENT = "setting-control-parent"; 307 const PREF_NESTED = "test.setting-control.nested"; 308 const SETTING_NESTED = "setting-control-nested"; 309 await SpecialPowers.pushPrefEnv({ 310 set: [ 311 [PREF_PARENT, false], 312 [PREF_NESTED, true], 313 ], 314 }); 315 Preferences.addAll([ 316 { id: PREF_PARENT, type: "bool" }, 317 { id: PREF_NESTED, type: "bool" }, 318 ]); 319 Preferences.addSetting({ 320 id: SETTING_PARENT, 321 pref: PREF_PARENT, 322 }); 323 Preferences.addSetting({ 324 id: SETTING_NESTED, 325 pref: PREF_NESTED, 326 }); 327 let itemConfig = { 328 l10nId: LABEL_L10N_ID, 329 id: SETTING_PARENT, 330 items: [{ l10nId: LABEL_L10N_ID, id: SETTING_NESTED }], 331 }; 332 let parentSetting = Preferences.getSetting(SETTING_PARENT); 333 let parentControl = await renderTemplate(itemConfig, parentSetting); 334 is( 335 parentControl.setting.id, 336 SETTING_PARENT, 337 "Parent control id is set" 338 ); 339 let nestedControl = parentControl.controlEl.firstElementChild; 340 info("Nested: " + nestedControl.localName); 341 is( 342 nestedControl.setting.id, 343 SETTING_NESTED, 344 "Nested control id is set" 345 ); 346 is(parentControl.controlEl.checked, false, "Parent is unchecked"); 347 is( 348 parentControl.controlEl.inputEl.disabled, 349 false, 350 "Parent is enabled" 351 ); 352 is(nestedControl.controlEl.checked, true, "Nested is checked"); 353 is( 354 nestedControl.controlEl.inputEl.disabled, 355 true, 356 "Nested is disabled" 357 ); 358 359 let settingChanged = waitForSettingChange(parentSetting); 360 // Click the label since the center of the entire <moz-checkbox> would 361 // be the space between the parent and nested checkboxes. 362 synthesizeMouseAtCenter(parentControl.controlEl.labelEl, {}); 363 await settingChanged; 364 await parentControl.updateComplete; 365 366 is( 367 parentControl.controlEl.checked, 368 true, 369 "Parent is checked after click" 370 ); 371 is( 372 parentControl.controlEl.inputEl.disabled, 373 false, 374 "Parent is enabled after click" 375 ); 376 is( 377 nestedControl.controlEl.checked, 378 true, 379 "Nested is checked after click" 380 ); 381 is( 382 nestedControl.controlEl.inputEl.disabled, 383 false, 384 "Nested is enabled after click" 385 ); 386 387 settingChanged = waitForSettingChange(parentSetting); 388 Services.prefs.setBoolPref(PREF_PARENT, false); 389 await settingChanged; 390 await parentControl.updateComplete; 391 392 is( 393 parentControl.controlEl.checked, 394 false, 395 "Parent is unchecked after pref change" 396 ); 397 is( 398 parentControl.controlEl.inputEl.disabled, 399 false, 400 "Parent is enabled after pref change" 401 ); 402 is( 403 nestedControl.controlEl.checked, 404 true, 405 "Nested is checked after pref change" 406 ); 407 is( 408 nestedControl.controlEl.inputEl.disabled, 409 true, 410 "Nested is disabled after pref change" 411 ); 412 }); 413 414 add_task(async function testDepsChangeVisibility() { 415 const DEP_PREF_ID = "test.depsChange.dep"; 416 const DEP_SETTING_ID = "testDepsChangeDep"; 417 await SpecialPowers.pushPrefEnv({ 418 set: [[DEP_PREF_ID, true]], 419 }); 420 Preferences.add({ id: DEP_PREF_ID, type: "bool" }); 421 Preferences.addSetting({ 422 id: DEP_SETTING_ID, 423 pref: DEP_PREF_ID, 424 }); 425 426 const PARENT_SETTING_ID = "testDepsChangeParent"; 427 Preferences.addSetting({ 428 id: PARENT_SETTING_ID, 429 deps: [DEP_SETTING_ID], 430 visible: deps => deps[DEP_SETTING_ID].value, 431 }); 432 433 let itemConfig = { l10nId: LABEL_L10N_ID, id: PARENT_SETTING_ID }; 434 let setting = Preferences.getSetting(PARENT_SETTING_ID); 435 let control = await renderTemplate(itemConfig, setting); 436 is( 437 control.controlEl.localName, 438 "moz-checkbox", 439 "The control rendered a checkbox" 440 ); 441 ok(!control.hidden, "The control is visible"); 442 ok(setting.visible, "Setting is visible initially"); 443 444 let settingChanged = waitForSettingChange(setting); 445 Services.prefs.setBoolPref(DEP_PREF_ID, false); 446 await settingChanged; 447 448 ok(!setting.visible, "Setting is not visible based on dep"); 449 ok(control.hidden, "The control is now hidden"); 450 }); 451 452 add_task(async function testSettingDisabled() { 453 const SETTING = "setting-control-disabled"; 454 let settingDisabled = false; 455 Preferences.addSetting({ 456 id: SETTING, 457 disabled: () => settingDisabled, 458 }); 459 let itemConfig = { l10nId: LABEL_L10N_ID, id: SETTING }; 460 let setting = Preferences.getSetting(SETTING); 461 let control = await renderTemplate(itemConfig, setting); 462 ok(!control.controlEl.disabled, "Setting is enabled"); 463 464 // Fake that something changed and the setting should be disabled 465 settingDisabled = true; 466 setting.emit("change"); 467 await control.updateComplete; 468 469 ok(control.controlEl.disabled, "Setting is disabled after change"); 470 }); 471 472 add_task(async function testSettingNotDefined() { 473 const SETTING = "setting-control-not-defined"; 474 let itemConfig = { l10nId: LABEL_L10N_ID, id: SETTING }; 475 let setting = Preferences.getSetting(SETTING); 476 try { 477 await renderTemplate(itemConfig, setting); 478 ok(false, "Should throw when setting isn't defined"); 479 } catch (e) { 480 let control = 481 testHelpers.renderTarget.querySelector("setting-control"); 482 ok(control, "setting-control is rendered"); 483 is(control.children.length, 0, "setting-control has no children"); 484 let SettingControl = customElements.get("setting-control"); 485 is( 486 e.constructor, 487 SettingControl.SettingNotDefinedError, 488 "We got an exception" 489 ); 490 } 491 492 Preferences.addSetting({ id: SETTING }); 493 let control = await renderTemplate(itemConfig, setting); 494 is( 495 control.firstElementChild.localName, 496 "moz-checkbox", 497 "Rendered with a setting" 498 ); 499 }); 500 501 add_task(async function testTabIndex() { 502 const PREF = "test.setting-control.tabindex"; 503 const SETTING = "setting-control-tabindex"; 504 await SpecialPowers.pushPrefEnv({ 505 set: [[PREF, true]], 506 }); 507 Preferences.addAll([{ id: PREF, type: "bool" }]); 508 Preferences.addSetting({ 509 id: SETTING, 510 pref: PREF, 511 }); 512 let itemConfig = { l10nId: LABEL_L10N_ID, id: SETTING }; 513 let control = await renderTemplate(itemConfig); 514 515 // Create an element to help with testing keyboard interactions. 516 let focusTrap = document.createElement("button"); 517 focusTrap.textContent = "before"; 518 control.prepend(focusTrap); 519 520 ok( 521 !control.getAttribute("tabindex"), 522 "tabindex is not set on the setting-control initially." 523 ); 524 ok( 525 !control.controlEl.getAttribute("tabindex"), 526 "tabindex is not set on the inner control element initially." 527 ); 528 isnot( 529 document.activeElement, 530 control.controlEl, 531 "The control is not focused initially." 532 ); 533 534 focusTrap.focus(); 535 synthesizeKey("KEY_Tab", {}); 536 537 is( 538 document.activeElement, 539 control.controlEl, 540 "The control element receives focus via keyboard interaction." 541 ); 542 543 // Restore focus to the button. 544 focusTrap.focus(); 545 546 control.setAttribute("tabindex", "-1"); 547 await control.updateComplete; 548 549 is( 550 control.tabIndex, 551 -1, 552 "tabIndex property gets set on the setting-control." 553 ); 554 is( 555 control.controlEl.getAttribute("tabindex"), 556 "-1", 557 "tabindex gets propagated to the control el." 558 ); 559 560 synthesizeKey("KEY_Tab", {}); 561 isnot( 562 document.activeElement, 563 control.controlEl, 564 "The control element no longer receives focus via keyboard interaction." 565 ); 566 }); 567 568 add_task(async function testSettingControlSlot() { 569 const SETTING_PARENT = "setting-control-slot-parent"; 570 const SETTING_CHILD_ACTION = "setting-control-slot-child-action"; 571 572 Preferences.addSetting({ id: SETTING_PARENT }); 573 Preferences.addSetting({ id: SETTING_CHILD_ACTION }); 574 575 const itemConfig = { 576 id: SETTING_PARENT, 577 l10nId: LABEL_L10N_ID, 578 control: "moz-box-item", 579 items: [ 580 { 581 id: SETTING_CHILD_ACTION, 582 l10nId: LABEL_L10N_ID, 583 slot: "actions", 584 }, 585 ], 586 }; 587 588 const parentSetting = Preferences.getSetting(SETTING_PARENT); 589 const parentControl = await renderTemplate(itemConfig, parentSetting); 590 591 is( 592 parentControl.setting.id, 593 SETTING_PARENT, 594 "Parent control id is set" 595 ); 596 ok(parentControl.controlEl, "Parent controlEl exists"); 597 598 const nestedControl = 599 parentControl.controlEl.querySelector("setting-control"); 600 601 is( 602 nestedControl.setting.id, 603 SETTING_CHILD_ACTION, 604 "Nested control id is set" 605 ); 606 ok(nestedControl.controlEl, "Nested controlEl exists"); 607 is( 608 nestedControl.getAttribute("slot"), 609 "actions", 610 "Nested setting-control has expected slot name" 611 ); 612 is( 613 parentControl.controlEl.shadowRoot 614 .querySelector("slot[name='actions']") 615 .assignedElements()[0], 616 nestedControl, 617 "Nested setting-control is slotted in the parent control." 618 ); 619 }); 620 </script> 621 </head> 622 <body> 623 <p id="display"></p> 624 <div id="content" style="display: none"></div> 625 <pre id="test"></pre> 626 </body> 627 </html>