Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIO-9308: Fixed the paths with nested forms by ensuring we are always… #188

Merged
merged 1 commit into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 147 additions & 1 deletion src/process/__tests__/process.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
skipValidForLogicallyHiddenComp,
skipValidWithHiddenParentComp,
} from './fixtures';
import { get } from 'lodash';

/*
describe('Process Tests', () => {
Expand Down Expand Up @@ -975,6 +976,148 @@ describe('Process Tests', function () {
assert.equal(context.scope.errors.length, 0);
});

it('Should allow data from a Conditionally shown nested form when another nested form is conditionally not shown.', async function () {
const form = {
components: [
{
label: 'Radio',
values: [
{
label: 'Show A',
value: 'a',
shortcut: '',
},
{
label: 'Show B',
value: 'b',
shortcut: '',
},
],
key: 'radio',
type: 'radio',
input: true,
},
{
label: 'Form',
conditional: {
show: true,
conjunction: 'all',
conditions: [
{
component: 'radio',
operator: 'isEqual',
value: 'a',
},
],
},
type: 'form',
key: 'form',
input: true,
components: [
{
label: 'Form',
key: 'form',
type: 'form',
input: true,
components: [
{
label: 'Text Field',
validate: {
required: true,
},
key: 'textField',
type: 'textfield',
input: true,
},
{
label: 'Text Field',
key: 'textField1',
type: 'textfield',
input: true,
},
],
},
],
},
{
label: 'Form',
key: 'form1',
conditional: {
show: true,
conjunction: 'all',
conditions: [
{
component: 'radio',
operator: 'isEqual',
value: 'b',
},
],
},
type: 'form',
input: true,
components: [
{
label: 'Form',
key: 'form',
type: 'form',
input: true,
components: [
{
label: 'Text Field',
validate: {
required: true,
},
key: 'textField',
type: 'textfield',
input: true,
},
{
label: 'Text Field',
key: 'textField1',
type: 'textfield',
input: true,
},
],
},
],
},
],
};
const submission = {
data: {
radio: 'b',
form1: {
data: {
form: {
data: {
textField: 'one 1',
textField1: 'two 2',
},
},
},
},
},
};
const errors: any = [];
const context = {
form,
submission,
data: submission.data,
components: form.components,
processors: ProcessTargets.submission,
scope: { errors },
config: {
server: true,
},
};
processSync(context);
submission.data = context.data;
context.processors = ProcessTargets.evaluator;
processSync(context);
assert.equal(get(context.submission.data, 'form1.data.form.data.textField'), 'one 1');
assert.equal(get(context.submission.data, 'form1.data.form.data.textField1'), 'two 2');
});

it('should remove submission data not in a nested form definition', async function () {
const form = {
_id: {},
Expand Down Expand Up @@ -4398,7 +4541,10 @@ describe('Process Tests', function () {
processSync(context);
assert.deepEqual(context.data, data);
context.scope.conditionals.forEach((cond: any) => {
assert.equal(cond.conditionallyHidden, cond.path === 'postalCode');
assert.equal(
cond.conditionallyHidden,
cond.path === 'pmta.data.contacts.data.applicantOrganization.data.address.data.postalCode',
);
});
});

Expand Down
10 changes: 6 additions & 4 deletions src/process/clearHidden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ProcessorFnSync,
ConditionsScope,
} from 'types';
import { getComponentAbsolutePath } from 'utils/formUtil';

type ClearHiddenScope = ProcessorScope & {
clearHidden: {
Expand All @@ -17,7 +18,8 @@ type ClearHiddenScope = ProcessorScope & {
* This processor function checks components for the `hidden` property and unsets corresponding data
*/
export const clearHiddenProcess: ProcessorFnSync<ClearHiddenScope> = (context) => {
const { component, data, path, value, scope } = context;
const { component, data, value, scope, path } = context;
const absolutePath = getComponentAbsolutePath(component) || path;

// No need to unset the value if it's undefined
if (value === undefined) {
Expand All @@ -30,7 +32,7 @@ export const clearHiddenProcess: ProcessorFnSync<ClearHiddenScope> = (context) =

// Check if there's a conditional set for the component and if it's marked as conditionally hidden
const isConditionallyHidden = (scope as ConditionsScope).conditionals?.find((cond) => {
return path === cond.path && cond.conditionallyHidden;
return absolutePath === cond.path && cond.conditionallyHidden;
});

const shouldClearValueWhenHidden =
Expand All @@ -40,8 +42,8 @@ export const clearHiddenProcess: ProcessorFnSync<ClearHiddenScope> = (context) =
shouldClearValueWhenHidden &&
(isConditionallyHidden || component.hidden || component.ephemeralState?.conditionallyHidden)
) {
unset(data, path);
scope.clearHidden[path] = true;
unset(data, absolutePath);
scope.clearHidden[absolutePath] = true;
}
};

Expand Down
8 changes: 5 additions & 3 deletions src/process/conditions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
isSimpleConditional,
isJSONConditional,
} from 'utils/conditions';
import { getComponentAbsolutePath } from 'utils/formUtil';

const hasCustomConditions = (context: ConditionsContext): boolean => {
const { component } = context;
Expand Down Expand Up @@ -83,17 +84,18 @@ export const isConditionallyHidden = (context: ConditionsContext): boolean => {
export type ConditionallyHidden = (context: ConditionsContext) => boolean;

export const conditionalProcess = (context: ConditionsContext, isHidden: ConditionallyHidden) => {
const { scope, path } = context;
const { scope, path, component } = context;
const absolutePath = getComponentAbsolutePath(component) || path;
if (!hasConditions(context)) {
return;
}

if (!scope.conditionals) {
scope.conditionals = [];
}
let conditionalComp = scope.conditionals.find((cond) => cond.path === path);
let conditionalComp = scope.conditionals.find((cond) => cond.path === absolutePath);
if (!conditionalComp) {
conditionalComp = { path, conditionallyHidden: false };
conditionalComp = { path: absolutePath, conditionallyHidden: false };
scope.conditionals.push(conditionalComp);
}

Expand Down
4 changes: 2 additions & 2 deletions src/process/filter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { Utils } from 'utils';
import { get, isObject } from 'lodash';
import { getComponentAbsolutePath } from 'utils/formUtil';
export const filterProcessSync: ProcessorFnSync<FilterScope> = (context: FilterContext) => {
const { scope, component } = context;
const { scope, component, path } = context;
const { value } = context;
const absolutePath = getComponentAbsolutePath(component);
const absolutePath = getComponentAbsolutePath(component) || path;
if (!scope.filter) scope.filter = {};
if (value !== undefined) {
const modelType = Utils.getModelType(component);
Expand Down
4 changes: 3 additions & 1 deletion src/process/hideChildren.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ import {
ProcessorFn,
} from 'types';
import { registerEphemeralState } from 'utils';
import { getComponentAbsolutePath } from 'utils/formUtil';

/**
* This processor function checks components for the `hidden` property and, if children are present, sets them to hidden as well.
*/
export const hideChildrenProcessor: ProcessorFnSync<ConditionsScope> = (context) => {
const { component, path, parent, scope } = context;
const absolutePath = getComponentAbsolutePath(component) || path;
// Check if there's a conditional set for the component and if it's marked as conditionally hidden
const isConditionallyHidden = scope.conditionals?.find((cond) => {
return path === cond.path && cond.conditionallyHidden;
return absolutePath === cond.path && cond.conditionallyHidden;
});

if (!scope.conditionals) {
Expand Down
9 changes: 5 additions & 4 deletions src/process/validation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { evaluationRules, rules, serverRules } from './rules';
import find from 'lodash/find';
import get from 'lodash/get';
import pick from 'lodash/pick';
import { getComponentAbsolutePath, getComponentPath } from 'utils/formUtil';
import { getComponentAbsolutePath } from 'utils/formUtil';
import { getErrorMessage } from 'utils/error';
import { FieldError } from 'error';
import {
Expand Down Expand Up @@ -107,11 +107,12 @@ export const _shouldSkipValidation = (
isConditionallyHidden: ConditionallyHidden,
) => {
const { component, scope, path } = context;
const absolutePath = getComponentAbsolutePath(component) || path;

if (
(scope as ConditionsScope)?.conditionals &&
(find((scope as ConditionsScope).conditionals, {
path: getComponentPath(component, path),
path: absolutePath,
conditionallyHidden: true,
}) ||
component.ephemeralState?.conditionallyHidden === true)
Expand Down Expand Up @@ -169,8 +170,8 @@ export function shouldValidateServer(context: ValidationContext): boolean {
}

function handleError(error: FieldError | null, context: ValidationContext) {
const { scope, component } = context;
const absolutePath = getComponentAbsolutePath(component);
const { scope, component, path } = context;
const absolutePath = getComponentAbsolutePath(component) || path;
if (error) {
const cleanedError = cleanupValidationError(error);
cleanedError.context.path = absolutePath;
Expand Down
14 changes: 10 additions & 4 deletions src/utils/conditions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { ConditionsContext, JSONConditional, LegacyConditional, SimpleConditional } from 'types';
import { EvaluatorFn, evaluate, JSONLogicEvaluator } from 'modules/jsonlogic';
import { flattenComponents, getComponent, getComponentActualValue } from './formUtil';
import {
flattenComponents,
getComponent,
getComponentAbsolutePath,
getComponentActualValue,
} from './formUtil';
import { has, isObject, map, every, some, find, filter, isBoolean, split } from 'lodash';
import ConditionOperators from './operators';

Expand All @@ -17,10 +22,11 @@ export const isSimpleConditional = (conditional: any): conditional is SimpleCond
};

export function conditionallyHidden(context: ConditionsContext) {
const { scope, path } = context;
if (scope.conditionals && path) {
const { scope, path, component } = context;
const absolutePath = getComponentAbsolutePath(component) || path;
if (scope.conditionals && absolutePath) {
const hidden = find(scope.conditionals, (conditional) => {
return conditional.path === path;
return conditional.path === absolutePath;
});
return hidden?.conditionallyHidden;
}
Expand Down
8 changes: 5 additions & 3 deletions src/utils/logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import { get, set, clone, isEqual, assign } from 'lodash';
import { evaluate, interpolate } from 'modules/jsonlogic';
import { registerEphemeralState } from './utils';
import { getComponentAbsolutePath } from './formUtil';

export const hasLogic = (context: LogicContext): boolean => {
const { component } = context;
Expand Down Expand Up @@ -69,6 +70,7 @@ export function setActionBooleanProperty(
action: LogicActionPropertyBoolean,
): boolean {
const { component, scope, path } = context;
const absolutePath = getComponentAbsolutePath(component) || path;
const property = action.property.value;
const currentValue = get(component, property, false).toString();
const newValue = action.state.toString();
Expand All @@ -77,19 +79,19 @@ export function setActionBooleanProperty(

// If this is "logic" forcing a component to set hidden property, then we will set the "conditionallyHidden"
// flag which will trigger the clearOnHide functionality.
if (property === 'hidden' && path) {
if (property === 'hidden' && absolutePath) {
if (!(scope as ConditionsScope).conditionals) {
(scope as ConditionsScope).conditionals = [];
}
const conditionallyHidden = (scope as ConditionsScope).conditionals?.find((cond: any) => {
return cond.path === path;
return cond.path === absolutePath;
});
if (conditionallyHidden) {
conditionallyHidden.conditionallyHidden = !!component.hidden;
registerEphemeralState(component, 'conditionallyHidden', !!component.hidden);
} else {
(scope as ConditionsScope).conditionals?.push({
path,
path: absolutePath,
conditionallyHidden: !!component.hidden,
});
}
Expand Down
Loading