/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ const PREF_MODIFY_PREFIX = "test.aboutconfig.modify"; const PREF_MODIFY_BOOLEAN = "test.aboutconfig.modify.boolean"; const PREF_MODIFY_NUMBER = "test.aboutconfig.modify.number"; const PREF_MODIFY_STRING = "test.aboutconfig.modify.string"; add_setup(async function () { await SpecialPowers.pushPrefEnv({ set: [ [PREF_MODIFY_BOOLEAN, true], [PREF_MODIFY_NUMBER, 1337], [ PREF_MODIFY_STRING, "the answer to the life the universe and everything", ], ], }); registerCleanupFunction(() => { Services.prefs.clearUserPref(PREF_BOOLEAN_DEFAULT_TRUE); Services.prefs.clearUserPref(PREF_NUMBER_DEFAULT_ZERO); Services.prefs.clearUserPref(PREF_STRING_DEFAULT_EMPTY); }); }); add_task(async function test_add_user_pref() { Assert.equal( Services.prefs.getPrefType(PREF_NEW), Ci.nsIPrefBranch.PREF_INVALID ); await AboutConfigTest.withNewTab(async function () { // The row for a new preference appears when searching for its name. Assert.ok(!this.getRow(PREF_NEW)); for (let [radioIndex, expectedValue, expectedEditingMode] of [ [0, true, false], [1, 0, true], [2, "", true], ]) { this.search(PREF_NEW); let row = this.getRow(PREF_NEW); Assert.ok(row.hasClass("deleted")); Assert.ok(row.hasClass("add")); // Adding the preference should set the default for the data type. row.element.querySelectorAll("input")[radioIndex].click(); row.editColumnButton.click(); Assert.ok(!row.hasClass("deleted")); Assert.ok(!row.hasClass("add")); Assert.ok(Preferences.get(PREF_NEW) === expectedValue); // Number and String preferences should be in edit mode. Assert.equal(!!row.valueInput, expectedEditingMode); // Repeat the search to verify that the preference remains. this.search(PREF_NEW); row = this.getRow(PREF_NEW); Assert.ok(!row.hasClass("deleted")); Assert.ok(!row.hasClass("add")); Assert.ok(!row.valueInput); // Reset the preference, then continue by adding a different type. row.resetColumnButton.click(); Assert.equal( Services.prefs.getPrefType(PREF_NEW), Ci.nsIPrefBranch.PREF_INVALID ); } }); }); add_task(async function test_delete_user_pref() { for (let [radioIndex, testValue] of [ [0, false], [1, -1], [2, "value"], ]) { Preferences.set(PREF_NEW, testValue); await AboutConfigTest.withNewTab(async function () { // Deleting the preference should keep the row. let row = this.getRow(PREF_NEW); row.resetColumnButton.click(); Assert.ok(row.hasClass("deleted")); Assert.equal( Services.prefs.getPrefType(PREF_NEW), Ci.nsIPrefBranch.PREF_INVALID ); // Re-adding the preference should keep the same value. Assert.ok(row.element.querySelectorAll("input")[radioIndex].checked); row.editColumnButton.click(); Assert.ok(!row.hasClass("deleted")); Assert.ok(Preferences.get(PREF_NEW) === testValue); // Filtering again after deleting should remove the row. row.resetColumnButton.click(); this.showAll(); Assert.ok(!this.getRow(PREF_NEW)); }); } }); add_task(async function test_click_type_label_multiple_forms() { // This test displays the row to add a preference while other preferences are // also displayed, and tries to select the type of the new preference by // clicking the label next to the radio button. This should work even if the // user has deleted a different preference, and multiple forms are displayed. const PREF_TO_DELETE = "test.aboutconfig.modify.boolean"; const PREF_NEW_WHILE_DELETED = "test.aboutconfig.modify."; await AboutConfigTest.withNewTab(async function () { this.search(PREF_NEW_WHILE_DELETED); // This preference will remain deleted during the test. let existingRow = this.getRow(PREF_TO_DELETE); existingRow.resetColumnButton.click(); let newRow = this.getRow(PREF_NEW_WHILE_DELETED); for (let [radioIndex, expectedValue] of [ [0, true], [1, 0], [2, ""], ]) { let radioLabels = newRow.element.querySelectorAll("label > span"); await this.document.l10n.translateElements(radioLabels); // Even if this is the second form on the page, the click should select // the radio button next to the label, not the one on the first form. EventUtils.synthesizeMouseAtCenter( radioLabels[radioIndex], {}, this.browser.contentWindow ); // Adding the preference should set the default for the data type. newRow.editColumnButton.click(); Assert.ok(Preferences.get(PREF_NEW_WHILE_DELETED) === expectedValue); // Reset the preference, then continue by adding a different type. newRow.resetColumnButton.click(); } // Re-adding the deleted preference should restore the value. existingRow.editColumnButton.click(); Assert.ok(Preferences.get(PREF_TO_DELETE) === true); }); }); add_task(async function test_reset_user_pref() { await SpecialPowers.pushPrefEnv({ set: [ [PREF_BOOLEAN_DEFAULT_TRUE, false], [PREF_STRING_LOCALIZED_MISSING, "user-value"], ], }); await AboutConfigTest.withNewTab(async function () { // Click reset. let row = this.getRow(PREF_BOOLEAN_DEFAULT_TRUE); row.resetColumnButton.click(); // Check new layout and reset. Assert.ok(!row.hasClass("has-user-value")); Assert.ok(!row.resetColumnButton); Assert.ok(!Services.prefs.prefHasUserValue(PREF_BOOLEAN_DEFAULT_TRUE)); Assert.equal(this.getRow(PREF_BOOLEAN_DEFAULT_TRUE).value, "true"); // Filter again to test the preference cache. this.showAll(); row = this.getRow(PREF_BOOLEAN_DEFAULT_TRUE); Assert.ok(!row.hasClass("has-user-value")); Assert.ok(!row.resetColumnButton); Assert.equal(this.getRow(PREF_BOOLEAN_DEFAULT_TRUE).value, "true"); // Clicking reset on a localized preference without a corresponding value. row = this.getRow(PREF_STRING_LOCALIZED_MISSING); Assert.equal(row.value, "user-value"); row.resetColumnButton.click(); // Check new layout and reset. Assert.ok(!row.hasClass("has-user-value")); Assert.ok(!row.resetColumnButton); Assert.ok(!Services.prefs.prefHasUserValue(PREF_STRING_LOCALIZED_MISSING)); Assert.equal(this.getRow(PREF_STRING_LOCALIZED_MISSING).value, ""); }); }); add_task(async function test_modify() { await AboutConfigTest.withNewTab(async function () { // Test toggle for boolean prefs. for (let nameOfBoolPref of [ PREF_MODIFY_BOOLEAN, PREF_BOOLEAN_DEFAULT_TRUE, ]) { let row = this.getRow(nameOfBoolPref); // Do this a two times to reset the pref. for (let i = 0; i < 2; i++) { row.editColumnButton.click(); // Check new layout and saving in backend. Assert.equal( this.getRow(nameOfBoolPref).value, "" + Preferences.get(nameOfBoolPref) ); let prefHasUserValue = Services.prefs.prefHasUserValue(nameOfBoolPref); Assert.equal(row.hasClass("has-user-value"), prefHasUserValue); Assert.equal(!!row.resetColumnButton, prefHasUserValue); } } // Test abort of edit by starting with string and continuing with editing Int pref. let row = this.getRow(PREF_MODIFY_STRING); row.editColumnButton.click(); row.valueInput.value = "test"; let intRow = this.getRow(PREF_MODIFY_NUMBER); intRow.editColumnButton.click(); Assert.equal(intRow.valueInput.value, Preferences.get(PREF_MODIFY_NUMBER)); Assert.ok(!row.valueInput); Assert.equal(row.value, Preferences.get(PREF_MODIFY_STRING)); // Test validation of integer values. for (let invalidValue of [ "", " ", "a", "1.5", "-2147483649", "2147483648", ]) { intRow.valueInput.value = invalidValue; intRow.editColumnButton.click(); // We should still be in edit mode. Assert.ok(intRow.valueInput); } // Test correct saving and DOM-update. for (let [prefName, willDelete] of [ [PREF_MODIFY_STRING, true], [PREF_MODIFY_NUMBER, true], [PREF_NUMBER_DEFAULT_ZERO, false], [PREF_STRING_DEFAULT_EMPTY, false], ]) { row = this.getRow(prefName); // Activate edit and check displaying. row.editColumnButton.click(); Assert.equal(row.valueInput.value, Preferences.get(prefName)); row.valueInput.value = "42"; // Save and check saving. row.editColumnButton.click(); Assert.equal(Preferences.get(prefName), "42"); Assert.equal(row.value, "42"); Assert.ok(row.hasClass("has-user-value")); // Reset or delete the preference while editing. row.editColumnButton.click(); Assert.equal(row.valueInput.value, Preferences.get(prefName)); row.resetColumnButton.click(); Assert.ok(!row.hasClass("has-user-value")); Assert.equal(row.hasClass("deleted"), willDelete); } }); // This test would have opened the invalid form popup, so just close it so as not to // affect later tests. let invalidFormPopup = window.document.getElementById("invalid-form-popup"); invalidFormPopup.hidePopup(); await BrowserTestUtils.waitForCondition(() => { return invalidFormPopup.state == "closed"; }, "form validation popup closed"); }); add_task(async function test_edit_field_selected() { let prefsToCheck = [ [PREF_MODIFY_STRING, "A string", "A new string"], [PREF_MODIFY_NUMBER, "100", "500"], ]; await AboutConfigTest.withNewTab(async function () { for (let [prefName, startValue, endValue] of prefsToCheck) { Preferences.set(prefName, startValue); let row = this.getRow(prefName); Assert.equal(row.value, startValue); row.editColumnButton.click(); Assert.equal(row.valueInput.value, startValue); EventUtils.sendString(endValue, this.window); row.editColumnButton.click(); Assert.equal(row.value, endValue); Assert.equal(Preferences.get(prefName), endValue); } }); }); add_task(async function test_escape_cancels_edit() { await AboutConfigTest.withNewTab(async function () { let row = this.getRow(PREF_MODIFY_STRING); Preferences.set(PREF_MODIFY_STRING, "Edit me, maybe"); for (let blurInput of [false, true]) { Assert.ok(!row.valueInput); row.editColumnButton.click(); Assert.ok(row.valueInput); Assert.equal(row.valueInput.value, "Edit me, maybe"); row.valueInput.value = "Edited"; // Test both cases of the input being focused and not being focused. if (blurInput) { row.valueInput.blur(); Assert.notEqual(this.document.activeElement, row.valueInput); } else { Assert.equal(this.document.activeElement, row.valueInput); } EventUtils.synthesizeKey("KEY_Escape", {}, this.window); Assert.ok(!row.valueInput); Assert.equal(row.value, "Edit me, maybe"); Assert.equal(row.value, Preferences.get(PREF_MODIFY_STRING)); } }); }); add_task(async function test_double_click_modify() { Preferences.set(PREF_MODIFY_BOOLEAN, true); Preferences.set(PREF_MODIFY_NUMBER, 10); Preferences.set(PREF_MODIFY_STRING, "Hello!"); await AboutConfigTest.withNewTab(async function () { this.search(PREF_MODIFY_PREFIX); let click = (target, opts) => EventUtils.synthesizeMouseAtCenter(target, opts, this.window); let doubleClick = target => { // Trigger two mouse events to simulate the first then second click. click(target, { clickCount: 1 }); click(target, { clickCount: 2 }); }; let tripleClick = target => { // Trigger all 3 mouse events to simulate the three mouse events we'd see. click(target, { clickCount: 1 }); click(target, { clickCount: 2 }); click(target, { clickCount: 3 }); }; // Check double-click to edit a boolean. let boolRow = this.getRow(PREF_MODIFY_BOOLEAN); Assert.equal(boolRow.value, "true"); doubleClick(boolRow.valueCell); Assert.equal(boolRow.value, "false"); doubleClick(boolRow.nameCell); Assert.equal(boolRow.value, "true"); // Check double-click to edit a number. let intRow = this.getRow(PREF_MODIFY_NUMBER); Assert.equal(intRow.value, 10); doubleClick(intRow.valueCell); Assert.equal(this.document.activeElement, intRow.valueInput); EventUtils.sendString("75"); EventUtils.synthesizeKey("KEY_Enter"); Assert.equal(intRow.value, 75); // Check double-click focuses input when already editing. Assert.equal(intRow.value, 75); doubleClick(intRow.nameCell); Assert.equal(this.document.activeElement, intRow.valueInput); intRow.valueInput.blur(); Assert.notEqual(this.document.activeElement, intRow.valueInput); doubleClick(intRow.nameCell); Assert.equal(this.document.activeElement, intRow.valueInput); EventUtils.sendString("20"); EventUtils.synthesizeKey("KEY_Enter"); Assert.equal(intRow.value, 20); // Check double-click to edit a string. let stringRow = this.getRow(PREF_MODIFY_STRING); Assert.equal(stringRow.value, "Hello!"); doubleClick(stringRow.valueCell); Assert.equal( this.document.activeElement, stringRow.valueInput, "The input is focused" ); EventUtils.sendString("New String!"); EventUtils.synthesizeKey("KEY_Enter"); Assert.equal(stringRow.value, "New String!"); // Check triple-click also edits the pref and selects the text inside. tripleClick(stringRow.nameCell); Assert.equal( this.document.activeElement, stringRow.valueInput, "The input is focused" ); // Check double-click inside input selects a word. let newString = "Another string..."; EventUtils.sendString(newString); Assert.equal(this.window.getSelection().toString(), ""); let stringInput = stringRow.valueInput; doubleClick(stringInput); let selectionLength = stringInput.selectionEnd - stringInput.selectionStart; Assert.greater(selectionLength, 0); Assert.less(selectionLength, newString.length); EventUtils.synthesizeKey("KEY_Enter"); Assert.equal(stringRow.value, newString); // Check that double/triple-click on the add row selects text as usual. let addRow = this.getRow(PREF_MODIFY_PREFIX); Assert.ok(addRow.hasClass("deleted")); doubleClick(addRow.nameCell); Assert.ok(PREF_MODIFY_PREFIX.includes(this.window.getSelection())); tripleClick(addRow.nameCell); Assert.equal(this.window.getSelection().toString(), PREF_MODIFY_PREFIX); // Make sure the localized text is set in the value cell. let labels = Array.from(addRow.valueCell.querySelectorAll("label > span")); await this.document.l10n.translateElements(labels); Assert.ok(labels.every(label => !!label.textContent)); // Double-click the first input label text. doubleClick(labels[0]); Assert.equal(this.window.getSelection().toString(), labels[0].textContent); tripleClick(addRow.valueCell.querySelector("label > span")); Assert.equal( this.window.getSelection().toString(), labels.map(l => l.textContent).join("") ); }); });