diff --git a/frontend/components.d.ts b/frontend/components.d.ts
index 5c07f309ea..1c8c549713 100644
--- a/frontend/components.d.ts
+++ b/frontend/components.d.ts
@@ -9,11 +9,11 @@ declare module 'vue' {
export interface GlobalComponents {
AddressGroupComponent: typeof import('./src/components/grouping/AddressGroupComponent.vue')['default']
AutoCompleteInputComponent: typeof import('./src/components/forms/AutoCompleteInputComponent.vue')['default']
+ ComboBoxInputComponent: typeof import('./src/components/forms/ComboBoxInputComponent.vue')['default']
ContactGroupComponent: typeof import('./src/components/grouping/ContactGroupComponent.vue')['default']
DataFetcher: typeof import('./src/components/DataFetcher.vue')['default']
DateInputComponent: typeof import('./src/components/forms/DateInputComponent/index.vue')['default']
DateInputPart: typeof import('./src/components/forms/DateInputComponent/DateInputPart.vue')['default']
- DropdownInputComponent: typeof import('./src/components/forms/DropdownInputComponent.vue')['default']
ErrorNotificationGroupingComponent: typeof import('./src/components/grouping/ErrorNotificationGroupingComponent.vue')['default']
FuzzyMatchNotificationGroupingComponent: typeof import('./src/components/grouping/FuzzyMatchNotificationGroupingComponent.vue')['default']
LoadingOverlayComponent: typeof import('./src/components/LoadingOverlayComponent.vue')['default']
diff --git a/frontend/src/components/forms/DropdownInputComponent.vue b/frontend/src/components/forms/ComboBoxInputComponent.vue
similarity index 100%
rename from frontend/src/components/forms/DropdownInputComponent.vue
rename to frontend/src/components/forms/ComboBoxInputComponent.vue
diff --git a/frontend/src/components/grouping/AddressGroupComponent.vue b/frontend/src/components/grouping/AddressGroupComponent.vue
index 2db5110765..e110db652b 100644
--- a/frontend/src/components/grouping/AddressGroupComponent.vue
+++ b/frontend/src/components/grouping/AddressGroupComponent.vue
@@ -320,7 +320,7 @@ const section = (index: number, purpose: string) => `section-address-${index} ${
:params="{ method: 'GET' }"
#="{ content }"
>
- `section-address-${index} ${
/>
- {
@error="validation.phoneNumber = !$event"
/>
- {
/>
-
:params="{ method: 'GET' }"
#="{ content }"
>
-
/>
- this.loadUser(), timeDifference);
+ if (!this.sessionRefreshIntervalId) {
+ this.sessionRefreshIntervalId = setInterval(
+ () => this.loadUser(),
+ timeDifference
+ );
}
} else {
this.user = undefined;
@@ -125,7 +128,7 @@ class ForestClientUserSession implements SessionProperties {
additionalInfo.lastName = payload.family_name;
}
- if(payload["custom:idp_business_name"]){
+ if (payload["custom:idp_business_name"]) {
additionalInfo.businessName = payload["custom:idp_business_name"];
}
@@ -134,12 +137,18 @@ class ForestClientUserSession implements SessionProperties {
(additionalInfo.firstName === "" && additionalInfo.lastName === "")
) {
const name = payload["custom:idp_display_name"];
- const nameParts: string[] = name.includes(",") ? name.split(",") : name.split(" ");
+ const nameParts: string[] = name.includes(",")
+ ? name.split(",")
+ : name.split(" ");
if (provider === "idir" && nameParts.length >= 2) {
// For IDIR, split by comma and then by space for the first name as the value will be Lastname, Firsname MIN:XX
additionalInfo.lastName = nameParts[0].trim();
- additionalInfo.firstName = nameParts[1].trim().split(" ").slice(0, -1).join(" ")
+ additionalInfo.firstName = nameParts[1]
+ .trim()
+ .split(" ")
+ .slice(0, -1)
+ .join(" ");
} else if (nameParts.length >= 2) {
// For others, assume space separates the first and last names
additionalInfo.firstName = nameParts[0].trim();
diff --git a/frontend/src/helpers/validators/GlobalValidators.ts b/frontend/src/helpers/validators/GlobalValidators.ts
index 723dce5326..f3d9c966b8 100644
--- a/frontend/src/helpers/validators/GlobalValidators.ts
+++ b/frontend/src/helpers/validators/GlobalValidators.ts
@@ -39,7 +39,8 @@ const errorBus = useEventBus(
export const isNotEmpty =
(message: string = "This field is required") =>
(value: string): string => {
- if (value && (typeof value === "string") && value.trim().length > 0) return "";
+ if (value && typeof value === "string" && value.trim().length > 0)
+ return "";
return message;
};
@@ -191,7 +192,7 @@ export const isExactSize =
(message: string = "This field must have the defined size") =>
(size: number) => {
return (value: string): string => {
- if (isNotEmpty(message)(value) === "" && value.length == size)
+ if (isNotEmpty(message)(value) === "" && value.length == size)
return "";
return message;
};
@@ -278,7 +279,10 @@ export const isNoSpecialCharacters =
};
export const isIdCharacters =
- (field: string = "field", message: string = `The ${field} can only contain: A-Z or 0-9`) =>
+ (
+ field: string = "field",
+ message: string = `The ${field} can only contain: A-Z or 0-9`
+ ) =>
(value: string): string => {
if (idCharacters.test(value)) return "";
return message;
@@ -287,7 +291,7 @@ export const isIdCharacters =
export const hasOnlyNamingCharacters =
(
field: string = "field",
- message: string = `The ${field} can only contain: A-Z, a-z, 0-9, space, apostrophe or hyphen`,
+ message: string = `The ${field} can only contain: A-Z, a-z, 0-9, space, apostrophe or hyphen`
) =>
(value: string): string => {
if (nameRegex.test(value)) return "";
@@ -297,7 +301,7 @@ export const hasOnlyNamingCharacters =
export const isAscii =
(
field: string = "field",
- message: string = `The ${field} can only contain: A-Z, a-z, 0-9, space or common symbols`,
+ message: string = `The ${field} can only contain: A-Z, a-z, 0-9, space or common symbols`
) =>
(value: string): string => {
if (ascii.test(value)) return "";
@@ -307,7 +311,7 @@ export const isAscii =
export const isAsciiLineBreak =
(
field: string = "field",
- message: string = `The ${field} can only contain: A-Z, a-z, 0-9, space, line break or common symbols`,
+ message: string = `The ${field} can only contain: A-Z, a-z, 0-9, space, line break or common symbols`
) =>
(value: string): string => {
if (asciiLineBreak.test(value)) return "";
@@ -433,7 +437,7 @@ export const isDateInThePast = (message: string) => (value: string) => {
export const isRegex =
(
regex: RegExp,
- message: string = `This field must conform to the following regular expression: ${regex}`,
+ message: string = `This field must conform to the following regular expression: ${regex}`
) =>
(value: string): string => {
if (regex.test(value)) return "";
@@ -447,7 +451,10 @@ export const isRegex =
* @returns A function that accepts a validator and applies it to the portion of the string obtained by applying the selector.
*/
export const validateSelection =
- (selector: (value: string) => string, selectorErrorMessage = "Value could not be validated") =>
+ (
+ selector: (value: string) => string,
+ selectorErrorMessage = "Value could not be validated"
+ ) =>
(validator: (value: string) => string) =>
(value: string): string => {
try {
@@ -466,17 +473,30 @@ export const validateSelection =
*/
export const optional =
(validator: (value: string) => string) =>
- (value: string): string => {
- return value === "" || value === null || value === undefined ? "" : validator(value);
+ (value: string): string => {
+ return value === "" || value === null || value === undefined
+ ? ""
+ : validator(value);
};
-export const isMinSizeMsg = (fieldName: string, minSize: number): ((value: string) => string) =>
- isMinSize(`The ${fieldName} must contain at least ${minSize} characters`)(minSize);
-
-export const isMaxSizeMsg = (fieldName: string, maxSize: number): ((value: string) => string) =>
+export const isMinSizeMsg = (
+ fieldName: string,
+ minSize: number
+): ((value: string) => string) =>
+ isMinSize(`The ${fieldName} must contain at least ${minSize} characters`)(
+ minSize
+ );
+
+export const isMaxSizeMsg = (
+ fieldName: string,
+ maxSize: number
+): ((value: string) => string) =>
isMaxSize(`The ${fieldName} has a ${maxSize} character limit`)(maxSize);
-export const isExactSizMsg = (fieldName: string, size: number): ((value: string) => string) =>
+export const isExactSizMsg = (
+ fieldName: string,
+ size: number
+): ((value: string) => string) =>
isExactSize(`The ${fieldName} must contain ${size} characters`)(size);
/**
@@ -515,31 +535,38 @@ export const getFieldValue = (path: string, value: any): string | string[] => {
return temporaryValue;
};
-
/**
* Parses the aggregator condition and returns an object containing the parsed values.
* @param conditional - The aggregator condition to parse.
* @returns An object with the parsed values: value1, operator, and value2.
* @throws {Error} If the condition format is invalid.
*/
-const parseAggregatorCondition = (conditinal: string) : { value1: string, operator: string, value2: string } =>{
-
- if (conditinal.includes("&&") || conditinal.includes("||")) {
- const regex = /(.+?)\s*(&&|\|\|)\s*(.+?)$/;
- const match = conditinal.replace("(","").replace(")","").match(regex);
- if (match) {
- return {
- value1: match[1],
- operator: match[2],
- value2: match[3]
- };
- } else {
- throw new Error("Invalid condition format it should be just string or string && string or string || string -> " + conditinal);
- }
- }
+const parseAggregatorCondition = (
+ conditinal: string
+): { value1: string; operator: string; value2: string } => {
+ if (conditinal.includes("&&") || conditinal.includes("||")) {
+ const regex = /(.+?)\s*(&&|\|\|)\s*(.+?)$/;
+ const match = conditinal.replace("(", "").replace(")", "").match(regex);
+ if (match) {
+ return {
+ value1: match[1],
+ operator: match[2],
+ value2: match[3],
+ };
+ } else {
+ throw new Error(
+ "Invalid condition format it should be just string or string && string or string || string -> " +
+ conditinal
+ );
+ }
+ }
- return { value1: conditinal.replace("(","").replace(")",""), operator: "&&", value2: "true" };
-}
+ return {
+ value1: conditinal.replace("(", "").replace(")", ""),
+ operator: "&&",
+ value2: "true",
+ };
+};
/**
* Parses an equality condition and returns an object with the parsed values.
@@ -547,22 +574,26 @@ const parseAggregatorCondition = (conditinal: string) : { value1: string, operat
* @returns An object with the parsed values: value1, operator, and value2.
* @throws {Error} If the condition format is invalid.
*/
-const parseEqualityCondition = (condition: string): { value1: string, operator: string, value2: string } => {
- if(condition.includes("===") || condition.includes("!==")) {
- const regex = /(.+?)\s*(===|!==)\s*(?:"(.*?)"|\$(\..+?))$/;
- const match = condition.match(regex);
- if (match) {
- return {
- value1: match[1].replace("$.",""),
- operator: match[2],
- value2: match[3]
- };
- } else {
- throw new Error("Invalid condition format, it should be just a string or string === string or string !== string");
- }
+const parseEqualityCondition = (
+ condition: string
+): { value1: string; operator: string; value2: string } => {
+ if (condition.includes("===") || condition.includes("!==")) {
+ const regex = /(.+?)\s*(===|!==)\s*(?:"(.*?)"|\$(\..+?))$/;
+ const match = condition.match(regex);
+ if (match) {
+ return {
+ value1: match[1].replace("$.", ""),
+ operator: match[2],
+ value2: match[3],
+ };
+ } else {
+ throw new Error(
+ "Invalid condition format, it should be just a string or string === string or string !== string"
+ );
+ }
}
return { value1: condition, operator: "===", value2: "true" };
-}
+};
/**
* Evaluates a logical condition based on the provided values and operator.
@@ -572,19 +603,27 @@ const parseEqualityCondition = (condition: string): { value1: string, operator:
* @returns The result of the logical condition evaluation.
* @throws {Error} If an invalid operator is provided.
*/
-const evaluateLogicalCondition = ({ value1, operator, value2 }: { value1: string, operator: string, value2: string }): boolean => {
- const boolValue1 = value1 === 'true';
- const boolValue2 = value2 === 'true';
+const evaluateLogicalCondition = ({
+ value1,
+ operator,
+ value2,
+}: {
+ value1: string;
+ operator: string;
+ value2: string;
+}): boolean => {
+ const boolValue1 = value1 === "true";
+ const boolValue2 = value2 === "true";
switch (operator) {
- case '&&':
- return boolValue1 && boolValue2;
- case '||':
- return boolValue1 || boolValue2;
- default:
- throw new Error('Invalid operator');
+ case "&&":
+ return boolValue1 && boolValue2;
+ case "||":
+ return boolValue1 || boolValue2;
+ default:
+ throw new Error("Invalid operator");
}
-}
+};
/**
* Evaluates an entry against an item based on the provided values and operator.
@@ -592,30 +631,37 @@ const evaluateLogicalCondition = ({ value1, operator, value2 }: { value1: string
* @param entry - The entry containing the values and operator to evaluate.
* @returns A boolean indicating whether the evaluation is true or false.
*/
-const evaluateEntry = (item: any, entry: { value1: string, operator: string, value2: string }): boolean => {
-
- if(entry.value1 === 'true' && entry.value2 === 'true')
- return true;
+const evaluateEntry = (
+ item: any,
+ entry: { value1: string; operator: string; value2: string }
+): boolean => {
+ if (entry.value1 === "true" && entry.value2 === "true") return true;
const value1Result = getFieldValue(entry.value1, item);
- const compareValues = (val1: any, val2: string, operator: string): boolean => {
- switch (operator) {
- case '===':
- return val1 === val2;
- case '!==':
- return val1 !== val2;
- default:
- throw new Error(`Unsupported operator: ${operator}`);
- }
+ const compareValues = (
+ val1: any,
+ val2: string,
+ operator: string
+ ): boolean => {
+ switch (operator) {
+ case "===":
+ return val1 === val2;
+ case "!==":
+ return val1 !== val2;
+ default:
+ throw new Error(`Unsupported operator: ${operator}`);
+ }
};
if (Array.isArray(value1Result)) {
- return value1Result.some(individualValue => compareValues(individualValue, entry.value2, entry.operator));
+ return value1Result.some((individualValue) =>
+ compareValues(individualValue, entry.value2, entry.operator)
+ );
} else {
- return compareValues(value1Result, entry.value2, entry.operator);
+ return compareValues(value1Result, entry.value2, entry.operator);
}
-}
+};
/**
* Evaluates a condition for a given item.
@@ -624,16 +670,25 @@ const evaluateEntry = (item: any, entry: { value1: string, operator: string, val
* @returns A boolean indicating whether the condition is true or false.
*/
const evaluateCondition = (item: any, condition: string): boolean => {
-
- if (condition === 'true') {
- return true;
+ if (condition === "true") {
+ return true;
}
-
+
const conditionParsed = parseAggregatorCondition(condition);
- const condition1 = evaluateEntry(item, parseEqualityCondition(conditionParsed.value1));
- const condition2 = evaluateEntry(item, parseEqualityCondition(conditionParsed.value2));
- return evaluateLogicalCondition({ value1: `${condition1}`, operator: conditionParsed.operator, value2: `${condition2}`});
-}
+ const condition1 = evaluateEntry(
+ item,
+ parseEqualityCondition(conditionParsed.value1)
+ );
+ const condition2 = evaluateEntry(
+ item,
+ parseEqualityCondition(conditionParsed.value2)
+ );
+ return evaluateLogicalCondition({
+ value1: `${condition1}`,
+ operator: conditionParsed.operator,
+ value2: `${condition2}`,
+ });
+};
// We declare here a collection of all validations for every field in the form
export const formFieldValidations: Record<
@@ -655,7 +710,7 @@ export const validate = (
keys: string[],
target: any,
notify: boolean = false,
- getValidations = defaultGetValidations,
+ getValidations = defaultGetValidations
): boolean => {
// For every received key we get the validations and run them
return keys.every((key) => {
@@ -670,7 +725,7 @@ export const validate = (
const fieldValue = getFieldValue(fieldKey, target);
const fieldEvaluation = evaluateCondition(
target,
- fieldCondition.replace(targetGlobalRegex, ""),
+ fieldCondition.replace(targetGlobalRegex, "")
);
// We skip if we evaluate and the result is false
@@ -683,7 +738,7 @@ export const validate = (
(
validation: (value: string) => string,
notify: boolean,
- eventOptions: Omit,
+ eventOptions: Omit
) =>
(value: string) => {
const validationResponse = validation(value);
@@ -696,7 +751,10 @@ export const validate = (
// For every validator we run it and check if the result is empty
return validations
.map((validation) =>
- validateNotifyFactory(validation, notify, { fieldName: key, fieldId: fieldKey }),
+ validateNotifyFactory(validation, notify, {
+ fieldName: key,
+ fieldId: fieldKey,
+ })
)
.every((validateNotify: (value: string) => boolean) => {
// If the field value is an array we run the validation for every item in the array
@@ -729,18 +787,21 @@ export const runValidation = (
const [fieldKey, fieldCondition] = key.includes("(")
? key.replace(")", "").split("(")
: [key, "true"];
-
+
// We then load the field value
const fieldValue = getFieldValue(fieldKey, target);
- const fieldEvaluation = evaluateCondition(target, fieldCondition.replace(targetGlobalRegex,""));
+ const fieldEvaluation = evaluateCondition(
+ target,
+ fieldCondition.replace(targetGlobalRegex, "")
+ );
// We skip if we evaluate and the result is false
// Meaning we should not validate this field using this set of validations
- if(!fieldEvaluation){
+ if (!fieldEvaluation) {
return true;
}
- const validateValue = () : string | (string | string[])[] => {
+ const validateValue = (): string | (string | string[])[] => {
if (Array.isArray(fieldValue)) {
return fieldValue.map((item: any) => {
// And sometimes we can end up with another array inside, that's life
@@ -751,73 +812,92 @@ export const runValidation = (
// If it is not an array here, just validate it
return validation(item);
});
- }else{
+ } else {
return validation(fieldValue);
}
- }
+ };
const validationResponse = validateValue();
-
if (notify) {
// Note: also notifies when valid - errorMsg will be empty.
- const validationResponseMessages : string[] = [];
- (Array.isArray(validationResponse) ? validationResponse.flat() : [validationResponse])
- .forEach((validationResponseMessage) => validationResponseMessages.push(validationResponseMessage))
-
- getFirstErrorMessage(validationResponseMessages,exhaustive).forEach((validationResponseMessage) => {
- notificationBus.emit({ fieldName: key, fieldId: fieldKey, errorMsg: validationResponseMessage }, fieldValue);
- })
+ const validationResponseMessages: string[] = [];
+ (Array.isArray(validationResponse)
+ ? validationResponse.flat()
+ : [validationResponse]
+ ).forEach((validationResponseMessage) =>
+ validationResponseMessages.push(validationResponseMessage)
+ );
-
+ getFirstErrorMessage(validationResponseMessages, exhaustive).forEach(
+ (validationResponseMessage) => {
+ notificationBus.emit(
+ {
+ fieldName: key,
+ fieldId: fieldKey,
+ errorMsg: validationResponseMessage,
+ },
+ fieldValue
+ );
+ }
+ );
}
// If the validation response is not empty we return false
return isValidResponse(validationResponse);
};
-const isValidResponse = (validationResponse: string | (string | string[])[]): boolean => {
- if (typeof validationResponse === 'string') {
- // Case 1: If it's a string and empty, return true
- return validationResponse.trim() === '';
+const isValidResponse = (
+ validationResponse: string | (string | string[])[]
+): boolean => {
+ if (typeof validationResponse === "string") {
+ // Case 1: If it's a string and empty, return true
+ return validationResponse.trim() === "";
} else if (Array.isArray(validationResponse)) {
- // Case 2: If it's an empty array, return true
- if (validationResponse.length === 0) {
- return true;
- }
+ // Case 2: If it's an empty array, return true
+ if (validationResponse.length === 0) {
+ return true;
+ }
- // Check if it's an array of empty arrays
- if (validationResponse.every(val => Array.isArray(val) && val.length === 0)) {
- return true;
- }
+ // Check if it's an array of empty arrays
+ if (
+ validationResponse.every((val) => Array.isArray(val) && val.length === 0)
+ ) {
+ return true;
+ }
- // Check if it's an array of empty strings
- if (validationResponse.every(val => typeof val === 'string' && val.trim() === '')) {
- return true;
- }
+ // Check if it's an array of empty strings
+ if (
+ validationResponse.every(
+ (val) => typeof val === "string" && val.trim() === ""
+ )
+ ) {
+ return true;
+ }
- // Otherwise, it's not one of the valid cases, so return false
- return false;
+ // Otherwise, it's not one of the valid cases, so return false
+ return false;
} else {
- // If it's neither a string nor an array, it's an invalid response
- return false;
+ // If it's neither a string nor an array, it's an invalid response
+ return false;
}
-}
-
-const getFirstErrorMessage = (errors: string[],includeAll: boolean): string[] => {
+};
- if(includeAll)
- return errors;
+const getFirstErrorMessage = (
+ errors: string[],
+ includeAll: boolean
+): string[] => {
+ if (includeAll) return errors;
const result: string[] = [];
for (const error of errors) {
- result.push(error);
- if (error.trim() !== '') {
- break;
- }
+ result.push(error);
+ if (error.trim() !== "") {
+ break;
+ }
}
return result;
-}
+};
export const isNullOrUndefinedOrBlank = (
input: string | null | undefined
diff --git a/frontend/src/pages/FormBCSCPage.vue b/frontend/src/pages/FormBCSCPage.vue
index b50f76f236..15e0c418d9 100644
--- a/frontend/src/pages/FormBCSCPage.vue
+++ b/frontend/src/pages/FormBCSCPage.vue
@@ -542,7 +542,7 @@ watch(submissionLimitError, () => {
>check this map.
- {
{{ progressData[0].title}}
- {
>check this map.
- {
- {
@empty="validation.identificationType = !$event"
/>
- {
+describe("ComboBoxInputComponent", () => {
const validations = [
(value: any) => (value === "A" ? "A is not supported" : ""),
];
@@ -17,7 +17,7 @@ describe("DropdownInputComponent", () => {
};
it("should render", () => {
- const wrapper = mount(DropdownInputComponent, {
+ const wrapper = mount(ComboBoxInputComponent, {
props: {
id: "test",
label: "test",
@@ -43,7 +43,7 @@ describe("DropdownInputComponent", () => {
});
it("should emit event when changing selection", async () => {
- const wrapper = mount(DropdownInputComponent, {
+ const wrapper = mount(ComboBoxInputComponent, {
props: {
id: "test",
label: "test",
@@ -72,7 +72,7 @@ describe("DropdownInputComponent", () => {
});
it("should emit empty then emit not empty", async () => {
- const wrapper = mount(DropdownInputComponent, {
+ const wrapper = mount(ComboBoxInputComponent, {
props: {
id: "test",
label: "test",
@@ -98,7 +98,7 @@ describe("DropdownInputComponent", () => {
});
it("should validate and emit error if required", async () => {
- const wrapper = mount(DropdownInputComponent, {
+ const wrapper = mount(ComboBoxInputComponent, {
props: {
id: "test",
label: "test",
@@ -132,7 +132,7 @@ describe("DropdownInputComponent", () => {
});
it("should not emit error if the change on selected value was not made by the user", async () => {
- const wrapper = mount(DropdownInputComponent, {
+ const wrapper = mount(ComboBoxInputComponent, {
props: {
id: "test",
label: "test",
@@ -161,7 +161,7 @@ describe("DropdownInputComponent", () => {
});
it("should emit error with empty payload (meaning it is valid) even if the change on selected value was not made by the user", async () => {
- const wrapper = mount(DropdownInputComponent, {
+ const wrapper = mount(ComboBoxInputComponent, {
props: {
id: "test",
label: "test",
@@ -195,7 +195,7 @@ describe("DropdownInputComponent", () => {
it.each([[""], [undefined], [null]])(
"should clear the selected value when initialValue changes to a falsy value (%s)",
async (value) => {
- const wrapper = mount(DropdownInputComponent, {
+ const wrapper = mount(ComboBoxInputComponent, {
props: {
id: "test",
label: "test",
@@ -216,7 +216,7 @@ describe("DropdownInputComponent", () => {
);
it("should validate and emit no error if required", async () => {
- const wrapper = mount(DropdownInputComponent, {
+ const wrapper = mount(ComboBoxInputComponent, {
props: {
id: "test",
label: "test",
@@ -248,7 +248,7 @@ describe("DropdownInputComponent", () => {
});
it("should reset selected to initial value when list change", async () => {
- const wrapper = mount(DropdownInputComponent, {
+ const wrapper = mount(ComboBoxInputComponent, {
props: {
id: "test",
label: "test",