+ }
key={`${value}-${index}`}
onDelete={() => removeValue(index)}
className={styles.valueChip}
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.test.ts b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.test.ts
new file mode 100644
index 0000000000..0dcd5870f0
--- /dev/null
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.test.ts
@@ -0,0 +1,110 @@
+import {
+ numberValidatorGenerator,
+ semVerValidatorGenerator,
+ dateValidatorGenerator,
+ stringValidatorGenerator,
+} from './constraintValidators';
+
+test('numbervalidator should accept 0', () => {
+ const numValidator = numberValidatorGenerator(0);
+ const [result, err] = numValidator();
+
+ expect(result).toBe(true);
+ expect(err).toBe('');
+});
+
+test('number validator should reject value that cannot be parsed to number', () => {
+ const numValidator = numberValidatorGenerator('testa31');
+ const [result, err] = numValidator();
+
+ expect(result).toBe(false);
+ expect(err).toBe('Value must be a number');
+});
+
+test('number validator should reject NaN', () => {
+ const numValidator = numberValidatorGenerator(NaN);
+ const [result, err] = numValidator();
+
+ expect(result).toBe(false);
+ expect(err).toBe('Value must be a number');
+});
+
+test('number validator should accept value that can be parsed to number', () => {
+ const numValidator = numberValidatorGenerator('31');
+ const [result, err] = numValidator();
+
+ expect(result).toBe(true);
+ expect(err).toBe('');
+});
+
+test('number validator should accept float values', () => {
+ const numValidator = numberValidatorGenerator('31.12');
+ const [result, err] = numValidator();
+
+ expect(result).toBe(true);
+ expect(err).toBe('');
+});
+
+test('semver validator should reject prefixed values', () => {
+ const semVerValidator = semVerValidatorGenerator('v1.4.2');
+ const [result, err] = semVerValidator();
+
+ expect(result).toBe(false);
+ expect(err).toBe('Value is not a valid semver. For example 1.2.4');
+});
+
+test('semver validator should reject partial semver values', () => {
+ const semVerValidator = semVerValidatorGenerator('4.2');
+ const [result, err] = semVerValidator();
+
+ expect(result).toBe(false);
+ expect(err).toBe('Value is not a valid semver. For example 1.2.4');
+});
+
+test('semver validator should accept semver complient values', () => {
+ const semVerValidator = semVerValidatorGenerator('1.4.2');
+ const [result, err] = semVerValidator();
+
+ expect(result).toBe(true);
+ expect(err).toBe('');
+});
+
+test('date validator should reject invalid date', () => {
+ const dateValidator = dateValidatorGenerator('114mydate2005');
+ const [result, err] = dateValidator();
+
+ expect(result).toBe(false);
+ expect(err).toBe('Value must be a valid date matching RFC3339');
+});
+
+test('date validator should accept valid date', () => {
+ const dateValidator = dateValidatorGenerator('2022-03-03T10:15:23.262Z');
+ const [result, err] = dateValidator();
+
+ expect(result).toBe(true);
+ expect(err).toBe('');
+});
+
+test('string validator should accept a list of strings', () => {
+ const stringValidator = stringValidatorGenerator(['1234', '4121']);
+ const [result, err] = stringValidator();
+
+ expect(result).toBe(true);
+ expect(err).toBe('');
+});
+
+test('string validator should reject values that are not arrays', () => {
+ const stringValidator = stringValidatorGenerator(4);
+ const [result, err] = stringValidator();
+
+ expect(result).toBe(false);
+ expect(err).toBe('Values must be a list of strings');
+});
+
+test('string validator should reject arrays that are not arrays of strings', () => {
+ const stringValidator = stringValidatorGenerator(['test', NaN, 5]);
+ const [result, err] = stringValidator();
+
+ expect(result).toBe(false);
+ expect(err).toBe('Values must be a list of strings');
+});
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.ts b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.ts
index 8709134e5f..cf23d3e420 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.ts
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/useConstraintInput/constraintValidators.ts
@@ -1,11 +1,13 @@
-import { isValid } from 'date-fns';
+import { isValid, parseISO } from 'date-fns';
import semver from 'semver';
export type ConstraintValidatorOutput = [boolean, string];
export const numberValidatorGenerator = (value: unknown) => {
return (): ConstraintValidatorOutput => {
- if (!Number(value)) {
+ const converted = Number(value);
+
+ if (typeof converted !== 'number' || Number.isNaN(converted)) {
return [false, 'Value must be a number'];
}
@@ -13,27 +15,39 @@ export const numberValidatorGenerator = (value: unknown) => {
};
};
-export const stringValidatorGenerator = (values: string[]) => {
+export const stringValidatorGenerator = (values: unknown) => {
return (): ConstraintValidatorOutput => {
+ const error: ConstraintValidatorOutput = [
+ false,
+ 'Values must be a list of strings',
+ ];
if (!Array.isArray(values)) {
- return [false, 'Values must be a list of strings'];
+ return error;
}
+
+ if (!values.every(value => typeof value === 'string')) {
+ return error;
+ }
+
return [true, ''];
};
};
export const semVerValidatorGenerator = (value: string) => {
return (): ConstraintValidatorOutput => {
- if (!semver.valid(value)) {
+ const isCleanValue = semver.clean(value) === value;
+
+ if (!semver.valid(value) || !isCleanValue) {
return [false, 'Value is not a valid semver. For example 1.2.4'];
}
+
return [true, ''];
};
};
export const dateValidatorGenerator = (value: string) => {
return (): ConstraintValidatorOutput => {
- if (isValid(value)) {
+ if (!isValid(parseISO(value))) {
return [false, 'Value must be a valid date matching RFC3339'];
}
return [true, ''];
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx
index 2ab8e51532..07de2209f4 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionView.tsx
@@ -8,7 +8,6 @@ import { IConstraint } from '../../../../interfaces/strategy';
import { ConstraintAccordionViewBody } from './ConstraintAccordionViewBody/ConstraintAccordionViewBody';
import { ConstraintAccordionViewHeader } from './ConstraintAccordionViewHeader/ConstraintAccordionViewHeader';
-import { useStyles } from '../ConstraintAccordion.styles';
import { oneOf } from '../../../../utils/one-of';
import {
dateOperators,
@@ -16,6 +15,7 @@ import {
semVerOperators,
} from '../../../../constants/operators';
+import { useStyles } from '../ConstraintAccordion.styles';
interface IConstraintAccordionViewProps {
environmentId: string;
constraint: IConstraint;
@@ -39,7 +39,13 @@ export const ConstraintAccordionView = ({
);
return (
-
+
}
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx
index 56cc188bb0..f2583698d5 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewBody/ConstraintAccordionViewBody.tsx
@@ -1,5 +1,6 @@
import { Chip } from '@material-ui/core';
import { ImportExportOutlined, TextFormatOutlined } from '@material-ui/icons';
+import StringTruncator from 'component/common/StringTruncator/StringTruncator';
import { useState } from 'react';
import { stringOperators } from '../../../../../constants/operators';
import { IConstraint } from '../../../../../interfaces/strategy';
@@ -37,7 +38,7 @@ export const ConstraintAccordionViewBody = ({
show={
{' '}
- Operator is inverted
+ Operator is negated
}
/>
@@ -65,7 +66,16 @@ const SingleValue = ({ value, operator }: ISingleValueProps) => {
return (
Value must {operator}
{' '}
-
+
+ }
+ className={styles.chip}
+ />
);
};
@@ -88,7 +98,13 @@ const MultipleValues = ({ values }: IMultipleValuesProps) => {
.map((value, index) => (
+ }
className={styles.chip}
/>
))}
diff --git a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx
index a11ec8d193..fcbb5b5df3 100644
--- a/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx
+++ b/frontend/src/component/common/ConstraintAccordion/ConstraintAccordionView/ConstraintAccordionViewHeader/ConstraintAccordionViewHeader.tsx
@@ -53,10 +53,15 @@ export const ConstraintAccordionViewHeader = ({
@@ -64,10 +69,14 @@ export const ConstraintAccordionViewHeader = ({
condition={singleValue}
show={
}
elseShow={
-
- {constraint?.values?.length} values. Expand to
- view
-
+
+
+ {constraint?.values?.length} values
+
+
+ Expand to view
+
+
}
/>
diff --git a/frontend/src/component/common/StringTruncator/StringTruncator.tsx b/frontend/src/component/common/StringTruncator/StringTruncator.tsx
index d053d96435..85ee8f1b02 100644
--- a/frontend/src/component/common/StringTruncator/StringTruncator.tsx
+++ b/frontend/src/component/common/StringTruncator/StringTruncator.tsx
@@ -1,34 +1,44 @@
import { Tooltip } from '@material-ui/core';
+import ConditionallyRender from '../ConditionallyRender';
interface IStringTruncatorProps {
text: string;
maxWidth: string;
className?: string;
+ maxLength: number;
}
const StringTruncator = ({
text,
maxWidth,
+ maxLength,
className,
...rest
}: IStringTruncatorProps) => {
return (
-