diff --git a/src/classes/Form.ts b/src/classes/Form.ts
index 47258c5..b5ba4a8 100644
--- a/src/classes/Form.ts
+++ b/src/classes/Form.ts
@@ -668,7 +668,9 @@ export default class Form extends EventEmitter implements FormDependence {
* */
validate(): boolean {
const result = this.dependencies.reduce((acc, dep) => {
- if (typeof dep.validate === "function") acc = acc && !!dep.validate();
+ const depValidationResult = (typeof dep.validate === "function") ? dep.validate() : true;
+ console.log("Dep validation result:", depValidationResult)
+ acc = acc && !!depValidationResult;
return acc;
}, true);
diff --git a/src/local-hooks/use-modify.ts b/src/local-hooks/use-modify.ts
index fdefae8..ac8e919 100644
--- a/src/local-hooks/use-modify.ts
+++ b/src/local-hooks/use-modify.ts
@@ -11,8 +11,7 @@ export default function useModify(callbackModifyProps: () => ModifyParam) {
return function execute(v: unknown): string {
const arr = parse(callbackModifyProps()); // Getting array of handlers
-
- arr.forEach(callback => v = callback(v));
+
try {
arr.forEach(callback => v = callback(v));
} catch (e) {
diff --git a/src/types/index.ts b/src/types/index.ts
index c3588cb..a2e28ab 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -7,6 +7,7 @@ export type Value = Values | any;
* @description Callback использующийся для валидации поля для ввода.
* */
export type FormInputValidationCallback = (values: any) => boolean | string
+export type ValidationError = string | false
export type ValidationRule = () => boolean | string;
diff --git a/src/widgets/field-wrap.vue b/src/widgets/field-wrap.vue
index cf1bdda..7fe9218 100644
--- a/src/widgets/field-wrap.vue
+++ b/src/widgets/field-wrap.vue
@@ -14,9 +14,11 @@
diff --git a/src/widgets/form-field.vue b/src/widgets/form-field.vue
index 41d7e40..7c40616 100644
--- a/src/widgets/form-field.vue
+++ b/src/widgets/form-field.vue
@@ -9,7 +9,7 @@
:disabled = "input?.disabled"
:changed = "input?.changed"
- :errors="input?.errors"
+ :errors="input?.errors || []"
/>
@@ -18,11 +18,13 @@ import {computed, onMounted, onUnmounted, reactive} from "vue";
import {getFieldType} from "../config/store";
import Form from "../classes/Form";
import {FormInputValidationCallback} from "../types";
+import STORE from "../../plugin/config/store";
interface IProps {
name: string,
type?: string,
- validation?: FormInputValidationCallback[]
+ validation?: FormInputValidationCallback[] | FormInputValidationCallback,
+ required?: boolean
}
const props = defineProps()
const parentForm = Form.getParentForm();
@@ -67,7 +69,9 @@ function useFormInput(name: string) {
const InputDependency = {
name,
- validation() {
+ validate() {
+
+ console.log("Input validation:", validation, input.value);
const result = validation.reduce((acc: (string | boolean)[], guard) => {
const guardResult = guard(input.value);
if (guardResult !== true) acc.push(guardResult);
@@ -75,7 +79,7 @@ function useFormInput(name: string) {
}, []);
input.errors = result;
- return result.length
+ return result.length === 0
}
}
@@ -87,15 +91,28 @@ function useFormInput(name: string) {
parentForm.unsubscribe(InputDependency);
})
- function setValidation(array: FormInputValidationCallback[]) {
- validation = array;
+ function setValidation(array?: FormInputValidationCallback[] | FormInputValidationCallback) {
+ validation = typeof array === 'function' ? [array] : (array || []);
}
return input;
}
-const input = useFormInput(props.name);
+function mergeValidation() {
+ const arr:FormInputValidationCallback[] = [];
+ if (props.validation) {
+ if (typeof props.validation === 'function') arr.push(props.validation);
+ else arr.push(...props.validation)
+ }
+
+ if (props.required) arr.unshift((v: any) => !!v || STORE.requiredMessage)
+
+ return arr;
+}
+const input = useFormInput(props.name)
+// @ts-ignore;
+input?.setValidation(mergeValidation())
\ No newline at end of file
diff --git a/tests/integrations/input-text.spec.ts b/tests/integrations/input-text.spec.ts
index 9939ebc..df68efb 100644
--- a/tests/integrations/input-text.spec.ts
+++ b/tests/integrations/input-text.spec.ts
@@ -4,6 +4,9 @@ import Form from "../../src/classes/Form";
import {defineComponent} from "vue";
import {InputField} from "../../src//index";
import wait from "../wait";
+import {FormInputValidationCallback} from "@/types";
+import STORE from "../../src/config/store";
+import AppInputTextPretty from "./components/input-text/AppInputTextPretty.vue"
describe("Input text", () => {
test("Default empty input-text", async () => {
@@ -176,4 +179,217 @@ describe("Input text", () => {
const inputs = app.findAll('input');
expect(inputs.map(a => a.element.value)).toEqual(["T", "T"])
})
+
+ test("Input-text with validation", async () => {
+ const test:FormInputValidationCallback[] = [
+ x => x !== 'Jack'
+ ];
+ const component = defineComponent({
+ template: `
+
+
`,
+ components: {InputField}
+ })
+ const app = mount(EmptyApp, {
+ slots: {
+ default: component
+ },
+ });
+ const form = (app.vm as any).form as Form;
+ app.vm.loadingResource = true
+ await app.vm.$nextTick()
+
+ expect(form.validate()).toBe(true);
+ form.setValues({
+ username: "Jack"
+ })
+
+ await app.vm.$nextTick()
+
+ expect(form.validate()).toBe(false);
+ form.setValues({
+ username: "Jack-1"
+ })
+ await app.vm.$nextTick()
+ expect(form.validate()).toBe(true)
+ })
+ test("Input should has error effect when one of validation was rejected", async () => {
+ const ERROR_TEXT = 'Jack is rejected'
+ const test:FormInputValidationCallback[] = [
+ x => x === 'Jack' ? true : 'Jack is rejected'
+ ];
+ const component = defineComponent({
+ template: `
`,
+ components: {InputField}
+ })
+ const app = mount(EmptyApp, {
+ slots: {
+ default: component
+ },
+ });
+ const form = (app.vm as any).form as Form;
+ app.vm.loadingResource = true
+ await app.vm.$nextTick()
+
+ const validateResult = form.validate()
+ expect(validateResult).toBe(false);
+ await app.vm.$nextTick();
+ await wait()
+ expect(app.find('.container-input-text_error').exists()).toBe(true);
+ expect(app.text()).toBe(ERROR_TEXT);
+
+ form.setValues({
+ username: "Jack"
+ })
+ form.validate()
+ await app.vm.$nextTick()
+ await wait()
+
+ expect(app.find('.container-input-text_error').exists()).toBe(false);
+ expect(app.text()).toBe("");
+ })
+ test("Input that required param should has required label and should be validation reject if empty", async () => {
+ const component = defineComponent({
+ template: `
`,
+ components: {InputField}
+ })
+ const app = mount(EmptyApp, {
+ slots: { default: component },
+ });
+ const form = (app.vm as any).form as Form;
+ app.vm.loadingResource = true
+ await app.vm.$nextTick()
+
+ expect(form.validate()).toBe(false);
+
+ await wait();
+
+ expect(app.text()).toBe(STORE.requiredMessage);
+ form.setValues({
+ username: "PP"
+ })
+ expect(form.validate()).toBe(true);
+ await wait()
+ expect(app.text()).toBe("")
+ })
+ test("If label is provided it should be visible", async () => {
+ const component = defineComponent({
+ template: `
`,
+ components: {InputField}
+ })
+ const app = mount(EmptyApp, {
+ slots: { default: component },
+ });
+ const form = (app.vm as any).form as Form;
+ app.vm.loadingResource = true
+ await app.vm.$nextTick()
+
+ expect(app.text()).toBe("Your name");
+ });
+ test("Input that has modify should edit value after input", async () => {
+ // Функция для модифицирования номера автобуса, функция удаляет всё кроме букв и вставляет -, после первых двух.
+ // AABBCC -> AA-BBCC
+ const modify = (x: unknown) => {
+ if (typeof x !== 'string') return '';
+ let str = x.replaceAll(/[^A-Z]/g, '');
+ str = str.slice(0, 6);
+ if (str.length < 6) return str;
+ return `${str.slice(0, 2)}-${str.slice(2, 6)}`
+ }
+ const component = defineComponent({
+ template: `
`,
+ components: {
+ InputField
+ }
+ })
+ const app = mount(EmptyApp, {
+ slots: {
+ default: component
+ }
+ })
+ const form = app.vm.form as Form;
+ app.vm.loadingResource = true;
+ await app.vm.$nextTick();
+
+ const input = app.get("input");
+ await input.setValue("123")
+ expect(form.getValueByName('bus-number')).toBe("")
+
+ await input.setValue("A1A2B3BC55")
+ expect(form.getValueByName('bus-number')).toBe("AABBC")
+
+ await input.setValue("AABBCC")
+ expect(form.getValueByName('bus-number')).toBe("AA-BBCC")
+ })
+ test("Input that has pretty should prettify just view-value, not value in form", async () => {
+
+ const app = mount(AppInputTextPretty)
+ const form = app.vm.form as Form;
+
+ //app.vm.loadingResource = true;
+ await app.vm.$nextTick();
+
+ const input = app.get("input");
+ await input.setValue("jack")
+ await app.vm.$nextTick();
+ await wait()
+
+ expect(form.getValueByName('name')).toBe("jack")
+ expect(input.element.value).toBe('-jack-')
+
+
+ await input.setValue("a")
+ await wait()
+ expect(form.getValueByName('name')).toBe("a")
+ expect(input.element.value).toBe("-a-")
+
+ await input.setValue("")
+ await wait()
+ expect(form.getValueByName('name')).toBe("")
+ expect(input.element.value).toBe("")
+ })
+ test("Pretty and modify should be executed once", async () => {
+ const pretty = (x: any) => x;
+ const modify = (x: any) => x;
+
+ const mockPretty = jest.fn(pretty)
+ const mockModify = jest.fn(modify)
+
+ const component = defineComponent({
+ // @ts-ignore
+ template: `
`,
+ components: {
+ InputField
+ }
+ })
+ const app = mount(EmptyApp, {
+ slots: {
+ default: component
+ }
+ })
+ const form = app.vm.form as Form;
+ app.vm.loadingResource = true;
+ await app.vm.$nextTick()
+ const input = app.get('input');
+
+ await input.setValue("123");
+ await app.vm.$nextTick()
+ await wait()
+
+
+ expect(input.element.value).toBe("123")
+ expect(form.getValueByName('username')).toBe("123++++++++++")
+ expect(mockPretty.mock.calls.length).toBe(1)
+ expect(mockModify.mock.calls.length).toBe(1)
+
+
+ })
+ test("Max length attr should limit the length of value", () => {})
+ test("Placeholder in input should be shown if provided", () => {})
+ test("Prefix should be shown if provided", () => {})
+ test("Numeric attr should return only numeric value and reject entering chars", () => {
+ const form:any = null;
+
+ expect(form.getValueByName('age')).toBe(24);
+ })
})
\ No newline at end of file