├── .eslintrc.json ├── .github └── workflows │ ├── linthtml.yml │ ├── lintjs.yml │ └── test.yml ├── .gitignore ├── .htmlhintrc ├── .npmignore ├── LICENSE ├── README.md ├── bin └── check ├── cypress.config.js ├── cypress ├── e2e │ ├── all_specs.cy.js │ ├── demo_playback │ │ └── demo.cy.js │ ├── events │ │ ├── blur │ │ │ ├── email.cy.js │ │ │ ├── json_validations.cy.js │ │ │ ├── length.cy.js │ │ │ ├── multiple_validations.cy.js │ │ │ ├── numericality.cy.js │ │ │ ├── presence.cy.js │ │ │ └── strong_password.cy.js │ │ └── input │ │ │ ├── email.cy.js │ │ │ ├── json_validations.cy.js │ │ │ ├── length.cy.js │ │ │ ├── multiple_validations.cy.js │ │ │ ├── numericality.cy.js │ │ │ ├── presence.cy.js │ │ │ └── strong_password.cy.js │ ├── i18n │ │ └── locales │ │ │ ├── en.cy.js │ │ │ ├── es.cy.js │ │ │ ├── fr.cy.js │ │ │ ├── invalid_locale.cy.js │ │ │ ├── pt_br.cy.js │ │ │ ├── zh_cn.cy.js │ │ │ └── zh_tw.cy.js │ └── styles │ │ ├── classes.cy.js │ │ └── css.cy.js ├── fixtures │ ├── example.json │ ├── fields.html │ └── i18n │ │ └── locales │ │ ├── en.html │ │ ├── es.html │ │ ├── fr.html │ │ ├── invalid_locale.html │ │ ├── pt_br.html │ │ ├── zh_cn.html │ │ └── zh_tw.html └── support │ ├── commands.js │ ├── e2e.js │ └── spec_helper.js ├── dist ├── stimulus-inline-input-validations.cjs ├── stimulus-inline-input-validations.cjs.map ├── stimulus-inline-input-validations.module.js └── stimulus-inline-input-validations.module.js.map ├── index.html ├── package.json ├── src ├── helpers │ └── regex.js ├── i18n │ ├── error_messages.js │ └── supported_locales.js ├── index.js ├── input_validator.js └── validations │ └── validate.js ├── styles └── tokyo_night_dark.css └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2020": true 5 | }, 6 | "extends": "standard", 7 | "overrides": [], 8 | "parserOptions": { 9 | "ecmaVersion": "latest", 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/linthtml.yml: -------------------------------------------------------------------------------- 1 | name: LintHTML 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | linthtml: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 16 23 | 24 | - name: Install dependencies 25 | run: yarn install 26 | 27 | - name: Run htmlhint 28 | run: yarn run htmlhint ./index.html 29 | -------------------------------------------------------------------------------- /.github/workflows/lintjs.yml: -------------------------------------------------------------------------------- 1 | name: LintJS 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | lintjs: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v2 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 16 23 | 24 | - name: Install dependencies 25 | run: yarn install 26 | 27 | - name: Run ESLint 28 | run: yarn eslint src 29 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Cypress Tests 2 | 3 | on: push 4 | 5 | jobs: 6 | cypress-run: 7 | runs-on: ubuntu-22.04 8 | strategy: 9 | matrix: 10 | browser: [chrome, edge, electron, firefox] 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | - name: Set up Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 16 19 | 20 | - name: Install dependencies 21 | run: yarn install 22 | 23 | - name: Cypress run 24 | run: yarn run cypress run --spec 'cypress/e2e/**/*.cy.js' --browser ${{ matrix.browser }} 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | yarn-error.log 3 | .DS_Store 4 | *.swp 5 | *.swo 6 | *~ 7 | /cypress/screenshots 8 | /cypress/downloads 9 | -------------------------------------------------------------------------------- /.htmlhintrc: -------------------------------------------------------------------------------- 1 | { 2 | "alt-require": true, 3 | "attr-lowercase": ["viewBox"], 4 | "attr-no-duplication": true, 5 | "attr-unsafe-chars": true, 6 | "attr-value-double-quotes": true, 7 | "attr-value-not-empty": false, 8 | "csslint": false, 9 | "doctype-first": false, 10 | "doctype-html5": true, 11 | "head-script-disabled": false, 12 | "href-abs-or-rel": false, 13 | "id-class-ad-disabled": true, 14 | "id-class-value": false, 15 | "id-unique": true, 16 | "inline-script-disabled": true, 17 | "inline-style-disabled": false, 18 | "jshint": false, 19 | "space-tab-mixed-disabled": "space", 20 | "spec-char-escape": false, 21 | "src-not-empty": true, 22 | "style-disabled": false, 23 | "tag-pair": true, 24 | "tag-self-close": false, 25 | "tagname-lowercase": true, 26 | "title-require": false 27 | } 28 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | *.log 3 | .DS_Store 4 | .gitignore 5 | .yarn.lock 6 | .prettierrc.json 7 | /.git 8 | /.vscode 9 | /cypress/screenshots 10 | /cypress/downloads 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mike Ray Arriaga 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm](https://img.shields.io/npm/v/stimulus-inline-input-validations.svg)](https://www.npmjs.com/package/stimulus-inline-input-validations) [![Tests](https://github.com/mikerayux/stimulus-inline-input-validations/actions/workflows/test.yml/badge.svg)](https://github.com/mikerayux/stimulus-inline-input-validations/actions/workflows/ci.yml) 2 | # Stimulus Inline Input Validations 3 | 4 | [Try the Demo Here](https://mikerayux.github.io/stimulus-inline-input-validations/) 5 | 6 | [Check out the screencast](https://www.youtube.com/watch?v=XUPmmgzc2ZY) 7 | 8 | A Stimulus controller for validating form inputs and rendering their errors in a custom error element. Validations are 9 | performed on input and blur events. 10 | 11 | Looking for another awesome validations library to try? Check out [Formulus](https://github.com/marcoroth/formulus) 12 | 13 | ## Installation 14 | 15 | [StimulusJS](https://stimulusjs.org) installation is required. 16 | 17 | Add the package to your project: 18 | 19 | ```bash 20 | yarn add stimulus-inline-input-validations 21 | ``` 22 | 23 | Import and register `InputValidator` to your application 24 | 25 | ```javascript 26 | import {InputValidator} from "stimulus-inline-input-validations" 27 | application.register("input-validator", InputValidator) 28 | ``` 29 | 30 | 1. Add `data-controller="input-validator"` to your form or any parent element of `` elements you want to validate. 31 | 32 | 33 | ```html 34 |
35 | ... 36 |
37 | 38 | ``` 39 | 2. Add an `` element with the `data-input-validator-target="field"` and `data-field` attribute that uniquely identifies the field. 40 | 41 | ```html 42 |
43 | 48 | ... 49 |
50 | 51 | ``` 52 | 53 | 3. Add an errors element with the `data-input-validator-target="errors"` attribute and a `data-field` name that matches the 54 | corrosponding input element. This is where any errors from the matching `data-field` will be rendered. 55 | 56 | ```html 57 |
58 | 62 | 63 |
Errors will be rendered here
67 |
68 | ``` 69 | 70 | 4. Add, mix, and match validations attributes to the input field 71 | 72 | ```html 73 |
74 | 83 | 84 |
88 |
89 | ``` 90 | 91 | ## Standard Validation attributes 92 | 93 | | Attribute | Description | Renders | 94 | | -------- | ----------- | --------------- | 95 | | `data-validates-presence` | Validates presence | `
Can't be blank
` 96 | | `data-validates-length="5,10"` | Validates length in format `"min,max"` | `
Too short. Must be 5 characters long
`| 97 | | `data-validates-numericality` | Ensures value is a Number | `
Must be a number
`| 98 | | `data-validates-email` | Ensures value is in Email format | `
Invalid email format
`| 99 | | `data-validates-strong-password` | Ensures value is strong password | `
Must be at least 10 characters long
,
Must contain at least one special character (!@#$%^&*)
,
Must contain at least one capital letter (A-Z)
`| 100 | | `data-validations="[{"presence": true}, {"email": true}, {"numericality": true}, {"length": {"min": 5, "max": 10}}]"` | Handles multiple validations from a json-friendly-string| `
...
...
...
...
`| 101 | 102 | 103 | ## Multiple validations passed as a json-friendly string 104 | 105 | You can also pass multiple validations as a json-friendly string with the `data-validations` attribute. 106 | 107 | Example: 108 | 109 | ```html 110 | 116 | ``` 117 | 118 | Will render 119 | 120 | ```html 121 |
122 |
Can't be blank
123 |
Too short. Must be 5 characters long
124 |
Must be a number
125 |
Invalid email format
126 |
127 | ``` 128 | 129 | ## Usage in Rails: Leveraging existing model validations in Rails form helpers 130 | 131 | 1. Add a `json_validations_for` method to `application_helper.rb` 132 | 133 | ```ruby 134 | module ApplicationHelper 135 | def json_validations_for(model, field) 136 | validations_hash = {} 137 | 138 | validators = model.class.validators_on(field) 139 | validators.each do |validator| 140 | validator_name = validator.class.name.demodulize.underscore.to_sym 141 | 142 | if validator_name == :length_validator 143 | options = validator.options.dup 144 | validations_hash[:length] = { min: options[:minimum].present? ? options[:minimum] : 1, 145 | max: options[:maximum].present? ? options[:maximum] : 1000 } 146 | end 147 | 148 | validations_hash[:presence] = true if validator_name == :presence_validator 149 | validations_hash[:numericality] = true if validator_name == :numericality_validator 150 | end 151 | 152 | validations_hash[:strong_password] = true if field == :password 153 | validations_hash[:email] = true if field == :email 154 | 155 | validations = validations_hash.map do |key, value| 156 | { key.to_s => value } 157 | end 158 | 159 | validations.to_json.html_safe 160 | end 161 | end 162 | ``` 163 | 164 | 2. Use the `json_validations_for` helper method in your Rails form helpers 165 | 166 | ```erb 167 | <%= f.text_field :email, 168 | data: { 169 | input_validator_target:"field", 170 | field: :email, 171 | validations: json_validations_for(@user, :email) 172 | } %> 173 |
174 | ``` 175 | 176 | Make sure you have a matching errors element with `data-input-validator-target="errors"` and matching `data-field=""` 177 | 178 | 179 | ## i18n 180 | 181 | You can use the `data-input-validator-i18n-locale` attribute to specify a locale for error messages. 182 | 183 | ```html 184 |
185 | ... 186 | 187 | ``` 188 | 189 | Supported languages values: 190 | 191 | | Value | Language | 192 | | -------- | ----------- | 193 | | `en` | English | 194 | | `es` | Spanish | 195 | | `fr` | French | 196 | | `pt-BR` | Portugese (Brazil) | 197 | | `zh-CN` | Chinese (Simplified) | 198 | | `zh-TW` | Chinese (Traditional) | 199 | -------------------------------------------------------------------------------- /bin/check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit when any command fails 4 | set -e 5 | 6 | # Check Javascript code formatting 7 | echo "npx eslint src" 8 | yarn run eslint src 9 | 10 | # Check HTML formatting 11 | echo "htmlhint ./index.html" 12 | yarn run htmlhint ./index.html 13 | 14 | # Run the test suite 15 | echo "yarn cypress run --spec 'cypress/e2e/**/*.cy.js' --browser chrome" 16 | yarn cypress run --spec 'cypress/e2e/**/*.cy.js' --browser chrome 17 | -------------------------------------------------------------------------------- /cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require("cypress"); 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | setupNodeEvents(on, config) { 6 | // implement node event listeners here 7 | }, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /cypress/e2e/all_specs.cy.js: -------------------------------------------------------------------------------- 1 | // import "./events/input/presence.cy"; 2 | // import "./events/input/length.cy"; 3 | // import "./events/input/numericality.cy"; 4 | // import "./events/input/email.cy"; 5 | // import "./events/input/multiple_validations.cy"; 6 | // import "./events/input/json_validations.cy"; 7 | // import "./events/input/strong_password.cy"; 8 | 9 | // import "./events/blur/presence.cy"; 10 | // import "./events/blur/length.cy"; 11 | // import "./events/blur/numericality.cy"; 12 | // import "./events/blur/email.cy"; 13 | // import "./events/blur/multiple_validations.cy"; 14 | // import "./events/blur/json_validations.cy"; 15 | // import "./events/blur/strong_password.cy"; 16 | 17 | // import "./styles/css.cy"; 18 | // import "./styles/classes.cy"; 19 | 20 | // import "./i18n/locales/en.cy"; 21 | // import "./i18n/locales/es.cy"; 22 | // import "./i18n/locales/fr.cy"; 23 | // import "./i18n/locales/invalid_locale.cy"; 24 | // import "./i18n/locales/zh_cn.cy"; 25 | // import "./i18n/locales/zh_tw.cy"; 26 | -------------------------------------------------------------------------------- /cypress/e2e/demo_playback/demo.cy.js: -------------------------------------------------------------------------------- 1 | // describe("demo_playback", () => { 2 | // const TYPING_DELAY_MS = 100; 3 | // const FOCUS_FIELD_DELAY_MS = 500; 4 | // const SHORT_DELAY_MS = 500; 5 | // const SHOW_OFF_DELAY_MS = 850; 6 | 7 | // it(`showcases validations functionality`, () => { 8 | // cy.visit("http://localhost:8080/"); 9 | 10 | // // Presence START 11 | // cy.get( 12 | // `input[data-validates-presence="true"][data-field='fullName']` 13 | // ).click(); 14 | // cy.wait(FOCUS_FIELD_DELAY_MS); 15 | 16 | // cy.get(`input[data-validates-presence="true"][data-field='fullName']`) 17 | // .type("sample text", { delay: TYPING_DELAY_MS }) 18 | // .clear(); 19 | // cy.wait(SHOW_OFF_DELAY_MS); 20 | // // Presence END 21 | 22 | // // Length START 23 | // cy.get(`input[data-validates-length="5,10"][data-field='userName']`).click(); 24 | // cy.wait(FOCUS_FIELD_DELAY_MS); 25 | 26 | // cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 27 | // "abcd", 28 | // { delay: TYPING_DELAY_MS } 29 | // ); 30 | // cy.wait(SHOW_OFF_DELAY_MS); 31 | 32 | // cy.get(`input[data-validates-length="5,10"][data-field='userName']`).clear(); 33 | // cy.wait(SHORT_DELAY_MS); 34 | 35 | // cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 36 | // "longer text", 37 | // { delay: TYPING_DELAY_MS } 38 | // ); 39 | // cy.wait(SHOW_OFF_DELAY_MS); 40 | // // Length END 41 | 42 | // // Numericality START 43 | // cy.get(`input[data-validates-numericality="true"][data-field='currency']`).click(); 44 | // cy.wait(FOCUS_FIELD_DELAY_MS); 45 | 46 | // cy.get( 47 | // `input[data-validates-numericality="true"][data-field='currency']` 48 | // ).type("12345", { delay: TYPING_DELAY_MS }); 49 | // cy.wait(SHOW_OFF_DELAY_MS); 50 | 51 | // cy.get( 52 | // `input[data-validates-numericality="true"][data-field='currency']` 53 | // ).clear(); 54 | // cy.wait(SHORT_DELAY_MS); 55 | 56 | // cy.get( 57 | // `input[data-validates-numericality="true"][data-field='currency']` 58 | // ).type("abcdefg", { delay: TYPING_DELAY_MS }); 59 | // cy.wait(SHOW_OFF_DELAY_MS); 60 | // // Numericality END 61 | 62 | // // Email START 63 | // cy.get(`input[data-validates-email="true"][data-field='emailField']`).click(); 64 | // cy.wait(FOCUS_FIELD_DELAY_MS); 65 | 66 | // cy.get( 67 | // `input[data-validates-email="true"][data-field='emailField']` 68 | // ).type("example.test@example.com", { delay: 0 }); 69 | // cy.wait(SHOW_OFF_DELAY_MS); 70 | 71 | // cy.get( 72 | // `input[data-validates-email="true"][data-field='emailField']` 73 | // ).clear(); 74 | // cy.wait(SHORT_DELAY_MS); 75 | 76 | // cy.get( 77 | // `input[data-validates-email="true"][data-field='emailField']` 78 | // ).type("12345", { delay: 0 }); 79 | // cy.wait(SHOW_OFF_DELAY_MS); 80 | // // Email END 81 | 82 | // // Multiple validations START 83 | // cy.get(`input[data-field='multiple']`).click(); 84 | // cy.wait(FOCUS_FIELD_DELAY_MS); 85 | 86 | // cy.get( 87 | // `input[data-field='multiple']` 88 | // ).type("abc", { delay: TYPING_DELAY_MS }); 89 | // cy.wait(SHOW_OFF_DELAY_MS); 90 | // // Multiple validations END 91 | // }); 92 | // }); 93 | -------------------------------------------------------------------------------- /cypress/e2e/events/blur/email.cy.js: -------------------------------------------------------------------------------- 1 | describe("blur/email", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`Renders email format error on blur`, () => { 7 | if (Cypress.browser.name === "firefox") { 8 | cy.log("realPress events library is not supported on firefox. Skipping."); 9 | return; 10 | } 11 | cy.get(`input[data-validates-email][data-field='emailField']`) 12 | .focus() 13 | .realPress("Tab"); 14 | 15 | cy.get( 16 | 'div[data-input-validator-target="errors"][data-field="emailField"]' 17 | ).within(() => { 18 | cy.get(`div[error="email"]`).should("exist"); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /cypress/e2e/events/blur/json_validations.cy.js: -------------------------------------------------------------------------------- 1 | describe("blur/json_validations", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`Renders errors from json_string passed to data-validations attribute`, () => { 7 | if (Cypress.browser.name === "firefox") { 8 | cy.log("realPress events library is not supported on firefox. Skipping."); 9 | return; 10 | } 11 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`) 12 | .focus() 13 | .realPress("Tab"); 14 | 15 | cy.get( 16 | 'div[data-input-validator-target="errors"][data-field="jsonBulkValidations"]' 17 | ).within(() => { 18 | cy.get(`div[error="presence"]`).should("exist"); 19 | cy.get(`div[error="length-min"]`).should("exist"); 20 | cy.get(`div[error="strong-password-capital-letter"]`).should("exist"); 21 | cy.get(`div[error="strong-password-number"]`).should("exist"); 22 | cy.get(`div[error="strong-password-special-character"]`).should("exist"); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /cypress/e2e/events/blur/length.cy.js: -------------------------------------------------------------------------------- 1 | describe("blur/length", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`Renders length error on blur`, () => { 7 | if (Cypress.browser.name === "firefox") { 8 | cy.log("realPress events library is not supported on firefox. Skipping."); 9 | return; 10 | } 11 | cy.get( 12 | `input[data-validates-length][data-field='userName']` 13 | ).focus().realPress("Tab"); 14 | 15 | cy.get( 16 | 'div[data-input-validator-target="errors"][data-field="userName"]' 17 | ).within(() => { 18 | cy.get(`div[error="length-min"]`) 19 | .should("exist") 20 | }); 21 | }); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /cypress/e2e/events/blur/multiple_validations.cy.js: -------------------------------------------------------------------------------- 1 | describe("blur/multiple_validations", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`Renders multiple errors on blur`, () => { 7 | if (Cypress.browser.name === "firefox") { 8 | cy.log("realPress events library is not supported on firefox. Skipping."); 9 | return; 10 | } 11 | cy.get(`input[data-validates-email][data-field='multiple']`) 12 | .focus() 13 | .realPress("Tab"); 14 | 15 | cy.get( 16 | 'div[data-input-validator-target="errors"][data-field="multiple"]' 17 | ).within(() => { 18 | cy.get(`div[error="presence"]`).should("exist"); 19 | cy.get(`div[error="length-min"]`).should("exist"); 20 | cy.get(`div[error="email"]`).should("exist"); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /cypress/e2e/events/blur/numericality.cy.js: -------------------------------------------------------------------------------- 1 | describe("blur/numericality", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`Renders numericality error on blur`, () => { 7 | if (Cypress.browser.name === "firefox") { 8 | cy.log("realPress events library is not supported on firefox. Skipping."); 9 | return; 10 | } 11 | cy.get(`input[data-validates-numericality][data-field='currency']`) 12 | .focus() 13 | .realPress("Tab"); 14 | 15 | cy.get( 16 | 'div[data-input-validator-target="errors"][data-field="currency"]' 17 | ).within(() => { 18 | cy.get(`div[error="numericality"]`).should("exist"); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /cypress/e2e/events/blur/presence.cy.js: -------------------------------------------------------------------------------- 1 | describe("blur/presence", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`Renders presence error on blur`, () => { 7 | if (Cypress.browser.name === "firefox") { 8 | cy.log("realPress events library is not supported on firefox. Skipping."); 9 | return; 10 | } 11 | cy.get(`input[data-validates-presence][data-field='fullName']`) 12 | .focus() 13 | .realPress("Tab"); 14 | 15 | cy.get( 16 | 'div[data-input-validator-target="errors"][data-field="fullName"]' 17 | ).within(() => { 18 | cy.get(`div[error="presence"]`) 19 | .should("exist") 20 | .should("contain", "Can't be blank"); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /cypress/e2e/events/blur/strong_password.cy.js: -------------------------------------------------------------------------------- 1 | describe("blur/strong_password", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`Renders length error on blur`, () => { 7 | if (Cypress.browser.name === "firefox") { 8 | cy.log("realPress events library is not supported on firefox. Skipping."); 9 | return; 10 | } 11 | cy.get(`input[data-validates-strong-password][data-field='passwordField']`) 12 | .type("abc") 13 | .focus() 14 | .realPress("Tab"); 15 | 16 | cy.get( 17 | 'div[data-input-validator-target="errors"][data-field="passwordField"]' 18 | ).within(() => { 19 | cy.get(`div[error="strong-password-length"]`) 20 | .should("exist") 21 | .should("contain", "Must be at least 10 characters long"); 22 | cy.get(`div[error="strong-password-number"]`) 23 | .should("exist") 24 | .should("contain", "Must contain at least one number"); 25 | cy.get(`div[error="strong-password-capital-letter"]`) 26 | .should("exist") 27 | .should("contain", "Must contain at least one capital letter (A-Z)"); 28 | cy.get(`div[error="strong-password-special-character"]`) 29 | .should("exist") 30 | .should( 31 | "contain", 32 | "Must contain at least one special character (!@#$%^&*)" 33 | ); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /cypress/e2e/events/input/email.cy.js: -------------------------------------------------------------------------------- 1 | describe("input/email", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`Renders error div on blank input field with data-validates-numericality attribute set to true`, () => { 7 | cy.get(`input[data-validates-email][data-field='emailField']`) 8 | .type("abc") 9 | 10 | cy.get( 11 | 'div[data-input-validator-target="errors"][data-field="emailField"]' 12 | ).within(() => { 13 | cy.get(`div[error="email"]`).should("exist").should("contain", "Invalid email format"); 14 | }); 15 | }); 16 | 17 | it(`Doesnt render error div on valid email`, () => { 18 | cy.get(`input[data-validates-email][data-field='emailField']`) 19 | .type("abc@sample.com") 20 | 21 | cy.get( 22 | 'div[data-input-validator-target="errors"][data-field="emailField"]' 23 | ).within(() => { 24 | cy.get(`div[error="email"]`).should("not.exist") 25 | }); 26 | }); 27 | 28 | 29 | it(`Renders error div on blank input field with data-validates-email attribute set to true`, () => { 30 | cy.get(`input[data-validates-email][data-field='emailField']`) 31 | .invoke("attr", "data-validates-email", "") 32 | 33 | cy.get(`input[data-validates-email][data-field='emailField']`) 34 | .type("abc") 35 | 36 | cy.get( 37 | 'div[data-input-validator-target="errors"][data-field="emailField"]' 38 | ).within(() => { 39 | cy.get(`div[error="email"]`).should("exist").should("contain", "Invalid email format"); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /cypress/e2e/events/input/json_validations.cy.js: -------------------------------------------------------------------------------- 1 | describe("input/json_validations", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`Renders errors from json_string passed to data-validations attribute`, () => { 7 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`).invoke( 8 | "attr", 9 | "data-validations", 10 | "" 11 | ); 12 | 13 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`).invoke( 14 | "attr", 15 | "data-validations", '[{"length": {"min": 5, "max": 10}}, {"email": true}]'); 16 | 17 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`) 18 | .type("abc") 19 | 20 | cy.get( 21 | 'div[data-input-validator-target="errors"][data-field="jsonBulkValidations"]' 22 | ).within(() => { 23 | cy.get(`div[error="email"]`).should("exist"); 24 | cy.get(`div[error="length-min"]`).should("exist"); 25 | }); 26 | }); 27 | 28 | it(`Accepts JSON in double quotes within a single quoted attribute`, () => { 29 | const jsonInSingleQuotes = '[{"email": true}, {"length": {"min": 5, "max": 10}}]' 30 | 31 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`) 32 | .type("abc") 33 | .invoke("attr", "data-validations", ""); 34 | 35 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`) 36 | .type("abc") 37 | .invoke("attr", "data-validations", jsonInSingleQuotes); 38 | 39 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`) 40 | .type("abc") 41 | .clear(); 42 | 43 | cy.get( 44 | 'div[data-input-validator-target="errors"][data-field="jsonBulkValidations"]' 45 | ).within(() => { 46 | cy.get(`div[error="email"]`).should("exist"); 47 | cy.get(`div[error="length-min"]`).should("exist"); 48 | }); 49 | }); 50 | 51 | it(`Accepts JSON in single within a single quoted attribute`, () => { 52 | const jsonInDoubleQuotes = "[{'email': true}, {'length': {'min': 5, 'max': 10}}]" 53 | 54 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`) 55 | .type("abc") 56 | .invoke("attr", "data-validations", ""); 57 | 58 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`) 59 | .type("abc") 60 | .invoke("attr", "data-validations", jsonInDoubleQuotes); 61 | 62 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`) 63 | .type("abc") 64 | .clear(); 65 | 66 | cy.get( 67 | 'div[data-input-validator-target="errors"][data-field="jsonBulkValidations"]' 68 | ).within(() => { 69 | cy.get(`div[error="email"]`).should("exist"); 70 | cy.get(`div[error="length-min"]`).should("exist"); 71 | }); 72 | }); 73 | 74 | it(`Defaults to strong_password length if length key is not included in validations array`, () => { 75 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`).invoke( 76 | "attr", 77 | "data-validations", 78 | "" 79 | ); 80 | 81 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`).invoke( 82 | "attr", 83 | "data-validations", 84 | '[{"presence": true}, {"strong_password":true}]' 85 | ); 86 | 87 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`).type( 88 | "abc" 89 | ); 90 | 91 | cy.get( 92 | 'div[data-input-validator-target="errors"][data-field="jsonBulkValidations"]' 93 | ).within(() => { 94 | cy.get(`div[error="strong-password-length"]`).should("exist"); 95 | cy.get(`div[error="length-min"]`).should("not.exist"); 96 | }); 97 | }); 98 | 99 | it(`Doesn't show duplicate length errors twice if length and strong_password keys both exist in the validations array`, () => { 100 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`).invoke( 101 | "attr", 102 | "data-validations", 103 | "" 104 | ); 105 | 106 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`).invoke( 107 | "attr", 108 | "data-validations", 109 | '[{"presence": true}, {"length": {"min":6, "max":128}}, {"strong_password":true}]' 110 | ); 111 | 112 | cy.get(`input[data-field="jsonBulkValidations"][data-validations]`).type( 113 | "abc" 114 | ); 115 | 116 | cy.get( 117 | 'div[data-input-validator-target="errors"][data-field="jsonBulkValidations"]' 118 | ).within(() => { 119 | cy.get(`div[error="length-min"]`).should("exist"); 120 | cy.get(`div[error="strong-password-length"]`).should("not.exist"); 121 | }); 122 | }); 123 | }); 124 | -------------------------------------------------------------------------------- /cypress/e2e/events/input/length.cy.js: -------------------------------------------------------------------------------- 1 | describe("input/length", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`Renders min error if string too short`, () => { 7 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 8 | "abc" 9 | ); 10 | 11 | cy.get( 12 | 'div[data-input-validator-target="errors"][data-field="userName"]' 13 | ).within(() => { 14 | cy.get(`div[error="length-min"]`) 15 | .should("exist") 16 | .should("contain", "Too short. Minimum 5 characters"); 17 | }); 18 | }); 19 | 20 | it(`Renders max error if string too long`, () => { 21 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 22 | "12345678910" 23 | ); 24 | 25 | cy.get( 26 | 'div[data-input-validator-target="errors"][data-field="userName"]' 27 | ).within(() => { 28 | cy.get(`div[error="length-max"]`) 29 | .should("exist") 30 | .should("contain", "Too long. Maximum 10 characters"); 31 | }); 32 | }); 33 | 34 | it(`Shows no errors if valid min/max`, () => { 35 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 36 | "123456" 37 | ); 38 | 39 | cy.get( 40 | 'div[data-input-validator-target="errors"][data-field="userName"]' 41 | ).within(() => { 42 | cy.get(`div[error="length-min"]`).should("not.exist"); 43 | cy.get(`div[error="length-max"]`).should("not.exist"); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /cypress/e2e/events/input/multiple_validations.cy.js: -------------------------------------------------------------------------------- 1 | describe("input/multiple_validations", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`Renders multiple errors on field with multiple validations specified`, () => { 7 | cy.get(`input[data-field='multiple']`).type("abc") 8 | 9 | cy.get(`div[data-input-validator-target="errors"][data-field="multiple"]`).within(() => { 10 | cy.get(`div[error="length-min"]`).should("exist") 11 | cy.get(`div[error="email"]`).should("exist"); 12 | }); 13 | 14 | cy.get(`input[data-field='multiple']`).type("abcabcdefghijklmnopqrstuvwxyz12345678910111213141516") 15 | 16 | cy.get(`div[data-input-validator-target="errors"][data-field="multiple"]`).within(() => { 17 | cy.get(`div[error="length-max"]`).should("exist") 18 | cy.get(`div[error="email"]`).should("exist"); 19 | }); 20 | 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /cypress/e2e/events/input/numericality.cy.js: -------------------------------------------------------------------------------- 1 | describe("input/numericality", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`Renders error div on blank input field with data-validates-numericality attribute set to true`, () => { 7 | cy.get(`input[data-validates-numericality][data-field='currency']`) 8 | .type("abc") 9 | 10 | cy.get( 11 | 'div[data-input-validator-target="errors"][data-field="currency"]' 12 | ).within(() => { 13 | cy.get(`div[error="numericality"]`).should("exist").should("contain", "Must be a number"); 14 | }); 15 | }); 16 | 17 | it(`Renders error div on blank input field with data-validates-numericality attribute set to true`, () => { 18 | cy.get(`input[data-validates-numericality][data-field='currency']`) 19 | .invoke("attr", "data-validates-numericality", "") 20 | 21 | cy.get(`input[data-validates-numericality][data-field='currency']`) 22 | .type("abc") 23 | 24 | cy.get( 25 | 'div[data-input-validator-target="errors"][data-field="currency"]' 26 | ).within(() => { 27 | cy.get(`div[error="numericality"]`).should("exist").should("contain", "Must be a number"); 28 | }); 29 | }); 30 | 31 | it(`Doesn't render error on valid number`, () => { 32 | cy.get(`input[data-validates-numericality][data-field='currency']`) 33 | .type("123") 34 | 35 | cy.get( 36 | 'div[data-input-validator-target="errors"][data-field="currency"]' 37 | ).within(() => { 38 | cy.get(`div[error="numericality"]`).should("not.exist"); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /cypress/e2e/events/input/presence.cy.js: -------------------------------------------------------------------------------- 1 | describe("input/presence", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`Renders error div on blank input field with validate-presence attribute set to true`, () => { 7 | cy.get(`input[data-validates-presence][data-field='fullName']`) 8 | .type("abc") 9 | .clear(); 10 | 11 | cy.get( 12 | 'div[data-input-validator-target="errors"][data-field="fullName"]' 13 | ).within(() => { 14 | cy.get(`div[error="presence"]`).should("exist").should("contain", "Can't be blank"); 15 | }); 16 | }); 17 | 18 | it(`Renders error div on blank input with blank data-validates-presence attribute`, () => { 19 | cy.get( 20 | `input[data-validates-presence][data-field='fullName']` 21 | ).invoke("attr", "data-validates-presence", ""); 22 | 23 | cy.get(`input[data-validates-presence][data-field='fullName']`) 24 | .type("abc") 25 | .clear(); 26 | 27 | cy.get(`input[data-validates-presence][data-field='fullName']`).should( 28 | "have.attr", 29 | "data-validates-presence", 30 | "" 31 | ); 32 | cy.get( 33 | 'div[data-input-validator-target="errors"][data-field="fullName"]' 34 | ).within(() => { 35 | cy.get(`div[error="presence"]`).should("exist").should("contain", "Can't be blank"); 36 | }); 37 | }); 38 | 39 | it("Hides errors div when valid", () => { 40 | let field = cy.get( 41 | `input[data-validates-presence][data-field='fullName']` 42 | ); 43 | 44 | field.type("abc").clear().type("abc"); 45 | 46 | cy.get( 47 | 'div[data-input-validator-target="errors"][data-field="fullName"]' 48 | ).within(() => { 49 | cy.get(`div[error="presence"]`).should("not.exist"); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /cypress/e2e/events/input/strong_password.cy.js: -------------------------------------------------------------------------------- 1 | import { getRandomStrongPassword } from "../../../support/spec_helper"; 2 | 3 | describe("input/strong_password", () => { 4 | beforeEach(() => { 5 | cy.visit("cypress/fixtures/fields.html"); 6 | }); 7 | 8 | it(`Renders default length error`, () => { 9 | cy.get( 10 | `input[data-validates-strong-password][data-field='passwordField']` 11 | ).type("Abc!"); 12 | 13 | cy.get( 14 | 'div[data-input-validator-target="errors"][data-field="passwordField"]' 15 | ).within(() => { 16 | cy.get(`div[error="strong-password-length"]`) 17 | .should("exist") 18 | .should("contain", "Must be at least 10 characters long"); 19 | }); 20 | }); 21 | 22 | it(`Renders capital letter error`, () => { 23 | cy.get( 24 | `input[data-validates-strong-password][data-field='passwordField']` 25 | ).type("abc!"); 26 | 27 | cy.get( 28 | 'div[data-input-validator-target="errors"][data-field="passwordField"]' 29 | ).within(() => { 30 | cy.get(`div[error="strong-password-capital-letter"]`) 31 | .should("exist") 32 | .should("contain", "Must contain at least one capital letter (A-Z)"); 33 | }); 34 | }); 35 | 36 | it(`Renders number error`, () => { 37 | cy.get( 38 | `input[data-validates-strong-password][data-field='passwordField']` 39 | ).type("abc!"); 40 | 41 | cy.get(`div[error="strong-password-number"]`) 42 | .should("exist") 43 | .should("contain", "Must contain at least one number"); 44 | }); 45 | 46 | it(`Renders special character error`, () => { 47 | cy.get( 48 | `input[data-validates-strong-password][data-field='passwordField']` 49 | ).type("Abc"); 50 | 51 | cy.get( 52 | 'div[data-input-validator-target="errors"][data-field="passwordField"]' 53 | ).within(() => { 54 | cy.get(`div[error="strong-password-special-character"]`) 55 | .should("exist") 56 | .should( 57 | "contain", 58 | "Must contain at least one special character (!@#$%^&*)" 59 | ); 60 | }); 61 | }); 62 | 63 | it(`Renders all errors`, () => { 64 | cy.get( 65 | `input[data-validates-strong-password][data-field='passwordField']` 66 | ).type("abc"); 67 | 68 | cy.get( 69 | 'div[data-input-validator-target="errors"][data-field="passwordField"]' 70 | ).within(() => { 71 | cy.get(`div[error="strong-password-length"]`) 72 | .should("exist") 73 | .should("contain", "Must be at least 10 characters long"); 74 | cy.get(`div[error="strong-password-number"]`) 75 | .should("exist") 76 | .should("contain", "Must contain at least one number"); 77 | cy.get(`div[error="strong-password-capital-letter"]`) 78 | .should("exist") 79 | .should("contain", "Must contain at least one capital letter (A-Z)"); 80 | cy.get(`div[error="strong-password-special-character"]`) 81 | .should("exist") 82 | .should( 83 | "contain", 84 | "Must contain at least one special character (!@#$%^&*)" 85 | ); 86 | }); 87 | }); 88 | 89 | it(`Doesn't renders erros on valid password`, () => { 90 | cy.get( 91 | `input[data-validates-strong-password][data-field='passwordField']` 92 | ).type(getRandomStrongPassword(10)); 93 | 94 | cy.get( 95 | 'div[data-input-validator-target="errors"][data-field="passwordField"]' 96 | ).within(() => { 97 | cy.get(`div[error="strong-password-length"]`).should("not.exist"); 98 | cy.get(`div[error="strong-password-capital-letter"]`).should("not.exist"); 99 | cy.get(`div[error="strong-password-special-character"]`).should( 100 | "not.exist" 101 | ); 102 | }); 103 | }); 104 | }); 105 | -------------------------------------------------------------------------------- /cypress/e2e/i18n/locales/en.cy.js: -------------------------------------------------------------------------------- 1 | describe("i18n/locales/en", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/i18n/locales/en.html"); 4 | }); 5 | 6 | it(`Renders presence error in en`, () => { 7 | cy.get(`input[data-validates-presence][data-field='fullName']`) 8 | .type("abc") 9 | .clear(); 10 | 11 | cy.get( 12 | 'div[data-input-validator-target="errors"][data-field="fullName"]' 13 | ).within(() => { 14 | cy.get(`div[error="presence"]`) 15 | .should("exist") 16 | .should("contain", "Can't be blank"); 17 | }); 18 | }); 19 | 20 | it(`Renders length-min error in en`, () => { 21 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 22 | "abc" 23 | ); 24 | 25 | cy.get( 26 | 'div[data-input-validator-target="errors"][data-field="userName"]' 27 | ).within(() => { 28 | cy.get(`div[error="length-min"]`) 29 | .should("exist") 30 | .should("contain", "Too short. Minimum 5 characters"); 31 | }); 32 | }); 33 | 34 | it(`Renders length-max error in en`, () => { 35 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 36 | "12345678910" 37 | ); 38 | 39 | cy.get( 40 | 'div[data-input-validator-target="errors"][data-field="userName"]' 41 | ).within(() => { 42 | cy.get(`div[error="length-max"]`) 43 | .should("exist") 44 | .should("contain", "Too long. Maximum 10 characters"); 45 | }); 46 | }); 47 | 48 | it(`Renders numericality error in en`, () => { 49 | cy.get(`input[data-validates-numericality][data-field='currency']`).type( 50 | "abc" 51 | ); 52 | 53 | cy.get( 54 | 'div[data-input-validator-target="errors"][data-field="currency"]' 55 | ).within(() => { 56 | cy.get(`div[error="numericality"]`) 57 | .should("exist") 58 | .should("contain", "Must be a number"); 59 | }); 60 | }); 61 | 62 | it(`Renders email format error in en`, () => { 63 | cy.get(`input[data-validates-email][data-field='emailField']`).type("abc"); 64 | 65 | cy.get( 66 | 'div[data-input-validator-target="errors"][data-field="emailField"]' 67 | ).within(() => { 68 | cy.get(`div[error="email"]`) 69 | .should("exist") 70 | .should("contain", "Invalid email format"); 71 | }); 72 | }); 73 | 74 | it(`Renders strong_password errors in en`, () => { 75 | cy.get( 76 | `input[data-validates-strong-password][data-field='passwordField']` 77 | ).type("abc"); 78 | 79 | cy.get( 80 | 'div[data-input-validator-target="errors"][data-field="passwordField"]' 81 | ).within(() => { 82 | cy.get(`div[error="strong-password-length"]`) 83 | .should("exist") 84 | .should("contain", "Must be at least 10 characters long"); 85 | cy.get(`div[error="strong-password-number"]`) 86 | .should("exist") 87 | .should("contain", "Must contain at least one number"); 88 | cy.get(`div[error="strong-password-capital-letter"]`) 89 | .should("exist") 90 | .should("contain", "Must contain at least one capital letter (A-Z)"); 91 | cy.get(`div[error="strong-password-special-character"]`) 92 | .should("exist") 93 | .should( 94 | "contain", 95 | "Must contain at least one special character (!@#$%^&*)" 96 | ); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /cypress/e2e/i18n/locales/es.cy.js: -------------------------------------------------------------------------------- 1 | describe("i18n/locales/es", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/i18n/locales/es.html"); 4 | }); 5 | 6 | it(`Renders presence error in es`, () => { 7 | cy.get(`input[data-validates-presence][data-field='fullName']`) 8 | .type("abc") 9 | .clear(); 10 | 11 | cy.get( 12 | 'div[data-input-validator-target="errors"][data-field="fullName"]' 13 | ).within(() => { 14 | cy.get(`div[error="presence"]`) 15 | .should("exist") 16 | .should("contain", "no puede estar en blanco"); 17 | }); 18 | }); 19 | 20 | it(`Renders length-min error in es`, () => { 21 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 22 | "abc" 23 | ); 24 | 25 | cy.get( 26 | 'div[data-input-validator-target="errors"][data-field="userName"]' 27 | ).within(() => { 28 | cy.get(`div[error="length-min"]`) 29 | .should("exist") 30 | .should("contain", "Demasiado corto. Mínimo 5 caracteres"); 31 | }); 32 | }); 33 | 34 | it(`Renders length-max error in es`, () => { 35 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 36 | "12345678910" 37 | ); 38 | 39 | cy.get( 40 | 'div[data-input-validator-target="errors"][data-field="userName"]' 41 | ).within(() => { 42 | cy.get(`div[error="length-max"]`) 43 | .should("exist") 44 | .should("contain", "Demasiado largo. Máximo 10 caracteres"); 45 | }); 46 | }); 47 | 48 | it(`Renders numericality error in es`, () => { 49 | cy.get(`input[data-validates-numericality][data-field='currency']`).type( 50 | "abc" 51 | ); 52 | 53 | cy.get( 54 | 'div[data-input-validator-target="errors"][data-field="currency"]' 55 | ).within(() => { 56 | cy.get(`div[error="numericality"]`) 57 | .should("exist") 58 | .should("contain", "Tiene que ser un número"); 59 | }); 60 | }); 61 | 62 | it(`Renders email format error in es`, () => { 63 | cy.get(`input[data-validates-email][data-field='emailField']`).type("abc"); 64 | 65 | cy.get( 66 | 'div[data-input-validator-target="errors"][data-field="emailField"]' 67 | ).within(() => { 68 | cy.get(`div[error="email"]`) 69 | .should("exist") 70 | .should("contain", "Formato de correo inválido"); 71 | }); 72 | }); 73 | 74 | it(`Renders strong_password errors in es`, () => { 75 | cy.get( 76 | `input[data-validates-strong-password][data-field='passwordField']` 77 | ).type("abc"); 78 | 79 | cy.get( 80 | 'div[data-input-validator-target="errors"][data-field="passwordField"]' 81 | ).within(() => { 82 | cy.get(`div[error="strong-password-capital-letter"]`) 83 | .should("exist") 84 | .should("contain", "Debe contener al menos una letra mayúscula (A-Z)"); 85 | cy.get(`div[error="strong-password-number"]`) 86 | .should("exist") 87 | .should("contain", "Debe contener al menos un número"); 88 | cy.get(`div[error="strong-password-special-character"]`) 89 | .should("exist") 90 | .should( 91 | "contain", 92 | "Debe contener al menos un carácter especial (!@#$%^&*)" 93 | ); 94 | cy.get(`div[error="strong-password-length"]`) 95 | .should("exist") 96 | .should("contain", "Debe tener al menos 10 caracteres"); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /cypress/e2e/i18n/locales/fr.cy.js: -------------------------------------------------------------------------------- 1 | describe("i18n/locales/fr", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/i18n/locales/fr.html"); 4 | }); 5 | 6 | it(`Renders presence error in fr`, () => { 7 | cy.get(`input[data-validates-presence][data-field='fullName']`) 8 | .type("abc") 9 | .clear(); 10 | 11 | cy.get( 12 | 'div[data-input-validator-target="errors"][data-field="fullName"]' 13 | ).within(() => { 14 | cy.get(`div[error="presence"]`) 15 | .should("exist") 16 | .should("contain", "Je ne peux pas être vide"); 17 | }); 18 | }); 19 | 20 | it(`Renders length-min error in fr`, () => { 21 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 22 | "abc" 23 | ); 24 | 25 | cy.get( 26 | 'div[data-input-validator-target="errors"][data-field="userName"]' 27 | ).within(() => { 28 | cy.get(`div[error="length-min"]`) 29 | .should("exist") 30 | .should("contain", "Trop court. Minimum 5 caractères"); 31 | }); 32 | }); 33 | 34 | it(`Renders length-max error in fr`, () => { 35 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 36 | "12345678910" 37 | ); 38 | 39 | cy.get( 40 | 'div[data-input-validator-target="errors"][data-field="userName"]' 41 | ).within(() => { 42 | cy.get(`div[error="length-max"]`) 43 | .should("exist") 44 | .should("contain", "Trop long. 10 caractères maximum"); 45 | }); 46 | }); 47 | 48 | it(`Renders numericality error in fr`, () => { 49 | cy.get(`input[data-validates-numericality][data-field='currency']`).type( 50 | "abc" 51 | ); 52 | 53 | cy.get( 54 | 'div[data-input-validator-target="errors"][data-field="currency"]' 55 | ).within(() => { 56 | cy.get(`div[error="numericality"]`) 57 | .should("exist") 58 | .should("contain", "Doit être un nombre"); 59 | }); 60 | }); 61 | 62 | it(`Renders email format error in fr`, () => { 63 | cy.get(`input[data-validates-email][data-field='emailField']`).type("abc"); 64 | 65 | cy.get( 66 | 'div[data-input-validator-target="errors"][data-field="emailField"]' 67 | ).within(() => { 68 | cy.get(`div[error="email"]`) 69 | .should("exist") 70 | .should("contain", "Format d'email invalide"); 71 | }); 72 | }); 73 | 74 | it(`Renders strong_password errors in fr`, () => { 75 | cy.get( 76 | `input[data-validates-strong-password][data-field='passwordField']` 77 | ).type("abc"); 78 | 79 | cy.get( 80 | 'div[data-input-validator-target="errors"][data-field="passwordField"]' 81 | ).within(() => { 82 | cy.get(`div[error="strong-password-capital-letter"]`) 83 | .should("exist") 84 | .should("contain", "Doit contenir au moins une lettre majuscule (A-Z)"); 85 | cy.get(`div[error="strong-password-number"]`) 86 | .should("exist") 87 | .should("contain", "Doit contenir au moins un chiffre"); 88 | cy.get(`div[error="strong-password-special-character"]`) 89 | .should("exist") 90 | .should( 91 | "contain", 92 | "Doit contenir au moins un caractère spécial (!@#$%^&*)" 93 | ); 94 | cy.get(`div[error="strong-password-length"]`) 95 | .should("exist") 96 | .should("contain", "Doit contenir au moins 10 caractères"); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /cypress/e2e/i18n/locales/invalid_locale.cy.js: -------------------------------------------------------------------------------- 1 | describe("i18n/locales/invalid_locale", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/i18n/locales/invalid_locale.html"); 4 | }); 5 | 6 | it(`Renders warning to console and defaults to en if unsupported locale value is passed to data-input-validator-i18n-locale`, () => { 7 | cy.on("window:console", (consoleMessage) => { 8 | if (consoleMessage.type === "warn") { 9 | expect(consoleMessage.content).to.contain( 10 | `Stimulus Inline Input Validations: Unsupported i18n locale 'asdf'. Supported data-input-validator-i18n-locale values are: en, es, fr, pt-BR, zh-CN, zh-TW. Using default language 'en'` 11 | ); 12 | } 13 | }); 14 | 15 | cy.get(`input[data-validates-presence][data-field='fullName']`) 16 | .type("abc") 17 | .clear(); 18 | 19 | cy.get( 20 | 'div[data-input-validator-target="errors"][data-field="fullName"]' 21 | ).within(() => { 22 | cy.get(`div[error="presence"]`) 23 | .should("exist") 24 | .should("contain", "Can't be blank"); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /cypress/e2e/i18n/locales/pt_br.cy.js: -------------------------------------------------------------------------------- 1 | describe("i18n/locales/pt_br", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/i18n/locales/pt_br.html"); 4 | }); 5 | 6 | it(`Renders presence error in pt-BR`, () => { 7 | cy.get(`input[data-validates-presence][data-field='fullName']`) 8 | .type("abc") 9 | .clear(); 10 | 11 | cy.get( 12 | 'div[data-input-validator-target="errors"][data-field="fullName"]' 13 | ).within(() => { 14 | cy.get(`div[error="presence"]`) 15 | .should("exist") 16 | .should("contain", "Não posso ficar em branco"); 17 | }); 18 | }); 19 | 20 | it(`Renders length-min error in pt-BR`, () => { 21 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 22 | "abc" 23 | ); 24 | 25 | cy.get( 26 | 'div[data-input-validator-target="errors"][data-field="userName"]' 27 | ).within(() => { 28 | cy.get(`div[error="length-min"]`) 29 | .should("exist") 30 | .should("contain", "Muito curto. Mínimo 5 caracteres"); 31 | }); 32 | }); 33 | 34 | it(`Renders length-max error in pt-BR`, () => { 35 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 36 | "12345678910" 37 | ); 38 | 39 | cy.get( 40 | 'div[data-input-validator-target="errors"][data-field="userName"]' 41 | ).within(() => { 42 | cy.get(`div[error="length-max"]`) 43 | .should("exist") 44 | .should("contain", "Demasiado longo. Máximo de 10 caracteres"); 45 | }); 46 | }); 47 | 48 | it(`Renders numericality error in pt-BR`, () => { 49 | cy.get(`input[data-validates-numericality][data-field='currency']`).type( 50 | "abc" 51 | ); 52 | 53 | cy.get( 54 | 'div[data-input-validator-target="errors"][data-field="currency"]' 55 | ).within(() => { 56 | cy.get(`div[error="numericality"]`) 57 | .should("exist") 58 | .should("contain", "Deve ser um número"); 59 | }); 60 | }); 61 | 62 | it(`Renders email format error in pt-BR`, () => { 63 | cy.get(`input[data-validates-email][data-field='emailField']`).type("abc"); 64 | 65 | cy.get( 66 | 'div[data-input-validator-target="errors"][data-field="emailField"]' 67 | ).within(() => { 68 | cy.get(`div[error="email"]`) 69 | .should("exist") 70 | .should("contain", "Formato de email inválido"); 71 | }); 72 | }); 73 | 74 | it(`Renders strong_password errors in pt-BR`, () => { 75 | cy.get( 76 | `input[data-validates-strong-password][data-field='passwordField']` 77 | ).type("abc"); 78 | 79 | cy.get( 80 | 'div[data-input-validator-target="errors"][data-field="passwordField"]' 81 | ).within(() => { 82 | cy.get(`div[error="strong-password-capital-letter"]`) 83 | .should("exist") 84 | .should("contain", "Deve conter pelo menos uma letra maiúscula (A-Z)"); 85 | cy.get(`div[error="strong-password-number"]`) 86 | .should("exist") 87 | .should("contain", "Deve conter pelo menos um número"); 88 | cy.get(`div[error="strong-password-special-character"]`) 89 | .should("exist") 90 | .should( 91 | "contain", 92 | "Deve conter pelo menos um caractere especial (!@#$%^&*)" 93 | ); 94 | cy.get(`div[error="strong-password-length"]`) 95 | .should("exist") 96 | .should("contain", "Deve ter pelo menos 10 caracteres"); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /cypress/e2e/i18n/locales/zh_cn.cy.js: -------------------------------------------------------------------------------- 1 | describe("i18n/locales/zh_cn", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/i18n/locales/zh_cn.html"); 4 | }); 5 | 6 | it(`Renders presence error in zh-CN`, () => { 7 | cy.get(`input[data-validates-presence][data-field='fullName']`) 8 | .type("abc") 9 | .clear(); 10 | 11 | cy.get( 12 | 'div[data-input-validator-target="errors"][data-field="fullName"]' 13 | ).within(() => { 14 | cy.get(`div[error="presence"]`) 15 | .should("exist") 16 | .should("contain", "不能为空"); 17 | }); 18 | }); 19 | 20 | it(`Renders length-min error in zh-CN`, () => { 21 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 22 | "abc" 23 | ); 24 | 25 | cy.get( 26 | 'div[data-input-validator-target="errors"][data-field="userName"]' 27 | ).within(() => { 28 | cy.get(`div[error="length-min"]`) 29 | .should("exist") 30 | .should("contain", "过短。最少 5 个字符"); 31 | }); 32 | }); 33 | 34 | it(`Renders length-max error in zh-CN`, () => { 35 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 36 | "12345678910" 37 | ); 38 | 39 | cy.get( 40 | 'div[data-input-validator-target="errors"][data-field="userName"]' 41 | ).within(() => { 42 | cy.get(`div[error="length-max"]`) 43 | .should("exist") 44 | .should("contain", "太长。最多 10 个字符"); 45 | }); 46 | }); 47 | 48 | it(`Renders numericality error in zh-CN`, () => { 49 | cy.get(`input[data-validates-numericality][data-field='currency']`).type( 50 | "abc" 51 | ); 52 | 53 | cy.get( 54 | 'div[data-input-validator-target="errors"][data-field="currency"]' 55 | ).within(() => { 56 | cy.get(`div[error="numericality"]`) 57 | .should("exist") 58 | .should("contain", "必须是一个数字"); 59 | }); 60 | }); 61 | 62 | it(`Renders email format error in zh-CN`, () => { 63 | cy.get(`input[data-validates-email][data-field='emailField']`).type("abc"); 64 | 65 | cy.get( 66 | 'div[data-input-validator-target="errors"][data-field="emailField"]' 67 | ).within(() => { 68 | cy.get(`div[error="email"]`) 69 | .should("exist") 70 | .should("contain", "电子邮件格式无效"); 71 | }); 72 | }); 73 | 74 | it(`Renders strong_password errors in zh-CN`, () => { 75 | cy.get( 76 | `input[data-validates-strong-password][data-field='passwordField']` 77 | ).type("abc"); 78 | 79 | cy.get( 80 | 'div[data-input-validator-target="errors"][data-field="passwordField"]' 81 | ).within(() => { 82 | cy.get(`div[error="strong-password-capital-letter"]`) 83 | .should("exist") 84 | .should("contain", "必须包含至少一个大写字母 (A-Z)"); 85 | cy.get(`div[error="strong-password-number"]`) 86 | .should("exist") 87 | .should("contain", "必须至少包含一个数字"); 88 | cy.get(`div[error="strong-password-special-character"]`) 89 | .should("exist") 90 | .should( 91 | "contain", 92 | "必须至少包含一个特殊字符 (!@#$%^&*)" 93 | ); 94 | cy.get(`div[error="strong-password-length"]`) 95 | .should("exist") 96 | .should("contain", "长度必须至少 10 个字符"); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /cypress/e2e/i18n/locales/zh_tw.cy.js: -------------------------------------------------------------------------------- 1 | describe("i18n/locales/zh_tw", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/i18n/locales/zh_tw.html"); 4 | }); 5 | 6 | it(`Renders presence error in zh-TW`, () => { 7 | cy.get(`input[data-validates-presence][data-field='fullName']`) 8 | .type("abc") 9 | .clear(); 10 | 11 | cy.get( 12 | 'div[data-input-validator-target="errors"][data-field="fullName"]' 13 | ).within(() => { 14 | cy.get(`div[error="presence"]`) 15 | .should("exist") 16 | .should("contain", "不能為空"); 17 | }); 18 | }); 19 | 20 | it(`Renders length-min error in zh-TW`, () => { 21 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 22 | "abc" 23 | ); 24 | 25 | cy.get( 26 | 'div[data-input-validator-target="errors"][data-field="userName"]' 27 | ).within(() => { 28 | cy.get(`div[error="length-min"]`) 29 | .should("exist") 30 | .should("contain", "過短。最少 5 個字符"); 31 | }); 32 | }); 33 | 34 | it(`Renders length-max error in zh-TW`, () => { 35 | cy.get(`input[data-validates-length="5,10"][data-field='userName']`).type( 36 | "12345678910" 37 | ); 38 | 39 | cy.get( 40 | 'div[data-input-validator-target="errors"][data-field="userName"]' 41 | ).within(() => { 42 | cy.get(`div[error="length-max"]`) 43 | .should("exist") 44 | .should("contain", "太長。最多 10 個字符"); 45 | }); 46 | }); 47 | 48 | it(`Renders numericality error in zh-TW`, () => { 49 | cy.get(`input[data-validates-numericality][data-field='currency']`).type( 50 | "abc" 51 | ); 52 | 53 | cy.get( 54 | 'div[data-input-validator-target="errors"][data-field="currency"]' 55 | ).within(() => { 56 | cy.get(`div[error="numericality"]`) 57 | .should("exist") 58 | .should("contain", "必須是一個數字"); 59 | }); 60 | }); 61 | 62 | it(`Renders email format error in zh-TW`, () => { 63 | cy.get(`input[data-validates-email][data-field='emailField']`).type("abc"); 64 | 65 | cy.get( 66 | 'div[data-input-validator-target="errors"][data-field="emailField"]' 67 | ).within(() => { 68 | cy.get(`div[error="email"]`) 69 | .should("exist") 70 | .should("contain", "電子郵件格式無效"); 71 | }); 72 | }); 73 | 74 | it(`Renders strong_password errors in zh-TW`, () => { 75 | cy.get( 76 | `input[data-validates-strong-password][data-field='passwordField']` 77 | ).type("abc"); 78 | 79 | cy.get( 80 | 'div[data-input-validator-target="errors"][data-field="passwordField"]' 81 | ).within(() => { 82 | cy.get(`div[error="strong-password-capital-letter"]`) 83 | .should("exist") 84 | .should("contain", "必須包含至少一個大寫字母 (A-Z)"); 85 | cy.get(`div[error="strong-password-number"]`) 86 | .should("exist") 87 | .should("contain", "必須至少包含一個數字"); 88 | cy.get(`div[error="strong-password-special-character"]`) 89 | .should("exist") 90 | .should( 91 | "contain", 92 | "必須至少包含一個特殊字元 (!@#$%^&*)" 93 | ); 94 | cy.get(`div[error="strong-password-length"]`) 95 | .should("exist") 96 | .should("contain", "長度必須至少 10 個字符"); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /cypress/e2e/styles/classes.cy.js: -------------------------------------------------------------------------------- 1 | describe("styles/classes", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`It applys default css styles if none are passed as data-errors-styles-css attribute`, () => { 7 | cy.get(`input[data-validates-email][data-field="customClasses"]`) 8 | .type("abc") 9 | .clear(); 10 | 11 | cy.get( 12 | 'div[data-input-validator-target="errors"][data-field="customClasses"]' 13 | ).within(() => { 14 | cy.get(`div[error="email"]`) 15 | .should("exist") 16 | .should("not.have.attr", "style"); 17 | cy.get(`div[error="email"]`) 18 | .should("exist") 19 | .should("have.attr", "class") 20 | .and("include", "text-purple-600") 21 | .and("include", "font-bold"); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /cypress/e2e/styles/css.cy.js: -------------------------------------------------------------------------------- 1 | describe("styles/css", () => { 2 | beforeEach(() => { 3 | cy.visit("cypress/fixtures/fields.html"); 4 | }); 5 | 6 | it(`It applys default css styles if none are passed as data-errors-styles-css attribute`, () => { 7 | cy.get(`input[data-validates-email][data-field="customCSS"]`) 8 | .type("abc") 9 | .clear(); 10 | 11 | cy.get( 12 | 'div[data-input-validator-target="errors"][data-field="customCSS"]' 13 | ).within(() => { 14 | cy.get(`div[error="email"]`) 15 | .should("exist") 16 | .should("not.have.attr", "class") 17 | cy.get(`div[error="email"]`) 18 | .should("exist") 19 | .should("have.attr", "style") 20 | .and("include", "font-size: 14px") 21 | .and("include", "color: blue"); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /cypress/fixtures/fields.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stimulus Inline Input Validations 7 | 8 | 11 | 12 | 20 | 21 | 30 | 31 | 32 | 33 | 34 |
35 | 37 | 38 |
39 |
40 | 41 | 42 | 43 |
44 | 46 | 47 |
48 |
49 | 50 | 51 | 52 |
53 | 55 | 56 |
57 |
58 | 59 | 60 |
61 | 63 | 64 |
65 |
66 | 67 | 68 | 69 |
70 | 72 | 73 |
74 |
75 | 76 | 77 | 78 |
79 | 81 | 82 |
83 |
84 | 85 | 86 | 87 |
88 | 91 | 92 |
93 |
94 | 95 | 96 |
97 | 99 | 100 |
102 |
103 | 104 | 105 | 106 |
107 | 109 | 110 |
112 |
113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /cypress/fixtures/i18n/locales/en.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stimulus Inline Input Validations 7 | 10 | 11 | 19 | 20 | 29 | 30 | 31 | 32 | 33 |
34 | 36 | 39 |
40 |
41 | 42 | 43 | 44 |
45 | 47 | 50 |
51 |
52 | 53 | 54 | 55 |
56 | 58 | 61 |
62 |
63 | 64 | 65 | 66 |
67 | 69 | 72 |
73 |
74 | 75 | 76 | 77 |
78 | 80 | 83 |
84 |
85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /cypress/fixtures/i18n/locales/es.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stimulus Inline Input Validations 7 | 10 | 11 | 19 | 20 | 29 | 30 | 31 | 32 | 33 |
34 | 36 | 39 |
40 |
41 | 42 | 43 | 44 |
45 | 47 | 50 |
51 |
52 | 53 | 54 | 55 |
56 | 58 | 61 |
62 |
63 | 64 | 65 | 66 |
67 | 69 | 72 |
73 |
74 | 75 | 76 | 77 |
78 | 80 | 83 |
84 |
85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /cypress/fixtures/i18n/locales/fr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stimulus Inline Input Validations 7 | 10 | 11 | 19 | 20 | 29 | 30 | 31 | 32 | 33 |
34 | 37 | 40 |
41 |
42 | 43 | 44 | 45 |
46 | 49 | 52 |
53 |
54 | 55 | 56 | 57 |
58 | 61 | 64 |
65 |
66 | 67 | 68 | 69 |
70 | 73 | 76 |
77 |
78 | 79 | 80 | 81 |
82 | 85 | 88 |
89 |
90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /cypress/fixtures/i18n/locales/invalid_locale.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stimulus Inline Input Validations 7 | 10 | 11 | 19 | 20 | 29 | 30 | 31 | 32 | 33 |
34 | 37 | 40 |
41 |
42 | 43 | 44 | 45 |
46 | 49 | 52 |
53 |
54 | 55 | 56 | 57 |
58 | 61 | 64 |
65 |
66 | 67 | 68 | 69 |
70 | 73 | 76 |
77 |
78 | 79 | 80 | 81 |
82 | 85 | 88 |
89 |
90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /cypress/fixtures/i18n/locales/pt_br.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stimulus Inline Input Validations 7 | 10 | 11 | 19 | 20 | 29 | 30 | 31 | 32 | 33 |
34 | 37 | 40 |
41 |
42 | 43 | 44 | 45 |
46 | 49 | 52 |
53 |
54 | 55 | 56 | 57 |
58 | 61 | 64 |
65 |
66 | 67 | 68 | 69 |
70 | 73 | 76 |
77 |
78 | 79 | 80 | 81 |
82 | 85 | 88 |
89 |
90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /cypress/fixtures/i18n/locales/zh_cn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stimulus Inline Input Validations 7 | 10 | 11 | 19 | 20 | 29 | 30 | 31 | 32 | 33 |
34 | 37 | 40 |
41 |
42 | 43 | 44 | 45 |
46 | 49 | 52 |
53 |
54 | 55 | 56 | 57 |
58 | 61 | 64 |
65 |
66 | 67 | 68 | 69 |
70 | 73 | 76 |
77 |
78 | 79 | 80 | 81 |
82 | 85 | 88 |
89 |
90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /cypress/fixtures/i18n/locales/zh_tw.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stimulus Inline Input Validations 7 | 10 | 11 | 19 | 20 | 29 | 30 | 31 | 32 | 33 |
34 | 37 | 40 |
41 |
42 | 43 | 44 | 45 |
46 | 49 | 52 |
53 |
54 | 55 | 56 | 57 |
58 | 61 | 64 |
65 |
66 | 67 | 68 | 69 |
70 | 73 | 76 |
77 |
78 | 79 | 80 | 81 |
82 | 85 | 88 |
89 |
90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/e2e.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | import "cypress-real-events"; 17 | // Import commands.js using ES2015 syntax: 18 | import './commands' 19 | 20 | // Alternatively you can use CommonJS syntax: 21 | // require('./commands') 22 | -------------------------------------------------------------------------------- /cypress/support/spec_helper.js: -------------------------------------------------------------------------------- 1 | const getRandomChar = (charSet) => { 2 | const randomIndex = Math.floor(Math.random() * charSet.length); 3 | return charSet.charAt(randomIndex); 4 | }; 5 | 6 | export function getRandomStrongPassword(length) { 7 | const lowercaseChars = "abcdefghijklmnopqrstuvwxyz"; 8 | const uppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 9 | const numericChars = "0123456789"; 10 | const specialChars = "!@#$%^&*"; 11 | 12 | const allChars = 13 | lowercaseChars + uppercaseChars + numericChars + specialChars; 14 | 15 | let password = ""; 16 | 17 | for (let i = 0; i < length - 4; i++) { 18 | const randomIndex = Math.floor(Math.random() * allChars.length); 19 | password += allChars.charAt(randomIndex); 20 | } 21 | 22 | password += getRandomChar(lowercaseChars); 23 | password += getRandomChar(uppercaseChars); 24 | password += getRandomChar(numericChars); 25 | password += getRandomChar(specialChars); 26 | 27 | return password; 28 | } 29 | -------------------------------------------------------------------------------- /dist/stimulus-inline-input-validations.cjs: -------------------------------------------------------------------------------- 1 | var m=Object.defineProperty;var h=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var f=Object.prototype.hasOwnProperty;var b=(e,t,a)=>t in e?m(e,t,{enumerable:!0,configurable:!0,writable:!0,value:a}):e[t]=a;var y=(e,t)=>{for(var a in t)m(e,a,{get:t[a],enumerable:!0})},v=(e,t,a,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of g(t))!f.call(e,i)&&i!==a&&m(e,i,{get:()=>t[i],enumerable:!(s=h(t,i))||s.enumerable});return e};var A=e=>v(m({},"__esModule",{value:!0}),e);var d=(e,t,a)=>(b(e,typeof t!="symbol"?t+"":t,a),a);var P={};y(P,{InputValidator:()=>c});module.exports=A(P);var p=require("@hotwired/stimulus");var x={numericality:/^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/,email:/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,singleNumber:/[0-9]/,singleCapitalLetter:/[A-Z]/,singleSpecialCharacter:/[!@#$%^&*]/,singleQuotes:/'/},o=x;var w={locales:{en:{presence:"Can't be blank",length:{min:"Too short. Minimum {value} characters",max:"Too long. Maximum {value} characters"},numericality:"Must be a number",email:"Invalid email format",strongPassword:{capitalLetter:"Must contain at least one capital letter (A-Z)",number:"Must contain at least one number",specialCharacter:"Must contain at least one special character (!@#$%^&*)",length:"Must be at least 10 characters long"}},es:{presence:"no puede estar en blanco",length:{min:"Demasiado corto. M\xEDnimo {value} caracteres",max:"Demasiado largo. M\xE1ximo {value} caracteres"},numericality:"Tiene que ser un n\xFAmero",email:"Formato de correo inv\xE1lido",strongPassword:{capitalLetter:"Debe contener al menos una letra may\xFAscula (A-Z)",number:"Debe contener al menos un n\xFAmero",specialCharacter:"Debe contener al menos un car\xE1cter especial (!@#$%^&*)",length:"Debe tener al menos 10 caracteres"}},fr:{presence:"Je ne peux pas \xEAtre vide",length:{min:"Trop court. Minimum {value} caract\xE8res",max:"Trop long. {value} caract\xE8res maximum"},numericality:"Doit \xEAtre un nombre",email:"Format d'email invalide",strongPassword:{capitalLetter:"Doit contenir au moins une lettre majuscule (A-Z)",number:"Doit contenir au moins un chiffre",specialCharacter:"Doit contenir au moins un caract\xE8re sp\xE9cial (!@#$%^&*)",length:"Doit contenir au moins 10\xA0caract\xE8res"}},"pt-BR":{presence:"N\xE3o posso ficar em branco",length:{min:"Muito curto. M\xEDnimo {value} caracteres",max:"Demasiado longo. M\xE1ximo de {value} caracteres"},numericality:"Deve ser um n\xFAmero",email:"Formato de email inv\xE1lido",strongPassword:{capitalLetter:"Deve conter pelo menos uma letra mai\xFAscula (A-Z)",number:"Deve conter pelo menos um n\xFAmero",specialCharacter:"Deve conter pelo menos um caractere especial (!@#$%^&*)",length:"Deve ter pelo menos 10 caracteres"}},"zh-CN":{presence:"\u4E0D\u80FD\u4E3A\u7A7A",length:{min:"\u8FC7\u77ED\u3002\u6700\u5C11 {value} \u4E2A\u5B57\u7B26",max:"\u592A\u957F\u3002\u6700\u591A {value} \u4E2A\u5B57\u7B26"},numericality:"\u5FC5\u987B\u662F\u4E00\u4E2A\u6570\u5B57",email:"\u7535\u5B50\u90AE\u4EF6\u683C\u5F0F\u65E0\u6548",strongPassword:{capitalLetter:"\u5FC5\u987B\u5305\u542B\u81F3\u5C11\u4E00\u4E2A\u5927\u5199\u5B57\u6BCD (A-Z)",number:"\u5FC5\u987B\u81F3\u5C11\u5305\u542B\u4E00\u4E2A\u6570\u5B57",specialCharacter:"\u5FC5\u987B\u81F3\u5C11\u5305\u542B\u4E00\u4E2A\u7279\u6B8A\u5B57\u7B26 (!@#$%^&*)",length:"\u957F\u5EA6\u5FC5\u987B\u81F3\u5C11 10 \u4E2A\u5B57\u7B26"}},"zh-TW":{presence:"\u4E0D\u80FD\u70BA\u7A7A",length:{min:"\u904E\u77ED\u3002\u6700\u5C11 {value} \u500B\u5B57\u7B26",max:"\u592A\u9577\u3002\u6700\u591A {value} \u500B\u5B57\u7B26"},numericality:"\u5FC5\u9808\u662F\u4E00\u500B\u6578\u5B57",email:"\u96FB\u5B50\u90F5\u4EF6\u683C\u5F0F\u7121\u6548",strongPassword:{capitalLetter:"\u5FC5\u9808\u5305\u542B\u81F3\u5C11\u4E00\u500B\u5927\u5BEB\u5B57\u6BCD (A-Z)",number:"\u5FC5\u9808\u81F3\u5C11\u5305\u542B\u4E00\u500B\u6578\u5B57",specialCharacter:"\u5FC5\u9808\u81F3\u5C11\u5305\u542B\u4E00\u500B\u7279\u6B8A\u5B57\u5143 (!@#$%^&*)",length:"\u9577\u5EA6\u5FC5\u9808\u81F3\u5C11 10 \u500B\u5B57\u7B26"}}}},n=w;var D={presence(e,t,a){e.trim().length===0&&t.push({type:"presence",message:n.locales[a].presence})},length(e,t,a,s){e.lengtht.max&&a.push({type:"length-max",message:n.locales[s].length.max.replace(/{value}/g,t.max)})},numericality(e,t,a){o.numericality.test(e)||t.push({type:"numericality",message:n.locales[a].numericality})},email(e,t,a){o.email.test(e)||t.push({type:"email",message:n.locales[a].email})},strongPassword(e,t,a,s){o.singleCapitalLetter.test(t)||a.push({type:"strong-password-capital-letter",message:n.locales[s].strongPassword.capitalLetter}),o.singleNumber.test(t)||a.push({type:"strong-password-number",message:n.locales[s].strongPassword.number}),o.singleSpecialCharacter.test(t)||a.push({type:"strong-password-special-character",message:n.locales[s].strongPassword.specialCharacter}),!(e.length&&e.some(i=>Object.keys(i).includes("length")))&&t.length<10&&a.push({type:"strong-password-length",message:n.locales[s].strongPassword.length})}},l=D;var M=["en","es","fr","pt-BR","zh-CN","zh-TW"],u=M;var c=class extends p.Controller{connect(){this.handlei18n(),this.fieldTargets.forEach(e=>{e.setAttribute("data-action","input->input-validator#validateInput"),e.addEventListener("blur",t=>{this.validateInput(t)})})}handlei18n(){let e=this.element.getAttribute("data-input-validator-i18n-locale")||"en";u.includes(e)?this.locale=e:(console.warn(`Stimulus Inline Input Validations: Unsupported i18n locale '${e}'. Supported data-input-validator-i18n-locale values are: ${u.join(", ")}. Using default language 'en'`),this.locale="en")}handleJSONValidations(e,t,a){t.forEach(s=>{let[i]=Object.keys(s);switch(i){case"presence":s.presence&&l.presence(e,a,this.locale);break;case"length":s.length.min&&s.length.max?l.length(e,s.length,a,this.locale):console.log("Couldn't validate length (missing keys min or max)");break;case"numericality":s.numericality&&l.numericality(e,a,this.locale);break;case"email":s.email&&l.email(e,a,this.locale);break;case"strong_password":s.strong_password&&l.strongPassword(t,e,a,this.locale);break;default:break}})}handleValidations(e,t,a){if(e.hasAttribute("data-validates-presence")&&e.getAttribute("data-validates-presence")!=="false"&&l.presence(t,a,this.locale),e.hasAttribute("data-validates-length")&&e.getAttribute("data-validates-length").length>2){let[s,i]=e.getAttribute("data-validates-length").split(",").map(Number);l.length(t,{min:s,max:i},a,this.locale)}e.hasAttribute("data-validates-numericality")&&e.getAttribute("data-validates-numericality")!=="false"&&l.numericality(t,a,this.locale),e.hasAttribute("data-validates-email")&&e.getAttribute("data-validates-email")!=="false"&&l.email(t,a,this.locale),e.hasAttribute("data-validates-strong-password")&&e.getAttribute("data-validates-strong-password")!=="false"&&l.strongPassword([],t,a,this.locale)}errorElement(e,t){let a="font-size: 14px; color: red",s;return e.hasAttribute("data-errors-styles-css")&&(a=e.getAttribute("data-errors-styles-css")),e.hasAttribute("data-errors-styles-class")&&(a=null,s=e.getAttribute("data-errors-styles-class")),`
${t.message}
`}validateInput({target:e,target:{value:t}}){let a=[],s=e.getAttribute("data-field");if(!s){console.log('one or more elements are the missing data-field="" attribute.');return}let[i]=this.errorsTargets.filter(r=>r.getAttribute("data-field")===s);if(e.hasAttribute("data-validations"))try{let r=e.getAttribute("data-validations");o.singleQuotes.test(r)&&(r=r.replace(/'/g,'"')),this.handleJSONValidations(t,JSON.parse(r),a)}catch(r){console.log(r),console.log(`Error parsing JSON string on the data-validations attribute on data-field="${s}". Is the json string formatted properly?`);return}else this.handleValidations(e,t,a);i.innerHTML="",a.length?a.forEach(r=>{i.innerHTML+=this.errorElement(i,r),i.style.visibility="visible"}):i.style.visibility="invisible"}};d(c,"targets",["field","errors"]); 2 | -------------------------------------------------------------------------------- /dist/stimulus-inline-input-validations.cjs.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../src/index.js", "../src/input_validator.js", "../src/helpers/regex.js", "../src/i18n/error_messages.js", "../src/validations/validate.js", "../src/i18n/supported_locales.js"], 4 | "sourcesContent": ["export { default as InputValidator } from './input_validator'\n", "import { Controller } from '@hotwired/stimulus'\nimport Validate from './validations/validate'\nimport Regex from './helpers/regex'\nimport SUPPORTED_LOCALES from './i18n/supported_locales'\n\n// Connects to data-controller=\"input-validator\"\nexport default class extends Controller {\n static targets = ['field', 'errors']\n\n connect () {\n this.handlei18n()\n\n this.fieldTargets.forEach((field) => {\n field.setAttribute('data-action', 'input->input-validator#validateInput')\n\n field.addEventListener('blur', (event) => {\n this.validateInput(event)\n })\n })\n }\n\n handlei18n () {\n const locale =\n this.element.getAttribute('data-input-validator-i18n-locale') || 'en'\n\n if (SUPPORTED_LOCALES.includes(locale)) {\n this.locale = locale\n } else {\n console.warn(\n `Stimulus Inline Input Validations: Unsupported i18n locale '${locale}'. Supported data-input-validator-i18n-locale values are: ${SUPPORTED_LOCALES.join(\n ', '\n )}. Using default language 'en'`\n )\n this.locale = 'en'\n }\n }\n\n handleJSONValidations (value, validations, errors) {\n validations.forEach((validation) => {\n const [validationType] = Object.keys(validation)\n\n switch (validationType) {\n case 'presence':\n if (validation.presence) {\n Validate.presence(value, errors, this.locale)\n }\n break\n case 'length':\n if (validation.length.min && validation.length.max) {\n Validate.length(value, validation.length, errors, this.locale)\n } else {\n console.log(\"Couldn't validate length (missing keys min or max)\")\n }\n break\n case 'numericality':\n if (validation.numericality) {\n Validate.numericality(value, errors, this.locale)\n }\n break\n case 'email':\n if (validation.email) {\n Validate.email(value, errors, this.locale)\n }\n break\n case 'strong_password':\n if (validation.strong_password) {\n Validate.strongPassword(validations, value, errors, this.locale)\n }\n break\n default:\n break\n }\n })\n }\n\n handleValidations (target, value, errors) {\n if (\n target.hasAttribute('data-validates-presence') &&\n target.getAttribute('data-validates-presence') !== 'false'\n ) {\n Validate.presence(value, errors, this.locale)\n }\n\n if (\n target.hasAttribute('data-validates-length') &&\n target.getAttribute('data-validates-length').length > 2\n ) {\n const [min, max] = target\n .getAttribute('data-validates-length')\n .split(',')\n .map(Number)\n\n Validate.length(value, { min, max }, errors, this.locale)\n }\n\n if (\n target.hasAttribute('data-validates-numericality') &&\n target.getAttribute('data-validates-numericality') !== 'false'\n ) {\n Validate.numericality(value, errors, this.locale)\n }\n\n if (\n target.hasAttribute('data-validates-email') &&\n target.getAttribute('data-validates-email') !== 'false'\n ) {\n Validate.email(value, errors, this.locale)\n }\n\n if (\n target.hasAttribute('data-validates-strong-password') &&\n target.getAttribute('data-validates-strong-password') !== 'false'\n ) {\n Validate.strongPassword([], value, errors, this.locale)\n }\n }\n\n errorElement (errorsContainer, error) {\n let styles = 'font-size: 14px; color: red'\n let classes\n\n if (errorsContainer.hasAttribute('data-errors-styles-css')) {\n styles = errorsContainer.getAttribute('data-errors-styles-css')\n }\n\n if (errorsContainer.hasAttribute('data-errors-styles-class')) {\n styles = null\n classes = errorsContainer.getAttribute('data-errors-styles-class')\n }\n\n return `
${error.message}
`\n }\n\n validateInput ({ target, target: { value } }) {\n const errors = []\n const field = target.getAttribute('data-field')\n\n if (!field) {\n console.log(\n 'one or more elements are the missing data-field=\"\" attribute.'\n )\n return\n }\n\n const [errorsContainer] = this.errorsTargets.filter(\n (item) => item.getAttribute('data-field') === field\n )\n\n if (target.hasAttribute('data-validations')) {\n try {\n let validations = target.getAttribute('data-validations')\n\n if (Regex.singleQuotes.test(validations)) {\n validations = validations.replace(/'/g, '\"')\n }\n\n this.handleJSONValidations(value, JSON.parse(validations), errors)\n } catch (error) {\n console.log(error)\n console.log(\n `Error parsing JSON string on the data-validations attribute on data-field=\"${field}\". Is the json string formatted properly?`\n )\n return\n }\n } else {\n this.handleValidations(target, value, errors)\n }\n\n errorsContainer.innerHTML = ''\n\n if (errors.length) {\n errors.forEach((error) => {\n errorsContainer.innerHTML += this.errorElement(errorsContainer, error)\n errorsContainer.style.visibility = 'visible'\n })\n } else {\n errorsContainer.style.visibility = 'invisible'\n }\n }\n}\n", "const Regex = {\n numericality: /^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)([eE][+-]?\\d+)?$/,\n email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/,\n singleNumber: /[0-9]/,\n singleCapitalLetter: /[A-Z]/,\n singleSpecialCharacter: /[!@#$%^&*]/,\n singleQuotes: /'/\n}\n\nexport default Regex\n", "const ErrorMessages = {\n locales: {\n en: {\n presence: \"Can't be blank\",\n length: {\n min: 'Too short. Minimum {value} characters',\n max: 'Too long. Maximum {value} characters'\n },\n numericality: 'Must be a number',\n email: 'Invalid email format',\n strongPassword: {\n capitalLetter: 'Must contain at least one capital letter (A-Z)',\n number: 'Must contain at least one number',\n specialCharacter:\n 'Must contain at least one special character (!@#$%^&*)',\n length: 'Must be at least 10 characters long'\n }\n },\n es: {\n presence: 'no puede estar en blanco',\n length: {\n min: 'Demasiado corto. M\u00EDnimo {value} caracteres',\n max: 'Demasiado largo. M\u00E1ximo {value} caracteres'\n },\n numericality: 'Tiene que ser un n\u00FAmero',\n email: 'Formato de correo inv\u00E1lido',\n strongPassword: {\n capitalLetter: 'Debe contener al menos una letra may\u00FAscula (A-Z)',\n number: 'Debe contener al menos un n\u00FAmero',\n specialCharacter:\n 'Debe contener al menos un car\u00E1cter especial (!@#$%^&*)',\n length: 'Debe tener al menos 10 caracteres'\n }\n },\n fr: {\n presence: 'Je ne peux pas \u00EAtre vide',\n length: {\n min: 'Trop court. Minimum {value} caract\u00E8res',\n max: 'Trop long. {value} caract\u00E8res maximum'\n },\n numericality: 'Doit \u00EAtre un nombre',\n email: \"Format d'email invalide\",\n strongPassword: {\n capitalLetter: 'Doit contenir au moins une lettre majuscule (A-Z)',\n number: 'Doit contenir au moins un chiffre',\n specialCharacter:\n 'Doit contenir au moins un caract\u00E8re sp\u00E9cial (!@#$%^&*)',\n length: 'Doit contenir au moins 10\u00A0caract\u00E8res'\n }\n },\n 'pt-BR': {\n presence: 'N\u00E3o posso ficar em branco',\n length: {\n min: 'Muito curto. M\u00EDnimo {value} caracteres',\n max: 'Demasiado longo. M\u00E1ximo de {value} caracteres'\n },\n numericality: 'Deve ser um n\u00FAmero',\n email: 'Formato de email inv\u00E1lido',\n strongPassword: {\n capitalLetter: 'Deve conter pelo menos uma letra mai\u00FAscula (A-Z)',\n number: 'Deve conter pelo menos um n\u00FAmero',\n specialCharacter:\n 'Deve conter pelo menos um caractere especial (!@#$%^&*)',\n length: 'Deve ter pelo menos 10 caracteres'\n }\n },\n 'zh-CN': {\n presence: '\u4E0D\u80FD\u4E3A\u7A7A',\n length: {\n min: '\u8FC7\u77ED\u3002\u6700\u5C11 {value} \u4E2A\u5B57\u7B26',\n max: '\u592A\u957F\u3002\u6700\u591A {value} \u4E2A\u5B57\u7B26'\n },\n numericality: '\u5FC5\u987B\u662F\u4E00\u4E2A\u6570\u5B57',\n email: '\u7535\u5B50\u90AE\u4EF6\u683C\u5F0F\u65E0\u6548',\n strongPassword: {\n capitalLetter: '\u5FC5\u987B\u5305\u542B\u81F3\u5C11\u4E00\u4E2A\u5927\u5199\u5B57\u6BCD (A-Z)',\n number: '\u5FC5\u987B\u81F3\u5C11\u5305\u542B\u4E00\u4E2A\u6570\u5B57',\n specialCharacter: '\u5FC5\u987B\u81F3\u5C11\u5305\u542B\u4E00\u4E2A\u7279\u6B8A\u5B57\u7B26 (!@#$%^&*)',\n length: '\u957F\u5EA6\u5FC5\u987B\u81F3\u5C11 10 \u4E2A\u5B57\u7B26'\n }\n },\n 'zh-TW': {\n presence: '\u4E0D\u80FD\u70BA\u7A7A',\n length: {\n min: '\u904E\u77ED\u3002\u6700\u5C11 {value} \u500B\u5B57\u7B26',\n max: '\u592A\u9577\u3002\u6700\u591A {value} \u500B\u5B57\u7B26'\n },\n numericality: '\u5FC5\u9808\u662F\u4E00\u500B\u6578\u5B57',\n email: '\u96FB\u5B50\u90F5\u4EF6\u683C\u5F0F\u7121\u6548',\n strongPassword: {\n capitalLetter: '\u5FC5\u9808\u5305\u542B\u81F3\u5C11\u4E00\u500B\u5927\u5BEB\u5B57\u6BCD (A-Z)',\n number: '\u5FC5\u9808\u81F3\u5C11\u5305\u542B\u4E00\u500B\u6578\u5B57',\n specialCharacter: '\u5FC5\u9808\u81F3\u5C11\u5305\u542B\u4E00\u500B\u7279\u6B8A\u5B57\u5143 (!@#$%^&*)',\n length: '\u9577\u5EA6\u5FC5\u9808\u81F3\u5C11 10 \u500B\u5B57\u7B26'\n }\n }\n }\n}\n\nexport default ErrorMessages\n", "import Regex from '../helpers/regex'\nimport ErrorMessages from '../i18n/error_messages'\n\nconst Validate = {\n presence (value, errors, locale) {\n if (value.trim().length === 0) {\n errors.push({\n type: 'presence',\n message: ErrorMessages.locales[locale].presence\n })\n }\n },\n\n length (value, length, errors, locale) {\n if (value.length < length.min) {\n errors.push({\n type: 'length-min',\n message: ErrorMessages.locales[locale].length.min.replace(\n /{value}/g,\n length.min\n )\n })\n }\n if (value.length > length.max) {\n errors.push({\n type: 'length-max',\n message: ErrorMessages.locales[locale].length.max.replace(\n /{value}/g,\n length.max\n )\n })\n }\n },\n\n numericality (value, errors, locale) {\n if (!Regex.numericality.test(value)) {\n errors.push({\n type: 'numericality',\n message: ErrorMessages.locales[locale].numericality\n })\n }\n },\n\n email (value, errors, locale) {\n if (!Regex.email.test(value)) {\n errors.push({\n type: 'email',\n message: ErrorMessages.locales[locale].email\n })\n }\n },\n\n strongPassword (validations, value, errors, locale) {\n if (!Regex.singleCapitalLetter.test(value)) {\n errors.push({\n type: 'strong-password-capital-letter',\n message: ErrorMessages.locales[locale].strongPassword.capitalLetter\n })\n }\n\n if (!Regex.singleNumber.test(value)) {\n errors.push({\n type: 'strong-password-number',\n message: ErrorMessages.locales[locale].strongPassword.number\n })\n }\n\n if (!Regex.singleSpecialCharacter.test(value)) {\n errors.push({\n type: 'strong-password-special-character',\n message: ErrorMessages.locales[locale].strongPassword.specialCharacter\n })\n }\n\n if (\n validations.length &&\n validations.some((validation) =>\n Object.keys(validation).includes('length')\n )\n ) {\n return\n }\n if (value.length < 10) {\n errors.push({\n type: 'strong-password-length',\n message: ErrorMessages.locales[locale].strongPassword.length\n })\n }\n }\n}\n\nexport default Validate\n", "const SUPPORTED_LOCALES = ['en', 'es', 'fr', 'pt-BR', 'zh-CN', 'zh-TW']\nexport default SUPPORTED_LOCALES\n"], 5 | "mappings": "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", 6 | "names": ["src_exports", "__export", "input_validator_default", "__toCommonJS", "import_stimulus", "Regex", "regex_default", "ErrorMessages", "error_messages_default", "Validate", "value", "errors", "locale", "error_messages_default", "length", "regex_default", "validations", "validation", "validate_default", "SUPPORTED_LOCALES", "supported_locales_default", "input_validator_default", "field", "event", "locale", "supported_locales_default", "value", "validations", "errors", "validation", "validationType", "validate_default", "target", "min", "max", "errorsContainer", "error", "styles", "classes", "item", "regex_default", "__publicField"] 7 | } 8 | -------------------------------------------------------------------------------- /dist/stimulus-inline-input-validations.module.js: -------------------------------------------------------------------------------- 1 | var d=Object.defineProperty;var p=(e,a,t)=>a in e?d(e,a,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[a]=t;var u=(e,a,t)=>(p(e,typeof a!="symbol"?a+"":a,t),t);import{Controller as y}from"@hotwired/stimulus";var h={numericality:/^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/,email:/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,singleNumber:/[0-9]/,singleCapitalLetter:/[A-Z]/,singleSpecialCharacter:/[!@#$%^&*]/,singleQuotes:/'/},o=h;var g={locales:{en:{presence:"Can't be blank",length:{min:"Too short. Minimum {value} characters",max:"Too long. Maximum {value} characters"},numericality:"Must be a number",email:"Invalid email format",strongPassword:{capitalLetter:"Must contain at least one capital letter (A-Z)",number:"Must contain at least one number",specialCharacter:"Must contain at least one special character (!@#$%^&*)",length:"Must be at least 10 characters long"}},es:{presence:"no puede estar en blanco",length:{min:"Demasiado corto. M\xEDnimo {value} caracteres",max:"Demasiado largo. M\xE1ximo {value} caracteres"},numericality:"Tiene que ser un n\xFAmero",email:"Formato de correo inv\xE1lido",strongPassword:{capitalLetter:"Debe contener al menos una letra may\xFAscula (A-Z)",number:"Debe contener al menos un n\xFAmero",specialCharacter:"Debe contener al menos un car\xE1cter especial (!@#$%^&*)",length:"Debe tener al menos 10 caracteres"}},fr:{presence:"Je ne peux pas \xEAtre vide",length:{min:"Trop court. Minimum {value} caract\xE8res",max:"Trop long. {value} caract\xE8res maximum"},numericality:"Doit \xEAtre un nombre",email:"Format d'email invalide",strongPassword:{capitalLetter:"Doit contenir au moins une lettre majuscule (A-Z)",number:"Doit contenir au moins un chiffre",specialCharacter:"Doit contenir au moins un caract\xE8re sp\xE9cial (!@#$%^&*)",length:"Doit contenir au moins 10\xA0caract\xE8res"}},"pt-BR":{presence:"N\xE3o posso ficar em branco",length:{min:"Muito curto. M\xEDnimo {value} caracteres",max:"Demasiado longo. M\xE1ximo de {value} caracteres"},numericality:"Deve ser um n\xFAmero",email:"Formato de email inv\xE1lido",strongPassword:{capitalLetter:"Deve conter pelo menos uma letra mai\xFAscula (A-Z)",number:"Deve conter pelo menos um n\xFAmero",specialCharacter:"Deve conter pelo menos um caractere especial (!@#$%^&*)",length:"Deve ter pelo menos 10 caracteres"}},"zh-CN":{presence:"\u4E0D\u80FD\u4E3A\u7A7A",length:{min:"\u8FC7\u77ED\u3002\u6700\u5C11 {value} \u4E2A\u5B57\u7B26",max:"\u592A\u957F\u3002\u6700\u591A {value} \u4E2A\u5B57\u7B26"},numericality:"\u5FC5\u987B\u662F\u4E00\u4E2A\u6570\u5B57",email:"\u7535\u5B50\u90AE\u4EF6\u683C\u5F0F\u65E0\u6548",strongPassword:{capitalLetter:"\u5FC5\u987B\u5305\u542B\u81F3\u5C11\u4E00\u4E2A\u5927\u5199\u5B57\u6BCD (A-Z)",number:"\u5FC5\u987B\u81F3\u5C11\u5305\u542B\u4E00\u4E2A\u6570\u5B57",specialCharacter:"\u5FC5\u987B\u81F3\u5C11\u5305\u542B\u4E00\u4E2A\u7279\u6B8A\u5B57\u7B26 (!@#$%^&*)",length:"\u957F\u5EA6\u5FC5\u987B\u81F3\u5C11 10 \u4E2A\u5B57\u7B26"}},"zh-TW":{presence:"\u4E0D\u80FD\u70BA\u7A7A",length:{min:"\u904E\u77ED\u3002\u6700\u5C11 {value} \u500B\u5B57\u7B26",max:"\u592A\u9577\u3002\u6700\u591A {value} \u500B\u5B57\u7B26"},numericality:"\u5FC5\u9808\u662F\u4E00\u500B\u6578\u5B57",email:"\u96FB\u5B50\u90F5\u4EF6\u683C\u5F0F\u7121\u6548",strongPassword:{capitalLetter:"\u5FC5\u9808\u5305\u542B\u81F3\u5C11\u4E00\u500B\u5927\u5BEB\u5B57\u6BCD (A-Z)",number:"\u5FC5\u9808\u81F3\u5C11\u5305\u542B\u4E00\u500B\u6578\u5B57",specialCharacter:"\u5FC5\u9808\u81F3\u5C11\u5305\u542B\u4E00\u500B\u7279\u6B8A\u5B57\u5143 (!@#$%^&*)",length:"\u9577\u5EA6\u5FC5\u9808\u81F3\u5C11 10 \u500B\u5B57\u7B26"}}}},n=g;var f={presence(e,a,t){e.trim().length===0&&a.push({type:"presence",message:n.locales[t].presence})},length(e,a,t,s){e.lengtha.max&&t.push({type:"length-max",message:n.locales[s].length.max.replace(/{value}/g,a.max)})},numericality(e,a,t){o.numericality.test(e)||a.push({type:"numericality",message:n.locales[t].numericality})},email(e,a,t){o.email.test(e)||a.push({type:"email",message:n.locales[t].email})},strongPassword(e,a,t,s){o.singleCapitalLetter.test(a)||t.push({type:"strong-password-capital-letter",message:n.locales[s].strongPassword.capitalLetter}),o.singleNumber.test(a)||t.push({type:"strong-password-number",message:n.locales[s].strongPassword.number}),o.singleSpecialCharacter.test(a)||t.push({type:"strong-password-special-character",message:n.locales[s].strongPassword.specialCharacter}),!(e.length&&e.some(i=>Object.keys(i).includes("length")))&&a.length<10&&t.push({type:"strong-password-length",message:n.locales[s].strongPassword.length})}},l=f;var b=["en","es","fr","pt-BR","zh-CN","zh-TW"],m=b;var c=class extends y{connect(){this.handlei18n(),this.fieldTargets.forEach(e=>{e.setAttribute("data-action","input->input-validator#validateInput"),e.addEventListener("blur",a=>{this.validateInput(a)})})}handlei18n(){let e=this.element.getAttribute("data-input-validator-i18n-locale")||"en";m.includes(e)?this.locale=e:(console.warn(`Stimulus Inline Input Validations: Unsupported i18n locale '${e}'. Supported data-input-validator-i18n-locale values are: ${m.join(", ")}. Using default language 'en'`),this.locale="en")}handleJSONValidations(e,a,t){a.forEach(s=>{let[i]=Object.keys(s);switch(i){case"presence":s.presence&&l.presence(e,t,this.locale);break;case"length":s.length.min&&s.length.max?l.length(e,s.length,t,this.locale):console.log("Couldn't validate length (missing keys min or max)");break;case"numericality":s.numericality&&l.numericality(e,t,this.locale);break;case"email":s.email&&l.email(e,t,this.locale);break;case"strong_password":s.strong_password&&l.strongPassword(a,e,t,this.locale);break;default:break}})}handleValidations(e,a,t){if(e.hasAttribute("data-validates-presence")&&e.getAttribute("data-validates-presence")!=="false"&&l.presence(a,t,this.locale),e.hasAttribute("data-validates-length")&&e.getAttribute("data-validates-length").length>2){let[s,i]=e.getAttribute("data-validates-length").split(",").map(Number);l.length(a,{min:s,max:i},t,this.locale)}e.hasAttribute("data-validates-numericality")&&e.getAttribute("data-validates-numericality")!=="false"&&l.numericality(a,t,this.locale),e.hasAttribute("data-validates-email")&&e.getAttribute("data-validates-email")!=="false"&&l.email(a,t,this.locale),e.hasAttribute("data-validates-strong-password")&&e.getAttribute("data-validates-strong-password")!=="false"&&l.strongPassword([],a,t,this.locale)}errorElement(e,a){let t="font-size: 14px; color: red",s;return e.hasAttribute("data-errors-styles-css")&&(t=e.getAttribute("data-errors-styles-css")),e.hasAttribute("data-errors-styles-class")&&(t=null,s=e.getAttribute("data-errors-styles-class")),`
${a.message}
`}validateInput({target:e,target:{value:a}}){let t=[],s=e.getAttribute("data-field");if(!s){console.log('one or more elements are the missing data-field="" attribute.');return}let[i]=this.errorsTargets.filter(r=>r.getAttribute("data-field")===s);if(e.hasAttribute("data-validations"))try{let r=e.getAttribute("data-validations");o.singleQuotes.test(r)&&(r=r.replace(/'/g,'"')),this.handleJSONValidations(a,JSON.parse(r),t)}catch(r){console.log(r),console.log(`Error parsing JSON string on the data-validations attribute on data-field="${s}". Is the json string formatted properly?`);return}else this.handleValidations(e,a,t);i.innerHTML="",t.length?t.forEach(r=>{i.innerHTML+=this.errorElement(i,r),i.style.visibility="visible"}):i.style.visibility="invisible"}};u(c,"targets",["field","errors"]);export{c as InputValidator}; 2 | -------------------------------------------------------------------------------- /dist/stimulus-inline-input-validations.module.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": ["../src/input_validator.js", "../src/helpers/regex.js", "../src/i18n/error_messages.js", "../src/validations/validate.js", "../src/i18n/supported_locales.js"], 4 | "sourcesContent": ["import { Controller } from '@hotwired/stimulus'\nimport Validate from './validations/validate'\nimport Regex from './helpers/regex'\nimport SUPPORTED_LOCALES from './i18n/supported_locales'\n\n// Connects to data-controller=\"input-validator\"\nexport default class extends Controller {\n static targets = ['field', 'errors']\n\n connect () {\n this.handlei18n()\n\n this.fieldTargets.forEach((field) => {\n field.setAttribute('data-action', 'input->input-validator#validateInput')\n\n field.addEventListener('blur', (event) => {\n this.validateInput(event)\n })\n })\n }\n\n handlei18n () {\n const locale =\n this.element.getAttribute('data-input-validator-i18n-locale') || 'en'\n\n if (SUPPORTED_LOCALES.includes(locale)) {\n this.locale = locale\n } else {\n console.warn(\n `Stimulus Inline Input Validations: Unsupported i18n locale '${locale}'. Supported data-input-validator-i18n-locale values are: ${SUPPORTED_LOCALES.join(\n ', '\n )}. Using default language 'en'`\n )\n this.locale = 'en'\n }\n }\n\n handleJSONValidations (value, validations, errors) {\n validations.forEach((validation) => {\n const [validationType] = Object.keys(validation)\n\n switch (validationType) {\n case 'presence':\n if (validation.presence) {\n Validate.presence(value, errors, this.locale)\n }\n break\n case 'length':\n if (validation.length.min && validation.length.max) {\n Validate.length(value, validation.length, errors, this.locale)\n } else {\n console.log(\"Couldn't validate length (missing keys min or max)\")\n }\n break\n case 'numericality':\n if (validation.numericality) {\n Validate.numericality(value, errors, this.locale)\n }\n break\n case 'email':\n if (validation.email) {\n Validate.email(value, errors, this.locale)\n }\n break\n case 'strong_password':\n if (validation.strong_password) {\n Validate.strongPassword(validations, value, errors, this.locale)\n }\n break\n default:\n break\n }\n })\n }\n\n handleValidations (target, value, errors) {\n if (\n target.hasAttribute('data-validates-presence') &&\n target.getAttribute('data-validates-presence') !== 'false'\n ) {\n Validate.presence(value, errors, this.locale)\n }\n\n if (\n target.hasAttribute('data-validates-length') &&\n target.getAttribute('data-validates-length').length > 2\n ) {\n const [min, max] = target\n .getAttribute('data-validates-length')\n .split(',')\n .map(Number)\n\n Validate.length(value, { min, max }, errors, this.locale)\n }\n\n if (\n target.hasAttribute('data-validates-numericality') &&\n target.getAttribute('data-validates-numericality') !== 'false'\n ) {\n Validate.numericality(value, errors, this.locale)\n }\n\n if (\n target.hasAttribute('data-validates-email') &&\n target.getAttribute('data-validates-email') !== 'false'\n ) {\n Validate.email(value, errors, this.locale)\n }\n\n if (\n target.hasAttribute('data-validates-strong-password') &&\n target.getAttribute('data-validates-strong-password') !== 'false'\n ) {\n Validate.strongPassword([], value, errors, this.locale)\n }\n }\n\n errorElement (errorsContainer, error) {\n let styles = 'font-size: 14px; color: red'\n let classes\n\n if (errorsContainer.hasAttribute('data-errors-styles-css')) {\n styles = errorsContainer.getAttribute('data-errors-styles-css')\n }\n\n if (errorsContainer.hasAttribute('data-errors-styles-class')) {\n styles = null\n classes = errorsContainer.getAttribute('data-errors-styles-class')\n }\n\n return `
${error.message}
`\n }\n\n validateInput ({ target, target: { value } }) {\n const errors = []\n const field = target.getAttribute('data-field')\n\n if (!field) {\n console.log(\n 'one or more elements are the missing data-field=\"\" attribute.'\n )\n return\n }\n\n const [errorsContainer] = this.errorsTargets.filter(\n (item) => item.getAttribute('data-field') === field\n )\n\n if (target.hasAttribute('data-validations')) {\n try {\n let validations = target.getAttribute('data-validations')\n\n if (Regex.singleQuotes.test(validations)) {\n validations = validations.replace(/'/g, '\"')\n }\n\n this.handleJSONValidations(value, JSON.parse(validations), errors)\n } catch (error) {\n console.log(error)\n console.log(\n `Error parsing JSON string on the data-validations attribute on data-field=\"${field}\". Is the json string formatted properly?`\n )\n return\n }\n } else {\n this.handleValidations(target, value, errors)\n }\n\n errorsContainer.innerHTML = ''\n\n if (errors.length) {\n errors.forEach((error) => {\n errorsContainer.innerHTML += this.errorElement(errorsContainer, error)\n errorsContainer.style.visibility = 'visible'\n })\n } else {\n errorsContainer.style.visibility = 'invisible'\n }\n }\n}\n", "const Regex = {\n numericality: /^[+-]?(\\d+(\\.\\d*)?|\\.\\d+)([eE][+-]?\\d+)?$/,\n email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/,\n singleNumber: /[0-9]/,\n singleCapitalLetter: /[A-Z]/,\n singleSpecialCharacter: /[!@#$%^&*]/,\n singleQuotes: /'/\n}\n\nexport default Regex\n", "const ErrorMessages = {\n locales: {\n en: {\n presence: \"Can't be blank\",\n length: {\n min: 'Too short. Minimum {value} characters',\n max: 'Too long. Maximum {value} characters'\n },\n numericality: 'Must be a number',\n email: 'Invalid email format',\n strongPassword: {\n capitalLetter: 'Must contain at least one capital letter (A-Z)',\n number: 'Must contain at least one number',\n specialCharacter:\n 'Must contain at least one special character (!@#$%^&*)',\n length: 'Must be at least 10 characters long'\n }\n },\n es: {\n presence: 'no puede estar en blanco',\n length: {\n min: 'Demasiado corto. M\u00EDnimo {value} caracteres',\n max: 'Demasiado largo. M\u00E1ximo {value} caracteres'\n },\n numericality: 'Tiene que ser un n\u00FAmero',\n email: 'Formato de correo inv\u00E1lido',\n strongPassword: {\n capitalLetter: 'Debe contener al menos una letra may\u00FAscula (A-Z)',\n number: 'Debe contener al menos un n\u00FAmero',\n specialCharacter:\n 'Debe contener al menos un car\u00E1cter especial (!@#$%^&*)',\n length: 'Debe tener al menos 10 caracteres'\n }\n },\n fr: {\n presence: 'Je ne peux pas \u00EAtre vide',\n length: {\n min: 'Trop court. Minimum {value} caract\u00E8res',\n max: 'Trop long. {value} caract\u00E8res maximum'\n },\n numericality: 'Doit \u00EAtre un nombre',\n email: \"Format d'email invalide\",\n strongPassword: {\n capitalLetter: 'Doit contenir au moins une lettre majuscule (A-Z)',\n number: 'Doit contenir au moins un chiffre',\n specialCharacter:\n 'Doit contenir au moins un caract\u00E8re sp\u00E9cial (!@#$%^&*)',\n length: 'Doit contenir au moins 10\u00A0caract\u00E8res'\n }\n },\n 'pt-BR': {\n presence: 'N\u00E3o posso ficar em branco',\n length: {\n min: 'Muito curto. M\u00EDnimo {value} caracteres',\n max: 'Demasiado longo. M\u00E1ximo de {value} caracteres'\n },\n numericality: 'Deve ser um n\u00FAmero',\n email: 'Formato de email inv\u00E1lido',\n strongPassword: {\n capitalLetter: 'Deve conter pelo menos uma letra mai\u00FAscula (A-Z)',\n number: 'Deve conter pelo menos um n\u00FAmero',\n specialCharacter:\n 'Deve conter pelo menos um caractere especial (!@#$%^&*)',\n length: 'Deve ter pelo menos 10 caracteres'\n }\n },\n 'zh-CN': {\n presence: '\u4E0D\u80FD\u4E3A\u7A7A',\n length: {\n min: '\u8FC7\u77ED\u3002\u6700\u5C11 {value} \u4E2A\u5B57\u7B26',\n max: '\u592A\u957F\u3002\u6700\u591A {value} \u4E2A\u5B57\u7B26'\n },\n numericality: '\u5FC5\u987B\u662F\u4E00\u4E2A\u6570\u5B57',\n email: '\u7535\u5B50\u90AE\u4EF6\u683C\u5F0F\u65E0\u6548',\n strongPassword: {\n capitalLetter: '\u5FC5\u987B\u5305\u542B\u81F3\u5C11\u4E00\u4E2A\u5927\u5199\u5B57\u6BCD (A-Z)',\n number: '\u5FC5\u987B\u81F3\u5C11\u5305\u542B\u4E00\u4E2A\u6570\u5B57',\n specialCharacter: '\u5FC5\u987B\u81F3\u5C11\u5305\u542B\u4E00\u4E2A\u7279\u6B8A\u5B57\u7B26 (!@#$%^&*)',\n length: '\u957F\u5EA6\u5FC5\u987B\u81F3\u5C11 10 \u4E2A\u5B57\u7B26'\n }\n },\n 'zh-TW': {\n presence: '\u4E0D\u80FD\u70BA\u7A7A',\n length: {\n min: '\u904E\u77ED\u3002\u6700\u5C11 {value} \u500B\u5B57\u7B26',\n max: '\u592A\u9577\u3002\u6700\u591A {value} \u500B\u5B57\u7B26'\n },\n numericality: '\u5FC5\u9808\u662F\u4E00\u500B\u6578\u5B57',\n email: '\u96FB\u5B50\u90F5\u4EF6\u683C\u5F0F\u7121\u6548',\n strongPassword: {\n capitalLetter: '\u5FC5\u9808\u5305\u542B\u81F3\u5C11\u4E00\u500B\u5927\u5BEB\u5B57\u6BCD (A-Z)',\n number: '\u5FC5\u9808\u81F3\u5C11\u5305\u542B\u4E00\u500B\u6578\u5B57',\n specialCharacter: '\u5FC5\u9808\u81F3\u5C11\u5305\u542B\u4E00\u500B\u7279\u6B8A\u5B57\u5143 (!@#$%^&*)',\n length: '\u9577\u5EA6\u5FC5\u9808\u81F3\u5C11 10 \u500B\u5B57\u7B26'\n }\n }\n }\n}\n\nexport default ErrorMessages\n", "import Regex from '../helpers/regex'\nimport ErrorMessages from '../i18n/error_messages'\n\nconst Validate = {\n presence (value, errors, locale) {\n if (value.trim().length === 0) {\n errors.push({\n type: 'presence',\n message: ErrorMessages.locales[locale].presence\n })\n }\n },\n\n length (value, length, errors, locale) {\n if (value.length < length.min) {\n errors.push({\n type: 'length-min',\n message: ErrorMessages.locales[locale].length.min.replace(\n /{value}/g,\n length.min\n )\n })\n }\n if (value.length > length.max) {\n errors.push({\n type: 'length-max',\n message: ErrorMessages.locales[locale].length.max.replace(\n /{value}/g,\n length.max\n )\n })\n }\n },\n\n numericality (value, errors, locale) {\n if (!Regex.numericality.test(value)) {\n errors.push({\n type: 'numericality',\n message: ErrorMessages.locales[locale].numericality\n })\n }\n },\n\n email (value, errors, locale) {\n if (!Regex.email.test(value)) {\n errors.push({\n type: 'email',\n message: ErrorMessages.locales[locale].email\n })\n }\n },\n\n strongPassword (validations, value, errors, locale) {\n if (!Regex.singleCapitalLetter.test(value)) {\n errors.push({\n type: 'strong-password-capital-letter',\n message: ErrorMessages.locales[locale].strongPassword.capitalLetter\n })\n }\n\n if (!Regex.singleNumber.test(value)) {\n errors.push({\n type: 'strong-password-number',\n message: ErrorMessages.locales[locale].strongPassword.number\n })\n }\n\n if (!Regex.singleSpecialCharacter.test(value)) {\n errors.push({\n type: 'strong-password-special-character',\n message: ErrorMessages.locales[locale].strongPassword.specialCharacter\n })\n }\n\n if (\n validations.length &&\n validations.some((validation) =>\n Object.keys(validation).includes('length')\n )\n ) {\n return\n }\n if (value.length < 10) {\n errors.push({\n type: 'strong-password-length',\n message: ErrorMessages.locales[locale].strongPassword.length\n })\n }\n }\n}\n\nexport default Validate\n", "const SUPPORTED_LOCALES = ['en', 'es', 'fr', 'pt-BR', 'zh-CN', 'zh-TW']\nexport default SUPPORTED_LOCALES\n"], 5 | "mappings": "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", 6 | "names": ["Controller", "Regex", "regex_default", "ErrorMessages", "error_messages_default", "Validate", "value", "errors", "locale", "error_messages_default", "length", "regex_default", "validations", "validation", "validate_default", "SUPPORTED_LOCALES", "supported_locales_default", "input_validator_default", "Controller", "field", "event", "locale", "supported_locales_default", "value", "validations", "errors", "validation", "validationType", "validate_default", "target", "min", "max", "errorsContainer", "error", "styles", "classes", "item", "regex_default", "__publicField"] 7 | } 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stimulus Inline Input Validations 7 | 8 | 11 | 12 | 13 | 14 | 15 | 22 | 30 | 31 | 40 | 41 | 42 | 43 |
44 | 45 |
46 |
47 |

48 | Stimulus Inline Input Validations 49 |

50 |

51 | A Stimulus controller for validating html form inputs and rendering their errors in a custom error element. 52 |

53 |
54 | status badge for download count of package 56 | status badge for version of package 58 | status badge for status of CI tests for package 60 | badge for licnese of package 62 |
63 | 74 |
75 | 76 |
77 | 78 |
79 | 80 |
81 |

82 | 1. Install the package 83 |

84 |
 85 |               
 86 | yarn add stimulus-inline-input-validations
 87 |             
 88 |             
89 |
90 | 91 |
92 | 93 | 94 |
95 | 96 |
97 |

98 | 2. Import and register the controller 99 |

100 |
101 |               
102 | import { InputValidator } from "stimulus-inline-input-validations"
103 | application.register("input-validator", InputValidator)
104 |               
105 |             
106 |
107 | 108 |
109 | 110 |
111 | 112 |
113 |
114 | 115 |
116 | 117 | 118 |
119 |

Validations as HTML attributes

120 |
121 | 122 |
123 |
124 |

125 | Length 126 |

127 | 128 | data-validates-length= 129 | "5,10" 130 | 131 |
132 |
133 |

Validates min,max length of 134 | a 135 | string

136 |
137 | 138 | 139 |
140 | 144 | 148 |
149 |
150 | 151 |
152 | 153 | 154 | 155 |
156 |
157 |

158 | Presence 159 |

160 | 161 | data-validates-presence 162 | 163 | 164 |
165 |
166 |

Validates that a field is not blank 167 |

168 |
169 | 170 | 171 |
172 | 175 | 179 |
180 |
181 | 182 |
183 | 184 | 185 | 186 |
187 |
188 |

189 | Numericality 190 |

191 | 192 | data-validates-numericality 193 | 194 | 195 |
196 |
197 |

Validates if a field is a number

198 |
199 | 200 | 201 |
202 | 206 | 210 |
211 |
212 | 213 |
214 | 215 | 216 | 217 |
218 |
219 |

220 | Email 221 |

222 | 223 | data-validates-email 224 | 225 | 226 |
227 |
228 |

Validates email format

229 |
230 | 231 | 232 |
233 | 236 | 240 |
241 |
242 | 243 |
244 | 245 | 246 | 247 |
248 |
249 |

250 | Strong password 251 |

252 | 253 | data-validates-strong-password 254 | 255 | 256 |
257 |
258 |

Validates strong password

259 |
260 | 261 | 262 |
263 | 267 | 271 |
272 |
273 | 274 |
275 | 276 |
277 |
278 | 279 | 280 | 281 |
282 |

Multiple Validations

283 |
284 | 285 |
286 |
287 |

288 | Multiple HTML attributes 289 |

290 | 291 | data-validates-* 292 | 293 |
294 |
295 |

Length, Presence, Email 296 |

297 |
298 | 299 | 300 |
301 | 305 | 309 |
310 |
311 | 312 |
313 | 314 | 315 | 316 |
317 |
318 |

319 | JSON 320 |

321 | 322 | data-validations= 323 | '[{"presence": true}, {"length": {"min":6, "max":128}}, 324 | {"strong_password":true}]' 325 | 326 |
327 |
328 |

Bulk validations via a json-friendly 329 | string

330 |
331 | 332 | 333 |
334 | 339 | 343 |
344 |
345 | 346 |
347 | 348 |
349 |
350 | 351 | 352 | 353 |
354 |

Custom Error Styles

355 |
356 | 357 |
358 |
359 |

360 | CSS 361 |

362 | 363 | data-errors-styles-css= 364 | "font-size: 14px; color: blue" 365 | 366 |
367 |
368 |

font-size: 14px; color: blue

369 |
370 | 371 | 372 |
373 | 376 | 380 |
382 |
383 | 384 |
385 | 386 | 387 | 388 |
389 |
390 |

391 | Classes 392 |

393 | 394 | data-errors-styles-class= 395 | "font-bold text-purple-600" 396 | 397 |
398 |
399 |

400 | font-bold text-purple-600 (Tailwind)

401 |
402 | 403 | 404 |
405 | 409 | 413 |
415 |
416 | 417 |
418 | 419 |
420 |
421 | 422 | 423 |
424 |

How to use

425 |
426 | 427 |
428 | 429 |
430 |

431 | 1. Add `data-controller="input-validator"` to your form or any parent element of `<input>` elements 432 | you want to validate. 433 |

434 |
435 |             
436 | <form data-controller="input-validator">
437 | ...
438 | </form>
439 |             
440 |           
441 |
442 | 443 |
444 | 445 | 446 | 447 |
448 | 449 |
450 |

451 | 2. Add `data-input-validator-target` and `data-field` attributes to an input element. 452 |

453 |
454 |             
455 | <input type="text" data-input-validator-target="field" data-field="userName">
456 |             
457 |           
458 |
459 | 460 |
461 | 462 | 463 | 464 |
465 | 466 |
467 |

468 | 3. Add an errors element with matching `data-field` attribute 469 |

470 |
471 |             
472 | <div data-input-validator-target="errors" data-field="userName"></div>
473 |             
474 |           
475 |
476 | 477 |
478 | 479 | 480 | 481 |
482 | 483 |
484 |

485 | 4. Add, mix, and match validations 486 |

487 |
488 |             
489 | <input ... data-validates-length="5,10" data-validates-numericality data-validates-email>
490 |             
491 |           
492 |
493 | 494 |
495 | 496 | 497 | 498 |
499 | 500 |
501 |

502 | Validations run on input/blur events, and will render any errors inside the errors element 503 |

504 |
505 |             
506 | <div data-input-validator-target="errors" data-field="userName">
507 |   <div error="length-min">Too short. Minimum 5 characters</div>
508 |   <div error="numericality">Must be a number</div>
509 |   <div error="email">Invalid email format</div>
510 | </div>
511 |             
512 |           
513 |
514 | 515 |
516 | 517 |
518 |
519 | 520 | 521 | 522 |
523 |

Usage in Rails:

524 |

Leveraging existing model validations in Rails form 525 | helpers

526 |
527 | 528 |
529 | 530 |
531 |

532 | 1. Add a `json_validations_for` method to `application_helper.rb` 533 |

534 |
535 |             
536 | module ApplicationHelper
537 |   def json_validations_for(model, field)
538 |     validations_hash = {}
539 | 
540 |     validators = model.class.validators_on(field)
541 |     validators.each do |validator|
542 |       validator_name = validator.class.name.demodulize.underscore.to_sym
543 | 
544 |       if validator_name == :length_validator
545 |         options = validator.options.dup
546 |         validations_hash[:length] = { min: options[:minimum].present? ? options[:minimum] : 1,
547 |         max: options[:maximum].present? ? options[:maximum] : 1000 }
548 |       end
549 | 
550 |       validations_hash[:presence] = true if validator_name == :presence_validator
551 |       validations_hash[:numericality] = true if validator_name == :numericality_validator
552 |     end
553 | 
554 |     validations_hash[:strong_password] = true if field == :password
555 |     validations_hash[:email] = true if field == :email
556 | 
557 |     validations = validations_hash.map do |key, value|
558 |       { key.to_s => value }
559 |     end
560 | 
561 |     validations.to_json.html_safe
562 |   end
563 | end
564 |             
565 |           
566 |
567 | 568 |
569 | 570 | 571 |
572 | 573 |
574 |

575 | 2. Use the `json_validations_for` helper method in your Rails form helpers 576 |

577 |
578 |             
579 | <%= f.text_field :email, 
580 |                  data: { 
581 |                   input_validator_target:"field",
582 |                   field: :email,
583 |                   validations: json_validations_for(@user, :email) 
584 |                  }%>
585 | <div data-input-validator-target="errors" data-field="email"></div>
586 |             
587 |           
588 |
589 | 590 |
591 | 592 |
593 |
594 | 595 |

596 | Made with 597 | 598 | 600 | 601 | in Akron, OH by Mike 602 | Ray Arriaga 603 |

604 | 605 | 606 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stimulus-inline-input-validations", 3 | "version": "1.2.0", 4 | "description": "A Stimulus controller for rendering rails model validation errors inline on form inputs", 5 | "source": "src/index.js", 6 | "main": "./dist/stimulus-inline-input-validations", 7 | "module": "./dist/stimulus-inline-input-validations.module.js", 8 | "repository": { 9 | "url": "https://github.com/mikerayux/stimulus-inline-input-validations", 10 | "type": "git" 11 | }, 12 | "scripts": { 13 | "lint:html": "htmlhint ./index.html", 14 | "eslint": "eslint", 15 | "build": "yarn run build-esm && yarn run build-cjs", 16 | "build-cjs": "esbuild src/index.js --format=cjs --target=es2020 --minify --bundle --sourcemap=external --external:@hotwired/stimulus --outfile=dist/stimulus-inline-input-validations.cjs", 17 | "build-esm": "esbuild src/index.js --format=esm --target=es2020 --minify --bundle --sourcemap=external --external:@hotwired/stimulus --outfile=dist/stimulus-inline-input-validations.module.js", 18 | "dev": "lite-server", 19 | "prepublish": "yarn run build", 20 | "test": "yarn run cypress run --spec 'cypress/e2e/**/*.cy.js' --browser chrome ", 21 | "cy:open": "yarn run cypress open" 22 | }, 23 | "author": "Mike Ray Arriaga ", 24 | "license": "MIT", 25 | "private": false, 26 | "amdName": "StimulusInlineValidations", 27 | "keywords": [ 28 | "stimulus", 29 | "stimulusjs", 30 | "rails", 31 | "inline", 32 | "input", 33 | "validations" 34 | ], 35 | "browserslist": [ 36 | "defaults" 37 | ], 38 | "devDependencies": { 39 | "cypress": "^13.6.2", 40 | "cypress-real-events": "^1.11.0", 41 | "esbuild": "^0.18.11", 42 | "eslint": "^8.0.1", 43 | "eslint-config-standard": "^17.1.0", 44 | "eslint-plugin-import": "^2.25.2", 45 | "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", 46 | "eslint-plugin-promise": "^6.0.0", 47 | "htmlhint": "^1.1.4" 48 | }, 49 | "peerDependencies": { 50 | "@hotwired/stimulus": ">= 3.0.0" 51 | }, 52 | "dependencies": {} 53 | } 54 | -------------------------------------------------------------------------------- /src/helpers/regex.js: -------------------------------------------------------------------------------- 1 | const Regex = { 2 | numericality: /^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/, 3 | email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, 4 | singleNumber: /[0-9]/, 5 | singleCapitalLetter: /[A-Z]/, 6 | singleSpecialCharacter: /[!@#$%^&*]/, 7 | singleQuotes: /'/ 8 | } 9 | 10 | export default Regex 11 | -------------------------------------------------------------------------------- /src/i18n/error_messages.js: -------------------------------------------------------------------------------- 1 | const ErrorMessages = { 2 | locales: { 3 | en: { 4 | presence: "Can't be blank", 5 | length: { 6 | min: 'Too short. Minimum {value} characters', 7 | max: 'Too long. Maximum {value} characters' 8 | }, 9 | numericality: 'Must be a number', 10 | email: 'Invalid email format', 11 | strongPassword: { 12 | capitalLetter: 'Must contain at least one capital letter (A-Z)', 13 | number: 'Must contain at least one number', 14 | specialCharacter: 15 | 'Must contain at least one special character (!@#$%^&*)', 16 | length: 'Must be at least 10 characters long' 17 | } 18 | }, 19 | es: { 20 | presence: 'no puede estar en blanco', 21 | length: { 22 | min: 'Demasiado corto. Mínimo {value} caracteres', 23 | max: 'Demasiado largo. Máximo {value} caracteres' 24 | }, 25 | numericality: 'Tiene que ser un número', 26 | email: 'Formato de correo inválido', 27 | strongPassword: { 28 | capitalLetter: 'Debe contener al menos una letra mayúscula (A-Z)', 29 | number: 'Debe contener al menos un número', 30 | specialCharacter: 31 | 'Debe contener al menos un carácter especial (!@#$%^&*)', 32 | length: 'Debe tener al menos 10 caracteres' 33 | } 34 | }, 35 | fr: { 36 | presence: 'Je ne peux pas être vide', 37 | length: { 38 | min: 'Trop court. Minimum {value} caractères', 39 | max: 'Trop long. {value} caractères maximum' 40 | }, 41 | numericality: 'Doit être un nombre', 42 | email: "Format d'email invalide", 43 | strongPassword: { 44 | capitalLetter: 'Doit contenir au moins une lettre majuscule (A-Z)', 45 | number: 'Doit contenir au moins un chiffre', 46 | specialCharacter: 47 | 'Doit contenir au moins un caractère spécial (!@#$%^&*)', 48 | length: 'Doit contenir au moins 10 caractères' 49 | } 50 | }, 51 | 'pt-BR': { 52 | presence: 'Não posso ficar em branco', 53 | length: { 54 | min: 'Muito curto. Mínimo {value} caracteres', 55 | max: 'Demasiado longo. Máximo de {value} caracteres' 56 | }, 57 | numericality: 'Deve ser um número', 58 | email: 'Formato de email inválido', 59 | strongPassword: { 60 | capitalLetter: 'Deve conter pelo menos uma letra maiúscula (A-Z)', 61 | number: 'Deve conter pelo menos um número', 62 | specialCharacter: 63 | 'Deve conter pelo menos um caractere especial (!@#$%^&*)', 64 | length: 'Deve ter pelo menos 10 caracteres' 65 | } 66 | }, 67 | 'zh-CN': { 68 | presence: '不能为空', 69 | length: { 70 | min: '过短。最少 {value} 个字符', 71 | max: '太长。最多 {value} 个字符' 72 | }, 73 | numericality: '必须是一个数字', 74 | email: '电子邮件格式无效', 75 | strongPassword: { 76 | capitalLetter: '必须包含至少一个大写字母 (A-Z)', 77 | number: '必须至少包含一个数字', 78 | specialCharacter: '必须至少包含一个特殊字符 (!@#$%^&*)', 79 | length: '长度必须至少 10 个字符' 80 | } 81 | }, 82 | 'zh-TW': { 83 | presence: '不能為空', 84 | length: { 85 | min: '過短。最少 {value} 個字符', 86 | max: '太長。最多 {value} 個字符' 87 | }, 88 | numericality: '必須是一個數字', 89 | email: '電子郵件格式無效', 90 | strongPassword: { 91 | capitalLetter: '必須包含至少一個大寫字母 (A-Z)', 92 | number: '必須至少包含一個數字', 93 | specialCharacter: '必須至少包含一個特殊字元 (!@#$%^&*)', 94 | length: '長度必須至少 10 個字符' 95 | } 96 | } 97 | } 98 | } 99 | 100 | export default ErrorMessages 101 | -------------------------------------------------------------------------------- /src/i18n/supported_locales.js: -------------------------------------------------------------------------------- 1 | const SUPPORTED_LOCALES = ['en', 'es', 'fr', 'pt-BR', 'zh-CN', 'zh-TW'] 2 | export default SUPPORTED_LOCALES 3 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as InputValidator } from './input_validator' 2 | -------------------------------------------------------------------------------- /src/input_validator.js: -------------------------------------------------------------------------------- 1 | import { Controller } from '@hotwired/stimulus' 2 | import Validate from './validations/validate' 3 | import Regex from './helpers/regex' 4 | import SUPPORTED_LOCALES from './i18n/supported_locales' 5 | 6 | // Connects to data-controller="input-validator" 7 | export default class extends Controller { 8 | static targets = ['field', 'errors'] 9 | 10 | connect () { 11 | this.handlei18n() 12 | 13 | this.fieldTargets.forEach((field) => { 14 | field.setAttribute('data-action', 'input->input-validator#validateInput') 15 | 16 | field.addEventListener('blur', (event) => { 17 | this.validateInput(event) 18 | }) 19 | }) 20 | } 21 | 22 | handlei18n () { 23 | const locale = 24 | this.element.getAttribute('data-input-validator-i18n-locale') || 'en' 25 | 26 | if (SUPPORTED_LOCALES.includes(locale)) { 27 | this.locale = locale 28 | } else { 29 | console.warn( 30 | `Stimulus Inline Input Validations: Unsupported i18n locale '${locale}'. Supported data-input-validator-i18n-locale values are: ${SUPPORTED_LOCALES.join( 31 | ', ' 32 | )}. Using default language 'en'` 33 | ) 34 | this.locale = 'en' 35 | } 36 | } 37 | 38 | handleJSONValidations (value, validations, errors) { 39 | validations.forEach((validation) => { 40 | const [validationType] = Object.keys(validation) 41 | 42 | switch (validationType) { 43 | case 'presence': 44 | if (validation.presence) { 45 | Validate.presence(value, errors, this.locale) 46 | } 47 | break 48 | case 'length': 49 | if (validation.length.min && validation.length.max) { 50 | Validate.length(value, validation.length, errors, this.locale) 51 | } else { 52 | console.log("Couldn't validate length (missing keys min or max)") 53 | } 54 | break 55 | case 'numericality': 56 | if (validation.numericality) { 57 | Validate.numericality(value, errors, this.locale) 58 | } 59 | break 60 | case 'email': 61 | if (validation.email) { 62 | Validate.email(value, errors, this.locale) 63 | } 64 | break 65 | case 'strong_password': 66 | if (validation.strong_password) { 67 | Validate.strongPassword(validations, value, errors, this.locale) 68 | } 69 | break 70 | default: 71 | break 72 | } 73 | }) 74 | } 75 | 76 | handleValidations (target, value, errors) { 77 | if ( 78 | target.hasAttribute('data-validates-presence') && 79 | target.getAttribute('data-validates-presence') !== 'false' 80 | ) { 81 | Validate.presence(value, errors, this.locale) 82 | } 83 | 84 | if ( 85 | target.hasAttribute('data-validates-length') && 86 | target.getAttribute('data-validates-length').length > 2 87 | ) { 88 | const [min, max] = target 89 | .getAttribute('data-validates-length') 90 | .split(',') 91 | .map(Number) 92 | 93 | Validate.length(value, { min, max }, errors, this.locale) 94 | } 95 | 96 | if ( 97 | target.hasAttribute('data-validates-numericality') && 98 | target.getAttribute('data-validates-numericality') !== 'false' 99 | ) { 100 | Validate.numericality(value, errors, this.locale) 101 | } 102 | 103 | if ( 104 | target.hasAttribute('data-validates-email') && 105 | target.getAttribute('data-validates-email') !== 'false' 106 | ) { 107 | Validate.email(value, errors, this.locale) 108 | } 109 | 110 | if ( 111 | target.hasAttribute('data-validates-strong-password') && 112 | target.getAttribute('data-validates-strong-password') !== 'false' 113 | ) { 114 | Validate.strongPassword([], value, errors, this.locale) 115 | } 116 | } 117 | 118 | errorElement (errorsContainer, error) { 119 | let styles = 'font-size: 14px; color: red' 120 | let classes 121 | 122 | if (errorsContainer.hasAttribute('data-errors-styles-css')) { 123 | styles = errorsContainer.getAttribute('data-errors-styles-css') 124 | } 125 | 126 | if (errorsContainer.hasAttribute('data-errors-styles-class')) { 127 | styles = null 128 | classes = errorsContainer.getAttribute('data-errors-styles-class') 129 | } 130 | 131 | return `
${error.message}
` 134 | } 135 | 136 | validateInput ({ target, target: { value } }) { 137 | const errors = [] 138 | const field = target.getAttribute('data-field') 139 | 140 | if (!field) { 141 | console.log( 142 | 'one or more elements are the missing data-field="" attribute.' 143 | ) 144 | return 145 | } 146 | 147 | const [errorsContainer] = this.errorsTargets.filter( 148 | (item) => item.getAttribute('data-field') === field 149 | ) 150 | 151 | if (target.hasAttribute('data-validations')) { 152 | try { 153 | let validations = target.getAttribute('data-validations') 154 | 155 | if (Regex.singleQuotes.test(validations)) { 156 | validations = validations.replace(/'/g, '"') 157 | } 158 | 159 | this.handleJSONValidations(value, JSON.parse(validations), errors) 160 | } catch (error) { 161 | console.log(error) 162 | console.log( 163 | `Error parsing JSON string on the data-validations attribute on data-field="${field}". Is the json string formatted properly?` 164 | ) 165 | return 166 | } 167 | } else { 168 | this.handleValidations(target, value, errors) 169 | } 170 | 171 | errorsContainer.innerHTML = '' 172 | 173 | if (errors.length) { 174 | errors.forEach((error) => { 175 | errorsContainer.innerHTML += this.errorElement(errorsContainer, error) 176 | errorsContainer.style.visibility = 'visible' 177 | }) 178 | } else { 179 | errorsContainer.style.visibility = 'invisible' 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/validations/validate.js: -------------------------------------------------------------------------------- 1 | import Regex from '../helpers/regex' 2 | import ErrorMessages from '../i18n/error_messages' 3 | 4 | const Validate = { 5 | presence (value, errors, locale) { 6 | if (value.trim().length === 0) { 7 | errors.push({ 8 | type: 'presence', 9 | message: ErrorMessages.locales[locale].presence 10 | }) 11 | } 12 | }, 13 | 14 | length (value, length, errors, locale) { 15 | if (value.length < length.min) { 16 | errors.push({ 17 | type: 'length-min', 18 | message: ErrorMessages.locales[locale].length.min.replace( 19 | /{value}/g, 20 | length.min 21 | ) 22 | }) 23 | } 24 | if (value.length > length.max) { 25 | errors.push({ 26 | type: 'length-max', 27 | message: ErrorMessages.locales[locale].length.max.replace( 28 | /{value}/g, 29 | length.max 30 | ) 31 | }) 32 | } 33 | }, 34 | 35 | numericality (value, errors, locale) { 36 | if (!Regex.numericality.test(value)) { 37 | errors.push({ 38 | type: 'numericality', 39 | message: ErrorMessages.locales[locale].numericality 40 | }) 41 | } 42 | }, 43 | 44 | email (value, errors, locale) { 45 | if (!Regex.email.test(value)) { 46 | errors.push({ 47 | type: 'email', 48 | message: ErrorMessages.locales[locale].email 49 | }) 50 | } 51 | }, 52 | 53 | strongPassword (validations, value, errors, locale) { 54 | if (!Regex.singleCapitalLetter.test(value)) { 55 | errors.push({ 56 | type: 'strong-password-capital-letter', 57 | message: ErrorMessages.locales[locale].strongPassword.capitalLetter 58 | }) 59 | } 60 | 61 | if (!Regex.singleNumber.test(value)) { 62 | errors.push({ 63 | type: 'strong-password-number', 64 | message: ErrorMessages.locales[locale].strongPassword.number 65 | }) 66 | } 67 | 68 | if (!Regex.singleSpecialCharacter.test(value)) { 69 | errors.push({ 70 | type: 'strong-password-special-character', 71 | message: ErrorMessages.locales[locale].strongPassword.specialCharacter 72 | }) 73 | } 74 | 75 | if ( 76 | validations.length && 77 | validations.some((validation) => 78 | Object.keys(validation).includes('length') 79 | ) 80 | ) { 81 | return 82 | } 83 | if (value.length < 10) { 84 | errors.push({ 85 | type: 'strong-password-length', 86 | message: ErrorMessages.locales[locale].strongPassword.length 87 | }) 88 | } 89 | } 90 | } 91 | 92 | export default Validate 93 | -------------------------------------------------------------------------------- /styles/tokyo_night_dark.css: -------------------------------------------------------------------------------- 1 | pre { 2 | margin: -9px !important; 3 | } 4 | 5 | code { 6 | /* background-color: #20293A !important; */ 7 | padding: 0px 30px !important; 8 | border-radius: 14px !important; 9 | } 10 | 11 | .language-bash { 12 | color: #9DC5F8 !important; 13 | } 14 | 15 | /*! 16 | Theme: Tokyo-night-Dark 17 | origin: https://github.com/enkia/tokyo-night-vscode-theme 18 | Description: Original highlight.js style 19 | Author: (c) Henri Vandersleyen 20 | License: see project LICENSE 21 | Touched: 2022 22 | */ 23 | 24 | /* Comment */ 25 | .hljs-meta, 26 | .hljs-comment { 27 | color: #565f89; 28 | } 29 | 30 | /* Red */ 31 | /*INFO: This keyword, HTML elements, Regex group symbol, CSS units, Terminal Red */ 32 | .hljs-tag, 33 | .hljs-doctag, 34 | .hljs-selector-id, 35 | .hljs-selector-class, 36 | .hljs-regexp, 37 | .hljs-template-tag, 38 | .hljs-selector-pseudo, 39 | .hljs-selector-attr, 40 | .hljs-variable.language_, 41 | .hljs-deletion { 42 | color: #9DC5F8; 43 | } 44 | 45 | .hljs-tag .hljs-attr, .hljs-tag .hljs-name { 46 | color: #9DC5F8; 47 | } 48 | 49 | /*Orange */ 50 | /*INFO: Number and Boolean constants, Language support constants */ 51 | .hljs-variable, 52 | .hljs-template-variable, 53 | .hljs-number, 54 | .hljs-literal, 55 | .hljs-type, 56 | .hljs-params, 57 | .hljs-link { 58 | color: #ff9e64; 59 | } 60 | 61 | 62 | /* Yellow */ 63 | /* INFO: Function parameters, Regex character sets, Terminal Yellow */ 64 | .hljs-built_in, 65 | .hljs-attribute { 66 | color: #e0af68; 67 | } 68 | /* cyan */ 69 | /* INFO: Language support functions, CSS HTML elements */ 70 | .hljs-selector-tag { 71 | color: #2ac3de; 72 | } 73 | 74 | /* light blue */ 75 | /* INFO: Object properties, Regex quantifiers and flags, Markdown headings, Terminal Cyan, Markdown code, Import/export keywords */ 76 | .hljs-keyword, 77 | .hljs-title.function_, 78 | .hljs-title, 79 | .hljs-title.class_, 80 | .hljs-title.class_.inherited__, 81 | .hljs-subst, 82 | .hljs-property {color: #7dcfff;} 83 | 84 | /*Green*/ 85 | /* INFO: Object literal keys, Markdown links, Terminal Green */ 86 | .hljs-selector-tag { color: #73daca;} 87 | 88 | 89 | /*Green(er) */ 90 | /* INFO: Strings, CSS class names */ 91 | .hljs-quote, 92 | .hljs-string, 93 | .hljs-symbol, 94 | .hljs-bullet, 95 | .hljs-addition { 96 | color: #8EE4BA; 97 | } 98 | 99 | /* Blue */ 100 | /* INFO: Function names, CSS property names, Terminal Blue */ 101 | .hljs-code, 102 | .hljs-formula, 103 | .hljs-section { 104 | color: #7aa2f7; 105 | } 106 | 107 | 108 | 109 | /* Magenta */ 110 | /*INFO: Control Keywords, Storage Types, Regex symbols and operators, HTML Attributes, Terminal Magenta */ 111 | .hljs-name, 112 | .hljs-keyword, 113 | .hljs-operator, 114 | .hljs-keyword, 115 | .hljs-char.escape_, 116 | .hljs-attr { 117 | color: #bb9af7; 118 | } 119 | 120 | /* white*/ 121 | /* INFO: Variables, Class names, Terminal White */ 122 | .hljs-punctuation {color: #c0caf5} 123 | 124 | .hljs { 125 | background: #1a1b26; 126 | color: #9aa5ce; 127 | } 128 | 129 | .hljs-emphasis { 130 | font-style: italic; 131 | } 132 | 133 | .hljs-strong { 134 | font-weight: bold; 135 | } 136 | --------------------------------------------------------------------------------