fix(iam): cancelling the Edit User dialog no longer reopens it (#15879)

This commit is contained in:
Bhargav
2026-10-27 11:33:56 +00:00
committed by GitHub
parent 0794fefac5
commit 3ddfe50a09
5 changed files with 124 additions and 2 deletions
@@ -128,6 +128,7 @@
"landing-regression.spec.js",
"internal-route-guards.spec.js",
"datetime-paste-invalid-values-14648.spec.js",
"iam-users-15546-edit-dialog-cancel.spec.js",
"query-management-sql-tooltip.spec.js"
],
"disabled": []
@@ -355,6 +355,12 @@ export class IamFormValidationPage {
return this.page.locator(this.editBasicUserFirstBtn).count();
}
async clickFirstEditUserButton() {
await this.page.locator(this.editBasicUserFirstBtn).first().click();
}
getUrlWithUserActionPattern() { return /[?&]action=(update|add)\b|[?&]email=/; }
async navigateToUsersTab() {
await this.navigateToIam();
const tab = this.page.locator(this.iamUsersTab);
@@ -0,0 +1,48 @@
const { test, expect, navigateToBase } = require('../../utils/enhanced-baseFixtures.js');
const testLogger = require('../../utils/test-logger.js');
const PageManager = require('../../../pages/page-manager.js');
test.describe("IAM Users edit dialog cancel Regression", () => {
let pm;
test.beforeEach(async ({ page }, testInfo) => {
testLogger.testStart(testInfo.title, testInfo.file);
await navigateToBase(page);
pm = new PageManager(page);
await pm.iamFormValidation.navigateToUsersTab();
});
test("cancelled edit dialog stays closed and leaves no action in the URL @bug-15546 @P3 @regression @iamUsers", async ({ page }) => {
const iam = pm.iamFormValidation;
await iam.openUpdateRoleDialogForFirstUser();
await expect(page).toHaveURL(/action=update/);
await iam.getUpdateRoleCancelBtnLocator().click();
await expect(iam.getUpdateRoleDialogLocator()).toBeHidden();
await expect(page).not.toHaveURL(iam.getUrlWithUserActionPattern());
// Outlast the dialog's 100ms open timer so a late reopen is caught.
await page.waitForTimeout(2000);
await expect(iam.getUpdateRoleDialogLocator()).toBeHidden();
testLogger.info('Edit dialog stayed closed and URL is clean after Cancel');
});
test("edit dialog cancelled during a slow roles load does not reopen @bug-15546 @P3 @regression @iamUsers", async ({ page }) => {
const iam = pm.iamFormValidation;
await page.route(/\/api\/[^/]+\/users\/roles(\?.*)?$/, async (route) => {
await new Promise((r) => setTimeout(r, 3000));
await route.continue();
});
await page.reload();
await iam.clickFirstEditUserButton();
await expect(iam.getUpdateRoleDialogLocator()).toBeVisible();
await iam.getUpdateRoleCancelBtnLocator().click();
await expect(iam.getUpdateRoleDialogLocator()).toBeHidden();
await page.waitForTimeout(4500);
await expect(iam.getUpdateRoleDialogLocator()).toBeHidden();
await expect(page).not.toHaveURL(iam.getUrlWithUserActionPattern());
});
});
+56
View File
@@ -1051,6 +1051,62 @@ describe("User Component", () => {
});
});
describe("AddUser dialog dismissal (#15546)", () => {
it("clears action/email from the URL when the dialog is cancelled", async () => {
const replaceSpy = vi.spyOn(router, "replace").mockImplementation(() => Promise.resolve());
wrapper.vm.showAddUserDialog = true;
await flushPromises();
wrapper.findComponent(AddUserStub).vm.$emit("update:open", false);
await flushPromises();
expect(wrapper.vm.showAddUserDialog).toBe(false);
expect(replaceSpy).toHaveBeenCalledWith({
name: "users",
query: { org_identifier: store.state.selectedOrganization.identifier },
});
replaceSpy.mockRestore();
});
it("drops a pending open timer when the form is hidden", async () => {
const replaceSpy = vi.spyOn(router, "replace").mockImplementation(() => Promise.resolve());
wrapper.vm.addUser({ row: { email: "test@example.com" } }, true);
wrapper.vm.hideForm();
await new Promise((r) => setTimeout(r, 150));
expect(wrapper.vm.showAddUserDialog).toBe(false);
replaceSpy.mockRestore();
});
it("does not reopen the edit dialog when it was cancelled while the page was still loading", async () => {
sharedUsersState.users = [];
queryClient.clear();
let resolveRoles: (v: any) => void = () => {};
mockUsersService.getRoles.mockReturnValue(new Promise((r) => (resolveRoles = r)) as any);
await router.push({
name: "users",
query: {
action: "update",
email: "test@example.com",
org_identifier: store.state.selectedOrganization.identifier,
},
});
const late = mountUser();
await flushPromises();
late.findComponent(AddUserStub).vm.$emit("update:open", false);
await flushPromises();
expect(router.currentRoute.value.query.action).toBeUndefined();
resolveRoles({ data: [{ label: "admin", value: "admin" }] });
await flushPromises();
await new Promise((r) => setTimeout(r, 150));
expect((late.vm as any).showAddUserDialog).toBe(false);
late.unmount();
});
});
describe("hideForm", () => {
it("should hide add user dialog and navigate", () => {
const replaceSpy = vi.spyOn(router, "replace").mockImplementation(() => Promise.resolve());
+13 -2
View File
@@ -283,7 +283,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/>
<AddUser
v-model:open="showAddUserDialog"
:open="showAddUserDialog"
@update:open="onAddUserOpenChange"
v-model="selectedUser"
:isUpdated="isUpdated"
:userRole="currentUserRole"
@@ -1117,6 +1118,8 @@ export default defineComponent({
showUpdateUserDialog.value = true;
};
let openAddUserTimer: ReturnType<typeof setTimeout> | undefined;
const addUser = (props: any, is_updated: boolean) => {
isUpdated.value = is_updated;
selectedUser.value.organization = store.state.selectedOrganization.identifier;
@@ -1133,7 +1136,8 @@ export default defineComponent({
} else {
selectedUser.value = {};
}
setTimeout(() => {
clearTimeout(openAddUserTimer);
openAddUserTimer = setTimeout(() => {
showAddUserDialog.value = true;
}, 100);
};
@@ -1227,6 +1231,7 @@ export default defineComponent({
};
const hideForm = () => {
clearTimeout(openAddUserTimer);
showAddUserDialog.value = false;
router.replace({
name: "users",
@@ -1236,6 +1241,11 @@ export default defineComponent({
});
};
const onAddUserOpenChange = (open: boolean) => {
if (open) showAddUserDialog.value = true;
else hideForm();
};
const addMember = async (res: any, data: any, operationType: string) => {
showAddUserDialog.value = false;
if (res.code == 200) {
@@ -1576,6 +1586,7 @@ export default defineComponent({
addRoutePush,
addMember,
hideForm,
onAddUserOpenChange,
isUpdated,
showAddUserDialog,
selectedUser,