diff options
author | Pascal Mugnier <pascal.mugnier@sonarsource.com> | 2018-03-29 16:40:55 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-04-05 20:20:47 +0200 |
commit | d11fb8ece1a77f028a7ba95004df46ce725a0c00 (patch) | |
tree | b987f741d2bd1a42eea69694f631059971465fb3 | |
parent | e2e069237203558e6db52f946d6b4564f30652fe (diff) | |
download | sonarqube-d11fb8ece1a77f028a7ba95004df46ce725a0c00.tar.gz sonarqube-d11fb8ece1a77f028a7ba95004df46ce725a0c00.zip |
SONAR-10039 Ease changing of password settings
8 files changed, 265 insertions, 145 deletions
diff --git a/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/settings/SettingsPage.java b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/settings/SettingsPage.java index bd03b0ea77d..9bb53d9b50f 100644 --- a/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/settings/SettingsPage.java +++ b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/settings/SettingsPage.java @@ -23,6 +23,9 @@ import com.codeborne.selenide.Condition; import com.codeborne.selenide.Selenide; import com.codeborne.selenide.SelenideElement; import org.openqa.selenium.By; +import org.openqa.selenium.Keys; + +import static com.codeborne.selenide.Selectors.byText; public class SettingsPage { @@ -55,6 +58,12 @@ public class SettingsPage { return this; } + public SettingsPage assertSettingValueIsNotedAsDefault(String settingKey) { + Selenide.$("[data-key=\"" + settingKey + "\"]").find(".spacer-top.note") + .shouldBe(Condition.text("(default)")); + return this; + } + public SettingsPage assertBooleanSettingValue(String settingKey, boolean value) { SelenideElement toggle = Selenide.$("button[name=\"settings[" + settingKey + "]\"]"); if (value) { @@ -65,6 +74,62 @@ public class SettingsPage { return this; } + public SettingsPage assertSettingValueCanBeSaved(String settingKey) { + Selenide.$("[data-key=\"" + settingKey + "\"]").find(byText("Save")) + .should(Condition.exist) + .shouldNotBe(Condition.attribute("disabled")); + return this; + } + + public SettingsPage assertSettingValueCannotBeSaved(String settingKey) { + Selenide.$("[data-key=\"" + settingKey + "\"]").find(byText("Save")) + .should(Condition.exist) + .shouldBe(Condition.attribute("disabled")); + return this; + } + + public SettingsPage assertSettingValueCanBeReset(String settingKey) { + Selenide.$("[data-key=\"" + settingKey + "\"]").find(byText("Reset")) + .should(Condition.exist); + return this; + } + + public SettingsPage assertSettingValueCanBeCanceled(String settingKey) { + Selenide.$("[data-key=\"" + settingKey + "\"]").find(byText("Cancel")) + .should(Condition.exist); + return this; + } + + public SettingsPage assertInputCount(String settingKey, int count) { + Selenide.$("[data-key=\"" + settingKey + "\"]").findAll("input").shouldHaveSize(count); + return this; + } + + public SettingsPage changeSettingValue(String settingKey, String value) { + Selenide.$("[data-key=\"" + settingKey + "\"]").find("input").val(value); + return this; + } + + public SettingsPage changeSettingValue(String settingKey, int index, String value) { + Selenide.$("[data-key=\"" + settingKey + "\"]").findAll("input").get(index).val(value); + return this; + } + + public SettingsPage clickOnCancel(String settingKey) { + Selenide.$("[data-key=\"" + settingKey + "\"]").find(byText("Cancel")).click(); + return this; + } + + public SettingsPage removeFirstValue(String settingKey) { + Selenide.$("[data-key=\"" + settingKey + "\"]").find(".button.js-remove-value.button-icon").click(); + return this; + } + + public SettingsPage sendDeleteKeyToSettingField(String settingKey) { + Selenide.$("[data-key=\"" + settingKey + "\"]").find("input").sendKeys(Keys.BACK_SPACE); + return this; + } + public SettingsPage setStringValue(String settingKey, String value) { SelenideElement setting = Selenide.$(".settings-definition[data-key=\"" + settingKey + "\"]"); setting.find("input").val(value); diff --git a/server/sonar-web/src/main/js/apps/settings/__tests__/DefinitionActions-test.tsx b/server/sonar-web/src/main/js/apps/settings/__tests__/DefinitionActions-test.tsx index d5ab2ec0ba7..f13959d9abf 100644 --- a/server/sonar-web/src/main/js/apps/settings/__tests__/DefinitionActions-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/__tests__/DefinitionActions-test.tsx @@ -69,16 +69,12 @@ it('displays reset button when empty and not default', () => { expect(wrapper).toMatchSnapshot(); }); -it('displays default value label when current value differs', () => { - const wrapper = shallowRender('foobar', true, false); - expect(wrapper).toMatchSnapshot(); -}); - function shallowRender(changedValue: string, hasError: boolean, isDefault: boolean) { return shallow( <DefinitionActions changedValue={changedValue} hasError={hasError} + hasValueChanged={changedValue !== ''} isDefault={isDefault} onCancel={() => {}} onReset={() => {}} diff --git a/server/sonar-web/src/main/js/apps/settings/__tests__/__snapshots__/DefinitionActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/__tests__/__snapshots__/DefinitionActions-test.tsx.snap index 12095239f94..1a8c1227c20 100644 --- a/server/sonar-web/src/main/js/apps/settings/__tests__/__snapshots__/DefinitionActions-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/__tests__/__snapshots__/DefinitionActions-test.tsx.snap @@ -3,16 +3,6 @@ exports[`disables save button on error 1`] = ` <React.Fragment> <div - className="spacer-top note" - style={ - Object { - "lineHeight": "24px", - } - } - > - settings._default - </div> - <div className="settings-definition-changes nowrap" > <Button @@ -35,16 +25,6 @@ exports[`disables save button on error 1`] = ` exports[`displays cancel button when value changed and has error 1`] = ` <React.Fragment> <div - className="spacer-top note" - style={ - Object { - "lineHeight": "24px", - } - } - > - settings._default - </div> - <div className="settings-definition-changes nowrap" > <Button @@ -67,16 +47,6 @@ exports[`displays cancel button when value changed and has error 1`] = ` exports[`displays cancel button when value changed and no error 1`] = ` <React.Fragment> <div - className="spacer-top note" - style={ - Object { - "lineHeight": "24px", - } - } - > - settings._default - </div> - <div className="settings-definition-changes nowrap" > <Button @@ -112,39 +82,10 @@ exports[`displays default message when value is default 1`] = ` className="settings-definition-changes nowrap" > <Button - className="spacer-right button-success" - disabled={false} - onClick={[Function]} - > - save - </Button> - <Button - className="spacer-right button-link" - onClick={[Function]} - > - cancel - </Button> - </div> -</React.Fragment> -`; - -exports[`displays default value label when current value differs 1`] = ` -<React.Fragment> - <div - className="settings-definition-changes nowrap" - > - <Button - className="spacer-right button-success" - disabled={true} - onClick={[Function]} - > - save - </Button> - <Button - className="spacer-right button-link" + className="spacer-right" onClick={[Function]} > - cancel + reset_verb </Button> <span className="note" @@ -163,24 +104,11 @@ exports[`displays reset button when empty and not default 1`] = ` className="settings-definition-changes nowrap" > <Button - className="spacer-right button-success" - disabled={true} - onClick={[Function]} - > - save - </Button> - <Button className="spacer-right" onClick={[Function]} > reset_verb </Button> - <Button - className="spacer-right button-link" - onClick={[Function]} - > - cancel - </Button> <span className="note" > @@ -195,16 +123,6 @@ exports[`displays reset button when empty and not default 1`] = ` exports[`displays save button when it can be saved 1`] = ` <React.Fragment> <div - className="spacer-top note" - style={ - Object { - "lineHeight": "24px", - } - } - > - settings._default - </div> - <div className="settings-definition-changes nowrap" > <Button diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js index cde7870ee16..b2ed1582ada 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js @@ -29,8 +29,15 @@ export default class InputForPassword extends React.PureComponent { changing: false }; + componentWillReceiveProps(nextProps /*: Props*/) { + if (!nextProps.hasValueChanged && this.props.hasValueChanged) { + this.setState({ changing: false, value: '' }); + } + } + handleInputChange(e) { - this.setState({ value: e.target.value }); + this.props.onChange(e.target.value); + this.setState({ changing: true, value: e.target.value }); } handleChangeClick(e) { @@ -39,57 +46,34 @@ export default class InputForPassword extends React.PureComponent { this.setState({ changing: true }); } - handleCancelChangeClick(e) { - e.preventDefault(); - e.target.blur(); - this.setState({ changing: false, value: '' }); - } - - handleFormSubmit(e) { - e.preventDefault(); - this.props.onChange(this.state.value); - this.setState({ changing: false, value: '' }); - } - renderInput() { return ( - <div> - <form onSubmit={e => this.handleFormSubmit(e)}> - <input className="hidden" type="password" /> - <input - value={this.state.value} - name={this.props.name} - className="js-password-input settings-large-input text-top" - type="password" - autoFocus={true} - autoComplete={false} - onChange={e => this.handleInputChange(e)} - /> - - <button className="spacer-left button-success">{translate('save')}</button> - - <a className="spacer-left" href="#" onClick={e => this.handleCancelChangeClick(e)}> - {translate('cancel')} - </a> - </form> - </div> + <form> + <input className="hidden" type="password" /> + <input + autoComplete="off" + autoFocus={this.state.changing} + className="js-password-input settings-large-input text-top" + name={this.props.name} + onChange={e => this.handleInputChange(e)} + type="password" + value={this.state.value} + /> + </form> ); } render() { - if (this.state.changing) { + const hasValue = !!this.props.value; + + if (this.state.changing || !hasValue) { return this.renderInput(); } - const hasValue = !!this.props.value; - return ( <div> - {hasValue && <i className="big-spacer-right icon-lock icon-gray" />} - - <button onClick={e => this.handleChangeClick(e)}> - {hasValue ? translate('change_verb') : translate('set')} - </button> + <i className="big-spacer-right icon-lock icon-gray" /> + <button onClick={e => this.handleChangeClick(e)}>{translate('change_verb')}</button> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js index 0506676493b..b30d3db8cc9 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js @@ -43,21 +43,6 @@ it('should open form', () => { expect(input.find('form').length).toBe(1); }); -it('should close form', () => { - const onChange = jest.fn(); - const input = shallow( - <InputForPassword name="foo" value="bar" isDefault={false} onChange={onChange} /> - ); - const button = input.find('button'); - expect(button.length).toBe(1); - - click(button); - expect(input.find('form').length).toBe(1); - - click(input.find('form').find('a')); - expect(input.find('form').length).toBe(0); -}); - it('should set value', () => { const onChange = jest.fn(() => Promise.resolve()); const input = shallow( diff --git a/tests/build.gradle b/tests/build.gradle index ae2b73cf2cc..666130b7e6f 100644 --- a/tests/build.gradle +++ b/tests/build.gradle @@ -102,6 +102,7 @@ task integrationTest(type: Test) { includeTestsMatching 'org.sonarqube.tests.authorization.*Suite' includeTestsMatching 'org.sonarqube.tests.measure.*Suite' includeTestsMatching 'org.sonarqube.tests.qualityGate.*Suite' + includeTestsMatching 'org.sonarqube.tests.settings.*Suite' includeTestsMatching 'org.sonarqube.tests.source.*Suite' break case 'Category2': diff --git a/tests/src/test/java/org/sonarqube/tests/settings/SettingsSuite.java b/tests/src/test/java/org/sonarqube/tests/settings/SettingsSuite.java new file mode 100644 index 00000000000..0b73df2a436 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/settings/SettingsSuite.java @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.settings; + +import com.sonar.orchestrator.Orchestrator; +import org.junit.ClassRule; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import util.ItUtils; + +import static util.ItUtils.xooPlugin; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + ValidatorsTest.class +}) +public class SettingsSuite { + + @ClassRule + public static final Orchestrator ORCHESTRATOR = ItUtils.newOrchestratorBuilder() + + .addPlugin(xooPlugin()) + + .build(); + +} diff --git a/tests/src/test/java/org/sonarqube/tests/settings/ValidatorsTest.java b/tests/src/test/java/org/sonarqube/tests/settings/ValidatorsTest.java new file mode 100644 index 00000000000..99bd3191ce9 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/settings/ValidatorsTest.java @@ -0,0 +1,128 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.settings; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.qa.util.Tester; +import org.sonarqube.qa.util.pageobjects.settings.SettingsPage; +import org.sonarqube.tests.component.ComponentSuite; +import org.sonarqube.ws.Users; + +import static util.ItUtils.projectDir; + +public class ValidatorsTest { + + private Users.CreateWsResponse.User adminUser; + + @ClassRule + public static Orchestrator orchestrator = ComponentSuite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + @Before + public void initUsers() { + adminUser = tester.users().generateAdministrator(u -> u.setLogin("admin-user").setPassword("admin-user")); + tester.users().generate(u -> u.setLogin("random-user").setPassword("random-user")); + } + + @Test + public void main_settings_simple_value() { + SettingsPage page = tester.openBrowser().logIn().submitCredentials(adminUser.getLogin()) + .openSettings(null); + tester.wsClient().users().skipOnboardingTutorial(); + + String elementSelector = "sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay"; + page.assertSettingValueIsNotedAsDefault(elementSelector) + .changeSettingValue(elementSelector, "1") + .assertSettingValueCanBeSaved(elementSelector) + .assertSettingValueCanBeCanceled(elementSelector); + + page.sendDeleteKeyToSettingField(elementSelector) + .assertSettingValueCannotBeSaved(elementSelector) + .assertSettingValueCanBeReset(elementSelector) + .assertSettingValueCanBeCanceled(elementSelector); + } + + @Test + public void main_settings_complex_value() { + SettingsPage page = tester.openBrowser().logIn().submitCredentials(adminUser.getLogin()) + .openSettings(null); + tester.wsClient().users().skipOnboardingTutorial(); + + String elementSelector = "sonar.preview.excludePlugins"; + page.assertSettingValueIsNotedAsDefault(elementSelector) + .removeFirstValue(elementSelector) + .assertSettingValueCanBeSaved(elementSelector) + .assertSettingValueCanBeCanceled(elementSelector); + + page.clickOnCancel(elementSelector).assertSettingValueIsNotedAsDefault(elementSelector); + } + + @Test + public void project_settings_simple_value() { + analyzeSample(); + + SettingsPage page = tester.openBrowser().logIn().submitCredentials(adminUser.getLogin()) + .openSettings("sample"); + tester.wsClient().users().skipOnboardingTutorial(); + + String elementSelector = "sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay"; + page.assertSettingValueIsNotedAsDefault(elementSelector) + .changeSettingValue(elementSelector, "1") + .assertSettingValueCanBeSaved(elementSelector) + .assertSettingValueCanBeCanceled(elementSelector); + + page.sendDeleteKeyToSettingField(elementSelector) + .assertSettingValueCannotBeSaved(elementSelector) + .assertSettingValueCanBeReset(elementSelector) + .assertSettingValueCanBeCanceled(elementSelector); + } + + @Test + public void project_settings_complex_value() { + analyzeSample(); + + SettingsPage page = tester.openBrowser().logIn().submitCredentials(adminUser.getLogin()) + .openSettings("sample"); + tester.wsClient().users().skipOnboardingTutorial(); + + String elementSelector = "sonar.issue.ignore.allfile"; + page.openCategory("Analysis Scope") + .changeSettingValue(elementSelector, "foo") + .changeSettingValue(elementSelector, 1, "bar") + .assertSettingValueCanBeSaved(elementSelector) + .assertSettingValueCanBeCanceled(elementSelector); + + page.clickOnCancel(elementSelector).assertInputCount(elementSelector, 1); + } + + private void analyzeSample() { + SonarScanner scan = SonarScanner.create(projectDir("shared/xoo-sample")) + .setProperty("sonar.cpd.exclusions", "**/*"); + orchestrator.executeBuild(scan); + } + +} |