diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 5b63966..401ce06 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -38,9 +38,9 @@ _Tell us what happens instead_ - [ ] I confirm that my browser's development console output does not contain errors ### Additional JavaScript Libraries* -_If your issue depends on other JavaScript libraries, please list them here. E.g: *Bootstrap Modal v3.3.7, jQuery UI Datepicker 1.12.4*._ +_If your issue depends on other JavaScript libraries, please list them here. E.g. Bootstrap Modal v3.3.7, flatpickr 4.6.13._ -### Repository demostrating the issue +### Repository demonstrating the issue Debugging CSV issues is a time consuming task. If you want to speed up things, please provide a link to a repository showing the issue. diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 60297bf..5bff5b3 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -16,12 +16,12 @@ jobs: strategy: matrix: ruby-version: ['3.2', '3.3', '3.4', '4.0'] - gemfile: [ csv_23.0 ] + gemfile: [ csv_24.0 ] channel: ['stable'] include: - ruby-version: 'head' - gemfile: csv_23.0 + gemfile: csv_24.0 channel: 'experimental' - ruby-version: '3.2' gemfile: csv_edge diff --git a/.rubocop.yml b/.rubocop.yml index 248033a..4f08b2e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -61,3 +61,7 @@ Style/Documentation: Style/IfUnlessModifier: Enabled: false + +Style/OneClassPerFile: + Exclude: + - 'test/**/*' diff --git a/Appraisals b/Appraisals index aebb5d4..ab2f369 100644 --- a/Appraisals +++ b/Appraisals @@ -1,7 +1,7 @@ # frozen_string_literal: true -appraise 'csv-23.0' do - gem 'client_side_validations', '~> 23.0' +appraise 'csv-24.0' do + gem 'client_side_validations', '~> 24.0' end appraise 'csv-edge' do diff --git a/CHANGELOG.md b/CHANGELOG.md index 91bd369..f83be72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 18.0.0 / 2026-04-19 + +* [FEATURE] Breaking change: Align the Simple Form JavaScript hooks with the DOM-first `ClientSideValidations` runtime +* [ENHANCEMENT] Remove jQuery-specific assumptions from the browser test harness and documentation + ## 17.0.0 / 2026-01-07 * [FEATURE] Drop Internet Explorer and other older browsers support diff --git a/README.md b/README.md index 1181f7b..3fc0def 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,11 @@ required **before** `client_side_validations-simple_form`. Instructions depend on your technology stack. +This package extends the jQuery-free, DOM-first `ClientSideValidations` runtime. + #### When using Webpacker #### -Make sure that you are requiring jQuery and Client Side Validations. +Make sure that you are requiring Client Side Validations before the Simple Form plugin. Add the following package: @@ -58,7 +60,7 @@ require('@client-side-validations/simple-form/dist/simple-form.bootstrap4.esm') #### When using Sprockets #### -Make sure that you are requiring jQuery and Client Side Validations. +Make sure that you are requiring Client Side Validations before the Simple Form plugin. According to the web framework you are using, add **one** of the following lines to your `app/assets/javascripts/application.js`, **after** @@ -80,6 +82,18 @@ rails g client_side_validations:copy_assets Note: If you run `copy_assets`, you will need to run it again each time you update this project. +## Migration Guide ## + +### Removing jQuery Dependency ### + +`client_side_validations-simple_form` now plugs into the DOM-first `ClientSideValidations` runtime and no longer assumes jQuery is present. + +Follow the main `ClientSideValidations` migration guide for the public API changes. In particular, load the current DOM-first `ClientSideValidations` bundle before loading this package, and update any custom code that still expects jQuery-wrapped objects. + +Custom Simple Form builders now receive native DOM elements in their `add` and `remove` hooks, so custom overrides should use DOM APIs such as `.closest()`, `.querySelector()`, and `.classList`. + +If you vendor the compiled assets with `rails g client_side_validations:copy_assets`, run that generator again after upgrading so the copied Simple Form assets stay in sync with the current `ClientSideValidations` bundle. + ## Usage ## The usage is the same as `ClientSideValidations`, just pass `validate: true` to the form builder diff --git a/client_side_validations-simple_form.gemspec b/client_side_validations-simple_form.gemspec index 5ee7ba4..b8971df 100644 --- a/client_side_validations-simple_form.gemspec +++ b/client_side_validations-simple_form.gemspec @@ -27,6 +27,6 @@ Gem::Specification.new do |spec| spec.platform = Gem::Platform::RUBY spec.required_ruby_version = '>= 3.2' - spec.add_dependency 'client_side_validations', '>= 23.0' + spec.add_dependency 'client_side_validations', '>= 24.0' spec.add_dependency 'simple_form', '>= 5.4' end diff --git a/dist/simple-form.bootstrap4.esm.js b/dist/simple-form.bootstrap4.esm.js index 51a41d0..e1d6ebd 100644 --- a/dist/simple-form.bootstrap4.esm.js +++ b/dist/simple-form.bootstrap4.esm.js @@ -1,5 +1,5 @@ /*! - * Client Side Validations Simple Form JS (Default) - v17.0.0 (https://github.com/DavyJonesLocker/client_side_validations-simple_form) + * Client Side Validations Simple Form JS (Default) - v18.0.0 (https://github.com/DavyJonesLocker/client_side_validations-simple_form) * Copyright (c) 2026 Geremia Taglialatela, Brian Cardarella * Licensed under MIT (https://opensource.org/licenses/mit-license.php) */ @@ -18,11 +18,11 @@ const removeClass = (element, customClass) => { }; ClientSideValidations.formBuilders['SimpleForm::FormBuilder'] = { - add: function ($element, settings, message) { - this.wrapper(settings.wrapper).add.call(this, $element[0], settings, message); + add: function (element, settings, message) { + this.wrapper(settings.wrapper).add.call(this, element, settings, message); }, - remove: function ($element, settings) { - this.wrapper(settings.wrapper).remove.call(this, $element[0], settings); + remove: function (element, settings) { + this.wrapper(settings.wrapper).remove.call(this, element, settings); }, wrapper: function (name) { return this.wrappers[name] || this.wrappers.default; diff --git a/dist/simple-form.bootstrap4.js b/dist/simple-form.bootstrap4.js index 9660d50..4044985 100644 --- a/dist/simple-form.bootstrap4.js +++ b/dist/simple-form.bootstrap4.js @@ -1,5 +1,5 @@ /*! - * Client Side Validations Simple Form JS (Bootstrap 4+) - v17.0.0 (https://github.com/DavyJonesLocker/client_side_validations-simple_form) + * Client Side Validations Simple Form JS (Bootstrap 4+) - v18.0.0 (https://github.com/DavyJonesLocker/client_side_validations-simple_form) * Copyright (c) 2026 Geremia Taglialatela, Brian Cardarella * Licensed under MIT (https://opensource.org/licenses/mit-license.php) */ @@ -22,11 +22,11 @@ }; ClientSideValidations.formBuilders['SimpleForm::FormBuilder'] = { - add: function ($element, settings, message) { - this.wrapper(settings.wrapper).add.call(this, $element[0], settings, message); + add: function (element, settings, message) { + this.wrapper(settings.wrapper).add.call(this, element, settings, message); }, - remove: function ($element, settings) { - this.wrapper(settings.wrapper).remove.call(this, $element[0], settings); + remove: function (element, settings) { + this.wrapper(settings.wrapper).remove.call(this, element, settings); }, wrapper: function (name) { return this.wrappers[name] || this.wrappers.default; diff --git a/dist/simple-form.esm.js b/dist/simple-form.esm.js index d8c469d..25ca17e 100644 --- a/dist/simple-form.esm.js +++ b/dist/simple-form.esm.js @@ -1,5 +1,5 @@ /*! - * Client Side Validations Simple Form JS (Default) - v17.0.0 (https://github.com/DavyJonesLocker/client_side_validations-simple_form) + * Client Side Validations Simple Form JS (Default) - v18.0.0 (https://github.com/DavyJonesLocker/client_side_validations-simple_form) * Copyright (c) 2026 Geremia Taglialatela, Brian Cardarella * Licensed under MIT (https://opensource.org/licenses/mit-license.php) */ @@ -18,11 +18,11 @@ const removeClass = (element, customClass) => { }; ClientSideValidations.formBuilders['SimpleForm::FormBuilder'] = { - add: function ($element, settings, message) { - this.wrapper(settings.wrapper).add.call(this, $element[0], settings, message); + add: function (element, settings, message) { + this.wrapper(settings.wrapper).add.call(this, element, settings, message); }, - remove: function ($element, settings) { - this.wrapper(settings.wrapper).remove.call(this, $element[0], settings); + remove: function (element, settings) { + this.wrapper(settings.wrapper).remove.call(this, element, settings); }, wrapper: function (name) { return this.wrappers[name] || this.wrappers.default; diff --git a/dist/simple-form.js b/dist/simple-form.js index bdc9814..51e0017 100644 --- a/dist/simple-form.js +++ b/dist/simple-form.js @@ -1,5 +1,5 @@ /*! - * Client Side Validations Simple Form JS (Default) - v17.0.0 (https://github.com/DavyJonesLocker/client_side_validations-simple_form) + * Client Side Validations Simple Form JS (Default) - v18.0.0 (https://github.com/DavyJonesLocker/client_side_validations-simple_form) * Copyright (c) 2026 Geremia Taglialatela, Brian Cardarella * Licensed under MIT (https://opensource.org/licenses/mit-license.php) */ @@ -22,11 +22,11 @@ }; ClientSideValidations.formBuilders['SimpleForm::FormBuilder'] = { - add: function ($element, settings, message) { - this.wrapper(settings.wrapper).add.call(this, $element[0], settings, message); + add: function (element, settings, message) { + this.wrapper(settings.wrapper).add.call(this, element, settings, message); }, - remove: function ($element, settings) { - this.wrapper(settings.wrapper).remove.call(this, $element[0], settings); + remove: function (element, settings) { + this.wrapper(settings.wrapper).remove.call(this, element, settings); }, wrapper: function (name) { return this.wrappers[name] || this.wrappers.default; diff --git a/gemfiles/csv_23.0.gemfile b/gemfiles/csv_24.0.gemfile similarity index 90% rename from gemfiles/csv_23.0.gemfile rename to gemfiles/csv_24.0.gemfile index 384ac3a..fddb0eb 100644 --- a/gemfiles/csv_23.0.gemfile +++ b/gemfiles/csv_24.0.gemfile @@ -19,6 +19,6 @@ gem "simplecov" gem "simplecov-lcov" gem "sinatra" gem "webrick" -gem "client_side_validations", "~> 23.0" +gem "client_side_validations", "~> 24.0" gemspec path: "../" diff --git a/lib/client_side_validations/simple_form/version.rb b/lib/client_side_validations/simple_form/version.rb index dd3a8c9..e469b80 100644 --- a/lib/client_side_validations/simple_form/version.rb +++ b/lib/client_side_validations/simple_form/version.rb @@ -2,6 +2,6 @@ module ClientSideValidations module SimpleForm - VERSION = '17.0.0' + VERSION = '18.0.0' end end diff --git a/package.json b/package.json index 3a7b6ba..804726a 100644 --- a/package.json +++ b/package.json @@ -23,20 +23,20 @@ "test": "test/javascript/run-qunit.mjs" }, "devDependencies": { - "@babel/core": "^7.28.5", - "@babel/preset-env": "^7.28.5", + "@babel/core": "^7.29.0", + "@babel/preset-env": "^7.29.2", "@rollup/plugin-babel": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.3", "chrome-launcher": "^1.2.1", "eslint": "^9.39.2", "eslint-plugin-compat": "^6.0.2", - "neostandard": "^0.12.2", - "puppeteer-core": "^24.34.0", - "rollup": "^4.55.1", + "neostandard": "^0.13.0", + "puppeteer-core": "^24.41.0", + "rollup": "^4.60.2", "rollup-plugin-copy": "^3.5.0" }, "peerDependencies": { - "@client-side-validations/client-side-validations": ">=23.0.0" + "@client-side-validations/client-side-validations": ">=24.0.0" }, "main": "dist/simple-form.js", "module": "dist/simple-form.esm.js", @@ -66,7 +66,7 @@ "/vendor/" ] }, - "version": "17.0.0", + "version": "18.0.0", "directories": { "lib": "lib", "test": "test" diff --git a/src/index.bootstrap4.js b/src/index.bootstrap4.js index 2b747ae..e8b7cb3 100644 --- a/src/index.bootstrap4.js +++ b/src/index.bootstrap4.js @@ -2,11 +2,11 @@ import ClientSideValidations from '@client-side-validations/client-side-validati import { addClass, removeClass } from './utils' ClientSideValidations.formBuilders['SimpleForm::FormBuilder'] = { - add: function ($element, settings, message) { - this.wrapper(settings.wrapper).add.call(this, $element[0], settings, message) + add: function (element, settings, message) { + this.wrapper(settings.wrapper).add.call(this, element, settings, message) }, - remove: function ($element, settings) { - this.wrapper(settings.wrapper).remove.call(this, $element[0], settings) + remove: function (element, settings) { + this.wrapper(settings.wrapper).remove.call(this, element, settings) }, wrapper: function (name) { return this.wrappers[name] || this.wrappers.default diff --git a/src/index.js b/src/index.js index eac92fa..3df2711 100644 --- a/src/index.js +++ b/src/index.js @@ -2,11 +2,11 @@ import ClientSideValidations from '@client-side-validations/client-side-validati import { addClass, removeClass } from './utils' ClientSideValidations.formBuilders['SimpleForm::FormBuilder'] = { - add: function ($element, settings, message) { - this.wrapper(settings.wrapper).add.call(this, $element[0], settings, message) + add: function (element, settings, message) { + this.wrapper(settings.wrapper).add.call(this, element, settings, message) }, - remove: function ($element, settings) { - this.wrapper(settings.wrapper).remove.call(this, $element[0], settings) + remove: function (element, settings) { + this.wrapper(settings.wrapper).remove.call(this, element, settings) }, wrapper: function (name) { return this.wrappers[name] || this.wrappers.default diff --git a/test/javascript/public/test/form_builders/validateSimpleForm.js b/test/javascript/public/test/form_builders/validateSimpleForm.js index a322e42..c03b093 100644 --- a/test/javascript/public/test/form_builders/validateSimpleForm.js +++ b/test/javascript/public/test/form_builders/validateSimpleForm.js @@ -1,3 +1,5 @@ +var currentFormBuilder + QUnit.module('Validate SimpleForm', { before: function () { currentFormBuilder = window.ClientSideValidations.formBuilders['SimpleForm::FormBuilder'] @@ -9,6 +11,12 @@ QUnit.module('Validate SimpleForm', { }, beforeEach: function () { + var fixture = document.getElementById('qunit-fixture') + var form = document.createElement('form') + var wrapper = document.createElement('div') + var input = document.createElement('input') + var label = document.createElement('label') + dataCsv = { html_settings: { type: 'SimpleForm::FormBuilder', @@ -20,70 +28,92 @@ QUnit.module('Validate SimpleForm', { wrapper: 'default' }, validators: { - 'user[name]': { presence: [{ message: 'must be present' }], format: [{ message: 'is invalid', 'with': { options: 'g', source: '\\d+' } }] } + 'user[name]': { presence: [{ message: 'must be present' }], format: [{ message: 'is invalid', with: { options: 'g', source: '\\d+' } }] } } } - $('#qunit-fixture') - .append($('
', { - action: '/users', - 'data-client-side-validations': JSON.stringify(dataCsv), - method: 'post', - id: 'new_user' - })) - .find('form') - .append($('
')) - .find('div') - .append($('', { - name: 'user[name]', - id: 'user_name', - type: 'text' - })) - .append($('')) - $('form#new_user').validate() + form.action = '/users' + form.dataset.clientSideValidations = JSON.stringify(dataCsv) + form.method = 'post' + form.id = 'new_user' + + wrapper.className = 'input' + + input.name = 'user[name]' + input.id = 'user_name' + input.type = 'text' + + label.htmlFor = 'user_name' + label.textContent = 'Name' + + wrapper.appendChild(input) + wrapper.appendChild(label) + form.appendChild(wrapper) + fixture.appendChild(form) + + ClientSideValidations.validate(form) + }, + + afterEach: function () { + document.getElementById('qunit-fixture').replaceChildren() } }) QUnit.test('Validate error attaching and detaching', function (assert) { - var form = $('form#new_user'); var input = form.find('input#user_name') - var label = $('label[for="user_name"]') - - input.trigger('focusout') - assert.ok(input.parent().hasClass('field_with_errors')) - assert.ok(label.parent().hasClass('field_with_errors')) - assert.ok(input.parent().find('span.error:contains("must be present")')[0]) - - input.val('abc') - input.trigger('change') - input.trigger('focusout') - assert.ok(input.parent().hasClass('field_with_errors')) - assert.ok(label.parent().hasClass('field_with_errors')) - assert.ok(input.parent().find('span.error:contains("is invalid")')[0]) - - input.val('123') - input.trigger('change') - input.trigger('focusout') - assert.notOk(input.parent().hasClass('field_with_errors')) - assert.notOk(label.parent().hasClass('field_with_errors')) - assert.notOk(form.find('span.error')[0]) + var form = document.getElementById('new_user') + var input = document.getElementById('user_name') + var label = form.querySelector('label[for="user_name"]') + + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.ok(input.parentElement.classList.contains('field_with_errors')) + assert.ok(label.parentElement.classList.contains('field_with_errors')) + assert.ok(input.parentElement.querySelector('span.error').textContent.includes('must be present')) + + input.value = 'abc' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.ok(input.parentElement.classList.contains('field_with_errors')) + assert.ok(label.parentElement.classList.contains('field_with_errors')) + assert.ok(input.parentElement.querySelector('span.error').textContent.includes('is invalid')) + + input.value = '123' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.notOk(input.parentElement.classList.contains('field_with_errors')) + assert.notOk(label.parentElement.classList.contains('field_with_errors')) + assert.notOk(form.querySelector('span.error')) }) QUnit.test('Validate pre-existing error blocks are re-used', function (assert) { - var form = $('form#new_user'); var input = form.find('input#user_name') - var label = $('label[for="user_name"]') - - input.parent().append($('Error from Server')) - assert.ok(input.parent().find('span.error:contains("Error from Server")')[0]) - input.val('abc') - input.trigger('change') - input.trigger('focusout') - assert.ok(input.parent().hasClass('field_with_errors')) - assert.ok(label.parent().hasClass('field_with_errors')) - assert.ok(input.parent().find('span.error:contains("is invalid")').length === 1) - assert.ok(form.find('span.error').length === 1) + var form = document.getElementById('new_user') + var input = document.getElementById('user_name') + var label = form.querySelector('label[for="user_name"]') + var errorElement = document.createElement('span') + + errorElement.className = 'error small' + errorElement.textContent = 'Error from Server' + input.parentElement.appendChild(errorElement) + + assert.ok(input.parentElement.querySelector('span.error').textContent.includes('Error from Server')) + + input.value = 'abc' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + + assert.ok(input.parentElement.classList.contains('field_with_errors')) + assert.ok(label.parentElement.classList.contains('field_with_errors')) + assert.equal(input.parentElement.querySelectorAll('span.error').length, 1) + assert.equal(form.querySelectorAll('span.error').length, 1) + assert.ok(input.parentElement.querySelector('span.error').textContent.includes('is invalid')) }) -QUnit.test("Display error messages when wrapper and error tags have more than two css classes", function (assert) { +QUnit.test('Display error messages when wrapper and error tags have more than two css classes', function (assert) { + var fixture = document.getElementById('qunit-fixture') + var form = document.createElement('form') + var wrapper = document.createElement('div') + var input = document.createElement('input') + var label = document.createElement('label') + dataCsv = { html_settings: { type: 'SimpleForm::FormBuilder', @@ -95,41 +125,41 @@ QUnit.test("Display error messages when wrapper and error tags have more than tw wrapper: 'default' }, validators: { - 'user_2[name]': { presence: [{ message: 'must be present' }], format: [{ message: 'is invalid', 'with': { options: 'g', source: '\\d+' } }] } + 'user_2[name]': { presence: [{ message: 'must be present' }], format: [{ message: 'is invalid', with: { options: 'g', source: '\\d+' } }] } } } - $('#qunit-fixture') - .html('') - .append($('', { - action: '/users', - 'data-client-side-validations': JSON.stringify(dataCsv), - method: 'post', - id: 'new_user_2' - })) - .find('form') - .append($('
')) - .find('div') - .append($('', { - name: 'user_2[name]', - id: 'user_2_name', - type: 'text' - })) - .append($('')) - $('form#new_user_2').validate() - - var form = $('form#new_user_2') - var input = form.find('input#user_2_name') - - input.val('') - input.trigger('focusout') - - assert.ok(input.parent().hasClass('field_with_errors')) - - input.val('123') - input.trigger('change') - input.trigger('focusout') - - assert.notOk(input.parent().hasClass('field_with_errors')) - assert.notOk(form.find('span.error').length) + fixture.replaceChildren() + + form.action = '/users' + form.dataset.clientSideValidations = JSON.stringify(dataCsv) + form.method = 'post' + form.id = 'new_user_2' + + wrapper.className = 'input wrapper_class_one wrapper_class_two' + + input.name = 'user_2[name]' + input.id = 'user_2_name' + input.type = 'text' + + label.htmlFor = 'user_2_name' + label.textContent = 'Name' + + wrapper.appendChild(input) + wrapper.appendChild(label) + form.appendChild(wrapper) + fixture.appendChild(form) + + ClientSideValidations.validate(form) + + input.dispatchEvent(new Event('focusout', { bubbles: true })) + + assert.ok(input.parentElement.classList.contains('field_with_errors')) + + input.value = '123' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + + assert.notOk(input.parentElement.classList.contains('field_with_errors')) + assert.notOk(form.querySelector('span.error')) }) diff --git a/test/javascript/public/test/form_builders/validateSimpleFormBootstrap.js b/test/javascript/public/test/form_builders/validateSimpleFormBootstrap.js index 23793df..66ffe3b 100644 --- a/test/javascript/public/test/form_builders/validateSimpleFormBootstrap.js +++ b/test/javascript/public/test/form_builders/validateSimpleFormBootstrap.js @@ -1,3 +1,5 @@ +var currentFormBuilder + QUnit.module('Validate SimpleForm Bootstrap', { before: function () { currentFormBuilder = window.ClientSideValidations.formBuilders['SimpleForm::FormBuilder'] @@ -9,6 +11,19 @@ QUnit.module('Validate SimpleForm Bootstrap', { }, beforeEach: function () { + var fixture = document.getElementById('qunit-fixture') + var form = document.createElement('form') + var formInputs = document.createElement('div') + var nameGroup = document.createElement('div') + var nameLabel = document.createElement('label') + var nameControls = document.createElement('div') + var nameInput = document.createElement('input') + var usernameGroup = document.createElement('div') + var usernameLabel = document.createElement('label') + var inputGroup = document.createElement('div') + var inputGroupAddon = document.createElement('span') + var usernameInput = document.createElement('input') + dataCsv = { html_settings: { type: 'SimpleForm::FormBuilder', @@ -20,118 +35,133 @@ QUnit.module('Validate SimpleForm Bootstrap', { wrapper: 'bootstrap' }, validators: { - 'user[name]': { presence: [{ message: 'must be present' }], format: [{ message: 'is invalid', 'with': { options: 'g', source: '\\d+' } }] }, + 'user[name]': { presence: [{ message: 'must be present' }], format: [{ message: 'is invalid', with: { options: 'g', source: '\\d+' } }] }, 'user[username]': { presence: [{ message: 'must be present' }] } } } - $('#qunit-fixture') - .append($('', { - action: '/users', - 'data-client-side-validations': JSON.stringify(dataCsv), - method: 'post', - id: 'new_user' - })) - .find('form') - .append($('
', { - 'class': 'form-inputs' - })) - .find('div') - .append($('
', { - 'class': 'control-group control-group-2 control-group-3 control-group-user-name' - })) - .find('div.control-group-user-name') - .append($('')) - .append($('
', { - 'class': 'controls' - })) - .find('div') - .append($('', { - name: 'user[name]', - id: 'user_name', - type: 'text' - })) - .append($('
', { - 'class': 'control-group control-group-2 control-group-3 control-group-user-username' - })) - .find('div.control-group-user-username') - .append($('')) - .append($('
', { - 'class': 'input-group' - })) - .find('div') - .append($('', { - 'class': 'input-group-addon', - text: '@' - })) - .append($('', { - name: 'user[username]', - id: 'user_username', - type: 'text' - })) - - $('form#new_user').validate() + form.action = '/users' + form.dataset.clientSideValidations = JSON.stringify(dataCsv) + form.method = 'post' + form.id = 'new_user' + + formInputs.className = 'form-inputs' + + nameGroup.className = 'control-group control-group-2 control-group-3 control-group-user-name' + nameLabel.htmlFor = 'user_name' + nameLabel.className = 'string control-label' + nameLabel.textContent = 'Name' + nameControls.className = 'controls' + nameInput.name = 'user[name]' + nameInput.id = 'user_name' + nameInput.type = 'text' + + usernameGroup.className = 'control-group control-group-2 control-group-3 control-group-user-username' + usernameLabel.htmlFor = 'user_username' + usernameLabel.className = 'string control-label' + usernameLabel.textContent = 'Username' + inputGroup.className = 'input-group' + inputGroupAddon.className = 'input-group-addon' + inputGroupAddon.textContent = '@' + usernameInput.name = 'user[username]' + usernameInput.id = 'user_username' + usernameInput.type = 'text' + + nameControls.appendChild(nameInput) + nameGroup.appendChild(nameLabel) + nameGroup.appendChild(nameControls) + + inputGroup.appendChild(inputGroupAddon) + inputGroup.appendChild(usernameInput) + usernameGroup.appendChild(usernameLabel) + usernameGroup.appendChild(inputGroup) + + formInputs.appendChild(nameGroup) + formInputs.appendChild(usernameGroup) + form.appendChild(formInputs) + fixture.appendChild(form) + + ClientSideValidations.validate(form) + }, + + afterEach: function () { + document.getElementById('qunit-fixture').replaceChildren() } }) var wrappers = ['horizontal_form', 'vertical_form', 'inline_form'] -for (var i = 0; i < wrappers.length; i++) { - var wrapper = wrappers[i] - +for (const wrapper of wrappers) { QUnit.test(wrapper + ' - Validate error attaching and detaching', function (assert) { - var form = $('form#new_user'); var input = form.find('input#user_name') - var label = $('label[for="user_name"]') - form[0].ClientSideValidations.settings.html_settings.wrapper = wrapper - - input.trigger('focusout') - assert.ok(input.parent().parent().hasClass('error')) - assert.ok(label.parent().hasClass('error')) - assert.ok(input.parent().parent().find('span.help-inline:contains("must be present")')[0]) - - input.val('abc') - input.trigger('change') - input.trigger('focusout') - assert.ok(input.parent().parent().hasClass('error')) - assert.ok(input.parent().parent().find('span.help-inline:contains("is invalid")')[0]) - assert.ok(label.parent().hasClass('error')) - - input.val('123') - input.trigger('change') - input.trigger('focusout') - assert.notOk(input.parent().parent().hasClass('error')) - assert.notOk(input.parent().parent().find('span.help-inline')[0]) - assert.notOk(label.parent().hasClass('error')) + var form = document.getElementById('new_user') + var input = document.getElementById('user_name') + var label = form.querySelector('label[for="user_name"]') + var wrapperElement = input.closest('.control-group-user-name') + + form.ClientSideValidations.settings.html_settings.wrapper = wrapper + + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.ok(wrapperElement.classList.contains('error')) + assert.ok(label.parentElement.classList.contains('error')) + assert.ok(wrapperElement.querySelector('span.help-inline').textContent.includes('must be present')) + + input.value = 'abc' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.ok(wrapperElement.classList.contains('error')) + assert.ok(wrapperElement.querySelector('span.help-inline').textContent.includes('is invalid')) + assert.ok(label.parentElement.classList.contains('error')) + + input.value = '123' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.notOk(wrapperElement.classList.contains('error')) + assert.notOk(wrapperElement.querySelector('span.help-inline')) + assert.notOk(label.parentElement.classList.contains('error')) }) QUnit.test(wrapper + ' - Validate pre-existing error blocks are re-used', function (assert) { - var form = $('form#new_user'); var input = form.find('input#user_name') - var label = $('label[for="user_name"]') - form[0].ClientSideValidations.settings.html_settings.wrapper = wrapper - - input.parent().append($('Error from Server')) - assert.ok(input.parent().find('span.help-inline:contains("Error from Server")')[0]) - input.val('abc') - input.trigger('change') - input.trigger('focusout') - assert.ok(input.parent().parent().hasClass('error')) - assert.ok(label.parent().hasClass('error')) - assert.ok(input.parent().find('span.help-inline:contains("is invalid")').length === 1) - assert.ok(form.find('span.help-inline').length === 1) + var form = document.getElementById('new_user') + var input = document.getElementById('user_name') + var label = form.querySelector('label[for="user_name"]') + var wrapperElement = input.closest('.control-group-user-name') + var errorElement = document.createElement('span') + + form.ClientSideValidations.settings.html_settings.wrapper = wrapper + + errorElement.className = 'help-inline' + errorElement.textContent = 'Error from Server' + input.parentElement.appendChild(errorElement) + + assert.ok(input.parentElement.querySelector('span.help-inline').textContent.includes('Error from Server')) + + input.value = 'abc' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + + assert.ok(wrapperElement.classList.contains('error')) + assert.ok(label.parentElement.classList.contains('error')) + assert.equal(wrapperElement.querySelectorAll('span.help-inline').length, 1) + assert.equal(form.querySelectorAll('span.help-inline').length, 1) + assert.ok(wrapperElement.querySelector('span.help-inline').textContent.includes('is invalid')) }) QUnit.test(wrapper + ' - Validate input-group', function (assert) { - var form = $('form#new_user'); var input = form.find('input#user_username') - form[0].ClientSideValidations.settings.html_settings.wrapper = wrapper - - input.trigger('change') - input.trigger('focusout') - assert.ok(input.closest('.input-group').find('span.help-inline').length === 0) - assert.ok(input.closest('.control-group').find('span.help-inline').length === 1) - - input.val('abc') - input.trigger('change') - input.trigger('focusout') - assert.ok(input.closest('.control-group').find('span.help-inline').length === 0) + var form = document.getElementById('new_user') + var input = document.getElementById('user_username') + var wrapperElement = input.closest('.control-group-user-username') + var inputGroup = input.closest('.input-group') + + form.ClientSideValidations.settings.html_settings.wrapper = wrapper + + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.equal(inputGroup.querySelectorAll('span.help-inline').length, 0) + assert.equal(wrapperElement.querySelectorAll('span.help-inline').length, 1) + + input.value = 'abc' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.equal(wrapperElement.querySelectorAll('span.help-inline').length, 0) }) } diff --git a/test/javascript/public/test/form_builders/validateSimpleFormBootstrap4.js b/test/javascript/public/test/form_builders/validateSimpleFormBootstrap4.js index 51b8205..28a8bf1 100644 --- a/test/javascript/public/test/form_builders/validateSimpleFormBootstrap4.js +++ b/test/javascript/public/test/form_builders/validateSimpleFormBootstrap4.js @@ -1,3 +1,6 @@ +var currentFormBuilder +var wrappers = ['horizontal_form', 'vertical_form', 'inline_form'] + QUnit.module('Validate SimpleForm Bootstrap 4', { before: function () { currentFormBuilder = window.ClientSideValidations.formBuilders['SimpleForm::FormBuilder'] @@ -9,6 +12,22 @@ QUnit.module('Validate SimpleForm Bootstrap 4', { }, beforeEach: function () { + var fixture = document.getElementById('qunit-fixture') + var form = document.createElement('form') + var nameGroup = document.createElement('div') + var nameLabel = document.createElement('label') + var nameInput = document.createElement('input') + var passwordGroup = document.createElement('div') + var passwordLabel = document.createElement('label') + var passwordInput = document.createElement('input') + var passwordHelp = document.createElement('small') + var usernameGroup = document.createElement('div') + var usernameLabel = document.createElement('label') + var inputGroup = document.createElement('div') + var prepend = document.createElement('div') + var inputGroupText = document.createElement('span') + var usernameInput = document.createElement('input') + dataCsv = { html_settings: { type: 'SimpleForm::FormBuilder', @@ -19,119 +38,156 @@ QUnit.module('Validate SimpleForm Bootstrap 4', { wrapper_class: 'form-group' }, validators: { - 'user[name]': { presence: [{ message: 'must be present' }], format: [{ message: 'is invalid', 'with': { options: 'g', source: '\\d+' } }] }, + 'user[name]': { presence: [{ message: 'must be present' }], format: [{ message: 'is invalid', with: { options: 'g', source: '\\d+' } }] }, 'user[username]': { presence: [{ message: 'must be present' }] }, 'user[password]': { presence: [{ message: 'must be present' }] } } } - $('#qunit-fixture') - .append( - $('', { - action: '/users', - 'data-client-side-validations': JSON.stringify(dataCsv), - method: 'post', - id: 'new_user' - }) - .append( - $('
', { 'class': 'form-group' }) - .append( - $('')) - .append( - $('', { 'class': 'form-control', name: 'user[name]', id: 'user_name', type: 'text' }))) - .append( - $('
', { 'class': 'form-group' }) - .append( - $('')) - .append( - $('', { 'class': 'form-control', name: 'user[password]', id: 'user_password', type: 'password' })) - .append( - $('', { 'class': 'form-text text-muted', text: 'Minimum 8 characters' }))) - .append( - $('
', { 'class': 'form-group' }) - .append( - $('')) - .append( - $('
', { 'class': 'input-group' }) - .append( - $('
', { 'class': 'input-group-prepend' }) - .append( - $('', { 'class': 'input-group-text', text: '@' }))) - .append( - $('', { 'class': 'form-control', name: 'user[username]', id: 'user_username', type: 'text' }))))) - - $('form#new_user').validate() + form.action = '/users' + form.dataset.clientSideValidations = JSON.stringify(dataCsv) + form.method = 'post' + form.id = 'new_user' + + nameGroup.className = 'form-group' + nameLabel.htmlFor = 'user_name' + nameLabel.className = 'string form-control-label' + nameLabel.textContent = 'Name' + nameInput.className = 'form-control' + nameInput.name = 'user[name]' + nameInput.id = 'user_name' + nameInput.type = 'text' + + passwordGroup.className = 'form-group' + passwordLabel.htmlFor = 'user_password' + passwordLabel.className = 'string form-control-label' + passwordLabel.textContent = 'Password' + passwordInput.className = 'form-control' + passwordInput.name = 'user[password]' + passwordInput.id = 'user_password' + passwordInput.type = 'password' + passwordHelp.className = 'form-text text-muted' + passwordHelp.textContent = 'Minimum 8 characters' + + usernameGroup.className = 'form-group' + usernameLabel.htmlFor = 'user_username' + usernameLabel.className = 'string control-label' + usernameLabel.textContent = 'Username' + inputGroup.className = 'input-group' + prepend.className = 'input-group-prepend' + inputGroupText.className = 'input-group-text' + inputGroupText.textContent = '@' + usernameInput.className = 'form-control' + usernameInput.name = 'user[username]' + usernameInput.id = 'user_username' + usernameInput.type = 'text' + + nameGroup.appendChild(nameLabel) + nameGroup.appendChild(nameInput) + + passwordGroup.appendChild(passwordLabel) + passwordGroup.appendChild(passwordInput) + passwordGroup.appendChild(passwordHelp) + + prepend.appendChild(inputGroupText) + inputGroup.appendChild(prepend) + inputGroup.appendChild(usernameInput) + usernameGroup.appendChild(usernameLabel) + usernameGroup.appendChild(inputGroup) + + form.appendChild(nameGroup) + form.appendChild(passwordGroup) + form.appendChild(usernameGroup) + fixture.appendChild(form) + + ClientSideValidations.validate(form) + }, + + afterEach: function () { + document.getElementById('qunit-fixture').replaceChildren() } }) -var wrappers = ['horizontal_form', 'vertical_form', 'inline_form'] - -for (var i = 0; i < wrappers.length; i++) { - var wrapper = wrappers[i] - +for (const wrapper of wrappers) { QUnit.test(wrapper + ' - Validate error attaching and detaching', function (assert) { - var form = $('form#new_user') - var input = form.find('input#user_name') - var label = $('label[for="user_name"]') - form[0].ClientSideValidations.settings.html_settings.wrapper = wrapper - - input.trigger('focusout') - assert.ok(input.parent().hasClass('form-group-invalid')) - assert.ok(label.parent().hasClass('form-group-invalid')) - assert.ok(input.parent().find('div.invalid-feedback:contains("must be present")')[0]) - - input.val('abc') - input.trigger('change') - input.trigger('focusout') - assert.ok(input.parent().hasClass('form-group-invalid')) - assert.ok(input.parent().find('div.invalid-feedback:contains("is invalid")')[0]) - assert.ok(input.hasClass('is-invalid')) - - input.val('123') - input.trigger('change') - input.trigger('focusout') - assert.notOk(input.parent().parent().hasClass('form-group-invalid')) - assert.notOk(input.parent().parent().find('span.help-inline')[0]) - assert.notOk(input.hasClass('is-invalid')) + var form = document.getElementById('new_user') + var input = document.getElementById('user_name') + var label = form.querySelector('label[for="user_name"]') + var wrapperElement = input.parentElement + + form.ClientSideValidations.settings.html_settings.wrapper = wrapper + + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.ok(wrapperElement.classList.contains('form-group-invalid')) + assert.ok(label.parentElement.classList.contains('form-group-invalid')) + assert.ok(wrapperElement.querySelector('div.invalid-feedback').textContent.includes('must be present')) + + input.value = 'abc' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.ok(wrapperElement.classList.contains('form-group-invalid')) + assert.ok(wrapperElement.querySelector('div.invalid-feedback').textContent.includes('is invalid')) + assert.ok(input.classList.contains('is-invalid')) + + input.value = '123' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.notOk(wrapperElement.classList.contains('form-group-invalid')) + assert.notOk(wrapperElement.querySelector('div.invalid-feedback')) + assert.notOk(input.classList.contains('is-invalid')) }) QUnit.test(wrapper + ' - Validate pre-existing error blocks are re-used', function (assert) { - var form = $('form#new_user'); var input = form.find('input#user_name') - var label = $('label[for="user_name"]') - form[0].ClientSideValidations.settings.html_settings.wrapper = wrapper - - input.parent().append($('
Error from Server')) - assert.ok(input.parent().find('div.invalid-feedback:contains("Error from Server")')[0]) - input.val('abc') - input.trigger('change') - input.trigger('focusout') - assert.ok(input.parent().hasClass('form-group-invalid')) - assert.ok(label.parent().hasClass('form-group-invalid')) - assert.ok(input.parent().find('div.invalid-feedback:contains("is invalid")').length === 1) - assert.ok(form.find('div.invalid-feedback').length === 1) + var form = document.getElementById('new_user') + var input = document.getElementById('user_name') + var label = form.querySelector('label[for="user_name"]') + var wrapperElement = input.parentElement + var errorElement = document.createElement('div') + + form.ClientSideValidations.settings.html_settings.wrapper = wrapper + + errorElement.className = 'invalid-feedback' + errorElement.textContent = 'Error from Server' + wrapperElement.appendChild(errorElement) + + assert.ok(wrapperElement.querySelector('div.invalid-feedback').textContent.includes('Error from Server')) + + input.value = 'abc' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + + assert.ok(wrapperElement.classList.contains('form-group-invalid')) + assert.ok(label.parentElement.classList.contains('form-group-invalid')) + assert.equal(wrapperElement.querySelectorAll('div.invalid-feedback').length, 1) + assert.equal(form.querySelectorAll('div.invalid-feedback').length, 1) + assert.ok(wrapperElement.querySelector('div.invalid-feedback').textContent.includes('is invalid')) }) QUnit.test(wrapper + ' - Validate input-group', function (assert) { - var form = $('form#new_user'); var input = form.find('input#user_username') - form[0].ClientSideValidations.settings.html_settings.wrapper = wrapper - - input.trigger('change') - input.trigger('focusout') - assert.ok(input.closest('.input-group-prepend').find('div.invalid-feedback').length === 0) - assert.ok(input.closest('.input-group').find('div.invalid-feedback').length === 1) - - input.val('abc') - input.trigger('change') - input.trigger('focusout') - assert.ok(input.closest('.input-group').find('div.invalid-feedback').length === 0) + var form = document.getElementById('new_user') + var input = document.getElementById('user_username') + var inputGroup = input.closest('.input-group') + + form.ClientSideValidations.settings.html_settings.wrapper = wrapper + + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.equal(inputGroup.querySelectorAll('.input-group-prepend .invalid-feedback').length, 0) + assert.equal(inputGroup.querySelectorAll('div.invalid-feedback').length, 1) + + input.value = 'abc' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.equal(inputGroup.querySelectorAll('div.invalid-feedback').length, 0) }) QUnit.test(wrapper + ' - Inserts before form texts', function (assert) { - var form = $('form#new_user') - var input = form.find('input#user_password') - form[0].ClientSideValidations.settings.html_settings.wrapper = wrapper + var form = document.getElementById('new_user') + var input = document.getElementById('user_password') + + form.ClientSideValidations.settings.html_settings.wrapper = wrapper - input.trigger('focusout') - assert.ok(input.parent().find('.invalid-feedback:contains("must be present") + .form-text')[0]) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.ok(input.parentElement.querySelector('.invalid-feedback + .form-text')) }) } diff --git a/test/javascript/public/test/form_builders/validateSimpleFormBootstrap5.js b/test/javascript/public/test/form_builders/validateSimpleFormBootstrap5.js index 7417547..f7d8a27 100644 --- a/test/javascript/public/test/form_builders/validateSimpleFormBootstrap5.js +++ b/test/javascript/public/test/form_builders/validateSimpleFormBootstrap5.js @@ -1,3 +1,6 @@ +var currentFormBuilder +var wrappers = ['horizontal_form', 'vertical_form', 'inline_form'] + QUnit.module('Validate SimpleForm Bootstrap 5', { before: function () { currentFormBuilder = window.ClientSideValidations.formBuilders['SimpleForm::FormBuilder'] @@ -9,6 +12,21 @@ QUnit.module('Validate SimpleForm Bootstrap 5', { }, beforeEach: function () { + var fixture = document.getElementById('qunit-fixture') + var form = document.createElement('form') + var nameGroup = document.createElement('div') + var nameLabel = document.createElement('label') + var nameInput = document.createElement('input') + var passwordGroup = document.createElement('div') + var passwordLabel = document.createElement('label') + var passwordInput = document.createElement('input') + var passwordHelp = document.createElement('div') + var usernameGroup = document.createElement('div') + var usernameLabel = document.createElement('label') + var inputGroup = document.createElement('div') + var inputGroupText = document.createElement('span') + var usernameInput = document.createElement('input') + dataCsv = { html_settings: { type: 'SimpleForm::FormBuilder', @@ -19,117 +37,154 @@ QUnit.module('Validate SimpleForm Bootstrap 5', { wrapper_class: 'mb-3' }, validators: { - 'user[name]': { presence: [{ message: 'must be present' }], format: [{ message: 'is invalid', 'with': { options: 'g', source: '\\d+' } }] }, + 'user[name]': { presence: [{ message: 'must be present' }], format: [{ message: 'is invalid', with: { options: 'g', source: '\\d+' } }] }, 'user[username]': { presence: [{ message: 'must be present' }] }, 'user[password]': { presence: [{ message: 'must be present' }] } } } - $('#qunit-fixture') - .append( - $('', { - action: '/users', - 'data-client-side-validations': JSON.stringify(dataCsv), - method: 'post', - id: 'new_user' - }) - .append( - $('
', { 'class': 'mb-3' }) - .append( - $('')) - .append( - $('', { 'class': 'form-control', name: 'user[name]', id: 'user_name', type: 'text' }))) - .append( - $('
', { 'class': 'mb-3' }) - .append( - $('')) - .append( - $('', { 'class': 'form-control', name: 'user[password]', id: 'user_password', type: 'password' })) - .append( - $('
', { 'class': 'form-text', text: 'Minimum 8 characters' }))) - .append( - $('
', { 'class': 'mb-3' }) - .append( - $('')) - .append( - $('
', { 'class': 'input-group' }) - .append( - $('', { 'class': 'input-group-text', text: '@' })) - .append( - $('', { 'class': 'form-control', name: 'user[username]', id: 'user_username', type: 'text' }))))) - - $('form#new_user').validate() + form.action = '/users' + form.dataset.clientSideValidations = JSON.stringify(dataCsv) + form.method = 'post' + form.id = 'new_user' + + nameGroup.className = 'mb-3' + nameLabel.htmlFor = 'user_name' + nameLabel.className = 'string form-label' + nameLabel.textContent = 'Name' + nameInput.className = 'form-control' + nameInput.name = 'user[name]' + nameInput.id = 'user_name' + nameInput.type = 'text' + + passwordGroup.className = 'mb-3' + passwordLabel.htmlFor = 'user_password' + passwordLabel.className = 'string form-label' + passwordLabel.textContent = 'Password' + passwordInput.className = 'form-control' + passwordInput.name = 'user[password]' + passwordInput.id = 'user_password' + passwordInput.type = 'password' + passwordHelp.className = 'form-text' + passwordHelp.textContent = 'Minimum 8 characters' + + usernameGroup.className = 'mb-3' + usernameLabel.htmlFor = 'user_username' + usernameLabel.className = 'string form-label' + usernameLabel.textContent = 'Username' + inputGroup.className = 'input-group' + inputGroupText.className = 'input-group-text' + inputGroupText.textContent = '@' + usernameInput.className = 'form-control' + usernameInput.name = 'user[username]' + usernameInput.id = 'user_username' + usernameInput.type = 'text' + + nameGroup.appendChild(nameLabel) + nameGroup.appendChild(nameInput) + + passwordGroup.appendChild(passwordLabel) + passwordGroup.appendChild(passwordInput) + passwordGroup.appendChild(passwordHelp) + + inputGroup.appendChild(inputGroupText) + inputGroup.appendChild(usernameInput) + usernameGroup.appendChild(usernameLabel) + usernameGroup.appendChild(inputGroup) + + form.appendChild(nameGroup) + form.appendChild(passwordGroup) + form.appendChild(usernameGroup) + fixture.appendChild(form) + + ClientSideValidations.validate(form) + }, + + afterEach: function () { + document.getElementById('qunit-fixture').replaceChildren() } }) -var wrappers = ['horizontal_form', 'vertical_form', 'inline_form'] - -for (var i = 0; i < wrappers.length; i++) { - var wrapper = wrappers[i] - +for (const wrapper of wrappers) { QUnit.test(wrapper + ' - Validate error attaching and detaching', function (assert) { - var form = $('form#new_user') - var input = form.find('input#user_name') - var label = $('label[for="user_name"]') - form[0].ClientSideValidations.settings.html_settings.wrapper = wrapper - - input.trigger('focusout') - assert.ok(input.parent().hasClass('form-group-invalid')) - assert.ok(label.parent().hasClass('form-group-invalid')) - assert.ok(input.parent().find('div.invalid-feedback:contains("must be present")')[0]) - - input.val('abc') - input.trigger('change') - input.trigger('focusout') - assert.ok(input.parent().hasClass('form-group-invalid')) - assert.ok(input.parent().find('div.invalid-feedback:contains("is invalid")')[0]) - assert.ok(input.hasClass('is-invalid')) - - input.val('123') - input.trigger('change') - input.trigger('focusout') - assert.notOk(input.parent().parent().hasClass('form-group-invalid')) - assert.notOk(input.parent().parent().find('span.help-inline')[0]) - assert.notOk(input.hasClass('is-invalid')) + var form = document.getElementById('new_user') + var input = document.getElementById('user_name') + var label = form.querySelector('label[for="user_name"]') + var wrapperElement = input.parentElement + + form.ClientSideValidations.settings.html_settings.wrapper = wrapper + + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.ok(wrapperElement.classList.contains('form-group-invalid')) + assert.ok(label.parentElement.classList.contains('form-group-invalid')) + assert.ok(wrapperElement.querySelector('div.invalid-feedback').textContent.includes('must be present')) + + input.value = 'abc' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.ok(wrapperElement.classList.contains('form-group-invalid')) + assert.ok(wrapperElement.querySelector('div.invalid-feedback').textContent.includes('is invalid')) + assert.ok(input.classList.contains('is-invalid')) + + input.value = '123' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.notOk(wrapperElement.classList.contains('form-group-invalid')) + assert.notOk(wrapperElement.querySelector('div.invalid-feedback')) + assert.notOk(input.classList.contains('is-invalid')) }) QUnit.test(wrapper + ' - Validate pre-existing error blocks are re-used', function (assert) { - var form = $('form#new_user'); var input = form.find('input#user_name') - var label = $('label[for="user_name"]') - form[0].ClientSideValidations.settings.html_settings.wrapper = wrapper - - input.parent().append($('
Error from Server')) - assert.ok(input.parent().find('div.invalid-feedback:contains("Error from Server")')[0]) - input.val('abc') - input.trigger('change') - input.trigger('focusout') - assert.ok(input.parent().hasClass('form-group-invalid')) - assert.ok(label.parent().hasClass('form-group-invalid')) - assert.ok(input.parent().find('div.invalid-feedback:contains("is invalid")').length === 1) - assert.ok(form.find('div.invalid-feedback').length === 1) + var form = document.getElementById('new_user') + var input = document.getElementById('user_name') + var label = form.querySelector('label[for="user_name"]') + var wrapperElement = input.parentElement + var errorElement = document.createElement('div') + + form.ClientSideValidations.settings.html_settings.wrapper = wrapper + + errorElement.className = 'invalid-feedback' + errorElement.textContent = 'Error from Server' + wrapperElement.appendChild(errorElement) + + assert.ok(wrapperElement.querySelector('div.invalid-feedback').textContent.includes('Error from Server')) + + input.value = 'abc' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + + assert.ok(wrapperElement.classList.contains('form-group-invalid')) + assert.ok(label.parentElement.classList.contains('form-group-invalid')) + assert.equal(wrapperElement.querySelectorAll('div.invalid-feedback').length, 1) + assert.equal(form.querySelectorAll('div.invalid-feedback').length, 1) + assert.ok(wrapperElement.querySelector('div.invalid-feedback').textContent.includes('is invalid')) }) QUnit.test(wrapper + ' - Validate input-group', function (assert) { - var form = $('form#new_user'); var input = form.find('input#user_username') - form[0].ClientSideValidations.settings.html_settings.wrapper = wrapper - - input.trigger('change') - input.trigger('focusout') - assert.ok(input.closest('.input-group-prepend').find('div.invalid-feedback').length === 0) - assert.ok(input.closest('.input-group').find('div.invalid-feedback').length === 1) - - input.val('abc') - input.trigger('change') - input.trigger('focusout') - assert.ok(input.closest('.input-group').find('div.invalid-feedback').length === 0) + var form = document.getElementById('new_user') + var input = document.getElementById('user_username') + var inputGroup = input.closest('.input-group') + + form.ClientSideValidations.settings.html_settings.wrapper = wrapper + + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.equal(inputGroup.querySelectorAll('.input-group-text .invalid-feedback').length, 0) + assert.equal(inputGroup.querySelectorAll('div.invalid-feedback').length, 1) + + input.value = 'abc' + input.dispatchEvent(new Event('change', { bubbles: true })) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.equal(inputGroup.querySelectorAll('div.invalid-feedback').length, 0) }) QUnit.test(wrapper + ' - Inserts before form texts', function (assert) { - var form = $('form#new_user') - var input = form.find('input#user_password') - form[0].ClientSideValidations.settings.html_settings.wrapper = wrapper + var form = document.getElementById('new_user') + var input = document.getElementById('user_password') + + form.ClientSideValidations.settings.html_settings.wrapper = wrapper - input.trigger('focusout') - assert.ok(input.parent().find('.invalid-feedback:contains("must be present") + .form-text')[0]) + input.dispatchEvent(new Event('focusout', { bubbles: true })) + assert.ok(input.parentElement.querySelector('.invalid-feedback + .form-text')) }) } diff --git a/test/javascript/public/test/settings.js b/test/javascript/public/test/settings.js index 0b40f98..0ab0669 100644 --- a/test/javascript/public/test/settings.js +++ b/test/javascript/public/test/settings.js @@ -1,18 +1 @@ QUnit.config.autostart = window.location.search.search('autostart=false') < 0 - -/* Hijacks normal form submit; lets it submit to an iframe to prevent - * navigating away from the test suite - */ -$(document).on('submit', function (e) { - if (!e.isDefaultPrevented()) { - var form = $(e.target) - var action = form.attr('action') - var name = 'form-frame' + jQuery.guid++ - var iframe = $('