Fix problems with key value input and empty entries (#19421)
This commit is contained in:
parent
9f15cf432b
commit
84e763b472
16 changed files with 215 additions and 150 deletions
|
@ -542,7 +542,8 @@ describe("Clients test", () => {
|
||||||
it("Should delete attribute from client role", () => {
|
it("Should delete attribute from client role", () => {
|
||||||
commonPage.tableUtils().clickRowItemLink(itemId);
|
commonPage.tableUtils().clickRowItemLink(itemId);
|
||||||
rolesTab.goToAttributesTab();
|
rolesTab.goToAttributesTab();
|
||||||
attributesTab.deleteAttribute(1);
|
attributesTab.deleteAttribute(0);
|
||||||
|
attributesTab.assertEmpty();
|
||||||
commonPage
|
commonPage
|
||||||
.masthead()
|
.masthead()
|
||||||
.checkNotificationMessage("The role has been saved", true);
|
.checkNotificationMessage("The role has been saved", true);
|
||||||
|
|
|
@ -420,7 +420,8 @@ describe("Group test", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Remove attribute", () => {
|
it("Remove attribute", () => {
|
||||||
attributesTab.deleteAttribute(1).assertRowItemsEqualTo(1);
|
attributesTab.deleteAttribute(0);
|
||||||
|
attributesTab.assertEmpty();
|
||||||
groupPage.assertNotificationGroupUpdated();
|
groupPage.assertNotificationGroupUpdated();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -429,7 +430,7 @@ describe("Group test", () => {
|
||||||
.addAttribute("key", "value")
|
.addAttribute("key", "value")
|
||||||
.addAnAttributeButton()
|
.addAnAttributeButton()
|
||||||
.revert()
|
.revert()
|
||||||
.assertRowItemsEqualTo(1);
|
.assertEmpty();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -248,10 +248,10 @@ describe("Realm roles test", () => {
|
||||||
listingPage.itemExist(editRoleName).goToItemDetails(editRoleName);
|
listingPage.itemExist(editRoleName).goToItemDetails(editRoleName);
|
||||||
|
|
||||||
createRealmRolePage.goToAttributesTab();
|
createRealmRolePage.goToAttributesTab();
|
||||||
keyValue.fillKeyValue({ key: "one", value: "1" }).validateRows(2);
|
keyValue.fillKeyValue({ key: "one", value: "1" }).validateRows(1);
|
||||||
keyValue.save();
|
keyValue.save();
|
||||||
masthead.checkNotificationMessage("The role has been saved", true);
|
masthead.checkNotificationMessage("The role has been saved", true);
|
||||||
keyValue.validateRows(2);
|
keyValue.validateRows(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should add attribute multiple", () => {
|
it("should add attribute multiple", () => {
|
||||||
|
@ -259,17 +259,17 @@ describe("Realm roles test", () => {
|
||||||
|
|
||||||
createRealmRolePage.goToAttributesTab();
|
createRealmRolePage.goToAttributesTab();
|
||||||
keyValue
|
keyValue
|
||||||
.fillKeyValue({ key: "two", value: "2" }, 1)
|
.fillKeyValue({ key: "two", value: "2" })
|
||||||
.fillKeyValue({ key: "three", value: "3" }, 2)
|
.fillKeyValue({ key: "three", value: "3" })
|
||||||
.save()
|
.save()
|
||||||
.validateRows(4);
|
.validateRows(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should delete attribute", () => {
|
it("should delete attribute", () => {
|
||||||
listingPage.itemExist(editRoleName).goToItemDetails(editRoleName);
|
listingPage.itemExist(editRoleName).goToItemDetails(editRoleName);
|
||||||
createRealmRolePage.goToAttributesTab();
|
createRealmRolePage.goToAttributesTab();
|
||||||
|
|
||||||
keyValue.deleteRow(1).save().validateRows(3);
|
keyValue.deleteRow(1).save().validateRows(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,10 @@ export default class AttributesTab {
|
||||||
private saveAttributeBtn = "save-attributes";
|
private saveAttributeBtn = "save-attributes";
|
||||||
private addAttributeBtn = "attributes-add-row";
|
private addAttributeBtn = "attributes-add-row";
|
||||||
private attributesTab = "attributes";
|
private attributesTab = "attributes";
|
||||||
private attributeRow = "row";
|
private keyInput = "attributes-key";
|
||||||
private keyInput = (index: number) => `attributes[${index}].key`;
|
private valueInput = "attributes-value";
|
||||||
private valueInput = (index: number) => `attributes[${index}].value`;
|
private removeBtn = "attributes-remove";
|
||||||
|
private emptyState = "attributes-empty-state";
|
||||||
|
|
||||||
public goToAttributesTab() {
|
public goToAttributesTab() {
|
||||||
cy.findByTestId(this.attributesTab).click();
|
cy.findByTestId(this.attributesTab).click();
|
||||||
|
@ -13,14 +14,15 @@ export default class AttributesTab {
|
||||||
}
|
}
|
||||||
|
|
||||||
public addAttribute(key: string, value: string) {
|
public addAttribute(key: string, value: string) {
|
||||||
cy.findAllByTestId(this.attributeRow)
|
cy.findByTestId(this.addAttributeBtn).click();
|
||||||
|
|
||||||
|
cy.findAllByTestId(this.keyInput)
|
||||||
.its("length")
|
.its("length")
|
||||||
.then((index) => {
|
.then((length) => {
|
||||||
cy.findByTestId(this.keyInput(index - 1)).type(key, { force: true });
|
this.keyInputAt(length - 1).type(key, { force: true });
|
||||||
cy.findByTestId(this.valueInput(index - 1)).type(value, {
|
this.valueInputAt(length - 1).type(value, { force: true });
|
||||||
force: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +37,7 @@ export default class AttributesTab {
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteAttributeButton(row: number) {
|
public deleteAttributeButton(row: number) {
|
||||||
cy.findByTestId(`attributes[${row - 1}].remove`).click({ force: true });
|
this.removeButtonAt(row).click({ force: true });
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,19 +50,27 @@ export default class AttributesTab {
|
||||||
this.deleteAttributeButton(rowIndex);
|
this.deleteAttributeButton(rowIndex);
|
||||||
this.save();
|
this.save();
|
||||||
|
|
||||||
cy.findAllByTestId(`attributes[${rowIndex - 1}].key`).should(
|
|
||||||
"have.value",
|
|
||||||
""
|
|
||||||
);
|
|
||||||
cy.findAllByTestId(`attributes[${rowIndex - 1}].value`).should(
|
|
||||||
"have.value",
|
|
||||||
""
|
|
||||||
);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public assertEmpty() {
|
||||||
|
cy.findByTestId(this.emptyState).should("exist");
|
||||||
|
}
|
||||||
|
|
||||||
public assertRowItemsEqualTo(amount: number) {
|
public assertRowItemsEqualTo(amount: number) {
|
||||||
cy.findAllByTestId("row").its("length").should("be.eq", amount);
|
cy.findAllByTestId(this.keyInput).its("length").should("be.eq", amount);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private keyInputAt(index: number) {
|
||||||
|
return cy.findAllByTestId(this.keyInput).eq(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private valueInputAt(index: number) {
|
||||||
|
return cy.findAllByTestId(this.valueInput).eq(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeButtonAt(index: number) {
|
||||||
|
return cy.findAllByTestId(this.removeBtn).eq(index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,22 +7,26 @@ export default class KeyValueInput {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
fillKeyValue({ key, value }: KeyValueType, index = 0) {
|
fillKeyValue({ key, value }: KeyValueType) {
|
||||||
cy.findByTestId(`${this.name}[${index}].key`).clear();
|
|
||||||
cy.findByTestId(`${this.name}[${index}].key`).type(key);
|
|
||||||
cy.findByTestId(`${this.name}[${index}].value`).clear();
|
|
||||||
cy.findByTestId(`${this.name}[${index}].value`).type(value);
|
|
||||||
cy.findByTestId(`${this.name}-add-row`).click();
|
cy.findByTestId(`${this.name}-add-row`).click();
|
||||||
|
|
||||||
|
cy.findAllByTestId(`${this.name}-key`)
|
||||||
|
.its("length")
|
||||||
|
.then((length) => {
|
||||||
|
this.keyInputAt(length - 1).type(key);
|
||||||
|
this.valueInputAt(length - 1).type(value);
|
||||||
|
});
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteRow(index: number) {
|
deleteRow(index: number) {
|
||||||
cy.findByTestId(`${this.name}[${index}].remove`).click();
|
cy.findAllByTestId(`${this.name}-remove`).eq(index).click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
validateRows(numberOfRows: number) {
|
validateRows(numberOfRows: number) {
|
||||||
cy.findAllByTestId("row").should("have.length", numberOfRows);
|
cy.findAllByTestId(`${this.name}-key`).should("have.length", numberOfRows);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,4 +34,12 @@ export default class KeyValueInput {
|
||||||
cy.findByTestId("save-attributes").click();
|
cy.findByTestId("save-attributes").click();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
keyInputAt(index: number) {
|
||||||
|
return cy.findAllByTestId(`${this.name}-key`).eq(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
valueInputAt(index: number) {
|
||||||
|
return cy.findAllByTestId(`${this.name}-value`).eq(index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import type { KeyValueType } from "../../../../../src/components/key-value-form/key-value-convert";
|
||||||
|
|
||||||
|
export default class LegacyKeyValueInput {
|
||||||
|
private name: string;
|
||||||
|
|
||||||
|
constructor(name: string) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
fillKeyValue({ key, value }: KeyValueType, index = 0) {
|
||||||
|
cy.findByTestId(`${this.name}.${index}.key`).clear();
|
||||||
|
cy.findByTestId(`${this.name}.${index}.key`).type(key);
|
||||||
|
cy.findByTestId(`${this.name}.${index}.value`).clear();
|
||||||
|
cy.findByTestId(`${this.name}.${index}.value`).type(value);
|
||||||
|
cy.findByTestId(`${this.name}-add-row`).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRow(index: number) {
|
||||||
|
cy.findByTestId(`${this.name}.${index}.remove`).click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateRows(numberOfRows: number) {
|
||||||
|
cy.findAllByTestId("row").should("have.length", numberOfRows);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
cy.findByTestId("save-attributes").click();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import KeyValueInput from "../KeyValueInput";
|
import LegacyKeyValueInput from "../LegacyKeyValueInput";
|
||||||
|
|
||||||
export default class AddMapperPage {
|
export default class AddMapperPage {
|
||||||
private mappersTab = "mappers-tab";
|
private mappersTab = "mappers-tab";
|
||||||
|
@ -23,8 +23,8 @@ export default class AddMapperPage {
|
||||||
private mappersUrl = "/oidc/mappers";
|
private mappersUrl = "/oidc/mappers";
|
||||||
private regexAttributeValuesSwitch = "are.attribute.values.regex";
|
private regexAttributeValuesSwitch = "are.attribute.values.regex";
|
||||||
private syncmodeSelectToggle = "#syncMode";
|
private syncmodeSelectToggle = "#syncMode";
|
||||||
private attributesKeyInput = '[data-testid="config.attributes[0].key"]';
|
private attributesKeyInput = '[data-testid="config.attributes.0.key"]';
|
||||||
private attributesValueInput = '[data-testid="config.attributes[0].value"]';
|
private attributesValueInput = '[data-testid="config.attributes.0.value"]';
|
||||||
private template = "template";
|
private template = "template";
|
||||||
private target = "#target";
|
private target = "#target";
|
||||||
|
|
||||||
|
@ -392,7 +392,7 @@ export default class AddMapperPage {
|
||||||
|
|
||||||
cy.findByTestId(this.idpMapperSelect).contains("Claim to Role").click();
|
cy.findByTestId(this.idpMapperSelect).contains("Claim to Role").click();
|
||||||
|
|
||||||
const keyValue = new KeyValueInput("config.claims");
|
const keyValue = new LegacyKeyValueInput("config.claims");
|
||||||
|
|
||||||
keyValue.fillKeyValue({ key: "key", value: "value" });
|
keyValue.fillKeyValue({ key: "key", value: "value" });
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,9 @@ export default class UserProfile {
|
||||||
private newAttributeRequiredFor = 'input[name="roles"]';
|
private newAttributeRequiredFor = 'input[name="roles"]';
|
||||||
private newAttributeRequiredWhen = 'input[name="requiredWhen"]';
|
private newAttributeRequiredWhen = 'input[name="requiredWhen"]';
|
||||||
private newAttributeEmptyValidators = ".kc-emptyValidators";
|
private newAttributeEmptyValidators = ".kc-emptyValidators";
|
||||||
private newAttributeAnnotationKey = 'input[name="annotations[0].key"]';
|
private newAttributeAnnotationBtn = "annotations-add-row";
|
||||||
private newAttributeAnnotationValue = 'input[name="annotations[0].value"]';
|
private newAttributeAnnotationKey = "annotations-key";
|
||||||
|
private newAttributeAnnotationValue = "annotations-value";
|
||||||
private validatorRolesList = "#validator";
|
private validatorRolesList = "#validator";
|
||||||
private validatorsList = 'tbody [data-label="name"]';
|
private validatorsList = 'tbody [data-label="name"]';
|
||||||
private saveNewAttributeBtn = "attribute-create";
|
private saveNewAttributeBtn = "attribute-create";
|
||||||
|
@ -106,8 +107,9 @@ export default class UserProfile {
|
||||||
cy.get(this.newAttributeRequiredFor).first().check({ force: true });
|
cy.get(this.newAttributeRequiredFor).first().check({ force: true });
|
||||||
cy.get(this.newAttributeRequiredWhen).first().check();
|
cy.get(this.newAttributeRequiredWhen).first().check();
|
||||||
cy.get(this.newAttributeEmptyValidators).contains("No validators.");
|
cy.get(this.newAttributeEmptyValidators).contains("No validators.");
|
||||||
cy.get(this.newAttributeAnnotationKey).type("test");
|
cy.findByTestId(this.newAttributeAnnotationBtn).click();
|
||||||
cy.get(this.newAttributeAnnotationValue).type("123");
|
cy.findByTestId(this.newAttributeAnnotationKey).type("test");
|
||||||
|
cy.findByTestId(this.newAttributeAnnotationValue).type("123");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -172,10 +172,13 @@
|
||||||
"years": "Years"
|
"years": "Years"
|
||||||
},
|
},
|
||||||
"attributes": "Attributes",
|
"attributes": "Attributes",
|
||||||
|
"missingAttributes": "No attributes have been defined yet. Click the below button to add attributes, key and value are required for a key pair.",
|
||||||
"addAttribute": "Add an attribute",
|
"addAttribute": "Add an attribute",
|
||||||
"removeAttribute": "Remove attribute",
|
"removeAttribute": "Remove attribute",
|
||||||
"keyPlaceholder": "Type a key",
|
"keyPlaceholder": "Type a key",
|
||||||
"valuePlaceholder": "Type a value",
|
"valuePlaceholder": "Type a value",
|
||||||
|
"keyError": "A key must be provided.",
|
||||||
|
"valueError": "A value must be provided.",
|
||||||
"credentials": "Credentials",
|
"credentials": "Credentials",
|
||||||
"clientId": "Client ID",
|
"clientId": "Client ID",
|
||||||
"clientName": "Name",
|
"clientName": "Name",
|
||||||
|
|
|
@ -103,14 +103,14 @@ const ValueInput = ({
|
||||||
<Td>
|
<Td>
|
||||||
{resources || attributeValues?.length ? (
|
{resources || attributeValues?.length ? (
|
||||||
<Controller
|
<Controller
|
||||||
name={`${name}[${rowIndex}].value`}
|
name={`${name}.${rowIndex}.value`}
|
||||||
defaultValue={[]}
|
defaultValue={[]}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Select
|
<Select
|
||||||
toggleId={`${attribute.id}-value`}
|
toggleId={`${attribute.id}-value`}
|
||||||
className="kc-attribute-value-selectable"
|
className="kc-attribute-value-selectable"
|
||||||
name={`${name}[${rowIndex}].value`}
|
name={`${name}.${rowIndex}.value`}
|
||||||
chipGroupProps={{
|
chipGroupProps={{
|
||||||
numChips: 1,
|
numChips: 1,
|
||||||
expandedText: t("common:hide"),
|
expandedText: t("common:hide"),
|
||||||
|
@ -171,7 +171,7 @@ export const KeyBasedAttributeInput = ({
|
||||||
}
|
}
|
||||||
}, [fields]);
|
}, [fields]);
|
||||||
|
|
||||||
const watchLastValue = watch(`${name}[${fields.length - 1}].value`, "");
|
const watchLastValue = watch(`${name}.${fields.length - 1}.value`, "");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableComposable
|
<TableComposable
|
||||||
|
@ -190,14 +190,14 @@ export const KeyBasedAttributeInput = ({
|
||||||
<Tr key={attribute.id} data-testid="attribute-row">
|
<Tr key={attribute.id} data-testid="attribute-row">
|
||||||
<Td>
|
<Td>
|
||||||
<Controller
|
<Controller
|
||||||
name={`${name}[${rowIndex}].key`}
|
name={`${name}.${rowIndex}.key`}
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Select
|
<Select
|
||||||
toggleId={`${name}[${rowIndex}].key`}
|
toggleId={`${name}.${rowIndex}.key`}
|
||||||
className="kc-attribute-key-selectable"
|
className="kc-attribute-key-selectable"
|
||||||
name={`${name}[${rowIndex}].key`}
|
name={`${name}.${rowIndex}.key`}
|
||||||
onToggle={(open) => toggleKeySelect(rowIndex, open)}
|
onToggle={(open) => toggleKeySelect(rowIndex, open)}
|
||||||
isOpen={isKeyOpenArray[rowIndex]}
|
isOpen={isKeyOpenArray[rowIndex]}
|
||||||
variant={SelectVariant.typeahead}
|
variant={SelectVariant.typeahead}
|
||||||
|
|
|
@ -84,11 +84,11 @@ export const MapComponent = ({ name, label, helpText }: ComponentProps) => {
|
||||||
<Flex key={attribute.id} data-testid="row">
|
<Flex key={attribute.id} data-testid="row">
|
||||||
<FlexItem grow={{ default: "grow" }}>
|
<FlexItem grow={{ default: "grow" }}>
|
||||||
<TextInput
|
<TextInput
|
||||||
name={`${fieldName}[${index}].key`}
|
name={`${fieldName}.${index}.key`}
|
||||||
placeholder={t("common:keyPlaceholder")}
|
placeholder={t("common:keyPlaceholder")}
|
||||||
aria-label={t("key")}
|
aria-label={t("key")}
|
||||||
defaultValue={attribute.key}
|
defaultValue={attribute.key}
|
||||||
data-testid={`${fieldName}[${index}].key`}
|
data-testid={`${fieldName}.${index}.key`}
|
||||||
onChange={(value) => updateKey(index, value)}
|
onChange={(value) => updateKey(index, value)}
|
||||||
onBlur={() => update()}
|
onBlur={() => update()}
|
||||||
/>
|
/>
|
||||||
|
@ -98,11 +98,11 @@ export const MapComponent = ({ name, label, helpText }: ComponentProps) => {
|
||||||
spacer={{ default: "spacerNone" }}
|
spacer={{ default: "spacerNone" }}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
name={`${fieldName}[${index}].value`}
|
name={`${fieldName}.${index}.value`}
|
||||||
placeholder={t("common:valuePlaceholder")}
|
placeholder={t("common:valuePlaceholder")}
|
||||||
aria-label={t("common:value")}
|
aria-label={t("common:value")}
|
||||||
defaultValue={attribute.value}
|
defaultValue={attribute.value}
|
||||||
data-testid={`${fieldName}[${index}].value`}
|
data-testid={`${fieldName}.${index}.value`}
|
||||||
onChange={(value) => updateValue(index, value)}
|
onChange={(value) => updateValue(index, value)}
|
||||||
onBlur={() => update()}
|
onBlur={() => update()}
|
||||||
/>
|
/>
|
||||||
|
@ -113,7 +113,7 @@ export const MapComponent = ({ name, label, helpText }: ComponentProps) => {
|
||||||
title={t("common:removeAttribute")}
|
title={t("common:removeAttribute")}
|
||||||
isDisabled={map.length === 1}
|
isDisabled={map.length === 1}
|
||||||
onClick={() => remove(index)}
|
onClick={() => remove(index)}
|
||||||
data-testid={`${fieldName}[${index}].remove`}
|
data-testid={`${fieldName}.${index}.remove`}
|
||||||
>
|
>
|
||||||
<MinusCircleIcon />
|
<MinusCircleIcon />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -2,16 +2,20 @@ import {
|
||||||
ActionList,
|
ActionList,
|
||||||
ActionListItem,
|
ActionListItem,
|
||||||
Button,
|
Button,
|
||||||
Flex,
|
EmptyState,
|
||||||
FlexItem,
|
EmptyStateBody,
|
||||||
|
Grid,
|
||||||
|
GridItem,
|
||||||
|
HelperText,
|
||||||
|
HelperTextItem,
|
||||||
|
InputGroup,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
|
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
|
||||||
import { useEffect } from "react";
|
import { Fragment } from "react";
|
||||||
import { useFieldArray, useFormContext, useWatch } from "react-hook-form";
|
import { useFieldArray, useFormContext } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { KeycloakTextInput } from "../keycloak-text-input/KeycloakTextInput";
|
import { KeycloakTextInput } from "../keycloak-text-input/KeycloakTextInput";
|
||||||
import { KeyValueType } from "./key-value-convert";
|
|
||||||
|
|
||||||
type KeyValueInputProps = {
|
type KeyValueInputProps = {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -19,83 +23,82 @@ type KeyValueInputProps = {
|
||||||
|
|
||||||
export const KeyValueInput = ({ name }: KeyValueInputProps) => {
|
export const KeyValueInput = ({ name }: KeyValueInputProps) => {
|
||||||
const { t } = useTranslation("common");
|
const { t } = useTranslation("common");
|
||||||
const { control, register } = useFormContext();
|
const {
|
||||||
|
control,
|
||||||
|
register,
|
||||||
|
formState: { errors },
|
||||||
|
} = useFormContext();
|
||||||
|
|
||||||
const { fields, append, remove } = useFieldArray({
|
const { fields, append, remove } = useFieldArray({
|
||||||
control,
|
control,
|
||||||
name,
|
name,
|
||||||
});
|
});
|
||||||
|
|
||||||
const watchFields = useWatch({
|
const appendNew = () => append({ key: "", value: "" });
|
||||||
control,
|
|
||||||
name,
|
|
||||||
defaultValue: [{ key: "", value: "" }],
|
|
||||||
});
|
|
||||||
|
|
||||||
const isValid =
|
return fields.length > 0 ? (
|
||||||
Array.isArray(watchFields) &&
|
<>
|
||||||
watchFields.every(
|
<Grid hasGutter>
|
||||||
({ key, value }: KeyValueType) =>
|
<GridItem className="pf-c-form__label" span={6}>
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
<span className="pf-c-form__label-text">{t("key")}</span>
|
||||||
key?.trim().length !== 0 && value?.trim().length !== 0
|
</GridItem>
|
||||||
);
|
<GridItem className="pf-c-form__label" span={6}>
|
||||||
|
<span className="pf-c-form__label-text">{t("value")}</span>
|
||||||
useEffect(() => {
|
</GridItem>
|
||||||
if (!fields.length) {
|
{fields.map((attribute, index) => {
|
||||||
append({ key: "", value: "" }, { shouldFocus: false });
|
const keyError = !!(errors as any)[name]?.[index]?.key;
|
||||||
}
|
const valueError = !!(errors as any)[name]?.[index]?.value;
|
||||||
}, [fields]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Fragment key={attribute.id}>
|
||||||
<Flex direction={{ default: "column" }}>
|
<GridItem span={6}>
|
||||||
<Flex>
|
|
||||||
<FlexItem
|
|
||||||
grow={{ default: "grow" }}
|
|
||||||
spacer={{ default: "spacerNone" }}
|
|
||||||
>
|
|
||||||
<strong>{t("key")}</strong>
|
|
||||||
</FlexItem>
|
|
||||||
<FlexItem grow={{ default: "grow" }}>
|
|
||||||
<strong>{t("value")}</strong>
|
|
||||||
</FlexItem>
|
|
||||||
</Flex>
|
|
||||||
{fields.map((attribute, index) => (
|
|
||||||
<Flex key={attribute.id} data-testid="row">
|
|
||||||
<FlexItem grow={{ default: "grow" }}>
|
|
||||||
<KeycloakTextInput
|
<KeycloakTextInput
|
||||||
placeholder={t("keyPlaceholder")}
|
placeholder={t("keyPlaceholder")}
|
||||||
aria-label={t("key")}
|
aria-label={t("key")}
|
||||||
defaultValue=""
|
data-testid={`${name}-key`}
|
||||||
data-testid={`${name}[${index}].key`}
|
{...register(`${name}.${index}.key`, { required: true })}
|
||||||
{...register(`${name}[${index}].key`)}
|
validated={keyError ? "error" : "default"}
|
||||||
|
isRequired
|
||||||
/>
|
/>
|
||||||
</FlexItem>
|
{keyError && (
|
||||||
<FlexItem
|
<HelperText>
|
||||||
grow={{ default: "grow" }}
|
<HelperTextItem variant="error">
|
||||||
spacer={{ default: "spacerNone" }}
|
{t("keyError")}
|
||||||
>
|
</HelperTextItem>
|
||||||
|
</HelperText>
|
||||||
|
)}
|
||||||
|
</GridItem>
|
||||||
|
<GridItem span={6}>
|
||||||
|
<InputGroup>
|
||||||
<KeycloakTextInput
|
<KeycloakTextInput
|
||||||
placeholder={t("valuePlaceholder")}
|
placeholder={t("valuePlaceholder")}
|
||||||
aria-label={t("value")}
|
aria-label={t("value")}
|
||||||
defaultValue=""
|
data-testid={`${name}-value`}
|
||||||
data-testid={`${name}[${index}].value`}
|
{...register(`${name}.${index}.value`, { required: true })}
|
||||||
{...register(`${name}[${index}].value`)}
|
validated={valueError ? "error" : "default"}
|
||||||
|
isRequired
|
||||||
/>
|
/>
|
||||||
</FlexItem>
|
|
||||||
<FlexItem>
|
|
||||||
<Button
|
<Button
|
||||||
variant="link"
|
variant="link"
|
||||||
title={t("removeAttribute")}
|
title={t("removeAttribute")}
|
||||||
isDisabled={watchFields.length === 1}
|
|
||||||
onClick={() => remove(index)}
|
onClick={() => remove(index)}
|
||||||
data-testid={`${name}[${index}].remove`}
|
data-testid={`${name}-remove`}
|
||||||
>
|
>
|
||||||
<MinusCircleIcon />
|
<MinusCircleIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</FlexItem>
|
</InputGroup>
|
||||||
</Flex>
|
{valueError && (
|
||||||
))}
|
<HelperText>
|
||||||
</Flex>
|
<HelperTextItem variant="error">
|
||||||
|
{t("valueError")}
|
||||||
|
</HelperTextItem>
|
||||||
|
</HelperText>
|
||||||
|
)}
|
||||||
|
</GridItem>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
<ActionList>
|
<ActionList>
|
||||||
<ActionListItem>
|
<ActionListItem>
|
||||||
<Button
|
<Button
|
||||||
|
@ -103,13 +106,29 @@ export const KeyValueInput = ({ name }: KeyValueInputProps) => {
|
||||||
className="pf-u-px-0 pf-u-mt-sm"
|
className="pf-u-px-0 pf-u-mt-sm"
|
||||||
variant="link"
|
variant="link"
|
||||||
icon={<PlusCircleIcon />}
|
icon={<PlusCircleIcon />}
|
||||||
isDisabled={!isValid}
|
onClick={appendNew}
|
||||||
onClick={() => append({ key: "", value: "" })}
|
|
||||||
>
|
>
|
||||||
{t("addAttribute")}
|
{t("addAttribute")}
|
||||||
</Button>
|
</Button>
|
||||||
</ActionListItem>
|
</ActionListItem>
|
||||||
</ActionList>
|
</ActionList>
|
||||||
</>
|
</>
|
||||||
|
) : (
|
||||||
|
<EmptyState
|
||||||
|
data-testid={`${name}-empty-state`}
|
||||||
|
className="pf-u-p-0"
|
||||||
|
variant="xs"
|
||||||
|
>
|
||||||
|
<EmptyStateBody>{t("missingAttributes")}</EmptyStateBody>
|
||||||
|
<Button
|
||||||
|
data-testid={`${name}-add-row`}
|
||||||
|
variant="link"
|
||||||
|
icon={<PlusCircleIcon />}
|
||||||
|
isSmall
|
||||||
|
onClick={appendNew}
|
||||||
|
>
|
||||||
|
{t("addAttribute")}
|
||||||
|
</Button>
|
||||||
|
</EmptyState>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -41,18 +41,6 @@ describe("Tests the convert functions for attribute input", () => {
|
||||||
expect(result).toEqual({ theKey: ["theValue"] });
|
expect(result).toEqual({ theKey: ["theValue"] });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("convert empty object to attributes", () => {
|
|
||||||
const given: {
|
|
||||||
[key: string]: string[];
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
//when
|
|
||||||
const result = arrayToKeyValue(given);
|
|
||||||
|
|
||||||
//then
|
|
||||||
expect(result).toEqual([{ key: "", value: "" }]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("convert object to attributes", () => {
|
it("convert object to attributes", () => {
|
||||||
const given = { one: ["1"], two: ["2"] };
|
const given = { one: ["1"], two: ["2"] };
|
||||||
|
|
||||||
|
@ -63,7 +51,6 @@ describe("Tests the convert functions for attribute input", () => {
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
{ key: "one", value: "1" },
|
{ key: "one", value: "1" },
|
||||||
{ key: "two", value: "2" },
|
{ key: "two", value: "2" },
|
||||||
{ key: "", value: "" },
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -22,5 +22,5 @@ export function arrayToKeyValue<T>(attributes: Record<string, string[]> = {}) {
|
||||||
value.map<KeyValueType>((value) => ({ key, value }))
|
value.map<KeyValueType>((value) => ({ key, value }))
|
||||||
);
|
);
|
||||||
|
|
||||||
return result.concat({ key: "", value: "" }) as PathValue<T, Path<T>>;
|
return result as PathValue<T, Path<T>>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ export const MultiLineInput = ({
|
||||||
<TextInput
|
<TextInput
|
||||||
data-testid={name + index}
|
data-testid={name + index}
|
||||||
onChange={(value) => updateValue(index, value)}
|
onChange={(value) => updateValue(index, value)}
|
||||||
name={`${name}[${index}].value`}
|
name={`${name}.${index}.value`}
|
||||||
value={value}
|
value={value}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
|
|
@ -26,10 +26,7 @@ describe("Tests the form convert util functions", () => {
|
||||||
expect(values).toEqual({
|
expect(values).toEqual({
|
||||||
name: "client",
|
name: "client",
|
||||||
other: { one: "1", two: "2" },
|
other: { one: "1", two: "2" },
|
||||||
attributes: [
|
attributes: [{ key: "one", value: "1" }],
|
||||||
{ key: "one", value: "1" },
|
|
||||||
{ key: "", value: "" },
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue