mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-04 13:48:56 +02:00
test(1-3734): test constraint reducer (#9966)
Adds a fairly comprehensive test suite for the constraint reducer. I put in all cases I thought were relevant. As part of this, I discovered one bug, and changed two actions. The bug was toggling on the wrong property when you tried to invert case sensitivity. The action changes are: - rename "remove value from list" to "remove value" - remove "set value" in favor of instead letting "add value(s)" work in single-value constraints too.
This commit is contained in:
parent
3e0b127113
commit
8bf3b1f135
@ -156,7 +156,7 @@ const TopRowInput: FC<{
|
|||||||
<ConstraintDateInput
|
<ConstraintDateInput
|
||||||
setValue={(value: string) =>
|
setValue={(value: string) =>
|
||||||
updateConstraint({
|
updateConstraint({
|
||||||
type: 'set value',
|
type: 'add value(s)',
|
||||||
payload: value,
|
payload: value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -171,7 +171,7 @@ const TopRowInput: FC<{
|
|||||||
validator={validator}
|
validator={validator}
|
||||||
onAddValue={(newValue) => {
|
onAddValue={(newValue) => {
|
||||||
updateConstraint({
|
updateConstraint({
|
||||||
type: 'set value',
|
type: 'add value(s)',
|
||||||
payload: newValue,
|
payload: newValue,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -188,7 +188,7 @@ const TopRowInput: FC<{
|
|||||||
validator={validator}
|
validator={validator}
|
||||||
onAddValue={(newValue) => {
|
onAddValue={(newValue) => {
|
||||||
updateConstraint({
|
updateConstraint({
|
||||||
type: 'set value',
|
type: 'add value(s)',
|
||||||
payload: newValue,
|
payload: newValue,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -338,17 +338,10 @@ export const EditableConstraint: FC<Props> = ({
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
removeValue={(value) => {
|
removeValue={(value) => {
|
||||||
if (isMultiValueConstraint(localConstraint)) {
|
|
||||||
updateConstraint({
|
updateConstraint({
|
||||||
type: 'remove value from list',
|
type: 'remove value',
|
||||||
payload: value,
|
payload: value,
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
updateConstraint({
|
|
||||||
type: 'set value',
|
|
||||||
payload: '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
getExternalFocusTarget={() =>
|
getExternalFocusTarget={() =>
|
||||||
addValuesButtonRef.current ??
|
addValuesButtonRef.current ??
|
||||||
@ -396,7 +389,7 @@ export const EditableConstraint: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
removeValue={(value) =>
|
removeValue={(value) =>
|
||||||
updateConstraint({
|
updateConstraint({
|
||||||
type: 'remove value from list',
|
type: 'remove value',
|
||||||
payload: value,
|
payload: value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -412,7 +405,7 @@ export const EditableConstraint: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
addValue={(newValues) =>
|
addValue={(newValues) =>
|
||||||
updateConstraint({
|
updateConstraint({
|
||||||
type: 'set value',
|
type: 'add value(s)',
|
||||||
payload: newValues,
|
payload: newValues,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,440 @@
|
|||||||
|
import {
|
||||||
|
allOperators,
|
||||||
|
IN,
|
||||||
|
isDateOperator,
|
||||||
|
isMultiValueOperator,
|
||||||
|
isSingleValueOperator,
|
||||||
|
NOT_IN,
|
||||||
|
NUM_EQ,
|
||||||
|
} from 'constants/operators';
|
||||||
|
import type {
|
||||||
|
EditableConstraint,
|
||||||
|
EditableDateConstraint,
|
||||||
|
EditableMultiValueConstraint,
|
||||||
|
EditableSingleValueConstraint,
|
||||||
|
} from './editable-constraint-type';
|
||||||
|
import { DATE_AFTER, DATE_BEFORE } from '@server/util/constants';
|
||||||
|
import { constraintReducer } from './constraint-reducer';
|
||||||
|
import { CURRENT_TIME_CONTEXT_FIELD } from 'utils/operatorsForContext';
|
||||||
|
|
||||||
|
const extraConstraintFields: Partial<EditableConstraint> = {
|
||||||
|
inverted: true,
|
||||||
|
caseInsensitive: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const multiValueConstraint = (
|
||||||
|
contextField: string = 'multi-value-context-field',
|
||||||
|
): EditableMultiValueConstraint => ({
|
||||||
|
...extraConstraintFields,
|
||||||
|
contextName: contextField,
|
||||||
|
operator: NOT_IN,
|
||||||
|
values: new Set(['A', 'B']),
|
||||||
|
});
|
||||||
|
|
||||||
|
const singleValueConstraint = (
|
||||||
|
contextField: string = 'single-value-context-field',
|
||||||
|
): EditableSingleValueConstraint => ({
|
||||||
|
...extraConstraintFields,
|
||||||
|
contextName: contextField,
|
||||||
|
operator: NUM_EQ,
|
||||||
|
value: '5',
|
||||||
|
});
|
||||||
|
|
||||||
|
const dateConstraint = (
|
||||||
|
contextField: string = CURRENT_TIME_CONTEXT_FIELD,
|
||||||
|
): EditableDateConstraint => ({
|
||||||
|
...extraConstraintFields,
|
||||||
|
contextName: contextField,
|
||||||
|
operator: DATE_AFTER,
|
||||||
|
value: '2024-05-05T00:00:00Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
const getConstraintForOperator = (
|
||||||
|
operator: string,
|
||||||
|
contextField?: string,
|
||||||
|
): EditableConstraint => {
|
||||||
|
if (isDateOperator(operator)) {
|
||||||
|
return { ...dateConstraint(contextField), operator };
|
||||||
|
}
|
||||||
|
if (isSingleValueOperator(operator)) {
|
||||||
|
return { ...singleValueConstraint(contextField), operator };
|
||||||
|
}
|
||||||
|
if (isMultiValueOperator(operator)) {
|
||||||
|
return { ...multiValueConstraint(contextField), operator };
|
||||||
|
}
|
||||||
|
return { ...multiValueConstraint(contextField), operator: IN };
|
||||||
|
};
|
||||||
|
|
||||||
|
// helper type to allow deconstruction to { value, values, ...rest }
|
||||||
|
type Extractable = EditableConstraint & {
|
||||||
|
value?: string;
|
||||||
|
values?: Set<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('changing context field', () => {
|
||||||
|
test.each([
|
||||||
|
['multi-value', multiValueConstraint],
|
||||||
|
['single-value', singleValueConstraint],
|
||||||
|
['date', dateConstraint],
|
||||||
|
])(
|
||||||
|
'changing context field to the same field is a no-op for %s constraints',
|
||||||
|
(_, constraint) => {
|
||||||
|
const input = constraint();
|
||||||
|
expect(
|
||||||
|
constraintReducer(input, {
|
||||||
|
type: 'set context field',
|
||||||
|
payload: input.contextName,
|
||||||
|
}),
|
||||||
|
).toStrictEqual(input);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
test('changing context field for a single-value constraint clears the `value` prop', () => {
|
||||||
|
const input = singleValueConstraint('field-a');
|
||||||
|
const result = constraintReducer(input, {
|
||||||
|
type: 'set context field',
|
||||||
|
payload: 'field-b',
|
||||||
|
});
|
||||||
|
expect(result).toStrictEqual({
|
||||||
|
...input,
|
||||||
|
contextName: 'field-b',
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('changing context field for a multi-value constraint clears the `values` prop', () => {
|
||||||
|
const input = multiValueConstraint('field-a');
|
||||||
|
const result = constraintReducer(input, {
|
||||||
|
type: 'set context field',
|
||||||
|
payload: 'field-b',
|
||||||
|
});
|
||||||
|
expect(result).toStrictEqual({
|
||||||
|
...input,
|
||||||
|
contextName: 'field-b',
|
||||||
|
values: new Set(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
['multi-value', multiValueConstraint],
|
||||||
|
['single-value', singleValueConstraint],
|
||||||
|
])(
|
||||||
|
'changing context field to currentTime from a %s constraint sets the current time as the value',
|
||||||
|
(_, constraint) => {
|
||||||
|
const now = new Date();
|
||||||
|
const input = constraint('field-a');
|
||||||
|
const { value, ...result } = constraintReducer(input, {
|
||||||
|
type: 'set context field',
|
||||||
|
payload: 'currentTime',
|
||||||
|
}) as Extractable;
|
||||||
|
const {
|
||||||
|
values: _vs,
|
||||||
|
value: _v,
|
||||||
|
...inputBody
|
||||||
|
} = input as Extractable;
|
||||||
|
expect(result).toStrictEqual({
|
||||||
|
...inputBody,
|
||||||
|
contextName: 'currentTime',
|
||||||
|
operator: DATE_AFTER,
|
||||||
|
});
|
||||||
|
expect(new Date(value as string).getTime()).toBeGreaterThanOrEqual(
|
||||||
|
now.getTime(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
test('changing context field from currentTime to something else sets a default operator', () => {
|
||||||
|
const input = dateConstraint();
|
||||||
|
const result = constraintReducer(input, {
|
||||||
|
type: 'set context field',
|
||||||
|
payload: 'somethingElse',
|
||||||
|
});
|
||||||
|
const { value: _, ...inputBody } = input;
|
||||||
|
expect(result).toStrictEqual({
|
||||||
|
...inputBody,
|
||||||
|
contextName: 'somethingElse',
|
||||||
|
operator: IN,
|
||||||
|
values: new Set(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('changing operator', () => {
|
||||||
|
test.each(allOperators)(
|
||||||
|
'changing operator to the same operator (%s) is a no-op',
|
||||||
|
(operator) => {
|
||||||
|
const constraint = getConstraintForOperator(operator);
|
||||||
|
expect(
|
||||||
|
constraintReducer(constraint, {
|
||||||
|
type: 'set operator',
|
||||||
|
payload: operator,
|
||||||
|
}),
|
||||||
|
).toStrictEqual(constraint);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const nonDateOperators = allOperators.filter((op) => !isDateOperator(op));
|
||||||
|
const allCombinations = nonDateOperators
|
||||||
|
.flatMap((a) => nonDateOperators.map((b) => [a, b]))
|
||||||
|
.filter(([a, b]) => a !== b);
|
||||||
|
|
||||||
|
test.each(allCombinations)(
|
||||||
|
"changing the operator to anything that isn't date based clears the value: %s -> %s",
|
||||||
|
(operatorA, operatorB) => {
|
||||||
|
const constraint = getConstraintForOperator(operatorA);
|
||||||
|
const { value, values, ...result } = constraintReducer(constraint, {
|
||||||
|
type: 'set operator',
|
||||||
|
payload: operatorB,
|
||||||
|
}) as Extractable;
|
||||||
|
const {
|
||||||
|
value: _v,
|
||||||
|
values: _values,
|
||||||
|
...inputConstraint
|
||||||
|
} = constraint as Extractable;
|
||||||
|
expect(result).toStrictEqual({
|
||||||
|
...inputConstraint,
|
||||||
|
operator: operatorB,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isMultiValueOperator(operatorB)) {
|
||||||
|
expect(values).toStrictEqual(new Set());
|
||||||
|
} else if (isSingleValueOperator(operatorB)) {
|
||||||
|
expect(value).toBe('');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const dateTransititons = [
|
||||||
|
[DATE_BEFORE, DATE_AFTER],
|
||||||
|
[DATE_AFTER, DATE_BEFORE],
|
||||||
|
] as const;
|
||||||
|
test.each(dateTransititons)(
|
||||||
|
'changing the operator from one date operator to another date operator leaves the value untouched: %s -> %s',
|
||||||
|
(operatorA, operatorB) => {
|
||||||
|
const input: EditableDateConstraint = {
|
||||||
|
...dateConstraint(),
|
||||||
|
operator: operatorA,
|
||||||
|
};
|
||||||
|
const output = constraintReducer(input, {
|
||||||
|
type: 'set operator',
|
||||||
|
payload: operatorB,
|
||||||
|
});
|
||||||
|
expect(input.value).toBe(
|
||||||
|
(output as EditableSingleValueConstraint).value,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe('adding values', () => {
|
||||||
|
describe('single-value constraints', () => {
|
||||||
|
test('adding a value replaces the existing value', () => {
|
||||||
|
const input = singleValueConstraint();
|
||||||
|
const output = constraintReducer(input, {
|
||||||
|
type: 'add value(s)',
|
||||||
|
payload: 'new-value',
|
||||||
|
});
|
||||||
|
expect(output).toStrictEqual({
|
||||||
|
...input,
|
||||||
|
value: 'new-value',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('adding a list replaces the existing value with the first value of the list', () => {
|
||||||
|
const input = singleValueConstraint();
|
||||||
|
const output = constraintReducer(input, {
|
||||||
|
type: 'add value(s)',
|
||||||
|
payload: ['list-value'],
|
||||||
|
});
|
||||||
|
expect(output).toStrictEqual({
|
||||||
|
...input,
|
||||||
|
value: 'list-value',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('adding an empty list effectively clears the value', () => {
|
||||||
|
const input = singleValueConstraint();
|
||||||
|
const output = constraintReducer(input, {
|
||||||
|
type: 'add value(s)',
|
||||||
|
payload: [],
|
||||||
|
});
|
||||||
|
expect(output).toStrictEqual({
|
||||||
|
...input,
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('trying to add a deleted legal value results in no change', () => {
|
||||||
|
const input = singleValueConstraint();
|
||||||
|
const output = constraintReducer(
|
||||||
|
input,
|
||||||
|
{
|
||||||
|
type: 'add value(s)',
|
||||||
|
payload: 'deleted',
|
||||||
|
},
|
||||||
|
new Set(['deleted']),
|
||||||
|
);
|
||||||
|
expect(output).toStrictEqual(input);
|
||||||
|
});
|
||||||
|
test('if both the new value and the old value are deleted legal values, it clears the field', () => {
|
||||||
|
const input = singleValueConstraint();
|
||||||
|
const output = constraintReducer(
|
||||||
|
input,
|
||||||
|
{
|
||||||
|
type: 'add value(s)',
|
||||||
|
payload: 'deleted',
|
||||||
|
},
|
||||||
|
new Set(['deleted', input.value]),
|
||||||
|
);
|
||||||
|
expect(output).toStrictEqual({
|
||||||
|
...input,
|
||||||
|
value: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('multi-value constraints', () => {
|
||||||
|
test('adding a single value to a multi-value constraint adds it to the set', () => {
|
||||||
|
const input = multiValueConstraint();
|
||||||
|
const output = constraintReducer(input, {
|
||||||
|
type: 'add value(s)',
|
||||||
|
payload: 'new-value',
|
||||||
|
});
|
||||||
|
expect(output).toStrictEqual({
|
||||||
|
...input,
|
||||||
|
values: new Set([...input.values, 'new-value']),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('adding a list to a multi-value constraint adds all new elements to the set', () => {
|
||||||
|
const input = multiValueConstraint();
|
||||||
|
const output = constraintReducer(input, {
|
||||||
|
type: 'add value(s)',
|
||||||
|
payload: ['X', 'Y'],
|
||||||
|
});
|
||||||
|
expect(output).toStrictEqual({
|
||||||
|
...input,
|
||||||
|
values: new Set([...input.values, 'X', 'Y']),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('adding an empty list to a multi-value constraint has no effect', () => {
|
||||||
|
const input = multiValueConstraint();
|
||||||
|
const output = constraintReducer(input, {
|
||||||
|
type: 'add value(s)',
|
||||||
|
payload: [],
|
||||||
|
});
|
||||||
|
expect(output).toStrictEqual(input);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deleted legal values are removed from the set before saving new values', () => {
|
||||||
|
const input = {
|
||||||
|
...multiValueConstraint(),
|
||||||
|
values: new Set(['deleted-old', 'A']),
|
||||||
|
};
|
||||||
|
const output = constraintReducer(
|
||||||
|
input,
|
||||||
|
{
|
||||||
|
type: 'add value(s)',
|
||||||
|
payload: ['deleted-new', 'B'],
|
||||||
|
},
|
||||||
|
new Set(['deleted-old', 'deleted-new']),
|
||||||
|
);
|
||||||
|
expect(output).toStrictEqual({
|
||||||
|
...input,
|
||||||
|
values: new Set(['A', 'B']),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('removing / clearing values', () => {
|
||||||
|
describe('single-value constraints', () => {
|
||||||
|
test('removing a value removes the existing value if it matches', () => {
|
||||||
|
const input = singleValueConstraint('context-field');
|
||||||
|
const noChange = constraintReducer(input, {
|
||||||
|
type: 'remove value',
|
||||||
|
payload: '55422b90-9bc4-4847-8a61-17fc928069ff',
|
||||||
|
});
|
||||||
|
expect(noChange).toStrictEqual(input);
|
||||||
|
|
||||||
|
const removed = constraintReducer(input, {
|
||||||
|
type: 'remove value',
|
||||||
|
payload: input.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(removed).toStrictEqual({ ...input, value: '' });
|
||||||
|
});
|
||||||
|
test('clearing a value removes the existing value', () => {
|
||||||
|
const input = singleValueConstraint('context-field');
|
||||||
|
const cleared = constraintReducer(input, {
|
||||||
|
type: 'clear values',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(cleared).toStrictEqual({ ...input, value: '' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('multi-value constraints', () => {
|
||||||
|
test('removing a value removes it from the set if it exists', () => {
|
||||||
|
const values = ['A', 'B', 'C'];
|
||||||
|
const input = {
|
||||||
|
...multiValueConstraint(),
|
||||||
|
values: new Set(values),
|
||||||
|
};
|
||||||
|
const noChange = constraintReducer(input, {
|
||||||
|
type: 'remove value',
|
||||||
|
payload: '55422b90-9bc4-4847-8a61-17fc928069ff',
|
||||||
|
});
|
||||||
|
expect(noChange).toStrictEqual(input);
|
||||||
|
|
||||||
|
const removed = constraintReducer(input, {
|
||||||
|
type: 'remove value',
|
||||||
|
payload: 'B',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(removed).toStrictEqual({
|
||||||
|
...input,
|
||||||
|
values: new Set(['A', 'C']),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('clearing values removes all values from the set', () => {
|
||||||
|
const input = multiValueConstraint();
|
||||||
|
const cleared = constraintReducer(input, {
|
||||||
|
type: 'clear values',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(cleared).toStrictEqual({
|
||||||
|
...input,
|
||||||
|
values: new Set(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('toggle options', () => {
|
||||||
|
const stateTransitions = [
|
||||||
|
[undefined, true],
|
||||||
|
[true, false],
|
||||||
|
[false, true],
|
||||||
|
];
|
||||||
|
test.each(stateTransitions)(
|
||||||
|
'toggle case sensitivity: %s -> %s',
|
||||||
|
(from, to) => {
|
||||||
|
const input = {
|
||||||
|
...multiValueConstraint(),
|
||||||
|
caseInsensitive: from,
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
constraintReducer(input, {
|
||||||
|
type: 'toggle case sensitivity',
|
||||||
|
}),
|
||||||
|
).toStrictEqual({
|
||||||
|
...input,
|
||||||
|
caseInsensitive: to,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
test.each(stateTransitions)('match inversion: %s -> %s', (from, to) => {
|
||||||
|
const input = {
|
||||||
|
...multiValueConstraint(),
|
||||||
|
inverted: from,
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
constraintReducer(input, {
|
||||||
|
type: 'toggle inverted operator',
|
||||||
|
}),
|
||||||
|
).toStrictEqual({
|
||||||
|
...input,
|
||||||
|
inverted: to,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -3,53 +3,46 @@ import {
|
|||||||
IN,
|
IN,
|
||||||
type Operator,
|
type Operator,
|
||||||
isDateOperator,
|
isDateOperator,
|
||||||
|
isMultiValueOperator,
|
||||||
isSingleValueOperator,
|
isSingleValueOperator,
|
||||||
} from 'constants/operators';
|
} from 'constants/operators';
|
||||||
import { CURRENT_TIME_CONTEXT_FIELD } from 'utils/operatorsForContext';
|
import { CURRENT_TIME_CONTEXT_FIELD } from 'utils/operatorsForContext';
|
||||||
import {
|
import {
|
||||||
|
type EditableDateConstraint,
|
||||||
isDateConstraint,
|
isDateConstraint,
|
||||||
isMultiValueConstraint,
|
|
||||||
isSingleValueConstraint,
|
isSingleValueConstraint,
|
||||||
type EditableConstraint,
|
type EditableConstraint,
|
||||||
|
type EditableMultiValueConstraint,
|
||||||
|
type EditableSingleValueConstraint,
|
||||||
} from './editable-constraint-type';
|
} from './editable-constraint-type';
|
||||||
import { difference, union } from './set-functions';
|
import { difference, union } from './set-functions';
|
||||||
|
|
||||||
export type ConstraintUpdateAction =
|
export type ConstraintUpdateAction =
|
||||||
| { type: 'add value(s)'; payload: string[] }
|
| { type: 'add value(s)'; payload: string | string[] }
|
||||||
| { type: 'set value'; payload: string }
|
|
||||||
| { type: 'clear values' }
|
| { type: 'clear values' }
|
||||||
| { type: 'remove value from list'; payload: string }
|
| { type: 'remove value'; payload: string }
|
||||||
| { type: 'set context field'; payload: string }
|
| { type: 'set context field'; payload: string }
|
||||||
| { type: 'set operator'; payload: Operator }
|
| { type: 'set operator'; payload: Operator }
|
||||||
| { type: 'toggle case sensitivity' }
|
| { type: 'toggle case sensitivity' }
|
||||||
| { type: 'toggle inverted operator' };
|
| { type: 'toggle inverted operator' };
|
||||||
|
|
||||||
const resetValues = (state: EditableConstraint): EditableConstraint => {
|
const withValue = <
|
||||||
if (isSingleValueConstraint(state)) {
|
T extends EditableConstraint & { value?: string; values?: Set<string> },
|
||||||
if ('values' in state) {
|
>(
|
||||||
const { values, ...rest } = state;
|
newValue: string | null,
|
||||||
|
constraint: T,
|
||||||
|
): EditableConstraint => {
|
||||||
|
const { value, values, ...rest } = constraint;
|
||||||
|
if (isMultiValueOperator(constraint.operator)) {
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
value: '',
|
values: new Set([newValue].filter(Boolean)),
|
||||||
};
|
} as EditableConstraint;
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
value: '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('value' in state) {
|
|
||||||
const { value, ...rest } = state;
|
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
values: new Set(),
|
value: newValue ?? '',
|
||||||
};
|
} as EditableConstraint;
|
||||||
}
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
values: new Set(),
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const constraintReducer = (
|
export const constraintReducer = (
|
||||||
@ -59,33 +52,37 @@ export const constraintReducer = (
|
|||||||
): EditableConstraint => {
|
): EditableConstraint => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'set context field':
|
case 'set context field':
|
||||||
|
if (action.payload === state.contextName) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
action.payload === CURRENT_TIME_CONTEXT_FIELD &&
|
action.payload === CURRENT_TIME_CONTEXT_FIELD &&
|
||||||
!isDateOperator(state.operator)
|
!isDateOperator(state.operator)
|
||||||
) {
|
) {
|
||||||
return {
|
return withValue(new Date().toISOString(), {
|
||||||
...state,
|
...state,
|
||||||
contextName: action.payload,
|
contextName: action.payload,
|
||||||
operator: DATE_AFTER,
|
operator: DATE_AFTER,
|
||||||
value: new Date().toISOString(),
|
} as EditableDateConstraint);
|
||||||
};
|
|
||||||
} else if (
|
} else if (
|
||||||
action.payload !== CURRENT_TIME_CONTEXT_FIELD &&
|
action.payload !== CURRENT_TIME_CONTEXT_FIELD &&
|
||||||
isDateOperator(state.operator)
|
isDateOperator(state.operator)
|
||||||
) {
|
) {
|
||||||
return {
|
return withValue(null, {
|
||||||
...state,
|
...state,
|
||||||
operator: IN,
|
operator: IN,
|
||||||
contextName: action.payload,
|
contextName: action.payload,
|
||||||
values: new Set(),
|
} as EditableMultiValueConstraint);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resetValues({
|
return withValue(null, {
|
||||||
...state,
|
...state,
|
||||||
contextName: action.payload,
|
contextName: action.payload,
|
||||||
});
|
});
|
||||||
case 'set operator':
|
case 'set operator':
|
||||||
|
if (action.payload === state.operator) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
if (isDateConstraint(state) && isDateOperator(action.payload)) {
|
if (isDateConstraint(state) && isDateOperator(action.payload)) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -94,23 +91,40 @@ export const constraintReducer = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isSingleValueOperator(action.payload)) {
|
if (isSingleValueOperator(action.payload)) {
|
||||||
return resetValues({
|
return withValue(null, {
|
||||||
|
...state,
|
||||||
|
operator: action.payload,
|
||||||
|
} as EditableSingleValueConstraint);
|
||||||
|
}
|
||||||
|
return withValue(null, {
|
||||||
|
...state,
|
||||||
|
operator: action.payload,
|
||||||
|
} as EditableMultiValueConstraint);
|
||||||
|
case 'add value(s)': {
|
||||||
|
if (isSingleValueConstraint(state)) {
|
||||||
|
const newValue = Array.isArray(action.payload)
|
||||||
|
? action.payload[0]
|
||||||
|
: action.payload;
|
||||||
|
if (deletedLegalValues?.has(newValue)) {
|
||||||
|
if (deletedLegalValues?.has(state.value)) {
|
||||||
|
return {
|
||||||
...state,
|
...state,
|
||||||
value: '',
|
value: '',
|
||||||
operator: action.payload,
|
};
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return resetValues({
|
|
||||||
...state,
|
|
||||||
values: new Set(),
|
|
||||||
operator: action.payload,
|
|
||||||
});
|
|
||||||
case 'add value(s)': {
|
|
||||||
if (!('values' in state)) {
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
value: newValue ?? '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const newValues = new Set(action.payload);
|
const newValues = new Set(
|
||||||
|
Array.isArray(action.payload)
|
||||||
|
? action.payload
|
||||||
|
: [action.payload],
|
||||||
|
);
|
||||||
const combinedValues = union(state.values, newValues);
|
const combinedValues = union(state.values, newValues);
|
||||||
const filteredValues = deletedLegalValues
|
const filteredValues = deletedLegalValues
|
||||||
? difference(combinedValues, deletedLegalValues)
|
? difference(combinedValues, deletedLegalValues)
|
||||||
@ -120,25 +134,27 @@ export const constraintReducer = (
|
|||||||
values: filteredValues,
|
values: filteredValues,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'set value':
|
|
||||||
if (isMultiValueConstraint(state)) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
return { ...state, value: action.payload };
|
|
||||||
case 'toggle inverted operator':
|
case 'toggle inverted operator':
|
||||||
return { ...state, inverted: !state.inverted };
|
return { ...state, inverted: !state.inverted };
|
||||||
case 'toggle case sensitivity':
|
case 'toggle case sensitivity':
|
||||||
return { ...state, caseInsensitive: !state.inverted };
|
return { ...state, caseInsensitive: !state.caseInsensitive };
|
||||||
case 'remove value from list':
|
case 'remove value':
|
||||||
if (isSingleValueConstraint(state)) {
|
if (isSingleValueConstraint(state)) {
|
||||||
|
if (state.value === action.payload) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
state.values.delete(action.payload);
|
state.values.delete(action.payload);
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
values: state.values ?? new Set(),
|
values: state.values ?? new Set(),
|
||||||
};
|
};
|
||||||
case 'clear values':
|
case 'clear values':
|
||||||
return resetValues(state);
|
return withValue(null, state);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user