fixed flaky-ness and removed deprecated select (#30078)
fixes: #29507 Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
parent
0222c803a9
commit
9afb1a47fb
6 changed files with 104 additions and 119 deletions
|
@ -9,7 +9,6 @@ import { keycloakBefore } from "../support/util/keycloak_hooks";
|
|||
import ModalUtils from "../support/util/ModalUtils";
|
||||
import RealmSettingsPage from "../support/pages/admin-ui/manage/realm_settings/RealmSettingsPage";
|
||||
import CreateUserPage from "../support/pages/admin-ui/manage/users/CreateUserPage";
|
||||
import { UserProfileConfig } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
||||
|
||||
const loginPage = new LoginPage();
|
||||
const sidebarPage = new SidebarPage();
|
||||
|
@ -28,37 +27,18 @@ const getAttributesGroupTab = () => userProfileTab.goToAttributesGroupTab();
|
|||
const usernameAttributeName = "username";
|
||||
const emailAttributeName = "email";
|
||||
|
||||
let defaultUserProfile: UserProfileConfig;
|
||||
|
||||
describe("User profile tabs", () => {
|
||||
const realmName = "Realm_" + uuid();
|
||||
const attributeName = "Test";
|
||||
const attributeDisplayName = "Test display name";
|
||||
|
||||
before(() => {
|
||||
cy.wrap(null).then(async () => {
|
||||
await adminClient.createRealm(realmName);
|
||||
before(() => adminClient.createRealm(realmName));
|
||||
|
||||
defaultUserProfile = await adminClient.getUserProfile(realmName);
|
||||
});
|
||||
});
|
||||
|
||||
after(() =>
|
||||
cy.wrap(null).then(async () => await adminClient.deleteRealm(realmName)),
|
||||
);
|
||||
after(() => adminClient.deleteRealm(realmName));
|
||||
|
||||
beforeEach(() => {
|
||||
loginPage.logIn();
|
||||
keycloakBefore();
|
||||
cy.wrap(null).then(async () => {
|
||||
await adminClient.updateUserProfile(realmName, defaultUserProfile);
|
||||
|
||||
await adminClient.updateRealm(realmName, {
|
||||
registrationEmailAsUsername: false,
|
||||
editUsernameAllowed: false,
|
||||
});
|
||||
});
|
||||
|
||||
sidebarPage.goToRealm(realmName);
|
||||
sidebarPage.goToRealmSettings();
|
||||
});
|
||||
|
@ -91,11 +71,12 @@ describe("User profile tabs", () => {
|
|||
});
|
||||
|
||||
it("Modifies existing attribute and performs save", () => {
|
||||
const attrName = "ModifyTest";
|
||||
getUserProfileTab();
|
||||
createAttributeDefinition(attributeName);
|
||||
createAttributeDefinition(attrName);
|
||||
|
||||
userProfileTab
|
||||
.selectElementInList(attributeName)
|
||||
.selectElementInList(attrName)
|
||||
.editAttribute("Edited display name")
|
||||
.saveAttributeCreation()
|
||||
.assertNotificationSaved();
|
||||
|
@ -103,7 +84,6 @@ describe("User profile tabs", () => {
|
|||
|
||||
it("Adds and removes validator to/from existing attribute and performs save", () => {
|
||||
getUserProfileTab();
|
||||
createAttributeDefinition(attributeName);
|
||||
|
||||
userProfileTab
|
||||
.selectElementInList(attributeName)
|
||||
|
@ -120,19 +100,16 @@ describe("User profile tabs", () => {
|
|||
});
|
||||
|
||||
describe("Attribute groups sub tab tests", () => {
|
||||
it.skip("Deletes an attributes group", () => {
|
||||
const group = "Test";
|
||||
cy.wrap(null).then(() =>
|
||||
adminClient.patchUserProfile(realmName, {
|
||||
groups: [{ name: group }],
|
||||
}),
|
||||
);
|
||||
const group = "Test" + uuid();
|
||||
|
||||
before(() => adminClient.addGroupToProfile(realmName, group));
|
||||
|
||||
it("Deletes an attributes group", () => {
|
||||
getUserProfileTab();
|
||||
getAttributesGroupTab();
|
||||
listingPage.deleteItem(group);
|
||||
modalUtils.confirmModal();
|
||||
listingPage.checkEmptyList();
|
||||
listingPage.itemExist(group, false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -202,6 +179,8 @@ describe("User profile tabs", () => {
|
|||
.setAttributeValue(emailAttributeName, `testuser9-${uuid()}@gmail.com`)
|
||||
.update()
|
||||
.assertNotificationUpdated();
|
||||
|
||||
deleteAttributeDefinition(attrName);
|
||||
});
|
||||
|
||||
it("Checks that not required attribute with permissions to view/edit is present when user is created", () => {
|
||||
|
@ -216,7 +195,9 @@ describe("User profile tabs", () => {
|
|||
realmSettingsPage.goToLoginTab();
|
||||
cy.wait(1000);
|
||||
realmSettingsPage
|
||||
.setSwitch(realmSettingsPage.emailAsUsernameSwitch, false)
|
||||
.assertSwitch(realmSettingsPage.emailAsUsernameSwitch, false)
|
||||
.setSwitch(realmSettingsPage.editUsernameSwitch, false)
|
||||
.assertSwitch(realmSettingsPage.editUsernameSwitch, false);
|
||||
|
||||
// Create user
|
||||
|
@ -228,6 +209,8 @@ describe("User profile tabs", () => {
|
|||
.create()
|
||||
.assertNotificationCreated()
|
||||
.assertAttributeFieldExists(attrName, true);
|
||||
|
||||
deleteAttributeDefinition(attrName);
|
||||
});
|
||||
|
||||
it("Checks that required attribute with permissions to view/edit is present and required when user is created", () => {
|
||||
|
@ -251,6 +234,8 @@ describe("User profile tabs", () => {
|
|||
.setAttributeValue(attrName, "MyAttribute")
|
||||
.create()
|
||||
.assertNotificationCreated();
|
||||
|
||||
deleteAttributeDefinition(attrName);
|
||||
});
|
||||
|
||||
it("Checks that required attribute with permissions to view/edit is accepted when user is created", () => {
|
||||
|
@ -270,6 +255,8 @@ describe("User profile tabs", () => {
|
|||
.setAttributeValue(attrName, "MyAttribute")
|
||||
.create()
|
||||
.assertNotificationCreated();
|
||||
|
||||
deleteAttributeDefinition(attrName);
|
||||
});
|
||||
|
||||
it("Checks that attribute group is visible when user with existing attribute is created", () => {
|
||||
|
@ -358,6 +345,8 @@ describe("User profile tabs", () => {
|
|||
.resetAttributeGroup()
|
||||
.saveAttributeCreation()
|
||||
.assertNotificationSaved();
|
||||
|
||||
deleteAttributeDefinition(attrName);
|
||||
});
|
||||
|
||||
it("Checks that attribute with select-annotation is displayed and editable when user is created/edited", () => {
|
||||
|
@ -382,7 +371,7 @@ describe("User profile tabs", () => {
|
|||
createUserPage
|
||||
.goToCreateUser()
|
||||
.assertAttributeLabel(attrName, attrName)
|
||||
.assertAttributeSelect(attrName, supportedOptions, "")
|
||||
.assertAttributeSelect(attrName, supportedOptions, "Choose...")
|
||||
.setUsername(userName)
|
||||
.setAttributeValueOnSelect(attrName, opt1)
|
||||
.create()
|
||||
|
@ -400,6 +389,15 @@ describe("User profile tabs", () => {
|
|||
});
|
||||
});
|
||||
|
||||
function deleteAttributeDefinition(attrName: string) {
|
||||
sidebarPage.goToRealmSettings();
|
||||
getUserProfileTab();
|
||||
getAttributesTab();
|
||||
listingPage.deleteItem(attrName);
|
||||
modalUtils.confirmModal();
|
||||
masthead.checkNotificationMessage("Attribute deleted");
|
||||
}
|
||||
|
||||
function createAttributeDefinition(
|
||||
attrName: string,
|
||||
attrConfigurer?: (attrConfigurer: UserProfile) => void,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Masthead from "../../Masthead";
|
||||
import FormValidation from "../../../../forms/FormValidation";
|
||||
import Select from "../../../../forms/Select";
|
||||
|
||||
export default class CreateUserPage {
|
||||
readonly masthead = new Masthead();
|
||||
|
@ -84,28 +85,11 @@ export default class CreateUserPage {
|
|||
expectedOptionsWithoutEmptyOption: string[],
|
||||
expectedValue: string,
|
||||
) {
|
||||
this.#getSelectFieldButton(attrName).should(
|
||||
"have.class",
|
||||
"pf-v5-c-select__toggle",
|
||||
Select.assertSelectedItem(
|
||||
this.#getSelectFieldButton(attrName),
|
||||
expectedValue,
|
||||
);
|
||||
|
||||
const valueToCheck = expectedValue ? expectedValue : this.emptyOptionValue;
|
||||
this.#getSelectFieldButton(attrName)
|
||||
.find(".pf-v5-c-select__toggle-text")
|
||||
.invoke("text")
|
||||
.should("eq", valueToCheck);
|
||||
|
||||
const expectedOptions = [this.emptyOptionValue].concat(
|
||||
expectedOptionsWithoutEmptyOption,
|
||||
);
|
||||
this.#withSelectExpanded(attrName, () => {
|
||||
this.#getSelectOptions(attrName)
|
||||
.should("have.length", expectedOptions.length)
|
||||
.each(($option, index) =>
|
||||
cy.wrap($option).should("have.text", expectedOptions[index]),
|
||||
);
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -132,33 +116,6 @@ export default class CreateUserPage {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given function in the context of an expanded select field.
|
||||
*
|
||||
* This method makes sure that the initial expanded/collapsed state is preserved:
|
||||
* The select field will be expanded before running the function, if it is not already expanded.
|
||||
* It will be collapsed after running the function, when it was not expanded before running the function.
|
||||
*
|
||||
* @param attrName the attribute name of the select field
|
||||
* @param func the function to be applied
|
||||
* @private
|
||||
*/
|
||||
#withSelectExpanded(attrName: string, func: () => any) {
|
||||
let wasInitiallyExpanded = false;
|
||||
this.#toggleSelectField(attrName, (currentlyExpanded) => {
|
||||
wasInitiallyExpanded = currentlyExpanded;
|
||||
return !currentlyExpanded;
|
||||
})
|
||||
.then(() => func())
|
||||
.then(() =>
|
||||
// click again on the dropdown to hide the values list, when necessary
|
||||
this.#toggleSelectField(
|
||||
attrName,
|
||||
(currentlyExpanded) => currentlyExpanded && !wasInitiallyExpanded,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
assertAttributeLabel(attrName: string, expectedText: string) {
|
||||
cy.get(`.pf-v5-c-form__label[for='${attrName}'] .pf-v5-c-form__label-text`)
|
||||
.contains(expectedText)
|
||||
|
@ -193,9 +150,7 @@ export default class CreateUserPage {
|
|||
}
|
||||
|
||||
setAttributeValueOnSelect(attrName: string, value: string) {
|
||||
this.#withSelectExpanded(attrName, () => {
|
||||
this.#getSelectOptions(attrName).contains(value).click();
|
||||
});
|
||||
Select.selectItem(this.#getSelectFieldButton(attrName), value);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -260,14 +260,16 @@ class AdminClient {
|
|||
await this.#client.users.updateProfile(merge(userProfile, { realm }));
|
||||
}
|
||||
|
||||
async patchUserProfile(realm: string, payload: UserProfileConfig) {
|
||||
async addGroupToProfile(realm: string, groupName: string) {
|
||||
await this.#login();
|
||||
|
||||
const currentProfile = await this.#client.users.getProfile({ realm });
|
||||
|
||||
await this.#client.users.updateProfile(
|
||||
merge(currentProfile, payload, { realm }),
|
||||
);
|
||||
await this.#client.users.updateProfile({
|
||||
...currentProfile,
|
||||
realm,
|
||||
...{ groups: [...currentProfile.groups!, { name: groupName }] },
|
||||
});
|
||||
}
|
||||
|
||||
async createRealmRole(payload: RoleRepresentation) {
|
||||
|
|
|
@ -36,7 +36,9 @@ export const ValueSelect = ({
|
|||
placeholderText={t("valuePlaceholder")}
|
||||
>
|
||||
{defaultItem.values.map((item) => (
|
||||
<SelectOption key={item} value={item} />
|
||||
<SelectOption key={item} value={item}>
|
||||
{item}
|
||||
</SelectOption>
|
||||
))}
|
||||
</KeycloakSelect>
|
||||
) : (
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { Select, SelectOption } from "@patternfly/react-core/deprecated";
|
||||
import {
|
||||
Chip,
|
||||
ChipGroup,
|
||||
MenuToggle,
|
||||
Select,
|
||||
SelectList,
|
||||
SelectOption,
|
||||
} from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import { Controller, ControllerRenderProps } from "react-hook-form";
|
||||
import {
|
||||
|
@ -7,12 +14,11 @@ import {
|
|||
UserProfileFieldProps,
|
||||
} from "./UserProfileFields";
|
||||
import { UserProfileGroup } from "./UserProfileGroup";
|
||||
import { UserFormFields, fieldName, isRequiredAttribute, label } from "./utils";
|
||||
import { UserFormFields, fieldName, label } from "./utils";
|
||||
|
||||
export const SelectComponent = (props: UserProfileFieldProps) => {
|
||||
const { t, form, inputType, attribute } = props;
|
||||
const [open, setOpen] = useState(false);
|
||||
const isRequired = isRequiredAttribute(attribute);
|
||||
const isMultiValue = inputType === "multiselect";
|
||||
|
||||
const setValue = (
|
||||
|
@ -23,7 +29,11 @@ export const SelectComponent = (props: UserProfileFieldProps) => {
|
|||
if (field.value.includes(value)) {
|
||||
field.onChange(field.value.filter((item: string) => item !== value));
|
||||
} else {
|
||||
field.onChange([...field.value, value]);
|
||||
if (Array.isArray(field.value)) {
|
||||
field.onChange([...field.value, value]);
|
||||
} else {
|
||||
field.onChange([value]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
field.onChange(value);
|
||||
|
@ -46,39 +56,56 @@ export const SelectComponent = (props: UserProfileFieldProps) => {
|
|||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId={attribute.name}
|
||||
onToggle={(_event, b) => setOpen(b)}
|
||||
toggle={(ref) => (
|
||||
<MenuToggle
|
||||
id={attribute.name}
|
||||
ref={ref}
|
||||
onClick={() => setOpen(!open)}
|
||||
isExpanded={open}
|
||||
isFullWidth
|
||||
isDisabled={attribute.readOnly}
|
||||
>
|
||||
{(isMultiValue && Array.isArray(field.value) ? (
|
||||
<ChipGroup>
|
||||
{field.value.map((selection, index: number) => (
|
||||
<Chip
|
||||
key={index}
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
setValue(selection, field);
|
||||
}}
|
||||
>
|
||||
{selection}
|
||||
</Chip>
|
||||
))}
|
||||
</ChipGroup>
|
||||
) : (
|
||||
fetchLabel(field.value)
|
||||
)) || t("choose")}
|
||||
</MenuToggle>
|
||||
)}
|
||||
onSelect={(_, value) => {
|
||||
const option = value.toString();
|
||||
const option = value?.toString() || "";
|
||||
setValue(option, field);
|
||||
if (!Array.isArray(field.value)) {
|
||||
if (!isMultiValue) {
|
||||
setOpen(false);
|
||||
}
|
||||
}}
|
||||
selections={
|
||||
field.value ? field.value : isMultiValue ? [] : t("choose")
|
||||
}
|
||||
variant={
|
||||
isMultiValue
|
||||
? "typeaheadmulti"
|
||||
: options.length >= 10
|
||||
? "typeahead"
|
||||
: "single"
|
||||
}
|
||||
selected={field.value}
|
||||
aria-label={t("selectOne")}
|
||||
isOpen={open}
|
||||
isDisabled={attribute.readOnly}
|
||||
required={isRequired}
|
||||
>
|
||||
{["", ...options].map((option) => (
|
||||
<SelectOption
|
||||
selected={field.value === option}
|
||||
key={option}
|
||||
value={option}
|
||||
>
|
||||
{option ? fetchLabel(option) : t("choose")}
|
||||
</SelectOption>
|
||||
))}
|
||||
<SelectList>
|
||||
{options.map((option) => (
|
||||
<SelectOption
|
||||
selected={field.value === option}
|
||||
key={option}
|
||||
value={option}
|
||||
>
|
||||
{fetchLabel(option)}
|
||||
</SelectOption>
|
||||
))}
|
||||
</SelectList>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -192,7 +192,8 @@ const FormField = ({
|
|||
const inputType = useMemo(() => determineInputType(attribute), [attribute]);
|
||||
|
||||
const Component =
|
||||
attribute.multivalued || isMultiValue(value)
|
||||
attribute.multivalued ||
|
||||
(isMultiValue(value) && attribute.annotations?.inputType === undefined)
|
||||
? FIELDS["multi-input"]
|
||||
: FIELDS[inputType];
|
||||
|
||||
|
|
Loading…
Reference in a new issue