diff --git a/app/components/organization-invitation-list/index.hbs b/app/components/organization-invitation-list/index.hbs index 7b43b87f11..d5f2977c08 100644 --- a/app/components/organization-invitation-list/index.hbs +++ b/app/components/organization-invitation-list/index.hbs @@ -27,7 +27,11 @@ - + {{#if r.columnValue.component}} {{#let (component r.columnValue.component) as |Component|}} diff --git a/app/components/organization-invitation-list/invite-delete/index.hbs b/app/components/organization-invitation-list/invite-delete/index.hbs index 957dee042e..2f98d7071c 100644 --- a/app/components/organization-invitation-list/invite-delete/index.hbs +++ b/app/components/organization-invitation-list/invite-delete/index.hbs @@ -1,6 +1,7 @@ {{! @glint-nocheck: perform }} + {{else}} diff --git a/app/components/organization-member/list/add-to-team/index.hbs b/app/components/organization-member/list/add-to-team/index.hbs index 1e8ffcdd84..cc672c39e0 100644 --- a/app/components/organization-member/list/add-to-team/index.hbs +++ b/app/components/organization-member/list/add-to-team/index.hbs @@ -6,6 +6,7 @@ > {{@member.member.username}} - + ({{@member.member.email}}) @@ -79,7 +80,13 @@ {{else}}
- + diff --git a/app/components/organization-member/list/index.hbs b/app/components/organization-member/list/index.hbs index 601f463d7c..096b22fe12 100644 --- a/app/components/organization-member/list/index.hbs +++ b/app/components/organization-member/list/index.hbs @@ -2,7 +2,7 @@
@@ -74,11 +74,17 @@ {{else}} - + - + diff --git a/app/components/organization-member/list/member-details/remove-action/index.hbs b/app/components/organization-member/list/member-details/remove-action/index.hbs index 7934b64ec2..005cf9c039 100644 --- a/app/components/organization-member/list/member-details/remove-action/index.hbs +++ b/app/components/organization-member/list/member-details/remove-action/index.hbs @@ -1,7 +1,8 @@ diff --git a/app/components/organization-member/list/member-drawer/index.hbs b/app/components/organization-member/list/member-drawer/index.hbs index c244720dfa..c15e2bbd05 100644 --- a/app/components/organization-member/list/member-drawer/index.hbs +++ b/app/components/organization-member/list/member-drawer/index.hbs @@ -17,6 +17,7 @@ diff --git a/app/components/organization-member/list/member-role/index.hbs b/app/components/organization-member/list/member-role/index.hbs index a27134eb62..103c28573f 100644 --- a/app/components/organization-member/list/member-role/index.hbs +++ b/app/components/organization-member/list/member-role/index.hbs @@ -1,6 +1,7 @@ {{#if this.me.org.is_owner}} { + // returning false here prevents Cypress from failing the test + return false; +}); + +let shouldRunAfterEach = false; + +describe('Organization Users', () => { + beforeEach(() => { + networkActions.hideNetworkLogsFor({ ...API_ROUTES.websockets }); + + cy.intercept(API_ROUTES.userInfo.route).as('userInfoRoute'); + cy.intercept(API_ROUTES.submissionList.route).as('submissionList'); + cy.intercept(API_ROUTES.teamsList.route).as('teamList'); + cy.intercept(API_ROUTES.membersList.route).as('membersList'); + cy.intercept(API_ROUTES.teamMembers.route).as('teamMembers'); + cy.intercept('PUT', API_ROUTES.member.route).as('memberReq'); + cy.intercept('PUT', API_ROUTES.editUserInfo.route).as('editUser'); + + loginActions.loginWithCredAndSaveSession({ + username: AUTH_USER.username, + password: AUTH_USER.password, + }); + + // Visit Organization Users page + cy.visit(APPLICATION_ROUTES.organizationUsers); + + // Wait for relevant network responses + cy.wait('@submissionList', NETWORK_WAIT_OPTS); + cy.wait('@userInfoRoute', NETWORK_WAIT_OPTS); + + // wait for page to load + cy.findByTestId('org-user-table') + .should('be.visible', ELEMENT_WAIT_OPTS) + .as('orgUserTable'); + }); + + it('should change user roles', function () { + cy.get('@orgUserTable') + .find('tr') + .filter(':contains(' + TEST_DATA.testUser.username + ')') + .as('userRow'); + + // Change user role within the row + cy.get('@userRow').within(() => { + cy.findByTestId('user-role-select', ELEMENT_WAIT_OPTS) + .as('userRoleSelect') + .should('exist'); + + // Click the role dropdown to show options + cy.get('@userRoleSelect').click(); + }); + + // Select and verify role "Admin" + cy.findByRole('option', { name: 'Admin' }).should('exist').click(); + + // Verify that the role has been changed to "Admin" + cy.get('@userRoleSelect').should('contain.text', 'Admin'); + + cy.wait('@memberReq', NETWORK_WAIT_OPTS).then((interception) => { + expect(interception.response?.body?.role_display).to.eq('Admin'); + + // Assert that the response contains the updated role "Admin" + expect(interception.response?.statusCode).to.eq(200); + }); + + // Verify the success message for role update + cy.findByText(cyTranslate('userRoleUpdated'), NETWORK_WAIT_OPTS).should( + 'exist' + ); + + // Change user role back to "Owner" + cy.get('@userRow').within(() => { + cy.findByTestId('user-role-select', ELEMENT_WAIT_OPTS) + .as('userRoleSelect') + .should('exist'); + + // Click the role dropdown to show options + cy.get('@userRoleSelect').click(); + }); + + cy.findByRole('option', { name: 'Owner' }).should('exist').click(); + + // Verify that the role has been changed to "Owner" + cy.get('@userRoleSelect').should('contain.text', 'Owner'); + + cy.wait('@memberReq').then((interception) => { + // Assert that the response contains the updated role "Owner" + expect(interception.response?.statusCode).to.eq(200); + + expect(interception.response?.body?.role_display).to.eq('Owner'); + }); + + // Verify the final success message for role update + cy.findByText(cyTranslate('userRoleUpdated'), NETWORK_WAIT_OPTS).should( + 'exist' + ); + }); + + it('should search user in users list', function () { + // Ensure the search input exists + cy.findByPlaceholderText(cyTranslate('searchUser')) + .as('searchInput') + .should('exist'); + + // Search for the user + cy.get('@searchInput').type(TEST_DATA.testUser.username); + + // Verify that the user was found + cy.get('@searchInput').should('have.value', TEST_DATA.testUser.username); + + // Find the user row containing the user + cy.findByTestId('org-user-row', ELEMENT_WAIT_OPTS).within(() => { + // Ensure the username exists within the row + cy.findByText(TEST_DATA.testUser.username).should('exist'); + }); + + // Clear the search input + cy.get('@searchInput').clear(); + + // Verify the input is cleared + cy.get('@searchInput').should('have.value', ''); + }); + + it('should invite user', function () { + // Find the invite button + cy.findByText(cyTranslate('inviteUsers')) + .as('inviteButton') + .should('exist'); + + // Click the invite button + cy.get('@inviteButton').click(); + + // Find email input box + cy.findByPlaceholderText(cyTranslate('email')) + .as('emailInput') + .should('exist'); + + // Enter email + cy.get('@emailInput').type(TEST_DATA.testUser.inviteEmail); + + cy.get('@emailInput').should('have.value', TEST_DATA.testUser.inviteEmail); + + // Find invite button + cy.findByText(cyTranslate('invite')).click(); + + // Find success message + cy.findByText(cyTranslate('orgMemberInvited'), ELEMENT_WAIT_OPTS).should( + 'exist' + ); + + // delete the invite + cy.findAllByTestId('invite-list-row') + .filter(`:contains(${TEST_DATA.testUser.inviteEmail})`) + .within(() => { + // Click the delete button inside the filtered row + cy.findByTestId('invite-delete-btn').click(); + }); + + cy.findByRole('button', { name: cyTranslate('delete') }).click(); + + // Find success message + cy.findByText(cyTranslate('invitationDeleted'), ELEMENT_WAIT_OPTS).should( + 'exist' + ); + }); + + it('should add/remove user to team', function () { + cy.get('@orgUserTable') + .find('tr') + .filter(':contains(' + TEST_DATA.testUser.username + ')') + .as('userRow'); + + cy.get('@userRow').click(); + + // Add user to team + cy.findByText(cyTranslate('addToTeams')).click(); + + // search team + cy.findByPlaceholderText(cyTranslate('searchTeam')).as('searchInput'); + + cy.get('@searchInput').type(TEST_DATA.teamName); + + cy.findByTestId('teamList-table', ELEMENT_WAIT_OPTS).as('teamListTable'); + + cy.get('@teamListTable') + .find('tr', ELEMENT_WAIT_OPTS) + .filter(':contains(' + TEST_DATA.teamName + ')') + .as('teamRow'); + + cy.get('@teamRow').within(() => { + // Find add to team button + cy.findByTestId('add-to-team-btn').click(); + }); + + cy.wait('@teamMembers', NETWORK_WAIT_OPTS); + + cy.wait('@teamList', NETWORK_WAIT_OPTS); + + // Go back + cy.findByTestId('add-to-team-back-btn').click(); + + cy.wait('@teamList', NETWORK_WAIT_OPTS); + + cy.findByTestId('teamList-table', ELEMENT_WAIT_OPTS) + .should('exist') + .as('teamListTable'); + + // Remove user from team + cy.get('@teamListTable') + .find('tr', ELEMENT_WAIT_OPTS) + .filter(':contains(' + TEST_DATA.teamName + ')') + .as('teamRow'); + + cy.get('@teamRow').within(() => { + // Find remove from team button + cy.findByTestId('remove-user').should('exist').click(); + }); + + cy.findByRole('button', { name: 'Remove' }).click(); + + cy.wait('@teamMembers', NETWORK_WAIT_OPTS); + + cy.wait('@teamList', NETWORK_WAIT_OPTS); + + cy.get('@teamListTable', ELEMENT_WAIT_OPTS).should('not.exist'); + + cy.findByTestId('member-drawer-close-btn').click(); + }); + + it('should deactivate and activate user', function () { + shouldRunAfterEach = true; + + // Used for clean up in afterEach + cy.wrap(false).as('testCompleted'); + + // Locate the user row by username + cy.get('@orgUserTable') + .find('tr') + .filter(`:contains(${TEST_DATA.testUser.username})`) + .as('userRow'); + + // Click the user row to open details + cy.get('@userRow').click(); + + // Deactivate the user + cy.findByText(cyTranslate('deactivateUser')).click(); + cy.findByText(cyTranslate('confirm')).click(); + + // Wait for the user edit action to complete + cy.wait('@editUser', NETWORK_WAIT_OPTS).then((interception) => { + expect(interception.response?.body?.is_active).to.eq(false); + + expect(interception.response?.statusCode).to.eq(200); + }); + + // Close the modal after deactivating the user + cy.findByTestId('member-drawer-close-btn').click(); + + // Enable viewing inactive members + cy.findByLabelText(cyTranslate('includeInactiveMembers')).check(); + + cy.wait('@membersList', NETWORK_WAIT_OPTS); + + cy.findByTestId('org-user-table', ELEMENT_WAIT_OPTS).as('orgUserTable'); + + // Locate the inactive user row by username + cy.get('@orgUserTable', ELEMENT_WAIT_OPTS) + .find('tr') + .filter(`:contains(${TEST_DATA.testUser.username})`) + .as('inactiveUserRow'); + + // Verify that the user is now inactive + cy.get('@inactiveUserRow', ELEMENT_WAIT_OPTS).within(() => { + cy.findByText(cyTranslate('chipStatus.inactive')).should('exist'); + }); + + // Click the inactive user row to activate the user + cy.get('@inactiveUserRow').click(); + + // // Activate the user + cy.findByText(cyTranslate('activateUser')).click(); + cy.findByText(cyTranslate('confirm')).click(); + + // Wait for the user edit action to complete + cy.wait('@editUser', NETWORK_WAIT_OPTS).then((interception) => { + expect(interception.response?.body?.is_active).to.eq(true); + + expect(interception.response?.statusCode).to.eq(200); + }); + + // Close the modal after activation + cy.findByTestId('member-drawer-close-btn').click(); + + cy.wrap(true).as('testCompleted'); + }); + + afterEach(function () { + if (shouldRunAfterEach) { + cy.get('@testCompleted').then((testCompleted) => { + if (testCompleted) { + return; + } else { + // if test fails activate user again + cy.findByTestId('org-user-table', ELEMENT_WAIT_OPTS).as( + 'orgUserTable' + ); + + // Locate the inactive user row by username + cy.get('@orgUserTable', ELEMENT_WAIT_OPTS) + .find('tr') + .filter(`:contains(${TEST_DATA.testUser.username})`) + .as('inactiveUserRow'); + + // Click the inactive user row to activate the user + cy.get('@inactiveUserRow').click(); + + // Activate the user + cy.findByText(cyTranslate('activateUser')).click({ force: true }); + cy.findByText(cyTranslate('confirm'), ELEMENT_WAIT_OPTS).click({ + force: true, + }); + + // Wait for the user edit action to complete + cy.wait('@editUser', NETWORK_WAIT_OPTS); + + // Close the modal after activation + cy.findByTestId('member-drawer-close-btn').click(); + } + }); + } + }); +});