Browse Source

fix(settings): Migrate away from deprecated `NcPopoverMenu`

* Replace popover menu with `NcActions`
* Deduplicate user actions code between `UserRow` and `UserRowSimple`
* Fix user action to cover whole row heigh to prevent dropdown from shining through the actions
* Fix user action popover to be overlayed by current edited row actions

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Ferdinand Thiessen 11 months ago
parent
commit
97683a5b66

+ 8 - 44
apps/settings/css/settings.scss

@@ -1386,7 +1386,7 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
 			// Scroll if too much groups
 			&:not(.row--editable) {
 				.groups,
-                                .subadmins,
+				.subadmins,
 				.subAdminsGroups {
 					overflow: auto;
 					max-height: 100%;
@@ -1395,7 +1395,7 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
 
 			.managers,
 			.groups,
-                        .subadmins,
+			.subadmins,
 			.subAdminsGroups,
 			.quota {
 				min-width: $grid-col-min-width;
@@ -1569,50 +1569,14 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
 
 				&.userActions {
 					display: flex;
+					align-items: center;
 					justify-content: flex-end;
 
-					#newsubmit {
-						width: 100%;
-					}
-
-					.toggleUserActions {
-						position: relative;
-						display: flex;
-						align-items: center;
-						background-color: var(--color-main-background);
-
-						.icon-more {
-							width: 44px;
-							height: 44px;
-							opacity: .5;
-							cursor: pointer;
-
-							&:focus,
-							&:hover,
-							&:active {
-								opacity: .7;
-								background-color: var(--color-background-dark)
-							}
-						}
-					}
-
-					.feedback {
-						display: flex;
-						align-items: center;
-						white-space: nowrap;
-						transition: opacity 200ms ease-in-out;
-
-						.icon-checkmark {
-							opacity: .5;
-							margin-right: 5px;
-						}
-					}
-				}
-
-				/* Fill the grid cell */
-				.v-select.select-vue {
-					min-width: 100%;
-					width: 100%;
+					// Make sure to cover whole row
+					height: 100%;
+					width: fit-content;
+					padding-inline: 12px;
+					background-color: var(--color-main-background);
 				}
 			}
 		}

+ 30 - 48
apps/settings/src/components/Users/UserRow.vue

@@ -44,7 +44,6 @@
 	<!-- User full data -->
 	<UserRowSimple v-else-if="!editing"
 		:editing.sync="editing"
-		:feedback-message="feedbackMessage"
 		:groups="groups"
 		:languages="languages"
 		:loading="loading"
@@ -222,59 +221,41 @@
 		</div>
 
 		<div class="userActions">
-			<div v-if="!loading.all"
-				class="toggleUserActions">
-				<NcActions>
-					<NcActionButton icon="icon-checkmark"
-						:title="t('settings', 'Done')"
-						:aria-label="t('settings', 'Done')"
-						@click="handleDoneButton" />
-				</NcActions>
-				<div v-click-outside="hideMenu" class="userPopoverMenuWrapper">
-					<button class="icon-more"
-						:aria-expanded="openedMenu"
-						:aria-label="t('settings', 'Toggle user actions menu')"
-						@click.prevent="toggleMenu" />
-					<div :class="{ 'open': openedMenu }" class="popovermenu">
-						<NcPopoverMenu :menu="userActions" />
-					</div>
-				</div>
-			</div>
-			<div :style="{opacity: feedbackMessage !== '' ? 1 : 0}"
-				class="feedback">
-				<div class="icon-checkmark" />
-				{{ feedbackMessage }}
-			</div>
+			<UserRowActions v-if="!loading.all"
+				:actions="userActions"
+				:edit="true"
+				@update:edit="toggleEdit" />
 		</div>
 	</div>
 </template>
 
 <script>
-import ClickOutside from 'vue-click-outside'
+import { showSuccess, showError } from '@nextcloud/dialogs'
 
-import NcPopoverMenu from '@nextcloud/vue/dist/Components/NcPopoverMenu.js'
 import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js'
-import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
-import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
 import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
+import ClickOutside from 'vue-click-outside'
+
+import UserRowActions from './UserRowActions.vue'
 import UserRowSimple from './UserRowSimple.vue'
 import UserRowMixin from '../../mixins/UserRowMixin.js'
-import { showSuccess, showError } from '@nextcloud/dialogs'
 
 export default {
 	name: 'UserRow',
+
 	components: {
-		UserRowSimple,
-		NcPopoverMenu,
-		NcActions,
-		NcActionButton,
 		NcSelect,
 		NcTextField,
+		UserRowActions,
+		UserRowSimple,
 	},
+
 	directives: {
 		ClickOutside,
 	},
+
 	mixins: [UserRowMixin],
+
 	props: {
 		users: {
 			type: Array,
@@ -325,7 +306,6 @@ export default {
 			selectedQuota: false,
 			rand: parseInt(Math.random() * 1000),
 			openedMenu: false,
-			feedbackMessage: '',
 			possibleManagers: [],
 			currentManager: '',
 			editing: false,
@@ -348,8 +328,8 @@ export default {
 			editedMail: this.user.email ?? '',
 		}
 	},
-	computed: {
 
+	computed: {
 		/* USER POPOVERMENU ACTIONS */
 		userActions() {
 			const actions = [
@@ -400,8 +380,10 @@ export default {
 			return this.languages[0].languages.concat(this.languages[1].languages)
 		},
 	},
+
 	async beforeMount() {
 		await this.searchUserManager()
+
 		if (this.user.manager) {
 			await this.initManager(this.user.manager)
 		}
@@ -432,13 +414,14 @@ export default {
 						this.loading.wipe = true
 						this.loading.all = true
 						this.$store.dispatch('wipeUserDevices', userid)
-							.then(() => {
+							.then(() => showSuccess(t('settings', 'Wiped {userid}\'s devices', { userid })), { timeout: 2000 })
+							.finally(() => {
 								this.loading.wipe = false
 								this.loading.all = false
 							})
 					}
 				},
-				true
+				true,
 			)
 		},
 
@@ -500,7 +483,7 @@ export default {
 							})
 					}
 				},
-				true
+				true,
 			)
 		},
 
@@ -778,19 +761,13 @@ export default {
 		sendWelcomeMail() {
 			this.loading.all = true
 			this.$store.dispatch('sendWelcomeMail', this.user.id)
-				.then(success => {
-					if (success) {
-						// Show feedback to indicate the success
-						this.feedbackMessage = t('setting', 'Welcome mail sent!')
-						setTimeout(() => {
-							this.feedbackMessage = ''
-						}, 2000)
-					}
+				.then(() => showSuccess(t('setting', 'Welcome mail sent!'), { timeout: 2000 }))
+				.finally(() => {
 					this.loading.all = false
 				})
 		},
 
-		handleDoneButton() {
+		toggleEdit() {
 			this.editing = false
 			if (this.editedDisplayName !== this.user.displayname) {
 				this.editedDisplayName = this.user.displayname
@@ -807,7 +784,12 @@ export default {
 		z-index: 1 !important;
 	}
 
-  .row :deep() {
+	.row :deep() {
+		.v-select.select {
+			// reset min width to 100% instead of X px
+			min-width: 100%;
+		}
+
 		.mailAddress,
 		.password,
 		.displayName {

+ 78 - 0
apps/settings/src/components/Users/UserRowActions.vue

@@ -0,0 +1,78 @@
+<template>
+	<NcActions :aria-label="t('settings', 'Toggle user actions menu')"
+		:inline="1">
+		<NcActionButton @click="toggleEdit">
+			{{ edit ? t('settings', 'Done') : t('settings', 'Edit') }}
+			<template #icon>
+				<NcIconSvgWrapper :svg="editSvg" aria-hidden="true" />
+			</template>
+		</NcActionButton>
+		<NcActionButton v-for="(action, index) in actions"
+			:key="index"
+			:aria-label="action.text"
+			:icon="action.icon"
+			@click="action.action">
+			{{ action.text }}
+		</NcActionButton>
+	</NcActions>
+</template>
+
+<script lang="ts">
+import { PropType, defineComponent } from 'vue'
+
+import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
+import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
+import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
+import SvgCheck from '@mdi/svg/svg/check.svg?raw'
+import SvgPencil from '@mdi/svg/svg/pencil.svg?raw'
+
+interface UserAction {
+	action: (event: MouseEvent) => void,
+	icon: string,
+	text: string
+}
+
+export default defineComponent({
+	components: {
+		NcActionButton,
+		NcActions,
+		NcIconSvgWrapper,
+	},
+
+	props: {
+		/**
+		 * Array of user actions
+		 */
+		actions: {
+			type: Array as PropType<readonly UserAction[]>,
+			required: true,
+		},
+
+		/**
+		 * The state whether the row is currently edited
+		 */
+		edit: {
+			type: Boolean,
+			required: true,
+		},
+	},
+
+	computed: {
+		/**
+		 * Current MDI logo to show for edit toggle
+		 */
+		editSvg() {
+			return this.edit ? SvgCheck : SvgPencil
+		},
+	},
+
+	methods: {
+		/**
+		 * Toggle edit mode by emitting the update event
+		 */
+		toggleEdit() {
+			this.$emit('update:edit', !this.edit)
+		},
+	},
+})
+</script>

+ 10 - 33
apps/settings/src/components/Users/UserRowSimple.vue

@@ -59,45 +59,26 @@
 			{{ user.manager }}
 		</div>
 		<div class="userActions">
-			<div v-if="canEdit && !loading.all" class="toggleUserActions">
-				<NcActions>
-					<NcActionButton icon="icon-rename"
-						:title="t('settings', 'Edit User')"
-						:aria-label="t('settings', 'Edit User')"
-						@click="toggleEdit" />
-				</NcActions>
-				<div class="userPopoverMenuWrapper">
-					<button v-click-outside="hideMenu"
-						class="icon-more"
-						:aria-expanded="openedMenu"
-						:aria-label="t('settings', 'Toggle user actions menu')"
-						@click.prevent="toggleMenu" />
-					<div class="popovermenu" :class="{ 'open': openedMenu }">
-						<NcPopoverMenu :menu="userActions" />
-					</div>
-				</div>
-			</div>
-			<div class="feedback" :style="{opacity: feedbackMessage !== '' ? 1 : 0}">
-				<div class="icon-checkmark" />
-				{{ feedbackMessage }}
-			</div>
+			<UserRowActions v-if="canEdit && !loading.all"
+				:actions="userActions"
+				:edit="false"
+				@update:edit="toggleEdit" />
 		</div>
 	</div>
 </template>
 
 <script>
-import NcPopoverMenu from '@nextcloud/vue/dist/Components/NcPopoverMenu.js'
-import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
-import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
-import ClickOutside from 'vue-click-outside'
 import { getCurrentUser } from '@nextcloud/auth'
+
+import ClickOutside from 'vue-click-outside'
+
+import UserRowActions from './UserRowActions.vue'
 import UserRowMixin from '../../mixins/UserRowMixin.js'
+
 export default {
 	name: 'UserRowSimple',
 	components: {
-		NcPopoverMenu,
-		NcActionButton,
-		NcActions,
+		UserRowActions,
 	},
 	directives: {
 		ClickOutside,
@@ -124,10 +105,6 @@ export default {
 			type: Boolean,
 			required: true,
 		},
-		feedbackMessage: {
-			type: String,
-			required: true,
-		},
 		subAdminsGroups: {
 			type: Array,
 			required: true,

+ 15 - 38
cypress/e2e/settings/users.cy.ts

@@ -25,9 +25,8 @@ import { User } from '@nextcloud/cypress'
 const admin = new User('admin', 'admin')
 const jdoe = new User('jdoe', 'jdoe')
 
-describe('Setting: Users list', function() {
+describe('Settings: Create and delete users', function() {
 	before(function() {
-		cy.createUser(jdoe)
 		cy.login(admin)
 	})
 
@@ -35,48 +34,26 @@ describe('Setting: Users list', function() {
 		cy.deleteUser(jdoe)
 	})
 
-	it('Can change the password', function() {
+	it('Can delete a user', function() {
+		// ensure user exists
+		cy.createUser(jdoe).login(admin)
+
 		// open the User settings
 		cy.visit('/settings/users')
 
-		cy.get(`.user-list-grid .row[data-id="${jdoe.userId}"]`).within(($row) => {
+		// see that the user is in the list
+		cy.get(`.user-list-grid .row[data-id="${jdoe.userId}"]`).within(() => {
 			// see that the list of users contains the user jdoe
 			cy.contains(jdoe.userId).should('exist')
-			// toggle the edit mode for the user jdoe
-			cy.get('.userActions button .icon-rename').click()
+			// open the actions menu for the user
+			cy.get('.userActions button.action-item__menutoggle').click()
 		})
 
-		cy.get(`.user-list-grid .row[data-id="${jdoe.userId}"]`).within(($row) => {
-			// see that the edit mode is on
-			cy.wrap($row).should('have.class', 'row--editable')
-			// see that the password of user0 is ""
-			cy.get('input[type="password"]').should('exist').and('have.value', '')
-			// set the password for user0 to 123456
-			cy.get('input[type="password"]').type('123456')
-			// When I set the password for user0 to 123456
-			cy.get('input[type="password"]').should('have.value', '123456')
-			cy.get('.password button').click()
-
-			// Ignore failure if modal is not shown
-			cy.once('fail', (error) => {
-				expect(error.name).to.equal('AssertionError')
-				expect(error).to.have.property('node', '.modal-container')
-			})
-			// Make sure no confirmation modal is shown
-			cy.root().closest('body').find('.modal-container').then(($modal) => {
-				if ($modal.length > 0) {
-					cy.wrap($modal).find('input[type="password"]').type(admin.password)
-					cy.wrap($modal).find('button').contains('Confirm').click()
-				}
-			})
-
-			// see that the password cell for user user0 is done loading
-			cy.get('.user-row-text-field.icon-loading-small').should('exist')
-			cy.waitUntil(() => cy.get('.user-row-text-field.icon-loading-small').should('not.exist'), { timeout: 10000 })
-			// password input is emptied on change
-			cy.get('input[type="password"]').should('have.value', '')
-		})
-		// Success message is shown
-		cy.get('.toastify.toast-success').contains(/Password.+successfully.+changed/i).should('exist')
+		// The "Delete user" action in the actions menu is shown and clicked
+		cy.get('.action-item__popper .action').contains('Delete user').should('exist').click()
+		// And confirmation dialog accepted
+		cy.get('.oc-dialog button').contains(`Delete ${jdoe.userId}`).click()
+		// deleted clicked the user is not shown anymore
+		cy.get(`.user-list-grid .row[data-id="${jdoe.userId}"]`).should('not.exist')
 	})
 })

+ 89 - 0
cypress/e2e/settings/users_disable.cy.ts

@@ -0,0 +1,89 @@
+/**
+ * @copyright Copyright (c) 2023 Ferdinand Thiessen <opensource@fthiessen.de>
+ *
+ * @author Ferdinand Thiessen <opensource@fthiessen.de>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import { User } from '@nextcloud/cypress'
+
+const admin = new User('admin', 'admin')
+const jdoe = new User('jdoe', 'jdoe')
+
+describe('Settings: Disable and enable users', function() {
+	before(function() {
+		cy.createUser(jdoe)
+		cy.login(admin)
+	})
+
+	after(() => {
+		cy.deleteUser(jdoe)
+	})
+
+	it('Can disable the user', function() {
+		// ensure user is enabled
+		cy.enableUser(jdoe)
+		// open the User settings
+		cy.visit('/settings/users')
+		// see that the user is in the list of active users
+		cy.get(`.user-list-grid .row[data-id="${jdoe.userId}"]`).within(() => {
+			// see that the list of users contains the user jdoe
+			cy.contains(jdoe.userId).should('exist')
+			// open the actions menu for the user
+			cy.get('.userActions button.action-item__menutoggle').click()
+		})
+
+		// The "Disable user" action in the actions menu is shown and clicked
+		cy.get('.action-item__popper .action').contains('Disable user').should('exist').click()
+		// When clicked the section is not shown anymore
+		cy.get(`.user-list-grid .row[data-id="${jdoe.userId}"]`).should('not.exist')
+		// But the disabled user section now exists
+		cy.get('#disabled').should('exist')
+		// Open disabled users section
+		cy.get('#disabled a').click()
+		cy.url().should('match', /\/disabled/)
+		// The list of disabled users should now contain the user
+		cy.get(`.user-list-grid .row[data-id="${jdoe.userId}"]`).should('exist')
+	})
+
+	it('Can enable the user', function() {
+		// ensure user is disabled
+		cy.enableUser(jdoe, false)
+		// open the User settings
+		cy.visit('/settings/users')
+
+		// Open disabled users section
+		cy.get('#disabled a').click()
+		cy.url().should('match', /\/disabled/)
+
+		cy.get(`.user-list-grid .row[data-id="${jdoe.userId}"]`).within(() => {
+			// see that the list of disabled users contains the user jdoe
+			cy.contains(jdoe.userId).should('exist')
+			// open the actions menu for the user
+			cy.get('.userActions button.action-item__menutoggle').click()
+		})
+
+		// The "Enable user" action in the actions menu is shown and clicked
+		cy.get('.action-item__popper .action').contains('Enable user').should('exist').click()
+		// When clicked the section is not shown anymore
+		cy.get('#disabled').should('not.exist')
+		// Make sure it is still gone after the reload reload
+		cy.reload().login(admin)
+		cy.get('#disabled').should('not.exist')
+	})
+})

+ 82 - 0
cypress/e2e/settings/users_modify.cy.ts

@@ -0,0 +1,82 @@
+/**
+ * @copyright Copyright (c) 2023 Ferdinand Thiessen <opensource@fthiessen.de>
+ *
+ * @author Ferdinand Thiessen <opensource@fthiessen.de>
+ *
+ * @license AGPL-3.0-or-later
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import { User } from '@nextcloud/cypress'
+
+const admin = new User('admin', 'admin')
+const jdoe = new User('jdoe', 'jdoe')
+
+describe('Settings: Change user properties', function() {
+	before(function() {
+		cy.createUser(jdoe)
+		cy.login(admin)
+	})
+
+	after(() => {
+		cy.deleteUser(jdoe)
+	})
+
+	it('Can change the password', function() {
+		// open the User settings
+		cy.visit('/settings/users')
+
+		cy.get(`.user-list-grid .row[data-id="${jdoe.userId}"]`).within(($row) => {
+			// see that the list of users contains the user jdoe
+			cy.contains(jdoe.userId).should('exist')
+			// toggle the edit mode for the user jdoe
+			cy.get('.userActions .action-items > button:first-of-type').click()
+		})
+
+		cy.get(`.user-list-grid .row[data-id="${jdoe.userId}"]`).within(($row) => {
+			// see that the edit mode is on
+			cy.wrap($row).should('have.class', 'row--editable')
+			// see that the password of user0 is ""
+			cy.get('input[type="password"]').should('exist').and('have.value', '')
+			// set the password for user0 to 123456
+			cy.get('input[type="password"]').type('123456')
+			// When I set the password for user0 to 123456
+			cy.get('input[type="password"]').should('have.value', '123456')
+			cy.get('.password button').click()
+
+			// Ignore failure if modal is not shown
+			cy.once('fail', (error) => {
+				expect(error.name).to.equal('AssertionError')
+				expect(error).to.have.property('node', '.modal-container')
+			})
+			// Make sure no confirmation modal is shown
+			cy.root().closest('body').find('.modal-container').then(($modal) => {
+				if ($modal.length > 0) {
+					cy.wrap($modal).find('input[type="password"]').type(admin.password)
+					cy.wrap($modal).find('button').contains('Confirm').click()
+				}
+			})
+
+			// see that the password cell for user user0 is done loading
+			cy.get('.user-row-text-field.icon-loading-small').should('exist')
+			cy.waitUntil(() => cy.get('.user-row-text-field.icon-loading-small').should('not.exist'), { timeout: 10000 })
+			// password input is emptied on change
+			cy.get('input[type="password"]').should('have.value', '')
+		})
+		// Success message is shown
+		cy.get('.toastify.toast-success').contains(/Password.+successfully.+changed/i).should('exist')
+	})
+})

+ 32 - 0
cypress/support/commands.ts

@@ -33,6 +33,11 @@ declare global {
 	// eslint-disable-next-line @typescript-eslint/no-namespace
 	namespace Cypress {
 		interface Chainable<Subject = any> {
+			/**
+			 * Enable or disable a given user
+			 */
+			enableUser(user: User, enable?: boolean): Cypress.Chainable<Cypress.Response<any>>,
+
 			/**
 			 * Upload a file from the fixtures folder to a given user storage.
 			 * **Warning**: Using this function will reset the previous session
@@ -69,6 +74,33 @@ declare global {
 const url = (Cypress.config('baseUrl') || '').replace(/\/index.php\/?$/g, '')
 Cypress.env('baseUrl', url)
 
+/**
+ * Enable or disable a user
+ * TODO: standardise in @nextcloud/cypress
+ *
+ * @param {User} user the user to dis- / enable
+ * @param {boolean} enable True if the user should be enable, false to disable
+ */
+Cypress.Commands.add('enableUser', (user: User, enable = true) => {
+	const url = `${Cypress.config('baseUrl')}/ocs/v2.php/cloud/users/${user.userId}/${enable ? 'enable' : 'disable'}`.replace('index.php/', '')
+	return cy.request({
+		method: 'PUT',
+		url,
+		form: true,
+		auth: {
+			user: 'admin',
+			password: 'admin',
+		},
+		headers: {
+			'OCS-ApiRequest': 'true',
+			'Content-Type': 'application/x-www-form-urlencoded',
+		},
+	}).then((response) => {
+		cy.log(`Enabled user ${user}`, response.status)
+		return cy.wrap(response)
+	})
+})
+
 /**
  * cy.uploadedFile - uploads a file from the fixtures folder
  * TODO: standardise in @nextcloud/cypress

+ 3 - 4
tests/acceptance/features/bootstrap/UsersSettingsContext.php

@@ -125,7 +125,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 * @return Locator
 	 */
 	public static function actionsMenuOf($user) {
-		return Locator::forThe()->css(".icon-more")->
+		return Locator::forThe()->css(".userActions .action-item:not(.action-item--single)")->
 			descendantOf(self::rowForUser($user))->
 			describedAs("Actions menu for user $user in Users Settings");
 	}
@@ -134,8 +134,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 * @return Locator
 	 */
 	public static function theAction($action, $user) {
-		return Locator::forThe()->xpath("//button[normalize-space() = '$action']")->
-			descendantOf(self::rowForUser($user))->
+		return Locator::forThe()->xpath("//button[@aria-label = normalize-space('$action')]")->
 			describedAs("$action action for the user $user row in Users Settings");
 	}
 
@@ -160,7 +159,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
 	 * @return Locator
 	 */
 	public static function editModeToggle($user) {
-		return Locator::forThe()->css(".toggleUserActions button")->
+		return Locator::forThe()->css(".userActions .action-items button:first-of-type")->
 			descendantOf(self::rowForUser($user))->
 			describedAs("The edit toggle button for the user $user in Users Settings");
 	}

+ 34 - 34
tests/acceptance/features/users.feature

@@ -22,42 +22,42 @@ Feature: users
     Then I see that the list of users contains the user "test"
 #    And I see that the display name for the user "test" is "Test display name"
 
-  Scenario: delete a user
-    Given I act as Jane
-    And I am logged in as the admin
-    And I open the User settings
-    And I see that the list of users contains the user user0
-    And I open the actions menu for the user user0
-    And I see that the "Delete user" action in the user0 actions menu is shown
-    When I click the "Delete user" action in the user0 actions menu
-    And I click the "Delete user0's account" button of the confirmation dialog
-    Then I see that the list of users does not contains the user user0
+#  Scenario: delete a user
+#    Given I act as Jane
+#    And I am logged in as the admin
+#    And I open the User settings
+#    And I see that the list of users contains the user user0
+#    And I open the actions menu for the user user0
+#    And I see that the "Delete user" action in the user0 actions menu is shown
+#    When I click the "Delete user" action in the user0 actions menu
+#    And I click the "Delete user0's account" button of the confirmation dialog
+#    Then I see that the list of users does not contains the user user0
 
-  Scenario: disable a user
-    Given I act as Jane
-    And I am logged in as the admin
-    And I open the User settings
-    And I see that the list of users contains the user user0
-    And I open the actions menu for the user user0
-    And I see that the "Disable user" action in the user0 actions menu is shown
-    When I click the "Disable user" action in the user0 actions menu
-    Then I see that the list of users does not contains the user user0
-    When I open the "Disabled users" section
-    Then I see that the list of users contains the user user0
+#  Scenario: disable a user
+#    Given I act as Jane
+#    And I am logged in as the admin
+#    And I open the User settings
+#    And I see that the list of users contains the user user0
+#    And I open the actions menu for the user user0
+#    And I see that the "Disable user" action in the user0 actions menu is shown
+#    When I click the "Disable user" action in the user0 actions menu
+#    Then I see that the list of users does not contains the user user0
+#    When I open the "Disabled users" section
+#    Then I see that the list of users contains the user user0
 
-  Scenario: users navigation without disabled users
-    Given I act as Jane
-    And I am logged in as the admin
-    And I open the User settings
-    And I open the "Disabled users" section
-    And I see that the list of users contains the user disabledUser
-    And I open the actions menu for the user disabledUser
-    And I see that the "Enable user" action in the disabledUser actions menu is shown
-    When I click the "Enable user" action in the disabledUser actions menu
-    Then I see that the section "Disabled users" is not shown
-    # check again after reloading the settings
-    When I open the User settings
-    Then I see that the section "Disabled users" is not shown
+#  Scenario: users navigation without disabled users
+#    Given I act as Jane
+#    And I am logged in as the admin
+#    And I open the User settings
+#    And I open the "Disabled users" section
+#    And I see that the list of users contains the user disabledUser
+#    And I open the actions menu for the user disabledUser
+#    And I see that the "Enable user" action in the disabledUser actions menu is shown
+#    When I click the "Enable user" action in the disabledUser actions menu
+#    Then I see that the section "Disabled users" is not shown
+#    # check again after reloading the settings
+#    When I open the User settings
+#    Then I see that the section "Disabled users" is not shown
 
   Scenario: assign user to a group
     Given I act as Jane