├── app ├── .gitkeep └── components │ ├── bs-form.js │ └── bs-form │ └── element.js ├── addon ├── .gitkeep └── components │ ├── bs-form.js │ └── bs-form │ └── element.js ├── vendor └── .gitkeep ├── tests ├── unit │ └── .gitkeep ├── integration │ ├── .gitkeep │ └── components │ │ └── bs-form-element-test.js ├── dummy │ ├── app │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── models │ │ │ └── .gitkeep │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── components │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ └── application.js │ │ ├── styles │ │ │ └── app.css │ │ ├── router.js │ │ ├── templates │ │ │ └── application.hbs │ │ ├── app.js │ │ ├── index.html │ │ └── deprecation-workflow.js │ ├── public │ │ └── robots.txt │ └── config │ │ ├── optional-features.json │ │ ├── targets.js │ │ ├── ember-cli-update.json │ │ ├── environment.js │ │ └── ember-try.js ├── test-helper.js ├── index.html └── helpers │ └── index.js ├── .watchmanconfig ├── .template-lintrc.js ├── index.js ├── .stylelintignore ├── .stylelintrc.js ├── config └── environment.js ├── renovate.json ├── .prettierignore ├── .ember-cli ├── .gitignore ├── .prettierrc.js ├── .editorconfig ├── .npmignore ├── testem.js ├── CONTRIBUTING.md ├── ember-cli-build.js ├── LICENSE.md ├── .github └── workflows │ └── ci.yml ├── RELEASE.md ├── README.md ├── eslint.config.mjs ├── package.json └── CHANGELOG.md /app/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addon/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["dist"] 3 | } 4 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | name: require('./package').name, 5 | }; 6 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | # unconventional files 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | -------------------------------------------------------------------------------- /app/components/bs-form.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-bootstrap-changeset-validations/components/bs-form'; 2 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: ['stylelint-config-standard'], 5 | }; 6 | -------------------------------------------------------------------------------- /app/components/bs-form/element.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-bootstrap-changeset-validations/components/bs-form/element'; 2 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (/* environment, appConfig */) { 4 | return {}; 5 | }; 6 | -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- 1 | /* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */ 2 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["local>kaliber5/renovate-config:ember-addon"] 4 | } 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | 7 | # misc 8 | /coverage/ 9 | !.* 10 | .*/ 11 | /pnpm-lock.yaml 12 | ember-cli-update.json 13 | *.html 14 | -------------------------------------------------------------------------------- /tests/dummy/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions', 7 | ]; 8 | 9 | module.exports = { 10 | browsers, 11 | }; 12 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript 4 | rather than JavaScript by default, when a TypeScript version of a given blueprint is available. 5 | */ 6 | "isTypeScriptProject": false 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /declarations/ 4 | 5 | # dependencies 6 | /node_modules/ 7 | 8 | # misc 9 | /.env* 10 | /.pnp* 11 | /.eslintcache 12 | /coverage/ 13 | /npm-debug.log* 14 | /testem.log 15 | /yarn-error.log 16 | 17 | # broccoli-debug 18 | /DEBUG/ 19 | -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'dummy/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () {}); 10 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | plugins: ['prettier-plugin-ember-template-tag'], 5 | overrides: [ 6 | { 7 | files: '*.{js,gjs,ts,gts,mjs,mts,cjs,cts}', 8 | options: { 9 | singleQuote: true, 10 | templateSingleQuote: false, 11 | }, 12 | }, 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # misc 6 | /.editorconfig 7 | /.ember-cli 8 | /.env* 9 | /.eslintcache 10 | /.git/ 11 | /.github/ 12 | /.gitignore 13 | /.prettierignore 14 | /.prettierrc.js 15 | /.stylelintignore 16 | /.stylelintrc.js 17 | /.template-lintrc.js 18 | /.watchmanconfig 19 | /CONTRIBUTING.md 20 | /ember-cli-build.js 21 | /eslint.config.mjs 22 | /testem.js 23 | /tests/ 24 | /tsconfig.declarations.json 25 | /tsconfig.json 26 | /yarn-error.log 27 | /yarn.lock 28 | .gitkeep 29 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'dummy/app'; 2 | import config from 'dummy/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { loadTests } from 'ember-qunit/test-loader'; 7 | import { start, setupEmberOnerrorValidation } from 'ember-qunit'; 8 | 9 | setApplication(Application.create(config.APP)); 10 | 11 | setup(QUnit.assert); 12 | setupEmberOnerrorValidation(); 13 | loadTests(); 14 | start(); 15 | -------------------------------------------------------------------------------- /tests/dummy/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "6.4.0", 7 | "blueprints": [ 8 | { 9 | "name": "addon", 10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output", 11 | "codemodsSource": "ember-addon-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [ 14 | "--pnpm", 15 | "--no-welcome" 16 | ] 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'dummy/config/environment'; 5 | import { importSync, isDevelopingApp, macroCondition } from '@embroider/macros'; 6 | 7 | if (macroCondition(isDevelopingApp())) { 8 | importSync('./deprecation-workflow'); 9 | } 10 | 11 | export default class App extends Application { 12 | modulePrefix = config.modulePrefix; 13 | podModulePrefix = config.podModulePrefix; 14 | Resolver = Resolver; 15 | } 16 | 17 | loadInitializers(App, config.modulePrefix); 18 | -------------------------------------------------------------------------------- /addon/components/bs-form.js: -------------------------------------------------------------------------------- 1 | import { assert } from '@ember/debug'; 2 | import BsForm from 'ember-bootstrap/components/bs-form'; 3 | 4 | export default class BsFormWithChangesetValidationsSupport extends BsForm { 5 | '__ember-bootstrap_subclass' = true; 6 | 7 | get hasValidator() { 8 | return typeof this.model?.validate === 'function'; 9 | } 10 | 11 | async validate(model) { 12 | let m = model; 13 | 14 | assert( 15 | 'Model must be a Changeset instance', 16 | m && typeof m.validate === 'function', 17 | ); 18 | 19 | await m.validate(); 20 | if (!model.get('isValid')) { 21 | throw new Error(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900', 20 | ].filter(Boolean), 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dummy 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | 11 | 12 | 13 | 14 | {{content-for "head-footer"}} 15 | 16 | 17 | {{content-for "body"}} 18 | 19 | 20 | 21 | 22 | {{content-for "body-footer"}} 23 | 24 | 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | - `git clone ` 6 | - `cd my-addon` 7 | - `pnpm install` 8 | 9 | ## Linting 10 | 11 | - `pnpm lint` 12 | - `pnpm lint:fix` 13 | 14 | ## Running tests 15 | 16 | - `pnpm test` – Runs the test suite on the current Ember version 17 | - `pnpm test:ember --server` – Runs the test suite in "watch mode" 18 | - `pnpm test:ember-compatibility` – Runs the test suite against multiple Ember versions 19 | 20 | ## Running the dummy application 21 | 22 | - `pnpm start` 23 | - Visit the dummy application at [http://localhost:4200](http://localhost:4200). 24 | 25 | For more information on using ember-cli, visit [https://cli.emberjs.com/release/](https://cli.emberjs.com/release/). 26 | -------------------------------------------------------------------------------- /tests/dummy/app/deprecation-workflow.js: -------------------------------------------------------------------------------- 1 | import setupDeprecationWorkflow from 'ember-cli-deprecation-workflow'; 2 | 3 | /** 4 | * Docs: https://github.com/ember-cli/ember-cli-deprecation-workflow 5 | */ 6 | setupDeprecationWorkflow({ 7 | /** 8 | false by default, but if a developer / team wants to be more aggressive about being proactive with 9 | handling their deprecations, this should be set to "true" 10 | */ 11 | throwOnUnhandled: false, 12 | workflow: [ 13 | /* ... handlers ... */ 14 | /* to generate this list, run your app for a while (or run the test suite), 15 | * and then run in the browser console: 16 | * 17 | * deprecationWorkflow.flushDeprecations() 18 | * 19 | * And copy the handlers here 20 | */ 21 | /* example: */ 22 | /* { handler: 'silence', matchId: 'template-action' }, */ 23 | ], 24 | }); 25 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | module.exports = function (defaults) { 6 | const app = new EmberAddon(defaults, { 7 | // Add options here 8 | 'ember-bootstrap': { 9 | bootstrapVersion: 5, 10 | importBootstrapFont: false, 11 | importBootstrapCSS: true, 12 | }, 13 | }); 14 | 15 | /* 16 | This build file specifies the options for the dummy test app of this 17 | addon, located in `/tests/dummy` 18 | This build file does *not* influence how the addon or the app using it 19 | behave. You most likely want to be modifying `./index.js` or app's build file 20 | */ 21 | 22 | const { maybeEmbroider } = require('@embroider/test-setup'); 23 | return maybeEmbroider(app, { 24 | skipBabel: [ 25 | { 26 | package: 'qunit', 27 | }, 28 | ], 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/application.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | import Changeset from 'ember-changeset'; 4 | import { 5 | validatePresence, 6 | validateLength, 7 | } from 'ember-changeset-validations/validators'; 8 | import lookupValidator from 'ember-changeset-validations'; 9 | 10 | class Model { 11 | name = ''; 12 | } 13 | 14 | const Validation = { 15 | name: [validatePresence(true), validateLength({ min: 4 })], 16 | }; 17 | 18 | export default class ApplicationController extends Controller { 19 | changeset; 20 | model = new Model(); 21 | 22 | constructor() { 23 | super(...arguments); 24 | 25 | this.changeset = new Changeset( 26 | this.model, 27 | lookupValidator(Validation), 28 | Validation, 29 | ); 30 | } 31 | 32 | @action 33 | submit() { 34 | window.alert('Submitted!'); 35 | } 36 | 37 | @action 38 | invalid() { 39 | window.alert('Invalid!'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dummy Tests 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | {{content-for "test-head"}} 11 | 12 | 13 | 14 | 15 | 16 | {{content-for "head-footer"}} 17 | {{content-for "test-head-footer"}} 18 | 19 | 20 | {{content-for "body"}} 21 | {{content-for "test-body"}} 22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {{content-for "body-footer"}} 37 | {{content-for "test-body-footer"}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | const ENV = { 5 | modulePrefix: 'dummy', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'history', 9 | EmberENV: { 10 | EXTEND_PROTOTYPES: false, 11 | FEATURES: { 12 | // Here you can enable experimental features on an ember canary build 13 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 14 | }, 15 | }, 16 | 17 | APP: { 18 | // Here you can pass flags/options to your application instance 19 | // when it is created 20 | }, 21 | }; 22 | 23 | if (environment === 'development') { 24 | // ENV.APP.LOG_RESOLVER = true; 25 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 26 | // ENV.APP.LOG_TRANSITIONS = true; 27 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 28 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 29 | } 30 | 31 | if (environment === 'test') { 32 | // Testem prefers this... 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | ENV.APP.autoboot = false; 41 | } 42 | 43 | if (environment === 'production') { 44 | // here you can enable a production-specific feature 45 | } 46 | 47 | return ENV; 48 | }; 49 | -------------------------------------------------------------------------------- /tests/helpers/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | setupApplicationTest as upstreamSetupApplicationTest, 3 | setupRenderingTest as upstreamSetupRenderingTest, 4 | setupTest as upstreamSetupTest, 5 | } from 'ember-qunit'; 6 | 7 | // This file exists to provide wrappers around ember-qunit's 8 | // test setup functions. This way, you can easily extend the setup that is 9 | // needed per test type. 10 | 11 | function setupApplicationTest(hooks, options) { 12 | upstreamSetupApplicationTest(hooks, options); 13 | 14 | // Additional setup for application tests can be done here. 15 | // 16 | // For example, if you need an authenticated session for each 17 | // application test, you could do: 18 | // 19 | // hooks.beforeEach(async function () { 20 | // await authenticateSession(); // ember-simple-auth 21 | // }); 22 | // 23 | // This is also a good place to call test setup functions coming 24 | // from other addons: 25 | // 26 | // setupIntl(hooks, 'en-us'); // ember-intl 27 | // setupMirage(hooks); // ember-cli-mirage 28 | } 29 | 30 | function setupRenderingTest(hooks, options) { 31 | upstreamSetupRenderingTest(hooks, options); 32 | 33 | // Additional setup for rendering tests can be done here. 34 | } 35 | 36 | function setupTest(hooks, options) { 37 | upstreamSetupTest(hooks, options); 38 | 39 | // Additional setup for unit tests can be done here. 40 | } 41 | 42 | export { setupApplicationTest, setupRenderingTest, setupTest }; 43 | -------------------------------------------------------------------------------- /tests/dummy/config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); 5 | 6 | module.exports = async function () { 7 | return { 8 | packageManager: 'pnpm', 9 | scenarios: [ 10 | { 11 | name: 'ember-lts-3.28', 12 | npm: { 13 | devDependencies: { 14 | '@glimmer/component': '^1.1.2', 15 | '@ember/test-waiters': '^3.1.0', 16 | 'ember-bootstrap': '^5.0.0', 17 | 'ember-cli': '~4.12.0', 18 | 'ember-resolver': '^11.0.0', 19 | 'ember-source': '~3.28.0', 20 | }, 21 | }, 22 | }, 23 | { 24 | name: 'ember-lts-4.12', 25 | npm: { 26 | devDependencies: { 27 | 'ember-source': '~4.12.0', 28 | }, 29 | }, 30 | }, 31 | { 32 | name: 'ember-lts-5.12', 33 | npm: { 34 | devDependencies: { 35 | 'ember-source': '~5.12.0', 36 | }, 37 | }, 38 | }, 39 | { 40 | name: 'ember-release', 41 | npm: { 42 | devDependencies: { 43 | 'ember-source': await getChannelURL('release'), 44 | }, 45 | }, 46 | }, 47 | { 48 | name: 'ember-beta', 49 | npm: { 50 | devDependencies: { 51 | 'ember-source': await getChannelURL('beta'), 52 | }, 53 | }, 54 | }, 55 | { 56 | name: 'ember-canary', 57 | npm: { 58 | devDependencies: { 59 | 'ember-source': await getChannelURL('canary'), 60 | }, 61 | }, 62 | }, 63 | { 64 | name: 'ember-changeset-v4', 65 | npm: { 66 | devDependencies: { 67 | 'ember-changeset': '^4.2.0', 68 | 'ember-changeset-validations': '^4.2.0', 69 | }, 70 | }, 71 | }, 72 | embroiderSafe(), 73 | embroiderOptimized(), 74 | ], 75 | }; 76 | }; 77 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | - renovate/* 9 | pull_request: {} 10 | 11 | concurrency: 12 | group: ci-${{ github.head_ref || github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | test: 17 | name: "Tests" 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 10 20 | 21 | steps: 22 | - uses: actions/checkout@v5 23 | - uses: pnpm/action-setup@v4 24 | with: 25 | version: 10 26 | - name: Install Node 27 | uses: actions/setup-node@v6 28 | with: 29 | node-version: 20.x 30 | cache: pnpm 31 | - name: Install Dependencies 32 | run: pnpm install --frozen-lockfile 33 | - name: Lint 34 | run: pnpm lint 35 | - name: Run Tests 36 | run: pnpm test:ember 37 | 38 | floating: 39 | name: "Floating Dependencies" 40 | runs-on: ubuntu-latest 41 | timeout-minutes: 10 42 | 43 | steps: 44 | - uses: actions/checkout@v5 45 | - uses: pnpm/action-setup@v4 46 | with: 47 | version: 10 48 | - uses: actions/setup-node@v6 49 | with: 50 | node-version: 20.x 51 | cache: pnpm 52 | - name: Install Dependencies 53 | run: pnpm install --no-lockfile 54 | - name: Run Tests 55 | run: pnpm test:ember 56 | 57 | try-scenarios: 58 | name: ${{ matrix.try-scenario }} 59 | runs-on: ubuntu-latest 60 | needs: "test" 61 | timeout-minutes: 10 62 | 63 | strategy: 64 | fail-fast: false 65 | matrix: 66 | try-scenario: 67 | - ember-lts-3.28 68 | - ember-lts-4.12 69 | - ember-lts-5.12 70 | - ember-release 71 | - ember-beta 72 | - ember-canary 73 | - ember-changeset-v4 74 | - embroider-safe 75 | - embroider-optimized 76 | 77 | steps: 78 | - uses: actions/checkout@v5 79 | - uses: pnpm/action-setup@v4 80 | with: 81 | version: 10 82 | - name: Install Node 83 | uses: actions/setup-node@v6 84 | with: 85 | node-version: 20.x 86 | cache: pnpm 87 | - name: Install Dependencies 88 | run: pnpm install --frozen-lockfile 89 | - name: Run Tests 90 | run: ./node_modules/.bin/ember try:one ${{ matrix.try-scenario }} 91 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release 2 | 3 | Releases are mostly automated using 4 | [release-it](https://github.com/release-it/release-it/) and 5 | [lerna-changelog](https://github.com/lerna/lerna-changelog/). 6 | 7 | ## Preparation 8 | 9 | Since the majority of the actual release process is automated, the primary 10 | remaining task prior to releasing is confirming that all pull requests that 11 | have been merged since the last release have been labeled with the appropriate 12 | `lerna-changelog` labels and the titles have been updated to ensure they 13 | represent something that would make sense to our users. Some great information 14 | on why this is important can be found at 15 | [keepachangelog.com](https://keepachangelog.com/en/1.0.0/), but the overall 16 | guiding principle here is that changelogs are for humans, not machines. 17 | 18 | When reviewing merged PR's the labels to be used are: 19 | 20 | - breaking - Used when the PR is considered a breaking change. 21 | - enhancement - Used when the PR adds a new feature or enhancement. 22 | - bug - Used when the PR fixes a bug included in a previous release. 23 | - documentation - Used when the PR adds or updates documentation. 24 | - internal - Used for internal changes that still require a mention in the 25 | changelog/release notes. 26 | 27 | ## Release 28 | 29 | Once the prep work is completed, the actual release is straight forward: 30 | 31 | - First ensure that you have `release-it` installed globally, generally done by 32 | using one of the following commands: 33 | 34 | ``` 35 | # using https://volta.sh 36 | volta install release-it 37 | 38 | # using Yarn 39 | yarn global add release-it 40 | 41 | # using npm 42 | npm install --global release-it 43 | ``` 44 | 45 | - Second, ensure that you have installed your projects dependencies: 46 | 47 | ``` 48 | yarn install 49 | ``` 50 | 51 | - And last (but not least 😁) do your release. It requires a 52 | [GitHub personal access token](https://github.com/settings/tokens) as 53 | `$GITHUB_AUTH` environment variable. Only "repo" access is needed; no "admin" 54 | or other scopes are required. 55 | 56 | ``` 57 | export GITHUB_AUTH="f941e0..." 58 | release-it 59 | ``` 60 | 61 | [release-it](https://github.com/release-it/release-it/) manages the actual 62 | release process. It will prompt you to to choose the version number after which 63 | you will have the chance to hand tweak the changelog to be used (for the 64 | `CHANGELOG.md` and GitHub release), then `release-it` continues on to tagging, 65 | pushing the tag and commits, etc. 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ember Bootstrap Changeset Validations 2 | 3 | [![Build Status](https://travis-ci.org/kaliber5/ember-bootstrap-changeset-validations.svg?branch=master)](https://travis-ci.org/kaliber5/ember-bootstrap-changeset-validations) 4 | 5 | This Ember addon adds support for validations based on [ember-changeset](https://github.com/poteto/ember-changeset) to [ember-bootstrap](https://www.ember-bootstrap.com/) forms. 6 | This way your forms are only submitted when the underlying data is valid, otherwise the appropriate bootstrap error 7 | markup will be applied. See the [FormElement documentation](https://www.ember-bootstrap.com/api/classes/Components.FormElement.html) for 8 | further details. 9 | 10 | ## Compatibility 11 | 12 | - Ember Bootstrap v5 or above 13 | - Ember Changeset and Ember Changeset Validations v4 14 | - Ember.js v3.28 or above 15 | - Ember CLI v3.28 or above 16 | - Node.js v20 or above 17 | 18 | ## Installation 19 | 20 | ember install ember-bootstrap-changeset-validations 21 | 22 | You should have installed the ember-bootstrap and ember-changeset addons already. If not install them: 23 | 24 | ``` 25 | ember install ember-bootstrap 26 | ember install ember-changeset 27 | ``` 28 | 29 | You probably also want to install [ember-changeset-validations](https://github.com/poteto/ember-changeset-validations/) 30 | if you do not have a custom validation implementation: 31 | 32 | ``` 33 | ember install ember-changeset-validations 34 | ``` 35 | 36 | ## Usage 37 | 38 | Define your model and its validations as described in [ember-changeset-validations](https://github.com/poteto/ember-changeset-validations/). 39 | Then assign the changeset based on that to your form: 40 | 41 | ```hbs 42 | 43 | 44 | 45 | 50 | Submit 51 | 52 | ``` 53 | 54 | ## Authors 55 | 56 | - [Simon Ihmig](https://github.com/simonihmig) @ [kaliber5](http://www.kaliber5.de) 57 | - [Jeldrik Hanschke](https://github.com/jelhan) 58 | 59 | ## Contributing 60 | 61 | See the [Contributing](CONTRIBUTING.md) guide for details. 62 | 63 | ## Copyright and license 64 | 65 | Code and documentation copyright 2017 kaliber5 GmbH and contributors. Code released under [the MIT license](LICENSE.md). 66 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Debugging: 3 | * https://eslint.org/docs/latest/use/configure/debug 4 | * ---------------------------------------------------- 5 | * 6 | * Print a file's calculated configuration 7 | * 8 | * npx eslint --print-config path/to/file.js 9 | * 10 | * Inspecting the config 11 | * 12 | * npx eslint --inspect-config 13 | * 14 | */ 15 | import globals from 'globals'; 16 | import js from '@eslint/js'; 17 | 18 | import ember from 'eslint-plugin-ember/recommended'; 19 | import eslintConfigPrettier from 'eslint-config-prettier'; 20 | import qunit from 'eslint-plugin-qunit'; 21 | import n from 'eslint-plugin-n'; 22 | 23 | import babelParser from '@babel/eslint-parser'; 24 | 25 | const esmParserOptions = { 26 | ecmaFeatures: { modules: true }, 27 | ecmaVersion: 'latest', 28 | requireConfigFile: false, 29 | babelOptions: { 30 | plugins: [ 31 | ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }], 32 | ], 33 | }, 34 | }; 35 | 36 | export default [ 37 | js.configs.recommended, 38 | eslintConfigPrettier, 39 | ember.configs.base, 40 | ember.configs.gjs, 41 | /** 42 | * Ignores must be in their own object 43 | * https://eslint.org/docs/latest/use/configure/ignore 44 | */ 45 | { 46 | ignores: ['dist/', 'node_modules/', 'coverage/', '!**/.*'], 47 | }, 48 | /** 49 | * https://eslint.org/docs/latest/use/configure/configuration-files#configuring-linter-options 50 | */ 51 | { 52 | linterOptions: { 53 | reportUnusedDisableDirectives: 'error', 54 | }, 55 | }, 56 | { 57 | files: ['**/*.js'], 58 | languageOptions: { 59 | parser: babelParser, 60 | }, 61 | }, 62 | { 63 | files: ['**/*.{js,gjs}'], 64 | languageOptions: { 65 | parserOptions: esmParserOptions, 66 | globals: { 67 | ...globals.browser, 68 | }, 69 | }, 70 | }, 71 | { 72 | files: ['tests/**/*-test.{js,gjs}'], 73 | plugins: { 74 | qunit, 75 | }, 76 | }, 77 | /** 78 | * CJS node files 79 | */ 80 | { 81 | files: [ 82 | '**/*.cjs', 83 | 'config/**/*.js', 84 | 'tests/dummy/config/**/*.js', 85 | 'testem.js', 86 | 'testem*.js', 87 | 'index.js', 88 | '.prettierrc.js', 89 | '.stylelintrc.js', 90 | '.template-lintrc.js', 91 | 'ember-cli-build.js', 92 | ], 93 | plugins: { 94 | n, 95 | }, 96 | 97 | languageOptions: { 98 | sourceType: 'script', 99 | ecmaVersion: 'latest', 100 | globals: { 101 | ...globals.node, 102 | }, 103 | }, 104 | }, 105 | /** 106 | * ESM node files 107 | */ 108 | { 109 | files: ['**/*.mjs'], 110 | plugins: { 111 | n, 112 | }, 113 | 114 | languageOptions: { 115 | sourceType: 'module', 116 | ecmaVersion: 'latest', 117 | parserOptions: esmParserOptions, 118 | globals: { 119 | ...globals.node, 120 | }, 121 | }, 122 | }, 123 | ]; 124 | -------------------------------------------------------------------------------- /addon/components/bs-form/element.js: -------------------------------------------------------------------------------- 1 | import BsFormElement from 'ember-bootstrap/components/bs-form/element'; 2 | import { action, get } from '@ember/object'; 3 | import { isNone, typeOf } from '@ember/utils'; 4 | 5 | export default class BsFormElementWithChangesetValidationsSupport extends BsFormElement { 6 | '__ember-bootstrap_subclass' = true; 7 | 8 | get errors() { 9 | let { model, property } = this.args; 10 | 11 | // must use `get` method to support nested properties 12 | let errors = get(model, `error.${property}.validation`); 13 | 14 | // no messages 15 | if (isNone(errors)) { 16 | return []; 17 | } 18 | 19 | // a single messages 20 | if (typeOf(errors) === 'string') { 21 | return [errors]; 22 | } 23 | 24 | // assume it's an array of messages 25 | return errors; 26 | } 27 | 28 | get hasValidator() { 29 | return typeof this.args.model?.validate === 'function'; 30 | } 31 | 32 | // Ember Changeset does not validate the initial state. Properties are not 33 | // validated until they are set the first time. But Ember Bootstrap may show 34 | // validation results before the property was changed. We need to make sure 35 | // that changeset is validated at that time. 36 | // Ember Bootstrap may show the validation in three cases: 37 | // 1. User triggered one of the events that should cause validation errors to 38 | // be shown (e.g. focus out) by interacting with the form element. 39 | // Ember Bootstrap stores these state in `showOwnValidation` property of 40 | // the form element. 41 | // 2. User submits the form. Ember Bootstrap will show validation errors 42 | // for all form elements in that case. That state is handled by 43 | // `showAllValidations` arguments passed to the form element. 44 | // 3. User passes in a validation error or warning explicilty using 45 | // `customError` or `customWarning` arguments of the form element. 46 | // Ember Bootstrap ensures that the model is valided as part of its submit 47 | // handler. So we can assume that validations are run in second case. Ember 48 | // Bootstrap does not show the validation errors of the model but only the 49 | // custom error and warning if present. So it does not matter if initial 50 | // state is validated or not. That means we only have to handle the first 51 | // case. 52 | // Ember Bootstrap does not provide any API for validation plugins to support 53 | // these needs. We have to override a private method to run the validate 54 | // logic for now. 55 | @action 56 | async showValidationOnHandler(event) { 57 | let validationShowBefore = this.showOwnValidation; 58 | 59 | // run original implementation provided by Ember Bootstrap 60 | super.showValidationOnHandler(event); 61 | 62 | // run initial validation if 63 | // - visibility of validations changed 64 | let canValidate = this.hasValidator && this.args.property; 65 | let validationVisibilityChanged = 66 | !validationShowBefore && this.showOwnValidation; 67 | if (canValidate && validationVisibilityChanged) { 68 | await this.args.model.validate(this.args.property); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-bootstrap-changeset-validations", 3 | "version": "6.0.0", 4 | "description": "This Ember addon adds support for validations based on ember-changeset to ember-bootstrap", 5 | "keywords": [ 6 | "ember-addon", 7 | "ember-bootstrap", 8 | "ember-changeset" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "http://github.com/ember-bootstrap/ember-bootstrap-changeset-validations.git" 13 | }, 14 | "license": "MIT", 15 | "author": "Simon Ihmig ", 16 | "directories": { 17 | "doc": "doc", 18 | "test": "tests" 19 | }, 20 | "scripts": { 21 | "build": "ember build --environment=production", 22 | "format": "prettier . --cache --write", 23 | "lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto", 24 | "lint:css": "stylelint \"**/*.css\"", 25 | "lint:css:fix": "concurrently \"pnpm:lint:css -- --fix\"", 26 | "lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefixColors auto && pnpm format", 27 | "lint:format": "prettier . --cache --check", 28 | "lint:hbs": "ember-template-lint .", 29 | "lint:hbs:fix": "ember-template-lint . --fix", 30 | "lint:js": "eslint . --cache", 31 | "lint:js:fix": "eslint . --fix", 32 | "start": "ember serve", 33 | "test": "concurrently \"pnpm:lint\" \"pnpm:test:*\" --names \"lint,test:\" --prefixColors auto", 34 | "test:ember": "ember test", 35 | "test:ember-compatibility": "ember try:each" 36 | }, 37 | "dependencies": { 38 | "@babel/core": "^7.27.1", 39 | "ember-cli-babel": "^8.2.0", 40 | "ember-cli-htmlbars": "^6.3.0", 41 | "ember-template-imports": "^4.3.0" 42 | }, 43 | "devDependencies": { 44 | "@babel/eslint-parser": "7.28.5", 45 | "@babel/plugin-proposal-decorators": "7.28.0", 46 | "@ember/optional-features": "2.3.0", 47 | "@ember/string": "4.0.1", 48 | "@ember/test-helpers": "5.4.1", 49 | "@embroider/macros": "1.19.5", 50 | "@embroider/test-setup": "4.0.0", 51 | "@eslint/js": "9.39.2", 52 | "@glimmer/component": "2.0.0", 53 | "@glimmer/tracking": "1.1.2", 54 | "@release-it-plugins/lerna-changelog": "8.0.1", 55 | "bootstrap": "5.3.8", 56 | "broccoli-asset-rev": "3.0.0", 57 | "concurrently": "9.2.1", 58 | "ember-auto-import": "2.12.0", 59 | "ember-bootstrap": "6.7.0", 60 | "ember-changeset": "5.0.0", 61 | "ember-changeset-validations": "5.0.0", 62 | "ember-cli": "6.9.1", 63 | "ember-cli-clean-css": "3.0.0", 64 | "ember-cli-dependency-checker": "3.3.3", 65 | "ember-cli-deprecation-workflow": "3.4.0", 66 | "ember-cli-inject-live-reload": "2.1.0", 67 | "ember-cli-sri": "2.1.1", 68 | "ember-cli-terser": "4.0.2", 69 | "ember-focus-trap": "1.1.1", 70 | "ember-load-initializers": "3.0.1", 71 | "ember-page-title": "9.0.3", 72 | "ember-qunit": "9.0.4", 73 | "ember-resolver": "13.1.1", 74 | "ember-source": "6.9.0", 75 | "ember-source-channel-url": "3.0.0", 76 | "ember-template-lint": "7.9.3", 77 | "ember-try": "4.0.0", 78 | "eslint": "9.39.2", 79 | "eslint-config-prettier": "10.1.8", 80 | "eslint-plugin-ember": "12.7.5", 81 | "eslint-plugin-n": "17.23.1", 82 | "eslint-plugin-qunit": "8.2.5", 83 | "globals": "16.5.0", 84 | "loader.js": "4.7.0", 85 | "prettier": "3.7.4", 86 | "prettier-plugin-ember-template-tag": "2.1.2", 87 | "qunit": "2.24.3", 88 | "qunit-dom": "3.5.0", 89 | "release-it": "19.1.0", 90 | "stylelint": "16.26.1", 91 | "stylelint-config-standard": "39.0.1", 92 | "webpack": "5.104.1" 93 | }, 94 | "peerDependencies": { 95 | "ember-bootstrap": ">=5.0.0", 96 | "ember-changeset-validations": ">=4.0.0", 97 | "ember-source": ">=3.28.0" 98 | }, 99 | "engines": { 100 | "node": "20.* || 22.* || >= 24" 101 | }, 102 | "publishConfig": { 103 | "registry": "https://registry.npmjs.org" 104 | }, 105 | "ember": { 106 | "edition": "octane" 107 | }, 108 | "ember-addon": { 109 | "configPath": "tests/dummy/config", 110 | "after": "ember-bootstrap" 111 | }, 112 | "release-it": { 113 | "plugins": { 114 | "@release-it-plugins/lerna-changelog": { 115 | "infile": "CHANGELOG.md", 116 | "launchEditor": true 117 | } 118 | }, 119 | "git": { 120 | "tagName": "v${version}" 121 | }, 122 | "github": { 123 | "release": true, 124 | "tokenRef": "GITHUB_AUTH" 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v6.0.0 (2025-09-30) 2 | 3 | #### :boom: Breaking Change 4 | 5 | - [#75](https://github.com/ember-bootstrap/ember-bootstrap-changeset-validations/pull/75) drop support for Ember < 3.28 and Ember classic ([@jelhan](https://github.com/jelhan)) 6 | - [#74](https://github.com/ember-bootstrap/ember-bootstrap-changeset-validations/pull/74) drop support for Node < 20 ([@jelhan](https://github.com/jelhan)) 7 | 8 | #### :rocket: Enhancement 9 | 10 | - [#109](https://github.com/ember-bootstrap/ember-bootstrap-changeset-validations/pull/109) declare Ember Bootstrap and Ember Changeset Validations compatbility per peer dependencies ([@jelhan](https://github.com/jelhan)) 11 | 12 | #### :house: Internal 13 | 14 | - [#106](https://github.com/ember-bootstrap/ember-bootstrap-changeset-validations/pull/106) Upgrade with Ember CLI v6.4 blueprints ([@jelhan](https://github.com/jelhan)) 15 | - [#91](https://github.com/ember-bootstrap/ember-bootstrap-changeset-validations/pull/91) Upgrade with Ember CLI v5.12 blueprints ([@jelhan](https://github.com/jelhan)) 16 | - [#89](https://github.com/ember-bootstrap/ember-bootstrap-changeset-validations/pull/89) ensure test coverage with ember-changeset v4 ([@jelhan](https://github.com/jelhan)) 17 | - [#86](https://github.com/ember-bootstrap/ember-bootstrap-changeset-validations/pull/86) Upgrade with Ember CLI 4.12 blueprints ([@jelhan](https://github.com/jelhan)) 18 | - [#76](https://github.com/ember-bootstrap/ember-bootstrap-changeset-validations/pull/76) ensure test coverage with Ember 4.12 ([@jelhan](https://github.com/jelhan)) 19 | - [#77](https://github.com/ember-bootstrap/ember-bootstrap-changeset-validations/pull/77) migrate to pnpm ([@jelhan](https://github.com/jelhan)) 20 | 21 | #### Committers: 1 22 | 23 | - Jeldrik Hanschke ([@jelhan](https://github.com/jelhan)) 24 | 25 | ## v5.0.0 (2022-04-29) 26 | 27 | #### :boom: Breaking Change 28 | 29 | - [#43](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/43) Drop support for ember-bootstrap v4 ([@simonihmig](https://github.com/simonihmig)) 30 | - [#41](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/41) Update to Ember 4.2, drop support for Ember < 3.24, node 10, ember-changeset < 4 ([@simonihmig](https://github.com/simonihmig)) 31 | 32 | #### :rocket: Enhancement 33 | 34 | - [#42](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/42) Convert extended Form to native class, providing compatibility with ember-bootstrap 5.1+ ([@simonihmig](https://github.com/simonihmig)) 35 | - [#41](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/41) Update to Ember 4.2, drop support for Ember < 3.24, node 10, ember-changeset < 4 ([@simonihmig](https://github.com/simonihmig)) 36 | 37 | #### Committers: 1 38 | 39 | - Simon Ihmig ([@simonihmig](https://github.com/simonihmig)) 40 | 41 | ## v4.0.0 (2020-12-04) 42 | 43 | #### :boom: Breaking Change 44 | 45 | - [#34](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/34) drop support for Ember Bootstrap <= v4.4 ([@jelhan](https://github.com/jelhan)) 46 | 47 | #### :rocket: Enhancement 48 | 49 | - [#34](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/34) support Ember Bootstrap v4.5 and above ([@jelhan](https://github.com/jelhan)) 50 | 51 | #### :memo: Documentation 52 | 53 | - [#31](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/31) use angle-bracket invocation in README and some other improvements ([@simonihmig](https://github.com/simonihmig)) 54 | 55 | #### :house: Internal 56 | 57 | - [#35](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/35) migrate CI from TravisCI to GitHub actions ([@jelhan](https://github.com/jelhan)) 58 | 59 | #### Committers: 2 60 | 61 | - Jeldrik Hanschke ([@jelhan](https://github.com/jelhan)) 62 | - Simon Ihmig ([@simonihmig](https://github.com/simonihmig)) 63 | 64 | ## v3.1.2 (2020-08-13) 65 | 66 | #### :bug: Bug Fix 67 | 68 | - [#29](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/29) support multiple validation errors ([@basz](https://github.com/basz)) 69 | 70 | #### Committers: 1 71 | 72 | - Bas Kamer ([@basz](https://github.com/basz)) 73 | 74 | ## v3.1.1 (2020-08-02) 75 | 76 | #### :bug: Bug Fix 77 | 78 | - [#28](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/28) fix validation for nested properties ([@basz](https://github.com/basz)) 79 | 80 | #### Committers: 1 81 | 82 | - Bas Kamer ([@basz](https://github.com/basz)) 83 | 84 | ## v3.1.0 (2020-07-20) 85 | 86 | #### :bug: Bug Fix 87 | 88 | - [#27](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/27) support latest version of ember-changeset / ember-changeset-validations ([@jelhan](https://github.com/jelhan)) 89 | - [#25](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/25) hide deprecation notice when subclassing ember bootstrap classes ([@basz](https://github.com/basz)) 90 | 91 | #### Committers: 2 92 | 93 | - Bas Kamer ([@basz](https://github.com/basz)) 94 | - Jeldrik Hanschke ([@jelhan](https://github.com/jelhan)) 95 | 96 | ## v3.0.0 (2020-03-31) 97 | 98 | #### :boom: Breaking Change 99 | 100 | - [#19](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/19) upgrade ember changeset to v3 ([@jelhan](https://github.com/jelhan)) 101 | - [#18](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/18) upgrade dependencies except ember-changeset ([@jelhan](https://github.com/jelhan)) 102 | 103 | #### :house: Internal 104 | 105 | - [#20](https://github.com/kaliber5/ember-bootstrap-changeset-validations/pull/20) automate releases with release-it and lerna-changelog ([@jelhan](https://github.com/jelhan)) 106 | 107 | #### Committers: 1 108 | 109 | - Jeldrik Hanschke ([@jelhan](https://github.com/jelhan)) 110 | -------------------------------------------------------------------------------- /tests/integration/components/bs-form-element-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { 4 | render, 5 | triggerEvent, 6 | fillIn, 7 | focus, 8 | blur, 9 | findAll, 10 | } from '@ember/test-helpers'; 11 | import hbs from 'htmlbars-inline-precompile'; 12 | import { 13 | validatePresence, 14 | validateLength, 15 | } from 'ember-changeset-validations/validators'; 16 | 17 | module('Integration | Component | bs form element', function (hooks) { 18 | setupRenderingTest(hooks); 19 | 20 | const validation = { 21 | name: [validatePresence(true), validateLength({ min: 4 })], 22 | }; 23 | 24 | const nestedValidation = { 25 | nested: { 26 | name: [validatePresence(true), validateLength({ min: 4 })], 27 | }, 28 | }; 29 | 30 | test('form is submitted if valid and validation success shown', async function (assert) { 31 | let model = { 32 | name: '1234', 33 | }; 34 | 35 | this.set('model', model); 36 | this.set('validation', validation); 37 | this.submitAction = function () { 38 | assert.step('submit action has been called.'); 39 | }; 40 | this.invalidAction = function () { 41 | assert.ok(false, 'Invalid action must not been called.'); 42 | }; 43 | 44 | await render(hbs` 45 | 46 | 47 | 48 | `); 49 | 50 | await triggerEvent('form', 'submit'); 51 | assert.verifySteps(['submit action has been called.']); 52 | }); 53 | 54 | test('validation errors are shown on submit', async function (assert) { 55 | let model = { 56 | name: '', 57 | }; 58 | 59 | this.set('model', model); 60 | this.set('validation', validation); 61 | this.submitAction = function () { 62 | assert.ok(false, 'submit action must not been called.'); 63 | }; 64 | this.invalidAction = function () { 65 | assert.step('Invalid action has been called.'); 66 | }; 67 | 68 | await render(hbs` 69 | 70 | 71 | 72 | `); 73 | 74 | await triggerEvent('form', 'submit'); 75 | assert.dom('input').hasClass('is-invalid', 'input has error class'); 76 | assert.verifySteps(['Invalid action has been called.']); 77 | }); 78 | 79 | test('validation nested errors are shown on submit', async function (assert) { 80 | let model = { 81 | nested: { name: '' }, 82 | }; 83 | 84 | this.set('model', model); 85 | this.set('validation', nestedValidation); 86 | this.submitAction = function () { 87 | assert.ok(false, 'submit action must not been called.'); 88 | }; 89 | this.invalidAction = function () { 90 | assert.step('Invalid action has been called.'); 91 | }; 92 | 93 | await render(hbs` 94 | 95 | 96 | 97 | `); 98 | 99 | await triggerEvent('form', 'submit'); 100 | assert.dom('input').hasClass('is-invalid', 'input has error class'); 101 | assert.verifySteps(['Invalid action has been called.']); 102 | }); 103 | 104 | test('validation errors are shown after blur', async function (assert) { 105 | this.set('model', { name: '' }); 106 | this.set('validation', validation); 107 | 108 | await render(hbs` 109 | 110 | 111 | 112 | `); 113 | assert.dom('input').doesNotHaveClass('is-invalid'); 114 | 115 | await focus('input'); 116 | await blur('input'); 117 | assert.dom('input').hasClass('is-invalid'); 118 | }); 119 | 120 | test('validation success is shown after blur', async function (assert) { 121 | this.set('model', { name: 'Clara' }); 122 | this.set('validation', validation); 123 | 124 | await render(hbs` 125 | 126 | 127 | 128 | `); 129 | assert.dom('input').doesNotHaveClass('is-valid'); 130 | 131 | await focus('input'); 132 | await blur('input'); 133 | assert.dom('input').hasClass('is-valid'); 134 | }); 135 | 136 | test('validation errors are shown after user input', async function (assert) { 137 | this.set('model', { name: '' }); 138 | this.set('validation', validation); 139 | 140 | await render(hbs` 141 | 142 | 143 | 144 | `); 145 | assert.dom('input').doesNotHaveClass('is-invalid'); 146 | 147 | await fillIn('input', 'R'); 148 | assert 149 | .dom('input') 150 | .doesNotHaveClass( 151 | 'is-invalid', 152 | 'validation is not shown while user is typing', 153 | ); 154 | 155 | await blur('input'); 156 | assert 157 | .dom('input') 158 | .hasClass('is-invalid', 'validation error is shown after focus out'); 159 | }); 160 | 161 | test('validation success is shown after user input', async function (assert) { 162 | this.set('model', { name: '' }); 163 | this.set('validation', validation); 164 | 165 | await render(hbs` 166 | 167 | 168 | 169 | `); 170 | assert.dom('input').doesNotHaveClass('is-valid'); 171 | 172 | await fillIn('input', 'Rosa'); 173 | assert 174 | .dom('input') 175 | .doesNotHaveClass( 176 | 'is-valid', 177 | 'validation is not shown while user is typing', 178 | ); 179 | 180 | await blur('input'); 181 | assert 182 | .dom('input') 183 | .hasClass('is-valid', 'validation error is shown after focus out'); 184 | }); 185 | 186 | test('does not break forms which are not using a changeset as model', async function (assert) { 187 | this.set('model', { name: '' }); 188 | this.set('submitAction', () => { 189 | assert.step('submit action has been called'); 190 | }); 191 | 192 | await render(hbs` 193 | 194 | 195 | 196 | `); 197 | assert.dom('input').doesNotHaveClass('is-valid'); 198 | assert.dom('input').doesNotHaveClass('is-invalid'); 199 | 200 | await fillIn('input', 'Rosa'); 201 | await blur('input'); 202 | assert.dom('input').doesNotHaveClass('is-valid'); 203 | assert.dom('input').doesNotHaveClass('is-invalid'); 204 | 205 | await triggerEvent('form', 'submit'); 206 | assert.dom('input').doesNotHaveClass('is-valid'); 207 | assert.dom('input').doesNotHaveClass('is-invalid'); 208 | assert.verifySteps(['submit action has been called']); 209 | }); 210 | 211 | test('does not break for forms which are not having a model at all', async function (assert) { 212 | this.set('submitAction', () => { 213 | assert.step('submit action has been called'); 214 | }); 215 | this.set('noop', () => {}); 216 | 217 | await render(hbs` 218 | 219 | 220 | 221 | `); 222 | assert.dom('input').doesNotHaveClass('is-valid'); 223 | assert.dom('input').doesNotHaveClass('is-invalid'); 224 | 225 | await fillIn('input', 'Rosa'); 226 | await blur('input'); 227 | assert.dom('input').doesNotHaveClass('is-valid'); 228 | assert.dom('input').doesNotHaveClass('is-invalid'); 229 | 230 | await triggerEvent('form', 'submit'); 231 | assert.dom('input').doesNotHaveClass('is-valid'); 232 | assert.dom('input').doesNotHaveClass('is-invalid'); 233 | assert.verifySteps(['submit action has been called']); 234 | }); 235 | 236 | test('invalid-feedback is shown from single validation', async function (assert) { 237 | let model = { 238 | name: '', 239 | }; 240 | 241 | this.set('model', model); 242 | this.set('validation', { 243 | name: validatePresence(true), 244 | }); 245 | 246 | await render(hbs` 247 | 248 | 249 | 250 | `); 251 | 252 | await triggerEvent('form', 'submit'); 253 | assert.dom('.invalid-feedback').hasText("Name can't be blank"); 254 | }); 255 | 256 | test('invalid-feedback is shown in order from multiple validations', async function (assert) { 257 | let model = { 258 | name: '', 259 | }; 260 | 261 | this.set('model', model); 262 | this.set('validation', { 263 | name: [validatePresence(true), validateLength({ min: 4 })], 264 | }); 265 | 266 | await render(hbs` 267 | 268 | 269 | 270 | `); 271 | 272 | await triggerEvent('form', 'submit'); 273 | assert.dom('.invalid-feedback').hasText("Name can't be blank"); 274 | 275 | await fillIn('input', 'R'); 276 | await triggerEvent('form', 'submit'); 277 | assert 278 | .dom('.invalid-feedback') 279 | .hasText('Name is too short (minimum is 4 characters)'); 280 | }); 281 | 282 | test('invalid-feedback is shown (multiple messages) in order from multiple validations', async function (assert) { 283 | let model = { 284 | name: '', 285 | }; 286 | 287 | this.set('model', model); 288 | this.set('validation', { 289 | name: [validatePresence(true), validateLength({ min: 4 })], 290 | }); 291 | 292 | await render(hbs` 293 | 294 | 295 | 296 | `); 297 | 298 | await triggerEvent('form', 'submit'); 299 | 300 | let feedbackElements = findAll('.invalid-feedback'); 301 | let results = Array.from(feedbackElements, (element) => 302 | element.textContent.trim(), 303 | ); 304 | let expected = [ 305 | "Name can't be blank", 306 | 'Name is too short (minimum is 4 characters)', 307 | ]; 308 | 309 | expected.forEach((message) => { 310 | assert.ok(results.includes(message)); 311 | }); 312 | }); 313 | 314 | test('no feedback is shown for nonexistant validations', async function (assert) { 315 | let model = { 316 | name: '', 317 | }; 318 | 319 | this.set('model', model); 320 | this.set('validation', { 321 | nombre: validatePresence(true), 322 | }); 323 | 324 | await render(hbs` 325 | 326 | 327 | 328 | `); 329 | 330 | await triggerEvent('form', 'submit'); 331 | assert.dom('.invalid-feedback').doesNotExist(); 332 | }); 333 | }); 334 | --------------------------------------------------------------------------------