aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPascal Mugnier <pascal.mugnier@sonarsource.com>2018-03-29 16:40:55 +0200
committerSonarTech <sonartech@sonarsource.com>2018-04-05 20:20:47 +0200
commitd11fb8ece1a77f028a7ba95004df46ce725a0c00 (patch)
treeb987f741d2bd1a42eea69694f631059971465fb3
parente2e069237203558e6db52f946d6b4564f30652fe (diff)
downloadsonarqube-d11fb8ece1a77f028a7ba95004df46ce725a0c00.tar.gz
sonarqube-d11fb8ece1a77f028a7ba95004df46ce725a0c00.zip
SONAR-10039 Ease changing of password settings
-rw-r--r--server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/settings/SettingsPage.java65
-rw-r--r--server/sonar-web/src/main/js/apps/settings/__tests__/DefinitionActions-test.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/settings/__tests__/__snapshots__/DefinitionActions-test.tsx.snap86
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/InputForPassword.js66
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.js15
-rw-r--r--tests/build.gradle1
-rw-r--r--tests/src/test/java/org/sonarqube/tests/settings/SettingsSuite.java43
-rw-r--r--tests/src/test/java/org/sonarqube/tests/settings/ValidatorsTest.java128
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);
+ }
+
+}