Skip to content
Open
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
16 changes: 13 additions & 3 deletions src/mixins/ValidationRules.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,18 @@ export const regex = (expression) => helpers.withParams({expression}, (value) =>
return !!String(value).match(regexp);
});

const hasRequiredValue = (value) => {
if (value instanceof Array) {
return value.length > 0;
}
if (typeof value === 'string') {
return value.trim().length > 0;
}
return !isNil(value) && value !== false;
};

export const required = (value) => {
return value instanceof Array ? value.length > 0 : !isNil(value) && value !== '' && value !== false;
return hasRequiredValue(value);
};

export const requiredIf = (variable, expected, fieldName) => helpers.withParams({variable, expected}, function(value, data) {
Expand All @@ -174,7 +184,7 @@ export const requiredIf = (variable, expected, fieldName) => helpers.withParams(
expectedValue = expected === 'true' || expected === '1';
}
if (variableValue != expectedValue) return true;
return value instanceof Array ? value.length > 0 : !!value;
return hasRequiredValue(value);
});

export const requiredUnless = (variable, expected, fieldName) => helpers.withParams({variable, expected}, function(value, data) {
Expand All @@ -187,7 +197,7 @@ export const requiredUnless = (variable, expected, fieldName) => helpers.withPar
expectedValue = expected === 'true' || expected === '1';
}
if (variableValue == expectedValue) return true;
return value instanceof Array ? value.length > 0 : !!value;
return hasRequiredValue(value);
});

export const sameAs = (field, fieldName) => helpers.withParams({field}, function(value, data) {
Expand Down
78 changes: 78 additions & 0 deletions tests/e2e/fixtures/required_whitespace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"type": "screen_package",
"version": "2",
"screens": [
{
"id": 32296,
"screen_category_id": "1",
"title": "Required Whitespace",
"description": "Required controls must reject whitespace-only values",
"type": "FORM",
"config": [
{
"name": "Required Whitespace",
"items": [
{
"label": "Line Input",
"component": "FormInput",
"config": {
"name": "required_input",
"type": "text",
"label": "Required Input",
"readonly": false,
"dataFormat": "string",
"validation": [
{
"value": "required",
"content": "Required"
}
]
},
"inspector": [],
"editor-control": "FormInput",
"editor-component": "FormInput"
},
{
"label": "Text Area",
"component": "FormTextArea",
"config": {
"name": "required_textarea",
"label": "Required Textarea",
"readonly": false,
"richtext": false,
"rows": 3,
"validation": [
{
"value": "required",
"content": "Required"
}
]
},
"inspector": [],
"editor-control": "FormTextArea",
"editor-component": "FormTextArea"
},
{
"label": "Submit Button",
"component": "FormButton",
"config": {
"name": "submit_button",
"event": "submit",
"label": "Submit",
"variant": "primary",
"fieldValue": null,
"defaultSubmit": true
},
"inspector": [],
"editor-control": "FormButton",
"editor-component": "FormButton"
}
]
}
],
"computed": [],
"watchers": [],
"custom_css": null
}
]
}
40 changes: 40 additions & 0 deletions tests/e2e/specs/RequiredWhitespace.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
describe("Required whitespace validation", () => {
beforeEach(() => {
cy.visit("/");
cy.loadFromJson("required_whitespace.json", 0);
cy.get("[data-cy=mode-preview]").click();
});

it("blocks whitespace-only values without modifying valid input", () => {
let alert = false;
cy.on("window:alert", (message) => {
alert = message;
});

cy.get("[data-cy=preview-content] [name=required_input]").type(" ", {
parseSpecialCharSequences: false
});
cy.get("[data-cy=preview-content] [name=required_textarea]").type(" ", {
parseSpecialCharSequences: false
});
cy.get("[data-cy=preview-content] [name=submit_button]").click();

cy.shouldHaveValidationErrors("screen-field-required_input");
cy.shouldHaveValidationErrors("screen-field-required_textarea");
cy.then(() => expect(alert).to.equal(false));

cy.get("[data-cy=preview-content] [name=required_input]")
.clear()
.type(" value ", { parseSpecialCharSequences: false })
.should("have.value", " value ");
cy.get("[data-cy=preview-content] [name=required_textarea]")
.clear()
.type(" value ", { parseSpecialCharSequences: false })
.should("have.value", " value ");

cy.shouldNotHaveValidationErrors("screen-field-required_input");
cy.shouldNotHaveValidationErrors("screen-field-required_textarea");
cy.get("[data-cy=preview-content] [name=submit_button]").click();
cy.then(() => expect(alert).to.equal("Preview Form was Submitted"));
});
});
12 changes: 12 additions & 0 deletions tests/e2e/specs/ValidationRules.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,12 @@ describe("Validation Rules", () => {
).click();
cy.shouldHaveValidationErrors("screen-field-form_input_2");

// Whitespace-only values should not satisfy the active rule
cy.get(
'[data-cy=preview-content] [data-cy="screen-field-form_input_2"]'
).type(" ", { parseSpecialCharSequences: false });
cy.shouldHaveValidationErrors("screen-field-form_input_2");

// Uncheck box 1
cy.get(
'[data-cy=preview-content] [data-cy="screen-field-form_checkbox_1"]'
Expand Down Expand Up @@ -515,6 +521,12 @@ describe("Validation Rules", () => {
).click();
cy.shouldHaveValidationErrors("screen-field-form_input_2");

// Whitespace-only values should not satisfy the active rule
cy.get(
'[data-cy=preview-content] [data-cy="screen-field-form_input_2"]'
).type(" ", { parseSpecialCharSequences: false });
cy.shouldHaveValidationErrors("screen-field-form_input_2");

// Uncheck box 1
cy.get(
'[data-cy=preview-content] [data-cy="screen-field-form_checkbox_1"]'
Expand Down
78 changes: 78 additions & 0 deletions tests/unit/ValidationRules.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
required,
requiredIf,
requiredUnless
} from "../../src/mixins/ValidationRules";

const validationContext = {
getDataAccordingToFieldLevel(data) {
return data;
},
getRootScreen() {
return {
addReferenceToParents(data) {
return data;
}
};
}
};

const activeValidators = [
["required", (value) => required(value)],
[
"requiredIf",
(value) =>
requiredIf("trigger", "true", "field").call(validationContext, value, {
trigger: true
})
],
[
"requiredUnless",
(value) =>
requiredUnless("trigger", "true", "field").call(
validationContext,
value,
{ trigger: false }
)
]
];

describe.each(activeValidators)("%s", (name, validate) => {
it.each([
["an empty string", ""],
["spaces", " "],
["tabs and newlines", "\t\n"],
["null", null],
["undefined", undefined],
["false", false],
["an empty array", []]
])("rejects %s", (description, value) => {
expect(validate(value)).toBe(false);
});

it.each([
["visible text with surrounding spaces", " value "],
["zero", 0],
["a non-empty array", ["value"]]
])("accepts %s", (description, value) => {
expect(validate(value)).toBe(true);
});
});

describe("inactive conditional required rules", () => {
it("does not require a value when requiredIf does not match", () => {
const validate = requiredIf("trigger", "true", "field");

expect(validate.call(validationContext, " ", { trigger: false })).toBe(
true
);
});

it("does not require a value when requiredUnless matches", () => {
const validate = requiredUnless("trigger", "true", "field");

expect(validate.call(validationContext, " ", { trigger: true })).toBe(
true
);
});
});
Loading