Skip to content
Permalink
Browse files

feat: add format validation to text and textarea

  • Loading branch information...
JohnL17 authored and anehx committed Jul 9, 2019
1 parent 67a1328 commit de6d46fa05b0cb0e0f51a324ff7fc6f24eb15e10
@@ -0,0 +1,9 @@
import { Factory } from "ember-cli-mirage";
import faker from "faker";

export default Factory.extend({
name: i => `Validator #${i + 1}`,
slug: i => `validator-${i + 1}`,
errorMsg: () => faker.lorem.paragraph(),
regex: "/asdf/"
});
@@ -0,0 +1,3 @@
import { Model } from "ember-cli-mirage";

export default Model.extend({});
@@ -20,7 +20,6 @@ import formEditorQuestionQuery from "ember-caluma/gql/queries/form-editor-questi
import addFormQuestionMutation from "ember-caluma/gql/mutations/add-form-question";
import formListQuery from "ember-caluma/gql/queries/form-list";
import allDataSourcesQuery from "ember-caluma/gql/queries/all-data-sources";

import saveOptionMutation from "ember-caluma/gql/mutations/save-option";
import saveTextQuestionMutation from "ember-caluma/gql/mutations/save-text-question";
import saveTextareaQuestionMutation from "ember-caluma/gql/mutations/save-textarea-question";
@@ -0,0 +1,49 @@
import Component from "@ember/component";
import { computed } from "@ember/object";
import { inject as service } from "@ember/service";
import { task } from "ember-concurrency";
import layout from "../../../templates/components/cfb-form-editor/question/validation";
import allFormatValidatorsQuery from "ember-caluma/gql/queries/all-format-validators";

export default Component.extend({
layout,

apollo: service(),

init() {
this._super(...arguments);

this.get("availableFormatValidators").perform();
},

availableFormatValidators: task(function*() {
const formatValidators = yield this.get("apollo").watchQuery(
{ query: allFormatValidatorsQuery, fetchPolicy: "cache-and-network" },
"allFormatValidators.edges"
);
return formatValidators.map(edge => edge.node);
}).drop(),

validators: computed(
"availableFormatValidators.lastSuccessful.value.[]",
function() {
return this.getWithDefault(
"availableFormatValidators.lastSuccessful.value",
[]
);
}
),

selected: computed("value.[]", "validators.@each.slug", function() {
return this.validators.filter(validator =>
this.getWithDefault("value", []).includes(validator.slug)
);
}),

actions: {
updateValidators(value) {
this.update(value.map(validator => validator.slug));
this.setDirty();
}
}
});
@@ -11,7 +11,14 @@ const Eng = Engine.extend({
Resolver,

dependencies: {
services: ["apollo", "notification", "router", "intl", "caluma-options"]
services: [
"apollo",
"notification",
"router",
"intl",
"caluma-options",
"validator"
]
}
});

@@ -0,0 +1,12 @@
query AllFormatValidators {
allFormatValidators {
edges {
node {
slug
name
regex
errorMsg
}
}
}
}
@@ -9,6 +9,7 @@ import { task } from "ember-concurrency";
import { all, resolve } from "rsvp";
import { validate } from "ember-validators";
import Evented from "@ember/object/evented";

import { next } from "@ember/runloop";
import { lastValue } from "ember-caluma/utils/concurrency";
import { getAST, getTransforms } from "ember-caluma/utils/jexl";
@@ -74,9 +75,13 @@ export default Base.extend(Evented, {
saveDocumentTableAnswerMutation,

apollo: service(),

intl: service(),

calumaStore: service(),

validator: service(),

init() {
assert("A document must be passed", this.document);

@@ -431,9 +436,15 @@ export default Base.extend(Evented, {
* @internal
*/
_validateTextQuestion() {
return validate("length", this.get("answer.value"), {
max: this.get("question.textMaxLength") || Number.POSITIVE_INFINITY
});
return [
...this.validator.validate(
this.get("answer.value"),
this.getWithDefault("question.meta.formatValidators", [])
),
validate("length", this.get("answer.value"), {
max: this.get("question.textMaxLength") || Number.POSITIVE_INFINITY
})
];
},

/**
@@ -445,9 +456,15 @@ export default Base.extend(Evented, {
* @internal
*/
_validateTextareaQuestion() {
return validate("length", this.get("answer.value"), {
max: this.get("question.textareaMaxLength") || Number.POSITIVE_INFINITY
});
return [
...this.validator.validate(
this.get("answer.value"),
this.getWithDefault("question.meta.formatValidators", [])
),
validate("length", this.get("answer.value"), {
max: this.get("question.textareaMaxLength") || Number.POSITIVE_INFINITY
})
];
},

/**
@@ -0,0 +1,65 @@
import Service from "@ember/service";
import { task } from "ember-concurrency";
import { inject as service } from "@ember/service";
import allFormatValidatorsQuery from "ember-caluma/gql/queries/all-format-validators";
import { computed } from "@ember/object";
import { assert } from "@ember/debug";

export default Service.extend({
apollo: service(),

init() {
this._super(...arguments);
this._fetchValidators.perform();
},

/**
* Tests a value against one or multiple regular expressions.
*
* ```js
* this.validator.validate("foo@example.com", ["email", "lowercase"]);
* ```
* @param {String} value The value to be tested.
* @param {String[]} slugs A list of tests (via slug) to run.
*/
validate(value, slugs) {
return slugs.map(slug => {
const validator = this.validators.find(
validator => validator.slug === slug
);

assert(`No validator found with the slug "${slug}".`, validator);

return (
validator.regex.test(value) || {
type: "format",
message: undefined,
context: { errorMsg: validator.errorMsg },
value
}
);
});
},

_fetchValidators: task(function*() {
return yield this.get("apollo").query(
{ query: allFormatValidatorsQuery },
"allFormatValidators.edges"
);
}),

validators: computed("_fetchValidators.lastSuccessful.value.[]", function() {
const rawValidators = this.getWithDefault(
"_fetchValidators.lastSuccessful.value",
[]
);

return rawValidators.map(rawValidator => {
return {
slug: rawValidator.node.slug,
regex: new RegExp(rawValidator.node.regex),
errorMsg: rawValidator.node.errorMsg
};
});
})
});
@@ -128,6 +128,16 @@
}}
{{/if}}

{{#if (contains f.model.__typename (array "TextQuestion" "TextareaQuestion"))}}
{{f.input
name="meta.formatValidators"
label=(t "caluma.form-builder.question.formatValidators")
placeholder=(t "caluma.form-builder.question.choose")
required=false
renderComponent=(component "cfb-form-editor/question/validation")
}}
{{/if}}

{{#if (eq f.model.__typename "FloatQuestion")}}
<div uk-grid class="uk-grid-small uk-child-width-1-2 uk-margin">
<div>
@@ -0,0 +1,13 @@
{{component labelComponent}}

{{#power-select-multiple
selected=selected
placeholder=placeholder
options=validators
onchange=(action "updateValidators")
as |item|}}
{{item.name}}
{{/power-select-multiple}}

{{component hintComponent}}
{{component errorComponent}}
@@ -0,0 +1,3 @@
export {
default
} from "ember-caluma/components/cfb-form-editor/question/validation";
@@ -0,0 +1 @@
export { default } from "ember-caluma/services/validator";
@@ -53,6 +53,7 @@
"ember-uikit": "^0.8.1",
"ember-validated-form": "^2.0.0-alpha.5",
"faker": "4.1.0",
"ember-power-select": "^2.3.5",
"graphql": "^14.2.1",
"graphql-iso-date": "^3.6.1",
"graphql-tag": "^2.10.1",
@@ -92,7 +93,6 @@
"ember-load-initializers": "2.0.0",
"ember-maybe-import-regenerator": "0.1.6",
"ember-pikaday": "2.3.1",
"ember-power-select": "2.3.5",
"ember-qunit": "4.4.1",
"ember-resolver": "5.1.3",
"ember-source": "3.11.1",
@@ -12,7 +12,14 @@ const App = Application.extend({
engines: {
emberCaluma: {
dependencies: {
services: ["apollo", "notification", "router", "intl", "caluma-options"]
services: [
"apollo",
"notification",
"router",
"intl",
"caluma-options",
"validator"
]
}
}
}
@@ -0,0 +1,7 @@
import Service from "@ember/service";

export default Service.extend({
validate() {
return [true];
}
});
@@ -3,11 +3,16 @@ import { setupRenderingTest } from "ember-qunit";
import { render, fillIn } from "@ember/test-helpers";
import hbs from "htmlbars-inline-precompile";
import Document from "ember-caluma/lib/document";
import { settled } from "@ember/test-helpers";
import setupMirage from "ember-cli-mirage/test-support/setup-mirage";

module("Integration | Component | cf-field", function(hooks) {
setupRenderingTest(hooks);
setupMirage(hooks);

hooks.beforeEach(function() {
this.server.create("format-validator", { slug: "email", regex: "/@/" });

const form = {
__typename: "Form",
slug: "some-form",
@@ -16,9 +21,25 @@ module("Integration | Component | cf-field", function(hooks) {
slug: "question-1",
label: "Test",
isRequired: "true",
isHidden: "true",
isHidden: "false",
textMaxLength: 2,
__typename: "TextQuestion"
},
{
slug: "question-2",
label: "Test2",
isRequired: "true",
isHidden: "false",
meta: { formatValidators: ["email"] },
__typename: "TextQuestion"
},
{
slug: "question-3",
label: "Test3",
isRequired: "true",
isHidden: "false",
formatValidators: ["email"],
__typename: "TextQuestion"
}
]
};
@@ -42,6 +63,8 @@ module("Integration | Component | cf-field", function(hooks) {
});

this.set("field", document.fields[0]);
this.set("errorField", document.fields[1]);
this.set("emailField", document.fields[2]);
});

test("it allows deleting existing input", async function(assert) {
@@ -94,4 +117,27 @@ module("Integration | Component | cf-field", function(hooks) {

assert.dom("uk-text-bold").doesNotExist();
});

test("it shows error message", async function(assert) {
assert.expect(1);
let service = this.owner.lookup("service:validator");
await settled();
let error = service.validators.find(i => i.slug === "email").errorMsg;

await render(hbs`{{cf-field field=errorField}}`);

await fillIn("input", "Test");

assert.dom("span.validation-errors").hasText(error);
});

test("it saves the valid email address", async function(assert) {
assert.expect(1);

await render(hbs`{{cf-field field=emailField}}`);

await fillIn("input", "test@test.com");

assert.dom("span.validation-errors").doesNotExist();
});
});

0 comments on commit de6d46f

Please sign in to comment.
You can’t perform that action at this time.