Stryker

jedi-validate.js - Stryker report

File / Directory
Mutation score
# Killed
# Survived
# Timeout
# No coverage
# Runtime errors
# Transpile errors
Total detected
Total undetected
Total mutants
jedi-validate.js
37.24 %
37.24 53 91 1 0 0 0 54 91 145
Expand all
import deepmerge from './lib/deepmerge';
import { getData, getInputData, getValueByName } from './lib/get-data';
import { convertData } from './lib/convert-data';
import Dictionary from './i18n/jedi-validate-i18n';
import { getFormOptions, getInputRules } from './lib/get-options';
import { validateData, validateField } from './lib/validate-data';
import { ajax } from './lib/ajax';
import { initErrorMessages, markField } from './lib/utils';
import defaultMethods from './lib/methods';
import defaultOptions from './options';

/**
 * JediValidate - validation
 */
export default class JediValidate {
    /**
     * Object with fields
     * @private
     * @type {Object.<string, Element>}
     */
    fields = {};

    /**
     * Object with inputs nodes
     * @private
     * @type {Object.<string, HTMLInputElement|HTMLSelectElement|Array>}
     */
    inputs = {};

    /**
     * Object with message nodes
     * @private
     * @type {Object.<string, Element>}
     */
    messages = {};

    /**
     * Object with error message
     * @private
     * @type {Object.<string, Object.<string, string>>}
     */
    errorMessages = {};

    /**
     * Object with error message
     * @private
     * @type {object} - data object
     */
    data = {};

    /**
     * Validate methods
     * @private
     * @type {Object.<string, {func: Function, message: string}>}
     */
    methods = 0{ ...defaultMethods };
    /* eslint-disable */
    /**
     * Validator options
     * @private
     * @type {{ajax: {url: string, enctype: string, sendType: string, method: string}, rules: {}, messages: {}, containers: {parent: string, message: string, baseMessage: string}, states: {error: string, valid: string, pristine: string, dirty: string}, formStatePrefix: string, callbacks: {success: function, error: function}, clean: boolean, redirect: boolean, language: string, translations: {}}}
     */
    options = {};

    /* eslint-enable */
    /**
     * Validator rules
     * @private
     * @type {object}
     */
    rules = {};

    /**
     * Translation dictionary
     * @private
     * @type {Dictionary}
     */
    dictionary = null;

    /**
     * Elements
     * @private
     * @type {object}
     */
    nodes = null;

    /**
     * Root element
     * @private
     * @type {Element}
     */
    root = null;

    /**
     * JediValidate
     * @param {HTMLElement} root - element which wraps form element
     * @param {object} options - object with options
     */
    constructor(root, options = {}) 1{
        this.root = root;

        const baseMessageClass =
            234(567options.containers && options.containers.baseMessage) || defaultOptions.containers.baseMessage;

        this.nodes = 8{
            form: this.root.querySelector(9'form'),
            inputs: this.root.querySelectorAll(10'form [name]'),
            baseMessage: this.root.querySelector(11`.${baseMessageClass}`),
        };

        const formOptions = getFormOptions(this.nodes.form);

        this.options = deepmerge(this.options, defaultOptions);
        this.options = deepmerge(this.options, formOptions);
        this.options = deepmerge(this.options, options);

        this.rules = 12{ ...this.options.rules };

        this.dictionary = new Dictionary(this.options.translations);

        this.ready();

        this.errorMessages = initErrorMessages(this.rules, this.options.messages, this.methods);
    }

    /**
     * Ready
     * @private
     */
    ready() 13{
        this.nodes.form.setAttribute(14'novalidate', 15'novalidate');

        this.nodes.form.addEventListener(16'submit', this.handleSubmit);

        Array.from(this.nodes.inputs).forEach(input => 17{
            // fixme "name" and "name in data" not the same
            // name === "phone[]",
            // data: { phone: [] } - name === "phone"
            const name = input.name; // eslint-disable-line prefer-destructuring

            if (1819this.inputs[name]) 20{
                if (2122Array.isArray(this.inputs[name])) 23{
                    this.inputs[name].push(input);
                } else 24{
                    const groupInput = 25[this.inputs[name], input];
                    groupInput.name = name;
                    this.inputs[name] = groupInput;
                }
            } else 26{
                this.inputs[name] = input;

                let field = input.parentNode;

                do 27{
                    if (2829field.classList.contains(this.options.containers.parent)) 30{
                        this.fields[name] = field;
                        break;
                    }

                    field = field.parentNode;
                } while (3132field && field.classList);

                if (333435!this.fields[name]) 36{
                    console.warn(37`Input ${name} has no parent field`);
                    delete this.inputs[name];
                    return;
                }

                this.fields[name].classList.add(this.options.states.pristine);

                const messageElement = this.fields[name].querySelector(38`.${this.options.containers.message}`);

                if (3940messageElement) 41{
                    this.messages[name] = messageElement;
                } else 42{
                    this.messages[name] = document.createElement(43'div');
                    this.messages[name].classList.add(this.options.containers.message);
                    this.fields[name].appendChild(this.messages[name]);
                }

                this.rules[name] = 444546this.rules[name] || {};
                const inputRules = getInputRules(input);
                this.rules[name] = deepmerge(inputRules, this.rules[name]);

                Object.keys(this.rules[name]).forEach(rule => 47{
                    if (4849this.rules[name][rule]) 50{
                        this.fields[name].classList.add(rule);
                    }
                });
            }

            input.addEventListener(51'change', this.handleInputChange.bind(this, name));
            input.addEventListener(52'input', this.handleInputInput.bind(this, name));
        });
    }

    /**
     * Handle input change
     * @private
     * @param {string} name
     */
    handleInputChange(name) 53{
        this.fields[name].classList.remove(this.options.states.dirty);

        const inputData = getInputData(this.inputs[name]);
        const value = getValueByName(name, inputData);

        // fixme don't work with 2 inputs phone[]
        this.data = 54{
            ...this.data,
            ...inputData,
        };

        const errors = validateField(
            this.rules[name],
            this.methods,
            value,
            name,
            this.errorMessages,
            this.data,
            this.translate,
        );

        markField(this.fields[name], this.messages[name], this.options.states, errors);
    }

    /**
     * Handle input
     * @private
     * @param {string} name
     */
    handleInputInput(name) 55{
        this.fields[name].classList.remove(this.options.states.pristine);
        this.fields[name].classList.add(this.options.states.dirty);
    }

    /**
     * Handle form submit
     * @private
     * @param {Event} event
     */
    handleSubmit = event => 56{
        this.data = getData(this.inputs);

        const errors = validateData(this.rules, this.methods, this.data, this.errorMessages, this.translate);

        const fieldNames = Object.keys(errors).filter(name => this.fields[name]);

        if (575859fieldNames.length !== 0) 60{
            fieldNames.forEach(name =>
                markField(this.fields[name], this.messages[name], this.options.states, errors[name]),
            );
        }

        const errorFieldNames = fieldNames.filter(name => errors[name]);

        if (616263errorFieldNames.length !== 0) 64{
            try 65{
                this.options.callbacks.error(66{ errors });
            } catch (e) 67{
                if (686970process.env.NODE_ENV === 71'development') 72{
                    console.error(e);
                }
            }

            event.preventDefault();
            return;
        }

        if (737475this.options.ajax && this.options.ajax.url) 76{
            event.preventDefault();
        } else 77{
            try 78{
                this.options.callbacks.success(79{ event });
            } catch (e) 80{
                if (818283process.env.NODE_ENV === 84'development') 85{
                    console.error(e);
                }
            }

            return;
        }

        const convertedData = convertData(this.data, this.options.ajax.sendType);
        this.send(86{
            ...this.options.ajax,
            data: convertedData,
        });
    };

    /**
     * Translate
     * @private
     * @param {string} text - text to translate
     */
    translate = text => this.dictionary.translate(text, this.options.language);

    /**
     * Send form
     * @private
     * @param {object} options - object with options for sending
     * @param {string} options.url
     * @param {string} options.enctype
     * @param {string} options.sendType
     * @param {string} options.method
     * @param {string|FormData} options.data
     */
    send(options) 87{
        ajax(options, this.translate)
            .then(response => 88{
                if (8990response.validationErrors) 91{
                    try 92{
                        this.options.callbacks.error(93{
                            errors: response.validationErrors,
                        });
                    } catch (e) 94{
                        if (959697process.env.NODE_ENV === 98'development') 99{
                            console.error(e);
                        }
                    }

                    if (100101response.validationErrors.base) 102{
                        this.nodes.baseMessage.innerHTML = response.validationErrors.base.join(103', ');
                        this.root.classList.add(104this.options.formStatePrefix + this.options.states.error);
                        this.root.classList.remove(105this.options.formStatePrefix + this.options.states.valid);
                        delete response.validationErrors.base;
                    } else 106{
                        this.nodes.baseMessage.innerHTML = 107'';
                    }

                    Object.keys(response.validationErrors).forEach(name =>
                        markField(
                            this.fields[name],
                            this.messages[name],
                            this.options.states,
                            response.validationErrors[name],
                        ),
                    );
                } else 108{
                    try 109{
                        this.options.callbacks.success(110{ response });
                    } catch (e) 111{
                        if (112113114process.env.NODE_ENV === 115'development') 116{
                            console.error(e);
                        }
                    }

                    if (117118119this.options.redirect && response.redirect) 120{
                        window.location.href = response.redirect;
                        return;
                    }

                    if (121122this.options.clean) 123{
                        this.nodes.form.reset();
                    }
                }
            })
            .catch(({ method, url, status, statusText }) => 124{
                console.warn(125`${method} ${url} ${status} (${statusText})`);

                this.nodes.baseMessage.innerHTML = this.translate(126'Can not send form!');
                this.root.classList.add(127this.options.formStatePrefix + this.options.states.error);
                this.root.classList.remove(128this.options.formStatePrefix + this.options.states.valid);
            });
    }

    /**
     * Collect data
     * @public
     * @param {string|Array.<string>} params - field
     * @returns {Object}
     */
    collect(params = 129'') 130{
        if (131132133!params) 134{
            this.data = getData(this.inputs);

            return this.data;
        }

        if (135136Array.isArray(params)) 137{
            return params.reduce((collected, name) => 138{
                const inputData = getInputData(this.inputs[name]);

                this.data = 139{
                    ...this.data,
                    ...inputData,
                };

                return 140{
                    ...collected,
                    ...inputData,
                };
            }, {});
        }

        const inputData = getInputData(this.inputs[params]);

        // fixme don't work with 2 inputs phone[]
        this.data = 141{
            ...this.data,
            ...inputData,
        };

        return inputData;
    }

    /**
     * Add rule to validator
     * @public
     * @param {string} rule - rule name
     * @param {Function} func - function
     * @param {string} message - error message
     */
    addMethod(rule, func, message) 142{
        this.methods[rule] = 143{
            func,
            message,
        };

        this.errorMessages = initErrorMessages(this.rules, this.options.messages, this.methods);
    }

    /**
     * Add localization to JediValidate
     * @public
     * @param {string} sourceText - text on english
     * @param {string} translatedText - text on needed language
     * @param {string} language - language
     */
    addToDictionary(sourceText, translatedText, language) 144{
        this.dictionary.addTranslation(sourceText, translatedText, language);
    }
}
# Mutator State Location Original Replacement
0 ObjectLiteral Killed 55 : 14 { ...... } {}
1 Block Killed 98 : 36 { ... } {}
2 ConditionalExpression Survived 102 : 12 ( ... .
3 BinaryExpression Survived 102 : 12 ( ... . . ... .
4 ConditionalExpression Survived 102 : 12 ( ... .
5 BinaryExpression Killed 102 : 13 . ... . . ... .
6 ConditionalExpression Survived 102 : 13 . ... .
7 ConditionalExpression Survived 102 : 13 . ... .
8 ObjectLiteral Killed 104 : 21 { ... } {}
9 StringLiteral Killed 105 : 42 ' ' ""
10 StringLiteral Killed 106 : 47 ' [ ]' ""
11 StringLiteral Killed 107 : 49 `.${ ... }` ""
12 ObjectLiteral Killed 116 : 21 { ...... } {}
13 Block Killed 129 : 12 { ... } {}
14 StringLiteral Killed 130 : 37 ' ' ""
15 StringLiteral Survived 130 : 51 ' ' ""
16 StringLiteral Survived 132 : 41 ' ' ""
17 Block Killed 134 : 55 { ... } {}
18 IfStatement Survived 140 : 16 . [ ]
19 IfStatement Killed 140 : 16 . [ ]
20 Block Survived 140 : 35 { ... } {}
21 IfStatement Survived 141 : 20 . ... ])
22 IfStatement Survived 141 : 20 . ... ])
23 Block Survived 141 : 54 { ... } {}
24 Block Survived 143 : 23 { ... } {}
25 ArrayLiteral Survived 144 : 39 [ ... ] []
26 Block Killed 148 : 19 { ... } {}
27 Block TimedOut 153 : 19 { ... } {}
28 IfStatement Killed 154 : 24 . ... )
29 IfStatement Killed 154 : 24 . ... )
30 Block Killed 154 : 82 { ... } {}
31 DoStatement Killed 160 : 25 && .
32 BinaryExpression Survived 160 : 25 && . || .
33 IfStatement Survived 162 : 20 ! ... ]
34 IfStatement Killed 162 : 20 ! ... ]
35 PrefixUnaryExpression Killed 162 : 20 ! ... ] . [ ]
36 Block Survived 162 : 40 { ... } {}
37 StringLiteral Survived 163 : 33 ` ... ` ""
38 StringLiteral Killed 170 : 71 `.${ ... }` ""
39 IfStatement Survived 172 : 20
40 IfStatement Killed 172 : 20
41 Block Survived 172 : 36 { ... } {}
42 Block Killed 174 : 23 { ... } {}
43 StringLiteral Killed 175 : 65 ' ' ""
44 BinaryExpression Killed 180 : 35 . ...|| {} . ...&& {}
45 ConditionalExpression Killed 180 : 35 . ...|| {}
46 ConditionalExpression Killed 180 : 35 . ...|| {}
47 Block Survived 184 : 62 { ... } {}
48 IfStatement Survived 185 : 24 . ... ]
49 IfStatement Survived 185 : 24 . ... ]
50 Block Survived 185 : 48 { ... } {}
51 StringLiteral Survived 191 : 35 ' ' ""
52 StringLiteral Survived 192 : 35 ' ' ""
53 Block Killed 201 : 28 { ... } {}
54 ObjectLiteral Survived 208 : 20 { ... } {}
55 Block Killed 231 : 27 { ... } {}
56 Block Killed 241 : 28 { ... } {}
57 IfStatement Survived 248 : 12 . !==
58 BinaryExpression Survived 248 : 12 . !== . ===
59 IfStatement Survived 248 : 12 . !==
60 Block Survived 248 : 37 { ... } {}
61 IfStatement Killed 256 : 12 . !==
62 IfStatement Killed 256 : 12 . !==
63 BinaryExpression Killed 256 : 12 . !== . ===
64 Block Killed 256 : 42 { ... } {}
65 Block Killed 257 : 16 { ... } {}
66 ObjectLiteral Survived 258 : 45 { } {}
67 Block Survived 259 : 24 { ... } {}
68 IfStatement Survived 260 : 20 . .... '
69 IfStatement Survived 260 : 20 . .... '
70 BinaryExpression Survived 260 : 20 . .... ' . .... '
71 StringLiteral Survived 260 : 45 ' ' ""
72 Block Survived 260 : 60 { ... } {}
73 IfStatement Killed 269 : 12 . ... .
74 IfStatement Killed 269 : 12 . ... .
75 BinaryExpression Survived 269 : 12 . ... . . ... .
76 Block Survived 269 : 56 { ... } {}
77 Block Killed 271 : 15 { ... } {}
78 Block Killed 272 : 16 { ... } {}
79 ObjectLiteral Survived 273 : 47 { } {}
80 Block Survived 274 : 24 { ... } {}
81 IfStatement Survived 275 : 20 . .... '
82 IfStatement Survived 275 : 20 . .... '
83 BinaryExpression Survived 275 : 20 . .... ' . .... '
84 StringLiteral Survived 275 : 45 ' ' ""
85 Block Survived 275 : 60 { ... } {}
86 ObjectLiteral Survived 284 : 18 { ... } {}
87 Block Killed 307 : 18 { ... } {}
88 Block Survived 309 : 30 { ... } {}
89 IfStatement Survived 310 : 20 .
90 IfStatement Survived 310 : 20 .
91 Block Survived 310 : 47 { ... } {}
92 Block Survived 311 : 24 { ... } {}
93 ObjectLiteral Survived 312 : 53 { ... } {}
94 Block Survived 315 : 32 { ... } {}
95 IfStatement Survived 316 : 28 . .... '
96 IfStatement Survived 316 : 28 . .... '
97 BinaryExpression Survived 316 : 28 . .... ' . .... '
98 StringLiteral Survived 316 : 53 ' ' ""
99 Block Survived 316 : 68 { ... } {}
100 IfStatement Survived 321 : 24 . ... .
101 IfStatement Survived 321 : 24 . ... .
102 Block Survived 321 : 56 { ... } {}
103 StringLiteral Survived 322 : 95 ', ' ""
104 BinaryExpression Survived 323 : 48 . ... . . ... .
105 BinaryExpression Survived 324 : 51 . ... . . ... .
106 Block Survived 326 : 27 { ... } {}
107 StringLiteral Survived 327 : 59 '' " ... !"
108 Block Survived 338 : 23 { ... } {}
109 Block Survived 339 : 24 { ... } {}
110 ObjectLiteral Survived 340 : 55 { } {}
111 Block Survived 341 : 32 { ... } {}
112 IfStatement Survived 342 : 28 . .... '
113 IfStatement Survived 342 : 28 . .... '
114 BinaryExpression Survived 342 : 28 . .... ' . .... '
115 StringLiteral Survived 342 : 53 ' ' ""
116 Block Survived 342 : 68 { ... } {}
117 IfStatement Survived 347 : 24 . ... .
118 IfStatement Survived 347 : 24 . ... .
119 BinaryExpression Survived 347 : 24 . ... . . ... .
120 Block Survived 347 : 68 { ... } {}
121 IfStatement Survived 352 : 24 . .
122 IfStatement Survived 352 : 24 . .
123 Block Survived 352 : 44 { ... } {}
124 Block Survived 357 : 60 { ... } {}
125 StringLiteral Survived 358 : 29 `${ ... })` ""
126 StringLiteral Survived 360 : 66 ' ... !' ""
127 BinaryExpression Survived 361 : 40 . ... . . ... .
128 BinaryExpression Survived 362 : 43 . ... . . ... .
129 StringLiteral Killed 372 : 21 '' " ... !"
130 Block Killed 372 : 25 { ... } {}
131 IfStatement Killed 373 : 12 !
132 IfStatement Killed 373 : 12 !
133 PrefixUnaryExpression Killed 373 : 12 !
134 Block Killed 373 : 21 { ... } {}
135 IfStatement Killed 379 : 12 . ... )
136 IfStatement Killed 379 : 12 . ... )
137 Block Killed 379 : 35 { ... } {}
138 Block Killed 380 : 54 { ... } {}
139 ObjectLiteral Survived 383 : 28 { ... } {}
140 ObjectLiteral Killed 388 : 23 { ... } {}
141 ObjectLiteral Survived 398 : 20 { ... } {}
142 Block Killed 413 : 35 { ... } {}
143 ObjectLiteral Killed 414 : 29 { ... } {}
144 Block Killed 429 : 58 { ... } {}